Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/cast/src/cmd/keychain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3758,6 +3758,28 @@ where
}
}

/// Fails early with `requirement` when a Tempo precompile is not active yet: a pre-fork call
/// would succeed as a silent no-op instead of reverting. Prefers the hardfork query and falls
/// back to checking the precompile's code when the RPC lacks the method.
pub(crate) async fn ensure_tempo_precompile_active<P>(
provider: &P,
hardfork: TempoHardfork,
precompile: Address,
requirement: &str,
) -> Result<()>
where
P: Provider<TempoNetwork>,
{
let active = match is_tempo_hardfork_active(provider, hardfork).await {
Ok(active) => active,
Err(_) => !provider.get_code_at(precompile).await?.is_empty(),
};
if !active {
eyre::bail!("{requirement}");
}
Ok(())
}

async fn anvil_tempo_hardfork_active<P>(
provider: &P,
hardfork: TempoHardfork,
Expand Down
32 changes: 9 additions & 23 deletions crates/cast/src/cmd/receive_policy.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::{
cmd::{
keychain::is_tempo_hardfork_active,
keychain::ensure_tempo_precompile_active,
tip20::{resolve_tip20_signer, send_tip20_transaction},
},
tempo::print_payload,
tx::{SendTxOpts, TxParams},
};
use alloy_ens::NameOrAddress;
Expand Down Expand Up @@ -399,16 +400,13 @@ async fn ensure_receive_policy_t6<P>(provider: &P, command: &str) -> Result<()>
where
P: Provider<TempoNetwork>,
{
// Prefer the hardfork query, but if it is unavailable (e.g. an older RPC without the method)
// fall back to checking whether the guard precompile has code.
let active = match is_tempo_hardfork_active(provider, TempoHardfork::T6).await {
Ok(active) => active,
Err(_) => !provider.get_code_at(RECEIVE_POLICY_GUARD_ADDRESS).await?.is_empty(),
};
if !active {
eyre::bail!("{command} requires a Tempo T6-capable ReceivePolicy RPC");
}
Ok(())
ensure_tempo_precompile_active(
provider,
TempoHardfork::T6,
RECEIVE_POLICY_GUARD_ADDRESS,
&format!("{command} requires a Tempo T6-capable ReceivePolicy RPC"),
)
.await
}

async fn burn_receipt(receipt: Bytes, send_tx: SendTxOpts, tx: TxParams) -> Result<()> {
Expand Down Expand Up @@ -624,18 +622,6 @@ fn print_claim_hint(payload: &Value) -> Result<()> {
}
}

fn print_payload<F>(payload: Value, human: F) -> Result<()>
where
F: FnOnce(&Value) -> Result<()>,
{
if shell::is_json() {
print_json_success(payload)?;
} else {
human(&payload)?;
}
Ok(())
}

fn recovery_mode(recovery_authority: Address) -> &'static str {
if recovery_authority == Address::ZERO { "originator" } else { "authority" }
}
Expand Down
36 changes: 12 additions & 24 deletions crates/cast/src/cmd/storage_credits.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use crate::{
cmd::{
keychain::is_tempo_hardfork_active,
keychain::ensure_tempo_precompile_active,
tip20::{resolve_tip20_signer, send_tip20_transaction},
},
tempo::print_payload,
tx::{SendTxOpts, TxParams},
};
use alloy_ens::NameOrAddress;
use clap::{Parser, ValueEnum};
use eyre::Result;
use foundry_cli::{json::print_json_success, opts::RpcOpts, utils::LoadConfig};
use foundry_common::{provider::ProviderBuilder, shell};
use foundry_cli::{opts::RpcOpts, utils::LoadConfig};
use foundry_common::provider::ProviderBuilder;
use foundry_evm::hardfork::TempoHardfork;
use serde_json::{Value, json};
use serde_json::json;
use std::str::FromStr;
use tempo_alloy::TempoNetwork;
use tempo_contracts::precompiles::{IStorageCredits, STORAGE_CREDITS_ADDRESS};
Expand Down Expand Up @@ -206,14 +207,13 @@ async fn ensure_storage_credits_t7<P>(provider: &P, command: &str) -> Result<()>
where
P: alloy_provider::Provider<TempoNetwork>,
{
let active = match is_tempo_hardfork_active(provider, TempoHardfork::T7).await {
Ok(active) => active,
Err(_) => !provider.get_code_at(STORAGE_CREDITS_ADDRESS).await?.is_empty(),
};
if !active {
eyre::bail!("{command} requires a Tempo T7-capable StorageCredits RPC");
}
Ok(())
ensure_tempo_precompile_active(
provider,
TempoHardfork::T7,
STORAGE_CREDITS_ADDRESS,
&format!("{command} requires a Tempo T7-capable StorageCredits RPC"),
)
.await
}

