diff --git a/Cargo.lock b/Cargo.lock index 4726ad0420a0..2d8c6f9be2b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b68b94c159bcc2ca5f758b8663d7b00fc7c5e40569984595ddf2221b0f7f7f6e" +checksum = "abf770dad29577cd3580f3dd09005799224a912b8cdfdd6dc04d030d42b3df4e" dependencies = [ "num_enum", "serde", @@ -6292,9 +6292,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" dependencies = [ "memchr", "thiserror", @@ -6303,9 +6303,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" dependencies = [ "pest", "pest_generator", @@ -6313,9 +6313,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" dependencies = [ "pest", "pest_meta", @@ -6326,9 +6326,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.12" +version = "2.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" dependencies = [ "once_cell", "pest", @@ -9105,15 +9105,15 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-xid" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index f856fef42c5a..71db35a4e2e2 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -2059,7 +2059,7 @@ impl EthApi { /// Handler for RPC call: `evm_snapshot` pub async fn evm_snapshot(&self) -> Result { node_info!("evm_snapshot"); - Ok(self.backend.create_snapshot().await) + Ok(self.backend.create_state_snapshot().await) } /// Revert the state of the blockchain to a previous snapshot. @@ -2068,7 +2068,7 @@ impl EthApi { /// Handler for RPC call: `evm_revert` pub async fn evm_revert(&self, id: U256) -> Result { node_info!("evm_revert"); - self.backend.revert_snapshot(id).await + self.backend.revert_state_snapshot(id).await } /// Jump forward in time by the given amount of time, in seconds. diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 60565c4d5467..cfd28377a7c5 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -11,7 +11,8 @@ use anvil_core::eth::{ use foundry_common::errors::FsPathError; use foundry_evm::{ backend::{ - BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertSnapshotAction, StateSnapshot, + BlockchainDb, DatabaseError, DatabaseResult, MemDb, RevertStateSnapshotAction, + StateSnapshot, }, revm::{ db::{CacheDB, DatabaseRef, DbAccount}, @@ -171,13 +172,13 @@ pub trait Db: Ok(true) } - /// Creates a new snapshot - fn snapshot(&mut self) -> U256; + /// Creates a new state snapshot + fn snapshot_state(&mut self) -> U256; - /// Reverts a snapshot + /// Reverts a state snapshot /// /// Returns `true` if the snapshot was reverted - fn revert(&mut self, snapshot: U256, action: RevertSnapshotAction) -> bool; + fn revert_state_snapshot(&mut self, snapshot: U256, action: RevertStateSnapshotAction) -> bool; /// Returns the state root if possible to compute fn maybe_state_root(&self) -> Option { @@ -216,11 +217,15 @@ impl + Send + Sync + Clone + fmt::Debug> D Ok(None) } - fn snapshot(&mut self) -> U256 { + fn snapshot_state(&mut self) -> U256 { U256::ZERO } - fn revert(&mut self, _snapshot: U256, _action: RevertSnapshotAction) -> bool { + fn revert_state_snapshot( + &mut self, + _snapshot: U256, + _action: RevertStateSnapshotAction, + ) -> bool { false } diff --git a/crates/anvil/src/eth/backend/mem/fork_db.rs b/crates/anvil/src/eth/backend/mem/fork_db.rs index 1329de724a1c..84fb4e578219 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -8,7 +8,7 @@ use crate::{ use alloy_primitives::{Address, B256, U256, U64}; use alloy_rpc_types::BlockId; use foundry_evm::{ - backend::{BlockchainDb, DatabaseResult, RevertSnapshotAction, StateSnapshot}, + backend::{BlockchainDb, DatabaseResult, RevertStateSnapshotAction, StateSnapshot}, fork::database::ForkDbSnapshot, revm::Database, }; @@ -73,16 +73,16 @@ impl Db for ForkedDatabase { })) } - fn snapshot(&mut self) -> U256 { - self.insert_snapshot() + fn snapshot_state(&mut self) -> U256 { + self.insert_state_snapshot() } - fn revert(&mut self, id: U256, action: RevertSnapshotAction) -> bool { - self.revert_snapshot(id, action) + fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { + self.revert_state_snapshot(id, action) } fn current_state(&self) -> StateDb { - StateDb::new(self.create_snapshot()) + StateDb::new(self.create_state_snapshot()) } } diff --git a/crates/anvil/src/eth/backend/mem/in_memory_db.rs b/crates/anvil/src/eth/backend/mem/in_memory_db.rs index 56cd3815cb08..a988cfd62ef5 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -17,7 +17,7 @@ use foundry_evm::{ // reexport for convenience pub use foundry_evm::{backend::MemDb, revm::db::DatabaseRef}; -use foundry_evm::{backend::RevertSnapshotAction, revm::primitives::BlockEnv}; +use foundry_evm::{backend::RevertStateSnapshotAction, revm::primitives::BlockEnv}; impl Db for MemDb { fn insert_account(&mut self, address: Address, account: AccountInfo) { @@ -73,14 +73,15 @@ impl Db for MemDb { })) } - /// Creates a new snapshot - fn snapshot(&mut self) -> U256 { + /// Creates a new state snapshot + fn snapshot_state(&mut self) -> U256 { let id = self.snapshots.insert(self.inner.clone()); trace!(target: "backend::memdb", "Created new snapshot {}", id); id } - fn revert(&mut self, id: U256, action: RevertSnapshotAction) -> bool { + /// Reverts to a previous state snapshot + fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { if let Some(snapshot) = self.snapshots.remove(id) { if action.is_keep() { self.snapshots.insert_at(snapshot.clone(), id); diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 0f28e28a5645..2a2253950766 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -68,7 +68,7 @@ use anvil_rpc::error::RpcError; use alloy_chains::NamedChain; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ - backend::{DatabaseError, DatabaseResult, RevertSnapshotAction}, + backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, constants::DEFAULT_CREATE2_DEPLOYER_RUNTIME_CODE, decode::RevertDecoder, inspectors::AccessListInspector, @@ -693,20 +693,20 @@ impl Backend { self.blockchain.storage.read().total_difficulty } - /// Creates a new `evm_snapshot` at the current height + /// Creates a new `evm_snapshot` at the current height. /// /// Returns the id of the snapshot created - pub async fn create_snapshot(&self) -> U256 { + pub async fn create_state_snapshot(&self) -> U256 { let num = self.best_number(); let hash = self.best_hash(); - let id = self.db.write().await.snapshot(); + let id = self.db.write().await.snapshot_state(); trace!(target: "backend", "creating snapshot {} at {}", id, num); self.active_snapshots.lock().insert(id, (num, hash)); id } /// Reverts the state to the snapshot identified by the given `id`. - pub async fn revert_snapshot(&self, id: U256) -> Result { + pub async fn revert_state_snapshot(&self, id: U256) -> Result { let block = { self.active_snapshots.lock().remove(&id) }; if let Some((num, hash)) = block { let best_block_hash = { @@ -750,7 +750,7 @@ impl Backend { ..Default::default() }; } - Ok(self.db.write().await.revert(id, RevertSnapshotAction::RevertRemove)) + Ok(self.db.write().await.revert_state_snapshot(id, RevertStateSnapshotAction::RevertRemove)) } pub fn list_snapshots(&self) -> BTreeMap { diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 4d337ff5ecba..7ad90736c78a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -472,11 +472,6 @@ "ty": "uint64", "description": "The total gas used." }, - { - "name": "gasMemoryUsed", - "ty": "uint64", - "description": "DEPRECATED: The amount of gas used for memory expansion. Ref: " - }, { "name": "gasRefunded", "ty": "int64", @@ -3574,7 +3569,7 @@ { "func": { "id": "deleteSnapshot", - "description": "Removes the snapshot with the given ID created by `snapshot`.\nTakes the snapshot ID to delete.\nReturns `true` if the snapshot was successfully deleted.\nReturns `false` if the snapshot does not exist.", + "description": "`deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions.", "declaration": "function deleteSnapshot(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", @@ -3588,13 +3583,15 @@ ] }, "group": "evm", - "status": "stable", + "status": { + "deprecated": "replaced by `deleteStateSnapshot`" + }, "safety": "unsafe" }, { "func": { "id": "deleteSnapshots", - "description": "Removes _all_ snapshots previously created by `snapshot`.", + "description": "`deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions.", "declaration": "function deleteSnapshots() external;", "visibility": "external", "mutability": "", @@ -3608,6 +3605,48 @@ ] }, "group": "evm", + "status": { + "deprecated": "replaced by `deleteStateSnapshots`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "deleteStateSnapshot", + "description": "Removes the snapshot with the given ID created by `snapshot`.\nTakes the snapshot ID to delete.\nReturns `true` if the snapshot was successfully deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function deleteStateSnapshot(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "deleteStateSnapshot(uint256)", + "selector": "0x08d6b37a", + "selectorBytes": [ + 8, + 214, + 179, + 122 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteStateSnapshots", + "description": "Removes _all_ snapshots previously created by `snapshot`.", + "declaration": "function deleteStateSnapshots() external;", + "visibility": "external", + "mutability": "", + "signature": "deleteStateSnapshots()", + "selector": "0xe0933c74", + "selectorBytes": [ + 224, + 147, + 60, + 116 + ] + }, + "group": "evm", "status": "stable", "safety": "unsafe" }, @@ -7256,7 +7295,7 @@ { "func": { "id": "revertTo", - "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted.\nReturns `false` if the snapshot does not exist.\n**Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot`.", + "description": "`revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions.", "declaration": "function revertTo(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", @@ -7270,13 +7309,15 @@ ] }, "group": "evm", - "status": "stable", + "status": { + "deprecated": "replaced by `revertToState`" + }, "safety": "unsafe" }, { "func": { "id": "revertToAndDelete", - "description": "Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted and deleted.\nReturns `false` if the snapshot does not exist.", + "description": "`revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions.", "declaration": "function revertToAndDelete(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", @@ -7290,6 +7331,48 @@ ] }, "group": "evm", + "status": { + "deprecated": "replaced by `revertToStateAndDelete`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "revertToState", + "description": "Revert the state of the EVM to a previous snapshot\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted.\nReturns `false` if the snapshot does not exist.\n**Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`.", + "declaration": "function revertToState(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToState(uint256)", + "selector": "0xc2527405", + "selectorBytes": [ + 194, + 82, + 116, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "revertToStateAndDelete", + "description": "Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots\nTakes the snapshot ID to revert to.\nReturns `true` if the snapshot was successfully reverted and deleted.\nReturns `false` if the snapshot does not exist.", + "declaration": "function revertToStateAndDelete(uint256 snapshotId) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "revertToStateAndDelete(uint256)", + "selector": "0x3a1985dc", + "selectorBytes": [ + 58, + 25, + 133, + 220 + ] + }, + "group": "evm", "status": "stable", "safety": "unsafe" }, @@ -8256,7 +8339,7 @@ { "func": { "id": "snapshot", - "description": "Snapshot the current state of the evm.\nReturns the ID of the snapshot that was created.\nTo revert a snapshot use `revertTo`.", + "description": "`snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions.", "declaration": "function snapshot() external returns (uint256 snapshotId);", "visibility": "external", "mutability": "", @@ -8270,6 +8353,108 @@ ] }, "group": "evm", + "status": { + "deprecated": "replaced by `snapshotState`" + }, + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotGasLastCall_0", + "description": "Snapshot capture the gas usage of the last call by name.", + "declaration": "function snapshotGasLastCall(string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string)", + "selector": "0xdd9fca12", + "selectorBytes": [ + 221, + 159, + 202, + 18 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotGasLastCall_1", + "description": "Snapshot capture the gas usage of the last call by name in a group.", + "declaration": "function snapshotGasLastCall(string calldata group, string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string,string)", + "selector": "0x200c6772", + "selectorBytes": [ + 32, + 12, + 103, + 114 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotState", + "description": "Snapshot the current state of the evm.\nReturns the ID of the snapshot that was created.\nTo revert a snapshot use `revertToState`.", + "declaration": "function snapshotState() external returns (uint256 snapshotId);", + "visibility": "external", + "mutability": "", + "signature": "snapshotState()", + "selector": "0x9cd23835", + "selectorBytes": [ + 156, + 210, + 56, + 53 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_0", + "description": "Snapshot capture an arbitrary numerical value by name.\nThe group name is derived from the contract name.", + "declaration": "function snapshotValue(string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,uint256)", + "selector": "0x51db805a", + "selectorBytes": [ + 81, + 219, + 128, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_1", + "description": "Snapshot capture an arbitrary numerical value by name in a group.", + "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,string,uint256)", + "selector": "0x6d2b27d8", + "selectorBytes": [ + 109, + 43, + 39, + 216 + ] + }, + "group": "evm", "status": "stable", "safety": "unsafe" }, @@ -8413,6 +8598,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "startSnapshotGas_0", + "description": "Start a snapshot capture of the current gas usage by name.\nThe group name is derived from the contract name.", + "declaration": "function startSnapshotGas(string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string)", + "selector": "0x3cad9d7b", + "selectorBytes": [ + 60, + 173, + 157, + 123 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startSnapshotGas_1", + "description": "Start a snapshot capture of the current gas usage by name in a group.", + "declaration": "function startSnapshotGas(string calldata group, string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string,string)", + "selector": "0x6cd0cc53", + "selectorBytes": [ + 108, + 208, + 204, + 83 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "startStateDiffRecording", @@ -8533,6 +8758,66 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "stopSnapshotGas_0", + "description": "", + "declaration": "function stopSnapshotGas() external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas()", + "selector": "0xf6402eda", + "selectorBytes": [ + 246, + 64, + 46, + 218 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_1", + "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.\nThe group name is derived from the contract name.", + "declaration": "function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string)", + "selector": "0x773b2805", + "selectorBytes": [ + 119, + 59, + 40, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_2", + "description": "Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string,string)", + "selector": "0x0c9db707", + "selectorBytes": [ + 12, + 157, + 183, + 7 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "store", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index fef210bae248..ebb2d0e27d14 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -102,8 +102,6 @@ interface Vm { uint64 gasLimit; /// The total gas used. uint64 gasTotalUsed; - /// DEPRECATED: The amount of gas used for memory expansion. Ref: - uint64 gasMemoryUsed; /// The amount of gas refunded. int64 gasRefunded; /// The amount of gas remaining. @@ -508,13 +506,66 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + // ----- Arbitrary Snapshots ----- + + /// Snapshot capture an arbitrary numerical value by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata name, uint256 value) external; + + /// Snapshot capture an arbitrary numerical value by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata group, string calldata name, uint256 value) external; + + // -------- Gas Snapshots -------- + + + /// Snapshot capture the gas usage of the last call by name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata name) external; + + /// Snapshot capture the gas usage of the last call by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata group, string calldata name) external; + + /// Start a snapshot capture of the current gas usage by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata name) external; + + /// Start a snapshot capture of the current gas usage by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata group, string calldata name) external; + + // Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas() external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); + // -------- State Snapshots -------- + + /// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `snapshotState`")))] + function snapshot() external returns (uint256 snapshotId); + /// Snapshot the current state of the evm. /// Returns the ID of the snapshot that was created. - /// To revert a snapshot use `revertTo`. + /// To revert a snapshot use `revertToState`. #[cheatcode(group = Evm, safety = Unsafe)] - function snapshot() external returns (uint256 snapshotId); + function snapshotState() external returns (uint256 snapshotId); + + /// `revertTo` is being deprecated in favor of `revertToState`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToState`")))] + function revertTo(uint256 snapshotId) external returns (bool success); /// Revert the state of the EVM to a previous snapshot /// Takes the snapshot ID to revert to. @@ -522,9 +573,13 @@ interface Vm { /// Returns `true` if the snapshot was successfully reverted. /// Returns `false` if the snapshot does not exist. /// - /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteSnapshot`. + /// **Note:** This does not automatically delete the snapshot. To delete the snapshot use `deleteStateSnapshot`. #[cheatcode(group = Evm, safety = Unsafe)] - function revertTo(uint256 snapshotId) external returns (bool success); + function revertToState(uint256 snapshotId) external returns (bool success); + + /// `revertToAndDelete` is being deprecated in favor of `revertToStateAndDelete`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `revertToStateAndDelete`")))] + function revertToAndDelete(uint256 snapshotId) external returns (bool success); /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots /// Takes the snapshot ID to revert to. @@ -532,7 +587,11 @@ interface Vm { /// Returns `true` if the snapshot was successfully reverted and deleted. /// Returns `false` if the snapshot does not exist. #[cheatcode(group = Evm, safety = Unsafe)] - function revertToAndDelete(uint256 snapshotId) external returns (bool success); + function revertToStateAndDelete(uint256 snapshotId) external returns (bool success); + + /// `deleteSnapshot` is being deprecated in favor of `deleteStateSnapshot`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshot`")))] + function deleteSnapshot(uint256 snapshotId) external returns (bool success); /// Removes the snapshot with the given ID created by `snapshot`. /// Takes the snapshot ID to delete. @@ -540,11 +599,15 @@ interface Vm { /// Returns `true` if the snapshot was successfully deleted. /// Returns `false` if the snapshot does not exist. #[cheatcode(group = Evm, safety = Unsafe)] - function deleteSnapshot(uint256 snapshotId) external returns (bool success); + function deleteStateSnapshot(uint256 snapshotId) external returns (bool success); + + /// `deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated(Some("replaced by `deleteStateSnapshots`")))] + function deleteSnapshots() external; /// Removes _all_ snapshots previously created by `snapshot`. #[cheatcode(group = Evm, safety = Unsafe)] - function deleteSnapshots() external; + function deleteStateSnapshots() external; // -------- Forking -------- // --- Creation and Selection --- diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 715e26dc57bb..caf83162bd4b 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -50,6 +50,8 @@ pub struct CheatsConfig { /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. /// If None, no validation is performed. pub available_artifacts: Option, + /// Name of the script/test contract which is currently running. + pub running_contract: Option, /// Version of the script/test contract which is currently running. pub running_version: Option, /// Whether to enable legacy (non-reverting) assertions. @@ -65,6 +67,7 @@ impl CheatsConfig { evm_opts: EvmOpts, available_artifacts: Option, script_wallets: Option, + running_contract: Option, running_version: Option, ) -> Self { let mut allowed_paths = vec![config.root.0.clone()]; @@ -93,6 +96,7 @@ impl CheatsConfig { labels: config.labels.clone(), script_wallets, available_artifacts, + running_contract, running_version, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, @@ -222,6 +226,7 @@ impl Default for CheatsConfig { labels: Default::default(), script_wallets: None, available_artifacts: Default::default(), + running_contract: Default::default(), running_version: Default::default(), assertions_revert: true, seed: None, @@ -241,6 +246,7 @@ mod tests { None, None, None, + None, ) } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 45b6f8c75270..5970d5758405 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -10,7 +10,7 @@ use alloy_rlp::Decodable; use alloy_sol_types::SolValue; use foundry_common::fs::{read_json_file, write_json_file}; use foundry_evm_core::{ - backend::{DatabaseExt, RevertSnapshotAction}, + backend::{DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, }; use rand::Rng; @@ -63,6 +63,19 @@ pub struct DealRecord { pub new_balance: U256, } +/// Records the `snapshotGas*` cheatcodes. +#[derive(Clone, Debug)] +pub struct GasRecord { + /// The group name of the gas snapshot. + pub group: String, + /// The name of the gas snapshot. + pub name: String, + /// The total gas used in the gas snapshot. + pub gas_used: u64, + /// Depth at which the gas snapshot was taken. + pub depth: u64, +} + impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; @@ -506,63 +519,157 @@ impl Cheatcode for readCallersCall { } } +impl Cheatcode for snapshotValue_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, value } = self; + inner_create_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name, value } = self; + inner_create_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotGasLastCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_create_value_snapshot( + ccx, + None, + Some(name.clone()), + last_call_gas.gasTotalUsed.to_string(), + ) + } +} + +impl Cheatcode for snapshotGasLastCall_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, group } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_create_value_snapshot( + ccx, + Some(group.clone()), + Some(name.clone()), + last_call_gas.gasTotalUsed.to_string(), + ) + } +} + +impl Cheatcode for startSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_start_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_stop_gas_snapshot(ccx, None, None) + } +} + +impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_stop_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + +// Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { 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()) + inner_snapshot_state(ccx) } } +impl Cheatcode for snapshotStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_snapshot_state(ccx) + } +} + +// Deprecated in favor of `revertToStateCall` impl Cheatcode for revertToCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert( - *snapshotId, - &ccx.ecx.journaled_state, - &mut ccx.ecx.env, - RevertSnapshotAction::RevertKeep, - ) { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state = journaled_state; - true - } else { - false - }; - Ok(result.abi_encode()) + inner_revert_to_state(ccx, *snapshotId) } } +impl Cheatcode for revertToStateCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state(ccx, *snapshotId) + } +} + +// Deprecated in favor of `revertToStateAndDeleteCall` impl Cheatcode for revertToAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert( - *snapshotId, - &ccx.ecx.journaled_state, - &mut ccx.ecx.env, - RevertSnapshotAction::RevertRemove, - ) { - // we reset the evm's journaled_state to the state of the snapshot previous state - ccx.ecx.journaled_state = journaled_state; - true - } else { - false - }; - Ok(result.abi_encode()) + inner_revert_to_state_and_delete(ccx, *snapshotId) + } +} + +impl Cheatcode for revertToStateAndDeleteCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_revert_to_state_and_delete(ccx, *snapshotId) } } +// Deprecated in favor of `deleteStateSnapshotCall` impl Cheatcode for deleteSnapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = ccx.ecx.db.delete_snapshot(*snapshotId); - Ok(result.abi_encode()) + inner_delete_state_snapshot(ccx, *snapshotId) } } + +impl Cheatcode for deleteStateSnapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { snapshotId } = self; + inner_delete_state_snapshot(ccx, *snapshotId) + } +} + +// Deprecated in favor of `deleteStateSnapshotsCall` impl Cheatcode for deleteSnapshotsCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.ecx.db.delete_snapshots(); - Ok(Default::default()) + inner_delete_state_snapshots(ccx) + } +} + +impl Cheatcode for deleteStateSnapshotsCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_delete_state_snapshots(ccx) } } @@ -628,6 +735,179 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } +fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { + Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) +} + +fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( + snapshot_id, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertStateSnapshotAction::RevertKeep, + ) { + // we reset the evm's journaled_state to the state of the snapshot previous state + ccx.ecx.journaled_state = journaled_state; + true + } else { + false + }; + Ok(result.abi_encode()) +} + +fn inner_revert_to_state_and_delete( + ccx: &mut CheatsCtxt, + snapshot_id: U256, +) -> Result { + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( + snapshot_id, + &ccx.ecx.journaled_state, + &mut ccx.ecx.env, + RevertStateSnapshotAction::RevertRemove, + ) { + // we reset the evm's journaled_state to the state of the snapshot previous state + ccx.ecx.journaled_state = journaled_state; + true + } else { + false + }; + Ok(result.abi_encode()) +} + +fn inner_delete_state_snapshot( + ccx: &mut CheatsCtxt, + snapshot_id: U256, +) -> Result { + let result = ccx.ecx.db.delete_state_snapshot(snapshot_id); + Ok(result.abi_encode()) +} + +fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { + ccx.ecx.db.delete_state_snapshots(); + Ok(Default::default()) +} + +fn inner_create_value_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, + value: String, +) -> Result { + let cheatcodes = ccx.state.clone(); + let group = group + .as_deref() + .unwrap_or(cheatcodes.config.running_contract.as_ref().expect("expected running contract")) + .to_string(); + let name = name.as_deref().unwrap_or("default").to_string(); + + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value); + + Ok(Default::default()) +} + +fn inner_start_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, +) -> Result { + // Revert if there is an active gas snapshot as we can only have one active snapshot at a time. + if ccx.state.gas_metering.last_snapshot_group.is_some() || + ccx.state.gas_metering.last_snapshot_name.is_some() + { + bail!( + "gas snapshot was already started with group: {:?} and name: {:?}", + ccx.state.gas_metering.last_snapshot_group, + ccx.state.gas_metering.last_snapshot_name + ); + } + + let group = group.as_deref().unwrap_or( + ccx.state.config.running_contract.as_deref().expect("expected running contract"), + ); + let name = name.as_deref().unwrap_or("default").to_string(); + + ccx.state.gas_metering.gas_records.push(GasRecord { + group: group.to_string(), + name: name.clone(), + gas_used: 0, + depth: ccx.ecx.journaled_state.depth() - 1, + }); + + ccx.state.gas_metering.last_snapshot_group = Some(group.to_string()); + ccx.state.gas_metering.last_snapshot_name = Some(name); + + ccx.state.gas_metering.start(); + + Ok(Default::default()) +} + +fn inner_stop_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: Option, +) -> Result { + // If group and name are not provided, use the last snapshot group and name. + let group = group + .as_deref() + .unwrap_or( + ccx.state + .gas_metering + .last_snapshot_group + .as_deref() + .expect("no gas snapshot was started with this group"), + ) + .to_string(); + let name = name + .as_deref() + .unwrap_or( + ccx.state + .gas_metering + .last_snapshot_name + .as_deref() + .expect("no gas snapshot was started with this name"), + ) + .to_string(); + + if let Some(record) = ccx + .state + .gas_metering + .gas_records + .iter_mut() + .find(|record| record.group == group && record.name == name) + { + // Calculate the gas used since the snapshot was started. + // We subtract 151 from the gas used to account for gas used by the snapshot itself. + let value = record.gas_used - 151; + + ccx.state + .gas_snapshots + .entry(group.to_string()) + .or_default() + .insert(name.clone(), value.to_string()); + + // Stop the gas metering. + ccx.state.gas_metering.stop(); + + // Remove the gas record. + ccx.state + .gas_metering + .gas_records + .retain(|record| record.group != group && record.name != name); + + // Clear last snapshot cache. + if ccx.state.gas_metering.last_snapshot_group == Some(group.to_string()) && + ccx.state.gas_metering.last_snapshot_name == Some(name) + { + ccx.state.gas_metering.last_snapshot_group = None; + ccx.state.gas_metering.last_snapshot_name = None; + } + + Ok(value.abi_encode()) + } else { + bail!("no gas snapshot was started with the name: {name} in group: {group}"); + } +} + /// Reads the current caller information and returns the current [CallerMode], `msg.sender` and /// `tx.origin`. /// diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 401966c4626b..2a0b0a656e57 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -5,7 +5,7 @@ use crate::{ mapping::{self, MappingSlots}, mock::{MockCallDataContext, MockCallReturnData}, prank::Prank, - DealRecord, RecordAccess, + DealRecord, GasRecord, RecordAccess, }, inspector::utils::CommonCreateInput, script::{Broadcast, ScriptWallets}, @@ -17,8 +17,8 @@ use crate::{ }, }, utils::IgnoredTraces, - CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm, - Vm::AccountAccess, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, + Vm::{self, AccountAccess}, }; use alloy_primitives::{hex, Address, Bytes, Log, TxKind, B256, U256}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; @@ -227,15 +227,37 @@ pub struct GasMetering { pub touched: bool, /// True if gas metering should be reset to frame limit. pub reset: bool, - /// Stores frames paused gas. + /// Stores paused gas frames. pub paused_frames: Vec, + /// The group of the last snapshot taken. + pub last_snapshot_group: Option, + /// The name of the last snapshot taken. + pub last_snapshot_name: Option, + /// Cache of the amount of gas used in previous call. /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option, + + /// True if gas metering is enabled. + pub recording: bool, + /// The gas used in the last frame. + pub last_gas_used: u64, + /// Gas records for the active snapshots. + pub gas_records: Vec, } impl GasMetering { + /// Start the gas recording. + pub fn start(&mut self) { + self.recording = true; + } + + /// Stop the gas recording. + pub fn stop(&mut self) { + self.recording = false; + } + /// Resume paused gas metering. pub fn resume(&mut self) { if self.paused { @@ -430,6 +452,10 @@ pub struct Cheatcodes { /// Gas metering state. pub gas_metering: GasMetering, + /// Contains gas snapshots made over the course of a test suite. + // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. + pub gas_snapshots: BTreeMap>, + /// Mapping slots. pub mapping_slots: Option>, @@ -488,6 +514,7 @@ impl Cheatcodes { serialized_jsons: Default::default(), eth_deals: Default::default(), gas_metering: Default::default(), + gas_snapshots: Default::default(), mapping_slots: Default::default(), pc: Default::default(), breakpoints: Default::default(), @@ -753,6 +780,13 @@ impl Cheatcodes { } } + // Store the total gas used for all active gas records started by `startSnapshotGas`. + self.gas_metering.gas_records.iter_mut().for_each(|record| { + if ecx.journaled_state.depth() == record.depth + 1 { + record.gas_used = record.gas_used.saturating_add(outcome.result.gas.spent()); + } + }); + // If `startStateDiffRecording` has been called, update the `reverted` status of the // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { @@ -1152,6 +1186,10 @@ impl Inspector for Cheatcodes { #[inline] fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { + if self.gas_metering.recording { + self.meter_gas_record(interpreter, ecx); + } + if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -1304,11 +1342,17 @@ impl Inspector for Cheatcodes { self.gas_metering.last_call_gas = Some(crate::Vm::Gas { gasLimit: gas.limit(), gasTotalUsed: gas.spent(), - gasMemoryUsed: 0, gasRefunded: gas.refunded(), gasRemaining: gas.remaining(), }); + // Store the total gas used for all active gas records started by `startSnapshotGas`. + self.gas_metering.gas_records.iter_mut().for_each(|record| { + if ecx.journaled_state.depth() == record.depth + 1 { + record.gas_used = record.gas_used.saturating_add(gas.spent()); + } + }); + // If `startStateDiffRecording` has been called, update the `reverted` status of the // previous call depth's recorded accesses, if any if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { @@ -1556,6 +1600,59 @@ impl Cheatcodes { } } + #[cold] + fn meter_gas_record( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext, + ) { + if matches!(interpreter.instruction_result, InstructionResult::Continue) { + match interpreter.current_opcode() { + op::CREATE | + op::CALL | + op::CALLCODE | + op::DELEGATECALL | + op::CREATE2 | + op::STATICCALL | + op::EXTSTATICCALL | + op::EXTDELEGATECALL => { + // Reset gas used when entering a new frame. + self.gas_metering.last_gas_used = 0; + } + _ => { + self.gas_metering.gas_records.iter_mut().for_each(|record| { + if ecx.journaled_state.depth() == record.depth + 1 { + // Initialize after new frame, use this as the starting point. + if self.gas_metering.last_gas_used == 0 { + self.gas_metering.last_gas_used = interpreter.gas.spent(); + return; + } + + // Calculate the gas difference between the last and current frame. + let gas_diff = interpreter + .gas + .spent() + .saturating_sub(self.gas_metering.last_gas_used); + + // Update the gas record. + record.gas_used = record.gas_used.saturating_add(gas_diff); + + // Update for next iteration. + self.gas_metering.last_gas_used = interpreter.gas.spent(); + } + }); + } + } + } + + println!( + "RECORD [{:?}]: {:?} @ {:?}", + ecx.journaled_state.depth(), + revm::interpreter::OpCode::new(interpreter.current_opcode()).unwrap().as_str(), + self.gas_metering.gas_records + ); + } + #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index a0b9fc391e9b..3e09f1fd14e0 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -308,6 +308,7 @@ impl SessionSource { self.config.evm_opts.clone(), None, None, + None, Some(self.solc.version.clone()), ) .into(), diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 8ee47d2fd2a0..9996fd22270f 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -57,6 +57,15 @@ pub fn write_json_file(path: &Path, obj: &T) -> Result<()> { writer.flush().map_err(|e| FsPathError::write(e, path)) } +/// Writes the object as a pretty JSON object. +pub fn write_pretty_json_file(path: &Path, obj: &T) -> Result<()> { + let file = create_file(path)?; + let mut writer = BufWriter::new(file); + serde_json::to_writer_pretty(&mut writer, obj) + .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?; + writer.flush().map_err(|e| FsPathError::write(e, path)) +} + /// Wrapper for `std::fs::write` pub fn write(path: impl AsRef, contents: impl AsRef<[u8]>) -> Result<()> { let path = path.as_ref(); diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d77e30492fc5..ff3f1003af28 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -176,6 +176,8 @@ pub struct Config { pub cache: bool, /// where the cache is stored if enabled pub cache_path: PathBuf, + /// where the snapshots are stored + pub snapshots: PathBuf, /// where the broadcast logs are stored pub broadcast: PathBuf, /// additional solc allow paths for `--allow-paths` @@ -719,6 +721,7 @@ impl Config { self.out = p(&root, &self.out); self.broadcast = p(&root, &self.broadcast); self.cache_path = p(&root, &self.cache_path); + self.snapshots = p(&root, &self.snapshots); if let Some(build_info_path) = self.build_info_path { self.build_info_path = Some(p(&root, &build_info_path)); @@ -2087,6 +2090,7 @@ impl Default for Config { cache: true, cache_path: "cache".into(), broadcast: "broadcast".into(), + snapshots: "snapshots".into(), allow_paths: vec![], include_paths: vec![], force: false, diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 2dcd985ae912..b60d95e3c5ef 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -3,7 +3,7 @@ use super::BackendError; use crate::{ backend::{ - diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertSnapshotAction, + diagnostic::RevertDiagnostic, Backend, DatabaseExt, LocalForkId, RevertStateSnapshotAction, }, fork::{CreateFork, ForkId}, InspectorExt, @@ -80,11 +80,11 @@ impl<'a> CowBackend<'a> { Ok(res) } - /// Returns whether there was a snapshot failure in the backend. + /// Returns whether there was a state snapshot failure in the backend. /// /// This is bubbled up from the underlying Copy-On-Write backend when a revert occurs. - pub fn has_snapshot_failure(&self) -> bool { - self.backend.has_snapshot_failure() + pub fn has_state_snapshot_failure(&self) -> bool { + self.backend.has_state_snapshot_failure() } /// Returns a mutable instance of the Backend. @@ -111,31 +111,31 @@ impl<'a> CowBackend<'a> { } impl<'a> DatabaseExt for CowBackend<'a> { - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - self.backend_mut(env).snapshot(journaled_state, env) + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + self.backend_mut(env).snapshot_state(journaled_state, env) } - fn revert( + fn revert_state_snapshot( &mut self, id: U256, journaled_state: &JournaledState, current: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option { - self.backend_mut(current).revert(id, journaled_state, current, action) + self.backend_mut(current).revert_state_snapshot(id, journaled_state, current, action) } - fn delete_snapshot(&mut self, id: U256) -> bool { - // delete snapshot requires a previous snapshot to be initialized + fn delete_state_snapshot(&mut self, id: U256) -> bool { + // Requires a previous snapshot to be initialized. if let Some(backend) = self.initialized_backend_mut() { - return backend.delete_snapshot(id) + return backend.delete_state_snapshot(id) } false } - fn delete_snapshots(&mut self) { + fn delete_state_snapshots(&mut self) { if let Some(backend) = self.initialized_backend_mut() { - backend.delete_snapshots() + backend.delete_state_snapshots() } } diff --git a/crates/evm/core/src/backend/in_memory_db.rs b/crates/evm/core/src/backend/in_memory_db.rs index e819c5313c2a..0664fdc17316 100644 --- a/crates/evm/core/src/backend/in_memory_db.rs +++ b/crates/evm/core/src/backend/in_memory_db.rs @@ -1,6 +1,6 @@ //! In-memory database. -use crate::snapshot::Snapshots; +use crate::state_snapshot::StateSnapshots; use alloy_primitives::{Address, B256, U256}; use foundry_fork_db::DatabaseError; use revm::{ @@ -16,11 +16,12 @@ pub type FoundryEvmInMemoryDB = CacheDB; /// In-memory [`Database`] for Anvil. /// -/// This acts like a wrapper type for [`FoundryEvmInMemoryDB`] but is capable of applying snapshots. +/// This acts like a wrapper type for [`FoundryEvmInMemoryDB`] but is capable of applying state +/// snapshots. #[derive(Debug)] pub struct MemDb { pub inner: FoundryEvmInMemoryDB, - pub snapshots: Snapshots, + pub snapshots: StateSnapshots, } impl Default for MemDb { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index e98ac00c5d6c..e506416aa2f6 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -3,7 +3,7 @@ use crate::{ constants::{CALLER, CHEATCODE_ADDRESS, DEFAULT_CREATE2_DEPLOYER, TEST_CONTRACT_ADDRESS}, fork::{CreateFork, ForkId, MultiFork}, - snapshot::Snapshots, + state_snapshot::StateSnapshots, utils::{configure_tx_env, new_evm_with_inspector}, InspectorExt, }; @@ -41,8 +41,8 @@ pub use cow::CowBackend; mod in_memory_db; pub use in_memory_db::{EmptyDBWrapper, FoundryEvmInMemoryDB, MemDb}; -mod snapshot; -pub use snapshot::{BackendSnapshot, RevertSnapshotAction, StateSnapshot}; +mod state_snapshot; +pub use state_snapshot::{BackendStateSnapshot, RevertStateSnapshotAction, StateSnapshot}; // A `revm::Database` that is used in forking mode type ForkDB = CacheDB; @@ -71,14 +71,14 @@ pub const GLOBAL_FAIL_SLOT: U256 = /// An extension trait that allows us to easily extend the `revm::Inspector` capabilities #[auto_impl::auto_impl(&mut)] pub trait DatabaseExt: Database + DatabaseCommit { - /// Creates a new snapshot at the current point of execution. + /// Creates a new state snapshot at the current point of execution. /// /// A snapshot is associated with a new unique id that's created for the snapshot. - /// Snapshots can be reverted: [DatabaseExt::revert], however, depending on the - /// [RevertSnapshotAction], it will keep the snapshot alive or delete it. - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; + /// Snapshots can be reverted: [`DatabaseExt::revert_state_snapshot`], however, depending on the + /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; - /// Reverts the snapshot if it exists + /// Reverts the state snapshot if it exists. /// /// Returns `true` if the snapshot was successfully reverted, `false` if no snapshot for that id /// exists. @@ -89,23 +89,23 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// This will also revert any changes in the `Env` and replace it with the captured `Env` of /// `Self::snapshot`. /// - /// Depending on [RevertSnapshotAction] it will keep the snapshot alive or delete it. - fn revert( + /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. + fn revert_state_snapshot( &mut self, id: U256, journaled_state: &JournaledState, env: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option; - /// Deletes the snapshot with the given `id` + /// Deletes the state snapshot with the given `id`. /// /// Returns `true` if the snapshot was successfully deleted, `false` if no snapshot for that id /// exists. - fn delete_snapshot(&mut self, id: U256) -> bool; + fn delete_state_snapshot(&mut self, id: U256) -> bool; - /// Deletes all snapshots. - fn delete_snapshots(&mut self); + /// Deletes all state snapshots. + fn delete_state_snapshots(&mut self); /// Creates and also selects a new fork /// @@ -553,9 +553,11 @@ impl Backend { } } - /// Returns all snapshots created in this backend - pub fn snapshots(&self) -> &Snapshots> { - &self.inner.snapshots + /// Returns all state snapshots created in this backend. + pub fn state_snapshots( + &self, + ) -> &StateSnapshots> { + &self.inner.state_snapshots } /// Sets the address of the `DSTest` contract that is being executed @@ -591,18 +593,18 @@ impl Backend { self.inner.caller } - /// Failures occurred in snapshots are tracked when the snapshot is reverted + /// Failures occurred in state snapshots are tracked when the snapshot is reverted /// /// If an error occurs in a restored snapshot, the test is considered failed. /// /// This returns whether there was a reverted snapshot that recorded an error - pub fn has_snapshot_failure(&self) -> bool { - self.inner.has_snapshot_failure + pub fn has_state_snapshot_failure(&self) -> bool { + self.inner.has_state_snapshot_failure } /// Sets the snapshot failure flag. - pub fn set_snapshot_failure(&mut self, has_snapshot_failure: bool) { - self.inner.has_snapshot_failure = has_snapshot_failure + pub fn set_state_snapshot_failure(&mut self, has_state_snapshot_failure: bool) { + self.inner.has_state_snapshot_failure = has_state_snapshot_failure } /// When creating or switching forks, we update the AccountInfo of the contract @@ -900,9 +902,9 @@ impl Backend { } impl DatabaseExt for Backend { - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - trace!("create snapshot"); - let id = self.inner.snapshots.insert(BackendSnapshot::new( + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + trace!("create state snapshot"); + let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), env.clone(), @@ -911,18 +913,18 @@ impl DatabaseExt for Backend { id } - fn revert( + fn revert_state_snapshot( &mut self, id: U256, current_state: &JournaledState, current: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option { - trace!(?id, "revert snapshot"); - if let Some(mut snapshot) = self.inner.snapshots.remove_at(id) { + trace!(?id, "revert state snapshot"); + if let Some(mut snapshot) = self.inner.state_snapshots.remove_at(id) { // Re-insert snapshot to persist it if action.is_keep() { - self.inner.snapshots.insert_at(snapshot.clone(), id); + self.inner.state_snapshots.insert_at(snapshot.clone(), id); } // https://github.com/foundry-rs/foundry/issues/3055 @@ -932,14 +934,14 @@ impl DatabaseExt for Backend { if let Some(account) = current_state.state.get(&CHEATCODE_ADDRESS) { if let Some(slot) = account.storage.get(&GLOBAL_FAIL_SLOT) { if !slot.present_value.is_zero() { - self.set_snapshot_failure(true); + self.set_state_snapshot_failure(true); } } } // merge additional logs snapshot.merge(current_state); - let BackendSnapshot { db, mut journaled_state, env } = snapshot; + let BackendStateSnapshot { db, mut journaled_state, env } = snapshot; match db { BackendDatabaseSnapshot::InMemory(mem_db) => { self.mem_db = mem_db; @@ -962,27 +964,27 @@ impl DatabaseExt for Backend { } caller_account.into() }); - self.inner.revert_snapshot(id, fork_id, idx, *fork); + self.inner.revert_state_snapshot(id, fork_id, idx, *fork); self.active_fork_ids = Some((id, idx)) } } update_current_env_with_fork_env(current, env); - trace!(target: "backend", "Reverted snapshot {}", id); + trace!(target: "backend", "Reverted state snapshot {}", id); Some(journaled_state) } else { - warn!(target: "backend", "No snapshot to revert for {}", id); + warn!(target: "backend", "No state snapshot to revert for {}", id); None } } - fn delete_snapshot(&mut self, id: U256) -> bool { - self.inner.snapshots.remove_at(id).is_some() + fn delete_state_snapshot(&mut self, id: U256) -> bool { + self.inner.state_snapshots.remove_at(id).is_some() } - fn delete_snapshots(&mut self) { - self.inner.snapshots.clear() + fn delete_state_snapshots(&mut self) { + self.inner.state_snapshots.clear() } fn create_fork(&mut self, create_fork: CreateFork) -> eyre::Result { @@ -1585,15 +1587,15 @@ pub struct BackendInner { /// issued _local_ numeric identifier, that remains constant, even if the underlying fork /// backend changes. pub issued_local_fork_ids: HashMap, - /// tracks all the created forks - /// Contains the index of the corresponding `ForkDB` in the `forks` vec + /// Tracks all the created forks. + /// Contains the index of the corresponding `ForkDB` in the `forks` vec. pub created_forks: HashMap, /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling pub forks: Vec>, - /// Contains snapshots made at a certain point - pub snapshots: Snapshots>, - /// Tracks whether there was a failure in a snapshot that was reverted + /// Contains state snapshots made at a certain point. + pub state_snapshots: StateSnapshots>, + /// Tracks whether there was a failure in a state snapshot that was reverted /// /// The Test contract contains a bool variable that is set to true when an `assert` function /// failed. When a snapshot is reverted, it reverts the state of the evm, but we still want @@ -1602,7 +1604,7 @@ pub struct BackendInner { /// reverted we get the _current_ `revm::JournaledState` which contains the state that we can /// check if the `_failed` variable is set, /// additionally - pub has_snapshot_failure: bool, + pub has_state_snapshot_failure: bool, /// Tracks the caller of the test function pub caller: Option
, /// Tracks numeric identifiers for forks @@ -1686,7 +1688,7 @@ impl BackendInner { } /// Reverts the entire fork database - pub fn revert_snapshot( + pub fn revert_state_snapshot( &mut self, id: LocalForkId, fork_id: ForkId, @@ -1789,8 +1791,8 @@ impl Default for BackendInner { issued_local_fork_ids: Default::default(), created_forks: Default::default(), forks: vec![], - snapshots: Default::default(), - has_snapshot_failure: false, + state_snapshots: Default::default(), + has_state_snapshot_failure: false, caller: None, next_fork_id: Default::default(), persistent_accounts: Default::default(), diff --git a/crates/evm/core/src/backend/snapshot.rs b/crates/evm/core/src/backend/state_snapshot.rs similarity index 90% rename from crates/evm/core/src/backend/snapshot.rs rename to crates/evm/core/src/backend/state_snapshot.rs index f8961c7a0e67..db20034fda3a 100644 --- a/crates/evm/core/src/backend/snapshot.rs +++ b/crates/evm/core/src/backend/state_snapshot.rs @@ -13,9 +13,9 @@ pub struct StateSnapshot { pub block_hashes: HashMap, } -/// Represents a snapshot taken during evm execution +/// Represents a state snapshot taken during evm execution #[derive(Clone, Debug)] -pub struct BackendSnapshot { +pub struct BackendStateSnapshot { pub db: T, /// The journaled_state state at a specific point pub journaled_state: JournaledState, @@ -23,7 +23,7 @@ pub struct BackendSnapshot { pub env: Env, } -impl BackendSnapshot { +impl BackendStateSnapshot { /// Takes a new snapshot pub fn new(db: T, journaled_state: JournaledState, env: Env) -> Self { Self { db, journaled_state, env } @@ -45,7 +45,7 @@ impl BackendSnapshot { /// /// Whether to remove the snapshot or keep it #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub enum RevertSnapshotAction { +pub enum RevertStateSnapshotAction { /// Remove the snapshot after reverting #[default] RevertRemove, @@ -53,7 +53,7 @@ pub enum RevertSnapshotAction { RevertKeep, } -impl RevertSnapshotAction { +impl RevertStateSnapshotAction { /// Returns `true` if the action is to keep the snapshot pub fn is_keep(&self) -> bool { matches!(self, Self::RevertKeep) diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index de6b2a6b9f22..1a9df8cd50af 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -1,8 +1,8 @@ //! A revm database that forks off a remote client use crate::{ - backend::{RevertSnapshotAction, StateSnapshot}, - snapshot::Snapshots, + backend::{RevertStateSnapshotAction, StateSnapshot}, + state_snapshot::StateSnapshots, }; use alloy_primitives::{Address, B256, U256}; use alloy_rpc_types::BlockId; @@ -37,8 +37,8 @@ pub struct ForkedDatabase { /// /// This exclusively stores the _unchanged_ remote client state db: BlockchainDb, - /// holds the snapshot state of a blockchain - snapshots: Arc>>, + /// Holds the snapshot state of a blockchain + snapshots: Arc>>, } impl ForkedDatabase { @@ -60,7 +60,7 @@ impl ForkedDatabase { &mut self.cache_db } - pub fn snapshots(&self) -> &Arc>> { + pub fn snapshots(&self) -> &Arc>> { &self.snapshots } @@ -92,7 +92,7 @@ impl ForkedDatabase { &self.db } - pub fn create_snapshot(&self) -> ForkDbSnapshot { + pub fn create_state_snapshot(&self) -> ForkDbSnapshot { let db = self.db.db(); let snapshot = StateSnapshot { accounts: db.accounts.read().clone(), @@ -102,8 +102,8 @@ impl ForkedDatabase { ForkDbSnapshot { local: self.cache_db.clone(), snapshot } } - pub fn insert_snapshot(&self) -> U256 { - let snapshot = self.create_snapshot(); + pub fn insert_state_snapshot(&self) -> U256 { + let snapshot = self.create_state_snapshot(); let mut snapshots = self.snapshots().lock(); let id = snapshots.insert(snapshot); trace!(target: "backend::forkdb", "Created new snapshot {}", id); @@ -111,7 +111,7 @@ impl ForkedDatabase { } /// Removes the snapshot from the tracked snapshot and sets it as the current state - pub fn revert_snapshot(&mut self, id: U256, action: RevertSnapshotAction) -> bool { + pub fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { let snapshot = { self.snapshots().lock().remove_at(id) }; if let Some(snapshot) = snapshot { if action.is_keep() { diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 26b392c433f3..347b1cc8f49a 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -26,7 +26,7 @@ pub mod fork; pub mod opcodes; pub mod opts; pub mod precompiles; -pub mod snapshot; +pub mod state_snapshot; pub mod utils; /// An extension trait that allows us to add additional hooks to Inspector for later use in diff --git a/crates/evm/core/src/snapshot.rs b/crates/evm/core/src/state_snapshot.rs similarity index 77% rename from crates/evm/core/src/snapshot.rs rename to crates/evm/core/src/state_snapshot.rs index 423f853eed49..6bcf19eb279f 100644 --- a/crates/evm/core/src/snapshot.rs +++ b/crates/evm/core/src/state_snapshot.rs @@ -3,26 +3,26 @@ use alloy_primitives::U256; use std::{collections::HashMap, ops::Add}; -/// Represents all snapshots +/// Represents all state snapshots #[derive(Clone, Debug)] -pub struct Snapshots { +pub struct StateSnapshots { id: U256, snapshots: HashMap, } -impl Snapshots { +impl StateSnapshots { fn next_id(&mut self) -> U256 { let id = self.id; self.id = id.saturating_add(U256::from(1)); id } - /// Returns the snapshot with the given id `id` + /// Returns the state snapshot with the given `id`. pub fn get(&self, id: U256) -> Option<&T> { self.snapshots.get(&id) } - /// Removes the snapshot with the given `id`. + /// Removes the state snapshot with the given `id`. /// /// This will also remove any snapshots taken after the snapshot with the `id`. e.g.: reverting /// to id 1 will delete snapshots with ids 1, 2, 3, etc.) @@ -39,26 +39,26 @@ impl Snapshots { snapshot } - /// Removes all snapshots + /// Removes all state snapshots. pub fn clear(&mut self) { self.snapshots.clear(); } - /// Removes the snapshot with the given `id`. + /// Removes the state snapshot with the given `id`. /// /// Does not remove snapshots after it. pub fn remove_at(&mut self, id: U256) -> Option { self.snapshots.remove(&id) } - /// Inserts the new snapshot and returns the id + /// Inserts the new state snapshot and returns the id. pub fn insert(&mut self, snapshot: T) -> U256 { let id = self.next_id(); self.snapshots.insert(id, snapshot); id } - /// Inserts the new snapshot at the given `id`. + /// Inserts the new state snapshot at the given `id`. /// /// Does not auto-increment the next `id`. pub fn insert_at(&mut self, snapshot: T, id: U256) -> U256 { @@ -67,7 +67,7 @@ impl Snapshots { } } -impl Default for Snapshots { +impl Default for StateSnapshots { fn default() -> Self { Self { id: U256::ZERO, snapshots: HashMap::new() } } diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 54480181ff1c..f8e17be79721 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -17,7 +17,10 @@ use foundry_evm_fuzz::{ use foundry_evm_traces::SparsedTraceArena; use indicatif::ProgressBar; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use std::{cell::RefCell, collections::HashMap}; +use std::{ + cell::RefCell, + collections::{BTreeMap, HashMap}, +}; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; @@ -39,6 +42,8 @@ pub struct FuzzTestData { pub coverage: Option, // Stores logs for all fuzz cases pub logs: Vec, + // Stores gas snapshots for all fuzz cases + pub gas_snapshots: BTreeMap>, // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, } @@ -121,6 +126,15 @@ impl FuzzedExecutor { if show_logs { data.logs.extend(case.logs); } + + // Collect gas snapshots. + for (group, new_snapshots) in case.gas_snapshots.iter() { + data.gas_snapshots + .entry(group.clone()) + .or_default() + .extend(new_snapshots.clone()); + } + // Collect and merge coverage if `forge snapshot` context. match &mut data.coverage { Some(prev) => prev.merge(case.coverage.unwrap()), @@ -158,6 +172,8 @@ impl FuzzedExecutor { (call.traces.clone(), call.cheatcodes.map(|c| c.breakpoints)) }; + let gas_snapshots = fuzz_result.gas_snapshots; + let mut result = FuzzTestResult { first_case: fuzz_result.first_case.unwrap_or_default(), gas_by_case: fuzz_result.gas_by_case, @@ -171,6 +187,7 @@ impl FuzzedExecutor { breakpoints: last_run_breakpoints, gas_report_traces: traces.into_iter().map(|a| a.arena).collect(), coverage: fuzz_result.coverage, + gas_snapshots, deprecated_cheatcodes: fuzz_result.deprecated_cheatcodes, }; @@ -239,6 +256,11 @@ impl FuzzedExecutor { (cheats.breakpoints.clone(), cheats.deprecated.clone()) }); + let gas_snapshots = call + .cheatcodes + .as_ref() + .map_or_else(Default::default, |cheats| cheats.gas_snapshots.clone()); + let success = self.executor.is_raw_call_mut_success(address, &mut call, should_fail); if success { Ok(FuzzOutcome::Case(CaseOutcome { @@ -247,6 +269,7 @@ impl FuzzedExecutor { coverage: call.coverage, breakpoints, logs: call.logs, + gas_snapshots, deprecated_cheatcodes, })) } else { diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs index 081ff91129cf..dd5b56d7dfaf 100644 --- a/crates/evm/evm/src/executors/fuzz/types.rs +++ b/crates/evm/evm/src/executors/fuzz/types.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use crate::executors::RawCallResult; use alloy_primitives::{Bytes, Log}; use foundry_common::evm::Breakpoints; @@ -20,6 +22,8 @@ pub struct CaseOutcome { pub breakpoints: Breakpoints, /// logs of a single fuzz test case. pub logs: Vec, + /// Gas snapshots of a single fuzz test case + pub gas_snapshots: BTreeMap>, // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, } diff --git a/crates/evm/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 655418fadeb6..c447c7423e45 100644 --- a/crates/evm/evm/src/executors/mod.rs +++ b/crates/evm/evm/src/executors/mod.rs @@ -395,7 +395,7 @@ impl Executor { let mut inspector = self.inspector().clone(); let mut backend = CowBackend::new_borrowed(self.backend()); let result = backend.inspect(&mut env, &mut inspector)?; - convert_executed_result(env, inspector, result, backend.has_snapshot_failure()) + convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure()) } /// Execute the transaction configured in `env.tx`. @@ -405,7 +405,7 @@ impl Executor { let backend = self.backend_mut(); let result = backend.inspect(&mut env, &mut inspector)?; let mut result = - convert_executed_result(env, inspector, result, backend.has_snapshot_failure())?; + convert_executed_result(env, inspector, result, backend.has_state_snapshot_failure())?; self.commit(&mut result); Ok(result) } @@ -517,7 +517,7 @@ impl Executor { } // A failure occurred in a reverted snapshot, which is considered a failed test. - if self.backend().has_snapshot_failure() { + if self.backend().has_state_snapshot_failure() { return false; } diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 2930a6aa020c..ef472fd296e3 100644 --- a/crates/evm/fuzz/src/lib.rs +++ b/crates/evm/fuzz/src/lib.rs @@ -15,7 +15,11 @@ use foundry_evm_coverage::HitMaps; use foundry_evm_traces::{CallTraceArena, SparsedTraceArena}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fmt, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + fmt, + sync::Arc, +}; pub use proptest::test_runner::{Config as FuzzConfig, Reason}; @@ -184,6 +188,9 @@ pub struct FuzzTestResult { /// Breakpoints for debugger. Correspond to the same fuzz case as `traces`. pub breakpoints: Option, + /// Any captured gas snapshots along the test's execution which should be accumulated. + pub gas_snapshots: BTreeMap>, + // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 3bc659ea083d..746afc3f334f 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -20,7 +20,12 @@ use foundry_cli::{ opts::CoreBuildArgs, utils::{self, LoadConfig}, }; -use foundry_common::{compile::ProjectCompiler, evm::EvmArgs, fs, shell}; +use foundry_common::{ + compile::ProjectCompiler, + evm::EvmArgs, + fs::{self, create_dir_all, read_json_file, remove_dir_all, write_pretty_json_file}, + shell, +}; use foundry_compilers::{ artifacts::output_selection::OutputSelection, compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language}, @@ -554,6 +559,8 @@ impl TestArgs { .gas_report .then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone())); + let mut gas_snapshots = BTreeMap::>::new(); + let mut outcome = TestOutcome::empty(self.allow_failure); let mut any_test_failed = false; @@ -663,8 +670,77 @@ impl TestArgs { } } } + + // Collect and merge gas snapshots. + for (group, new_snapshots) in result.gas_snapshots.iter() { + gas_snapshots.entry(group.clone()).or_default().extend(new_snapshots.clone()); + } + } + + // Check for differences in gas snapshots if `FORGE_SNAPSHOT_CHECK` is set. + // Exiting early with code 1 if differences are found. + if std::env::var("FORGE_SNAPSHOT_CHECK").is_ok() { + let differences_found = gas_snapshots.clone().into_iter().fold( + false, + |mut found, (group, snapshots)| { + let previous_snapshots: BTreeMap = + read_json_file(&config.snapshots.join(format!("{group}.json"))) + .expect("Failed to read snapshots from disk"); + + let diff: BTreeMap<_, _> = snapshots + .iter() + .filter_map(|(k, v)| { + previous_snapshots.get(k).and_then(|previous_snapshot| { + if previous_snapshot != v { + Some((k.clone(), (previous_snapshot.clone(), v.clone()))) + } else { + None + } + }) + }) + .collect(); + + if !diff.is_empty() { + println!( + "{}", + format!("\n[{group}] Failed to match snapshots:").red().bold() + ); + + for (key, (previous_snapshot, snapshot)) in &diff { + println!( + "{}", + format!("- [{key}] {previous_snapshot} → {snapshot}").red() + ); + } + + found = true; + } + + found + }, + ); + + if differences_found { + println!(); + eyre::bail!("Snapshots differ from previous run"); + } } + // Remove any existing gas snapshots. + if config.snapshots.exists() { + remove_dir_all(&config.snapshots) + .expect("Failed to remove gas snapshots directory"); + } + + // Create `snapshots` directory if it doesn't exist. + create_dir_all(&config.snapshots)?; + + // Write gas snapshots to disk per group. + gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + write_pretty_json_file(&config.snapshots.join(format!("{group}.json")), &snapshots) + .expect("Failed to write gas snapshots to disk"); + }); + // Print suite summary. shell::println(suite_result.summary())?; diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 43aade0ff65c..802ad3884cc6 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -243,6 +243,7 @@ impl MultiContractRunner { self.evm_opts.clone(), Some(self.known_contracts.clone()), None, + Some(artifact_id.name.clone()), Some(artifact_id.version.clone()), ); diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 171a234a5ee0..7f0af0fea50a 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -409,6 +409,9 @@ pub struct TestResult { /// pc breakpoint char map pub breakpoints: Breakpoints, + /// Any captured gas snapshots along the test's execution which should be accumulated. + pub gas_snapshots: BTreeMap>, + /// Deprecated cheatcodes (mapped to their replacements, if any) used in current test. #[serde(skip)] pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, @@ -527,6 +530,7 @@ impl TestResult { self.gas_report_traces = Vec::new(); if let Some(cheatcodes) = raw_call_result.cheatcodes { + self.gas_snapshots = cheatcodes.gas_snapshots; self.breakpoints = cheatcodes.breakpoints; self.deprecated_cheatcodes = cheatcodes.deprecated; } @@ -562,6 +566,7 @@ impl TestResult { self.duration = Duration::default(); self.gas_report_traces = result.gas_report_traces.into_iter().map(|t| vec![t]).collect(); self.breakpoints = result.breakpoints.unwrap_or_default(); + self.gas_snapshots = result.gas_snapshots; self.deprecated_cheatcodes = result.deprecated_cheatcodes; self diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 1ddd402722dd..5aa60c27225a 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -37,6 +37,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { libs: vec!["lib-test".into()], cache: true, cache_path: "test-cache".into(), + snapshots: "snapshots".into(), broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index 5a571f77298f..87e483baf479 100644 --- a/crates/forge/tests/it/invariant.rs +++ b/crates/forge/tests/it/invariant.rs @@ -500,7 +500,7 @@ async fn test_invariant_decode_custom_error() { vec![( "invariant_decode_error()", false, - Some("InvariantCustomError(111, \"custom\")".into()), + Some("CustomError(111, \"custom\")".into()), None, None, )], diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index a20a884f04b3..d9a18d80f964 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -609,6 +609,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(script_wallets), + Some(target.name), Some(target.version), ) .into(), diff --git a/testdata/.gitignore b/testdata/.gitignore index b84c736371ba..7cee7d2ab985 100644 --- a/testdata/.gitignore +++ b/testdata/.gitignore @@ -1,4 +1,5 @@ # Compiler files cache/ out/ +snapshots/ .lock diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 4b7f3fe42f50..05896e483f5e 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -19,7 +19,7 @@ interface Vm { struct ChainInfo { uint256 forkId; uint256 chainId; } struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } - struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } + struct Gas { uint64 gasLimit; uint64 gasTotalUsed; int64 gasRefunded; uint64 gasRemaining; } function _expectCheatcodeRevert() external; function _expectCheatcodeRevert(bytes4 revertData) external; function _expectCheatcodeRevert(bytes calldata revertData) external; @@ -176,6 +176,8 @@ interface Vm { function deal(address account, uint256 newBalance) external; function deleteSnapshot(uint256 snapshotId) external returns (bool success); function deleteSnapshots() external; + function deleteStateSnapshot(uint256 snapshotId) external returns (bool success); + function deleteStateSnapshots() 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); @@ -360,6 +362,8 @@ interface Vm { function resumeTracing() external view; function revertTo(uint256 snapshotId) external returns (bool success); function revertToAndDelete(uint256 snapshotId) external returns (bool success); + function revertToState(uint256 snapshotId) external returns (bool success); + function revertToStateAndDelete(uint256 snapshotId) external returns (bool success); function revokePersistent(address account) external; function revokePersistent(address[] calldata accounts) external; function roll(uint256 newHeight) external; @@ -409,6 +413,11 @@ interface Vm { function skip(bool skipTest, string calldata reason) external; function sleep(uint256 duration) external; function snapshot() external returns (uint256 snapshotId); + function snapshotGasLastCall(string calldata name) external; + function snapshotGasLastCall(string calldata group, string calldata name) external; + function snapshotState() external returns (uint256 snapshotId); + function snapshotValue(string calldata name, uint256 value) external; + function snapshotValue(string calldata group, string calldata name, uint256 value) external; function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; @@ -416,12 +425,17 @@ interface Vm { function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; + function startSnapshotGas(string calldata name) external; + function startSnapshotGas(string calldata group, string calldata name) external; function startStateDiffRecording() external; function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); function stopBroadcast() external; function stopExpectSafeMemory() external; function stopMappingRecording() external; function stopPrank() external; + function stopSnapshotGas() external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); function store(address target, bytes32 slot, bytes32 value) external; function toBase64URL(bytes calldata data) external pure returns (string memory); function toBase64URL(string calldata data) external pure returns (string memory); diff --git a/testdata/default/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol index 7d757101ad3b..51f1d3ea0d9f 100644 --- a/testdata/default/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -55,8 +55,8 @@ contract SimpleCall { } contract ProxyWithDelegateCall { - function delegateCall(SimpleCall simpleCall) public { - address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); + function delegateCall(SimpleCall simpleCall) public returns (bool success) { + (success,) = address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); } } diff --git a/testdata/default/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol index cad184355443..f3e0a03c9fbc 100644 --- a/testdata/default/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -102,8 +102,8 @@ contract Emitter { /// Emulates `Emitter` in #760 contract LowLevelCaller { - function f() external { - address(this).call(abi.encodeWithSignature("g()")); + function f() external returns (bool success) { + (success,) = address(this).call(abi.encodeWithSignature("g()")); } function g() public {} diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 3e8f68a6cd6d..06e1c13e1c31 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -13,7 +13,7 @@ contract MyContract { uint256 forkId; bytes32 blockHash; - constructor(uint256 _forkId) public { + constructor(uint256 _forkId) { forkId = _forkId; blockHash = blockhash(block.number - 1); } @@ -98,14 +98,14 @@ contract ForkTest is DSTest { // test that we can "roll" blocks until a transaction function testCanRollForkUntilTransaction() public { // block to run transactions from - uint256 block = 16261704; + uint256 blockNumber = 16261704; // fork until previous block - uint256 fork = vm.createSelectFork("mainnet", block - 1); + uint256 fork = vm.createSelectFork("mainnet", blockNumber - 1); // block transactions in order: https://beaconcha.in/block/16261704#transactions // run transactions from current block until tx - bytes32 tx = 0x67cbad73764049e228495a3f90144aab4a37cb4b5fd697dffc234aa5ed811ace; + bytes32 transaction = 0x67cbad73764049e228495a3f90144aab4a37cb4b5fd697dffc234aa5ed811ace; // account that sends ether in 2 transaction before tx address account = 0xAe45a8240147E6179ec7c9f92c5A18F9a97B3fCA; @@ -119,7 +119,7 @@ contract ForkTest is DSTest { uint256 newBalance = account.balance - transferAmount; // execute transactions in block until tx - vm.rollFork(tx); + vm.rollFork(transaction); // balance must be less than newBalance due to gas spent assert(account.balance < newBalance); @@ -156,7 +156,7 @@ contract ForkTest is DSTest { DummyContract dummy = new DummyContract(); // this will succeed since `dummy` is deployed on the currently active fork - string memory msg = dummy.hello(); + string memory message = dummy.hello(); address dummyAddress = address(dummy); @@ -164,7 +164,7 @@ contract ForkTest is DSTest { assertEq(dummyAddress, address(dummy)); // this will revert since `dummy` does not exists on the currently active fork - string memory msg2 = dummy.hello(); + string memory message2 = dummy.hello(); } struct EthGetLogsJsonParseable { diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol new file mode 100644 index 000000000000..3a91493a50d4 --- /dev/null +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GasSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Flare public flare; + uint256 public slot; + + function setUp() public { + flare = new Flare(); + } + + function testGasExternal() public { + vm.startSnapshotGas("testAssertGasExternal"); + + flare.update(2); + + vm.stopSnapshotGas(); + } + + function testGasInternal() public { + vm.startSnapshotGas("testAssertGasInternalA"); + + slot = 1; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalB"); + + slot = 2; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalC"); + + slot = 0; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalD"); + + slot = 1; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalE"); + + slot = 2; + + vm.stopSnapshotGas(); + } + + // Writes to `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroup1() public { + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + vm.snapshotValue("a", a); + vm.snapshotValue("b", b); + vm.snapshotValue("c", c); + } + + // Writes to same `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroup2() public { + uint256 d = 123; + uint256 e = 456; + uint256 f = 789; + + vm.snapshotValue("d", d); + vm.snapshotValue("e", e); + vm.snapshotValue("f", f); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroup1() public { + uint256 o = 123; + uint256 i = 456; + uint256 q = 789; + + vm.snapshotValue("CustomGroup", "q", q); + vm.snapshotValue("CustomGroup", "i", i); + vm.snapshotValue("CustomGroup", "o", o); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroup2() public { + uint256 x = 123; + uint256 e = 456; + uint256 z = 789; + + vm.snapshotValue("CustomGroup", "z", z); + vm.snapshotValue("CustomGroup", "x", x); + vm.snapshotValue("CustomGroup", "e", e); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. + function testSnapshotGasSectionDefaultGroupStop() public { + vm.startSnapshotGas("testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasCustom` name. + function testSnapshotGasSectionCustomGroupStop() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionName() public { + vm.startSnapshotGas("testSnapshotGasSectionName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); + assertGt(gasUsed, 0); + } + + // Writes to `CustomGroup` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionGroupName() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. + function testSnapshotGasLastCallName() public { + flare.run(1); + + vm.snapshotGasLastCall("testSnapshotGasName"); + } + + // Writes to `CustomGroup` group with `testSnapshotGas` name. + function testSnapshotGasLastCallGroupName() public { + flare.run(1); + + vm.snapshotGasLastCall("CustomGroup", "testSnapshotGasGroupName"); + } +} + +contract GasComparisonTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public slotA; + uint256 public slotB; + uint256 public cachedGas; + + function testGasComparisonEmpty() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonEmptyA"); + vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonEmptyB", _snapEnd()); + } + + function testGasComparisonInternalCold() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalColdA"); + slotA = 1; + vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slotB = 1; + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalColdB", _snapEnd()); + } + + function testGasComparisonInternalWarm() public { + // Warm up the cache. + slotA = 1; + slotB = 1; + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalWarmA"); + slotA = 2; + vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slotB = 2; + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalWarmB", _snapEnd()); + } + + function testGasComparisonExternal() public { + // Warm up the cache. + TargetB targetA = new TargetB(); + targetA.update(1); + TargetB targetB = new TargetB(); + targetB.update(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonExternalA"); + targetA.update(2); + vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + targetB.update(2); + vm.snapshotValue("ComparisonGroup", "testGasComparisonExternalB", _snapEnd()); + } + + function testGasComparisonCreateA() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonCreateA"); + new TargetEmpty(); + vm.stopSnapshotGas(); + } + + function testGasComparisonCreateB() public { + // Start a comparitive Solidity snapshot. + _snapStart(); + new TargetEmpty(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonCreateB", _snapEnd()); + } + + // Internal function to start a Solidity snapshot. + function _snapStart() internal { + cachedGas = 1; + cachedGas = gasleft(); + } + + // Internal function to end a Solidity snapshot. + function _snapEnd() internal returns (uint256 gasUsed) { + gasUsed = cachedGas - gasleft() - 174; + cachedGas = 2; + } +} + +contract Flare { + TargetA public target; + bytes32[] public data; + + constructor() { + target = new TargetA(); + } + + function run(uint256 n_) public { + for (uint256 i = 0; i < n_; i++) { + data.push(keccak256(abi.encodePacked(i))); + } + } + + function update(uint256 x_) public { + target.update(x_); + } +} + +contract TargetA { + TargetB public target; + + constructor() { + target = new TargetB(); + } + + function update(uint256 x_) public { + target.update(x_); + } +} + +contract TargetB { + uint256 public x; + + function update(uint256 x_) public { + x = x_; + } +} + +contract TargetEmpty {} diff --git a/testdata/default/cheats/LastCallGas.t.sol b/testdata/default/cheats/LastCallGas.t.sol index 0f5b65e350e1..1164e67ac41e 100644 --- a/testdata/default/cheats/LastCallGas.t.sol +++ b/testdata/default/cheats/LastCallGas.t.sol @@ -34,7 +34,6 @@ abstract contract LastCallGasFixture is DSTest { struct Gas { uint64 gasTotalUsed; - uint64 gasMemoryUsed; int64 gasRefunded; } @@ -62,7 +61,6 @@ abstract contract LastCallGasFixture is DSTest { assertGt(lhs.gasLimit, 0); assertGt(lhs.gasRemaining, 0); assertEq(lhs.gasTotalUsed, rhs.gasTotalUsed); - assertEq(lhs.gasMemoryUsed, rhs.gasMemoryUsed); assertEq(lhs.gasRefunded, rhs.gasRefunded); } } @@ -71,19 +69,19 @@ contract LastCallGasIsolatedTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); } function testRecordGasRefund() public { _setup(); _performRefund(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasMemoryUsed: 0, gasRefunded: 4800})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasRefunded: 4800})); } } @@ -92,18 +90,18 @@ contract LastCallGasDefaultTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); _performCall(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasMemoryUsed: 0, gasRefunded: 0})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); } function testRecordGasRefund() public { _setup(); _performRefund(); - _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasMemoryUsed: 0, gasRefunded: 19900})); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasRefunded: 19900})); } } diff --git a/testdata/default/cheats/Prevrandao.t.sol b/testdata/default/cheats/Prevrandao.t.sol index 7011ce3bee9c..75f2b2cc1302 100644 --- a/testdata/default/cheats/Prevrandao.t.sol +++ b/testdata/default/cheats/Prevrandao.t.sol @@ -23,12 +23,12 @@ contract PrevrandaoTest is DSTest { function testPrevrandaoSnapshotFuzzed(uint256 newPrevrandao) public { vm.assume(newPrevrandao != block.prevrandao); uint256 oldPrevrandao = block.prevrandao; - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); vm.prevrandao(newPrevrandao); assertEq(block.prevrandao, newPrevrandao); - assert(vm.revertTo(snapshot)); + assert(vm.revertToState(snapshotId)); assertEq(block.prevrandao, oldPrevrandao); } } diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index a0aa2cb5332d..a61ffecc9ce8 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -225,7 +225,7 @@ contract RecordAccountAccessesTest is DSTest { Proxy proxy = new Proxy(address(one)); cheats.startStateDiffRecording(); - address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); + (bool success,) = address(proxy).call(abi.encodeCall(StorageAccessor.read, bytes32(uint256(1234)))); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); assertEq(called.length, 2, "incorrect length"); diff --git a/testdata/default/cheats/Snapshots.t.sol b/testdata/default/cheats/Snapshots.t.sol deleted file mode 100644 index bb7b4e0e6008..000000000000 --- a/testdata/default/cheats/Snapshots.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -pragma solidity 0.8.18; - -import "ds-test/test.sol"; -import "cheats/Vm.sol"; - -struct Storage { - uint256 slot0; - uint256 slot1; -} - -contract SnapshotTest is DSTest { - Vm constant vm = Vm(HEVM_ADDRESS); - - Storage store; - - function setUp() public { - store.slot0 = 10; - store.slot1 = 20; - } - - function testSnapshot() public { - uint256 snapshot = vm.snapshot(); - store.slot0 = 300; - store.slot1 = 400; - - assertEq(store.slot0, 300); - assertEq(store.slot1, 400); - - vm.revertTo(snapshot); - assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); - assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); - } - - function testSnapshotRevertDelete() public { - uint256 snapshot = vm.snapshot(); - store.slot0 = 300; - store.slot1 = 400; - - assertEq(store.slot0, 300); - assertEq(store.slot1, 400); - - vm.revertToAndDelete(snapshot); - assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); - assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); - // nothing to revert to anymore - assert(!vm.revertTo(snapshot)); - } - - function testSnapshotDelete() public { - uint256 snapshot = vm.snapshot(); - store.slot0 = 300; - store.slot1 = 400; - - vm.deleteSnapshot(snapshot); - // nothing to revert to anymore - assert(!vm.revertTo(snapshot)); - } - - function testSnapshotDeleteAll() public { - uint256 snapshot = vm.snapshot(); - store.slot0 = 300; - store.slot1 = 400; - - vm.deleteSnapshots(); - // nothing to revert to anymore - assert(!vm.revertTo(snapshot)); - } - - // - function testSnapshotsMany() public { - uint256 preState; - for (uint256 c = 0; c < 10; c++) { - for (uint256 cc = 0; cc < 10; cc++) { - preState = vm.snapshot(); - vm.revertToAndDelete(preState); - assert(!vm.revertTo(preState)); - } - } - } - - // tests that snapshots can also revert changes to `block` - function testBlockValues() public { - uint256 num = block.number; - uint256 time = block.timestamp; - uint256 prevrandao = block.prevrandao; - - uint256 snapshot = vm.snapshot(); - - vm.warp(1337); - assertEq(block.timestamp, 1337); - - vm.roll(99); - assertEq(block.number, 99); - - vm.prevrandao(uint256(123)); - assertEq(block.prevrandao, 123); - - assert(vm.revertTo(snapshot)); - - assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); - assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); - assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); - } -} diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol new file mode 100644 index 000000000000..c26c11c3414e --- /dev/null +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +struct Storage { + uint256 slot0; + uint256 slot1; +} + +contract StateSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Storage store; + + function setUp() public { + store.slot0 = 10; + store.slot1 = 20; + } + + function testSnapshotState() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToState(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + } + + function testSnapshotStateRevertDelete() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToStateAndDelete(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + function testSnapshotStateDelete() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteStateSnapshot(snapshotId); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + function testSnapshotStateDeleteAll() public { + uint256 snapshotId = vm.snapshotState(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteStateSnapshots(); + // nothing to revert to anymore + assert(!vm.revertToState(snapshotId)); + } + + // + function testSnapshotStatesMany() public { + uint256 snapshotId; + for (uint256 c = 0; c < 10; c++) { + for (uint256 cc = 0; cc < 10; cc++) { + snapshotId = vm.snapshotState(); + vm.revertToStateAndDelete(snapshotId); + assert(!vm.revertToState(snapshotId)); + } + } + } + + // tests that snapshots can also revert changes to `block` + function testBlockValues() public { + uint256 num = block.number; + uint256 time = block.timestamp; + uint256 prevrandao = block.prevrandao; + + uint256 snapshotId = vm.snapshotState(); + + vm.warp(1337); + assertEq(block.timestamp, 1337); + + vm.roll(99); + assertEq(block.number, 99); + + vm.prevrandao(uint256(123)); + assertEq(block.prevrandao, 123); + + assert(vm.revertToState(snapshotId)); + + assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); + assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); + assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); + } +} + +// TODO: remove this test suite once `snapshot*` has been deprecated in favor of `snapshotState*`. +contract DeprecatedStateSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + Storage store; + + function setUp() public { + store.slot0 = 10; + store.slot1 = 20; + } + + function testSnapshotState() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertTo(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + } + + function testSnapshotStateRevertDelete() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + assertEq(store.slot0, 300); + assertEq(store.slot1, 400); + + vm.revertToAndDelete(snapshotId); + assertEq(store.slot0, 10, "snapshot revert for slot 0 unsuccessful"); + assertEq(store.slot1, 20, "snapshot revert for slot 1 unsuccessful"); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + function testSnapshotStateDelete() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshot(snapshotId); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + function testSnapshotStateDeleteAll() public { + uint256 snapshotId = vm.snapshot(); + store.slot0 = 300; + store.slot1 = 400; + + vm.deleteSnapshots(); + // nothing to revert to anymore + assert(!vm.revertTo(snapshotId)); + } + + // + function testSnapshotStatesMany() public { + uint256 snapshotId; + for (uint256 c = 0; c < 10; c++) { + for (uint256 cc = 0; cc < 10; cc++) { + snapshotId = vm.snapshot(); + vm.revertToAndDelete(snapshotId); + assert(!vm.revertTo(snapshotId)); + } + } + } + + // tests that snapshots can also revert changes to `block` + function testBlockValues() public { + uint256 num = block.number; + uint256 time = block.timestamp; + uint256 prevrandao = block.prevrandao; + + uint256 snapshotId = vm.snapshot(); + + vm.warp(1337); + assertEq(block.timestamp, 1337); + + vm.roll(99); + assertEq(block.number, 99); + + vm.prevrandao(uint256(123)); + assertEq(block.prevrandao, 123); + + assert(vm.revertTo(snapshotId)); + + assertEq(block.number, num, "snapshot revert for block.number unsuccessful"); + assertEq(block.timestamp, time, "snapshot revert for block.timestamp unsuccessful"); + assertEq(block.prevrandao, prevrandao, "snapshot revert for block.prevrandao unsuccessful"); + } +} diff --git a/testdata/default/cheats/loadAllocs.t.sol b/testdata/default/cheats/loadAllocs.t.sol index 358608860bd5..a4b72af6b2fe 100644 --- a/testdata/default/cheats/loadAllocs.t.sol +++ b/testdata/default/cheats/loadAllocs.t.sol @@ -16,7 +16,7 @@ contract LoadAllocsTest is DSTest { allocsPath = string.concat(vm.projectRoot(), "/fixtures/Json/test_allocs.json"); // Snapshot the state; We'll restore it in each test that loads allocs inline. - snapshotId = vm.snapshot(); + snapshotId = vm.snapshotState(); // Load the allocs file. vm.loadAllocs(allocsPath); @@ -40,7 +40,7 @@ contract LoadAllocsTest is DSTest { /// @dev Checks that the `loadAllocs` cheatcode persists account info if called inline function testLoadAllocsStatic() public { // Restore the state snapshot prior to the allocs file being loaded. - vm.revertTo(snapshotId); + vm.revertToState(snapshotId); // Load the allocs file vm.loadAllocs(allocsPath); @@ -61,7 +61,7 @@ contract LoadAllocsTest is DSTest { /// @dev Checks that the `loadAllocs` cheatcode overrides existing account information (if present) function testLoadAllocsOverride() public { // Restore the state snapshot prior to the allocs file being loaded. - vm.revertTo(snapshotId); + vm.revertToState(snapshotId); // Populate the alloc'd account's code. vm.etch(ALLOCD, hex"FF"); @@ -88,7 +88,7 @@ contract LoadAllocsTest is DSTest { /// within the allocs/genesis file for the account field (i.e., partial overrides) function testLoadAllocsPartialOverride() public { // Restore the state snapshot prior to the allocs file being loaded. - vm.revertTo(snapshotId); + vm.revertToState(snapshotId); // Populate the alloc'd account's code. vm.etch(ALLOCD_B, hex"FF"); diff --git a/testdata/default/fork/Transact.t.sol b/testdata/default/fork/Transact.t.sol index 0e3d9c9cb763..0f95abb40677 100644 --- a/testdata/default/fork/Transact.t.sol +++ b/testdata/default/fork/Transact.t.sol @@ -23,7 +23,7 @@ contract TransactOnForkTest is DSTest { uint256 fork = vm.createFork("mainnet", 17134913); vm.selectFork(fork); // a random transfer transaction in the next block: https://etherscan.io/tx/0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771 - bytes32 tx = 0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771; + bytes32 transaction = 0xaf6201d435b216a858c580e20512a16136916d894aa33260650e164e3238c771; address sender = address(0x9B315A70FEe05a70A9F2c832E93a7095FEb32Bfe); address recipient = address(0xDB358B93157Df9b3B1eE9Ea5CDB7D0aE9a1D8110); @@ -37,7 +37,7 @@ contract TransactOnForkTest is DSTest { uint256 expectedSenderBalance = sender.balance - transferAmount; // execute the transaction - vm.transact(tx); + vm.transact(transaction); // recipient received transfer assertEq(recipient.balance, expectedRecipientBalance); @@ -52,7 +52,7 @@ contract TransactOnForkTest is DSTest { vm.selectFork(fork); // a random ERC20 USDT transfer transaction in the next block: https://etherscan.io/tx/0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c - bytes32 tx = 0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c; + bytes32 transaction = 0x33350512fec589e635865cbdb38fa3a20a2aa160c52611f1783d0ba24ad13c8c; address sender = address(0x2e09BB78B3D64d98Da44D1C776fa77dcd133ED54); address recipient = address(0x23a6B9711B711b1d404F2AA740bde350c67a6F06); @@ -83,7 +83,7 @@ contract TransactOnForkTest is DSTest { vm.recordLogs(); // execute the transaction - vm.transact(tx); + vm.transact(transaction); // extract recorded logs Vm.Log[] memory logs = vm.getRecordedLogs(); diff --git a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol index 737cf5ba9dd0..cfd2d18627f6 100644 --- a/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol +++ b/testdata/default/fuzz/invariant/common/InvariantCustomError.t.sol @@ -5,10 +5,10 @@ import "ds-test/test.sol"; import "cheats/Vm.sol"; contract ContractWithCustomError { - error InvariantCustomError(uint256, string); + error CustomError(uint256, string); - function revertWithInvariantCustomError() external { - revert InvariantCustomError(111, "custom"); + function revertWithCustomError() external { + revert CustomError(111, "custom"); } } @@ -20,7 +20,7 @@ contract Handler is DSTest { } function revertTarget() external { - target.revertWithInvariantCustomError(); + target.revertWithCustomError(); } } diff --git a/testdata/default/repros/Issue2984.t.sol b/testdata/default/repros/Issue2984.t.sol index 1a181ad533c2..5f0203369e0d 100644 --- a/testdata/default/repros/Issue2984.t.sol +++ b/testdata/default/repros/Issue2984.t.sol @@ -12,11 +12,11 @@ contract Issue2984Test is DSTest { function setUp() public { fork = vm.createSelectFork("avaxTestnet", 12880747); - snapshot = vm.snapshot(); + snapshot = vm.snapshotState(); } function testForkRevertSnapshot() public { - vm.revertTo(snapshot); + vm.revertToState(snapshot); } function testForkSelectSnapshot() public { diff --git a/testdata/default/repros/Issue3055.t.sol b/testdata/default/repros/Issue3055.t.sol index cacf5282f058..72461a6c0165 100644 --- a/testdata/default/repros/Issue3055.t.sol +++ b/testdata/default/repros/Issue3055.t.sol @@ -9,15 +9,15 @@ contract Issue3055Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function test_snapshot() external { - uint256 snapId = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertEq(uint256(0), uint256(1)); - vm.revertTo(snapId); + vm.revertToState(snapshotId); } function test_snapshot2() public { - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertTrue(false); - vm.revertTo(snapshot); + vm.revertToState(snapshotId); assertTrue(true); } @@ -29,8 +29,8 @@ contract Issue3055Test is DSTest { } function exposed_snapshot3() public { - uint256 snapshot = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); assertTrue(false); - vm.revertTo(snapshot); + vm.revertToState(snapshotId); } } diff --git a/testdata/default/repros/Issue3792.t.sol b/testdata/default/repros/Issue3792.t.sol index 723329f937a1..1adeb88af8c9 100644 --- a/testdata/default/repros/Issue3792.t.sol +++ b/testdata/default/repros/Issue3792.t.sol @@ -16,10 +16,10 @@ contract TestSetup is Config, DSTest { // We now check for keccak256("failed") on the hevm address. // This test should succeed. function testSnapshotStorageShift() public { - uint256 snapshotId = vm.snapshot(); + uint256 snapshotId = vm.snapshotState(); vm.prank(test); - vm.revertTo(snapshotId); + vm.revertToState(snapshotId); } } diff --git a/testdata/default/repros/Issue6355.t.sol b/testdata/default/repros/Issue6355.t.sol index d7830152a60a..990ae941c547 100644 --- a/testdata/default/repros/Issue6355.t.sol +++ b/testdata/default/repros/Issue6355.t.sol @@ -7,11 +7,11 @@ import "cheats/Vm.sol"; // https://github.com/foundry-rs/foundry/issues/6355 contract Issue6355Test is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - uint256 snapshot; + uint256 snapshotId; Target targ; function setUp() public { - snapshot = vm.snapshot(); + snapshotId = vm.snapshotState(); targ = new Target(); } @@ -23,7 +23,7 @@ contract Issue6355Test is DSTest { // always fails function test_shouldFailWithRevertTo() public { assertEq(3, targ.num()); - vm.revertTo(snapshot); + vm.revertToState(snapshotId); } // always fails diff --git a/testdata/default/repros/Issue8383.t.sol b/testdata/default/repros/Issue8383.t.sol index a002b4b3dc1d..36e34d331cf3 100644 --- a/testdata/default/repros/Issue8383.t.sol +++ b/testdata/default/repros/Issue8383.t.sol @@ -28,8 +28,8 @@ contract Issue8383Test is DSTest { _verifierCall(truncatedPayload); } if (uint256(keccak256(abi.encode(payload, "1"))) & 0x1f == 0) { - uint256 r = uint256(keccak256(abi.encode(payload, "2"))); - payload = abi.encodePacked(payload, new bytes(r & 0xff)); + uint256 z = uint256(keccak256(abi.encode(payload, "2"))); + payload = abi.encodePacked(payload, new bytes(z & 0xff)); } bytes32 payloadHash = keccak256(payload); if (_vectorTested[payloadHash]) return _vectorResult[payloadHash]; diff --git a/testdata/foundry.toml b/testdata/foundry.toml index e9189bb008a3..30621914fa35 100644 --- a/testdata/foundry.toml +++ b/testdata/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc = "0.8.18" +# solc = "0.8.18" block_base_fee_per_gas = 0 block_coinbase = "0x0000000000000000000000000000000000000000" block_difficulty = 0