From 8cf4121e8def6f5ead5dfdabf698337bdcec6f78 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:28:01 +0200 Subject: [PATCH 1/4] feat(common): `FoundryTransactionBuilder::sign_with_access_key` method --- Cargo.lock | 4 +- crates/cast/src/cmd/batch_mktx.rs | 3 +- crates/cast/src/cmd/batch_send.rs | 3 +- crates/cast/src/cmd/erc20.rs | 3 +- crates/cast/src/cmd/send.rs | 2 +- crates/cast/src/tempo.rs | 2 - crates/common/Cargo.toml | 1 + crates/common/src/transactions/builder.rs | 90 ++++++++++++++++++++++- crates/script/Cargo.toml | 2 +- crates/script/src/broadcast.rs | 58 +++++---------- crates/wallets/Cargo.toml | 2 - crates/wallets/src/tempo.rs | 38 +--------- 12 files changed, 115 insertions(+), 93 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 56b01ce8455c0..925f65088d553 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4689,7 +4689,6 @@ dependencies = [ "serde", "serde_json", "tempfile", - "tempo-alloy", "thiserror 2.0.18", "tokio", "tracing", @@ -4948,6 +4947,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", + "alloy-signer", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -5492,7 +5492,6 @@ version = "1.6.0" dependencies = [ "alloy-consensus", "alloy-dyn-abi", - "alloy-eips 2.0.0-rc.0", "alloy-network", "alloy-primitives", "alloy-rlp", @@ -5518,7 +5517,6 @@ dependencies = [ "rpassword", "serde", "serde_json", - "tempo-alloy", "tempo-primitives", "thiserror 2.0.18", "tokio", diff --git a/crates/cast/src/cmd/batch_mktx.rs b/crates/cast/src/cmd/batch_mktx.rs index f495fcf9220a8..a3d05d032676b 100644 --- a/crates/cast/src/cmd/batch_mktx.rs +++ b/crates/cast/src/cmd/batch_mktx.rs @@ -5,7 +5,6 @@ use crate::{ call_spec::CallSpec, - tempo::sign_with_access_key, tx::{self, CastTxBuilder}, }; use alloy_consensus::SignableTransaction; @@ -164,7 +163,7 @@ impl BatchMakeTxArgs { }; let signed_tx = if let Some(ref access_key) = tempo_access_key { - let raw_tx = sign_with_access_key(tx, &signer, access_key.wallet_address).await?; + let raw_tx = tx.sign_with_access_key(&signer, access_key.wallet_address).await?; alloy_primitives::hex::encode(raw_tx) } else { let envelope = tx.build(&EthereumWallet::new(signer)).await?; diff --git a/crates/cast/src/cmd/batch_send.rs b/crates/cast/src/cmd/batch_send.rs index 7655145973b7b..a683a768d0b3c 100644 --- a/crates/cast/src/cmd/batch_send.rs +++ b/crates/cast/src/cmd/batch_send.rs @@ -7,7 +7,6 @@ use crate::{ call_spec::CallSpec, cmd::send::cast_send, - tempo::sign_with_access_key, tx::{self, CastTxBuilder, CastTxSender, SendTxOpts}, }; use alloy_network::{EthereumWallet, TransactionBuilder}; @@ -160,7 +159,7 @@ impl BatchSendArgs { if let Some(ref access_key) = tempo_access_key { let raw_tx = - sign_with_access_key(tx_request, &signer, access_key.wallet_address).await?; + tx_request.sign_with_access_key(&signer, access_key.wallet_address).await?; let cast = CastTxSender::new(&provider); let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); diff --git a/crates/cast/src/cmd/erc20.rs b/crates/cast/src/cmd/erc20.rs index 166729b6ecbb6..aa327823c4525 100644 --- a/crates/cast/src/cmd/erc20.rs +++ b/crates/cast/src/cmd/erc20.rs @@ -594,8 +594,7 @@ async fn send_tempo_keychain>( tx.set_key_authorization(auth.clone()); } - let raw_tx = - foundry_wallets::tempo::sign_with_access_key(tx, signer, access_key.wallet_address).await?; + let raw_tx = tx.sign_with_access_key(signer, access_key.wallet_address).await?; let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); diff --git a/crates/cast/src/cmd/send.rs b/crates/cast/src/cmd/send.rs index f6cdb2a3f2c98..c0d9c263f61c8 100644 --- a/crates/cast/src/cmd/send.rs +++ b/crates/cast/src/cmd/send.rs @@ -162,7 +162,7 @@ impl SendTxArgs { tx_request.set_key_authorization(auth); } - let raw_tx = crate::tempo::sign_with_access_key(tx_request, &signer, from).await?; + let raw_tx = tx_request.sign_with_access_key(&signer, from).await?; let timeout = send_tx.timeout.unwrap_or(config.transaction_timeout); let tx_hash = *provider.send_raw_transaction(&raw_tx).await?.tx_hash(); diff --git a/crates/cast/src/tempo.rs b/crates/cast/src/tempo.rs index 3c64491aaea63..b5a7444b5d37d 100644 --- a/crates/cast/src/tempo.rs +++ b/crates/cast/src/tempo.rs @@ -2,8 +2,6 @@ use alloy_primitives::Address; use alloy_provider::Provider; use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; -pub use foundry_wallets::tempo::sign_with_access_key; - /// Checks whether an access key is already provisioned on-chain. /// /// Queries the AccountKeychain precompile's `getKey` function. A key is considered diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 4d57de7dac31a..1486b8ef43fb2 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -30,6 +30,7 @@ alloy-primitives = { workspace = true, features = [ "rlp", ] } alloy-provider.workspace = true +alloy-signer.workspace = true alloy-pubsub.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth", "engine"] } diff --git a/crates/common/src/transactions/builder.rs b/crates/common/src/transactions/builder.rs index 5f0fd7b98c2e4..3b09012211dbe 100644 --- a/crates/common/src/transactions/builder.rs +++ b/crates/common/src/transactions/builder.rs @@ -1,11 +1,17 @@ use alloy_consensus::{ BlobTransactionSidecar, BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant, }; -use alloy_eips::eip7702::SignedAuthorization; +use alloy_eips::{Encodable2718, eip7702::SignedAuthorization}; use alloy_network::{AnyNetwork, Ethereum, Network, TransactionBuilder}; use alloy_primitives::{Address, B256, Signature, U256}; -use tempo_alloy::TempoNetwork; -use tempo_primitives::transaction::SignedKeyAuthorization; +use alloy_provider::Provider; +use alloy_signer::Signer; +use eyre::Result; +use tempo_alloy::{TempoNetwork, provider::TempoProviderExt}; +use tempo_primitives::{ + TempoSignature, + transaction::{KeychainSignature, PrimitiveSignature, SignedKeyAuthorization}, +}; /// Composite transaction builder trait for Foundry transactions. /// @@ -233,6 +239,37 @@ pub trait FoundryTransactionBuilder: TransactionBuilder { /// Embeds a [`SignedKeyAuthorization`] in the transaction body, provisioning the access key /// on-chain as part of this transaction. fn set_key_authorization(&mut self, _key_authorization: SignedKeyAuthorization) {} + + /// Signs the transaction using an access key (keychain mode). + /// + /// Builds the transaction, computes the keychain signing hash, signs with the access + /// key, and returns the EIP-2718 encoded raw transaction bytes. + /// + /// The default implementation returns an error. Only `TempoNetwork` supports this. + fn sign_with_access_key( + self, + _signer: &(impl Signer + Sync), + _wallet_address: Address, + ) -> impl Future>> + Send { + async { eyre::bail!("access key signing is not supported for this network") } + } + + /// Signs the transaction using an access key, checking on-chain provisioning first. + /// + /// If `key_authorization` is provided and the key is not yet provisioned on-chain, + /// embeds the authorization in the transaction before signing. + /// + /// The default implementation returns an error. Only `TempoNetwork` supports this. + fn sign_with_access_key_provisioning( + self, + _provider: &impl Provider, + _signer: &(impl Signer + Sync), + _wallet_address: Address, + _key_address: Address, + _key_authorization: Option<&SignedKeyAuthorization>, + ) -> impl Future>> + Send { + async { eyre::bail!("access key signing is not supported for this network") } + } } impl FoundryTransactionBuilder for ::TransactionRequest { @@ -382,4 +419,51 @@ impl FoundryTransactionBuilder for ::Tran fn set_key_authorization(&mut self, key_authorization: SignedKeyAuthorization) { self.key_authorization = Some(key_authorization); } + + async fn sign_with_access_key( + self, + signer: &(impl Signer + Sync), + wallet_address: Address, + ) -> Result> { + let tempo_tx = self + .build_aa() + .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; + + let sig_hash = tempo_tx.signature_hash(); + let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); + let raw_sig = signer.sign_hash(&signing_hash).await?; + + let keychain_sig = + KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); + let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); + + let mut buf = Vec::new(); + aa_signed.encode_2718(&mut buf); + Ok(buf) + } + + fn sign_with_access_key_provisioning( + mut self, + provider: &impl Provider, + signer: &(impl Signer + Sync), + wallet_address: Address, + key_address: Address, + key_authorization: Option<&SignedKeyAuthorization>, + ) -> impl Future>> + Send { + let auth = key_authorization.cloned(); + let provisioning_fut = provider.get_keychain_key(wallet_address, key_address); + + async move { + if let Some(auth) = auth { + let is_provisioned = + provisioning_fut.await.map(|info| info.keyId != Address::ZERO).unwrap_or(false); + + if !is_provisioned { + self.set_key_authorization(auth); + } + } + + self.sign_with_access_key(signer, wallet_address).await + } + } } diff --git a/crates/script/Cargo.toml b/crates/script/Cargo.toml index d88e13ab58ed3..1d403ee05ebdc 100644 --- a/crates/script/Cargo.toml +++ b/crates/script/Cargo.toml @@ -55,7 +55,7 @@ alloy-primitives.workspace = true alloy-eips.workspace = true alloy-consensus.workspace = true thiserror.workspace = true -tempo-alloy.workspace = true + [dev-dependencies] tempfile.workspace = true diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 2139fa2ae6e23..4a971041922cf 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -1,5 +1,9 @@ use std::{cmp::Ordering, sync::Arc, time::Duration}; +use crate::{ + ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, + sequence::ScriptSequenceKind, verify::BroadcastedState, +}; use alloy_chains::{Chain, NamedChain}; use alloy_consensus::{SignableTransaction, Signed}; use alloy_eips::{BlockId, eip2718::Encodable2718}; @@ -26,12 +30,6 @@ use foundry_wallets::{TempoAccessKeyConfig, WalletSigner, wallet_browser::signer use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use tempo_alloy::provider::TempoProviderExt; - -use crate::{ - ScriptArgs, ScriptConfig, build::LinkedBuildData, progress::ScriptProgress, - sequence::ScriptSequenceKind, verify::BroadcastedState, -}; pub async fn estimate_gas>( tx: &mut N::TransactionRequest, @@ -72,15 +70,14 @@ pub enum SendTransactionKind<'a, N: Network> { Raw(N::TransactionRequest, &'a EthereumWallet), Browser(N::TransactionRequest, &'a BrowserSigner), Signed(N::TxEnvelope), - AccessKey(N::TransactionRequest, &'a WalletSigner, &'a TempoAccessKeyConfig, String), + AccessKey(N::TransactionRequest, &'a WalletSigner, &'a TempoAccessKeyConfig), } impl<'a, N: Network> SendTransactionKind<'a, N> where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, - N::TransactionRequest: - FoundryTransactionBuilder + Into, + N::TransactionRequest: FoundryTransactionBuilder, { /// Prepares the transaction for broadcasting by synchronizing nonce and estimating gas. /// @@ -99,7 +96,7 @@ where if let Self::Raw(tx, _) | Self::Unlocked(tx) | Self::Browser(tx, _) - | Self::AccessKey(tx, _, _, _) = self + | Self::AccessKey(tx, _, _) = self { if sequential_broadcast { let from = tx.from().expect("no sender"); @@ -176,31 +173,18 @@ where // Sign and send the transaction via the browser wallet Ok(signer.send_transaction_via_browser(tx).await?) } - Self::AccessKey(mut tx, signer, access_key, rpc_url) => { + Self::AccessKey(tx, signer, access_key) => { debug!("sending transaction via tempo access key: {:?}", tx); - // Check if the key needs on-chain provisioning. - if let Some(auth) = &access_key.key_authorization { - let tempo_provider = foundry_common::provider::ProviderBuilder::< - tempo_alloy::TempoNetwork, - >::new(&rpc_url) - .build()?; - if !tempo_provider - .get_keychain_key(access_key.wallet_address, access_key.key_address) - .await - .map(|info| info.keyId != Address::ZERO) - .unwrap_or(false) - { - tx.set_key_authorization(auth.clone()); - } - } - - let raw_tx = foundry_wallets::tempo::sign_with_access_key( - tx, - signer, - access_key.wallet_address, - ) - .await?; + let raw_tx = tx + .sign_with_access_key_provisioning( + provider.as_ref(), + signer, + access_key.wallet_address, + access_key.key_address, + access_key.key_authorization.as_ref(), + ) + .await?; let pending = provider.send_raw_transaction(&raw_tx).await?; Ok(*pending.tx_hash()) @@ -253,7 +237,6 @@ impl SendTransactionsKind { &self, addr: &Address, tx: N::TransactionRequest, - rpc_url: &str, ) -> Result> { match self { Self::Unlocked(unlocked) => { @@ -264,7 +247,7 @@ impl SendTransactionsKind { } Self::Raw { eth_wallets, browser, access_keys } => { if let Some((signer, config)) = access_keys.get(addr) { - Ok(SendTransactionKind::AccessKey(tx, signer, config, rpc_url.to_string())) + Ok(SendTransactionKind::AccessKey(tx, signer, config)) } else if let Some(wallet) = eth_wallets.get(addr) { Ok(SendTransactionKind::Raw(tx, wallet)) } else if let Some(b) = browser @@ -338,8 +321,7 @@ where where N::TxEnvelope: From>, N::UnsignedTx: SignableTransaction, - N::TransactionRequest: - FoundryTransactionBuilder + Into, + N::TransactionRequest: FoundryTransactionBuilder, { let required_addresses = self .sequence @@ -503,7 +485,7 @@ where tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); } - send_kind.for_sender(&from, tx, sequence.rpc_url())? + send_kind.for_sender(&from, tx)? } }; diff --git a/crates/wallets/Cargo.toml b/crates/wallets/Cargo.toml index 381d8b2c600f7..0f30346c72039 100644 --- a/crates/wallets/Cargo.toml +++ b/crates/wallets/Cargo.toml @@ -44,8 +44,6 @@ alloy-signer-gcp = { workspace = true, features = ["eip712"], optional = true } alloy-signer-turnkey = { workspace = true, features = ["eip712"], optional = true } tempo-primitives.workspace = true -tempo-alloy.workspace = true -alloy-eips.workspace = true alloy-rlp.workspace = true async-trait.workspace = true diff --git a/crates/wallets/src/tempo.rs b/crates/wallets/src/tempo.rs index a86b568fdea2b..e0a05d1212eae 100644 --- a/crates/wallets/src/tempo.rs +++ b/crates/wallets/src/tempo.rs @@ -1,13 +1,8 @@ -use alloy_eips::Encodable2718; use alloy_primitives::{Address, hex}; use alloy_rlp::Decodable; -use alloy_signer::Signer; use eyre::Result; use std::path::PathBuf; -use tempo_alloy::rpc::TempoTransactionRequest; -use tempo_primitives::transaction::{ - KeychainSignature, PrimitiveSignature, SignedKeyAuthorization, TempoSignature, -}; +use tempo_primitives::transaction::SignedKeyAuthorization; use crate::{WalletSigner, utils}; @@ -163,34 +158,3 @@ pub fn lookup_signer(from: Address) -> Result { Ok(TempoLookup::NotFound) } - -/// Signs a Tempo transaction request using an access key (keychain V2 mode). -/// -/// Bypasses the standard `EthereumWallet` signing path and instead: -/// 1. Builds the `TempoTransaction` from the request -/// 2. Computes the V2 keychain signing hash -/// 3. Signs with the access key -/// 4. Wraps in a `KeychainSignature` and encodes to EIP-2718 wire format -pub async fn sign_with_access_key( - tx_request: impl Into, - signer: &impl Signer, - wallet_address: Address, -) -> Result> { - let tx_request: TempoTransactionRequest = tx_request.into(); - let tempo_tx = tx_request - .build_aa() - .map_err(|e| eyre::eyre!("failed to build Tempo AA transaction: {e}"))?; - - let sig_hash = tempo_tx.signature_hash(); - let signing_hash = KeychainSignature::signing_hash(sig_hash, wallet_address); - let raw_sig = signer.sign_hash(&signing_hash).await?; - - let keychain_sig = - KeychainSignature::new(wallet_address, PrimitiveSignature::Secp256k1(raw_sig)); - let aa_signed = tempo_tx.into_signed(TempoSignature::Keychain(keychain_sig)); - - let mut buf = Vec::new(); - aa_signed.encode_2718(&mut buf); - - Ok(buf) -} From 09ad2c9051f0aa20db4995d47a414365e788b7b2 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Fri, 3 Apr 2026 16:47:24 +0200 Subject: [PATCH 2/4] feat(script): `forge script` generic `FoundryEvmNetwork` --- crates/common/src/transactions/receipt.rs | 22 ++++++++- crates/evm/core/src/evm.rs | 22 +++++++-- crates/script/src/broadcast.rs | 28 +++-------- crates/script/src/build.rs | 32 ++++++------- crates/script/src/execute.rs | 57 ++++++++++++----------- crates/script/src/lib.rs | 20 ++++---- crates/script/src/receipts.rs | 16 +------ crates/script/src/simulate.rs | 56 ++++++++++------------ crates/script/src/transaction.rs | 4 +- crates/script/src/verify.rs | 29 ++++-------- 10 files changed, 140 insertions(+), 146 deletions(-) diff --git a/crates/common/src/transactions/receipt.rs b/crates/common/src/transactions/receipt.rs index ea657f2ea352d..363eaedca8cea 100644 --- a/crates/common/src/transactions/receipt.rs +++ b/crates/common/src/transactions/receipt.rs @@ -1,12 +1,32 @@ use alloy_network::{AnyNetwork, AnyTransactionReceipt, Network, TransactionResponse}; +use alloy_primitives::Address; use alloy_provider::{ Provider, network::{ReceiptResponse, TransactionBuilder}, }; -use alloy_rpc_types::BlockId; +use alloy_rpc_types::{BlockId, TransactionReceipt}; use eyre::Result; use foundry_common_fmt::{UIfmt, UIfmtReceiptExt, get_pretty_receipt_attr}; use serde::{Deserialize, Serialize}; +use tempo_alloy::rpc::TempoTransactionReceipt; + +/// Helper trait providing `contract_address` setter for generic `ReceiptResponse` +pub trait FoundryReceiptResponse { + /// Sets address of the created contract, or `None` if the transaction was not a deployment. + fn set_contract_address(&mut self, contract_address: Address); +} + +impl FoundryReceiptResponse for TransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.contract_address = Some(contract_address); + } +} + +impl FoundryReceiptResponse for TempoTransactionReceipt { + fn set_contract_address(&mut self, contract_address: Address) { + self.contract_address = Some(contract_address); + } +} /// Helper type to carry a transaction along with an optional revert reason #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/evm/core/src/evm.rs b/crates/evm/core/src/evm.rs index 965517c36c6ba..77afe8e7d84da 100644 --- a/crates/evm/core/src/evm.rs +++ b/crates/evm/core/src/evm.rs @@ -9,15 +9,17 @@ use crate::{ backend::{DatabaseExt, JournaledState, LocalForkId}, constants::DEFAULT_CREATE2_DEPLOYER_CODEHASH, }; -use alloy_consensus::{constants::KECCAK_EMPTY, transaction::SignerRecoverable}; +use alloy_consensus::{ + SignableTransaction, Signed, constants::KECCAK_EMPTY, transaction::SignerRecoverable, +}; use alloy_evm::{ EthEvmFactory, Evm, EvmEnv, EvmFactory, FromRecoveredTx, eth::EthEvmContext, precompiles::PrecompilesMap, }; use alloy_network::{Ethereum, Network}; -use alloy_primitives::{Address, B256, Bytes, U256}; +use alloy_primitives::{Address, B256, Bytes, Signature, U256}; use alloy_rlp::Decodable; -use foundry_common::FoundryTransactionBuilder; +use foundry_common::{FoundryReceiptResponse, FoundryTransactionBuilder, fmt::UIfmt}; use foundry_config::FromEvmVersion; use foundry_fork_db::{DatabaseError, ForkBlockEnv}; use op_revm::OpHaltReason; @@ -38,6 +40,7 @@ use revm::{ }, primitives::hardfork::SpecId, }; +use serde::{Deserialize, Serialize}; use tempo_alloy::TempoNetwork; use tempo_chainspec::hardfork::TempoHardfork; use tempo_evm::evm::TempoEvmFactory; @@ -78,8 +81,17 @@ impl IntoInstructionResult for TempoHaltReason { /// Foundry's supertrait associating [Network] with [FoundryEvmFactory] pub trait FoundryEvmNetwork: Copy + Debug + Default + 'static { type Network: Network< - TxEnvelope: Decodable + SignerRecoverable, - TransactionRequest: FoundryTransactionBuilder, + TxEnvelope: Decodable + + SignerRecoverable + + From::UnsignedTx>> + + for<'d> Deserialize<'d> + + Serialize + + UIfmt, + UnsignedTx: SignableTransaction, + TransactionRequest: FoundryTransactionBuilder + + for<'d> Deserialize<'d> + + Serialize, + ReceiptResponse: FoundryReceiptResponse, >; type EvmFactory: FoundryEvmFactory::TxEnvelope>>; } diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index 4a971041922cf..65612ed11888c 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -25,11 +25,10 @@ use foundry_common::{ shell, }; use foundry_config::Config; -use foundry_evm::core::evm::EthEvmNetwork; +use foundry_evm::core::evm::FoundryEvmNetwork; use foundry_wallets::{TempoAccessKeyConfig, WalletSigner, wallet_browser::signer::BrowserSigner}; use futures::{FutureExt, StreamExt, future::join_all, stream::FuturesUnordered}; use itertools::Itertools; -use serde::{Deserialize, Serialize}; pub async fn estimate_gas>( tx: &mut N::TransactionRequest, @@ -265,24 +264,16 @@ impl SendTransactionsKind { /// State after we have bundled all /// [`TransactionWithMetadata`](forge_script_sequence::TransactionWithMetadata) objects into a /// single [`ScriptSequenceKind`] object containing one or more script sequences. -pub struct BundledState -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ +pub struct BundledState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BundledState -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ +impl BundledState { pub async fn wait_for_pending(mut self) -> Result { let progress = ScriptProgress::default(); let progress_ref = &progress; @@ -317,12 +308,7 @@ where } /// Broadcasts transactions from all sequences. - pub async fn broadcast(mut self) -> Result> - where - N::TxEnvelope: From>, - N::UnsignedTx: SignableTransaction, - N::TransactionRequest: FoundryTransactionBuilder, - { + pub async fn broadcast(mut self) -> Result> { let required_addresses = self .sequence .sequences() diff --git a/crates/script/src/build.rs b/crates/script/src/build.rs index 1dd0250843b45..bf8cfcddf6f64 100644 --- a/crates/script/src/build.rs +++ b/crates/script/src/build.rs @@ -2,7 +2,7 @@ use crate::{ ScriptArgs, ScriptConfig, broadcast::BundledState, execute::LinkedState, multi_sequence::MultiChainSequence, sequence::ScriptSequenceKind, }; -use alloy_network::{AnyNetwork, Ethereum}; +use alloy_network::AnyNetwork; use alloy_primitives::{B256, Bytes}; use alloy_provider::Provider; use eyre::{OptionExt, Result}; @@ -18,7 +18,7 @@ use foundry_compilers::{ info::ContractInfo, utils::source_files_iter, }; -use foundry_evm::{core::evm::EthEvmNetwork, traces::debug::ContractSources}; +use foundry_evm::{core::evm::FoundryEvmNetwork, traces::debug::ContractSources}; use foundry_linking::Linker; use foundry_wallets::wallet_browser::signer::BrowserSigner; use std::{path::PathBuf, str::FromStr, sync::Arc}; @@ -41,9 +41,9 @@ impl BuildData { /// Links contracts. Uses CREATE2 linking when possible, otherwise falls back to /// default linking with sender nonce and address. - pub async fn link( + pub async fn link( self, - script_config: &ScriptConfig, + script_config: &ScriptConfig, ) -> Result { let create2_deployer = script_config.evm_opts.create2_deployer; let can_use_create2 = if let Some(fork_url) = &script_config.evm_opts.fork_url { @@ -157,17 +157,17 @@ impl LinkedBuildData { } /// First state basically containing only inputs of the user. -pub struct PreprocessedState { +pub struct PreprocessedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, } -impl PreprocessedState { +impl PreprocessedState { /// Parses user input and compiles the contracts depending on script target. /// After compilation, finds exact [ArtifactId] of the target contract. - pub fn compile(self) -> Result { + pub fn compile(self) -> Result> { let Self { args, script_config, script_wallets, browser_wallet } = self; let project = script_config.config.project()?; @@ -243,17 +243,17 @@ impl PreprocessedState { } /// State after we have determined and compiled target contract to be executed. -pub struct CompiledState { +pub struct CompiledState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: BuildData, } -impl CompiledState { +impl CompiledState { /// Uses provided sender address to compute library addresses and link contracts with them. - pub async fn link(self) -> Result { + pub async fn link(self) -> Result> { let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let build_data = build_data.link(&script_config).await?; @@ -262,7 +262,7 @@ impl CompiledState { } /// Tries loading the resumed state from the cache files, skipping simulation stage. - pub async fn resume(self) -> Result> { + pub async fn resume(self) -> Result> { let chain = if self.args.multi { None } else { @@ -358,7 +358,7 @@ impl CompiledState { &self, chain: Option, dry_run: bool, - ) -> Result> { + ) -> Result> { if let Some(chain) = chain { let sequence = ScriptSequence::load( &self.script_config.config, diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 574181faffa22..68df630bd1127 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -6,13 +6,13 @@ use crate::{ }; use alloy_dyn_abi::FunctionExt; use alloy_json_abi::{Function, InternalType, JsonAbi}; -use alloy_network::{AnyNetwork, Ethereum, Network}; +use alloy_network::{AnyNetwork, Network, TransactionBuilder}; use alloy_primitives::{ Address, Bytes, map::{HashMap, HashSet}, }; use alloy_provider::Provider; -use alloy_rpc_types::TransactionInput; +use alloy_rpc_types::TransactionInputKind; use eyre::{OptionExt, Result}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{ensure_clean_constructor, needs_setup}; @@ -24,7 +24,7 @@ use foundry_common::{ use foundry_config::NamedChain; use foundry_debugger::Debugger; use foundry_evm::{ - core::evm::EthEvmNetwork, + core::evm::FoundryEvmNetwork, decode::decode_console_logs, inspectors::cheatcodes::BroadcastableTransactions, traces::{ @@ -41,11 +41,11 @@ use yansi::Paint; /// State after linking, contains the linked build data along with library addresses and optional /// array of libraries that need to be predeployed. -pub struct LinkedState { +pub struct LinkedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, } @@ -62,10 +62,10 @@ pub struct ExecutionData { pub abi: JsonAbi, } -impl LinkedState { +impl LinkedState { /// Given linked and compiled artifacts, prepares data we need for execution. /// This includes the function to call and the calldata to pass to it. - pub async fn prepare_execution(self) -> Result { + pub async fn prepare_execution(self) -> Result> { let Self { args, script_config, script_wallets, browser_wallet, build_data } = self; let target_contract = build_data.get_target_contract()?; @@ -94,19 +94,19 @@ impl LinkedState { /// Same as [LinkedState], but also contains [ExecutionData]. #[derive(Debug)] -pub struct PreExecutionState { +pub struct PreExecutionState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, } -impl PreExecutionState { +impl PreExecutionState { /// Executes the script and returns the state after execution. /// Might require executing script twice in cases when we determine sender from execution. - pub async fn execute(mut self) -> Result { + pub async fn execute(mut self) -> Result> { let mut runner = self .script_config .get_runner_with_cheatcodes( @@ -149,8 +149,8 @@ impl PreExecutionState { /// Executes the script using the provided runner and returns the [ScriptResult]. pub async fn execute_with_runner( &self, - runner: &mut ScriptRunner, - ) -> Result> { + runner: &mut ScriptRunner, + ) -> Result> { let (address, mut setup_result) = runner.setup( &self.build_data.predeploy_libraries, self.execution_data.bytecode.clone(), @@ -190,7 +190,7 @@ impl PreExecutionState { /// them instead. fn maybe_new_sender( &self, - transactions: Option<&BroadcastableTransactions>, + transactions: Option<&BroadcastableTransactions>, ) -> Result> { let mut new_sender = None; @@ -230,7 +230,7 @@ pub struct RpcData { impl RpcData { /// Iterates over script transactions and collects RPC urls. - fn from_transactions(txs: &BroadcastableTransactions) -> Self { + fn from_transactions(txs: &BroadcastableTransactions) -> Self { let missing_rpc = txs.iter().any(|tx| tx.rpc.is_none()); let total_rpcs = txs.iter().filter_map(|tx| tx.rpc.as_ref().cloned()).collect::>(); @@ -282,32 +282,33 @@ pub struct ExecutionArtifacts { } /// State after the script has been executed. -pub struct ExecutedState { +pub struct ExecutedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, } -impl ExecutedState { +impl ExecutedState { /// Collects the data we need for simulation and various post-execution tasks. - pub async fn prepare_simulation(self) -> Result> { + pub async fn prepare_simulation(self) -> Result> { let returns = self.get_returns()?; let decoder = self.build_trace_decoder(&self.build_data.known_contracts).await?; - let mut txs: BroadcastableTransactions = + let mut txs: BroadcastableTransactions = self.execution_result.transactions.clone().unwrap_or_default(); // Ensure that unsigned transactions have both `data` and `input` populated to avoid // issues with eth_estimateGas and eth_sendTransaction requests. for tx in &mut txs { - if let Some(req) = tx.transaction.as_unsigned_mut() { - req.input = - TransactionInput::maybe_both(std::mem::take(&mut req.input).into_input()); + if let Some(req) = tx.transaction.as_unsigned_mut() + && let Some(input) = req.input().cloned() + { + *req = req.clone().with_input_kind(input, TransactionInputKind::Both); } } let rpc_data = RpcData::from_transactions(&txs); @@ -400,7 +401,7 @@ impl ExecutedState { } } -impl PreSimulationState { +impl PreSimulationState { pub async fn show_json(&self) -> Result<()> { let mut result = self.execution_result.clone(); diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 357df58ce6f89..1dc4963d75fd0 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -13,7 +13,7 @@ extern crate tracing; use crate::runner::ScriptRunner; use alloy_json_abi::{Function, JsonAbi}; -use alloy_network::{Ethereum, Network}; +use alloy_network::Network; use alloy_primitives::{ Address, Bytes, Log, U256, hex, map::{AddressHashMap, HashMap}, @@ -45,7 +45,10 @@ use foundry_config::{ }; use foundry_evm::{ backend::Backend, - core::{Breakpoints, evm::FoundryEvmNetwork}, + core::{ + Breakpoints, + evm::{EthEvmNetwork, FoundryEvmNetwork}, + }, executors::ExecutorBuilder, inspectors::{ CheatsConfig, @@ -227,9 +230,9 @@ pub struct ScriptArgs { } impl ScriptArgs { - pub async fn preprocess(self) -> Result { + pub async fn preprocess(self) -> Result> { let script_wallets = Wallets::new(self.wallets.get_multi_wallet().await?, self.evm.sender); - let browser_wallet = self.wallets.browser_signer::().await?; + let browser_wallet = self.wallets.browser_signer::().await?; let (config, mut evm_opts) = self.load_config_and_evm_opts()?; @@ -257,7 +260,7 @@ impl ScriptArgs { pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let state = self.preprocess().await?; + let state = self.preprocess::().await?; let create2_deployer = state.script_config.evm_opts.create2_deployer; let compiled = state.compile()?; @@ -415,9 +418,9 @@ impl ScriptArgs { /// /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns /// the user. - fn check_contract_sizes( + fn check_contract_sizes( &self, - result: &ScriptResult, + result: &ScriptResult, known_contracts: &ContractsByArtifact, create2_deployer: Address, ) -> Result<()> { @@ -713,6 +716,7 @@ impl ScriptConfig { #[cfg(test)] mod tests { use super::*; + use alloy_network::Ethereum; use foundry_config::{NamedChain, UnresolvedEnvVarError}; use std::fs; use tempfile::tempdir; @@ -766,7 +770,7 @@ mod tests { ScriptArgs::parse_from(["foundry-cli", "Contract.sol", "--disable-code-size-limit"]); assert!(args.disable_code_size_limit); - let result = ScriptResult::default(); + let result = ScriptResult::::default(); let contracts = ContractsByArtifact::default(); let create = Address::ZERO; assert!(args.check_contract_sizes(&result, &contracts, create).is_ok()); diff --git a/crates/script/src/receipts.rs b/crates/script/src/receipts.rs index 217cfc36c088a..dc6f8755d042b 100644 --- a/crates/script/src/receipts.rs +++ b/crates/script/src/receipts.rs @@ -1,27 +1,14 @@ use alloy_chains::{Chain, NamedChain}; use alloy_network::{Network, ReceiptResponse}; -use alloy_primitives::{Address, TxHash, U256, utils::format_units}; +use alloy_primitives::{TxHash, U256, utils::format_units}; use alloy_provider::{ PendingTransactionBuilder, PendingTransactionError, Provider, RootProvider, WatchTxError, }; -use alloy_rpc_types::TransactionReceipt; use eyre::{Result, eyre}; use forge_script_sequence::ScriptSequence; use foundry_common::{retry, retry::RetryError, shell}; use std::time::Duration; -/// Helper trait providing `contract_address` setter for generic `ReceiptResponse` -pub trait FoundryReceiptResponse { - /// Sets address of the created contract, or `None` if the transaction was not a deployment. - fn set_contract_address(&mut self, contract_address: Address); -} - -impl FoundryReceiptResponse for TransactionReceipt { - fn set_contract_address(&mut self, contract_address: Address) { - self.contract_address = Some(contract_address); - } -} - /// Marker error type for pending receipts #[derive(Debug, thiserror::Error)] #[error( @@ -196,6 +183,7 @@ mod tests { use super::*; use alloy_network::Ethereum; use alloy_primitives::B256; + use alloy_rpc_types::TransactionReceipt; use std::collections::VecDeque; fn mock_receipt(tx_hash: B256, success: bool) -> TransactionReceipt { diff --git a/crates/script/src/simulate.rs b/crates/script/src/simulate.rs index 42fdce5661210..2d942670af066 100644 --- a/crates/script/src/simulate.rs +++ b/crates/script/src/simulate.rs @@ -10,22 +10,22 @@ use crate::{ sequence::get_commit_hash, }; use alloy_chains::NamedChain; -use alloy_network::{Network, TransactionBuilder}; +use alloy_evm::revm::context::Block; +use alloy_network::TransactionBuilder; use alloy_primitives::{Address, U256, map::HashMap, utils::format_units}; use dialoguer::Confirm; use eyre::{Context, Result}; use forge_script_sequence::{ScriptSequence, TransactionWithMetadata}; use foundry_cheatcodes::Wallets; use foundry_cli::utils::{has_different_gas_calc, now}; -use foundry_common::{ContractData, FoundryTransactionBuilder, shell}; +use foundry_common::{ContractData, shell}; use foundry_evm::{ - core::evm::EthEvmNetwork, + core::{FoundryBlock, evm::FoundryEvmNetwork}, traces::{decode_trace_arena, render_trace_arena}, }; use foundry_wallets::wallet_browser::signer::BrowserSigner; use futures::future::{join_all, try_join_all}; use parking_lot::RwLock; -use serde::{Deserialize, Serialize}; use std::{ collections::{BTreeMap, VecDeque}, mem, @@ -37,27 +37,24 @@ use std::{ /// /// Can be either converted directly to [BundledState] or driven to it through /// [FilledTransactionsState]. -pub struct PreSimulationState { +pub struct PreSimulationState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_data: ExecutionData, - pub execution_result: ScriptResult, + pub execution_result: ScriptResult, pub execution_artifacts: ExecutionArtifacts, } -impl PreSimulationState -where - N::TransactionRequest: FoundryTransactionBuilder, -{ +impl PreSimulationState { /// If simulation is enabled, simulates transactions against fork and fills gas estimation and /// metadata. Otherwise, metadata (e.g. additional contracts, created contract names) is /// left empty. /// /// Both modes will panic if any of the transactions have None for the `rpc` field. - pub async fn fill_metadata(self) -> Result> { + pub async fn fill_metadata(self) -> Result> { let address_to_abi = self.build_address_to_abi_map(); let mut transactions = self @@ -111,8 +108,8 @@ where /// Collects gas usage and metadata for each transaction. pub async fn simulate_and_fill( &self, - transactions: VecDeque>, - ) -> Result>> { + transactions: VecDeque>, + ) -> Result>> { trace!(target: "script", "executing onchain simulation"); let runners = Arc::new( @@ -150,7 +147,8 @@ where // Simulate mining the transaction if the user passes `--slow`. if self.args.slow { - runner.executor.evm_env_mut().block_env.number += U256::from(1); + let block_number = runner.executor.evm_env().block_env.number() + U256::from(1); + runner.executor.evm_env_mut().block_env.set_number(block_number); } let is_noop_tx = if let Some(to) = to { @@ -237,7 +235,7 @@ where } /// Build [ScriptRunner] forking given RPC for each RPC used in the script. - async fn build_runners(&self) -> Result)>> { + async fn build_runners(&self) -> Result)>> { let rpcs = self.execution_artifacts.rpc_data.total_rpcs.clone(); if !shell::is_json() { @@ -259,27 +257,23 @@ where /// At this point we have converted transactions collected during script execution to /// [TransactionWithMetadata] objects which contain additional metadata needed for broadcasting and /// verification. -pub struct FilledTransactionsState { +pub struct FilledTransactionsState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub script_wallets: Wallets, - pub browser_wallet: Option>, + pub browser_wallet: Option>, pub build_data: LinkedBuildData, pub execution_artifacts: ExecutionArtifacts, - pub transactions: VecDeque>, + pub transactions: VecDeque>, } -impl FilledTransactionsState -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize + FoundryTransactionBuilder, -{ +impl FilledTransactionsState { /// Bundles all transactions of the [`TransactionWithMetadata`] type in a list of /// [`ScriptSequence`]. List length will be higher than 1, if we're dealing with a multi /// chain deployment. /// /// Each transaction will be added with the correct transaction type and gas estimation. - pub async fn bundle(mut self) -> Result> { + pub async fn bundle(mut self) -> Result> { let is_multi_deployment = self.execution_artifacts.rpc_data.total_rpcs.len() > 1; if is_multi_deployment && !self.build_data.libraries.is_empty() { @@ -290,7 +284,7 @@ where // Batches sequence of transactions from different rpcs. let mut new_sequence = VecDeque::new(); - let mut manager = ProvidersManager::::default(); + let mut manager = ProvidersManager::::default(); let mut sequences = vec![]; // Peeking is used to check if the next rpc url is different. If so, it creates a @@ -440,14 +434,14 @@ where &self, multi: bool, chain: u64, - transactions: VecDeque>, - ) -> Result> { + transactions: VecDeque>, + ) -> Result> { // Paths are set to None for multi-chain sequences parts, because they don't need to be // saved to a separate file. let paths = if multi { None } else { - Some(ScriptSequence::::get_paths( + Some(ScriptSequence::::get_paths( &self.script_config.config, &self.args.sig, &self.build_data.build_data.target, diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index 6d47be97ecd84..2a2bbf3e47bf0 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -1,7 +1,7 @@ use super::ScriptResult; use crate::build::LinkedBuildData; use alloy_dyn_abi::JsonAbiExt; -use alloy_network::{Ethereum, Network, TransactionBuilder}; +use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::{Address, B256, hex}; use eyre::Result; use forge_script_sequence::TransactionWithMetadata; @@ -146,7 +146,7 @@ impl ScriptTransactionBuilder { /// Populates additional data from the transaction execution result. pub fn with_execution_result( mut self, - result: &ScriptResult, + result: &ScriptResult, gas_estimate_multiplier: u64, linked_build_data: &LinkedBuildData, ) -> Self { diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index 4b9c1c1f73049..0a2b171b379ba 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -1,7 +1,6 @@ use crate::{ ScriptArgs, ScriptConfig, build::LinkedBuildData, - receipts::FoundryReceiptResponse, sequence::{ScriptSequenceKind, get_commit_hash}, }; use alloy_network::{Network, ReceiptResponse}; @@ -10,33 +9,23 @@ use eyre::{Result, eyre}; use forge_script_sequence::{AdditionalContract, ScriptSequence}; use forge_verify::{RetryArgs, VerifierArgs, VerifyArgs, provider::VerificationProviderType}; use foundry_cli::opts::{EtherscanOpts, ProjectPathOpts}; -use foundry_common::ContractsByArtifact; +use foundry_common::{ContractsByArtifact, FoundryReceiptResponse}; use foundry_compilers::{Project, artifacts::EvmVersion, info::ContractInfo}; use foundry_config::{Chain, Config}; -use foundry_evm::core::evm::EthEvmNetwork; +use foundry_evm::core::evm::FoundryEvmNetwork; use semver::Version; -use serde::{Deserialize, Serialize}; /// State after we have broadcasted the script. /// It is assumed that at this point [BroadcastedState::sequence] contains receipts for all /// broadcasted transactions. -pub struct BroadcastedState -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, -{ +pub struct BroadcastedState { pub args: ScriptArgs, - pub script_config: ScriptConfig, + pub script_config: ScriptConfig, pub build_data: LinkedBuildData, - pub sequence: ScriptSequenceKind, + pub sequence: ScriptSequenceKind, } -impl BroadcastedState -where - N::TxEnvelope: for<'d> Deserialize<'d> + Serialize, - N::TransactionRequest: for<'d> Deserialize<'d> + Serialize, - N::ReceiptResponse: FoundryReceiptResponse, -{ +impl BroadcastedState { pub async fn verify(self) -> Result<()> { let Self { args, script_config, build_data, mut sequence, .. } = self; @@ -49,7 +38,7 @@ where ); for sequence in sequence.sequences_mut() { - verify_contracts(sequence, &script_config.config, verify.clone()).await?; + verify_contracts::(sequence, &script_config.config, verify.clone()).await?; } Ok(()) @@ -191,8 +180,8 @@ impl VerifyBundle { /// Given the broadcast log, it matches transactions with receipts, and tries to verify any /// created contract on etherscan. -async fn verify_contracts>( - sequence: &mut ScriptSequence, +async fn verify_contracts( + sequence: &mut ScriptSequence, config: &Config, mut verify: VerifyBundle, ) -> Result<()> { From f69f21713fe0e37266a26730add5ded6669c171d Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:10:34 +0200 Subject: [PATCH 3/4] fix: recursion limit --- crates/script/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 1dc4963d75fd0..f1dc9c1957e93 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -2,6 +2,7 @@ //! //! Smart contract scripting. +#![recursion_limit = "256"] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg))] From b0a7e40d9905a16fde45998389f60723e3dd89d6 Mon Sep 17 00:00:00 2001 From: Mablr <59505383+mablr@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:41:09 +0200 Subject: [PATCH 4/4] feat: evm selection based on `EvmOpts` --- crates/script/src/lib.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index f1dc9c1957e93..4f15c4b05dea7 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -48,7 +48,7 @@ use foundry_evm::{ backend::Backend, core::{ Breakpoints, - evm::{EthEvmNetwork, FoundryEvmNetwork}, + evm::{EthEvmNetwork, FoundryEvmNetwork, TempoEvmNetwork}, }, executors::ExecutorBuilder, inspectors::{ @@ -261,7 +261,15 @@ impl ScriptArgs { pub async fn run_script(self) -> Result<()> { trace!(target: "script", "executing script command"); - let state = self.preprocess::().await?; + if self.load_config_and_evm_opts()?.1.networks.is_tempo() { + self.run_generic_script::().await + } else { + self.run_generic_script::().await + } + } + + async fn run_generic_script(self) -> Result<()> { + let state = self.preprocess::().await?; let create2_deployer = state.script_config.evm_opts.create2_deployer; let compiled = state.compile()?;