/// Gate a write command on T7 before signing: on pre-T7 the precompile address is an empty account,
Expand All @@ -224,18 +224,6 @@ async fn ensure_send_storage_credits_t7(send_tx: &SendTxOpts, command: &str) ->
ensure_storage_credits_t7(&provider, command).await
}

fn print_payload<F>(payload: Value, human: F) -> Result<()>
where
F: FnOnce(&Value) -> Result<()>,
{
if shell::is_json() {
print_json_success(payload)?;
} else {
human(&payload)?;
}
Ok(())
}

impl CreditMode {
const fn to_sol(self) -> IStorageCredits::Mode {
match self {
Expand Down
99 changes: 63 additions & 36 deletions crates/cast/src/cmd/tip20/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,14 +290,14 @@ pub(crate) async fn send_tip20_transaction(
tx.set_gas_limit(gas + TEMPO_BROWSER_GAS_BUFFER);
}
if let Some(sponsor) = &tempo_sponsor {
sponsor
.resolve_and_set_fee_token(
(!config.eth_rpc_curl).then_some(&provider),
Some(chain),
&mut tx,
)
.await?;
sponsor.attach_and_print::<TempoNetwork>(&mut tx, browser.address()).await?;
attach_sponsor(
sponsor,
(!config.eth_rpc_curl).then_some(&provider),
chain,
&mut tx,
browser.address(),
)
.await?;
} else {
let fee_token = resolve_and_set_fee_token(
(!config.eth_rpc_curl).then_some(&provider),
Expand All @@ -319,14 +319,14 @@ pub(crate) async fn send_tip20_transaction(
let (mut tx, _) = builder.build_with_access_key(ak.wallet_address, &ak).await?;
maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
if let Some(sponsor) = &tempo_sponsor {
sponsor
.resolve_and_set_fee_token(
(!config.eth_rpc_curl).then_some(&provider),
Some(chain),
&mut tx,
)
.await?;
sponsor.attach_and_print::<TempoNetwork>(&mut tx, ak.wallet_address).await?;
attach_sponsor(
sponsor,
(!config.eth_rpc_curl).then_some(&provider),
chain,
&mut tx,
ak.wallet_address,
)
.await?;
}
cast_send_with_access_key(
&provider,
Expand All @@ -342,12 +342,7 @@ pub(crate) async fn send_tip20_transaction(
)
.await?;
} else if let Some(sponsor_url) = sponsor_url {
let signer = match pre_resolved_signer {
Some(signer) => signer,
None => send_tx.eth.wallet.signer().await?,
};
let from = signer.address();
crate::tx::validate_from_address(send_tx.eth.wallet.from, from)?;
let (signer, _) = resolve_send_signer(pre_resolved_signer, &send_tx.eth).await?;

let (mut tx, _) = builder.build(&signer).await?;
maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
Expand Down Expand Up @@ -376,24 +371,19 @@ pub(crate) async fn send_tip20_transaction(
)
.await?;
} else {
let signer = match pre_resolved_signer {
Some(signer) => signer,
None => send_tx.eth.wallet.signer().await?,
};
let from = signer.address();
crate::tx::validate_from_address(send_tx.eth.wallet.from, from)?;
let (signer, from) = resolve_send_signer(pre_resolved_signer, &send_tx.eth).await?;

let (mut tx, _) = builder.build(&signer).await?;
maybe_print_resolved_lane(resolved_lane.as_ref(), tx.nonce().unwrap_or_default())?;
if let Some(sponsor) = &tempo_sponsor {
sponsor
.resolve_and_set_fee_token(
(!config.eth_rpc_curl).then_some(&provider),
Some(chain),
&mut tx,
)
.await?;
sponsor.attach_and_print::<TempoNetwork>(&mut tx, from).await?;
attach_sponsor(
sponsor,
(!config.eth_rpc_curl).then_some(&provider),
chain,
&mut tx,
from,
)
.await?;
}

let wallet = EthereumWallet::from(signer);
Expand All @@ -417,6 +407,43 @@ pub(crate) async fn send_tip20_transaction(
Ok(())
}

/// Resolves the sending signer, falling back to the wallet options, and validates it against
/// an explicit `--from`.
async fn resolve_send_signer(
pre_resolved: Option<WalletSigner>,
eth: &foundry_cli::opts::EthereumOpts,
) -> eyre::Result<(WalletSigner, Address)> {
let signer = match pre_resolved {
Some(signer) => signer,
None => eth.wallet.signer().await?,
};
let from = signer.address();
crate::tx::validate_from_address(eth.wallet.from, from)?;
Ok((signer, from))
}

/// Resolves the sponsored fee token and attaches the sponsor signature preview for `payer`.
async fn attach_sponsor<P>(
sponsor: &crate::tempo::TempoSponsor,
provider: Option<&P>,
chain: foundry_config::Chain,
tx: &mut <TempoNetwork as alloy_network::Network>::TransactionRequest,
payer: Address,
) -> eyre::Result<()>
where
P: Provider<TempoNetwork>,
{
sponsor
.resolve_and_set_fee_token(
provider.map(|p| p as &dyn Provider<TempoNetwork>),
Some(chain),
tx,
)
.await?;
sponsor.attach_and_print::<TempoNetwork>(tx, payer).await?;
Ok(())
}

impl TxParams {
fn into_transaction_opts(self) -> TransactionOpts {
TransactionOpts {
Expand Down
17 changes: 2 additions & 15 deletions crates/cast/src/cmd/tip403.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use crate::{
cmd::tip20::{resolve_tip20_signer, send_tip20_transaction},
tempo::print_payload,
tx::{SendTxOpts, TxParams},
};
use alloy_ens::NameOrAddress;
use alloy_primitives::Address;
use clap::{Parser, ValueEnum};
use eyre::Result;
use foundry_cli::{
json::print_json_success,
opts::RpcOpts,
utils::{LoadConfig, get_provider},
};
use foundry_common::shell;
use serde_json::{Value, json};
use serde_json::json;
use std::str::FromStr;
use tempo_contracts::precompiles::{ITIP403Registry, TIP403_REGISTRY_ADDRESS};
use tempo_primitives::TempoAddressExt;
Expand Down Expand Up @@ -307,18 +306,6 @@ fn address_array(accounts: &[Address]) -> String {
format!("[{}]", accounts.iter().map(Address::to_string).collect::<Vec<_>>().join(","))
}

fn print_payload<F>(payload: Value, human: F) -> Result<()>
where
F: FnOnce(&Value) -> Result<()>,
{
if shell::is_json() {
print_json_success(payload)?;
} else {
human(&payload)?;
}
Ok(())
}

impl PolicyKind {
const fn to_sol(self) -> ITIP403Registry::PolicyType {
match self {
Expand Down
18 changes: 16 additions & 2 deletions crates/cast/src/tempo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,28 @@ use crate::tx::fill_transaction_gas_fees;
use alloy_network::{Network, TransactionBuilder};
use alloy_provider::Provider;
use eyre::Result;
use foundry_cli::opts::TempoOpts;
use foundry_common::FoundryTransactionBuilder;
use foundry_cli::{json::print_json_success, opts::TempoOpts};
use foundry_common::{FoundryTransactionBuilder, shell};
use foundry_config::{Chain, Eip1559FeeEstimatePreset};
use foundry_wallets::{TempoAccessKeyConfig, WalletOpts, WalletSigner};
use serde_json::Value;
use tempo_alloy::TempoNetwork;

pub use foundry_common::tempo::{TempoSponsor, TempoSponsorPreview, resolve_tempo_sponsor_signer};

/// Prints a command result: the raw payload in JSON mode, the human rendering otherwise.
pub(crate) fn print_payload<F>(payload: Value, human: F) -> Result<()>
where
F: FnOnce(&Value) -> Result<()>,
{
if shell::is_json() {
print_json_success(payload)?;
} else {
human(&payload)?;
}
Ok(())
}

pub(crate) fn print_expires(expires_at: Option<u64>) -> Result<()> {
if let Some(ts) = expires_at {
sh_status!("Transaction expires at unix timestamp {ts}")?;
Expand Down
Loading