diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 85b2766da128e..2a125df98c5f7 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3551,6 +3551,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "deployCode_0", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.", + "declaration": "function deployCode(string calldata artifactPath) external returns (address deployedAddress);", + "visibility": "external", + "mutability": "", + "signature": "deployCode(string)", + "selector": "0x9a8325a0", + "selectorBytes": [ + 154, + 131, + 37, + 160 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "deployCode_1", + "description": "Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the\nartifact in the form of :: where and parts are optional.\nAdditionaly accepts abi-encoded constructor arguments.", + "declaration": "function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress);", + "visibility": "external", + "mutability": "", + "signature": "deployCode(string,bytes)", + "selector": "0x29ce9dde", + "selectorBytes": [ + 41, + 206, + 157, + 222 + ] + }, + "group": "filesystem", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "deriveKey_0", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index f774fcbc7e1bb..cd8aa08c509c6 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -1474,6 +1474,18 @@ interface Vm { #[cheatcode(group = Filesystem)] function getCode(string calldata artifactPath) external view returns (bytes memory creationBytecode); + /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. + #[cheatcode(group = Filesystem)] + function deployCode(string calldata artifactPath) external returns (address deployedAddress); + + /// Deploys a contract from an artifact file. Takes in the relative path to the json file or the path to the + /// artifact in the form of :: where and parts are optional. + /// + /// Additionaly accepts abi-encoded constructor arguments. + #[cheatcode(group = Filesystem)] + function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress); + /// Gets the deployed bytecode from an artifact file. Takes in the relative path to the json file or the path to the /// artifact in the form of :: where and parts are optional. #[cheatcode(group = Filesystem)] diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 50265b4b12e0d..4e559687dbc1e 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -67,14 +67,14 @@ impl Cheatcode for addrCall { } impl Cheatcode for getNonce_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; get_nonce(ccx, account) } } impl Cheatcode for loadCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); ccx.ecx.load_account(target)?; @@ -84,7 +84,7 @@ impl Cheatcode for loadCall { } impl Cheatcode for loadAllocsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); @@ -110,7 +110,7 @@ impl Cheatcode for loadAllocsCall { } impl Cheatcode for dumpStateCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); @@ -156,28 +156,28 @@ impl Cheatcode for dumpStateCall { } impl Cheatcode for sign_0Call { - fn apply_full(&self, _: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, _: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; super::utils::sign(privateKey, digest) } } impl Cheatcode for sign_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { digest } = self; super::utils::sign_with_wallet(ccx, None, digest) } } impl Cheatcode for sign_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer, digest } = self; super::utils::sign_with_wallet(ccx, Some(*signer), digest) } } impl Cheatcode for signP256Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey, digest } = self; super::utils::sign_p256(privateKey, digest, ccx.state) } @@ -255,7 +255,7 @@ impl Cheatcode for lastCallGasCall { } impl Cheatcode for chainIdCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); ccx.ecx.env.cfg.chain_id = newChainId.to(); @@ -264,7 +264,7 @@ impl Cheatcode for chainIdCall { } impl Cheatcode for coinbaseCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; ccx.ecx.env.block.coinbase = *newCoinbase; Ok(Default::default()) @@ -272,7 +272,7 @@ impl Cheatcode for coinbaseCall { } impl Cheatcode for difficultyCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( ccx.ecx.spec_id() < SpecId::MERGE, @@ -285,7 +285,7 @@ impl Cheatcode for difficultyCall { } impl Cheatcode for feeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; ccx.ecx.env.block.basefee = *newBasefee; Ok(Default::default()) @@ -293,7 +293,7 @@ impl Cheatcode for feeCall { } impl Cheatcode for prevrandao_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -306,7 +306,7 @@ impl Cheatcode for prevrandao_0Call { } impl Cheatcode for prevrandao_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -319,7 +319,7 @@ impl Cheatcode for prevrandao_1Call { } impl Cheatcode for blobhashesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { hashes } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -332,7 +332,7 @@ impl Cheatcode for blobhashesCall { } impl Cheatcode for getBlobhashesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -344,7 +344,7 @@ impl Cheatcode for getBlobhashesCall { } impl Cheatcode for rollCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; ccx.ecx.env.block.number = *newHeight; Ok(Default::default()) @@ -352,14 +352,14 @@ impl Cheatcode for rollCall { } impl Cheatcode for getBlockNumberCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.number.abi_encode()) } } impl Cheatcode for txGasPriceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; ccx.ecx.env.tx.gas_price = *newGasPrice; Ok(Default::default()) @@ -367,7 +367,7 @@ impl Cheatcode for txGasPriceCall { } impl Cheatcode for warpCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; ccx.ecx.env.block.timestamp = *newTimestamp; Ok(Default::default()) @@ -375,14 +375,14 @@ impl Cheatcode for warpCall { } impl Cheatcode for getBlockTimestampCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.timestamp.abi_encode()) } } impl Cheatcode for blobBaseFeeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBlobBaseFee } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -395,14 +395,14 @@ impl Cheatcode for blobBaseFeeCall { } impl Cheatcode for getBlobBaseFeeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) } } impl Cheatcode for dealCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); @@ -413,7 +413,7 @@ impl Cheatcode for dealCall { } impl Cheatcode for etchCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; ensure_not_precompile!(target, ccx); ccx.ecx.load_account(*target)?; @@ -424,7 +424,7 @@ impl Cheatcode for etchCall { } impl Cheatcode for resetNonceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces @@ -439,7 +439,7 @@ impl Cheatcode for resetNonceCall { } impl Cheatcode for setNonceCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; // nonce must increment only @@ -455,7 +455,7 @@ impl Cheatcode for setNonceCall { } impl Cheatcode for setNonceUnsafeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; @@ -464,7 +464,7 @@ impl Cheatcode for setNonceUnsafeCall { } impl Cheatcode for storeCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot, value } = *self; ensure_not_precompile!(&target, ccx); // ensure the account is touched @@ -475,7 +475,7 @@ impl Cheatcode for storeCall { } impl Cheatcode for coolCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); @@ -486,21 +486,21 @@ impl Cheatcode for coolCall { } impl Cheatcode for readCallersCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; read_callers(ccx.state, &ccx.ecx.env.tx.caller) } } impl Cheatcode for snapshotCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.db.snapshot(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } impl Cheatcode for revertToCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, @@ -519,7 +519,7 @@ impl Cheatcode for revertToCall { } impl Cheatcode for revertToAndDeleteCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( *snapshotId, @@ -538,14 +538,14 @@ impl Cheatcode for revertToAndDeleteCall { } impl Cheatcode for deleteSnapshotCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = ccx.ecx.db.delete_snapshot(*snapshotId); Ok(result.abi_encode()) } } impl Cheatcode for deleteSnapshotsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx.db.delete_snapshots(); Ok(Default::default()) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index a767520288f21..70b7591f8a82e 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -7,7 +7,7 @@ use foundry_common::provider::ProviderBuilder; use foundry_evm_core::fork::CreateFork; impl Cheatcode for activeForkCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx .db @@ -18,49 +18,49 @@ impl Cheatcode for activeForkCall { } impl Cheatcode for createFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -74,7 +74,7 @@ impl Cheatcode for rollFork_0Call { } impl Cheatcode for rollFork_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -88,7 +88,7 @@ impl Cheatcode for rollFork_1Call { } impl Cheatcode for rollFork_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -102,7 +102,7 @@ impl Cheatcode for rollFork_2Call { } impl Cheatcode for rollFork_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -116,7 +116,7 @@ impl Cheatcode for rollFork_3Call { } impl Cheatcode for selectForkCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; @@ -127,35 +127,43 @@ impl Cheatcode for selectForkCall { } impl Cheatcode for transact_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { let Self { txHash } = *self; ccx.ecx.db.transact( None, txHash, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state, - ccx.state, + &mut executor.get_inspector(ccx.state), )?; Ok(Default::default()) } } impl Cheatcode for transact_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { let Self { forkId, txHash } = *self; ccx.ecx.db.transact( Some(forkId), txHash, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state, - ccx.state, + &mut executor.get_inspector(ccx.state), )?; Ok(Default::default()) } } impl Cheatcode for allowCheatcodesCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.allow_cheatcode_access(*account); Ok(Default::default()) @@ -163,7 +171,7 @@ impl Cheatcode for allowCheatcodesCall { } impl Cheatcode for makePersistent_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.add_persistent_account(*account); Ok(Default::default()) @@ -171,7 +179,7 @@ impl Cheatcode for makePersistent_0Call { } impl Cheatcode for makePersistent_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -180,7 +188,7 @@ impl Cheatcode for makePersistent_1Call { } impl Cheatcode for makePersistent_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -190,15 +198,17 @@ impl Cheatcode for makePersistent_2Call { } impl Cheatcode for makePersistent_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.ecx.db.extend_persistent_accounts(accounts.iter().copied()); + for account in accounts { + ccx.ecx.db.add_persistent_account(*account); + } Ok(Default::default()) } } impl Cheatcode for revokePersistent_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.remove_persistent_account(account); Ok(Default::default()) @@ -206,22 +216,24 @@ impl Cheatcode for revokePersistent_0Call { } impl Cheatcode for revokePersistent_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; - ccx.ecx.db.remove_persistent_accounts(accounts.iter().copied()); + for account in accounts { + ccx.ecx.db.remove_persistent_account(account); + } Ok(Default::default()) } } impl Cheatcode for isPersistentCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; Ok(ccx.ecx.db.is_persistent(account).abi_encode()) } } impl Cheatcode for rpcCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; @@ -239,7 +251,7 @@ impl Cheatcode for rpcCall { } impl Cheatcode for eth_getLogsCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index becf86f178f54..0949cbf4f973d 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -47,7 +47,7 @@ impl Cheatcode for clearMockedCallsCall { } impl Cheatcode for mockCall_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; let (acc, _) = ccx.ecx.load_account(*callee)?; @@ -65,7 +65,7 @@ impl Cheatcode for mockCall_0Call { } impl Cheatcode for mockCall_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; ccx.ecx.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index 4e4ef81f759f3..fe5418b3157f8 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -45,28 +45,28 @@ impl Prank { } impl Cheatcode for prank_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true) } } impl Cheatcode for startPrank_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false) } } impl Cheatcode for prank_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true) } } impl Cheatcode for startPrank_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false) } diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index a126d8dc9e909..045ebea27e45f 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -1,7 +1,7 @@ //! Implementations of [`Filesystem`](spec::Group::Filesystem) cheatcodes. use super::string::parse; -use crate::{Cheatcode, Cheatcodes, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::DynSolType; use alloy_json_abi::ContractObject; use alloy_primitives::{hex, Bytes, U256}; @@ -9,6 +9,8 @@ use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; +use foundry_evm_core::backend::DatabaseExt; +use revm::interpreter::CreateInputs; use semver::Version; use std::{ collections::hash_map::Entry, @@ -262,6 +264,59 @@ impl Cheatcode for getDeployedCodeCall { } } +impl Cheatcode for deployCode_0Call { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { artifactPath: path } = self; + let bytecode = get_artifact_code(ccx.state, path, false)?; + let output = executor + .exec_create( + CreateInputs { + caller: ccx.caller, + scheme: revm::primitives::CreateScheme::Create, + value: U256::ZERO, + init_code: bytecode, + gas_limit: ccx.gas_limit, + }, + ccx.state, + ccx.ecx, + ) + .unwrap(); + + Ok(output.address.unwrap().abi_encode()) + } +} + +impl Cheatcode for deployCode_1Call { + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + executor: &mut E, + ) -> Result { + let Self { artifactPath: path, constructorArgs } = self; + let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec(); + bytecode.extend_from_slice(constructorArgs); + let output = executor + .exec_create( + CreateInputs { + caller: ccx.caller, + scheme: revm::primitives::CreateScheme::Create, + value: U256::ZERO, + init_code: bytecode.into(), + gas_limit: ccx.gas_limit, + }, + ccx.state, + ccx.ecx, + ) + .unwrap(); + + Ok(output.address.unwrap().abi_encode()) + } +} + /// Returns the path to the json artifact depending on the input /// /// Can parse following input formats: diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index a2d85c83b2ca0..300ada83d7c10 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1,4 +1,4 @@ -//! Cheatcode EVM [Inspector]. +//! Cheatcode EVM inspector. use crate::{ evm::{ @@ -24,6 +24,7 @@ use foundry_evm_core::{ abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, + utils::new_evm_with_existing_context, InspectorExt, }; use itertools::Itertools; @@ -32,7 +33,7 @@ use revm::{ opcode, CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter, InterpreterAction, InterpreterResult, }, - primitives::{BlockEnv, CreateScheme}, + primitives::{BlockEnv, CreateScheme, EVMError}, EvmContext, InnerEvmContext, Inspector, }; use rustc_hash::FxHashMap; @@ -46,6 +47,85 @@ use std::{ sync::Arc, }; +/// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to +/// [Cheatcodes]. +/// +/// This is needed for cases when inspector itself needs mutable access to [Cheatcodes] state and +/// allows us to correctly execute arbitrary EVM frames from inside cheatcode implementations. +pub trait CheatcodesExecutor { + /// Core trait method accepting mutable reference to [Cheatcodes] and returning + /// [revm::Inspector]. + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a; + + /// Obtains [revm::Inspector] instance and executes the given CREATE frame. + fn exec_create( + &mut self, + inputs: CreateInputs, + cheats: &mut Cheatcodes, + ecx: &mut InnerEvmContext, + ) -> Result> { + let inspector = self.get_inspector(cheats); + let error = std::mem::replace(&mut ecx.error, Ok(())); + let l1_block_info = std::mem::take(&mut ecx.l1_block_info); + + let inner = revm::InnerEvmContext { + env: ecx.env.clone(), + journaled_state: std::mem::replace( + &mut ecx.journaled_state, + revm::JournaledState::new(Default::default(), Default::default()), + ), + db: &mut ecx.db as &mut dyn DatabaseExt, + error, + l1_block_info, + }; + + let mut evm = new_evm_with_existing_context(inner, inspector); + + evm.context.evm.inner.journaled_state.depth += 1; + + let first_frame_or_result = + evm.handler.execution().create(&mut evm.context, Box::new(inputs))?; + + let mut result = match first_frame_or_result { + revm::FrameOrResult::Frame(first_frame) => evm.run_the_loop(first_frame)?, + revm::FrameOrResult::Result(result) => result, + }; + + evm.handler.execution().last_frame_return(&mut evm.context, &mut result)?; + + let outcome = match result { + revm::FrameResult::Call(_) | revm::FrameResult::EOFCreate(_) => unreachable!(), + revm::FrameResult::Create(create) => create, + }; + + evm.context.evm.inner.journaled_state.depth -= 1; + + ecx.journaled_state = evm.context.evm.inner.journaled_state; + ecx.env = evm.context.evm.inner.env; + ecx.l1_block_info = evm.context.evm.inner.l1_block_info; + ecx.error = evm.context.evm.inner.error; + + Ok(outcome) + } +} + +/// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an +/// inspector. +#[derive(Debug, Default, Clone, Copy)] +struct TransparentCheatcodesExecutor; + +impl CheatcodesExecutor for TransparentCheatcodesExecutor { + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a { + cheats + } +} + macro_rules! try_or_return { ($e:expr) => { match $e { @@ -255,10 +335,11 @@ impl Cheatcodes { } /// Decodes the input data and applies the cheatcode. - fn apply_cheatcode( + fn apply_cheatcode( &mut self, ecx: &mut EvmContext, call: &CallInputs, + executor: &mut E, ) -> Result { // decode the cheatcode call let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { @@ -285,8 +366,10 @@ impl Cheatcodes { state: self, ecx: &mut ecx.inner, precompiles: &mut ecx.precompiles, + gas_limit: call.gas_limit, caller, }, + executor, ) } @@ -346,67 +429,13 @@ impl Cheatcodes { } } } -} - -impl Inspector for Cheatcodes { - #[inline] - fn initialize_interp(&mut self, _interpreter: &mut Interpreter, ecx: &mut EvmContext) { - // When the first interpreter is initialized we've circumvented the balance and gas checks, - // so we apply our actual block data with the correct fees and all. - if let Some(block) = self.block.take() { - ecx.env.block = block; - } - if let Some(gas_price) = self.gas_price.take() { - ecx.env.tx.gas_price = gas_price; - } - } - - #[inline] - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { - self.pc = interpreter.program_counter(); - - // `pauseGasMetering`: reset interpreter gas. - if self.gas_metering.is_some() { - self.meter_gas(interpreter); - } - - // `record`: record storage reads and writes. - if self.accesses.is_some() { - self.record_accesses(interpreter); - } - - // `startStateDiffRecording`: record granular ordered storage accesses. - if self.recorded_account_diffs_stack.is_some() { - self.record_state_diffs(interpreter, ecx); - } - - // `expectSafeMemory`: check if the current opcode is allowed to interact with memory. - if !self.allowed_mem_writes.is_empty() { - self.check_mem_opcodes(interpreter, ecx.journaled_state.depth()); - } - - // `startMappingRecording`: record SSTORE and KECCAK256. - if let Some(mapping_slots) = &mut self.mapping_slots { - mapping::step(mapping_slots, interpreter); - } - } - - fn log(&mut self, _context: &mut EvmContext, log: &Log) { - if !self.expected_emits.is_empty() { - expect::handle_expect_emit(self, log); - } - - // `recordLogs` - if let Some(storage_recorded_logs) = &mut self.recorded_logs { - storage_recorded_logs.push(Vm::Log { - topics: log.data.topics().to_vec(), - data: log.data.data.clone(), - emitter: log.address, - }); - } - } - fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { + pub fn call_with_executor( + &mut self, + ecx: &mut EvmContext, + call: &mut CallInputs, + executor: &mut impl CheatcodesExecutor, + ) -> Option { let gas = Gas::new(call.gas_limit); // At the root call to test function or script `run()`/`setUp()` functions, we are @@ -436,7 +465,7 @@ impl Inspector for Cheatcodes { } if call.target_address == CHEATCODE_ADDRESS { - return match self.apply_cheatcode(ecx, call) { + return match self.apply_cheatcode(ecx, call, executor) { Ok(retdata) => Some(CallOutcome { result: InterpreterResult { result: InstructionResult::Return, @@ -658,6 +687,73 @@ impl Inspector for Cheatcodes { None } +} + +impl Inspector for Cheatcodes { + #[inline] + fn initialize_interp(&mut self, _interpreter: &mut Interpreter, ecx: &mut EvmContext) { + // When the first interpreter is initialized we've circumvented the balance and gas checks, + // so we apply our actual block data with the correct fees and all. + if let Some(block) = self.block.take() { + ecx.env.block = block; + } + if let Some(gas_price) = self.gas_price.take() { + ecx.env.tx.gas_price = gas_price; + } + } + + #[inline] + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.pc = interpreter.program_counter(); + + // `pauseGasMetering`: reset interpreter gas. + if self.gas_metering.is_some() { + self.meter_gas(interpreter); + } + + // `record`: record storage reads and writes. + if self.accesses.is_some() { + self.record_accesses(interpreter); + } + + // `startStateDiffRecording`: record granular ordered storage accesses. + if self.recorded_account_diffs_stack.is_some() { + self.record_state_diffs(interpreter, ecx); + } + + // `expectSafeMemory`: check if the current opcode is allowed to interact with memory. + if !self.allowed_mem_writes.is_empty() { + self.check_mem_opcodes(interpreter, ecx.journaled_state.depth()); + } + + // `startMappingRecording`: record SSTORE and KECCAK256. + if let Some(mapping_slots) = &mut self.mapping_slots { + mapping::step(mapping_slots, interpreter); + } + } + + fn log(&mut self, _context: &mut EvmContext, log: &Log) { + if !self.expected_emits.is_empty() { + expect::handle_expect_emit(self, log); + } + + // `recordLogs` + if let Some(storage_recorded_logs) = &mut self.recorded_logs { + storage_recorded_logs.push(Vm::Log { + topics: log.data.topics().to_vec(), + data: log.data.data.clone(), + emitter: log.address, + }); + } + } + + fn call( + &mut self, + context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + Self::call_with_executor(self, context, inputs, &mut TransparentCheatcodesExecutor) + } fn call_end( &mut self, @@ -1679,11 +1775,15 @@ fn append_storage_access( } /// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch(calls: &Vm::VmCalls, ccx: &mut CheatsCtxt) -> Result { +fn apply_dispatch( + calls: &Vm::VmCalls, + ccx: &mut CheatsCtxt, + executor: &mut E, +) -> Result { macro_rules! dispatch { ($($variant:ident),*) => { match calls { - $(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_full(cheat, ccx),)* + $(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_full(cheat, ccx, executor),)* } }; } diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 718b173426873..85ce8d78c51d8 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -17,7 +17,9 @@ use revm::{ContextPrecompiles, InnerEvmContext}; pub use config::CheatsConfig; pub use error::{Error, ErrorKind, Result}; -pub use inspector::{BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, Context}; +pub use inspector::{ + BroadcastableTransaction, BroadcastableTransactions, Cheatcodes, CheatcodesExecutor, Context, +}; pub use spec::{CheatcodeDef, Vm}; pub use Vm::ForgeContext; @@ -65,9 +67,21 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { self.apply(ccx.state) } + + /// Applies this cheatcode to the given context and executor. + /// + /// Implement this function if you need access to the executor. + #[inline(always)] + fn apply_full( + &self, + ccx: &mut CheatsCtxt, + _executor: &mut E, + ) -> Result { + self.apply_stateful(ccx) + } } pub(crate) trait DynCheatcode { @@ -94,6 +108,8 @@ pub(crate) struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { pub(crate) precompiles: &'evm mut ContextPrecompiles, /// The original `msg.sender`. pub(crate) caller: Address, + /// Gas limit of the current cheatcode call. + pub(crate) gas_limit: u64, } impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'evm, DB> { diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 1f84e2475f815..af4457f8edb10 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -8,49 +8,49 @@ use parking_lot::Mutex; use std::sync::Arc; impl Cheatcode for broadcast_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, true) } } impl Cheatcode for broadcast_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } impl Cheatcode for broadcast_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } impl Cheatcode for startBroadcast_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, false) } } impl Cheatcode for startBroadcast_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } impl Cheatcode for startBroadcast_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } impl Cheatcode for stopBroadcastCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index cab8b9f8b430d..4bccabda253e2 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -20,14 +20,14 @@ impl Cheatcode for assumeCall { } impl Cheatcode for breakpoint_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } @@ -64,7 +64,7 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skipCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { skipTest } = *self; if skipTest { // Skip should not work if called deeper than at test level. diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index eba665856b117..9c070a7ca8874 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -197,7 +197,7 @@ impl Cheatcode for expectCallMinGas_1Call { } impl Cheatcode for expectEmit_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, @@ -209,7 +209,7 @@ impl Cheatcode for expectEmit_0Call { } impl Cheatcode for expectEmit_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, @@ -221,69 +221,69 @@ impl Cheatcode for expectEmit_1Call { } impl Cheatcode for expectEmit_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], None) } } impl Cheatcode for expectEmit_3Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 4], Some(emitter)) } } impl Cheatcode for expectRevert_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for expectRevert_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), false) } } impl Cheatcode for _expectCheatcodeRevert_0Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData.as_ref()), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for _expectCheatcodeRevert_2Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert(ccx.state, Some(revertData), ccx.ecx.journaled_state.depth(), true) } } impl Cheatcode for expectSafeMemoryCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) } } impl Cheatcode for stopExpectSafeMemoryCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); Ok(Default::default()) @@ -291,7 +291,7 @@ impl Cheatcode for stopExpectSafeMemoryCall { } impl Cheatcode for expectSafeMemoryCallCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index 733ed36837279..8bea510eb9a7c 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -46,14 +46,14 @@ impl Cheatcode for createWallet_2Call { } impl Cheatcode for getNonce_1Call { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { wallet } = self; super::evm::get_nonce(ccx, &wallet.addr) } } impl Cheatcode for sign_3Call { - fn apply_full(&self, _: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, _: &mut CheatsCtxt) -> Result { let Self { wallet, digest } = self; sign(&wallet.privateKey, digest) } @@ -88,7 +88,7 @@ impl Cheatcode for deriveKey_3Call { } impl Cheatcode for rememberKeyCall { - fn apply_full(&self, ccx: &mut CheatsCtxt) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; let wallet = parse_wallet(privateKey)?; let address = wallet.address(); diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 766070e997f5e..2866f4ec14c80 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -178,13 +178,13 @@ impl<'a> DatabaseExt for CowBackend<'a> { self.backend_mut(env).roll_fork_to_transaction(id, transaction, env, journaled_state) } - fn transact>( + fn transact( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { self.backend_mut(env).transact(id, transaction, env, journaled_state, inspector) } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index a59bcdc5b01a7..04ce60c027385 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -66,7 +66,7 @@ const GLOBAL_FAILURE_SLOT: B256 = /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] -pub trait DatabaseExt: Database { +pub trait DatabaseExt: Database + DatabaseCommit { /// Creates a new snapshot at the current point of execution. /// /// A snapshot is associated with a new unique id that's created for the snapshot. @@ -190,16 +190,14 @@ pub trait DatabaseExt: Database { ) -> eyre::Result<()>; /// Fetches the given transaction for the fork and executes it, committing the state in the DB - fn transact>( + fn transact( &mut self, id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, - ) -> eyre::Result<()> - where - Self: Sized; + inspector: &mut dyn InspectorExt, + ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on fn active_fork_id(&self) -> Option; @@ -277,7 +275,8 @@ pub trait DatabaseExt: Database { /// Marks the given account as persistent. fn add_persistent_account(&mut self, account: Address) -> bool; - /// Removes persistent status from all given accounts + /// Removes persistent status from all given accounts. + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] fn remove_persistent_accounts(&mut self, accounts: impl IntoIterator) where Self: Sized, @@ -288,6 +287,7 @@ pub trait DatabaseExt: Database { } /// Extends the persistent accounts with the accounts the iterator yields. + #[auto_impl(keep_default_for(&, &mut, Rc, Arc, Box))] fn extend_persistent_accounts(&mut self, accounts: impl IntoIterator) where Self: Sized, @@ -1249,13 +1249,13 @@ impl DatabaseExt for Backend { Ok(()) } - fn transact>( + fn transact( &mut self, maybe_id: Option, transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1907,10 +1907,13 @@ fn commit_transaction>( let res = { let fork = fork.clone(); let journaled_state = journaled_state.clone(); + let depth = journaled_state.depth; let db = Backend::new_with_fork(fork_id, fork, journaled_state); - crate::utils::new_evm_with_inspector(db, env, inspector) - .transact() - .wrap_err("backend: failed committing transaction")? + + let mut evm = crate::utils::new_evm_with_inspector(db, env, inspector); + // Adjust inner EVM depth to ensure that inspectors receive accurate data. + evm.context.evm.inner.journaled_state.depth = depth + 1; + evm.transact().wrap_err("backend: failed committing transaction")? }; trace!(elapsed = ?now.elapsed(), "transacted transaction"); diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 58a088c06a4b7..2fc202c0dc189 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,7 +1,7 @@ pub use crate::ic::*; use crate::{constants::DEFAULT_CREATE2_DEPLOYER, InspectorExt}; use alloy_json_abi::{Function, JsonAbi}; -use alloy_primitives::{Address, Selector, U256}; +use alloy_primitives::{Address, Selector, TxKind, U256}; use alloy_rpc_types::{Block, Transaction}; use foundry_config::NamedChain; use revm::{ @@ -11,7 +11,7 @@ use revm::{ return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, Gas, InstructionResult, InterpreterResult, }, - primitives::{CreateScheme, EVMError, SpecId, TxKind, KECCAK_EMPTY}, + primitives::{CreateScheme, EVMError, HandlerCfg, SpecId, KECCAK_EMPTY}, FrameOrResult, FrameResult, }; use std::{cell::RefCell, rc::Rc, sync::Arc}; @@ -267,6 +267,23 @@ where new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) } +pub fn new_evm_with_existing_context<'a, DB, I>( + inner: revm::InnerEvmContext, + inspector: I, +) -> revm::Evm<'a, I, DB> +where + DB: revm::Database, + I: InspectorExt, +{ + let handler_cfg = HandlerCfg::new(inner.spec_id()); + let context = + revm::Context::new(revm::EvmContext { inner, precompiles: Default::default() }, inspector); + let mut handler = revm::Handler::new(handler_cfg); + handler.append_handler_register_plain(revm::inspector_handle_register); + handler.append_handler_register_plain(create2_handler_register); + revm::Evm::new(context, handler) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 8e235efe83de6..e290a55fc7c15 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -2,7 +2,8 @@ use super::{ Cheatcodes, CheatsConfig, ChiselState, CoverageCollector, Debugger, Fuzzer, LogCollector, StackSnapshotType, TracingInspector, TracingInspectorConfig, }; -use alloy_primitives::{Address, Bytes, Log, U256}; +use alloy_primitives::{Address, Bytes, Log, TxKind, U256}; +use foundry_cheatcodes::CheatcodesExecutor; use foundry_evm_core::{ backend::{update_state, DatabaseExt}, debug::DebugArena, @@ -16,10 +17,16 @@ use revm::{ CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, Interpreter, InterpreterResult, }, - primitives::{BlockEnv, CreateScheme, Env, EnvWithHandlerCfg, ExecutionResult, Output, TxKind}, - DatabaseCommit, EvmContext, Inspector, + primitives::{ + BlockEnv, CreateScheme, Env, EnvWithHandlerCfg, ExecutionResult, Output, TransactTo, + }, + EvmContext, Inspector, +}; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, + sync::Arc, }; -use std::{collections::HashMap, sync::Arc}; #[derive(Clone, Debug, Default)] #[must_use = "builders do nothing unless you call `build` on them"] @@ -264,9 +271,23 @@ pub struct InnerContextData { /// /// If a call to an inspector returns a value other than [InstructionResult::Continue] (or /// equivalent) the remaining inspectors are not called. +/// +/// Stack is divided into [Cheatcodes] and `InspectorStackInner`. This is done to allow assembling +/// `InspectorStackRefMut` inside [Cheatcodes] to allow usage of it as [revm::Inspector]. This gives +/// us ability to create and execute separate EVM frames from inside cheatcodes while still having +/// access to entire stack of inspectors and correctly handling traces, logs, debugging info +/// collection, etc. #[derive(Clone, Debug, Default)] pub struct InspectorStack { pub cheatcodes: Option, + pub inner: InspectorStackInner, +} + +/// All used inpectors besides [Cheatcodes]. +/// +/// See [`InspectorStack`]. +#[derive(Default, Clone, Debug)] +pub struct InspectorStackInner { pub chisel_state: Option, pub coverage: Option, pub debugger: Option, @@ -281,6 +302,23 @@ pub struct InspectorStack { pub inner_context_data: Option, } +/// Struct keeping mutable references to both parts of [InspectorStack] and implementing +/// [revm::Inspector]. This struct can be obtained via [InspectorStack::as_mut] or via +/// [CheatcodesExecutor::get_inspector] method implemented for [InspectorStackInner]. +pub struct InspectorStackRefMut<'a> { + pub cheatcodes: Option<&'a mut Cheatcodes>, + pub inner: &'a mut InspectorStackInner, +} + +impl CheatcodesExecutor for InspectorStackInner { + fn get_inspector<'a, DB: DatabaseExt>( + &'a mut self, + cheats: &'a mut Cheatcodes, + ) -> impl InspectorExt + 'a { + InspectorStackRefMut { cheatcodes: Some(cheats), inner: self } + } +} + impl InspectorStack { /// Creates a new inspector stack. /// @@ -402,24 +440,53 @@ impl InspectorStack { /// Collects all the data gathered during inspection into a single struct. #[inline] pub fn collect(self) -> InspectorData { + let Self { + cheatcodes, + inner: + InspectorStackInner { chisel_state, coverage, debugger, log_collector, tracer, .. }, + } = self; + InspectorData { - logs: self.log_collector.map(|logs| logs.logs).unwrap_or_default(), - labels: self - .cheatcodes + logs: log_collector.map(|logs| logs.logs).unwrap_or_default(), + labels: cheatcodes .as_ref() .map(|cheatcodes| cheatcodes.labels.clone()) .unwrap_or_default(), - traces: self.tracer.map(|tracer| tracer.get_traces().clone()), - debug: self.debugger.map(|debugger| debugger.arena), - coverage: self.coverage.map(|coverage| coverage.maps), - cheatcodes: self.cheatcodes, - chisel_state: self.chisel_state.and_then(|state| state.state), + traces: tracer.map(|tracer| tracer.get_traces().clone()), + debug: debugger.map(|debugger| debugger.arena), + coverage: coverage.map(|coverage| coverage.maps), + cheatcodes, + chisel_state: chisel_state.and_then(|state| state.state), } } + fn as_mut(&mut self) -> InspectorStackRefMut<'_> { + InspectorStackRefMut { cheatcodes: self.cheatcodes.as_mut(), inner: &mut self.inner } + } +} + +impl<'a> InspectorStackRefMut<'a> { + /// Adjusts the EVM data for the inner EVM context. + /// Should be called on the top-level call of inner context (depth == 0 && + /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility + /// Updates tx.origin to the value before entering inner context + fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EvmContext) { + let inner_context_data = + self.inner_context_data.as_ref().expect("should be called in inner context"); + let sender_acc = ecx + .journaled_state + .state + .get_mut(&inner_context_data.sender) + .expect("failed to load sender"); + if !inner_context_data.is_create { + sender_acc.info.nonce = inner_context_data.original_sender_nonce; + } + ecx.env.tx.caller = inner_context_data.original_origin; + } + fn do_call_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { @@ -450,10 +517,10 @@ impl InspectorStack { outcome } - fn transact_inner( + fn transact_inner( &mut self, - ecx: &mut EvmContext<&mut DB>, - transact_to: TxKind, + ecx: &mut EvmContext, + transact_to: TransactTo, caller: Address, input: Bytes, gas_limit: u64, @@ -499,7 +566,11 @@ impl InspectorStack { let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); let res = { - let mut evm = crate::utils::new_evm_with_inspector(&mut *ecx.db, env, &mut *self); + let mut evm = crate::utils::new_evm_with_inspector( + &mut ecx.db as &mut dyn DatabaseExt, + env, + &mut *self, + ); let res = evm.transact(); // need to reset the env in case it was modified via cheatcodes during execution @@ -576,36 +647,10 @@ impl InspectorStack { }; (InterpreterResult { result, output, gas }, address) } - - /// Adjusts the EVM data for the inner EVM context. - /// Should be called on the top-level call of inner context (depth == 0 && - /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility - /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context( - &mut self, - ecx: &mut EvmContext<&mut DB>, - ) { - let inner_context_data = - self.inner_context_data.as_ref().expect("should be called in inner context"); - let sender_acc = ecx - .journaled_state - .state - .get_mut(&inner_context_data.sender) - .expect("failed to load sender"); - if !inner_context_data.is_create { - sender_acc.info.nonce = inner_context_data.original_sender_nonce; - } - ecx.env.tx.caller = inner_context_data.original_origin; - } } -// NOTE: `&mut DB` is required because we recurse inside of `transact_inner` and we need to use the -// same reference to the DB, otherwise there's infinite recursion and Rust fails to instatiate this -// implementation. This currently works because internally we only use `&mut DB` anyways, but if -// this ever needs to be changed, this can be reverted back to using just `DB`, and instead using -// dynamic dispatch (`&mut dyn ...`) in `transact_inner`. -impl Inspector<&mut DB> for InspectorStack { - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { +impl<'a, DB: DatabaseExt> Inspector for InspectorStackRefMut<'a> { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.initialize_interp(interpreter, ecx), @@ -614,7 +659,7 @@ impl Inspector<&mut DB> for InspectorStack { ); } - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( [ &mut self.fuzzer, @@ -630,7 +675,7 @@ impl Inspector<&mut DB> for InspectorStack { ); } - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut DB>) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { call_inspectors_adjust_depth!( [&mut self.tracer, &mut self.chisel_state, &mut self.printer], |inspector| inspector.step_end(interpreter, ecx), @@ -639,7 +684,7 @@ impl Inspector<&mut DB> for InspectorStack { ); } - fn log(&mut self, ecx: &mut EvmContext<&mut DB>, log: &Log) { + fn log(&mut self, ecx: &mut EvmContext, log: &Log) { call_inspectors_adjust_depth!( [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.log(ecx, log), @@ -648,11 +693,7 @@ impl Inspector<&mut DB> for InspectorStack { ); } - fn call( - &mut self, - ecx: &mut EvmContext<&mut DB>, - call: &mut CallInputs, - ) -> Option { + fn call(&mut self, ecx: &mut EvmContext, call: &mut CallInputs) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 0 { self.adjust_evm_data_for_inner_context(ecx); return None; @@ -665,7 +706,6 @@ impl Inspector<&mut DB> for InspectorStack { &mut self.debugger, &mut self.tracer, &mut self.log_collector, - &mut self.cheatcodes, &mut self.printer, ], |inspector| { @@ -681,6 +721,14 @@ impl Inspector<&mut DB> for InspectorStack { ecx ); + if let Some(cheatcodes) = self.cheatcodes.as_deref_mut() { + if let Some(output) = cheatcodes.call_with_executor(ecx, call, self.inner) { + if output.result.result != InstructionResult::Continue { + return Some(output) + } + } + } + if self.enable_isolation && call.scheme == CallScheme::Call && !self.in_inner_context && @@ -702,7 +750,7 @@ impl Inspector<&mut DB> for InspectorStack { fn call_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { @@ -727,7 +775,7 @@ impl Inspector<&mut DB> for InspectorStack { fn create( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, create: &mut CreateInputs, ) -> Option { if self.in_inner_context && ecx.journaled_state.depth == 0 { @@ -764,7 +812,7 @@ impl Inspector<&mut DB> for InspectorStack { fn create_end( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, call: &CreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { @@ -803,10 +851,10 @@ impl Inspector<&mut DB> for InspectorStack { } } -impl InspectorExt<&mut DB> for InspectorStack { +impl<'a, DB: DatabaseExt> InspectorExt for InspectorStackRefMut<'a> { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<&mut DB>, + ecx: &mut EvmContext, inputs: &mut CreateInputs, ) -> bool { call_inspectors_adjust_depth!( @@ -820,3 +868,97 @@ impl InspectorExt<&mut DB> for InspectorStack false } } + +impl Inspector for InspectorStack { + fn call( + &mut self, + context: &mut EvmContext, + inputs: &mut CallInputs, + ) -> Option { + self.as_mut().call(context, inputs) + } + + fn call_end( + &mut self, + context: &mut EvmContext, + inputs: &CallInputs, + outcome: CallOutcome, + ) -> CallOutcome { + self.as_mut().call_end(context, inputs, outcome) + } + + fn create( + &mut self, + context: &mut EvmContext, + create: &mut CreateInputs, + ) -> Option { + self.as_mut().create(context, create) + } + + fn create_end( + &mut self, + context: &mut EvmContext, + call: &CreateInputs, + outcome: CreateOutcome, + ) -> CreateOutcome { + self.as_mut().create_end(context, call, outcome) + } + + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().initialize_interp(interpreter, ecx) + } + + fn log(&mut self, ecx: &mut EvmContext, log: &Log) { + self.as_mut().log(ecx, log) + } + + fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { + Inspector::::selfdestruct(&mut self.as_mut(), contract, target, value) + } + + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().step(interpreter, ecx) + } + + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + self.as_mut().step_end(interpreter, ecx) + } +} + +impl InspectorExt for InspectorStack { + fn should_use_create2_factory( + &mut self, + ecx: &mut EvmContext, + inputs: &mut CreateInputs, + ) -> bool { + self.as_mut().should_use_create2_factory(ecx, inputs) + } +} + +impl<'a> Deref for InspectorStackRefMut<'a> { + type Target = &'a mut InspectorStackInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for InspectorStackRefMut<'_> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +impl Deref for InspectorStack { + type Target = InspectorStackInner; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for InspectorStack { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 7712aa9a6f2a5..537569745aaf4 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -318,7 +318,7 @@ async fn test_shrink_big_sequence() { let mut runner = TEST_DATA_DEFAULT.runner(); runner.test_options.fuzz.seed = Some(U256::from(119u32)); runner.test_options.invariant.runs = 1; - runner.test_options.invariant.depth = 500; + runner.test_options.invariant.depth = 1000; let initial_counterexample = runner .test_collect(&filter) diff --git a/crates/forge/tests/it/main.rs b/crates/forge/tests/it/main.rs index 48c0d66351ab7..aaa129796a39a 100644 --- a/crates/forge/tests/it/main.rs +++ b/crates/forge/tests/it/main.rs @@ -10,3 +10,4 @@ mod inline; mod invariant; mod repros; mod spec; +mod vyper; diff --git a/crates/forge/tests/it/test_helpers.rs b/crates/forge/tests/it/test_helpers.rs index cacfca10caf3b..27143e90ca61a 100644 --- a/crates/forge/tests/it/test_helpers.rs +++ b/crates/forge/tests/it/test_helpers.rs @@ -7,7 +7,8 @@ use forge::{ }; use foundry_compilers::{ artifacts::{EvmVersion, Libraries, Settings}, - Project, ProjectCompileOutput, SolcConfig, + utils::RuntimeOrHandle, + Project, ProjectCompileOutput, SolcConfig, Vyper, }; use foundry_config::{ fs_permissions::PathPermission, Config, FsPermissions, FuzzConfig, FuzzDictionaryConfig, @@ -28,6 +29,7 @@ use std::{ pub const RE_PATH_SEPARATOR: &str = "/"; const TESTDATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata"); +static VYPER: Lazy = Lazy::new(|| std::env::temp_dir().join("vyper")); /// Profile for the tests group. Used to configure separate configurations for test runs. pub enum ForgeTestProfile { @@ -173,8 +175,8 @@ impl ForgeTestData { /// /// Uses [get_compiled] to lazily compile the project. pub fn new(profile: ForgeTestProfile) -> Self { - let project = profile.project(); - let output = get_compiled(&project); + let mut project = profile.project(); + let output = get_compiled(&mut project); let test_opts = profile.test_opts(&output); let config = profile.config(); let evm_opts = profile.evm_opts(); @@ -259,7 +261,41 @@ impl ForgeTestData { } } -pub fn get_compiled(project: &Project) -> ProjectCompileOutput { +/// Installs Vyper if it's not already present. +pub fn get_vyper() -> Vyper { + if let Ok(vyper) = Vyper::new("vyper") { + return vyper; + } + if let Ok(vyper) = Vyper::new(&*VYPER) { + return vyper; + } + RuntimeOrHandle::new().block_on(async { + #[cfg(target_family = "unix")] + use std::{fs::Permissions, os::unix::fs::PermissionsExt}; + + let url = match svm::platform() { + svm::Platform::MacOsAarch64 => "https://github.com/vyperlang/vyper/releases/download/v0.4.0rc6/vyper.0.4.0rc6+commit.33719560.darwin", + svm::Platform::LinuxAmd64 => "https://github.com/vyperlang/vyper/releases/download/v0.4.0rc6/vyper.0.4.0rc6+commit.33719560.linux", + svm::Platform::WindowsAmd64 => "https://github.com/vyperlang/vyper/releases/download/v0.4.0rc6/vyper.0.4.0rc6+commit.33719560.windows.exe", + _ => panic!("unsupported") + }; + + let res = reqwest::Client::builder().build().unwrap().get(url).send().await.unwrap(); + + assert!(res.status().is_success()); + + let bytes = res.bytes().await.unwrap(); + + std::fs::write(&*VYPER, bytes).unwrap(); + + #[cfg(target_family = "unix")] + std::fs::set_permissions(&*VYPER, Permissions::from_mode(0o755)).unwrap(); + + Vyper::new(&*VYPER).unwrap() + }) +} + +pub fn get_compiled(project: &mut Project) -> ProjectCompileOutput { let lock_file_path = project.sources_path().join(".lock"); // Compile only once per test run. // We need to use a file lock because `cargo-nextest` runs tests in different processes. @@ -268,21 +304,27 @@ pub fn get_compiled(project: &Project) -> ProjectCompileOutput { let mut lock = fd_lock::new_lock(&lock_file_path); let read = lock.read().unwrap(); let out; - if project.cache_path().exists() && std::fs::read(&lock_file_path).unwrap() == b"1" { - out = project.compile(); - drop(read); - } else { + + let mut write = None; + if !project.cache_path().exists() || std::fs::read(&lock_file_path).unwrap() != b"1" { drop(read); - let mut write = lock.write().unwrap(); - write.write_all(b"1").unwrap(); - out = project.compile(); - drop(write); + write = Some(lock.write().unwrap()); + } + + if project.compiler.vyper.is_none() { + project.compiler.vyper = Some(get_vyper()); } - let out = out.unwrap(); + out = project.compile().unwrap(); + if out.has_compiler_errors() { panic!("Compiled with errors:\n{out}"); } + + if let Some(ref mut write) = write { + write.write_all(b"1").unwrap(); + } + out } diff --git a/crates/forge/tests/it/vyper.rs b/crates/forge/tests/it/vyper.rs new file mode 100644 index 0000000000000..c40b87541bfb9 --- /dev/null +++ b/crates/forge/tests/it/vyper.rs @@ -0,0 +1,10 @@ +//! Integration tests for EVM specifications. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_basic_vyper_test() { + let filter = Filter::new("", "CounterTest", ".*vyper"); + TestConfig::with_filter(TEST_DATA_DEFAULT.runner(), filter).run().await; +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 23e1f699c89d1..4a0ec81c60790 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -173,6 +173,8 @@ interface Vm { function deal(address account, uint256 newBalance) external; function deleteSnapshot(uint256 snapshotId) external returns (bool success); function deleteSnapshots() external; + function deployCode(string calldata artifactPath) external returns (address deployedAddress); + function deployCode(string calldata artifactPath, bytes calldata constructorArgs) external returns (address deployedAddress); function deriveKey(string calldata mnemonic, uint32 index) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, string calldata derivationPath, uint32 index) external pure returns (uint256 privateKey); function deriveKey(string calldata mnemonic, uint32 index, string calldata language) external pure returns (uint256 privateKey); diff --git a/testdata/default/cheats/DeployCode.t.sol b/testdata/default/cheats/DeployCode.t.sol new file mode 100644 index 0000000000000..330e826511da7 --- /dev/null +++ b/testdata/default/cheats/DeployCode.t.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract TestContract {} + +contract TestContractWithArgs { + uint256 public a; + uint256 public b; + + constructor(uint256 _a, uint256 _b) { + a = _a; + b = _b; + } +} + +contract DeployCodeTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + address public constant overrideAddress = 0x0000000000000000000000000000000000000064; + + event Payload(address sender, address target, bytes data); + + function testDeployCode() public { + address addrDefault = address(new TestContract()); + address addrDeployCode = vm.deployCode("cheats/DeployCode.t.sol:TestContract"); + + assertEq(addrDefault.code, addrDeployCode.code); + } + + function testDeployCodeWithArgs() public { + address withNew = address(new TestContractWithArgs(1, 2)); + TestContractWithArgs withDeployCode = + TestContractWithArgs(vm.deployCode("cheats/DeployCode.t.sol:TestContractWithArgs", abi.encode(3, 4))); + + assertEq(withNew.code, address(withDeployCode).code); + assertEq(withDeployCode.a(), 3); + assertEq(withDeployCode.b(), 4); + } +} diff --git a/testdata/default/vyper/Counter.vy b/testdata/default/vyper/Counter.vy new file mode 100644 index 0000000000000..772bddd11919c --- /dev/null +++ b/testdata/default/vyper/Counter.vy @@ -0,0 +1,12 @@ +from . import ICounter +implements: ICounter + +number: public(uint256) + +@external +def set_number(new_number: uint256): + self.number = new_number + +@external +def increment(): + self.number += 1 diff --git a/testdata/default/vyper/CounterTest.vy b/testdata/default/vyper/CounterTest.vy new file mode 100644 index 0000000000000..b6cc517d25dd6 --- /dev/null +++ b/testdata/default/vyper/CounterTest.vy @@ -0,0 +1,16 @@ +from . import ICounter + +interface Vm: + def deployCode(artifact_name: String[1024], args: Bytes[1024] = b"") -> address: nonpayable + +vm: constant(Vm) = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D) +counter: ICounter + +@external +def setUp(): + self.counter = ICounter(extcall vm.deployCode("vyper/Counter.vy")) + +@external +def test_increment(): + extcall self.counter.increment() + assert staticcall self.counter.number() == 1 diff --git a/testdata/default/vyper/ICounter.vyi b/testdata/default/vyper/ICounter.vyi new file mode 100644 index 0000000000000..e600c71c87e19 --- /dev/null +++ b/testdata/default/vyper/ICounter.vyi @@ -0,0 +1,12 @@ +@view +@external +def number() -> uint256: + ... + +@external +def set_number(new_number: uint256): + ... + +@external +def increment(): + ... \ No newline at end of file