From 5f1b49baca7c42eb67b7b81439fd747d24c9685f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 11:50:54 +0000 Subject: [PATCH 01/64] snapshot -> snapshotState (breaking change) --- Cargo.lock | 14 +- crates/cheatcodes/assets/cheatcodes.json | 122 ++++++++++++------ crates/cheatcodes/spec/src/vm.rs | 12 +- crates/cheatcodes/src/evm.rs | 10 +- testdata/cheats/Vm.sol | 12 +- .../{Snapshots.t.sol => StateSnapshots.t.sol} | 60 +++++---- 6 files changed, 142 insertions(+), 88 deletions(-) rename testdata/default/cheats/{Snapshots.t.sol => StateSnapshots.t.sol} (58%) diff --git a/Cargo.lock b/Cargo.lock index 3d1e00b943c1..6e0cc7171596 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -307,9 +307,9 @@ dependencies = [ [[package]] name = "alloy-rlp" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a43b18702501396fa9bcdeecd533bc85fac75150d308fc0f6800a01e6234a003" +checksum = "26154390b1d205a4a7ac7352aa2eb4f81f391399d4e2f546fb81a2f8bb383f62" dependencies = [ "alloy-rlp-derive", "arrayvec", @@ -318,9 +318,9 @@ dependencies = [ [[package]] name = "alloy-rlp-derive" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83524c1f6162fcb5b0decf775498a125066c86dda6066ed609531b0e912f85a" +checksum = "4d0f2d905ebd295e7effec65e5f6868d153936130ae718352771de3e7d03c75c" dependencies = [ "proc-macro2", "quote", @@ -6002,14 +6002,14 @@ dependencies = [ [[package]] name = "opener" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8df34be653210fbe9ffaff41d3b92721c56ce82dfee58ee684f9afb5e3a90c0" +checksum = "d0812e5e4df08da354c851a3376fead46db31c2214f849d3de356d774d057681" dependencies = [ "bstr", "dbus", "normpath", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 6549b31af167..951fa8640aab 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3533,18 +3533,18 @@ }, { "func": { - "id": "deleteSnapshot", + "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 deleteSnapshot(uint256 snapshotId) external returns (bool success);", + "declaration": "function deleteStateSnapshot(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", - "signature": "deleteSnapshot(uint256)", - "selector": "0xa6368557", + "signature": "deleteStateSnapshot(uint256)", + "selector": "0x08d6b37a", "selectorBytes": [ - 166, - 54, - 133, - 87 + 8, + 214, + 179, + 122 ] }, "group": "evm", @@ -3553,18 +3553,18 @@ }, { "func": { - "id": "deleteSnapshots", + "id": "deleteStateSnapshots", "description": "Removes _all_ snapshots previously created by `snapshot`.", - "declaration": "function deleteSnapshots() external;", + "declaration": "function deleteStateSnapshots() external;", "visibility": "external", "mutability": "", - "signature": "deleteSnapshots()", - "selector": "0x421ae469", + "signature": "deleteStateSnapshots()", + "selector": "0xe0933c74", "selectorBytes": [ - 66, - 26, - 228, - 105 + 224, + 147, + 60, + 116 ] }, "group": "evm", @@ -7013,18 +7013,18 @@ }, { "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`.", - "declaration": "function revertTo(uint256 snapshotId) external returns (bool success);", + "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": "revertTo(uint256)", - "selector": "0x44d7f0a4", + "signature": "revertToState(uint256)", + "selector": "0xc2527405", "selectorBytes": [ - 68, - 215, - 240, - 164 + 194, + 82, + 116, + 5 ] }, "group": "evm", @@ -7033,18 +7033,18 @@ }, { "func": { - "id": "revertToAndDelete", + "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 revertToAndDelete(uint256 snapshotId) external returns (bool success);", + "declaration": "function revertToStateAndDelete(uint256 snapshotId) external returns (bool success);", "visibility": "external", "mutability": "", - "signature": "revertToAndDelete(uint256)", - "selector": "0x03e0aca9", + "signature": "revertToStateAndDelete(uint256)", + "selector": "0x3a1985dc", "selectorBytes": [ - 3, - 224, - 172, - 169 + 58, + 25, + 133, + 220 ] }, "group": "evm", @@ -7973,18 +7973,18 @@ }, { "func": { - "id": "snapshot", + "id": "snapshotState", "description": "Snapshot the current state of the evm.\nReturns the ID of the snapshot that was created.\nTo revert a snapshot use `revertTo`.", - "declaration": "function snapshot() external returns (uint256 snapshotId);", + "declaration": "function snapshotState() external returns (uint256 snapshotId);", "visibility": "external", "mutability": "", - "signature": "snapshot()", - "selector": "0x9711715a", + "signature": "snapshotState()", + "selector": "0x9cd23835", "selectorBytes": [ - 151, - 17, - 113, - 90 + 156, + 210, + 56, + 53 ] }, "group": "evm", @@ -8071,6 +8071,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "startGasSnapshot", + "description": "", + "declaration": "function startGasSnapshot() external;", + "visibility": "external", + "mutability": "", + "signature": "startGasSnapshot()", + "selector": "0xe6b17ea5", + "selectorBytes": [ + 230, + 177, + 126, + 165 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "startMappingRecording", @@ -8211,6 +8231,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "stopGasSnapshot", + "description": "", + "declaration": "function stopGasSnapshot() external;", + "visibility": "external", + "mutability": "", + "signature": "stopGasSnapshot()", + "selector": "0x10aa79b7", + "selectorBytes": [ + 16, + 170, + 121, + 183 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "stopMappingRecording", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index a4dc636902f1..e53103ab9ec6 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -505,7 +505,7 @@ interface Vm { /// Returns the ID of the snapshot that was created. /// To revert a snapshot use `revertTo`. #[cheatcode(group = Evm, safety = Unsafe)] - function snapshot() external returns (uint256 snapshotId); + function snapshotState() external returns (uint256 snapshotId); /// Revert the state of the EVM to a previous snapshot /// Takes the snapshot ID to revert to. @@ -513,9 +513,9 @@ 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); /// Revert the state of the EVM to a previous snapshot and automatically deletes the snapshots /// Takes the snapshot ID to revert to. @@ -523,7 +523,7 @@ 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); /// Removes the snapshot with the given ID created by `snapshot`. /// Takes the snapshot ID to delete. @@ -531,11 +531,11 @@ 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); /// 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/evm.rs b/crates/cheatcodes/src/evm.rs index 1c93afd0f265..a73a17b7ed7d 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -472,14 +472,14 @@ impl Cheatcode for readCallersCall { } } -impl Cheatcode for snapshotCall { +impl Cheatcode for snapshotStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.db.snapshot(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } -impl Cheatcode for revertToCall { +impl Cheatcode for revertToStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( @@ -498,7 +498,7 @@ impl Cheatcode for revertToCall { } } -impl Cheatcode for revertToAndDeleteCall { +impl Cheatcode for revertToStateAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = if let Some(journaled_state) = ccx.ecx.db.revert( @@ -517,14 +517,14 @@ impl Cheatcode for revertToAndDeleteCall { } } -impl Cheatcode for deleteSnapshotCall { +impl Cheatcode for deleteStateSnapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; let result = ccx.ecx.db.delete_snapshot(*snapshotId); Ok(result.abi_encode()) } } -impl Cheatcode for deleteSnapshotsCall { +impl Cheatcode for deleteStateSnapshotsCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx.db.delete_snapshots(); diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 0e731d88ebf7..c5292c0a14e8 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -172,8 +172,8 @@ interface Vm { function createWallet(uint256 privateKey) external returns (Wallet memory wallet); function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); 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); @@ -346,8 +346,8 @@ interface Vm { function replace(string calldata input, string calldata from, string calldata to) external pure returns (string memory output); function resetNonce(address account) external; function resumeGasMetering() external; - 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; @@ -394,11 +394,12 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; - function snapshot() external returns (uint256 snapshotId); + function snapshotState() external returns (uint256 snapshotId); function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; function startBroadcast(uint256 privateKey) external; + function startGasSnapshot() external; function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; @@ -406,6 +407,7 @@ interface Vm { function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); function stopBroadcast() external; function stopExpectSafeMemory() external; + function stopGasSnapshot() external; function stopMappingRecording() external; function stopPrank() external; function store(address target, bytes32 slot, bytes32 value) external; diff --git a/testdata/default/cheats/Snapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol similarity index 58% rename from testdata/default/cheats/Snapshots.t.sol rename to testdata/default/cheats/StateSnapshots.t.sol index bb7b4e0e6008..7fa669c50316 100644 --- a/testdata/default/cheats/Snapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -9,7 +9,7 @@ struct Storage { uint256 slot1; } -contract SnapshotTest is DSTest { +contract StateSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); Storage store; @@ -19,62 +19,62 @@ contract SnapshotTest is DSTest { store.slot1 = 20; } - function testSnapshot() public { - uint256 snapshot = vm.snapshot(); + function testSnapshotState() public { + uint256 snapshot = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; assertEq(store.slot0, 300); assertEq(store.slot1, 400); - vm.revertTo(snapshot); + vm.revertToState(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(); + function testSnapshotStateRevertDelete() public { + uint256 snapshot = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; assertEq(store.slot0, 300); assertEq(store.slot1, 400); - vm.revertToAndDelete(snapshot); + vm.revertToStateAndDelete(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)); + assert(!vm.revertToState(snapshot)); } - function testSnapshotDelete() public { - uint256 snapshot = vm.snapshot(); + function testSnapshotStateDelete() public { + uint256 snapshot = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; vm.deleteSnapshot(snapshot); // nothing to revert to anymore - assert(!vm.revertTo(snapshot)); + assert(!vm.revertToState(snapshot)); } - function testSnapshotDeleteAll() public { - uint256 snapshot = vm.snapshot(); + function testSnapshotStateDeleteAll() public { + uint256 snapshot = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; vm.deleteSnapshots(); // nothing to revert to anymore - assert(!vm.revertTo(snapshot)); + assert(!vm.revertToState(snapshot)); } // - function testSnapshotsMany() public { + function testSnapshotStatesMany() 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)); + preState = vm.snapshotState(); + vm.revertToStateAndDelete(preState); + assert(!vm.revertToState(preState)); } } } @@ -85,7 +85,7 @@ contract SnapshotTest is DSTest { uint256 time = block.timestamp; uint256 prevrandao = block.prevrandao; - uint256 snapshot = vm.snapshot(); + uint256 snapshot = vm.snapshotState(); vm.warp(1337); assertEq(block.timestamp, 1337); @@ -96,10 +96,22 @@ contract SnapshotTest is DSTest { 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"); + assert(vm.revertToState(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" + ); } } From 43bb19c42d52d73db951fb30dd19ef27c22e7716 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 12:10:44 +0000 Subject: [PATCH 02/64] specify snapshot as state_snapshot to make room for gas_snapshot logic --- crates/cheatcodes/spec/src/vm.rs | 20 +++++++---- crates/cheatcodes/src/evm.rs | 45 +++++++++++++++++++------ crates/evm/core/src/backend/cow.rs | 8 ++--- crates/evm/core/src/backend/mod.rs | 52 ++++++++++++++--------------- crates/evm/evm/src/executors/mod.rs | 6 ++-- 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index e53103ab9ec6..5ff311744a08 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,6 +499,20 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + // -------- Gas Snapshots -------- + + /// Gets the gas used in the last call. + #[cheatcode(group = Evm, safety = Safe)] + function lastCallGas() external view returns (Gas memory gas); + + /// Start a snapshot capture of the current gas usage. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata name) external returns (uint256 snapshotId); + + /// Stop the snapshot capture of the current gas usage, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas() external returns (bool success); + // -------- State Snapshots -------- /// Snapshot the current state of the evm. @@ -662,12 +676,6 @@ interface Vm { #[cheatcode(group = Evm, safety = Safe)] function resumeGasMetering() external; - // -------- Gas Measurement -------- - - /// Gets the gas used in the last call. - #[cheatcode(group = Evm, safety = Safe)] - function lastCallGas() external view returns (Gas memory gas); - // ======== Test Assertions and Utilities ======== /// If the condition is false, discard this run's fuzz inputs and generate new ones. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index a73a17b7ed7d..49673f908a08 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -62,6 +62,15 @@ pub struct DealRecord { pub new_balance: U256, } +/// Records the `snapshotGas` cheatcodes. +#[derive(Clone, Debug)] +pub struct SnapshotGasRecord { + /// The name of the snapshot. + pub name: String, + /// The total gas used in the snapshot. + pub gas_used: U256, +} + impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; @@ -224,16 +233,6 @@ impl Cheatcode for resumeGasMeteringCall { } } -impl Cheatcode for lastCallGasCall { - fn apply(&self, state: &mut Cheatcodes) -> Result { - let Self {} = self; - let Some(last_call_gas) = &state.last_call_gas else { - bail!("no external call was made yet"); - }; - Ok(last_call_gas.abi_encode()) - } -} - impl Cheatcode for chainIdCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; @@ -472,6 +471,30 @@ impl Cheatcode for readCallersCall { } } +impl Cheatcode for lastCallGasCall { + fn apply(&self, state: &mut Cheatcodes) -> Result { + let Self {} = self; + let Some(last_call_gas) = &state.last_call_gas else { + bail!("no external call was made yet"); + }; + Ok(last_call_gas.abi_encode()) + } +} + +impl Cheatcode for startSnapshotGasCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + + Ok(Default::default()) + } +} + +impl Cheatcode for stopSnapshotGasCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + Ok(Default::default()) + } +} + impl Cheatcode for snapshotStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; @@ -527,7 +550,7 @@ impl Cheatcode for deleteStateSnapshotCall { impl Cheatcode for deleteStateSnapshotsCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.ecx.db.delete_snapshots(); + ccx.ecx.db.delete_state_snapshots(); Ok(Default::default()) } } diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 2dcd985ae912..fb3b603c9ed7 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -83,8 +83,8 @@ impl<'a> CowBackend<'a> { /// Returns whether there was a 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. @@ -133,9 +133,9 @@ impl<'a> DatabaseExt for CowBackend<'a> { 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/mod.rs b/crates/evm/core/src/backend/mod.rs index 7c2ed11c4b23..c41b5fcf5cbd 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -99,14 +99,14 @@ pub trait DatabaseExt: Database + DatabaseCommit { action: RevertSnapshotAction, ) -> 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; - /// 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 /// @@ -554,9 +554,9 @@ 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) -> &Snapshots> { + &self.inner.state_snapshots } /// Sets the address of the `DSTest` contract that is being executed @@ -592,18 +592,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 @@ -904,7 +904,7 @@ 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( + let id = self.inner.state_snapshots.insert(BackendSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), env.clone(), @@ -920,11 +920,11 @@ impl DatabaseExt for Backend { current: &mut Env, action: RevertSnapshotAction, ) -> 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 @@ -934,7 +934,7 @@ 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); } } } @@ -970,21 +970,21 @@ impl DatabaseExt for Backend { } 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() + 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 { @@ -1594,8 +1594,8 @@ pub struct BackendInner { // 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 + pub state_snapshots: Snapshots>, + /// 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 @@ -1604,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 @@ -1791,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/evm/src/executors/mod.rs b/crates/evm/evm/src/executors/mod.rs index 2f08b2f42efd..cc70a7d45c51 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) } @@ -513,7 +513,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; } From a868b438e0b6da9f8fd6e26163dfb197b40d0d17 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 12:18:30 +0000 Subject: [PATCH 03/64] further internal changes in preperation of snapshot_gas --- crates/cheatcodes/src/evm.rs | 8 ++++---- crates/evm/core/src/backend/cow.rs | 14 +++++++------- crates/evm/core/src/backend/mod.rs | 20 ++++++++++---------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 49673f908a08..49a0fecdfaf3 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -498,14 +498,14 @@ impl Cheatcode for stopSnapshotGasCall { impl Cheatcode for snapshotStateCall { 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()) + Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } } impl Cheatcode for revertToStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert( + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( *snapshotId, &ccx.ecx.journaled_state, &mut ccx.ecx.env, @@ -524,7 +524,7 @@ impl Cheatcode for revertToStateCall { impl Cheatcode for revertToStateAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert( + let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( *snapshotId, &ccx.ecx.journaled_state, &mut ccx.ecx.env, @@ -543,7 +543,7 @@ impl Cheatcode for revertToStateAndDeleteCall { impl Cheatcode for deleteStateSnapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = ccx.ecx.db.delete_snapshot(*snapshotId); + let result = ccx.ecx.db.delete_state_snapshot(*snapshotId); Ok(result.abi_encode()) } } diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index fb3b603c9ed7..0c544c3bf393 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -111,24 +111,24 @@ 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, ) -> 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 } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index c41b5fcf5cbd..4d4d857317f5 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -72,14 +72,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; + 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. @@ -91,7 +91,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// `Self::snapshot`. /// /// Depending on [RevertSnapshotAction] it will keep the snapshot alive or delete it. - fn revert( + fn revert_state_snapshot( &mut self, id: U256, journaled_state: &JournaledState, @@ -99,11 +99,11 @@ pub trait DatabaseExt: Database + DatabaseCommit { action: RevertSnapshotAction, ) -> Option; - /// Deletes the state 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 state snapshots. fn delete_state_snapshots(&mut self); @@ -902,8 +902,8 @@ impl Backend { } impl DatabaseExt for Backend { - fn snapshot(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { - trace!("create snapshot"); + fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { + trace!("create state snapshot"); let id = self.inner.state_snapshots.insert(BackendSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), @@ -913,7 +913,7 @@ impl DatabaseExt for Backend { id } - fn revert( + fn revert_state_snapshot( &mut self, id: U256, current_state: &JournaledState, @@ -979,7 +979,7 @@ impl DatabaseExt for Backend { } } - fn delete_snapshot(&mut self, id: U256) -> bool { + fn delete_state_snapshot(&mut self, id: U256) -> bool { self.inner.state_snapshots.remove_at(id).is_some() } From 91ff76d01f5620050aaf222fac1a9113799ef244 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 12:42:41 +0000 Subject: [PATCH 04/64] start adding gas snapshot logic --- crates/cheatcodes/spec/src/vm.rs | 6 +++--- crates/cheatcodes/src/evm.rs | 26 ++++++++++++++++++++++---- crates/cheatcodes/src/inspector.rs | 11 +++++++---- crates/evm/core/src/backend/cow.rs | 2 +- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 5ff311744a08..eec2a67d01f9 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -103,7 +103,7 @@ interface Vm { /// The total gas used. uint64 gasTotalUsed; /// DEPRECATED: The amount of gas used for memory expansion. Ref: - uint64 gasMemoryUsed; + // uint64 gasMemoryUsed; /// The amount of gas refunded. int64 gasRefunded; /// The amount of gas remaining. @@ -507,11 +507,11 @@ interface Vm { /// Start a snapshot capture of the current gas usage. #[cheatcode(group = Evm, safety = Unsafe)] - function startSnapshotGas(string calldata name) external returns (uint256 snapshotId); + function startSnapshotGas(string calldata name) external returns (bool success); /// Stop the snapshot capture of the current gas usage, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] - function stopSnapshotGas() external returns (bool success); + function stopSnapshotGas(string calldata name) external returns (Gas memory gas); // -------- State Snapshots -------- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 49a0fecdfaf3..b7069a4afc0c 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -64,11 +64,13 @@ pub struct DealRecord { /// Records the `snapshotGas` cheatcodes. #[derive(Clone, Debug)] -pub struct SnapshotGasRecord { +pub struct GasRecord { /// The name of the snapshot. pub name: String, - /// The total gas used in the snapshot. - pub gas_used: U256, + /// The total gas used at the start of the snapshot. + pub gas_start: Option, + /// The amount of gas used at the end of the snapshot. + pub gas_end: Option, } impl Cheatcode for addrCall { @@ -484,13 +486,29 @@ impl Cheatcode for lastCallGasCall { impl Cheatcode for startSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - + let record = GasRecord { + name: name.clone(), + gas_start: ccx.state.last_call_gas.clone(), + gas_end: None, + }; + ccx.state.recorded_gas.push(record); Ok(Default::default()) } } impl Cheatcode for stopSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + let record = ccx + .state + .recorded_gas + .iter_mut() + .find(|record| record.name == *name) + .ok_or_else(|| fmt_err!("gas snapshot not found: {name}"))?; + record.gas_end = ccx.state.last_call_gas.clone(); + + // Write record to disk + Ok(Default::default()) } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 0d377a382d96..fb63315b5154 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}, @@ -13,8 +13,8 @@ use crate::{ self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, ExpectedRevert, ExpectedRevertKind, }, - 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}; @@ -259,6 +259,9 @@ pub struct Cheatcodes { /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option, + /// Recorded gas + pub recorded_gas: Vec, + /// Mocked calls // **Note**: inner must a BTreeMap because of special `Ord` impl for `MockCallDataContext` pub mocked_calls: HashMap>, @@ -341,6 +344,7 @@ impl Cheatcodes { accesses: Default::default(), recorded_account_diffs_stack: Default::default(), recorded_logs: Default::default(), + recorded_gas: Default::default(), last_call_gas: Default::default(), mocked_calls: Default::default(), expected_calls: Default::default(), @@ -1090,7 +1094,6 @@ impl Inspector for Cheatcodes { self.last_call_gas = Some(crate::Vm::Gas { gasLimit: gas.limit(), gasTotalUsed: gas.spent(), - gasMemoryUsed: 0, gasRefunded: gas.refunded(), gasRemaining: gas.remaining(), }); diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 0c544c3bf393..0796cdb1ec24 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -80,7 +80,7 @@ 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_state_snapshot_failure(&self) -> bool { From c36d74df068bfe3067c6ea557b37327d4bed2919 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 14:26:36 +0000 Subject: [PATCH 05/64] add basic writing to file --- Cargo.lock | 15 +++++---------- Cargo.toml | 3 +++ crates/cheatcodes/src/evm.rs | 16 ++++++++++++++-- crates/config/src/lib.rs | 4 ++++ crates/forge/tests/cli/config.rs | 1 + 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6e0cc7171596..5880299d63e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3727,8 +3727,7 @@ dependencies = [ [[package]] name = "foundry-compilers" version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b8ffe1d5a00cd78a9461262377270d88b8d6a8a5f51b402996242bccef3994" +source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3765,8 +3764,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdb80803e20447fc8c3f4ec97d47ad5fa37286648bb8224edbbc553ebe1a0f4" +source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3775,8 +3773,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3280cf657d802358856a397cb8465b18a0a6c09b1fa6422842e422a9aa21276d" +source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3799,8 +3796,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ecc61aa540bff773d4441a94e0f158769fcedd61f61d3e91608a76d6bcd7aa" +source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3814,8 +3810,7 @@ dependencies = [ [[package]] name = "foundry-compilers-core" version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a14603a33a217e64cc38977c215b01b37b48a0cae0a739a9f9b3555f16938704" +source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" dependencies = [ "alloy-primitives", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index d1646a070bee..a5d08582a6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -264,3 +264,6 @@ soldeer = "0.2.19" proptest = "1" comfy-table = "7" + +[patch.crates-io] +foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "05cb214982b2bebd359bd72b3fc2de67dd6f9492", default-features = false } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index b7069a4afc0c..c8007c33ad27 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -8,7 +8,7 @@ use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; -use foundry_common::fs::{read_json_file, write_json_file}; +use foundry_common::fs::{create_dir_all, read_json_file, write_json_file}; use foundry_evm_core::{ backend::{DatabaseExt, RevertSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, @@ -507,7 +507,19 @@ impl Cheatcode for stopSnapshotGasCall { .ok_or_else(|| fmt_err!("gas snapshot not found: {name}"))?; record.gas_end = ccx.state.last_call_gas.clone(); - // Write record to disk + let gas_used = record + .gas_end + .as_ref() + .zip(record.gas_start.as_ref()) + .map(|(end, start)| end.gasTotalUsed - start.gasTotalUsed) + .unwrap_or_default(); + + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // Write the snapshot to a file + let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); + write_json_file(&snapshot_path, &gas_used)?; Ok(Default::default()) } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index d9c762581027..09116ae602ff 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` @@ -700,6 +702,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)); @@ -2047,6 +2050,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/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index fcec5f15a84a..7bc369eb593a 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: "test-snapshot".into(), broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, From 124303cf6d1e7f4b118e1f1e25fafdf5c1374b85 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 14:27:29 +0000 Subject: [PATCH 06/64] remove snapshot post dump --- crates/cheatcodes/src/evm.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index c8007c33ad27..f068bd6e7da9 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -521,6 +521,9 @@ impl Cheatcode for stopSnapshotGasCall { let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); write_json_file(&snapshot_path, &gas_used)?; + // Delete the snapshot after writing it + ccx.state.recorded_gas.retain(|record| record.name != *name); + Ok(Default::default()) } } From 267a2440b0b02a524c443849be0a536ea2781d4c Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 6 Aug 2024 14:29:58 +0000 Subject: [PATCH 07/64] simplify, gas_end inline --- crates/cheatcodes/src/evm.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index f068bd6e7da9..4ddce911b9ff 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -68,9 +68,7 @@ pub struct GasRecord { /// The name of the snapshot. pub name: String, /// The total gas used at the start of the snapshot. - pub gas_start: Option, - /// The amount of gas used at the end of the snapshot. - pub gas_end: Option, + pub gas: Option, } impl Cheatcode for addrCall { @@ -486,11 +484,7 @@ impl Cheatcode for lastCallGasCall { impl Cheatcode for startSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - let record = GasRecord { - name: name.clone(), - gas_start: ccx.state.last_call_gas.clone(), - gas_end: None, - }; + let record = GasRecord { name: name.clone(), gas: ccx.state.last_call_gas.clone() }; ccx.state.recorded_gas.push(record); Ok(Default::default()) } @@ -505,12 +499,11 @@ impl Cheatcode for stopSnapshotGasCall { .iter_mut() .find(|record| record.name == *name) .ok_or_else(|| fmt_err!("gas snapshot not found: {name}"))?; - record.gas_end = ccx.state.last_call_gas.clone(); + let gas_end = ccx.state.last_call_gas.clone(); - let gas_used = record - .gas_end + let gas_used = gas_end .as_ref() - .zip(record.gas_start.as_ref()) + .zip(record.gas.as_ref()) .map(|(end, start)| end.gasTotalUsed - start.gasTotalUsed) .unwrap_or_default(); From 3daf78259d04d46f2e86cf11d3e50e0c7bd610fd Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 8 Aug 2024 13:33:18 +0000 Subject: [PATCH 08/64] add file --- testdata/default/cheats/GasSnapshots.t.sol | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 testdata/default/cheats/GasSnapshots.t.sol diff --git a/testdata/default/cheats/GasSnapshots.t.sol b/testdata/default/cheats/GasSnapshots.t.sol new file mode 100644 index 000000000000..e69de29bb2d1 From 4c87e1b53961712a13fc80a778f1bed3c9df2391 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 26 Aug 2024 13:29:15 +0000 Subject: [PATCH 09/64] fix upstream --- Cargo.lock | 10 +++++----- Cargo.toml | 2 +- crates/config/src/lib.rs | 3 ++- crates/verify/src/provider.rs | 2 +- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 738035362544..c96b49f78078 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3692,7 +3692,7 @@ dependencies = [ [[package]] name = "foundry-compilers" version = "0.10.2" -source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" +source = "git+https://github.com/foundry-rs/compilers.git?rev=388db54a233fc826bb65e49a247b24a7bb4e7757#388db54a233fc826bb65e49a247b24a7bb4e7757" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3729,7 +3729,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" version = "0.10.2" -source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" +source = "git+https://github.com/foundry-rs/compilers.git?rev=388db54a233fc826bb65e49a247b24a7bb4e7757#388db54a233fc826bb65e49a247b24a7bb4e7757" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3738,7 +3738,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" version = "0.10.2" -source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" +source = "git+https://github.com/foundry-rs/compilers.git?rev=388db54a233fc826bb65e49a247b24a7bb4e7757#388db54a233fc826bb65e49a247b24a7bb4e7757" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3761,7 +3761,7 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" version = "0.10.2" -source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" +source = "git+https://github.com/foundry-rs/compilers.git?rev=388db54a233fc826bb65e49a247b24a7bb4e7757#388db54a233fc826bb65e49a247b24a7bb4e7757" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3775,7 +3775,7 @@ dependencies = [ [[package]] name = "foundry-compilers-core" version = "0.10.2" -source = "git+https://github.com/foundry-rs/compilers.git?rev=05cb214982b2bebd359bd72b3fc2de67dd6f9492#05cb214982b2bebd359bd72b3fc2de67dd6f9492" +source = "git+https://github.com/foundry-rs/compilers.git?rev=388db54a233fc826bb65e49a247b24a7bb4e7757#388db54a233fc826bb65e49a247b24a7bb4e7757" dependencies = [ "alloy-primitives", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 2436e253a333..a28e3b892b49 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -264,7 +264,7 @@ proptest = "1" comfy-table = "7" [patch.crates-io] -foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "05cb214982b2bebd359bd72b3fc2de67dd6f9492", default-features = false } +foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "388db54a233fc826bb65e49a247b24a7bb4e7757" } alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index aed828f53dab..2a2ec12b29b8 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -1000,7 +1000,7 @@ impl Config { /// Returns configuration for a compiler to use when setting up a [Project]. pub fn compiler(&self) -> Result { - Ok(MultiCompiler { solc: self.solc_compiler()?, vyper: self.vyper_compiler()? }) + Ok(MultiCompiler { solc: Some(self.solc_compiler()?), vyper: self.vyper_compiler()? }) } /// Returns configured [MultiCompilerSettings]. @@ -1350,6 +1350,7 @@ impl Config { "evm.deployedBytecode".to_string(), ]), search_paths: None, + experimental_codegen: None, }) } diff --git a/crates/verify/src/provider.rs b/crates/verify/src/provider.rs index 1506585abf25..6e5b29d5e34c 100644 --- a/crates/verify/src/provider.rs +++ b/crates/verify/src/provider.rs @@ -38,7 +38,7 @@ impl VerificationContext { project.no_artifacts = true; let solc = Solc::find_or_install(&compiler_version)?; - project.compiler.solc = SolcCompiler::Specific(solc); + project.compiler.solc = Some(SolcCompiler::Specific(solc)); Ok(Self { config, project, target_name, target_path, compiler_version }) } From 1b522d69e3e97ff552a2200aa417e13554989926 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 10:43:28 +0000 Subject: [PATCH 10/64] fix Solidity compiler warnings and failing tests --- crates/cheatcodes/assets/cheatcodes.json | 40 ++ crates/cheatcodes/spec/src/vm.rs | 12 +- crates/cheatcodes/src/evm.rs | 30 ++ crates/forge/tests/it/invariant.rs | 2 +- testdata/cheats/Vm.sol | 2 + testdata/default/cheats/ExpectCall.t.sol | 400 +++++++++++++++--- testdata/default/cheats/ExpectEmit.t.sol | 126 ++++-- testdata/default/cheats/Fork2.t.sol | 52 ++- testdata/default/cheats/GasSnapshots.t.sol | 0 testdata/default/cheats/LastCallGas.t.sol | 44 +- testdata/default/cheats/Prevrandao.t.sol | 4 +- .../cheats/RecordAccountAccesses.t.sol | 2 +- testdata/default/cheats/StateSnapshots.t.sol | 34 +- testdata/default/cheats/loadAllocs.t.sol | 8 +- testdata/default/fork/Transact.t.sol | 8 +- .../common/InvariantCustomError.t.sol | 8 +- testdata/default/repros/Issue2984.t.sol | 4 +- testdata/default/repros/Issue3055.t.sol | 12 +- testdata/default/repros/Issue3792.t.sol | 4 +- testdata/default/repros/Issue6355.t.sol | 6 +- testdata/default/repros/Issue8383.t.sol | 4 +- 21 files changed, 635 insertions(+), 167 deletions(-) delete mode 100644 testdata/default/cheats/GasSnapshots.t.sol diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 15ef7f1d1f13..3f1d2781fcb2 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8066,6 +8066,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "snapshotData", + "description": "Snapshot capture arbitrary abi-encoded data .", + "declaration": "function snapshotData(string calldata name, bytes calldata data) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "snapshotData(string,bytes)", + "selector": "0xeb257170", + "selectorBytes": [ + 235, + 37, + 113, + 112 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "snapshotState", @@ -8086,6 +8106,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "snapshotValue", + "description": "Snapshot capture an arbitrary numerical value.", + "declaration": "function snapshotValue(string calldata name, uint256 value) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,uint256)", + "selector": "0x51db805a", + "selectorBytes": [ + 81, + 219, + 128, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "split", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index ef10a732b2af..4d0518262935 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. @@ -499,6 +497,16 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + // ----- Arbitrary Snapshots ----- + + /// Snapshot capture arbitrary abi-encoded data . + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotData(string calldata name, bytes calldata data) external returns (bool success); + + /// Snapshot capture an arbitrary numerical value. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata name, uint256 value) external returns (bool success); + // -------- Gas Snapshots -------- /// Start a snapshot capture of the current gas usage. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index e9cbfee0d310..0ecc76a4d687 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -487,6 +487,36 @@ impl Cheatcode for readCallersCall { } } +impl Cheatcode for snapshotDataCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, data } = self; + + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // Write the snapshot to a file + let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); + write_json_file(&snapshot_path, &data)?; + + Ok(Default::default()) + } +} + +impl Cheatcode for snapshotValueCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, value } = self; + + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // Write the snapshot to a file + let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); + write_json_file(&snapshot_path, &value)?; + + Ok(Default::default()) + } +} + impl Cheatcode for startSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; diff --git a/crates/forge/tests/it/invariant.rs b/crates/forge/tests/it/invariant.rs index bd9286713b1d..e9d07206ee07 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/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index c6998c40d1b5..d918274e0290 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -399,7 +399,9 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; + function snapshotData(string calldata name, bytes calldata data) external returns (bool success); function snapshotState() external returns (uint256 snapshotId); + function snapshotValue(string calldata name, uint256 value) external returns (bool success); function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; diff --git a/testdata/default/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol index 7d757101ad3b..bfde512fdf0c 100644 --- a/testdata/default/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -55,70 +55,115 @@ 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()") + ); } } contract ExpectCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { + function exposed_callTargetNTimes( + Contract target, + uint256 a, + uint256 b, + uint256 times + ) public { for (uint256 i = 0; i < times; i++) { target.add(a, b); } } - function exposed_expectCallWithValue(Contract target, uint256 value, uint256 amount) public { + function exposed_expectCallWithValue( + Contract target, + uint256 value, + uint256 amount + ) public { target.pay{value: value}(amount); } function testExpectCallWithData() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); this.exposed_callTargetNTimes(target, 1, 2, 1); } function testExpectMultipleCallsWithData() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); // Even though we expect one call, we're using additive behavior, so getting more than one call is okay. this.exposed_callTargetNTimes(target, 1, 2, 2); } function testExpectMultipleCallsWithDataAdditive() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); this.exposed_callTargetNTimes(target, 1, 2, 2); } function testExpectMultipleCallsWithDataAdditiveLowerBound() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); this.exposed_callTargetNTimes(target, 1, 2, 3); } function testFailExpectMultipleCallsWithDataAdditive() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); // Not enough calls to satisfy the additive expectCall, which expects 3 calls. this.exposed_callTargetNTimes(target, 1, 2, 2); } function testFailExpectCallWithData() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 1 + ); this.exposed_callTargetNTimes(target, 3, 3, 1); } function testExpectInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.numberB.selector) + ); this.exposed_expectInnerCall(target); } @@ -130,7 +175,10 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.numberB.selector) + ); this.exposed_failExpectInnerCall(target); } @@ -146,8 +194,14 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(target), abi.encodeWithSelector(target.forwardPay.selector)); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.pay.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.forwardPay.selector) + ); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.pay.selector) + ); this.exposed_forwardPay(target); } @@ -157,55 +211,90 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(target), abi.encodeWithSelector(target.sumInPlace.selector)); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.add.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.sumInPlace.selector) + ); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.add.selector) + ); this.exposed_expectCallMultipleFunctionsFlattened(target, inner); } - function exposed_expectCallMultipleFunctionsFlattened(NestedContract target, Contract inner) public { + function exposed_expectCallMultipleFunctionsFlattened( + NestedContract target, + Contract inner + ) public { target.sumInPlace(1, 1); inner.add(1, 1); } function testExpectSelectorCall() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector) + ); this.exposed_callTargetNTimes(target, 5, 5, 1); } function testFailExpectSelectorCall() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector) + ); } function testFailExpectCallWithMoreParameters() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 3, 3, 3)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 3, 3, 3) + ); target.add(3, 3); this.exposed_callTargetNTimes(target, 3, 3, 1); } function testExpectCallWithValue() public { Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); + vm.expectCall( + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2) + ); this.exposed_expectCallWithValue(target, 1, 2); } function testFailExpectCallValue() public { Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); + vm.expectCall( + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2) + ); } function testExpectCallWithValueWithoutParameters() public { Contract target = new Contract(); - vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector)); + vm.expectCall( + address(target), + 3, + abi.encodeWithSelector(target.pay.selector) + ); this.exposed_expectCallWithValue(target, 3, 100); } function testExpectCallWithValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); + vm.expectCall( + address(inner), + 1, + 50_000, + abi.encodeWithSelector(inner.pay.selector, 1) + ); this.exposed_forwardPay(target); } @@ -216,7 +305,12 @@ contract ExpectCallTest is DSTest { function testExpectCallWithNoValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + vm.expectCall( + address(inner), + 0, + 50_000, + abi.encodeWithSelector(inner.add.selector, 1, 1) + ); this.exposed_addHardGasLimit(target); } @@ -227,28 +321,48 @@ contract ExpectCallTest is DSTest { function testFailExpectCallWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + vm.expectCall( + address(inner), + 0, + 25_000, + abi.encodeWithSelector(inner.add.selector, 1, 1) + ); this.exposed_addHardGasLimit(target); } function testExpectCallWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); + vm.expectCallMinGas( + address(inner), + 1, + 50_000, + abi.encodeWithSelector(inner.pay.selector, 1) + ); this.exposed_forwardPay(target); } function testExpectCallWithNoValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); + vm.expectCallMinGas( + address(inner), + 0, + 25_000, + abi.encodeWithSelector(inner.add.selector, 1, 1) + ); this.exposed_addHardGasLimit(target); } function testFailExpectCallWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1)); + vm.expectCallMinGas( + address(inner), + 0, + 50_001, + abi.encodeWithSelector(inner.add.selector, 1, 1) + ); this.exposed_addHardGasLimit(target); } @@ -256,7 +370,10 @@ contract ExpectCallTest is DSTest { function testFailExpectCallWithRevertDisallowed() public { Contract target = new Contract(); vm.expectRevert(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector) + ); this.exposed_callTargetNTimes(target, 5, 5, 1); } @@ -274,7 +391,11 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithData() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(Contract.add.selector, 1, 2), 3); + vm.expectCall( + address(target), + abi.encodeWithSelector(Contract.add.selector, 1, 2), + 3 + ); this.exposed_expectCallCountWithData(target); } @@ -286,20 +407,32 @@ contract ExpectCallCountTest is DSTest { function testExpectZeroCallCountAssert() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 0); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 0 + ); target.add(3, 3); } function testFailExpectCallCountWithWrongCount() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ); target.add(1, 2); } function testExpectCountInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.numberB.selector), + 1 + ); target.sum(); } @@ -307,7 +440,11 @@ contract ExpectCallCountTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.numberB.selector), + 1 + ); // this function does not call inner target.hello(); @@ -316,44 +453,77 @@ contract ExpectCallCountTest is DSTest { function testExpectCountInnerAndOuterCalls() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 2); + vm.expectCall( + address(inner), + abi.encodeWithSelector(inner.numberB.selector), + 2 + ); this.exposed_expectCountInnerAndOuterCalls(inner, target); } - function exposed_expectCountInnerAndOuterCalls(Contract inner, NestedContract target) public { + function exposed_expectCountInnerAndOuterCalls( + Contract inner, + NestedContract target + ) public { inner.numberB(); target.sum(); } - function exposed_pay(Contract target, uint256 value, uint256 amount) public payable { + function exposed_pay( + Contract target, + uint256 value, + uint256 amount + ) public payable { target.pay{value: value}(amount); } function testExpectCallCountWithValue() public { Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); + vm.expectCall( + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2), + 1 + ); this.exposed_pay{value: 1}(target, 1, 2); } function testExpectZeroCallCountValue() public { Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 0); + vm.expectCall( + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2), + 0 + ); this.exposed_pay{value: 2}(target, 2, 2); } function testFailExpectCallCountValue() public { Contract target = new Contract(); - vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); + vm.expectCall( + address(target), + 1, + abi.encodeWithSelector(target.pay.selector, 2), + 1 + ); this.exposed_pay{value: 2}(target, 2, 2); } function testExpectCallCountWithValueWithoutParameters() public { Contract target = new Contract(); - vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector), 3); + vm.expectCall( + address(target), + 3, + abi.encodeWithSelector(target.pay.selector), + 3 + ); this.exposed_expectCallCountWithValueWithoutParameters(target); } - function exposed_expectCallCountWithValueWithoutParameters(Contract target) public { + function exposed_expectCallCountWithValueWithoutParameters( + Contract target + ) public { target.pay{value: 3}(100); target.pay{value: 3}(100); target.pay{value: 3}(100); @@ -362,16 +532,27 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 2); + vm.expectCall( + address(inner), + 1, + 50_000, + abi.encodeWithSelector(inner.pay.selector, 1), + 2 + ); this.exposed_expectCallCountWithValueAndGas(target); } - function exposed_expectCallCountWithValueAndGas(NestedContract target) public { + function exposed_expectCallCountWithValueAndGas( + NestedContract target + ) public { target.forwardPay{value: 1}(); target.forwardPay{value: 1}(); } - function exposed_addHardGasLimit(NestedContract target, uint256 times) public { + function exposed_addHardGasLimit( + NestedContract target, + uint256 times + ) public { for (uint256 i = 0; i < times; i++) { target.addHardGasLimit(); } @@ -380,28 +561,52 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithNoValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); + vm.expectCall( + address(inner), + 0, + 50_000, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 1 + ); this.exposed_addHardGasLimit(target, 1); } function testExpectZeroCallCountWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); + vm.expectCall( + address(inner), + 0, + 25_000, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 0 + ); this.exposed_addHardGasLimit(target, 1); } function testFailExpectCallCountWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); + vm.expectCall( + address(inner), + 0, + 25_000, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 2 + ); this.exposed_addHardGasLimit(target, 2); } function testExpectCallCountWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 1); + vm.expectCallMinGas( + address(inner), + 1, + 50_000, + abi.encodeWithSelector(inner.pay.selector, 1), + 1 + ); this.exposed_forwardPay(target); } @@ -412,21 +617,39 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithNoValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); + vm.expectCallMinGas( + address(inner), + 0, + 25_000, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 2 + ); this.exposed_addHardGasLimit(target, 2); } function testExpectCallZeroCountWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); + vm.expectCallMinGas( + address(inner), + 0, + 50_001, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 0 + ); this.exposed_addHardGasLimit(target, 1); } function testFailExpectCallCountWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); + vm.expectCallMinGas( + address(inner), + 0, + 50_001, + abi.encodeWithSelector(inner.add.selector, 1, 1), + 1 + ); this.exposed_addHardGasLimit(target, 1); } } @@ -434,7 +657,12 @@ contract ExpectCallCountTest is DSTest { contract ExpectCallMixedTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { + function exposed_callTargetNTimes( + Contract target, + uint256 a, + uint256 b, + uint256 times + ) public { for (uint256 i = 0; i < times; i++) { target.add(1, 2); } @@ -442,36 +670,65 @@ contract ExpectCallMixedTest is DSTest { function testFailOverrideNoCountWithCount() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); // You should not be able to overwrite a expectCall that had no count with some count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ); this.exposed_callTargetNTimes(target, 1, 2, 2); } function testFailOverrideCountWithCount() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ); // You should not be able to overwrite a expectCall that had a count with some count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 1 + ); target.add(1, 2); target.add(1, 2); } function testFailOverrideCountWithNoCount() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ); // You should not be able to overwrite a expectCall that had a count with no count. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); target.add(1, 2); target.add(1, 2); } function testExpectMatchPartialAndFull() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector), + 2 + ); // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2) + ); this.exposed_expectMatchPartialAndFull(target); } @@ -482,10 +739,17 @@ contract ExpectCallMixedTest is DSTest { function testExpectMatchPartialAndFullFlipped() public { Contract target = new Contract(); - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector) + ); // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. - vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); + vm.expectCall( + address(target), + abi.encodeWithSelector(target.add.selector, 1, 2), + 2 + ); this.exposed_expectMatchPartialAndFullFlipped(target); } diff --git a/testdata/default/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol index cad184355443..8baa41083c4a 100644 --- a/testdata/default/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -7,7 +7,12 @@ import "cheats/Vm.sol"; contract Emitter { uint256 public thing; - event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + event Something( + uint256 indexed topic1, + uint256 indexed topic2, + uint256 indexed topic3, + uint256 data + ); event A(uint256 indexed topic1); event B(uint256 indexed topic1); event C(uint256 indexed topic1); @@ -24,7 +29,12 @@ contract Emitter { event SomethingNonIndexed(uint256 data); - function emitEvent(uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { + function emitEvent( + uint256 topic1, + uint256 topic2, + uint256 topic3, + uint256 data + ) public { emit Something(topic1, topic2, topic3, data); } @@ -50,7 +60,13 @@ contract Emitter { emit Something(1, 2, 3, 4); } - function emitNested(Emitter inner, uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { + function emitNested( + Emitter inner, + uint256 topic1, + uint256 topic2, + uint256 topic3, + uint256 data + ) public { inner.emitEvent(topic1, topic2, topic3, data); } @@ -102,8 +118,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 {} @@ -113,7 +129,12 @@ contract ExpectEmitTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); Emitter emitter; - event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); + event Something( + uint256 indexed topic1, + uint256 indexed topic2, + uint256 indexed topic3, + uint256 data + ); event SomethingElse(uint256 indexed topic1); @@ -154,15 +175,26 @@ contract ExpectEmitTest is DSTest { uint128 topic3, uint128 data ) public { - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) : uint256(topic1) + 1; - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) : uint256(topic2) + 1; - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) : uint256(topic3) + 1; + uint256 transformedTopic1 = checkTopic1 + ? uint256(topic1) + : uint256(topic1) + 1; + uint256 transformedTopic2 = checkTopic2 + ? uint256(topic2) + : uint256(topic2) + 1; + uint256 transformedTopic3 = checkTopic3 + ? uint256(topic3) + : uint256(topic3) + 1; uint256 transformedData = checkData ? uint256(data) : uint256(data) + 1; vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + emitter.emitEvent( + transformedTopic1, + transformedTopic2, + transformedTopic3, + transformedData + ); } /// The topics that are checked are altered to be incorrect @@ -179,15 +211,26 @@ contract ExpectEmitTest is DSTest { ) public { vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); + uint256 transformedTopic1 = checkTopic1 + ? uint256(topic1) + 1 + : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 + ? uint256(topic2) + 1 + : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 + ? uint256(topic3) + 1 + : uint256(topic3); uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + emitter.emitEvent( + transformedTopic1, + transformedTopic2, + transformedTopic3, + transformedData + ); } /// The topics that are checked are altered to be incorrect @@ -204,15 +247,27 @@ contract ExpectEmitTest is DSTest { ) public { Emitter inner = new Emitter(); - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) : uint256(topic1) + 1; - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) : uint256(topic2) + 1; - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) : uint256(topic3) + 1; + uint256 transformedTopic1 = checkTopic1 + ? uint256(topic1) + : uint256(topic1) + 1; + uint256 transformedTopic2 = checkTopic2 + ? uint256(topic2) + : uint256(topic2) + 1; + uint256 transformedTopic3 = checkTopic3 + ? uint256(topic3) + : uint256(topic3) + 1; uint256 transformedData = checkData ? uint256(data) : uint256(data) + 1; vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + emitter.emitNested( + inner, + transformedTopic1, + transformedTopic2, + transformedTopic3, + transformedData + ); } /// The topics that are checked are altered to be incorrect @@ -230,15 +285,27 @@ contract ExpectEmitTest is DSTest { vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); Emitter inner = new Emitter(); - uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); + uint256 transformedTopic1 = checkTopic1 + ? uint256(topic1) + 1 + : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 + ? uint256(topic2) + 1 + : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 + ? uint256(topic3) + 1 + : uint256(topic3); uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); + emitter.emitNested( + inner, + transformedTopic1, + transformedTopic2, + transformedTopic3, + transformedData + ); } function testExpectEmitMultiple() public { @@ -248,7 +315,10 @@ contract ExpectEmitTest is DSTest { emit Something(5, 6, 7, 8); emitter.emitMultiple( - [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] + [uint256(1), uint256(5)], + [uint256(2), uint256(6)], + [uint256(3), uint256(7)], + [uint256(4), uint256(8)] ); } @@ -268,7 +338,10 @@ contract ExpectEmitTest is DSTest { emit Something(5, 6, 7, 8); emitter.emitMultiple( - [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] + [uint256(1), uint256(5)], + [uint256(2), uint256(6)], + [uint256(3), uint256(7)], + [uint256(4), uint256(8)] ); } @@ -379,7 +452,10 @@ contract ExpectEmitTest is DSTest { emit Something(1, 2, 3, 4); emitter.emitMultiple( - [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] + [uint256(1), uint256(5)], + [uint256(2), uint256(6)], + [uint256(3), uint256(7)], + [uint256(4), uint256(8)] ); } diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 3e8f68a6cd6d..1c33676c2cb6 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); } @@ -23,7 +23,10 @@ contract MyContract { } function ensureBlockHash() public view { - require(blockhash(block.number - 1) == blockHash, "Block Hash does not match"); + require( + blockhash(block.number - 1) == blockHash, + "Block Hash does not match" + ); } } @@ -98,14 +101,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 +122,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 +159,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 +167,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 { @@ -188,11 +191,19 @@ contract ForkTest is DSTest { string memory path = "fixtures/Rpc/eth_getLogs.json"; string memory file = vm.readFile(path); bytes memory parsed = vm.parseJson(file); - EthGetLogsJsonParseable[] memory fixtureLogs = abi.decode(parsed, (EthGetLogsJsonParseable[])); + EthGetLogsJsonParseable[] memory fixtureLogs = abi.decode( + parsed, + (EthGetLogsJsonParseable[]) + ); bytes32[] memory topics = new bytes32[](1); topics[0] = withdrawalTopic; - Vm.EthGetLogs[] memory logs = vm.eth_getLogs(blockNumber, blockNumber, weth, topics); + Vm.EthGetLogs[] memory logs = vm.eth_getLogs( + blockNumber, + blockNumber, + weth, + topics + ); assertEq(logs.length, 3); for (uint256 i = 0; i < logs.length; i++) { @@ -204,9 +215,24 @@ contract ForkTest is DSTest { if (i == 1) i_str = "1"; if (i == 2) i_str = "2"; - assertEq(log.blockNumber, vm.parseJsonUint(file, string.concat("[", i_str, "].blockNumber"))); - assertEq(log.logIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].logIndex"))); - assertEq(log.transactionIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].transactionIndex"))); + assertEq( + log.blockNumber, + vm.parseJsonUint( + file, + string.concat("[", i_str, "].blockNumber") + ) + ); + assertEq( + log.logIndex, + vm.parseJsonUint(file, string.concat("[", i_str, "].logIndex")) + ); + assertEq( + log.transactionIndex, + vm.parseJsonUint( + file, + string.concat("[", i_str, "].transactionIndex") + ) + ); assertEq(log.blockHash, fixtureLogs[i].blockHash); assertEq(log.removed, fixtureLogs[i].removed); diff --git a/testdata/default/cheats/GasSnapshots.t.sol b/testdata/default/cheats/GasSnapshots.t.sol deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/testdata/default/cheats/LastCallGas.t.sol b/testdata/default/cheats/LastCallGas.t.sol index 0f5b65e350e1..810d935eebc6 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; } @@ -50,7 +49,7 @@ abstract contract LastCallGasFixture is DSTest { } function _performCall() internal returns (bool success) { - (success,) = address(target).call(""); + (success, ) = address(target).call(""); } function _performRefund() internal { @@ -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,31 @@ 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 +102,30 @@ 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..b20c245ad649 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/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 7fa669c50316..ddc5df151971 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -20,61 +20,61 @@ contract StateSnapshotTest is DSTest { } function testSnapshotState() public { - uint256 snapshot = vm.snapshotState(); + uint256 snapshotId = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; assertEq(store.slot0, 300); assertEq(store.slot1, 400); - vm.revertToState(snapshot); + 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 snapshot = vm.snapshotState(); + uint256 snapshotId = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; assertEq(store.slot0, 300); assertEq(store.slot1, 400); - vm.revertToStateAndDelete(snapshot); + 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(snapshot)); + assert(!vm.revertToState(snapshotId)); } function testSnapshotStateDelete() public { - uint256 snapshot = vm.snapshotState(); + uint256 snapshotId = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; - vm.deleteSnapshot(snapshot); + vm.deleteStateSnapshot(snapshotId); // nothing to revert to anymore - assert(!vm.revertToState(snapshot)); + assert(!vm.revertToState(snapshotId)); } function testSnapshotStateDeleteAll() public { - uint256 snapshot = vm.snapshotState(); + uint256 snapshotId = vm.snapshotState(); store.slot0 = 300; store.slot1 = 400; - vm.deleteSnapshots(); + vm.deleteStateSnapshots(); // nothing to revert to anymore - assert(!vm.revertToState(snapshot)); + assert(!vm.revertToState(snapshotId)); } // function testSnapshotStatesMany() public { - uint256 preState; + uint256 snapshotId; for (uint256 c = 0; c < 10; c++) { for (uint256 cc = 0; cc < 10; cc++) { - preState = vm.snapshotState(); - vm.revertToStateAndDelete(preState); - assert(!vm.revertToState(preState)); + snapshotId = vm.snapshotState(); + vm.revertToStateAndDelete(snapshotId); + assert(!vm.revertToState(snapshotId)); } } } @@ -85,7 +85,7 @@ contract StateSnapshotTest is DSTest { uint256 time = block.timestamp; uint256 prevrandao = block.prevrandao; - uint256 snapshot = vm.snapshotState(); + uint256 snapshotId = vm.snapshotState(); vm.warp(1337); assertEq(block.timestamp, 1337); @@ -96,7 +96,7 @@ contract StateSnapshotTest is DSTest { vm.prevrandao(uint256(123)); assertEq(block.prevrandao, 123); - assert(vm.revertToState(snapshot)); + assert(vm.revertToState(snapshotId)); assertEq( block.number, 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]; From c788ddd5efe97367a2b0f0f452716eaf301eb6f3 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 10:50:50 +0000 Subject: [PATCH 11/64] remove snapshotData --- crates/cheatcodes/assets/cheatcodes.json | 22 +--------------------- crates/cheatcodes/spec/src/vm.rs | 4 ---- crates/cheatcodes/src/evm.rs | 15 --------------- testdata/cheats/Vm.sol | 1 - 4 files changed, 1 insertion(+), 41 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 3f1d2781fcb2..cd64957c7874 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -475,7 +475,7 @@ { "name": "gasRefunded", "ty": "int64", - "description": "DEPRECATED: The amount of gas used for memory expansion. Ref: \n The amount of gas refunded." + "description": "The amount of gas refunded." }, { "name": "gasRemaining", @@ -8066,26 +8066,6 @@ "status": "stable", "safety": "safe" }, - { - "func": { - "id": "snapshotData", - "description": "Snapshot capture arbitrary abi-encoded data .", - "declaration": "function snapshotData(string calldata name, bytes calldata data) external returns (bool success);", - "visibility": "external", - "mutability": "", - "signature": "snapshotData(string,bytes)", - "selector": "0xeb257170", - "selectorBytes": [ - 235, - 37, - 113, - 112 - ] - }, - "group": "evm", - "status": "stable", - "safety": "unsafe" - }, { "func": { "id": "snapshotState", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 4d0518262935..40aa304d5bc0 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,10 +499,6 @@ interface Vm { // ----- Arbitrary Snapshots ----- - /// Snapshot capture arbitrary abi-encoded data . - #[cheatcode(group = Evm, safety = Unsafe)] - function snapshotData(string calldata name, bytes calldata data) external returns (bool success); - /// Snapshot capture an arbitrary numerical value. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotValue(string calldata name, uint256 value) external returns (bool success); diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 0ecc76a4d687..24fddf01b612 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -487,21 +487,6 @@ impl Cheatcode for readCallersCall { } } -impl Cheatcode for snapshotDataCall { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { name, data } = self; - - // Create the snapshot if it doesn't exist - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - // Write the snapshot to a file - let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); - write_json_file(&snapshot_path, &data)?; - - Ok(Default::default()) - } -} - impl Cheatcode for snapshotValueCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index d918274e0290..273bf2df86ba 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -399,7 +399,6 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; - function snapshotData(string calldata name, bytes calldata data) external returns (bool success); function snapshotState() external returns (uint256 snapshotId); function snapshotValue(string calldata name, uint256 value) external returns (bool success); function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); From 86c5aab62a371d4a0efeb3d63890dd22fb095196 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 13:07:19 +0000 Subject: [PATCH 12/64] add basic tests --- testdata/.gitignore | 1 + testdata/cheats/Vm.sol | 4 +-- testdata/default/cheats/GasSnapshot.t.sol | 43 +++++++++++++++++++++++ testdata/foundry.toml | 2 +- 4 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 testdata/default/cheats/GasSnapshot.t.sol 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 273bf2df86ba..37547a2e43c2 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -408,14 +408,14 @@ interface Vm { function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; - function startSnapshotGas(string calldata name) external returns (bool success); + function startSnapshotGas(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(string calldata name) external returns (Gas memory gas); + function stopSnapshotGas(string calldata name) external returns (bool success, 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/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol new file mode 100644 index 000000000000..d49e9e1e5a40 --- /dev/null +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Flare { + function run(uint256 n) public { + for (uint256 i = 0; i < n; i++) { + keccak256(abi.encodePacked(i)); + } + } +} + +contract GasSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSnapshotValue() public { + uint256 a = 123; + + bool success = vm.snapshotValue("testSnapshotValue", a); + assertTrue(success); + + string memory value = vm.readFile("snapshots/testSnapshotValue.json"); + assertEq(value, '"123"'); + } + + function testSnapshotGasSection() public { + Flare a = new Flare(); + + a.run(100); + + vm.startSnapshotGas("testSnapshotGasSection"); + + a.run(1000); + a.run(1000); + + (bool success, uint256 gasUsed) = vm.stopSnapshotGas( + "testSnapshotGasSection" + ); + assertTrue(success); + } +} 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 From 610f25565fd50e1fa0d9f6162113e3ce3f0b4bc5 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 13:07:41 +0000 Subject: [PATCH 13/64] add correct gas capturing --- crates/cheatcodes/assets/cheatcodes.json | 10 ++-- crates/cheatcodes/spec/src/vm.rs | 12 +++-- crates/cheatcodes/src/evm.rs | 63 ++++++++++++------------ crates/cheatcodes/src/inspector.rs | 9 +++- 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index cd64957c7874..7278e24a5b88 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8089,7 +8089,7 @@ { "func": { "id": "snapshotValue", - "description": "Snapshot capture an arbitrary numerical value.", + "description": "Snapshot capture an arbitrary numerical value by name.", "declaration": "function snapshotValue(string calldata name, uint256 value) external returns (bool success);", "visibility": "external", "mutability": "", @@ -8249,8 +8249,8 @@ { "func": { "id": "startSnapshotGas", - "description": "Start a snapshot capture of the current gas usage.", - "declaration": "function startSnapshotGas(string calldata name) external returns (bool success);", + "description": "Start a snapshot capture of the current gas usage by name.", + "declaration": "function startSnapshotGas(string calldata name) external;", "visibility": "external", "mutability": "", "signature": "startSnapshotGas(string)", @@ -8389,8 +8389,8 @@ { "func": { "id": "stopSnapshotGas", - "description": "Stop the snapshot capture of the current gas usage, capturing the gas used since the start.", - "declaration": "function stopSnapshotGas(string calldata name) external returns (Gas memory gas);", + "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas(string)", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 40aa304d5bc0..f1aeed56401a 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,19 +499,21 @@ interface Vm { // ----- Arbitrary Snapshots ----- - /// Snapshot capture an arbitrary numerical value. + // TODO: add group support + + /// Snapshot capture an arbitrary numerical value by name. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotValue(string calldata name, uint256 value) external returns (bool success); // -------- Gas Snapshots -------- - /// Start a snapshot capture of the current gas usage. + /// Start a snapshot capture of the current gas usage by name. #[cheatcode(group = Evm, safety = Unsafe)] - function startSnapshotGas(string calldata name) external returns (bool success); + function startSnapshotGas(string calldata name) external; - /// Stop the snapshot capture of the current gas usage, capturing the gas used since the start. + /// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] - function stopSnapshotGas(string calldata name) external returns (Gas memory gas); + function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed); // -------- State Snapshots -------- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 24fddf01b612..861b284eba14 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -65,10 +65,10 @@ pub struct DealRecord { /// Records the `snapshotGas` cheatcodes. #[derive(Clone, Debug)] pub struct GasRecord { - /// The name of the snapshot. + /// The name of the gas snapshot. pub name: String, - /// The total gas used at the start of the snapshot. - pub gas: Option, + /// The total gas used in the gas snapshot. + pub gas_used: u64, } impl Cheatcode for addrCall { @@ -495,19 +495,24 @@ impl Cheatcode for snapshotValueCall { create_dir_all(ccx.state.config.paths.snapshots.clone())?; // Write the snapshot to a file - let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); - write_json_file(&snapshot_path, &value)?; + let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{}.{}", name, "json")); + let result = write_json_file(&snapshot_path, &value.to_string()).is_ok(); - Ok(Default::default()) + Ok(result.abi_encode()) } } impl Cheatcode for startSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - let record = - GasRecord { name: name.clone(), gas: ccx.state.gas_metering.last_call_gas.clone() }; - ccx.state.gas_metering.recorded_gas.push(record); + + if ccx.state.gas_metering.gas_records.iter().any(|record| record.name == *name) { + bail!("gas snapshot already exists: {name}"); + } + + // Initialize the gas record, starting at 0. + ccx.state.gas_metering.gas_records.push(GasRecord { name: name.clone(), gas_used: 0 }); + Ok(Default::default()) } } @@ -515,32 +520,28 @@ impl Cheatcode for startSnapshotGasCall { impl Cheatcode for stopSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - let record = ccx - .state - .gas_metering - .recorded_gas - .iter_mut() - .find(|record| record.name == *name) - .ok_or_else(|| fmt_err!("gas snapshot not found: {name}"))?; - let gas_end = ccx.state.gas_metering.last_call_gas.clone(); - - let gas_used = gas_end - .as_ref() - .zip(record.gas.as_ref()) - .map(|(end, start)| end.gasTotalUsed - start.gasTotalUsed) - .unwrap_or_default(); - // Create the snapshot if it doesn't exist - create_dir_all(ccx.state.config.paths.snapshots.clone())?; + if let Some(record) = + ccx.state.gas_metering.gas_records.iter_mut().find(|record| record.name == *name) + { + // Get the gas used since the snapshot was started. + let gas_used = record.gas_used; - // Write the snapshot to a file - let snapshot_path = ccx.state.config.paths.snapshots.join(name).join(".json"); - write_json_file(&snapshot_path, &gas_used)?; + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; - // Delete the snapshot after writing it - ccx.state.gas_metering.recorded_gas.retain(|record| record.name != *name); + // Write the snapshot to a file + let snapshot_path = + ccx.state.config.paths.snapshots.join(format!("{}.{}", name, "json")); + let result = write_json_file(&snapshot_path, &gas_used.to_string()).is_ok(); - Ok(Default::default()) + // Delete the snapshot after writing it + ccx.state.gas_metering.gas_records.retain(|record| record.name != *name); + + Ok((result, gas_used).abi_encode_params()) + } else { + bail!("no gas snapshot was started with the name: {name}"); + } } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index ebff2759092c..03de34bf5764 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -231,8 +231,8 @@ pub struct GasMetering { /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option, - /// Recorded gas - pub recorded_gas: Vec, + /// Gas records for the active snapshots. + pub gas_records: Vec, } impl GasMetering { @@ -1172,6 +1172,11 @@ impl Inspector for Cheatcodes { 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| { + 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 { From 2dff96485bdc7433058cf1683488144a282804ff Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 13:12:48 +0000 Subject: [PATCH 14/64] basic tests --- testdata/default/cheats/GasSnapshot.t.sol | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index d49e9e1e5a40..b91cbb395d00 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -5,9 +5,11 @@ import "ds-test/test.sol"; import "cheats/Vm.sol"; contract Flare { + bytes32[] public data; + function run(uint256 n) public { for (uint256 i = 0; i < n; i++) { - keccak256(abi.encodePacked(i)); + data.push(keccak256(abi.encodePacked(i))); } } } @@ -28,16 +30,22 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSection() public { Flare a = new Flare(); - a.run(100); + a.run(64); vm.startSnapshotGas("testSnapshotGasSection"); - a.run(1000); - a.run(1000); + a.run(256); // 5_821_576 gas + a.run(512); // 11_617_936 gas (bool success, uint256 gasUsed) = vm.stopSnapshotGas( "testSnapshotGasSection" ); assertTrue(success); + assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + + string memory value = vm.readFile( + "snapshots/testSnapshotGasSection.json" + ); + assertEq(value, '"17439512"'); } } From 7eb00971c32f288e154e06353be80767dace2657 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 13:51:11 +0000 Subject: [PATCH 15/64] add basic group support --- crates/cheatcodes/assets/cheatcodes.json | 22 ++++++++++++- crates/cheatcodes/spec/src/vm.rs | 14 ++++++-- crates/cheatcodes/src/evm.rs | 28 ++++++++++++++-- testdata/cheats/Vm.sol | 1 + testdata/default/cheats/GasSnapshot.t.sol | 40 +++++++++++++++++++---- 5 files changed, 93 insertions(+), 12 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 7278e24a5b88..9c2dc912275b 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8088,7 +8088,7 @@ }, { "func": { - "id": "snapshotValue", + "id": "snapshotValue_0", "description": "Snapshot capture an arbitrary numerical value by name.", "declaration": "function snapshotValue(string calldata name, uint256 value) external returns (bool success);", "visibility": "external", @@ -8106,6 +8106,26 @@ "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 returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,string,uint256)", + "selector": "0x6d2b27d8", + "selectorBytes": [ + 109, + 43, + 39, + 216 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "split", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index f1aeed56401a..5847bbdd1390 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,22 +499,32 @@ interface Vm { // ----- Arbitrary Snapshots ----- - // TODO: add group support - /// Snapshot capture an arbitrary numerical value by name. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotValue(string calldata name, uint256 value) external returns (bool success); + /// 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 returns (bool success); + // -------- Gas Snapshots -------- /// Start a snapshot capture of the current gas usage by 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 usage by name, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata name) external returns (bool success, 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 (bool success, uint256 gasUsed); + // -------- State Snapshots -------- /// Snapshot the current state of the evm. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 861b284eba14..ba586c23ac17 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -487,14 +487,15 @@ impl Cheatcode for readCallersCall { } } -impl Cheatcode for snapshotValueCall { +impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; // Create the snapshot if it doesn't exist create_dir_all(ccx.state.config.paths.snapshots.clone())?; - // Write the snapshot to a file + // Write the snapshot to a file. + // Note that we always overwrite the snapshot if it already exists. let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{}.{}", name, "json")); let result = write_json_file(&snapshot_path, &value.to_string()).is_ok(); @@ -502,12 +503,33 @@ impl Cheatcode for snapshotValueCall { } } +impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name, value } = self; + + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // Check if the snapshot already exists, if so, update the value in place. + // Otherwise, create a new snapshot. + let snapshot_path = &ccx.state.config.paths.snapshots.join(format!("{}.{}", group, "json")); + let mut snapshot: BTreeMap = + read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); + snapshot.insert(name.clone(), value.to_string()); + write_json_file(&snapshot_path, &snapshot)?; + + let result = write_json_file(&snapshot_path, &snapshot).is_ok(); + + Ok(result.abi_encode()) + } +} + impl Cheatcode for startSnapshotGasCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; if ccx.state.gas_metering.gas_records.iter().any(|record| record.name == *name) { - bail!("gas snapshot already exists: {name}"); + bail!("gas snapshot already active: {name}"); } // Initialize the gas record, starting at 0. diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 37547a2e43c2..9efc6252f359 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -401,6 +401,7 @@ interface Vm { function sleep(uint256 duration) external; function snapshotState() external returns (uint256 snapshotId); function snapshotValue(string calldata name, uint256 value) external returns (bool success); + function snapshotValue(string calldata group, string calldata name, uint256 value) external returns (bool success); function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index b91cbb395d00..9be517d505ee 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -18,16 +18,39 @@ contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testSnapshotValue() public { + string memory file = "snapshots/testSnapshotValue.json"; + clear(file); + uint256 a = 123; - bool success = vm.snapshotValue("testSnapshotValue", a); - assertTrue(success); + assertTrue(vm.snapshotValue("testSnapshotValue", a)); - string memory value = vm.readFile("snapshots/testSnapshotValue.json"); + string memory value = vm.readFile(file); assertEq(value, '"123"'); } + function testSnapshotGroupValue() public { + string memory file = "snapshots/testSnapshotGroupValue.json"; + clear(file); + + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a)); + assertTrue(vm.snapshotValue("testSnapshotGroupValue", "b", b)); + + assertEq(vm.readFile(file), '{"a":"123","b":"456"}'); + + assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); + + assertEq(vm.readFile(file), '{"a":"123","b":"456","c":"789"}'); + } + function testSnapshotGasSection() public { + string memory file = "snapshots/testSnapshotGasSection.json"; + clear(file); + Flare a = new Flare(); a.run(64); @@ -43,9 +66,14 @@ contract GasSnapshotTest is DSTest { assertTrue(success); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - string memory value = vm.readFile( - "snapshots/testSnapshotGasSection.json" - ); + string memory value = vm.readFile(file); assertEq(value, '"17439512"'); } + + // Remove file if it exists so each test can start with a clean slate. + function clear(string memory name) public { + if (vm.exists(name)) { + vm.removeFile(name); + } + } } From b83d22b5f8dcc98aa4e69e6d36bca2d32808ab74 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 15:18:55 +0000 Subject: [PATCH 16/64] output in formatted json, add basic abstraction for grouping --- crates/cheatcodes/assets/cheatcodes.json | 44 ++++- crates/cheatcodes/spec/src/vm.rs | 12 +- crates/cheatcodes/src/evm.rs | 187 +++++++++++++++------- crates/common/src/fs.rs | 9 ++ testdata/cheats/Vm.sol | 2 + testdata/default/cheats/GasSnapshot.t.sol | 19 ++- 6 files changed, 206 insertions(+), 67 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 9c2dc912275b..1dc742f1aa90 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8268,7 +8268,7 @@ }, { "func": { - "id": "startSnapshotGas", + "id": "startSnapshotGas_0", "description": "Start a snapshot capture of the current gas usage by name.", "declaration": "function startSnapshotGas(string calldata name) external;", "visibility": "external", @@ -8286,6 +8286,26 @@ "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", @@ -8408,7 +8428,7 @@ }, { "func": { - "id": "stopSnapshotGas", + "id": "stopSnapshotGas_0", "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.", "declaration": "function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed);", "visibility": "external", @@ -8426,6 +8446,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "stopSnapshotGas_1", + "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 (bool success, 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 5847bbdd1390..f9c631fce0e9 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -513,17 +513,17 @@ interface Vm { #[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; + /// 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 usage by name, capturing the gas used since the start. #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata name) external returns (bool success, 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 (bool success, 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 (bool success, uint256 gasUsed); // -------- State Snapshots -------- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index ba586c23ac17..3a17ffbc6cef 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -8,7 +8,7 @@ use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; -use foundry_common::fs::{create_dir_all, read_json_file, write_json_file}; +use foundry_common::fs::{create_dir_all, read_json_file, write_json_file, write_pretty_json_file}; use foundry_evm_core::{ backend::{DatabaseExt, RevertSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, @@ -65,6 +65,8 @@ pub struct DealRecord { /// Records the `snapshotGas` cheatcodes. #[derive(Clone, Debug)] pub struct GasRecord { + /// The group name of the gas snapshot. + pub group: Option, /// The name of the gas snapshot. pub name: String, /// The total gas used in the gas snapshot. @@ -490,80 +492,42 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; - - // Create the snapshot if it doesn't exist - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - // Write the snapshot to a file. - // Note that we always overwrite the snapshot if it already exists. - let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{}.{}", name, "json")); - let result = write_json_file(&snapshot_path, &value.to_string()).is_ok(); - - Ok(result.abi_encode()) + update_gas_snapshot(ccx, None, name.to_string(), value.to_string()) } } impl Cheatcode for snapshotValue_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name, value } = self; - - // Create the snapshot if it doesn't exist - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - // Check if the snapshot already exists, if so, update the value in place. - // Otherwise, create a new snapshot. - let snapshot_path = &ccx.state.config.paths.snapshots.join(format!("{}.{}", group, "json")); - let mut snapshot: BTreeMap = - read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(name.clone(), value.to_string()); - write_json_file(&snapshot_path, &snapshot)?; - - let result = write_json_file(&snapshot_path, &snapshot).is_ok(); - - Ok(result.abi_encode()) + update_gas_snapshot(ccx, Some(group), name.to_string(), value.to_string()) } } -impl Cheatcode for startSnapshotGasCall { +impl Cheatcode for startSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; + start_gas_snapshot(ccx, None, name.to_string()) + } +} - if ccx.state.gas_metering.gas_records.iter().any(|record| record.name == *name) { - bail!("gas snapshot already active: {name}"); - } - - // Initialize the gas record, starting at 0. - ccx.state.gas_metering.gas_records.push(GasRecord { name: name.clone(), gas_used: 0 }); - - Ok(Default::default()) +impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + start_gas_snapshot(ccx, Some(group.clone()), name.to_string()) } } -impl Cheatcode for stopSnapshotGasCall { +impl Cheatcode for stopSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; + stop_gas_snapshot(ccx, None, name.to_string()) + } +} - if let Some(record) = - ccx.state.gas_metering.gas_records.iter_mut().find(|record| record.name == *name) - { - // Get the gas used since the snapshot was started. - let gas_used = record.gas_used; - - // Create the snapshot if it doesn't exist - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - // Write the snapshot to a file - let snapshot_path = - ccx.state.config.paths.snapshots.join(format!("{}.{}", name, "json")); - let result = write_json_file(&snapshot_path, &gas_used.to_string()).is_ok(); - - // Delete the snapshot after writing it - ccx.state.gas_metering.gas_records.retain(|record| record.name != *name); - - Ok((result, gas_used).abi_encode_params()) - } else { - bail!("no gas snapshot was started with the name: {name}"); - } +impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + stop_gas_snapshot(ccx, Some(group.clone()), name.to_string()) } } @@ -689,6 +653,115 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } +fn update_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option<&String>, + name: String, + value: String, +) -> Result { + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + if let Some(group) = group { + // Prepare the snapshot. + // Check if the snapshot already exists, if so, update the value in place. + // Otherwise, create a new snapshot. + let snapshot_path = &ccx.state.config.paths.snapshots.join(format!("{}.json", group)); + let mut snapshot: BTreeMap = + read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); + snapshot.insert(name.clone(), value); + + // Write the snapshot to a file, asserting that the write was successful. + let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); + + Ok(result.abi_encode()) + } else { + // Prepare the snapshot. + // Check if the snapshot already exists, if so, update the value in place. + // Otherwise, create a new snapshot. + let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{}.json", name)); + let snapshot = value.to_string(); + + // Write the snapshot to a file, asserting that the write was successful. + let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); + + Ok(result.abi_encode()) + } +} + +fn start_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: String, +) -> Result { + if ccx.state.gas_metering.gas_records.iter().any(|record| match &group { + Some(g) => record.group.as_ref() == Some(g) && record.name == *name, + None => record.group.is_none() && record.name == *name, + }) { + let group_name = group.as_deref().unwrap_or("default"); + bail!("gas snapshot already active: {name} in group: {group_name}"); + } + + // Initialize the gas record, starting at 0. + ccx.state.gas_metering.gas_records.push(GasRecord { + group: group.clone(), + name: name.clone(), + gas_used: 0, + }); + + Ok(Default::default()) +} + +fn stop_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option, + name: String, +) -> Result { + if let Some(record) = ccx + .state + .gas_metering + .gas_records + .iter_mut() + .find(|record| record.group == group && record.name == name) + { + // Get the gas used since the snapshot was started. + let gas_used = record.gas_used; + + // Create the snapshot if it doesn't exist + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // Prepare the snapshot. + let snapshot_path = match &group { + Some(g) => ccx.state.config.paths.snapshots.join(format!("{}.json", g)), + None => ccx.state.config.paths.snapshots.join(format!("{}.json", name)), + }; + + // Depending on whether a group is provided, handle the snapshot differently. + // Check if the snapshot already exists, if so, update the value in place. + // Otherwise, create a new snapshot. + let result = if group.is_some() { + let mut snapshot: BTreeMap = + read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); + snapshot.insert(name.clone(), gas_used.to_string()); + write_pretty_json_file(&snapshot_path, &snapshot).is_ok() + } else { + let snapshot = gas_used.to_string(); + write_pretty_json_file(&snapshot_path, &snapshot).is_ok() + }; + + // Delete the snapshot after writing. + ccx.state + .gas_metering + .gas_records + .retain(|record| !(record.group == group && record.name == name)); + + Ok((result, gas_used).abi_encode_params()) + } else { + let group_name = group.as_deref().unwrap_or("default"); + bail!("no gas snapshot was started with the name: {name} in group: {group_name}"); + } +} + /// Reads the current caller information and returns the current [CallerMode], `msg.sender` and /// `tx.origin`. /// 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/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 9efc6252f359..72a25f85a6d6 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -410,6 +410,7 @@ interface Vm { 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; @@ -417,6 +418,7 @@ interface Vm { function stopMappingRecording() external; function stopPrank() external; function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed); + function stopSnapshotGas(string calldata group, string calldata name) external returns (bool success, 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/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 9be517d505ee..955e80493bc2 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -29,6 +29,18 @@ contract GasSnapshotTest is DSTest { assertEq(value, '"123"'); } + function testSnapshotValueGroup() public { + string memory file = "snapshots/GasSnapshotTest.json"; + clear(file); + + uint256 a = 123; + + assertTrue(vm.snapshotValue("GasSnapshotTest", "testSnapshotValue", a)); + + string memory value = vm.readFile(file); + assertEq(value, '{\n "testSnapshotValue": "123"\n}'); + } + function testSnapshotGroupValue() public { string memory file = "snapshots/testSnapshotGroupValue.json"; clear(file); @@ -40,11 +52,14 @@ contract GasSnapshotTest is DSTest { assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a)); assertTrue(vm.snapshotValue("testSnapshotGroupValue", "b", b)); - assertEq(vm.readFile(file), '{"a":"123","b":"456"}'); + assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456"\n}'); assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); - assertEq(vm.readFile(file), '{"a":"123","b":"456","c":"789"}'); + assertEq( + vm.readFile(file), + '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' + ); } function testSnapshotGasSection() public { From 07cd7f3d9972b34ecbc29427e193a798b3ad737f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 15:23:34 +0000 Subject: [PATCH 17/64] simplify and cleanup --- crates/cheatcodes/src/evm.rs | 53 ++++++++++++++---------------------- 1 file changed, 20 insertions(+), 33 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 3a17ffbc6cef..bb9b2f033a22 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -659,34 +659,24 @@ fn update_gas_snapshot( name: String, value: String, ) -> Result { - // Create the snapshot if it doesn't exist create_dir_all(ccx.state.config.paths.snapshots.clone())?; - if let Some(group) = group { - // Prepare the snapshot. - // Check if the snapshot already exists, if so, update the value in place. - // Otherwise, create a new snapshot. - let snapshot_path = &ccx.state.config.paths.snapshots.join(format!("{}.json", group)); + let snapshot_path = match &group { + Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{}.json", group_name)), + None => ccx.state.config.paths.snapshots.join(format!("{}.json", name)), + }; + + let result = if group.is_some() { let mut snapshot: BTreeMap = read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); snapshot.insert(name.clone(), value); - - // Write the snapshot to a file, asserting that the write was successful. - let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); - - Ok(result.abi_encode()) + write_pretty_json_file(&snapshot_path, &snapshot).is_ok() } else { - // Prepare the snapshot. - // Check if the snapshot already exists, if so, update the value in place. - // Otherwise, create a new snapshot. - let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{}.json", name)); let snapshot = value.to_string(); + write_pretty_json_file(&snapshot_path, &snapshot).is_ok() + }; - // Write the snapshot to a file, asserting that the write was successful. - let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); - - Ok(result.abi_encode()) - } + Ok(result.abi_encode()) } fn start_gas_snapshot( @@ -694,15 +684,17 @@ fn start_gas_snapshot( group: Option, name: String, ) -> Result { - if ccx.state.gas_metering.gas_records.iter().any(|record| match &group { - Some(g) => record.group.as_ref() == Some(g) && record.name == *name, - None => record.group.is_none() && record.name == *name, - }) { + if ccx + .state + .gas_metering + .gas_records + .iter() + .any(|record| record.group == group && record.name == *name) + { let group_name = group.as_deref().unwrap_or("default"); bail!("gas snapshot already active: {name} in group: {group_name}"); } - // Initialize the gas record, starting at 0. ccx.state.gas_metering.gas_records.push(GasRecord { group: group.clone(), name: name.clone(), @@ -724,21 +716,17 @@ fn stop_gas_snapshot( .iter_mut() .find(|record| record.group == group && record.name == name) { - // Get the gas used since the snapshot was started. let gas_used = record.gas_used; - // Create the snapshot if it doesn't exist create_dir_all(ccx.state.config.paths.snapshots.clone())?; - // Prepare the snapshot. let snapshot_path = match &group { - Some(g) => ccx.state.config.paths.snapshots.join(format!("{}.json", g)), + Some(group_name) => { + ccx.state.config.paths.snapshots.join(format!("{}.json", group_name)) + } None => ccx.state.config.paths.snapshots.join(format!("{}.json", name)), }; - // Depending on whether a group is provided, handle the snapshot differently. - // Check if the snapshot already exists, if so, update the value in place. - // Otherwise, create a new snapshot. let result = if group.is_some() { let mut snapshot: BTreeMap = read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); @@ -749,7 +737,6 @@ fn stop_gas_snapshot( write_pretty_json_file(&snapshot_path, &snapshot).is_ok() }; - // Delete the snapshot after writing. ccx.state .gas_metering .gas_records From fd83fe318c308f409f96482685c337fee2d88fc6 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 15:35:39 +0000 Subject: [PATCH 18/64] fix clippy --- crates/cheatcodes/src/evm.rs | 21 +++++++-------------- crates/evm/core/src/backend/mod.rs | 2 +- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index bb9b2f033a22..e36077678eb3 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -662,18 +662,17 @@ fn update_gas_snapshot( create_dir_all(ccx.state.config.paths.snapshots.clone())?; let snapshot_path = match &group { - Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{}.json", group_name)), - None => ccx.state.config.paths.snapshots.join(format!("{}.json", name)), + Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{group_name}.json")), + None => ccx.state.config.paths.snapshots.join(format!("{name}.json")), }; let result = if group.is_some() { let mut snapshot: BTreeMap = read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(name.clone(), value); + snapshot.insert(name, value); write_pretty_json_file(&snapshot_path, &snapshot).is_ok() } else { - let snapshot = value.to_string(); - write_pretty_json_file(&snapshot_path, &snapshot).is_ok() + write_pretty_json_file(&snapshot_path, &value).is_ok() }; Ok(result.abi_encode()) @@ -695,11 +694,7 @@ fn start_gas_snapshot( bail!("gas snapshot already active: {name} in group: {group_name}"); } - ccx.state.gas_metering.gas_records.push(GasRecord { - group: group.clone(), - name: name.clone(), - gas_used: 0, - }); + ccx.state.gas_metering.gas_records.push(GasRecord { group, name, gas_used: 0 }); Ok(Default::default()) } @@ -721,10 +716,8 @@ fn stop_gas_snapshot( create_dir_all(ccx.state.config.paths.snapshots.clone())?; let snapshot_path = match &group { - Some(group_name) => { - ccx.state.config.paths.snapshots.join(format!("{}.json", group_name)) - } - None => ccx.state.config.paths.snapshots.join(format!("{}.json", name)), + Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{group_name}.json")), + None => ccx.state.config.paths.snapshots.join(format!("{name}.json")), }; let result = if group.is_some() { diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index c7381093e3b9..8595cb4e24b3 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -76,7 +76,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// 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 + /// Snapshots can be reverted: [`DatabaseExt::revert_state_snapshot`], however, depending on the /// [RevertSnapshotAction], it will keep the snapshot alive or delete it. fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; From 3d7013e19d01f4a25c42d2316f1597c154d73b35 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 15:39:59 +0000 Subject: [PATCH 19/64] fix forge fmt --- testdata/default/cheats/ExpectCall.t.sol | 398 +++--------------- testdata/default/cheats/ExpectEmit.t.sol | 124 ++---- testdata/default/cheats/Fork2.t.sol | 38 +- testdata/default/cheats/GasSnapshot.t.sol | 9 +- testdata/default/cheats/LastCallGas.t.sol | 42 +- .../cheats/RecordAccountAccesses.t.sol | 2 +- testdata/default/cheats/StateSnapshots.t.sol | 18 +- testdata/foundry.toml | 2 +- 8 files changed, 113 insertions(+), 520 deletions(-) diff --git a/testdata/default/cheats/ExpectCall.t.sol b/testdata/default/cheats/ExpectCall.t.sol index bfde512fdf0c..51f1d3ea0d9f 100644 --- a/testdata/default/cheats/ExpectCall.t.sol +++ b/testdata/default/cheats/ExpectCall.t.sol @@ -56,114 +56,69 @@ contract SimpleCall { contract ProxyWithDelegateCall { function delegateCall(SimpleCall simpleCall) public returns (bool success) { - (success, ) = address(simpleCall).delegatecall( - abi.encodeWithSignature("call()") - ); + (success,) = address(simpleCall).delegatecall(abi.encodeWithSignature("call()")); } } contract ExpectCallTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function exposed_callTargetNTimes( - Contract target, - uint256 a, - uint256 b, - uint256 times - ) public { + function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(a, b); } } - function exposed_expectCallWithValue( - Contract target, - uint256 value, - uint256 amount - ) public { + function exposed_expectCallWithValue(Contract target, uint256 value, uint256 amount) public { target.pay{value: value}(amount); } function testExpectCallWithData() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); this.exposed_callTargetNTimes(target, 1, 2, 1); } function testExpectMultipleCallsWithData() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); // Even though we expect one call, we're using additive behavior, so getting more than one call is okay. this.exposed_callTargetNTimes(target, 1, 2, 2); } function testExpectMultipleCallsWithDataAdditive() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); this.exposed_callTargetNTimes(target, 1, 2, 2); } function testExpectMultipleCallsWithDataAdditiveLowerBound() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); this.exposed_callTargetNTimes(target, 1, 2, 3); } function testFailExpectMultipleCallsWithDataAdditive() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); // Not enough calls to satisfy the additive expectCall, which expects 3 calls. this.exposed_callTargetNTimes(target, 1, 2, 2); } function testFailExpectCallWithData() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 1 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); this.exposed_callTargetNTimes(target, 3, 3, 1); } function testExpectInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.numberB.selector) - ); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); this.exposed_expectInnerCall(target); } @@ -175,10 +130,7 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.numberB.selector) - ); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector)); this.exposed_failExpectInnerCall(target); } @@ -194,14 +146,8 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.forwardPay.selector) - ); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.pay.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.forwardPay.selector)); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.pay.selector)); this.exposed_forwardPay(target); } @@ -211,90 +157,55 @@ contract ExpectCallTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.sumInPlace.selector) - ); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.add.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.sumInPlace.selector)); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.add.selector)); this.exposed_expectCallMultipleFunctionsFlattened(target, inner); } - function exposed_expectCallMultipleFunctionsFlattened( - NestedContract target, - Contract inner - ) public { + function exposed_expectCallMultipleFunctionsFlattened(NestedContract target, Contract inner) public { target.sumInPlace(1, 1); inner.add(1, 1); } function testExpectSelectorCall() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); this.exposed_callTargetNTimes(target, 5, 5, 1); } function testFailExpectSelectorCall() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); } function testFailExpectCallWithMoreParameters() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 3, 3, 3) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 3, 3, 3)); target.add(3, 3); this.exposed_callTargetNTimes(target, 3, 3, 1); } function testExpectCallWithValue() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 1, - abi.encodeWithSelector(target.pay.selector, 2) - ); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); this.exposed_expectCallWithValue(target, 1, 2); } function testFailExpectCallValue() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 1, - abi.encodeWithSelector(target.pay.selector, 2) - ); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2)); } function testExpectCallWithValueWithoutParameters() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 3, - abi.encodeWithSelector(target.pay.selector) - ); + vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector)); this.exposed_expectCallWithValue(target, 3, 100); } function testExpectCallWithValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 1, - 50_000, - abi.encodeWithSelector(inner.pay.selector, 1) - ); + vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); this.exposed_forwardPay(target); } @@ -305,12 +216,7 @@ contract ExpectCallTest is DSTest { function testExpectCallWithNoValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 0, - 50_000, - abi.encodeWithSelector(inner.add.selector, 1, 1) - ); + vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); this.exposed_addHardGasLimit(target); } @@ -321,48 +227,28 @@ contract ExpectCallTest is DSTest { function testFailExpectCallWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 0, - 25_000, - abi.encodeWithSelector(inner.add.selector, 1, 1) - ); + vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); this.exposed_addHardGasLimit(target); } function testExpectCallWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 1, - 50_000, - abi.encodeWithSelector(inner.pay.selector, 1) - ); + vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1)); this.exposed_forwardPay(target); } function testExpectCallWithNoValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 0, - 25_000, - abi.encodeWithSelector(inner.add.selector, 1, 1) - ); + vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1)); this.exposed_addHardGasLimit(target); } function testFailExpectCallWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 0, - 50_001, - abi.encodeWithSelector(inner.add.selector, 1, 1) - ); + vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1)); this.exposed_addHardGasLimit(target); } @@ -370,10 +256,7 @@ contract ExpectCallTest is DSTest { function testFailExpectCallWithRevertDisallowed() public { Contract target = new Contract(); vm.expectRevert(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); this.exposed_callTargetNTimes(target, 5, 5, 1); } @@ -391,11 +274,7 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithData() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(Contract.add.selector, 1, 2), - 3 - ); + vm.expectCall(address(target), abi.encodeWithSelector(Contract.add.selector, 1, 2), 3); this.exposed_expectCallCountWithData(target); } @@ -407,32 +286,20 @@ contract ExpectCallCountTest is DSTest { function testExpectZeroCallCountAssert() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 0 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 0); target.add(3, 3); } function testFailExpectCallCountWithWrongCount() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); target.add(1, 2); } function testExpectCountInnerCall() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.numberB.selector), - 1 - ); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); target.sum(); } @@ -440,11 +307,7 @@ contract ExpectCallCountTest is DSTest { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.numberB.selector), - 1 - ); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 1); // this function does not call inner target.hello(); @@ -453,77 +316,44 @@ contract ExpectCallCountTest is DSTest { function testExpectCountInnerAndOuterCalls() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - abi.encodeWithSelector(inner.numberB.selector), - 2 - ); + vm.expectCall(address(inner), abi.encodeWithSelector(inner.numberB.selector), 2); this.exposed_expectCountInnerAndOuterCalls(inner, target); } - function exposed_expectCountInnerAndOuterCalls( - Contract inner, - NestedContract target - ) public { + function exposed_expectCountInnerAndOuterCalls(Contract inner, NestedContract target) public { inner.numberB(); target.sum(); } - function exposed_pay( - Contract target, - uint256 value, - uint256 amount - ) public payable { + function exposed_pay(Contract target, uint256 value, uint256 amount) public payable { target.pay{value: value}(amount); } function testExpectCallCountWithValue() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 1, - abi.encodeWithSelector(target.pay.selector, 2), - 1 - ); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); this.exposed_pay{value: 1}(target, 1, 2); } function testExpectZeroCallCountValue() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 1, - abi.encodeWithSelector(target.pay.selector, 2), - 0 - ); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 0); this.exposed_pay{value: 2}(target, 2, 2); } function testFailExpectCallCountValue() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 1, - abi.encodeWithSelector(target.pay.selector, 2), - 1 - ); + vm.expectCall(address(target), 1, abi.encodeWithSelector(target.pay.selector, 2), 1); this.exposed_pay{value: 2}(target, 2, 2); } function testExpectCallCountWithValueWithoutParameters() public { Contract target = new Contract(); - vm.expectCall( - address(target), - 3, - abi.encodeWithSelector(target.pay.selector), - 3 - ); + vm.expectCall(address(target), 3, abi.encodeWithSelector(target.pay.selector), 3); this.exposed_expectCallCountWithValueWithoutParameters(target); } - function exposed_expectCallCountWithValueWithoutParameters( - Contract target - ) public { + function exposed_expectCallCountWithValueWithoutParameters(Contract target) public { target.pay{value: 3}(100); target.pay{value: 3}(100); target.pay{value: 3}(100); @@ -532,27 +362,16 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 1, - 50_000, - abi.encodeWithSelector(inner.pay.selector, 1), - 2 - ); + vm.expectCall(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 2); this.exposed_expectCallCountWithValueAndGas(target); } - function exposed_expectCallCountWithValueAndGas( - NestedContract target - ) public { + function exposed_expectCallCountWithValueAndGas(NestedContract target) public { target.forwardPay{value: 1}(); target.forwardPay{value: 1}(); } - function exposed_addHardGasLimit( - NestedContract target, - uint256 times - ) public { + function exposed_addHardGasLimit(NestedContract target, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.addHardGasLimit(); } @@ -561,52 +380,28 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithNoValueAndGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 0, - 50_000, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 1 - ); + vm.expectCall(address(inner), 0, 50_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); this.exposed_addHardGasLimit(target, 1); } function testExpectZeroCallCountWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 0, - 25_000, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 0 - ); + vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); this.exposed_addHardGasLimit(target, 1); } function testFailExpectCallCountWithNoValueAndWrongGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCall( - address(inner), - 0, - 25_000, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 2 - ); + vm.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); this.exposed_addHardGasLimit(target, 2); } function testExpectCallCountWithValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 1, - 50_000, - abi.encodeWithSelector(inner.pay.selector, 1), - 1 - ); + vm.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1), 1); this.exposed_forwardPay(target); } @@ -617,39 +412,21 @@ contract ExpectCallCountTest is DSTest { function testExpectCallCountWithNoValueAndMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 0, - 25_000, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 2 - ); + vm.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1), 2); this.exposed_addHardGasLimit(target, 2); } function testExpectCallZeroCountWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 0, - 50_001, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 0 - ); + vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 0); this.exposed_addHardGasLimit(target, 1); } function testFailExpectCallCountWithNoValueAndWrongMinGas() public { Contract inner = new Contract(); NestedContract target = new NestedContract(inner); - vm.expectCallMinGas( - address(inner), - 0, - 50_001, - abi.encodeWithSelector(inner.add.selector, 1, 1), - 1 - ); + vm.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1), 1); this.exposed_addHardGasLimit(target, 1); } } @@ -657,12 +434,7 @@ contract ExpectCallCountTest is DSTest { contract ExpectCallMixedTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function exposed_callTargetNTimes( - Contract target, - uint256 a, - uint256 b, - uint256 times - ) public { + function exposed_callTargetNTimes(Contract target, uint256 a, uint256 b, uint256 times) public { for (uint256 i = 0; i < times; i++) { target.add(1, 2); } @@ -670,65 +442,36 @@ contract ExpectCallMixedTest is DSTest { function testFailOverrideNoCountWithCount() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); // You should not be able to overwrite a expectCall that had no count with some count. - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); this.exposed_callTargetNTimes(target, 1, 2, 2); } function testFailOverrideCountWithCount() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); // You should not be able to overwrite a expectCall that had a count with some count. - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 1 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 1); target.add(1, 2); target.add(1, 2); } function testFailOverrideCountWithNoCount() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); // You should not be able to overwrite a expectCall that had a count with no count. - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); target.add(1, 2); target.add(1, 2); } function testExpectMatchPartialAndFull() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector), 2); // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2)); this.exposed_expectMatchPartialAndFull(target); } @@ -739,17 +482,10 @@ contract ExpectCallMixedTest is DSTest { function testExpectMatchPartialAndFullFlipped() public { Contract target = new Contract(); - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector) - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector)); // Even if a partial match is specified, you should still be able to look for full matches // as one does not override the other. - vm.expectCall( - address(target), - abi.encodeWithSelector(target.add.selector, 1, 2), - 2 - ); + vm.expectCall(address(target), abi.encodeWithSelector(target.add.selector, 1, 2), 2); this.exposed_expectMatchPartialAndFullFlipped(target); } diff --git a/testdata/default/cheats/ExpectEmit.t.sol b/testdata/default/cheats/ExpectEmit.t.sol index 8baa41083c4a..f3e0a03c9fbc 100644 --- a/testdata/default/cheats/ExpectEmit.t.sol +++ b/testdata/default/cheats/ExpectEmit.t.sol @@ -7,12 +7,7 @@ import "cheats/Vm.sol"; contract Emitter { uint256 public thing; - event Something( - uint256 indexed topic1, - uint256 indexed topic2, - uint256 indexed topic3, - uint256 data - ); + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); event A(uint256 indexed topic1); event B(uint256 indexed topic1); event C(uint256 indexed topic1); @@ -29,12 +24,7 @@ contract Emitter { event SomethingNonIndexed(uint256 data); - function emitEvent( - uint256 topic1, - uint256 topic2, - uint256 topic3, - uint256 data - ) public { + function emitEvent(uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { emit Something(topic1, topic2, topic3, data); } @@ -60,13 +50,7 @@ contract Emitter { emit Something(1, 2, 3, 4); } - function emitNested( - Emitter inner, - uint256 topic1, - uint256 topic2, - uint256 topic3, - uint256 data - ) public { + function emitNested(Emitter inner, uint256 topic1, uint256 topic2, uint256 topic3, uint256 data) public { inner.emitEvent(topic1, topic2, topic3, data); } @@ -119,7 +103,7 @@ contract Emitter { /// Emulates `Emitter` in #760 contract LowLevelCaller { function f() external returns (bool success) { - (success, ) = address(this).call(abi.encodeWithSignature("g()")); + (success,) = address(this).call(abi.encodeWithSignature("g()")); } function g() public {} @@ -129,12 +113,7 @@ contract ExpectEmitTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); Emitter emitter; - event Something( - uint256 indexed topic1, - uint256 indexed topic2, - uint256 indexed topic3, - uint256 data - ); + event Something(uint256 indexed topic1, uint256 indexed topic2, uint256 indexed topic3, uint256 data); event SomethingElse(uint256 indexed topic1); @@ -175,26 +154,15 @@ contract ExpectEmitTest is DSTest { uint128 topic3, uint128 data ) public { - uint256 transformedTopic1 = checkTopic1 - ? uint256(topic1) - : uint256(topic1) + 1; - uint256 transformedTopic2 = checkTopic2 - ? uint256(topic2) - : uint256(topic2) + 1; - uint256 transformedTopic3 = checkTopic3 - ? uint256(topic3) - : uint256(topic3) + 1; + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) : uint256(topic1) + 1; + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) : uint256(topic2) + 1; + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) : uint256(topic3) + 1; uint256 transformedData = checkData ? uint256(data) : uint256(data) + 1; vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitEvent( - transformedTopic1, - transformedTopic2, - transformedTopic3, - transformedData - ); + emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } /// The topics that are checked are altered to be incorrect @@ -211,26 +179,15 @@ contract ExpectEmitTest is DSTest { ) public { vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); - uint256 transformedTopic1 = checkTopic1 - ? uint256(topic1) + 1 - : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 - ? uint256(topic2) + 1 - : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 - ? uint256(topic3) + 1 - : uint256(topic3); + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitEvent( - transformedTopic1, - transformedTopic2, - transformedTopic3, - transformedData - ); + emitter.emitEvent(transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } /// The topics that are checked are altered to be incorrect @@ -247,27 +204,15 @@ contract ExpectEmitTest is DSTest { ) public { Emitter inner = new Emitter(); - uint256 transformedTopic1 = checkTopic1 - ? uint256(topic1) - : uint256(topic1) + 1; - uint256 transformedTopic2 = checkTopic2 - ? uint256(topic2) - : uint256(topic2) + 1; - uint256 transformedTopic3 = checkTopic3 - ? uint256(topic3) - : uint256(topic3) + 1; + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) : uint256(topic1) + 1; + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) : uint256(topic2) + 1; + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) : uint256(topic3) + 1; uint256 transformedData = checkData ? uint256(data) : uint256(data) + 1; vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitNested( - inner, - transformedTopic1, - transformedTopic2, - transformedTopic3, - transformedData - ); + emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } /// The topics that are checked are altered to be incorrect @@ -285,27 +230,15 @@ contract ExpectEmitTest is DSTest { vm.assume(checkTopic1 || checkTopic2 || checkTopic3 || checkData); Emitter inner = new Emitter(); - uint256 transformedTopic1 = checkTopic1 - ? uint256(topic1) + 1 - : uint256(topic1); - uint256 transformedTopic2 = checkTopic2 - ? uint256(topic2) + 1 - : uint256(topic2); - uint256 transformedTopic3 = checkTopic3 - ? uint256(topic3) + 1 - : uint256(topic3); + uint256 transformedTopic1 = checkTopic1 ? uint256(topic1) + 1 : uint256(topic1); + uint256 transformedTopic2 = checkTopic2 ? uint256(topic2) + 1 : uint256(topic2); + uint256 transformedTopic3 = checkTopic3 ? uint256(topic3) + 1 : uint256(topic3); uint256 transformedData = checkData ? uint256(data) + 1 : uint256(data); vm.expectEmit(checkTopic1, checkTopic2, checkTopic3, checkData); emit Something(topic1, topic2, topic3, data); - emitter.emitNested( - inner, - transformedTopic1, - transformedTopic2, - transformedTopic3, - transformedData - ); + emitter.emitNested(inner, transformedTopic1, transformedTopic2, transformedTopic3, transformedData); } function testExpectEmitMultiple() public { @@ -315,10 +248,7 @@ contract ExpectEmitTest is DSTest { emit Something(5, 6, 7, 8); emitter.emitMultiple( - [uint256(1), uint256(5)], - [uint256(2), uint256(6)], - [uint256(3), uint256(7)], - [uint256(4), uint256(8)] + [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] ); } @@ -338,10 +268,7 @@ contract ExpectEmitTest is DSTest { emit Something(5, 6, 7, 8); emitter.emitMultiple( - [uint256(1), uint256(5)], - [uint256(2), uint256(6)], - [uint256(3), uint256(7)], - [uint256(4), uint256(8)] + [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] ); } @@ -452,10 +379,7 @@ contract ExpectEmitTest is DSTest { emit Something(1, 2, 3, 4); emitter.emitMultiple( - [uint256(1), uint256(5)], - [uint256(2), uint256(6)], - [uint256(3), uint256(7)], - [uint256(4), uint256(8)] + [uint256(1), uint256(5)], [uint256(2), uint256(6)], [uint256(3), uint256(7)], [uint256(4), uint256(8)] ); } diff --git a/testdata/default/cheats/Fork2.t.sol b/testdata/default/cheats/Fork2.t.sol index 1c33676c2cb6..06e1c13e1c31 100644 --- a/testdata/default/cheats/Fork2.t.sol +++ b/testdata/default/cheats/Fork2.t.sol @@ -23,10 +23,7 @@ contract MyContract { } function ensureBlockHash() public view { - require( - blockhash(block.number - 1) == blockHash, - "Block Hash does not match" - ); + require(blockhash(block.number - 1) == blockHash, "Block Hash does not match"); } } @@ -191,19 +188,11 @@ contract ForkTest is DSTest { string memory path = "fixtures/Rpc/eth_getLogs.json"; string memory file = vm.readFile(path); bytes memory parsed = vm.parseJson(file); - EthGetLogsJsonParseable[] memory fixtureLogs = abi.decode( - parsed, - (EthGetLogsJsonParseable[]) - ); + EthGetLogsJsonParseable[] memory fixtureLogs = abi.decode(parsed, (EthGetLogsJsonParseable[])); bytes32[] memory topics = new bytes32[](1); topics[0] = withdrawalTopic; - Vm.EthGetLogs[] memory logs = vm.eth_getLogs( - blockNumber, - blockNumber, - weth, - topics - ); + Vm.EthGetLogs[] memory logs = vm.eth_getLogs(blockNumber, blockNumber, weth, topics); assertEq(logs.length, 3); for (uint256 i = 0; i < logs.length; i++) { @@ -215,24 +204,9 @@ contract ForkTest is DSTest { if (i == 1) i_str = "1"; if (i == 2) i_str = "2"; - assertEq( - log.blockNumber, - vm.parseJsonUint( - file, - string.concat("[", i_str, "].blockNumber") - ) - ); - assertEq( - log.logIndex, - vm.parseJsonUint(file, string.concat("[", i_str, "].logIndex")) - ); - assertEq( - log.transactionIndex, - vm.parseJsonUint( - file, - string.concat("[", i_str, "].transactionIndex") - ) - ); + assertEq(log.blockNumber, vm.parseJsonUint(file, string.concat("[", i_str, "].blockNumber"))); + assertEq(log.logIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].logIndex"))); + assertEq(log.transactionIndex, vm.parseJsonUint(file, string.concat("[", i_str, "].transactionIndex"))); assertEq(log.blockHash, fixtureLogs[i].blockHash); assertEq(log.removed, fixtureLogs[i].removed); diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 955e80493bc2..c8ecb2c538bb 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -56,10 +56,7 @@ contract GasSnapshotTest is DSTest { assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); - assertEq( - vm.readFile(file), - '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' - ); + assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); } function testSnapshotGasSection() public { @@ -75,9 +72,7 @@ contract GasSnapshotTest is DSTest { a.run(256); // 5_821_576 gas a.run(512); // 11_617_936 gas - (bool success, uint256 gasUsed) = vm.stopSnapshotGas( - "testSnapshotGasSection" - ); + (bool success, uint256 gasUsed) = vm.stopSnapshotGas("testSnapshotGasSection"); assertTrue(success); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas diff --git a/testdata/default/cheats/LastCallGas.t.sol b/testdata/default/cheats/LastCallGas.t.sol index 810d935eebc6..1164e67ac41e 100644 --- a/testdata/default/cheats/LastCallGas.t.sol +++ b/testdata/default/cheats/LastCallGas.t.sol @@ -49,7 +49,7 @@ abstract contract LastCallGasFixture is DSTest { } function _performCall() internal returns (bool success) { - (success, ) = address(target).call(""); + (success,) = address(target).call(""); } function _performRefund() internal { @@ -69,31 +69,19 @@ contract LastCallGasIsolatedTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 21064, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 21064, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 21064, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21064, gasRefunded: 0})); } function testRecordGasRefund() public { _setup(); _performRefund(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 21380, gasRefunded: 4800}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 21380, gasRefunded: 4800})); } } @@ -102,30 +90,18 @@ contract LastCallGasDefaultTest is LastCallGasFixture { function testRecordLastCallGas() public { _setup(); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 64, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 64, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); _performCall(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 64, gasRefunded: 0}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 64, gasRefunded: 0})); } function testRecordGasRefund() public { _setup(); _performRefund(); - _assertGas( - vm.lastCallGas(), - Gas({gasTotalUsed: 216, gasRefunded: 19900}) - ); + _assertGas(vm.lastCallGas(), Gas({gasTotalUsed: 216, gasRefunded: 19900})); } } diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index b20c245ad649..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(); - (bool success, ) = 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/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index ddc5df151971..1a938b03c64f 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -98,20 +98,8 @@ contract StateSnapshotTest is DSTest { 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" - ); + 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/foundry.toml b/testdata/foundry.toml index 30621914fa35..e9189bb008a3 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 From 11803618aead274983640abcb07f68be3033cc2d Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 28 Aug 2024 16:18:23 +0000 Subject: [PATCH 20/64] assert that alphabetic ordering is maintained --- testdata/default/cheats/GasSnapshot.t.sol | 131 ++++++++++++++++++---- testdata/foundry.toml | 2 +- 2 files changed, 112 insertions(+), 21 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index c8ecb2c538bb..0e2387cf7cb0 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -4,16 +4,6 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; import "cheats/Vm.sol"; -contract Flare { - bytes32[] public data; - - function run(uint256 n) public { - for (uint256 i = 0; i < n; i++) { - data.push(keccak256(abi.encodePacked(i))); - } - } -} - contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); @@ -29,7 +19,7 @@ contract GasSnapshotTest is DSTest { assertEq(value, '"123"'); } - function testSnapshotValueGroup() public { + function testSnapshotValueGroupSingle() public { string memory file = "snapshots/GasSnapshotTest.json"; clear(file); @@ -41,7 +31,7 @@ contract GasSnapshotTest is DSTest { assertEq(value, '{\n "testSnapshotValue": "123"\n}'); } - function testSnapshotGroupValue() public { + function testSnapshotValueGroupMultiple() public { string memory file = "snapshots/testSnapshotGroupValue.json"; clear(file); @@ -56,28 +46,119 @@ contract GasSnapshotTest is DSTest { assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); - assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); + // Expect: + // { + // "a": "123", + // "b": "456", + // "c": "789" + // } + assertEq( + vm.readFile(file), + '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' + ); } function testSnapshotGasSection() public { string memory file = "snapshots/testSnapshotGasSection.json"; clear(file); - Flare a = new Flare(); + Flare f = new Flare(); - a.run(64); + f.run(1); vm.startSnapshotGas("testSnapshotGasSection"); - a.run(256); // 5_821_576 gas - a.run(512); // 11_617_936 gas + f.run(256); // 5_821_576 gas + f.run(512); // 11_617_936 gas - (bool success, uint256 gasUsed) = vm.stopSnapshotGas("testSnapshotGasSection"); + (bool success, uint256 gasUsed) = vm.stopSnapshotGas( + "testSnapshotGasSection" + ); assertTrue(success); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - string memory value = vm.readFile(file); - assertEq(value, '"17439512"'); + // Expect: "17439512" + assertEq(vm.readFile(file), '"17439512"'); + } + + function testSnapshotOrdering() public { + string memory file = "snapshots/SnapshotOrdering.json"; + clear(file); + + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + vm.snapshotValue("SnapshotOrdering", "c", c); + + // Expect: + // { + // "c": "789" + // } + assertEq(vm.readFile(file), '{\n "c": "789"\n}'); + + vm.snapshotValue("SnapshotOrdering", "a", a); + + // Expect: + // { + // "a": "123", + // "c": "789" + // } + assertEq(vm.readFile(file), '{\n "a": "123",\n "c": "789"\n}'); + + vm.snapshotValue("SnapshotOrdering", "b", b); + + // Expect: + // { + // "a": "123", + // "b": "456", + // "c": "789" + // } + assertEq( + vm.readFile(file), + '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' + ); + } + + function testSnapshotCombination() public { + string memory file = "snapshots/SnapshotCombination.json"; + clear(file); + + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + vm.snapshotValue("SnapshotCombination", "c", c); + vm.snapshotValue("SnapshotCombination", "a", a); + + Flare f = new Flare(); + + f.run(1); + + vm.startSnapshotGas("SnapshotCombination", "z"); + + f.run(256); // 5_821_576 gas + + (bool success, uint256 gasUsed) = vm.stopSnapshotGas( + "SnapshotCombination", + "z" + ); + assertTrue(success); + assertEq(gasUsed, 5_821_576); + + vm.snapshotValue("SnapshotCombination", "b", b); + + // Expect: + // { + // "a": "123", + // "b": "456", + // "c": "789", + // "z": "5821576" + // } + assertEq( + vm.readFile(file), + '{\n "a": "123",\n "b": "456",\n "c": "789",\n "z": "5821576"\n}' + ); } // Remove file if it exists so each test can start with a clean slate. @@ -87,3 +168,13 @@ contract GasSnapshotTest is DSTest { } } } + +contract Flare { + bytes32[] public data; + + function run(uint256 n) public { + for (uint256 i = 0; i < n; i++) { + data.push(keccak256(abi.encodePacked(i))); + } + } +} 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 From b8453db0c01b4cbde8d990c4a4cc0fe5171400c5 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 10:07:31 +0000 Subject: [PATCH 21/64] prepare for context aware derived names --- crates/cheatcodes/assets/cheatcodes.json | 72 +++++++++++++-- crates/cheatcodes/spec/src/vm.rs | 21 +++++ crates/cheatcodes/src/evm.rs | 108 ++++++++++++++-------- testdata/cheats/Vm.sol | 3 + testdata/default/cheats/GasSnapshot.t.sol | 73 ++++++++------- 5 files changed, 196 insertions(+), 81 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 1dc742f1aa90..3998a3d43f60 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8089,7 +8089,27 @@ { "func": { "id": "snapshotValue_0", - "description": "Snapshot capture an arbitrary numerical value by name.", + "description": "Snapshot capture an arbitrary numerical value.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", + "declaration": "function snapshotValue(uint256 value) external returns (bool success);", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(uint256)", + "selector": "0xeaa943bf", + "selectorBytes": [ + 234, + 169, + 67, + 191 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_1", + "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 returns (bool success);", "visibility": "external", "mutability": "", @@ -8108,7 +8128,7 @@ }, { "func": { - "id": "snapshotValue_1", + "id": "snapshotValue_2", "description": "Snapshot capture an arbitrary numerical value by name in a group.", "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external returns (bool success);", "visibility": "external", @@ -8269,7 +8289,27 @@ { "func": { "id": "startSnapshotGas_0", - "description": "Start a snapshot capture of the current gas usage by name.", + "description": "Start a snapshot capture of the current gas usage.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", + "declaration": "function startSnapshotGas() external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas()", + "selector": "0x939251c0", + "selectorBytes": [ + 147, + 146, + 81, + 192 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startSnapshotGas_1", + "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": "", @@ -8288,7 +8328,7 @@ }, { "func": { - "id": "startSnapshotGas_1", + "id": "startSnapshotGas_2", "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", @@ -8429,7 +8469,27 @@ { "func": { "id": "stopSnapshotGas_0", - "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.", + "description": "Stop the snapshot capture of the current gas usage, capturing the gas used since the start.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", + "declaration": "function stopSnapshotGas() external returns (bool success, 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 (bool success, uint256 gasUsed);", "visibility": "external", "mutability": "", @@ -8448,7 +8508,7 @@ }, { "func": { - "id": "stopSnapshotGas_1", + "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 (bool success, uint256 gasUsed);", "visibility": "external", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index f9c631fce0e9..eff755ed9edf 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,7 +499,14 @@ interface Vm { // ----- Arbitrary Snapshots ----- + /// Snapshot capture an arbitrary numerical value. + /// The name is derived from the function name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(uint256 value) external returns (bool success); + /// 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 returns (bool success); @@ -509,7 +516,14 @@ interface Vm { // -------- Gas Snapshots -------- + /// Start a snapshot capture of the current gas usage. + /// The name is derived from the function name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas() 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; @@ -517,7 +531,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function startSnapshotGas(string calldata group, string calldata name) external; + /// Stop the snapshot capture of the current gas usage, capturing the gas used since the start. + /// The name is derived from the function name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas() external returns (bool success, 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 (bool success, uint256 gasUsed); diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index e36077678eb3..63d2852b0de7 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -66,7 +66,7 @@ pub struct DealRecord { #[derive(Clone, Debug)] pub struct GasRecord { /// The group name of the gas snapshot. - pub group: Option, + pub group: String, /// The name of the gas snapshot. pub name: String, /// The total gas used in the gas snapshot. @@ -491,43 +491,66 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { name, value } = self; - update_gas_snapshot(ccx, None, name.to_string(), value.to_string()) + let Self { value } = self; + // TODO: derive name, use Option + create_gas_snapshot(ccx, None, None, value.to_string()) } } impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, value } = self; + create_gas_snapshot(ccx, None, Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotValue_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name, value } = self; - update_gas_snapshot(ccx, Some(group), name.to_string(), value.to_string()) + create_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } impl Cheatcode for startSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { name } = self; - start_gas_snapshot(ccx, None, name.to_string()) + let Self {} = self; + start_gas_snapshot(ccx, None, None) } } impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + start_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for startSnapshotGas_2Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name } = self; - start_gas_snapshot(ccx, Some(group.clone()), name.to_string()) + 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 { name } = self; - stop_gas_snapshot(ccx, None, name.to_string()) + let Self {} = self; + // TODO: derive name, use Option + stop_gas_snapshot(ccx, None, None) } } impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + 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; - stop_gas_snapshot(ccx, Some(group.clone()), name.to_string()) + stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } @@ -653,23 +676,25 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } -fn update_gas_snapshot( +fn create_gas_snapshot( ccx: &mut CheatsCtxt, - group: Option<&String>, - name: String, + group: Option, + name: Option, value: String, ) -> Result { create_dir_all(ccx.state.config.paths.snapshots.clone())?; - let snapshot_path = match &group { - Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{group_name}.json")), - None => ccx.state.config.paths.snapshots.join(format!("{name}.json")), - }; + // TODO: derive from contract name, function name. + let snapshot_group_name = group.clone().or_else(|| Some("default".to_string())); + let snapshot_name = name.clone().or_else(|| Some("default".to_string())); + + let snapshot_path = + ccx.state.config.paths.snapshots.join(format!("{}.json", snapshot_group_name.unwrap())); let result = if group.is_some() { let mut snapshot: BTreeMap = read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(name, value); + snapshot.insert(snapshot_name.unwrap(), value); write_pretty_json_file(&snapshot_path, &snapshot).is_ok() } else { write_pretty_json_file(&snapshot_path, &value).is_ok() @@ -681,20 +706,27 @@ fn update_gas_snapshot( fn start_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: String, + name: Option, ) -> Result { + // TODO: derive from contract name, function name. + let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_name = name.as_deref().unwrap_or("default"); + if ccx .state .gas_metering .gas_records .iter() - .any(|record| record.group == group && record.name == *name) + .any(|record| record.group == snapshot_group_name && record.name == snapshot_name) { - let group_name = group.as_deref().unwrap_or("default"); - bail!("gas snapshot already active: {name} in group: {group_name}"); + bail!("gas snapshot already active: {snapshot_name} in group: {snapshot_group_name}"); } - ccx.state.gas_metering.gas_records.push(GasRecord { group, name, gas_used: 0 }); + ccx.state.gas_metering.gas_records.push(GasRecord { + group: snapshot_group_name.to_string(), + name: snapshot_name.to_string(), + gas_used: 0, + }); Ok(Default::default()) } @@ -702,43 +734,43 @@ fn start_gas_snapshot( fn stop_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: String, + name: Option, ) -> Result { + create_dir_all(ccx.state.config.paths.snapshots.clone())?; + + // TODO: derive from contract name, function name. + let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_name = name.as_deref().unwrap_or("default"); + if let Some(record) = ccx .state .gas_metering .gas_records .iter_mut() - .find(|record| record.group == group && record.name == name) + .find(|record| record.group == snapshot_group_name && record.name == snapshot_name) { let gas_used = record.gas_used; - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - let snapshot_path = match &group { - Some(group_name) => ccx.state.config.paths.snapshots.join(format!("{group_name}.json")), - None => ccx.state.config.paths.snapshots.join(format!("{name}.json")), - }; + let snapshot_path = + ccx.state.config.paths.snapshots.join(format!("{snapshot_group_name}.json")); let result = if group.is_some() { let mut snapshot: BTreeMap = read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(name.clone(), gas_used.to_string()); + snapshot.insert(snapshot_name.to_string(), gas_used.to_string()); write_pretty_json_file(&snapshot_path, &snapshot).is_ok() } else { let snapshot = gas_used.to_string(); write_pretty_json_file(&snapshot_path, &snapshot).is_ok() }; - ccx.state - .gas_metering - .gas_records - .retain(|record| !(record.group == group && record.name == name)); + ccx.state.gas_metering.gas_records.retain(|record| { + !(record.group == snapshot_group_name && record.name == snapshot_name) + }); Ok((result, gas_used).abi_encode_params()) } else { - let group_name = group.as_deref().unwrap_or("default"); - bail!("no gas snapshot was started with the name: {name} in group: {group_name}"); + bail!("no gas snapshot was started with the name: {snapshot_name} in group: {snapshot_group_name}"); } } diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 72a25f85a6d6..75de65dc6837 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -400,6 +400,7 @@ interface Vm { function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshotState() external returns (uint256 snapshotId); + function snapshotValue(uint256 value) external returns (bool success); function snapshotValue(string calldata name, uint256 value) external returns (bool success); function snapshotValue(string calldata group, string calldata name, uint256 value) external returns (bool success); function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); @@ -409,6 +410,7 @@ interface Vm { function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; + function startSnapshotGas() external; function startSnapshotGas(string calldata name) external; function startSnapshotGas(string calldata group, string calldata name) external; function startStateDiffRecording() external; @@ -417,6 +419,7 @@ interface Vm { function stopExpectSafeMemory() external; function stopMappingRecording() external; function stopPrank() external; + function stopSnapshotGas() external returns (bool success, uint256 gasUsed); function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed); function stopSnapshotGas(string calldata group, string calldata name) external returns (bool success, uint256 gasUsed); function store(address target, bytes32 slot, bytes32 value) external; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 0e2387cf7cb0..c91fd48a8a0e 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -13,25 +13,17 @@ contract GasSnapshotTest is DSTest { uint256 a = 123; - assertTrue(vm.snapshotValue("testSnapshotValue", a)); - - string memory value = vm.readFile(file); - assertEq(value, '"123"'); - } - - function testSnapshotValueGroupSingle() public { - string memory file = "snapshots/GasSnapshotTest.json"; - clear(file); - - uint256 a = 123; - - assertTrue(vm.snapshotValue("GasSnapshotTest", "testSnapshotValue", a)); + assertTrue(vm.snapshotValue("testSnapshotValue", "a", a)); + // Expect: + // { + // "a": "123" + // } string memory value = vm.readFile(file); - assertEq(value, '{\n "testSnapshotValue": "123"\n}'); + assertEq(value, '{\n "a": "123"\n}'); } - function testSnapshotValueGroupMultiple() public { + function testSnapshotValueGroup() public { string memory file = "snapshots/testSnapshotGroupValue.json"; clear(file); @@ -42,6 +34,11 @@ contract GasSnapshotTest is DSTest { assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a)); assertTrue(vm.snapshotValue("testSnapshotGroupValue", "b", b)); + // Expect: + // { + // "a": "123", + // "b": "456" + // } assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456"\n}'); assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); @@ -52,10 +49,20 @@ contract GasSnapshotTest is DSTest { // "b": "456", // "c": "789" // } - assertEq( - vm.readFile(file), - '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' - ); + assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); + + // Overwrite a + uint256 a2 = 321; + + assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a2)); + + // Expect: + // { + // "a": "321", + // "b": "456", + // "c": "789" + // } + assertEq(vm.readFile(file), '{\n "a": "321",\n "b": "456",\n "c": "789"\n}'); } function testSnapshotGasSection() public { @@ -66,19 +73,20 @@ contract GasSnapshotTest is DSTest { f.run(1); - vm.startSnapshotGas("testSnapshotGasSection"); + vm.startSnapshotGas("testSnapshotGasSection", "a"); f.run(256); // 5_821_576 gas f.run(512); // 11_617_936 gas - (bool success, uint256 gasUsed) = vm.stopSnapshotGas( - "testSnapshotGasSection" - ); + (bool success, uint256 gasUsed) = vm.stopSnapshotGas("testSnapshotGasSection", "a"); assertTrue(success); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - // Expect: "17439512" - assertEq(vm.readFile(file), '"17439512"'); + // Expect: + // { + // "a": "17439512" + // } + assertEq(vm.readFile(file), '{\n "a": "17439512"\n}'); } function testSnapshotOrdering() public { @@ -114,10 +122,7 @@ contract GasSnapshotTest is DSTest { // "b": "456", // "c": "789" // } - assertEq( - vm.readFile(file), - '{\n "a": "123",\n "b": "456",\n "c": "789"\n}' - ); + assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); } function testSnapshotCombination() public { @@ -139,10 +144,7 @@ contract GasSnapshotTest is DSTest { f.run(256); // 5_821_576 gas - (bool success, uint256 gasUsed) = vm.stopSnapshotGas( - "SnapshotCombination", - "z" - ); + (bool success, uint256 gasUsed) = vm.stopSnapshotGas("SnapshotCombination", "z"); assertTrue(success); assertEq(gasUsed, 5_821_576); @@ -155,10 +157,7 @@ contract GasSnapshotTest is DSTest { // "c": "789", // "z": "5821576" // } - assertEq( - vm.readFile(file), - '{\n "a": "123",\n "b": "456",\n "c": "789",\n "z": "5821576"\n}' - ); + assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789",\n "z": "5821576"\n}'); } // Remove file if it exists so each test can start with a clean slate. From 22f3af44999494b1f33b45fc09c4b11e7aa77b67 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 10:29:54 +0000 Subject: [PATCH 22/64] prepare for context aware group and name generation --- crates/cheatcodes/src/evm.rs | 33 ++++++++--------------- testdata/default/cheats/GasSnapshot.t.sol | 18 ++++++++++++- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 63d2852b0de7..e90eb3cd9be9 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -492,7 +492,6 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { value } = self; - // TODO: derive name, use Option create_gas_snapshot(ccx, None, None, value.to_string()) } } @@ -535,7 +534,6 @@ impl Cheatcode for startSnapshotGas_2Call { impl Cheatcode for stopSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - // TODO: derive name, use Option stop_gas_snapshot(ccx, None, None) } } @@ -685,20 +683,16 @@ fn create_gas_snapshot( create_dir_all(ccx.state.config.paths.snapshots.clone())?; // TODO: derive from contract name, function name. - let snapshot_group_name = group.clone().or_else(|| Some("default".to_string())); - let snapshot_name = name.clone().or_else(|| Some("default".to_string())); + let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_name = name.as_deref().unwrap_or("default"); let snapshot_path = - ccx.state.config.paths.snapshots.join(format!("{}.json", snapshot_group_name.unwrap())); + ccx.state.config.paths.snapshots.join(format!("{snapshot_group_name}.json")); - let result = if group.is_some() { - let mut snapshot: BTreeMap = - read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(snapshot_name.unwrap(), value); - write_pretty_json_file(&snapshot_path, &snapshot).is_ok() - } else { - write_pretty_json_file(&snapshot_path, &value).is_ok() - }; + let mut snapshot: BTreeMap = + read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); + snapshot.insert(snapshot_name.to_string(), value); + let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); Ok(result.abi_encode()) } @@ -754,15 +748,10 @@ fn stop_gas_snapshot( let snapshot_path = ccx.state.config.paths.snapshots.join(format!("{snapshot_group_name}.json")); - let result = if group.is_some() { - let mut snapshot: BTreeMap = - read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(snapshot_name.to_string(), gas_used.to_string()); - write_pretty_json_file(&snapshot_path, &snapshot).is_ok() - } else { - let snapshot = gas_used.to_string(); - write_pretty_json_file(&snapshot_path, &snapshot).is_ok() - }; + let mut snapshot: BTreeMap = + read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); + snapshot.insert(snapshot_name.to_string(), gas_used.to_string()); + let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); ccx.state.gas_metering.gas_records.retain(|record| { !(record.group == snapshot_group_name && record.name == snapshot_name) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index c91fd48a8a0e..c7e6bb9ff999 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,7 +7,23 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function testSnapshotValue() public { + function testSnapshotValueDefault() public { + string memory file = "snapshots/default.json"; + clear(file); + + uint256 a = 123; + + assertTrue(vm.snapshotValue(a)); + + // Expect: + // { + // "default": "123" + // } + string memory value = vm.readFile(file); + assertEq(value, '{\n "default": "123"\n}'); + } + + function testSnapshotValueWithGroupAndName() public { string memory file = "snapshots/testSnapshotValue.json"; clear(file); From 963fed2689a331972d1b85f5233c0a11ced01474 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 11:52:33 +0000 Subject: [PATCH 23/64] use contract name as group --- crates/cheatcodes/src/config.rs | 6 ++++++ crates/cheatcodes/src/evm.rs | 17 +++++++++++------ crates/chisel/src/executor.rs | 1 + crates/forge/src/multi_runner.rs | 1 + crates/script/src/lib.rs | 1 + testdata/default/cheats/GasSnapshot.t.sol | 2 +- 6 files changed, 21 insertions(+), 7 deletions(-) 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 e90eb3cd9be9..063f0f21bb22 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -674,6 +674,8 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } +// TODO: move to backend? + fn create_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, @@ -682,8 +684,9 @@ fn create_gas_snapshot( ) -> Result { create_dir_all(ccx.state.config.paths.snapshots.clone())?; - // TODO: derive from contract name, function name. - let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_group_name = group.as_deref().unwrap_or( + ccx.state.config.running_contract.as_deref().expect("expected running contract"), + ); let snapshot_name = name.as_deref().unwrap_or("default"); let snapshot_path = @@ -702,8 +705,9 @@ fn start_gas_snapshot( group: Option, name: Option, ) -> Result { - // TODO: derive from contract name, function name. - let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_group_name = group.as_deref().unwrap_or( + ccx.state.config.running_contract.as_deref().expect("expected running contract"), + ); let snapshot_name = name.as_deref().unwrap_or("default"); if ccx @@ -732,8 +736,9 @@ fn stop_gas_snapshot( ) -> Result { create_dir_all(ccx.state.config.paths.snapshots.clone())?; - // TODO: derive from contract name, function name. - let snapshot_group_name = group.as_deref().unwrap_or("default"); + let snapshot_group_name = group.as_deref().unwrap_or( + ccx.state.config.running_contract.as_deref().expect("expected running contract"), + ); let snapshot_name = name.as_deref().unwrap_or("default"); if let Some(record) = ccx 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/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 4df00f7944ee..49e9e7807d41 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/script/src/lib.rs b/crates/script/src/lib.rs index 6af605690e2b..50cd3a44dc1f 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -604,6 +604,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(script_wallets), + Some(target.name), Some(target.version), ) .into(), diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index c7e6bb9ff999..a7362ff7a893 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -8,7 +8,7 @@ contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); function testSnapshotValueDefault() public { - string memory file = "snapshots/default.json"; + string memory file = "snapshots/GasSnapshotTest.json"; clear(file); uint256 a = 123; From d9c9e4f91068307e4e94a5f72d4bef955b0afce6 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 12:56:07 +0000 Subject: [PATCH 24/64] refactor due to race condition issues, now moving to a write-once model that runs once at the end of the test --- crates/anvil/src/eth/api.rs | 4 +- crates/anvil/src/eth/backend/db.rs | 12 +- crates/anvil/src/eth/backend/mem/fork_db.rs | 10 +- .../anvil/src/eth/backend/mem/in_memory_db.rs | 7 +- crates/anvil/src/eth/backend/mem/mod.rs | 8 +- crates/cheatcodes/assets/cheatcodes.json | 74 +-------- crates/cheatcodes/spec/src/vm.rs | 26 +-- crates/cheatcodes/src/evm.rs | 97 ++++------- crates/evm/core/src/backend/mod.rs | 4 +- crates/evm/core/src/fork/database.rs | 8 +- testdata/cheats/Vm.sol | 11 +- testdata/default/cheats/GasSnapshot.t.sol | 151 +----------------- 12 files changed, 81 insertions(+), 331 deletions(-) diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index fbd37a3f2a4e..786fdf208fdc 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -1920,7 +1920,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. @@ -1929,7 +1929,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 cf8f1f5c9ae0..6cfe0e86e0f8 100644 --- a/crates/anvil/src/eth/backend/db.rs +++ b/crates/anvil/src/eth/backend/db.rs @@ -158,13 +158,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: RevertSnapshotAction) -> bool; /// Returns the state root if possible to compute fn maybe_state_root(&self) -> Option { @@ -202,11 +202,11 @@ 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: RevertSnapshotAction) -> 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 a179f50c3b49..84e3637b8bde 100644 --- a/crates/anvil/src/eth/backend/mem/fork_db.rs +++ b/crates/anvil/src/eth/backend/mem/fork_db.rs @@ -71,16 +71,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: RevertSnapshotAction) -> 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 059c00f326bf..f0f0868ed23f 100644 --- a/crates/anvil/src/eth/backend/mem/in_memory_db.rs +++ b/crates/anvil/src/eth/backend/mem/in_memory_db.rs @@ -71,14 +71,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: RevertSnapshotAction) -> 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 12ae96dabd73..3dd529944184 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -681,17 +681,17 @@ impl Backend { /// 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 = { @@ -735,7 +735,7 @@ impl Backend { ..Default::default() }; } - Ok(self.db.write().await.revert(id, RevertSnapshotAction::RevertRemove)) + Ok(self.db.write().await.revert_state_snapshot(id, RevertSnapshotAction::RevertRemove)) } pub fn list_snapshots(&self) -> BTreeMap { diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 3998a3d43f60..e42fdfa09888 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8089,28 +8089,8 @@ { "func": { "id": "snapshotValue_0", - "description": "Snapshot capture an arbitrary numerical value.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", - "declaration": "function snapshotValue(uint256 value) external returns (bool success);", - "visibility": "external", - "mutability": "", - "signature": "snapshotValue(uint256)", - "selector": "0xeaa943bf", - "selectorBytes": [ - 234, - 169, - 67, - 191 - ] - }, - "group": "evm", - "status": "stable", - "safety": "unsafe" - }, - { - "func": { - "id": "snapshotValue_1", "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 returns (bool success);", + "declaration": "function snapshotValue(string calldata name, uint256 value) external;", "visibility": "external", "mutability": "", "signature": "snapshotValue(string,uint256)", @@ -8128,9 +8108,9 @@ }, { "func": { - "id": "snapshotValue_2", + "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 returns (bool success);", + "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external;", "visibility": "external", "mutability": "", "signature": "snapshotValue(string,string,uint256)", @@ -8289,26 +8269,6 @@ { "func": { "id": "startSnapshotGas_0", - "description": "Start a snapshot capture of the current gas usage.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", - "declaration": "function startSnapshotGas() external;", - "visibility": "external", - "mutability": "", - "signature": "startSnapshotGas()", - "selector": "0x939251c0", - "selectorBytes": [ - 147, - 146, - 81, - 192 - ] - }, - "group": "evm", - "status": "stable", - "safety": "unsafe" - }, - { - "func": { - "id": "startSnapshotGas_1", "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", @@ -8328,7 +8288,7 @@ }, { "func": { - "id": "startSnapshotGas_2", + "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", @@ -8469,28 +8429,8 @@ { "func": { "id": "stopSnapshotGas_0", - "description": "Stop the snapshot capture of the current gas usage, capturing the gas used since the start.\nThe name is derived from the function name.\nThe group name is derived from the contract name.", - "declaration": "function stopSnapshotGas() external returns (bool success, 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 (bool success, uint256 gasUsed);", + "declaration": "function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas(string)", @@ -8508,9 +8448,9 @@ }, { "func": { - "id": "stopSnapshotGas_2", + "id": "stopSnapshotGas_1", "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 (bool success, uint256 gasUsed);", + "declaration": "function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);", "visibility": "external", "mutability": "", "signature": "stopSnapshotGas(string,string)", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index eff755ed9edf..9d0f3b920410 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -499,29 +499,17 @@ interface Vm { // ----- Arbitrary Snapshots ----- - /// Snapshot capture an arbitrary numerical value. - /// The name is derived from the function name. - /// The group name is derived from the contract name. - #[cheatcode(group = Evm, safety = Unsafe)] - function snapshotValue(uint256 value) external returns (bool success); - /// 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 returns (bool success); + 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 returns (bool success); + function snapshotValue(string calldata group, string calldata name, uint256 value) external; // -------- Gas Snapshots -------- - /// Start a snapshot capture of the current gas usage. - /// The name is derived from the function name. - /// The group name is derived from the contract name. - #[cheatcode(group = Evm, safety = Unsafe)] - function startSnapshotGas() 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)] @@ -531,20 +519,14 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function startSnapshotGas(string calldata group, string calldata name) external; - /// Stop the snapshot capture of the current gas usage, capturing the gas used since the start. - /// The name is derived from the function name. - /// The group name is derived from the contract name. - #[cheatcode(group = Evm, safety = Unsafe)] - function stopSnapshotGas() external returns (bool success, 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 (bool success, uint256 gasUsed); + 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 (bool success, uint256 gasUsed); + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); // -------- State Snapshots -------- diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 063f0f21bb22..060b1057b826 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -490,65 +490,44 @@ impl Cheatcode for readCallersCall { } impl Cheatcode for snapshotValue_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { value } = self; - create_gas_snapshot(ccx, None, None, value.to_string()) - } -} - -impl Cheatcode for snapshotValue_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; - create_gas_snapshot(ccx, None, Some(name.clone()), value.to_string()) + create_gas_snapshot(ccx, None, name.clone(), value.to_string()) } } -impl Cheatcode for snapshotValue_2Call { +impl Cheatcode for snapshotValue_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name, value } = self; - create_gas_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) + create_gas_snapshot(ccx, Some(group.clone()), name.clone(), value.to_string()) } } impl Cheatcode for startSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self {} = self; - start_gas_snapshot(ccx, None, None) - } -} - -impl Cheatcode for startSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - start_gas_snapshot(ccx, None, Some(name.clone())) + start_gas_snapshot(ccx, None, name.clone()) } } -impl Cheatcode for startSnapshotGas_2Call { +impl Cheatcode for startSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name } = self; - start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + start_gas_snapshot(ccx, Some(group.clone()), name.clone()) } } impl Cheatcode for stopSnapshotGas_0Call { - fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self {} = self; - stop_gas_snapshot(ccx, None, None) - } -} - -impl Cheatcode for stopSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - stop_gas_snapshot(ccx, None, Some(name.clone())) + stop_gas_snapshot(ccx, None, name.clone()) } } -impl Cheatcode for stopSnapshotGas_2Call { +impl Cheatcode for stopSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name } = self; - stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + stop_gas_snapshot(ccx, Some(group.clone()), name.clone()) } } @@ -679,50 +658,46 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add fn create_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: Option, + name: String, value: String, ) -> Result { - create_dir_all(ccx.state.config.paths.snapshots.clone())?; + let snapshot_dir = ccx.state.config.paths.snapshots.clone(); - let snapshot_group_name = group.as_deref().unwrap_or( + let group = group.as_deref().unwrap_or( ccx.state.config.running_contract.as_deref().expect("expected running contract"), ); - let snapshot_name = name.as_deref().unwrap_or("default"); - let snapshot_path = - ccx.state.config.paths.snapshots.join(format!("{snapshot_group_name}.json")); + let snapshot_path = snapshot_dir.join(format!("{group}.json")); + + create_dir_all(snapshot_dir)?; - let mut snapshot: BTreeMap = - read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(snapshot_name.to_string(), value); - let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); + println!("{group} {name} {value} {:?}", snapshot_path); - Ok(result.abi_encode()) + Ok(Default::default()) } fn start_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: Option, + name: String, ) -> Result { - let snapshot_group_name = group.as_deref().unwrap_or( + let group = group.as_deref().unwrap_or( ccx.state.config.running_contract.as_deref().expect("expected running contract"), ); - let snapshot_name = name.as_deref().unwrap_or("default"); if ccx .state .gas_metering .gas_records .iter() - .any(|record| record.group == snapshot_group_name && record.name == snapshot_name) + .any(|record| record.group == group && record.name == name) { - bail!("gas snapshot already active: {snapshot_name} in group: {snapshot_group_name}"); + bail!("gas snapshot already active: {name} in group: {group}"); } ccx.state.gas_metering.gas_records.push(GasRecord { - group: snapshot_group_name.to_string(), - name: snapshot_name.to_string(), + group: group.to_string(), + name: name.to_string(), gas_used: 0, }); @@ -732,39 +707,31 @@ fn start_gas_snapshot( fn stop_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: Option, + name: String, ) -> Result { - create_dir_all(ccx.state.config.paths.snapshots.clone())?; - - let snapshot_group_name = group.as_deref().unwrap_or( + let group = group.as_deref().unwrap_or( ccx.state.config.running_contract.as_deref().expect("expected running contract"), ); - let snapshot_name = name.as_deref().unwrap_or("default"); if let Some(record) = ccx .state .gas_metering .gas_records .iter_mut() - .find(|record| record.group == snapshot_group_name && record.name == snapshot_name) + .find(|record| record.group == group && record.name == name) { let gas_used = record.gas_used; - let snapshot_path = - ccx.state.config.paths.snapshots.join(format!("{snapshot_group_name}.json")); + let snapshot_dir = ccx.state.config.paths.snapshots.clone(); + let snapshot_path = snapshot_dir.join(format!("{group}.json")); - let mut snapshot: BTreeMap = - read_json_file(&snapshot_path).unwrap_or_else(|_| BTreeMap::new()); - snapshot.insert(snapshot_name.to_string(), gas_used.to_string()); - let result = write_pretty_json_file(&snapshot_path, &snapshot).is_ok(); + create_dir_all(snapshot_dir)?; - ccx.state.gas_metering.gas_records.retain(|record| { - !(record.group == snapshot_group_name && record.name == snapshot_name) - }); + println!("{group} {name} {gas_used} in {:?}", snapshot_path); - Ok((result, gas_used).abi_encode_params()) + Ok(gas_used.abi_encode()) } else { - bail!("no gas snapshot was started with the name: {snapshot_name} in group: {snapshot_group_name}"); + bail!("no gas snapshot was started with the name: {name} in group: {group}"); } } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 8595cb4e24b3..6612fef7ed3d 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -965,7 +965,7 @@ 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)) } } @@ -1689,7 +1689,7 @@ impl BackendInner { } /// Reverts the entire fork database - pub fn revert_snapshot( + pub fn revert_state_snapshot( &mut self, id: LocalForkId, fork_id: ForkId, diff --git a/crates/evm/core/src/fork/database.rs b/crates/evm/core/src/fork/database.rs index de6b2a6b9f22..c8acc52c6d0a 100644 --- a/crates/evm/core/src/fork/database.rs +++ b/crates/evm/core/src/fork/database.rs @@ -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: RevertSnapshotAction) -> bool { let snapshot = { self.snapshots().lock().remove_at(id) }; if let Some(snapshot) = snapshot { if action.is_keep() { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 75de65dc6837..08f08c5b8c89 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -400,9 +400,8 @@ interface Vm { function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshotState() external returns (uint256 snapshotId); - function snapshotValue(uint256 value) external returns (bool success); - function snapshotValue(string calldata name, uint256 value) external returns (bool success); - function snapshotValue(string calldata group, string calldata name, uint256 value) external returns (bool success); + 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; @@ -410,7 +409,6 @@ interface Vm { function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; - function startSnapshotGas() external; function startSnapshotGas(string calldata name) external; function startSnapshotGas(string calldata group, string calldata name) external; function startStateDiffRecording() external; @@ -419,9 +417,8 @@ interface Vm { function stopExpectSafeMemory() external; function stopMappingRecording() external; function stopPrank() external; - function stopSnapshotGas() external returns (bool success, uint256 gasUsed); - function stopSnapshotGas(string calldata name) external returns (bool success, uint256 gasUsed); - function stopSnapshotGas(string calldata group, string calldata name) external returns (bool success, 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/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index a7362ff7a893..761dc0dc3e10 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,180 +7,43 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function testSnapshotValueDefault() public { - string memory file = "snapshots/GasSnapshotTest.json"; - clear(file); - - uint256 a = 123; - - assertTrue(vm.snapshotValue(a)); - - // Expect: - // { - // "default": "123" - // } - string memory value = vm.readFile(file); - assertEq(value, '{\n "default": "123"\n}'); - } - - function testSnapshotValueWithGroupAndName() public { - string memory file = "snapshots/testSnapshotValue.json"; - clear(file); - - uint256 a = 123; - - assertTrue(vm.snapshotValue("testSnapshotValue", "a", a)); - - // Expect: - // { - // "a": "123" - // } - string memory value = vm.readFile(file); - assertEq(value, '{\n "a": "123"\n}'); - } - - function testSnapshotValueGroup() public { - string memory file = "snapshots/testSnapshotGroupValue.json"; - clear(file); - + function testSnapshotValue() public { uint256 a = 123; uint256 b = 456; uint256 c = 789; - assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a)); - assertTrue(vm.snapshotValue("testSnapshotGroupValue", "b", b)); - - // Expect: - // { - // "a": "123", - // "b": "456" - // } - assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456"\n}'); - - assertTrue(vm.snapshotValue("testSnapshotGroupValue", "c", c)); - - // Expect: - // { - // "a": "123", - // "b": "456", - // "c": "789" - // } - assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); + vm.snapshotValue("snapshotValueA", a); + vm.snapshotValue("snapshotValueB", b); + vm.snapshotValue("snapshotValueC", c); // Overwrite a uint256 a2 = 321; - assertTrue(vm.snapshotValue("testSnapshotGroupValue", "a", a2)); - - // Expect: - // { - // "a": "321", - // "b": "456", - // "c": "789" - // } - assertEq(vm.readFile(file), '{\n "a": "321",\n "b": "456",\n "c": "789"\n}'); + vm.snapshotValue("snapshotValueA", a2); } function testSnapshotGasSection() public { - string memory file = "snapshots/testSnapshotGasSection.json"; - clear(file); - Flare f = new Flare(); f.run(1); - vm.startSnapshotGas("testSnapshotGasSection", "a"); + vm.startSnapshotGas("testSnapshotGasSection"); f.run(256); // 5_821_576 gas f.run(512); // 11_617_936 gas - (bool success, uint256 gasUsed) = vm.stopSnapshotGas("testSnapshotGasSection", "a"); - assertTrue(success); + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - - // Expect: - // { - // "a": "17439512" - // } - assertEq(vm.readFile(file), '{\n "a": "17439512"\n}'); } function testSnapshotOrdering() public { - string memory file = "snapshots/SnapshotOrdering.json"; - clear(file); - uint256 a = 123; uint256 b = 456; uint256 c = 789; vm.snapshotValue("SnapshotOrdering", "c", c); - - // Expect: - // { - // "c": "789" - // } - assertEq(vm.readFile(file), '{\n "c": "789"\n}'); - vm.snapshotValue("SnapshotOrdering", "a", a); - - // Expect: - // { - // "a": "123", - // "c": "789" - // } - assertEq(vm.readFile(file), '{\n "a": "123",\n "c": "789"\n}'); - vm.snapshotValue("SnapshotOrdering", "b", b); - - // Expect: - // { - // "a": "123", - // "b": "456", - // "c": "789" - // } - assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789"\n}'); - } - - function testSnapshotCombination() public { - string memory file = "snapshots/SnapshotCombination.json"; - clear(file); - - uint256 a = 123; - uint256 b = 456; - uint256 c = 789; - - vm.snapshotValue("SnapshotCombination", "c", c); - vm.snapshotValue("SnapshotCombination", "a", a); - - Flare f = new Flare(); - - f.run(1); - - vm.startSnapshotGas("SnapshotCombination", "z"); - - f.run(256); // 5_821_576 gas - - (bool success, uint256 gasUsed) = vm.stopSnapshotGas("SnapshotCombination", "z"); - assertTrue(success); - assertEq(gasUsed, 5_821_576); - - vm.snapshotValue("SnapshotCombination", "b", b); - - // Expect: - // { - // "a": "123", - // "b": "456", - // "c": "789", - // "z": "5821576" - // } - assertEq(vm.readFile(file), '{\n "a": "123",\n "b": "456",\n "c": "789",\n "z": "5821576"\n}'); - } - - // Remove file if it exists so each test can start with a clean slate. - function clear(string memory name) public { - if (vm.exists(name)) { - vm.removeFile(name); - } } } From 22adf65f03281b9d2b644a2c64651b994db9cfbd Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 13:27:35 +0000 Subject: [PATCH 25/64] rename generic Snapshots -> StateSnapshots --- crates/anvil/src/eth/backend/db.rs | 11 +++++-- crates/anvil/src/eth/backend/mem/fork_db.rs | 4 +-- .../anvil/src/eth/backend/mem/in_memory_db.rs | 4 +-- crates/anvil/src/eth/backend/mem/mod.rs | 4 +-- crates/cheatcodes/src/evm.rs | 8 ++--- crates/evm/core/src/backend/cow.rs | 4 +-- crates/evm/core/src/backend/in_memory_db.rs | 7 +++-- crates/evm/core/src/backend/mod.rs | 29 +++++++++++-------- .../{snapshot.rs => state_snapshot.rs} | 10 +++---- crates/evm/core/src/fork/database.rs | 12 ++++---- crates/evm/core/src/lib.rs | 2 +- .../src/{snapshot.rs => state_snapshot.rs} | 20 ++++++------- 12 files changed, 63 insertions(+), 52 deletions(-) rename crates/evm/core/src/backend/{snapshot.rs => state_snapshot.rs} (90%) rename crates/evm/core/src/{snapshot.rs => state_snapshot.rs} (77%) diff --git a/crates/anvil/src/eth/backend/db.rs b/crates/anvil/src/eth/backend/db.rs index 6cfe0e86e0f8..2a852b109e58 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}, @@ -164,7 +165,7 @@ pub trait Db: /// Reverts a state snapshot /// /// Returns `true` if the snapshot was reverted - fn revert_state_snapshot(&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 { @@ -206,7 +207,11 @@ impl + Send + Sync + Clone + fmt::Debug> D U256::ZERO } - fn revert_state_snapshot(&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 84e3637b8bde..d1b509a505ec 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, }; @@ -75,7 +75,7 @@ impl Db for ForkedDatabase { self.insert_state_snapshot() } - fn revert_state_snapshot(&mut self, id: U256, action: RevertSnapshotAction) -> bool { + fn revert_state_snapshot(&mut self, id: U256, action: RevertStateSnapshotAction) -> bool { self.revert_state_snapshot(id, action) } 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 f0f0868ed23f..1d554e7eea5d 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) { @@ -79,7 +79,7 @@ impl Db for MemDb { } /// Reverts to a previous state snapshot - fn revert_state_snapshot(&mut self, id: U256, action: RevertSnapshotAction) -> bool { + 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 3dd529944184..8da50dc4003a 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -67,7 +67,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, @@ -735,7 +735,7 @@ impl Backend { ..Default::default() }; } - Ok(self.db.write().await.revert_state_snapshot(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/src/evm.rs b/crates/cheatcodes/src/evm.rs index 060b1057b826..c4c4bf9fd49c 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -8,9 +8,9 @@ use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; -use foundry_common::fs::{create_dir_all, read_json_file, write_json_file, write_pretty_json_file}; +use foundry_common::fs::{create_dir_all, 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 revm::{ @@ -545,7 +545,7 @@ impl Cheatcode for revertToStateCall { *snapshotId, &ccx.ecx.journaled_state, &mut ccx.ecx.env, - RevertSnapshotAction::RevertKeep, + RevertStateSnapshotAction::RevertKeep, ) { // we reset the evm's journaled_state to the state of the snapshot previous state ccx.ecx.journaled_state = journaled_state; @@ -564,7 +564,7 @@ impl Cheatcode for revertToStateAndDeleteCall { *snapshotId, &ccx.ecx.journaled_state, &mut ccx.ecx.env, - RevertSnapshotAction::RevertRemove, + RevertStateSnapshotAction::RevertRemove, ) { // we reset the evm's journaled_state to the state of the snapshot previous state ccx.ecx.journaled_state = journaled_state; diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 0796cdb1ec24..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, @@ -120,7 +120,7 @@ impl<'a> DatabaseExt for CowBackend<'a> { id: U256, journaled_state: &JournaledState, current: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option { self.backend_mut(current).revert_state_snapshot(id, journaled_state, current, action) } 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 6612fef7ed3d..23b0587a1178 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, }; @@ -43,8 +43,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; @@ -97,7 +97,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { id: U256, journaled_state: &JournaledState, env: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option; /// Deletes the state snapshot with the given `id`. @@ -556,7 +556,9 @@ impl Backend { } /// Returns all state snapshots created in this backend. - pub fn state_snapshots(&self) -> &Snapshots> { + pub fn state_snapshots( + &self, + ) -> &StateSnapshots> { &self.inner.state_snapshots } @@ -905,7 +907,7 @@ impl Backend { impl DatabaseExt for Backend { fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256 { trace!("create state snapshot"); - let id = self.inner.state_snapshots.insert(BackendSnapshot::new( + let id = self.inner.state_snapshots.insert(BackendStateSnapshot::new( self.create_db_snapshot(), journaled_state.clone(), env.clone(), @@ -919,7 +921,7 @@ impl DatabaseExt for Backend { id: U256, current_state: &JournaledState, current: &mut Env, - action: RevertSnapshotAction, + action: RevertStateSnapshotAction, ) -> Option { trace!(?id, "revert state snapshot"); if let Some(mut snapshot) = self.inner.state_snapshots.remove_at(id) { @@ -942,7 +944,7 @@ impl DatabaseExt for Backend { // 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; @@ -1588,14 +1590,16 @@ 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 state_snapshots: Snapshots>, + /// Contains gas snapshots made over the course of a test suite. + pub gas_snapshots: Vec>, + /// 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 @@ -1792,6 +1796,7 @@ impl Default for BackendInner { issued_local_fork_ids: Default::default(), created_forks: Default::default(), forks: vec![], + gas_snapshots: vec![], state_snapshots: Default::default(), has_state_snapshot_failure: false, caller: None, 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 c8acc52c6d0a..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 } @@ -111,7 +111,7 @@ impl ForkedDatabase { } /// Removes the snapshot from the tracked snapshot and sets it as the current state - pub fn revert_state_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() } } From 6145c3a362098e5fe1507f8763a5ce4fb7b60e18 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 16:41:24 +0000 Subject: [PATCH 26/64] accumulate gas snapshots per test --- crates/anvil/src/eth/backend/mem/mod.rs | 2 +- crates/cheatcodes/src/evm.rs | 44 ++++++------ crates/cheatcodes/src/inspector.rs | 5 ++ crates/evm/core/src/backend/mod.rs | 3 - crates/evm/evm/src/executors/fuzz/mod.rs | 15 ++++- crates/evm/evm/src/executors/fuzz/types.rs | 6 +- crates/evm/fuzz/src/lib.rs | 9 ++- crates/forge/bin/cmd/test/mod.rs | 13 ++++ crates/forge/src/result.rs | 9 ++- testdata/default/cheats/GasSnapshot.t.sol | 78 +++++++++++++++------- 10 files changed, 128 insertions(+), 56 deletions(-) diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 8da50dc4003a..ebb9d922a162 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -678,7 +678,7 @@ 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_state_snapshot(&self) -> U256 { diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index c4c4bf9fd49c..729062530af3 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -8,7 +8,7 @@ use alloy_genesis::{Genesis, GenesisAccount}; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rlp::Decodable; use alloy_sol_types::SolValue; -use foundry_common::fs::{create_dir_all, read_json_file, write_json_file}; +use foundry_common::fs::{read_json_file, write_json_file}; use foundry_evm_core::{ backend::{DatabaseExt, RevertStateSnapshotAction}, constants::{CALLER, CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, TEST_CONTRACT_ADDRESS}, @@ -492,14 +492,14 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; - create_gas_snapshot(ccx, None, name.clone(), value.to_string()) + create_value_snapshot(ccx, None, name.clone(), value.to_string()) } } impl Cheatcode for snapshotValue_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name, value } = self; - create_gas_snapshot(ccx, Some(group.clone()), name.clone(), value.to_string()) + create_value_snapshot(ccx, Some(group.clone()), name.clone(), value.to_string()) } } @@ -653,25 +653,22 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } -// TODO: move to backend? - -fn create_gas_snapshot( +fn create_value_snapshot( ccx: &mut CheatsCtxt, group: Option, name: String, value: String, ) -> Result { - let snapshot_dir = ccx.state.config.paths.snapshots.clone(); - - let group = group.as_deref().unwrap_or( - ccx.state.config.running_contract.as_deref().expect("expected running contract"), - ); + let cheatcodes = ccx.state.clone(); + let group = group + .as_deref() + .unwrap_or(cheatcodes.config.running_contract.as_ref().expect("expected running contract")); - let snapshot_path = snapshot_dir.join(format!("{group}.json")); - - create_dir_all(snapshot_dir)?; - - println!("{group} {name} {value} {:?}", snapshot_path); + ccx.state + .gas_snapshots + .entry(group.to_string()) + .or_default() + .insert(name.clone(), value.clone()); Ok(Default::default()) } @@ -720,16 +717,15 @@ fn stop_gas_snapshot( .iter_mut() .find(|record| record.group == group && record.name == name) { - let gas_used = record.gas_used; - - let snapshot_dir = ccx.state.config.paths.snapshots.clone(); - let snapshot_path = snapshot_dir.join(format!("{group}.json")); - - create_dir_all(snapshot_dir)?; + let value = record.gas_used; - println!("{group} {name} {gas_used} in {:?}", snapshot_path); + ccx.state + .gas_snapshots + .entry(group.to_string()) + .or_default() + .insert(name.clone(), value.to_string()); - Ok(gas_used.abi_encode()) + Ok(value.abi_encode()) } else { bail!("no gas snapshot was started with the name: {name} in group: {group}"); } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 03de34bf5764..966bd055a347 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -351,6 +351,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>, @@ -401,6 +405,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(), diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 23b0587a1178..2731da1410f3 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -1596,8 +1596,6 @@ pub struct BackendInner { /// Holds all created fork databases // Note: data is stored in an `Option` so we can remove it without reshuffling pub forks: Vec>, - /// Contains gas snapshots made over the course of a test suite. - pub gas_snapshots: Vec>, /// 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 @@ -1796,7 +1794,6 @@ impl Default for BackendInner { issued_local_fork_ids: Default::default(), created_forks: Default::default(), forks: vec![], - gas_snapshots: vec![], state_snapshots: Default::default(), has_state_snapshot_failure: false, caller: None, diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 7fa5498bfcab..bf8a8308c81c 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -14,7 +14,7 @@ use foundry_evm_fuzz::{ use foundry_evm_traces::SparsedTraceArena; use indicatif::ProgressBar; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use std::cell::RefCell; +use std::{cell::RefCell, collections::BTreeMap}; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; @@ -36,6 +36,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>, } /// Wrapper around an [`Executor`] which provides fuzzing support using [`proptest`]. @@ -116,6 +118,8 @@ impl FuzzedExecutor { if show_logs { data.logs.extend(case.logs); } + // TODO: conditionally collect gas snapshots + data.gas_snapshots.extend(case.gas_snapshots); // Collect and merge coverage if `forge snapshot` context. match &mut data.coverage { Some(prev) => prev.merge(case.coverage.unwrap()), @@ -153,6 +157,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, @@ -165,6 +171,7 @@ impl FuzzedExecutor { breakpoints: last_run_breakpoints, gas_report_traces: traces.into_iter().map(|a| a.arena).collect(), coverage: fuzz_result.coverage, + gas_snapshots, }; match run_result { @@ -224,6 +231,11 @@ impl FuzzedExecutor { .as_ref() .map_or_else(Default::default, |cheats| cheats.breakpoints.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 { @@ -232,6 +244,7 @@ impl FuzzedExecutor { coverage: call.coverage, breakpoints, logs: call.logs, + gas_snapshots, })) } else { Ok(FuzzOutcome::CounterExample(CounterExampleOutcome { diff --git a/crates/evm/evm/src/executors/fuzz/types.rs b/crates/evm/evm/src/executors/fuzz/types.rs index ac7c143739bc..b35bf05bebc0 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; @@ -17,8 +19,10 @@ pub struct CaseOutcome { pub coverage: Option, /// Breakpoints char pc map pub breakpoints: Breakpoints, - /// logs of a single fuzz test case + /// Logs of a single fuzz test case pub logs: Vec, + /// Gas snapshots of a single fuzz test case + pub gas_snapshots: BTreeMap>, } /// Returned by a single fuzz when a counterexample has been discovered diff --git a/crates/evm/fuzz/src/lib.rs b/crates/evm/fuzz/src/lib.rs index 99232443b9cd..1beec7f20de5 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}; @@ -181,6 +185,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>, } impl FuzzTestResult { diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index ec582f298103..3a750046d4dd 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -463,6 +463,9 @@ impl TestArgs { )?); } + // Create the gas snapshot collector. + let mut gas_snapshots = BTreeMap::>::new(); + if self.decode_internal.is_some() { let sources = ContractSources::from_project_output(output, &config.root, Some(&libraries))?; @@ -579,8 +582,18 @@ impl TestArgs { } } } + + // Collect gas snapshots. + for (group, new_snapshots) in result.gas_snapshots.iter() { + gas_snapshots + .entry(group.clone()) + .or_insert_with(BTreeMap::new) + .extend(new_snapshots.clone()); + } } + println!("Gas snapshots: {:?}", gas_snapshots); + // Print suite summary. shell::println(suite_result.summary())?; diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index e7953575f3b5..dad73bac26ad 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -377,6 +377,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>, } impl fmt::Display for TestResult { @@ -477,9 +480,12 @@ impl TestResult { false => TestStatus::Failure, }; self.reason = reason; - self.breakpoints = raw_call_result.cheatcodes.map(|c| c.breakpoints).unwrap_or_default(); + self.breakpoints = + raw_call_result.cheatcodes.clone().map(|c| c.breakpoints).unwrap_or_default(); self.duration = Duration::default(); self.gas_report_traces = Vec::new(); + self.gas_snapshots = + raw_call_result.cheatcodes.map(|c| c.gas_snapshots).unwrap_or_default(); self } @@ -508,6 +514,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 } diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 761dc0dc3e10..4ea9afa62cdb 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,44 +7,74 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function testSnapshotValue() public { + // function testFuzzSnapshotValue1(string memory a, uint256 b) public { + // vm.snapshotValue(a, b); + // } + + function testSnapshotValue1() public { uint256 a = 123; uint256 b = 456; uint256 c = 789; - vm.snapshotValue("snapshotValueA", a); - vm.snapshotValue("snapshotValueB", b); - vm.snapshotValue("snapshotValueC", c); + vm.snapshotValue("a", a); + vm.snapshotValue("b", b); + vm.snapshotValue("c", c); + } - // Overwrite a - uint256 a2 = 321; + function testSnapshotValue2() public { + uint256 d = 123; + uint256 e = 456; + uint256 f = 789; - vm.snapshotValue("snapshotValueA", a2); + vm.snapshotValue("d", d); + vm.snapshotValue("e", e); + vm.snapshotValue("f", f); } - function testSnapshotGasSection() public { - Flare f = new Flare(); + // function testSnapshotValue2() public { + // uint256 a = 123; + // uint256 b = 456; + // uint256 c = 789; - f.run(1); + // vm.snapshotValue("snapshotValue2A", a); + // vm.snapshotValue("snapshotValue2B", b); + // vm.snapshotValue("snapshotValue2C", c); - vm.startSnapshotGas("testSnapshotGasSection"); + // // Overwrite a + // uint256 a2 = 321; - f.run(256); // 5_821_576 gas - f.run(512); // 11_617_936 gas + // vm.snapshotValue("snapshotValue2A", a2); + // } - uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); - assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - } + // function testSnapshotValue3() public { + // vm.snapshotValue("snapshotValue3A", 1); + // vm.snapshotValue("snapshotValue3B", 2); + // vm.snapshotValue("snapshotValue3C", 3); + // } - function testSnapshotOrdering() public { - uint256 a = 123; - uint256 b = 456; - uint256 c = 789; + // function testSnapshotGasSection() public { + // Flare f = new Flare(); - vm.snapshotValue("SnapshotOrdering", "c", c); - vm.snapshotValue("SnapshotOrdering", "a", a); - vm.snapshotValue("SnapshotOrdering", "b", b); - } + // f.run(1); + + // vm.startSnapshotGas("testSnapshotGasSection"); + + // f.run(256); // 5_821_576 gas + // f.run(512); // 11_617_936 gas + + // uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); + // assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + // } + + // function testSnapshotOrdering() public { + // uint256 a = 123; + // uint256 b = 456; + // uint256 c = 789; + + // vm.snapshotValue("SnapshotOrdering", "c", c); + // vm.snapshotValue("SnapshotOrdering", "a", a); + // vm.snapshotValue("SnapshotOrdering", "b", b); + // } } contract Flare { From f93be6cb7a81ffa7d6dd6b6488611c113b32eebb Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 17:06:47 +0000 Subject: [PATCH 27/64] write to disk once, at the end --- crates/config/src/lib.rs | 6 +-- crates/evm/evm/src/executors/fuzz/mod.rs | 11 +++- crates/forge/bin/cmd/test/mod.rs | 24 ++++++++- testdata/default/cheats/GasSnapshot.t.sol | 66 ++++++++++------------- 4 files changed, 62 insertions(+), 45 deletions(-) diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 5446c3786142..33733907bcf5 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -177,7 +177,7 @@ pub struct Config { /// where the cache is stored if enabled pub cache_path: PathBuf, /// where the snapshots are stored - pub snapshots: PathBuf, + pub snapshot_path: PathBuf, /// where the broadcast logs are stored pub broadcast: PathBuf, /// additional solc allow paths for `--allow-paths` @@ -712,7 +712,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); + self.snapshot_path = p(&root, &self.snapshot_path); if let Some(build_info_path) = self.build_info_path { self.build_info_path = Some(p(&root, &build_info_path)); @@ -2061,7 +2061,7 @@ impl Default for Config { cache: true, cache_path: "cache".into(), broadcast: "broadcast".into(), - snapshots: "snapshots".into(), + snapshot_path: "snapshots".into(), allow_paths: vec![], include_paths: vec![], force: false, diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index bf8a8308c81c..6c2ff08464d8 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -118,8 +118,15 @@ impl FuzzedExecutor { if show_logs { data.logs.extend(case.logs); } - // TODO: conditionally collect gas snapshots - data.gas_snapshots.extend(case.gas_snapshots); + + // Collect gas snapshots. + for (group, new_snapshots) in case.gas_snapshots.iter() { + data.gas_snapshots + .entry(group.clone()) + .or_insert_with(BTreeMap::new) + .extend(new_snapshots.clone()); + } + // Collect and merge coverage if `forge snapshot` context. match &mut data.coverage { Some(prev) => prev.merge(case.coverage.unwrap()), diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 3a750046d4dd..f067d987c65b 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -19,7 +19,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, remove_dir_all, write_pretty_json_file}, + shell, +}; use foundry_compilers::{ artifacts::output_selection::OutputSelection, compilers::{multi::MultiCompilerLanguage, CompilerSettings, Language}, @@ -592,7 +597,22 @@ impl TestArgs { } } - println!("Gas snapshots: {:?}", gas_snapshots); + // Remove any existing gas snapshots. + remove_dir_all(&config.snapshot_path).unwrap_or_else(|e| { + eprintln!("Failed to remove gas snapshots: {e}"); + }); + + // Create `snapshots` directory if it doesn't exist. + create_dir_all(&config.snapshot_path)?; + + // Write gas snapshots to disk per group. + gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + write_pretty_json_file( + &config.snapshot_path.join(format!("{group}.json")), + &snapshots, + ) + .unwrap_or_else(|e| eprintln!("Failed to write gas snapshots: {e}")); + }); // Print suite summary. shell::println(suite_result.summary())?; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 4ea9afa62cdb..cb72a8fd2e19 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,11 +7,12 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + // /// forge-config: default.fuzz.runs = 10 // function testFuzzSnapshotValue1(string memory a, uint256 b) public { // vm.snapshotValue(a, b); // } - function testSnapshotValue1() public { + function testSnapshotValueDefaultGroup1() public { uint256 a = 123; uint256 b = 456; uint256 c = 789; @@ -21,7 +22,7 @@ contract GasSnapshotTest is DSTest { vm.snapshotValue("c", c); } - function testSnapshotValue2() public { + function testSnapshotValueDefaultGroup2() public { uint256 d = 123; uint256 e = 456; uint256 f = 789; @@ -31,50 +32,39 @@ contract GasSnapshotTest is DSTest { vm.snapshotValue("f", f); } - // function testSnapshotValue2() public { - // uint256 a = 123; - // uint256 b = 456; - // uint256 c = 789; + function testSnapshotValueCustomGroup1() public { + uint256 o = 123; + uint256 i = 456; + uint256 q = 789; - // vm.snapshotValue("snapshotValue2A", a); - // vm.snapshotValue("snapshotValue2B", b); - // vm.snapshotValue("snapshotValue2C", c); - - // // Overwrite a - // uint256 a2 = 321; - - // vm.snapshotValue("snapshotValue2A", a2); - // } - - // function testSnapshotValue3() public { - // vm.snapshotValue("snapshotValue3A", 1); - // vm.snapshotValue("snapshotValue3B", 2); - // vm.snapshotValue("snapshotValue3C", 3); - // } + vm.snapshotValue("CustomGroup", "q", q); + vm.snapshotValue("CustomGroup", "i", i); + vm.snapshotValue("CustomGroup", "o", o); + } - // function testSnapshotGasSection() public { - // Flare f = new Flare(); + function testSnapshotValueCustomGroup2() public { + uint256 x = 123; + uint256 e = 456; + uint256 z = 789; - // f.run(1); + vm.snapshotValue("CustomGroup", "z", z); + vm.snapshotValue("CustomGroup", "x", x); + vm.snapshotValue("CustomGroup", "e", e); + } - // vm.startSnapshotGas("testSnapshotGasSection"); + function testSnapshotGasSection() public { + Flare f = new Flare(); - // f.run(256); // 5_821_576 gas - // f.run(512); // 11_617_936 gas + f.run(1); - // uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); - // assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas - // } + vm.startSnapshotGas("testSnapshotGasSection"); - // function testSnapshotOrdering() public { - // uint256 a = 123; - // uint256 b = 456; - // uint256 c = 789; + f.run(256); // 5_821_576 gas + f.run(512); // 11_617_936 gas - // vm.snapshotValue("SnapshotOrdering", "c", c); - // vm.snapshotValue("SnapshotOrdering", "a", a); - // vm.snapshotValue("SnapshotOrdering", "b", b); - // } + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); + assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + } } contract Flare { From 58b5acf45dce1ac89d7a95b3c63fd55012d20807 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 29 Aug 2024 17:38:51 +0000 Subject: [PATCH 28/64] fix ci issues --- crates/cheatcodes/src/evm.rs | 8 ++------ crates/evm/core/src/backend/mod.rs | 4 ++-- crates/evm/evm/src/executors/fuzz/mod.rs | 2 +- crates/forge/bin/cmd/test/mod.rs | 7 ++----- crates/forge/tests/cli/config.rs | 2 +- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 729062530af3..66c1cdf7f055 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -664,11 +664,7 @@ fn create_value_snapshot( .as_deref() .unwrap_or(cheatcodes.config.running_contract.as_ref().expect("expected running contract")); - ccx.state - .gas_snapshots - .entry(group.to_string()) - .or_default() - .insert(name.clone(), value.clone()); + ccx.state.gas_snapshots.entry(group.to_string()).or_default().insert(name, value); Ok(Default::default()) } @@ -694,7 +690,7 @@ fn start_gas_snapshot( ccx.state.gas_metering.gas_records.push(GasRecord { group: group.to_string(), - name: name.to_string(), + name, gas_used: 0, }); diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 2731da1410f3..72364bed7ccc 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -77,7 +77,7 @@ pub trait DatabaseExt: Database + DatabaseCommit { /// /// A snapshot is associated with a new unique id that's created for the snapshot. /// Snapshots can be reverted: [`DatabaseExt::revert_state_snapshot`], however, depending on the - /// [RevertSnapshotAction], it will keep the snapshot alive or delete it. + /// [RevertStateSnapshotAction], it will keep the snapshot alive or delete it. fn snapshot_state(&mut self, journaled_state: &JournaledState, env: &Env) -> U256; /// Reverts the state snapshot if it exists. @@ -91,7 +91,7 @@ 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. + /// Depending on [RevertStateSnapshotAction] it will keep the snapshot alive or delete it. fn revert_state_snapshot( &mut self, id: U256, diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index 6c2ff08464d8..41ccb7b03a69 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -123,7 +123,7 @@ impl FuzzedExecutor { for (group, new_snapshots) in case.gas_snapshots.iter() { data.gas_snapshots .entry(group.clone()) - .or_insert_with(BTreeMap::new) + .or_default() .extend(new_snapshots.clone()); } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index f067d987c65b..62d91d5eda5f 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -588,12 +588,9 @@ impl TestArgs { } } - // Collect gas snapshots. + // Collect and merge gas snapshots. for (group, new_snapshots) in result.gas_snapshots.iter() { - gas_snapshots - .entry(group.clone()) - .or_insert_with(BTreeMap::new) - .extend(new_snapshots.clone()); + gas_snapshots.entry(group.clone()).or_default().extend(new_snapshots.clone()); } } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 4ce5b30f9f40..f1b642f3940e 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -37,7 +37,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { libs: vec!["lib-test".into()], cache: true, cache_path: "test-cache".into(), - snapshots: "test-snapshot".into(), + snapshot_path: "test-snapshot".into(), broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, From 47baa409d22e1dccc4368ceb6ff966b14628b0a1 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 30 Aug 2024 15:45:32 +0000 Subject: [PATCH 29/64] only attempt remove if exists --- crates/forge/bin/cmd/test/mod.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 62d91d5eda5f..553cad243701 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -595,9 +595,10 @@ impl TestArgs { } // Remove any existing gas snapshots. - remove_dir_all(&config.snapshot_path).unwrap_or_else(|e| { - eprintln!("Failed to remove gas snapshots: {e}"); - }); + if config.snapshot_path.exists() { + remove_dir_all(&config.snapshot_path) + .expect("Failed to remove gas snapshots directory"); + } // Create `snapshots` directory if it doesn't exist. create_dir_all(&config.snapshot_path)?; @@ -608,7 +609,7 @@ impl TestArgs { &config.snapshot_path.join(format!("{group}.json")), &snapshots, ) - .unwrap_or_else(|e| eprintln!("Failed to write gas snapshots: {e}")); + .expect("Failed to write gas snapshots to disk"); }); // Print suite summary. From 2aa51e4d4799bf731fe1bc2299f6e326d50e6331 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 30 Aug 2024 16:23:21 +0000 Subject: [PATCH 30/64] add snapshotGasLastCall shorthand --- testdata/cheats/Vm.sol | 1 + testdata/default/cheats/GasSnapshot.t.sol | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 08f08c5b8c89..8b5aff914b3c 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -399,6 +399,7 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; + function snapshotGasLastCall(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; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index cb72a8fd2e19..d91b7024c37c 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -65,6 +65,14 @@ contract GasSnapshotTest is DSTest { uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas } + + function testSnapshotLastCallGas() public { + Flare f = new Flare(); + + f.run(1); + + vm.snapshotGasLastCall("testSnapshotLastCallGas"); + } } contract Flare { From 06121cd16a9ffb97a51957f58898abb4ec95e834 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 30 Aug 2024 16:23:45 +0000 Subject: [PATCH 31/64] add snapshotGasLastCall --- crates/cheatcodes/assets/cheatcodes.json | 20 ++++++++++++++++++++ crates/cheatcodes/spec/src/vm.rs | 4 ++++ crates/cheatcodes/src/evm.rs | 10 ++++++++++ 3 files changed, 34 insertions(+) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index e42fdfa09888..3c441c387ea9 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8066,6 +8066,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "snapshotGasLastCall", + "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": "snapshotState", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 9d0f3b920410..f0911e5e3dc4 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -528,6 +528,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); + /// Snapshot capture the gas usage of the last call by name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata name) external; + // -------- State Snapshots -------- /// Snapshot the current state of the evm. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 66c1cdf7f055..62651f753c2f 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -531,6 +531,16 @@ impl Cheatcode for stopSnapshotGas_1Call { } } +impl Cheatcode for snapshotGasLastCallCall { + 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"); + }; + create_value_snapshot(ccx, None, name.clone(), last_call_gas.gasTotalUsed.to_string()) + } +} + impl Cheatcode for snapshotStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; From f72fbca3607a519db017501ddb6b22e879b867f4 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 30 Aug 2024 16:31:50 +0000 Subject: [PATCH 32/64] snapshotGasLastCall -> snapshotGas --- crates/cheatcodes/assets/cheatcodes.json | 16 ++++++++-------- crates/cheatcodes/spec/src/vm.rs | 8 ++++---- crates/cheatcodes/src/evm.rs | 2 +- testdata/cheats/Vm.sol | 2 +- testdata/default/cheats/GasSnapshot.t.sol | 4 ++-- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 3c441c387ea9..8eb177f4eec8 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8068,18 +8068,18 @@ }, { "func": { - "id": "snapshotGasLastCall", + "id": "snapshotGas", "description": "Snapshot capture the gas usage of the last call by name.", - "declaration": "function snapshotGasLastCall(string calldata name) external;", + "declaration": "function snapshotGas(string calldata name) external;", "visibility": "external", "mutability": "", - "signature": "snapshotGasLastCall(string)", - "selector": "0xdd9fca12", + "signature": "snapshotGas(string)", + "selector": "0x245b926b", "selectorBytes": [ - 221, - 159, - 202, - 18 + 36, + 91, + 146, + 107 ] }, "group": "evm", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index f0911e5e3dc4..802890645568 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -510,6 +510,10 @@ interface Vm { // -------- Gas Snapshots -------- + /// Snapshot capture the gas usage of the last call by name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGas(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)] @@ -528,10 +532,6 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); - /// Snapshot capture the gas usage of the last call by name. - #[cheatcode(group = Evm, safety = Unsafe)] - function snapshotGasLastCall(string calldata name) external; - // -------- State Snapshots -------- /// Snapshot the current state of the evm. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 62651f753c2f..3fd1682992c9 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -531,7 +531,7 @@ impl Cheatcode for stopSnapshotGas_1Call { } } -impl Cheatcode for snapshotGasLastCallCall { +impl Cheatcode for snapshotGasCall { 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 { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 8b5aff914b3c..29636e3c383e 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -399,7 +399,7 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; - function snapshotGasLastCall(string calldata name) external; + function snapshotGas(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; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index d91b7024c37c..5ff012425fa8 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -66,12 +66,12 @@ contract GasSnapshotTest is DSTest { assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas } - function testSnapshotLastCallGas() public { + function testSnapshotGas() public { Flare f = new Flare(); f.run(1); - vm.snapshotGasLastCall("testSnapshotLastCallGas"); + vm.snapshotGas("testSnapshotLastCallGas"); } } From 0dd876286cbf64700851e4c95aac60b1ed79dbd7 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 10:33:40 +0000 Subject: [PATCH 33/64] no need to add path to compilers --- Cargo.lock | 15 ++++++++++----- Cargo.toml | 1 - crates/config/src/lib.rs | 6 +++--- crates/forge/bin/cmd/test/mod.rs | 13 +++++-------- crates/forge/tests/cli/config.rs | 2 +- 5 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05b1e46c62ab..73194f995427 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3693,7 +3693,8 @@ dependencies = [ [[package]] name = "foundry-compilers" version = "0.10.3" -source = "git+https://github.com/foundry-rs/compilers.git?rev=ee116703c7f572289e1acc5f638e45c4a8c840f0#ee116703c7f572289e1acc5f638e45c4a8c840f0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eaa24a47bb84e1db38c84f03e8c90ca81050bd20beac8bdc99aae8afd0b8784" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3730,7 +3731,8 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" version = "0.10.3" -source = "git+https://github.com/foundry-rs/compilers.git?rev=ee116703c7f572289e1acc5f638e45c4a8c840f0#ee116703c7f572289e1acc5f638e45c4a8c840f0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3588ee6a986f89040d1158fb90459731580b404fb72b8c6c832c0ddbc95fed58" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3739,7 +3741,8 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" version = "0.10.3" -source = "git+https://github.com/foundry-rs/compilers.git?rev=ee116703c7f572289e1acc5f638e45c4a8c840f0#ee116703c7f572289e1acc5f638e45c4a8c840f0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a149c5e8c326c7bae8f73cacb28c637f4bc2e535f950eec10348494990e9636f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3762,7 +3765,8 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" version = "0.10.3" -source = "git+https://github.com/foundry-rs/compilers.git?rev=ee116703c7f572289e1acc5f638e45c4a8c840f0#ee116703c7f572289e1acc5f638e45c4a8c840f0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8645c9e7c070c81bf8c90f456416953234334f097b67445c773af98df74e27b0" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3776,7 +3780,8 @@ dependencies = [ [[package]] name = "foundry-compilers-core" version = "0.10.3" -source = "git+https://github.com/foundry-rs/compilers.git?rev=ee116703c7f572289e1acc5f638e45c4a8c840f0#ee116703c7f572289e1acc5f638e45c4a8c840f0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66492aeb708f3d142c078457dba5f52b04ca5031012d48903a0bcb37d205d595" dependencies = [ "alloy-primitives", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 5f5dd79f0d2c..c6c8aee07db8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -263,7 +263,6 @@ proptest = "1" comfy-table = "7" [patch.crates-io] -foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "ee116703c7f572289e1acc5f638e45c4a8c840f0" } alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 33733907bcf5..5446c3786142 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -177,7 +177,7 @@ pub struct Config { /// where the cache is stored if enabled pub cache_path: PathBuf, /// where the snapshots are stored - pub snapshot_path: PathBuf, + pub snapshots: PathBuf, /// where the broadcast logs are stored pub broadcast: PathBuf, /// additional solc allow paths for `--allow-paths` @@ -712,7 +712,7 @@ impl Config { self.out = p(&root, &self.out); self.broadcast = p(&root, &self.broadcast); self.cache_path = p(&root, &self.cache_path); - self.snapshot_path = p(&root, &self.snapshot_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)); @@ -2061,7 +2061,7 @@ impl Default for Config { cache: true, cache_path: "cache".into(), broadcast: "broadcast".into(), - snapshot_path: "snapshots".into(), + snapshots: "snapshots".into(), allow_paths: vec![], include_paths: vec![], force: false, diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 553cad243701..d777a6bf2b08 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -595,21 +595,18 @@ impl TestArgs { } // Remove any existing gas snapshots. - if config.snapshot_path.exists() { - remove_dir_all(&config.snapshot_path) + 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.snapshot_path)?; + 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.snapshot_path.join(format!("{group}.json")), - &snapshots, - ) - .expect("Failed to write gas snapshots to disk"); + write_pretty_json_file(&config.snapshots.join(format!("{group}.json")), &snapshots) + .expect("Failed to write gas snapshots to disk"); }); // Print suite summary. diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index f1b642f3940e..5f810d328b07 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -37,7 +37,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { libs: vec!["lib-test".into()], cache: true, cache_path: "test-cache".into(), - snapshot_path: "test-snapshot".into(), + snapshots: "snapshots".into(), broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, From f70ec05b0bdc6f4cbea1488abf1bd8e56a59debd Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 11:58:48 +0000 Subject: [PATCH 34/64] add FORGE_SNAPSHOT_CHECK --- crates/forge/bin/cmd/test/mod.rs | 51 +++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index a31f69dfd4fb..914e5d30549d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -22,7 +22,7 @@ use foundry_cli::{ use foundry_common::{ compile::ProjectCompiler, evm::EvmArgs, - fs::{self, create_dir_all, remove_dir_all, write_pretty_json_file}, + fs::{self, create_dir_all, read_json_file, remove_dir_all, write_pretty_json_file}, shell, }; use foundry_compilers::{ @@ -599,6 +599,55 @@ impl TestArgs { } } + // 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 gas 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 gas snapshots:").red().bold() + ); + + for (key, (old_value, new_value)) in &diff { + println!( + "{}", + format!("- [{}] {} → {}", key, old_value, new_value).red() + ); + } + + found = true; + } + + found + }, + ); + + if differences_found { + println!(); + eyre::bail!("Gas snapshots differ from previous run"); + } + } + // Remove any existing gas snapshots. if config.snapshots.exists() { remove_dir_all(&config.snapshots) From efac9f9db39be4dcc428a65bcf5702225286c3ad Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 12:00:43 +0000 Subject: [PATCH 35/64] fix naming --- crates/forge/bin/cmd/test/mod.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 914e5d30549d..0f6eea4a71ee 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -607,7 +607,7 @@ impl TestArgs { |mut found, (group, snapshots)| { let previous_snapshots: BTreeMap = read_json_file(&config.snapshots.join(format!("{group}.json"))) - .expect("Failed to read gas snapshots from disk"); + .expect("Failed to read snapshots from disk"); let diff: BTreeMap<_, _> = snapshots .iter() @@ -625,13 +625,14 @@ impl TestArgs { if !diff.is_empty() { println!( "{}", - format!("\n[{group}] Failed to match gas snapshots:").red().bold() + format!("\n[{group}] Failed to match snapshots:").red().bold() ); - for (key, (old_value, new_value)) in &diff { + for (key, (previous_snapshot, snapshot)) in &diff { println!( "{}", - format!("- [{}] {} → {}", key, old_value, new_value).red() + format!("- [{}] {} → {}", key, previous_snapshot, snapshot) + .red() ); } @@ -644,7 +645,7 @@ impl TestArgs { if differences_found { println!(); - eyre::bail!("Gas snapshots differ from previous run"); + eyre::bail!("Snapshots differ from previous run"); } } From 71a8e8a87398a28497bc9094d075f69f9a0896c4 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:18:46 +0000 Subject: [PATCH 36/64] prepare for derived function name --- crates/cheatcodes/assets/cheatcodes.json | 22 ++++++++++- crates/cheatcodes/spec/src/vm.rs | 5 +++ crates/cheatcodes/src/evm.rs | 45 ++++++++++++++++------- crates/forge/bin/cmd/test/mod.rs | 5 +-- testdata/cheats/Vm.sol | 1 + testdata/default/cheats/GasSnapshot.t.sol | 45 ++++++++++++++++++++--- 6 files changed, 101 insertions(+), 22 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 8eb177f4eec8..61ce4206946e 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8068,7 +8068,7 @@ }, { "func": { - "id": "snapshotGas", + "id": "snapshotGas_0", "description": "Snapshot capture the gas usage of the last call by name.", "declaration": "function snapshotGas(string calldata name) external;", "visibility": "external", @@ -8086,6 +8086,26 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "snapshotGas_1", + "description": "Snapshot capture the gas usage of the last call by name in a group.", + "declaration": "function snapshotGas(string calldata group, string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotGas(string,string)", + "selector": "0x700bd230", + "selectorBytes": [ + 112, + 11, + 210, + 48 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "snapshotState", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 802890645568..44f79b0d31a1 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -510,10 +510,15 @@ interface Vm { // -------- Gas Snapshots -------- + /// Snapshot capture the gas usage of the last call by name. #[cheatcode(group = Evm, safety = Unsafe)] function snapshotGas(string calldata name) external; + /// Snapshot capture the gas usage of the last call by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGas(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)] diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 3fd1682992c9..87f88fce5a70 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -492,52 +492,67 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; - create_value_snapshot(ccx, None, name.clone(), value.to_string()) + 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; - create_value_snapshot(ccx, Some(group.clone()), name.clone(), value.to_string()) + create_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } impl Cheatcode for startSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - start_gas_snapshot(ccx, None, name.clone()) + 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; - start_gas_snapshot(ccx, Some(group.clone()), name.clone()) + 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 { name } = self; - stop_gas_snapshot(ccx, None, name.clone()) + stop_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name } = self; - stop_gas_snapshot(ccx, Some(group.clone()), name.clone()) + stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } -impl Cheatcode for snapshotGasCall { +impl Cheatcode for snapshotGas_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"); }; - create_value_snapshot(ccx, None, name.clone(), last_call_gas.gasTotalUsed.to_string()) + create_value_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed.to_string()) + } +} + +impl Cheatcode for snapshotGas_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"); + }; + create_value_snapshot( + ccx, + Some(group.clone()), + Some(name.clone()), + last_call_gas.gasTotalUsed.to_string(), + ) } } @@ -666,15 +681,17 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add fn create_value_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: String, + 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")); + .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.to_string()).or_default().insert(name, value); + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value); Ok(Default::default()) } @@ -682,11 +699,12 @@ fn create_value_snapshot( fn start_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: String, + name: Option, ) -> Result { 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(); if ccx .state @@ -710,11 +728,12 @@ fn start_gas_snapshot( fn stop_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, - name: String, + name: Option, ) -> Result { 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(); if let Some(record) = ccx .state diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 0f6eea4a71ee..148aba72b817 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -473,9 +473,6 @@ impl TestArgs { )?); } - // Create the gas snapshot collector. - let mut gas_snapshots = BTreeMap::>::new(); - if self.decode_internal.is_some() { let sources = ContractSources::from_project_output(output, &config.root, Some(&libraries))?; @@ -487,6 +484,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; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 29636e3c383e..d628fe1e92c5 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -400,6 +400,7 @@ interface Vm { function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshotGas(string calldata name) external; + function snapshotGas(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; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 5ff012425fa8..b8e5c76d891d 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -12,6 +12,7 @@ contract GasSnapshotTest is DSTest { // vm.snapshotValue(a, b); // } + // Writes to `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup1() public { uint256 a = 123; uint256 b = 456; @@ -22,6 +23,7 @@ contract GasSnapshotTest is DSTest { vm.snapshotValue("c", c); } + // Writes to same `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup2() public { uint256 d = 123; uint256 e = 456; @@ -32,6 +34,8 @@ contract GasSnapshotTest is DSTest { 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; @@ -42,6 +46,8 @@ contract GasSnapshotTest is DSTest { 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; @@ -52,26 +58,55 @@ contract GasSnapshotTest is DSTest { vm.snapshotValue("CustomGroup", "e", e); } - function testSnapshotGasSection() public { + // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionName() public { Flare f = new Flare(); f.run(1); - vm.startSnapshotGas("testSnapshotGasSection"); + vm.startSnapshotGas("testSnapshotGasSectionName"); f.run(256); // 5_821_576 gas f.run(512); // 11_617_936 gas - uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSection"); + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas } - function testSnapshotGas() public { + // Writes to `CustomGroup` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionGroupName() public { Flare f = new Flare(); f.run(1); - vm.snapshotGas("testSnapshotLastCallGas"); + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + + f.run(256); // 5_821_576 gas + f.run(512); // 11_617_936 gas + + uint256 gasUsed = vm.stopSnapshotGas( + "CustomGroup", + "testSnapshotGasSectionGroupName" + ); + assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. + function testSnapshotGasName() public { + Flare f = new Flare(); + + f.run(1); + + vm.snapshotGas("testSnapshotGasName"); + } + + // Writes to `CustomGroup` group with `testSnapshotGas` name. + function testSnapshotGasGroupName() public { + Flare f = new Flare(); + + f.run(1); + + vm.snapshotGas("CustomGroup", "testSnapshotGasGroupName"); } } From 279fb3f2d4c3f3b0f8d7d794ea7a11b33854ff1a Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:26:20 +0000 Subject: [PATCH 37/64] fix clippy --- crates/forge/bin/cmd/test/mod.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 148aba72b817..d8a16a8cc860 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -630,8 +630,7 @@ impl TestArgs { for (key, (previous_snapshot, snapshot)) in &diff { println!( "{}", - format!("- [{}] {} → {}", key, previous_snapshot, snapshot) - .red() + format!("- [{key}] {previous_snapshot} → {snapshot}").red() ); } From a1d66291fa18fb5bf4a84e5d8573d1a826df180b Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:30:34 +0000 Subject: [PATCH 38/64] gas not consistent, loosen assertion --- testdata/default/cheats/GasSnapshot.t.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index b8e5c76d891d..5873591a0b0c 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -70,7 +70,7 @@ contract GasSnapshotTest is DSTest { f.run(512); // 11_617_936 gas uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); - assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + assertGt(gasUsed, 0); } // Writes to `CustomGroup` group with `testSnapshotGasSection` name. @@ -84,11 +84,8 @@ contract GasSnapshotTest is DSTest { f.run(256); // 5_821_576 gas f.run(512); // 11_617_936 gas - uint256 gasUsed = vm.stopSnapshotGas( - "CustomGroup", - "testSnapshotGasSectionGroupName" - ); - assertEq(gasUsed, 17_439_512); // 5_821_576 + 11_617_936 = 17_439_512 gas + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + assertGt(gasUsed, 0); } // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. From f7be68757081b66fa69c026113f41a78b6fdaeef Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:45:54 +0000 Subject: [PATCH 39/64] simplify tests --- testdata/default/cheats/GasSnapshot.t.sol | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 5873591a0b0c..9f3f0af29c20 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -62,12 +62,9 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionName() public { Flare f = new Flare(); - f.run(1); - vm.startSnapshotGas("testSnapshotGasSectionName"); - f.run(256); // 5_821_576 gas - f.run(512); // 11_617_936 gas + f.run(256); uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); assertGt(gasUsed, 0); @@ -77,12 +74,9 @@ contract GasSnapshotTest is DSTest { function testSnapshotGasSectionGroupName() public { Flare f = new Flare(); - f.run(1); - vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); - f.run(256); // 5_821_576 gas - f.run(512); // 11_617_936 gas + f.run(256); uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); From 42c11f3aae2f8fc8e5c820b5b8c50bb4b6bacac9 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:46:48 +0000 Subject: [PATCH 40/64] remove old fuzz function --- testdata/default/cheats/GasSnapshot.t.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 9f3f0af29c20..3d3d497b2d4d 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,11 +7,6 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - // /// forge-config: default.fuzz.runs = 10 - // function testFuzzSnapshotValue1(string memory a, uint256 b) public { - // vm.snapshotValue(a, b); - // } - // Writes to `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup1() public { uint256 a = 123; From 20c6bbc040e1006e7b02a3625687f5af06e1e1a1 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 2 Sep 2024 13:54:55 +0000 Subject: [PATCH 41/64] re-enable solc --- testdata/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/foundry.toml b/testdata/foundry.toml index 30621914fa35..e9189bb008a3 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 From d0a97948db2d21587337caae94dbf1220035e779 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 4 Sep 2024 12:53:08 +0000 Subject: [PATCH 42/64] add back deprecated cheatcodes with notes, use inner --- crates/cheatcodes/assets/cheatcodes.json | 102 ++++++++++++++- crates/cheatcodes/spec/src/vm.rs | 23 +++- crates/cheatcodes/src/evm.rs | 155 +++++++++++++++++------ testdata/cheats/Vm.sol | 5 + 4 files changed, 241 insertions(+), 44 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 61ce4206946e..afe6f6bf1bfc 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3526,6 +3526,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "deleteSnapshot", + "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": "", + "signature": "deleteSnapshot(uint256)", + "selector": "0xa6368557", + "selectorBytes": [ + 166, + 54, + 133, + 87 + ] + }, + "group": "evm", + "status": "deprecated", + "safety": "unsafe" + }, + { + "func": { + "id": "deleteSnapshots", + "description": "`deleteSnapshots` is being deprecated in favor of `deleteStateSnapshots`. It will be removed in future versions.", + "declaration": "function deleteSnapshots() external;", + "visibility": "external", + "mutability": "", + "signature": "deleteSnapshots()", + "selector": "0x421ae469", + "selectorBytes": [ + 66, + 26, + 228, + 105 + ] + }, + "group": "evm", + "status": "deprecated", + "safety": "unsafe" + }, { "func": { "id": "deleteStateSnapshot", @@ -7106,6 +7146,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "revertTo", + "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": "", + "signature": "revertTo(uint256)", + "selector": "0x44d7f0a4", + "selectorBytes": [ + 68, + 215, + 240, + 164 + ] + }, + "group": "evm", + "status": "deprecated", + "safety": "unsafe" + }, + { + "func": { + "id": "revertToAndDelete", + "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": "", + "signature": "revertToAndDelete(uint256)", + "selector": "0x03e0aca9", + "selectorBytes": [ + 3, + 224, + 172, + 169 + ] + }, + "group": "evm", + "status": "deprecated", + "safety": "unsafe" + }, { "func": { "id": "revertToState", @@ -8066,6 +8146,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "snapshot", + "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": "", + "signature": "snapshot()", + "selector": "0x9711715a", + "selectorBytes": [ + 151, + 17, + 113, + 90 + ] + }, + "group": "evm", + "status": "deprecated", + "safety": "unsafe" + }, { "func": { "id": "snapshotGas_0", @@ -8109,7 +8209,7 @@ { "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 `revertTo`.", + "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": "", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 44f79b0d31a1..df239ff8471d 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -539,12 +539,21 @@ interface Vm { // -------- State Snapshots -------- + + /// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions. + #[cheatcode(group = Evm, safety = Unsafe, status = Deprecated)] + 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 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)] + 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. /// @@ -555,6 +564,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] 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)] + 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. /// @@ -563,6 +576,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] 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)] + function deleteSnapshot(uint256 snapshotId) external returns (bool success); + /// Removes the snapshot with the given ID created by `snapshot`. /// Takes the snapshot ID to delete. /// @@ -571,6 +588,10 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] 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)] + function deleteSnapshots() external; + /// Removes _all_ snapshots previously created by `snapshot`. #[cheatcode(group = Evm, safety = Unsafe)] function deleteStateSnapshots() external; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 87f88fce5a70..c52451432063 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -492,42 +492,42 @@ impl Cheatcode for readCallersCall { impl Cheatcode for snapshotValue_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name, value } = self; - create_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) + 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; - create_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) + inner_create_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) } } impl Cheatcode for startSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { name } = self; - start_gas_snapshot(ccx, None, Some(name.clone())) + 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; - start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + 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 { name } = self; - stop_gas_snapshot(ccx, None, Some(name.clone())) + inner_stop_gas_snapshot(ccx, None, Some(name.clone())) } } impl Cheatcode for stopSnapshotGas_1Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { group, name } = self; - stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) } } @@ -537,7 +537,12 @@ impl Cheatcode for snapshotGas_0Call { let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); }; - create_value_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed.to_string()) + inner_create_value_snapshot( + ccx, + None, + Some(name.clone()), + last_call_gas.gasTotalUsed.to_string(), + ) } } @@ -547,7 +552,7 @@ impl Cheatcode for snapshotGas_1Call { let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { bail!("no external call was made yet"); }; - create_value_snapshot( + inner_create_value_snapshot( ccx, Some(group.clone()), Some(name.clone()), @@ -556,63 +561,77 @@ impl Cheatcode for snapshotGas_1Call { } } +// Deprecated in favor of `snapshotStateCall` +impl Cheatcode for snapshotCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + inner_snapshot_state(ccx) + } +} + impl Cheatcode for snapshotStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) + 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; + inner_revert_to_state(ccx, *snapshotId) } } impl Cheatcode for revertToStateCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( - *snapshotId, - &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()) + 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; + inner_revert_to_state_and_delete(ccx, *snapshotId) } } impl Cheatcode for revertToStateAndDeleteCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = if let Some(journaled_state) = ccx.ecx.db.revert_state_snapshot( - *snapshotId, - &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()) + 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; + inner_delete_state_snapshot(ccx, *snapshotId) } } impl Cheatcode for deleteStateSnapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; - let result = ccx.ecx.db.delete_state_snapshot(*snapshotId); - Ok(result.abi_encode()) + 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; + inner_delete_state_snapshots(ccx) + } +} + impl Cheatcode for deleteStateSnapshotsCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - ccx.ecx.db.delete_state_snapshots(); - Ok(Default::default()) + inner_delete_state_snapshots(ccx) } } @@ -678,7 +697,59 @@ pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Add Ok(account.info.nonce.abi_encode()) } -fn create_value_snapshot( +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, @@ -696,7 +767,7 @@ fn create_value_snapshot( Ok(Default::default()) } -fn start_gas_snapshot( +fn inner_start_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, name: Option, @@ -725,7 +796,7 @@ fn start_gas_snapshot( Ok(Default::default()) } -fn stop_gas_snapshot( +fn inner_stop_gas_snapshot( ccx: &mut CheatsCtxt, group: Option, name: Option, diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index d628fe1e92c5..56eca8c63529 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -172,6 +172,8 @@ interface Vm { function createWallet(uint256 privateKey) external returns (Wallet memory wallet); function createWallet(uint256 privateKey, string calldata walletLabel) external returns (Wallet memory wallet); 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); @@ -351,6 +353,8 @@ interface Vm { function resetNonce(address account) external; function resumeGasMetering() external; 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; @@ -399,6 +403,7 @@ interface Vm { function sign(address signer, bytes32 digest) external pure returns (uint8 v, bytes32 r, bytes32 s); function skip(bool skipTest) external; function sleep(uint256 duration) external; + function snapshot() external returns (uint256 snapshotId); function snapshotGas(string calldata name) external; function snapshotGas(string calldata group, string calldata name) external; function snapshotState() external returns (uint256 snapshotId); From 1cacb51b80a92aa46fc9dfc93bc3752e900269f6 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 4 Sep 2024 12:55:06 +0000 Subject: [PATCH 43/64] add test for deprecated cheatcodes --- testdata/default/cheats/StateSnapshots.t.sol | 125 ++++++++++++++++++- 1 file changed, 122 insertions(+), 3 deletions(-) diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 1a938b03c64f..1a6d4b02bb38 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -98,8 +98,127 @@ contract StateSnapshotTest is DSTest { 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"); + 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" + ); + } +} + +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" + ); } } From 4c87e31d1bcbb442789aee56a52ea8eb51f1a986 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Wed, 4 Sep 2024 13:30:35 +0000 Subject: [PATCH 44/64] apply fmt --- testdata/default/cheats/StateSnapshots.t.sol | 36 ++++---------------- 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 1a6d4b02bb38..37d1061303e1 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -98,21 +98,9 @@ contract StateSnapshotTest is DSTest { 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" - ); + 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"); } } @@ -205,20 +193,8 @@ contract DeprecatedStateSnapshotTest is DSTest { 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" - ); + 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"); } } From 0c48753a39ef650ed6f6794ac4e9fb3f39ada9dc Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 5 Sep 2024 10:14:30 +0000 Subject: [PATCH 45/64] add stopSnapshotGas override to close last opened snapshot --- crates/cheatcodes/assets/cheatcodes.json | 22 +++++++++++++++++++- crates/cheatcodes/spec/src/vm.rs | 4 ++++ crates/cheatcodes/src/evm.rs | 17 +++++++++++++-- crates/cheatcodes/src/inspector.rs | 3 +++ testdata/cheats/Vm.sol | 1 + testdata/default/cheats/GasSnapshot.t.sol | 13 ++++++++++++ testdata/default/cheats/StateSnapshots.t.sol | 1 + 7 files changed, 58 insertions(+), 3 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index afe6f6bf1bfc..26f412dd0c30 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8569,6 +8569,26 @@ { "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", @@ -8588,7 +8608,7 @@ }, { "func": { - "id": "stopSnapshotGas_1", + "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", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index df239ff8471d..651b52879bb2 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -528,6 +528,10 @@ interface Vm { #[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)] diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index c52451432063..f565ed018c5a 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -519,12 +519,22 @@ impl Cheatcode for startSnapshotGas_1Call { impl Cheatcode for stopSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { - let Self { name } = self; + let Self {} = self; + let Some(name) = &ccx.state.gas_metering.last_snapshot_name else { + bail!("no gas snapshot was started yet"); + }; inner_stop_gas_snapshot(ccx, None, Some(name.clone())) } } 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())) @@ -564,6 +574,7 @@ impl Cheatcode for snapshotGas_1Call { // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; inner_snapshot_state(ccx) } } @@ -789,10 +800,12 @@ fn inner_start_gas_snapshot( ccx.state.gas_metering.gas_records.push(GasRecord { group: group.to_string(), - name, + name: name.clone(), gas_used: 0, }); + ccx.state.gas_metering.last_snapshot_name = Some(name); + Ok(Default::default()) } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 966bd055a347..7fad3efdbc22 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -227,6 +227,9 @@ pub struct GasMetering { /// Stores frames paused gas. pub paused_frames: Vec, + /// 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, diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 56eca8c63529..8b9f3d89cdb2 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -424,6 +424,7 @@ interface Vm { 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; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 3d3d497b2d4d..3b9b8c9dd4a5 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -53,6 +53,19 @@ contract GasSnapshotTest is DSTest { vm.snapshotValue("CustomGroup", "e", e); } + // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. + function testSnapshotGasSectionDefaultStop() public { + Flare f = new Flare(); + + vm.startSnapshotGas("testSnapshotGasSectionDefault"); + + f.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. function testSnapshotGasSectionName() public { Flare f = new Flare(); diff --git a/testdata/default/cheats/StateSnapshots.t.sol b/testdata/default/cheats/StateSnapshots.t.sol index 37d1061303e1..c26c11c3414e 100644 --- a/testdata/default/cheats/StateSnapshots.t.sol +++ b/testdata/default/cheats/StateSnapshots.t.sol @@ -104,6 +104,7 @@ contract StateSnapshotTest is DSTest { } } +// TODO: remove this test suite once `snapshot*` has been deprecated in favor of `snapshotState*`. contract DeprecatedStateSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); From f7cf91fadb5ccf27c9d7303a2c9212df9ffb3c08 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 6 Sep 2024 08:00:22 +0000 Subject: [PATCH 46/64] snapshotGas -> snapshotGasLastCall --- crates/cheatcodes/assets/cheatcodes.json | 32 ++++++------ crates/cheatcodes/spec/src/vm.rs | 4 +- crates/cheatcodes/src/evm.rs | 62 +++++++++++------------ testdata/cheats/Vm.sol | 4 +- testdata/default/cheats/GasSnapshot.t.sol | 8 +-- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 26f412dd0c30..05a4a18ec4bc 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -8168,18 +8168,18 @@ }, { "func": { - "id": "snapshotGas_0", + "id": "snapshotGasLastCall_0", "description": "Snapshot capture the gas usage of the last call by name.", - "declaration": "function snapshotGas(string calldata name) external;", + "declaration": "function snapshotGasLastCall(string calldata name) external;", "visibility": "external", "mutability": "", - "signature": "snapshotGas(string)", - "selector": "0x245b926b", + "signature": "snapshotGasLastCall(string)", + "selector": "0xdd9fca12", "selectorBytes": [ - 36, - 91, - 146, - 107 + 221, + 159, + 202, + 18 ] }, "group": "evm", @@ -8188,18 +8188,18 @@ }, { "func": { - "id": "snapshotGas_1", + "id": "snapshotGasLastCall_1", "description": "Snapshot capture the gas usage of the last call by name in a group.", - "declaration": "function snapshotGas(string calldata group, string calldata name) external;", + "declaration": "function snapshotGasLastCall(string calldata group, string calldata name) external;", "visibility": "external", "mutability": "", - "signature": "snapshotGas(string,string)", - "selector": "0x700bd230", + "signature": "snapshotGasLastCall(string,string)", + "selector": "0x200c6772", "selectorBytes": [ - 112, - 11, - 210, - 48 + 32, + 12, + 103, + 114 ] }, "group": "evm", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 651b52879bb2..176eded68a5c 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -513,11 +513,11 @@ interface Vm { /// Snapshot capture the gas usage of the last call by name. #[cheatcode(group = Evm, safety = Unsafe)] - function snapshotGas(string calldata name) external; + 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 snapshotGas(string calldata group, string calldata name) external; + 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. diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index f565ed018c5a..14f3721a210d 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -62,7 +62,7 @@ pub struct DealRecord { pub new_balance: U256, } -/// Records the `snapshotGas` cheatcodes. +/// Records the `snapshotGas*` cheatcodes. #[derive(Clone, Debug)] pub struct GasRecord { /// The group name of the gas snapshot. @@ -503,6 +503,36 @@ impl Cheatcode for snapshotValue_1Call { } } +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; @@ -541,36 +571,6 @@ impl Cheatcode for stopSnapshotGas_2Call { } } -impl Cheatcode for snapshotGas_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 snapshotGas_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(), - ) - } -} - // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 8b9f3d89cdb2..e76678b22b4b 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -404,8 +404,8 @@ interface Vm { function skip(bool skipTest) external; function sleep(uint256 duration) external; function snapshot() external returns (uint256 snapshotId); - function snapshotGas(string calldata name) external; - function snapshotGas(string calldata group, string calldata name) external; + 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; diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 3b9b8c9dd4a5..28006231a6ff 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -91,21 +91,21 @@ contract GasSnapshotTest is DSTest { } // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. - function testSnapshotGasName() public { + function testSnapshotGasLastCallName() public { Flare f = new Flare(); f.run(1); - vm.snapshotGas("testSnapshotGasName"); + vm.snapshotGasLastCall("testSnapshotGasName"); } // Writes to `CustomGroup` group with `testSnapshotGas` name. - function testSnapshotGasGroupName() public { + function testSnapshotGasLastCallGroupName() public { Flare f = new Flare(); f.run(1); - vm.snapshotGas("CustomGroup", "testSnapshotGasGroupName"); + vm.snapshotGasLastCall("CustomGroup", "testSnapshotGasGroupName"); } } From 190b12ffd4e79e389ab3c45f3680406913a71682 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 10 Sep 2024 12:31:47 +0000 Subject: [PATCH 47/64] update from master --- Cargo.lock | 858 +++++++++--------- Cargo.toml | 127 +-- Dockerfile | 2 +- crates/anvil/Cargo.toml | 1 + crates/anvil/core/src/eth/mod.rs | 81 +- crates/anvil/core/src/eth/transaction/mod.rs | 35 +- .../core/src/eth/transaction/optimism.rs | 38 + crates/anvil/core/src/types.rs | 20 +- crates/anvil/src/anvil.rs | 13 +- crates/anvil/src/config.rs | 24 +- crates/anvil/src/eth/api.rs | 175 +++- crates/anvil/src/eth/backend/fork.rs | 67 +- crates/anvil/src/eth/backend/info.rs | 8 +- crates/anvil/src/eth/backend/mem/mod.rs | 210 +++-- crates/anvil/src/eth/backend/mem/storage.rs | 17 + crates/anvil/src/eth/error.rs | 15 +- crates/anvil/src/eth/otterscan/api.rs | 102 ++- crates/anvil/src/eth/pool/transactions.rs | 9 +- crates/anvil/src/tasks/mod.rs | 8 +- crates/anvil/tests/it/abi.rs | 2 +- crates/anvil/tests/it/anvil_api.rs | 167 +++- crates/anvil/tests/it/api.rs | 86 +- crates/anvil/tests/it/fork.rs | 55 +- crates/anvil/tests/it/ipc.rs | 2 +- crates/anvil/tests/it/logs.rs | 6 +- crates/anvil/tests/it/optimism.rs | 6 +- crates/anvil/tests/it/otterscan.rs | 26 +- crates/anvil/tests/it/pubsub.rs | 8 +- crates/anvil/tests/it/traces.rs | 18 +- crates/anvil/tests/it/transaction.rs | 11 +- crates/cast/Cargo.toml | 7 +- crates/cast/bin/{opts.rs => args.rs} | 42 + crates/cast/bin/cmd/access_list.rs | 10 +- crates/cast/bin/cmd/call.rs | 10 +- crates/cast/bin/cmd/estimate.rs | 11 +- crates/cast/bin/cmd/interface.rs | 6 +- crates/cast/bin/cmd/mktx.rs | 8 +- crates/cast/bin/cmd/run.rs | 8 +- crates/cast/bin/cmd/send.rs | 4 +- crates/cast/bin/cmd/storage.rs | 2 +- crates/cast/bin/main.rs | 79 +- crates/cast/bin/tx.rs | 46 +- crates/cast/src/lib.rs | 103 ++- crates/cast/tests/cli/main.rs | 27 +- crates/cheatcodes/assets/cheatcodes.json | 20 + crates/cheatcodes/spec/src/vm.rs | 4 + crates/cheatcodes/src/inspector.rs | 28 +- crates/cheatcodes/src/json.rs | 3 + crates/cheatcodes/src/test.rs | 16 +- crates/cheatcodes/src/test/assume.rs | 29 + crates/cheatcodes/src/test/expect.rs | 49 +- crates/chisel/bin/main.rs | 9 +- crates/cli/src/opts/build/core.rs | 2 +- crates/cli/src/opts/build/paths.rs | 10 +- crates/cli/src/utils/mod.rs | 6 +- crates/common/fmt/src/ui.rs | 47 +- crates/common/src/ens.rs | 2 +- crates/config/src/lib.rs | 30 +- crates/config/src/macros.rs | 7 +- crates/config/src/utils.rs | 103 ++- .../doc/src/preprocessor/infer_hyperlinks.rs | 4 +- crates/evm/core/src/backend/mod.rs | 73 +- crates/evm/core/src/fork/init.rs | 27 +- crates/evm/core/src/fork/multi.rs | 3 +- crates/evm/core/src/opts.rs | 4 +- crates/evm/core/src/utils.rs | 27 +- crates/evm/traces/src/folded_stack_trace.rs | 301 ++++++ crates/evm/traces/src/lib.rs | 2 + crates/forge/Cargo.toml | 1 + crates/forge/bin/cmd/bind.rs | 12 +- crates/forge/bin/cmd/build.rs | 2 +- crates/forge/bin/cmd/clone.rs | 6 +- crates/forge/bin/cmd/doc/mod.rs | 7 +- crates/forge/bin/cmd/selectors.rs | 2 +- crates/forge/bin/cmd/test/mod.rs | 101 ++- crates/forge/bin/main.rs | 2 +- crates/forge/src/lib.rs | 6 +- crates/forge/src/multi_runner.rs | 20 +- crates/forge/src/result.rs | 12 + crates/forge/tests/cli/cmd.rs | 10 +- crates/forge/tests/cli/config.rs | 6 +- crates/forge/tests/cli/soldeer.rs | 265 ++++-- crates/forge/tests/cli/svm.rs | 2 +- crates/forge/tests/cli/test_cmd.rs | 85 ++ crates/forge/tests/cli/verify_bytecode.rs | 6 +- crates/forge/tests/it/fuzz.rs | 28 +- crates/forge/tests/it/repros.rs | 3 + crates/test-utils/Cargo.toml | 4 + crates/test-utils/src/rpc.rs | 68 +- crates/verify/src/bytecode.rs | 2 +- crates/verify/src/utils.rs | 4 +- deny.toml | 3 + testdata/cheats/Vm.sol | 1 + testdata/default/repros/Issue6643.t.sol | 65 ++ 94 files changed, 2815 insertions(+), 1274 deletions(-) rename crates/cast/bin/{opts.rs => args.rs} (96%) create mode 100644 crates/cheatcodes/src/test/assume.rs create mode 100644 crates/evm/traces/src/folded_stack_trace.rs create mode 100644 testdata/default/repros/Issue6643.t.sol diff --git a/Cargo.lock b/Cargo.lock index 56b720bb4f66..011a7af0fb62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,9 +74,9 @@ checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] name = "alloy-chains" -version = "0.1.29" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb07629a5d0645d29f68d2fb6f4d0cf15c89ec0965be915f303967180929743f" +checksum = "2b4f201b0ac8f81315fbdc55269965a8ddadbc04ab47fa65a1a468f9a40f7a5f" dependencies = [ "num_enum", "serde", @@ -85,8 +85,8 @@ dependencies = [ [[package]] name = "alloy-consensus" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-eips", "alloy-primitives", @@ -98,8 +98,8 @@ dependencies = [ [[package]] name = "alloy-contract" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", @@ -118,9 +118,9 @@ dependencies = [ [[package]] name = "alloy-dyn-abi" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413902aa18a97569e60f679c23f46a18db1656d87ab4d4e49d0e1e52042f66df" +checksum = "b03f58cfae4d41b624effe1f11624ee40499449174b20a6d6505fd72ef0d547d" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -129,7 +129,7 @@ dependencies = [ "arbitrary", "const-hex", "derive_arbitrary", - "derive_more 0.99.18", + "derive_more 1.0.0", "itoa", "proptest", "serde", @@ -137,28 +137,55 @@ dependencies = [ "winnow", ] +[[package]] +name = "alloy-eip2930" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "rand", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d319bb544ca6caeab58c39cea8921c55d924d4f68f2c60f24f914673f9a74a" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "arbitrary", + "k256", + "rand", + "serde", +] + [[package]] name = "alloy-eips" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ + "alloy-eip2930", + "alloy-eip7702", "alloy-primitives", "alloy-rlp", "alloy-serde", "arbitrary", "c-kzg", "derive_more 1.0.0", - "k256", "once_cell", - "rand", "serde", "sha2", ] [[package]] name = "alloy-genesis" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-serde", @@ -167,9 +194,9 @@ dependencies = [ [[package]] name = "alloy-json-abi" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc05b04ac331a9f07e3a4036ef7926e49a8bf84a99a1ccfc7e2ab55a5fcbb372" +checksum = "a28ecae8b5315daecd0075084eb47f4374b3037777346ca52fc8d9c327693f02" dependencies = [ "alloy-primitives", "alloy-sol-type-parser", @@ -179,8 +206,8 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -192,8 +219,8 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-eips", @@ -212,8 +239,8 @@ dependencies = [ [[package]] name = "alloy-network-primitives" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-serde", @@ -222,9 +249,9 @@ dependencies = [ [[package]] name = "alloy-primitives" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccb3ead547f4532bc8af961649942f0b9c16ee9226e26caa3f38420651cc0bf4" +checksum = "ccb865df835f851b367ae439d6c82b117ded971628c8888b24fed411a290e38a" dependencies = [ "alloy-rlp", "arbitrary", @@ -232,8 +259,7 @@ dependencies = [ "cfg-if", "const-hex", "derive_arbitrary", - "derive_more 0.99.18", - "ethereum_ssz", + "derive_more 1.0.0", "getrandom", "hex-literal", "itoa", @@ -249,8 +275,8 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-chains", "alloy-consensus", @@ -287,8 +313,8 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -299,7 +325,7 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tracing", ] @@ -327,8 +353,8 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -344,15 +370,15 @@ dependencies = [ "serde_json", "tokio", "tokio-stream", - "tower", + "tower 0.4.13", "tracing", "url", ] [[package]] name = "alloy-rpc-types" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-rpc-types-anvil", "alloy-rpc-types-engine", @@ -365,8 +391,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-anvil" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-serde", @@ -375,8 +401,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-engine" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-eips", @@ -392,8 +418,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-eth" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-eips", @@ -410,8 +436,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-trace" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -423,8 +449,8 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "alloy-rpc-types-eth", @@ -434,8 +460,8 @@ dependencies = [ [[package]] name = "alloy-serde" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-primitives", "arbitrary", @@ -445,8 +471,8 @@ dependencies = [ [[package]] name = "alloy-signer" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -460,8 +486,8 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-network", @@ -477,8 +503,8 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-network", @@ -494,8 +520,8 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-dyn-abi", @@ -513,8 +539,8 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-network", @@ -532,8 +558,8 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-consensus", "alloy-network", @@ -548,13 +574,13 @@ dependencies = [ [[package]] name = "alloy-sol-macro" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b40397ddcdcc266f59f959770f601ce1280e699a91fc1862f29cef91707cd09" +checksum = "e2dc5201ca0018afb7a3e0cd8bd15f7ca6aca924333b5f3bb87463b41d0c4ef2" dependencies = [ "alloy-sol-macro-expander", "alloy-sol-macro-input", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.77", @@ -562,16 +588,16 @@ dependencies = [ [[package]] name = "alloy-sol-macro-expander" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "867a5469d61480fea08c7333ffeca52d5b621f5ca2e44f271b117ec1fc9a0525" +checksum = "155f63dc6945885aa4532601800201fddfaa3b20901fda8e8c2570327242fe0e" dependencies = [ "alloy-json-abi", "alloy-sol-macro-input", "const-hex", "heck", "indexmap 2.5.0", - "proc-macro-error", + "proc-macro-error2", "proc-macro2", "quote", "syn 2.0.77", @@ -581,9 +607,9 @@ dependencies = [ [[package]] name = "alloy-sol-macro-input" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e482dc33a32b6fadbc0f599adea520bd3aaa585c141a80b404d0a3e3fa72528" +checksum = "847700aa9cb59d3c7b290b2d05976cd8d76b64d73bb63116a9533132d995586b" dependencies = [ "alloy-json-abi", "const-hex", @@ -598,9 +624,9 @@ dependencies = [ [[package]] name = "alloy-sol-type-parser" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcba3ca07cf7975f15d871b721fb18031eec8bce51103907f6dcce00b255d98" +checksum = "3a6b5d462d4520bd9ed70d8364c6280aeff13baa46ea26be1ddd33538dbbe6ac" dependencies = [ "serde", "winnow", @@ -608,9 +634,9 @@ dependencies = [ [[package]] name = "alloy-sol-types" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91ca40fa20793ae9c3841b83e74569d1cc9af29a2f5237314fd3452d51e38c7" +checksum = "83665e5607725a7a1aab3cb0dea708f4a05e70776954ec7f0a9461439175c957" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -621,8 +647,8 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -632,29 +658,29 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "tower", + "tower 0.4.13", "tracing", "url", ] [[package]] name = "alloy-transport-http" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-json-rpc", "alloy-transport", "reqwest", "serde_json", - "tower", + "tower 0.4.13", "tracing", "url", ] [[package]] name = "alloy-transport-ipc" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -673,8 +699,8 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.2.1" -source = "git+https://github.com/alloy-rs/alloy?rev=511ae98#511ae9820210798d4a35f1e28a7993bb5d445cd4" +version = "0.3.1" +source = "git+https://github.com/alloy-rs/alloy?rev=7fab7ee#7fab7ee4b27220e755d21f4b50108d083ea341fd" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -690,13 +716,13 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03704f265cbbb943b117ecb5055fd46e8f41e7dc8a58b1aed20bcd40ace38c15" +checksum = "398a977d774db13446b8cead8cfa9517aebf9e03fc8a1512892dc1e03e70bb04" dependencies = [ "alloy-primitives", "alloy-rlp", - "derive_more 0.99.18", + "derive_more 1.0.0", "hashbrown 0.14.5", "nybbles", "serde", @@ -838,6 +864,7 @@ dependencies = [ "hyper 1.4.1", "itertools 0.13.0", "k256", + "op-alloy-rpc-types", "parking_lot", "rand", "revm", @@ -849,7 +876,7 @@ dependencies = [ "thiserror", "tikv-jemallocator", "tokio", - "tower", + "tower 0.4.13", "tracing", "tracing-subscriber", "vergen", @@ -908,9 +935,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "arbitrary" @@ -1264,9 +1291,9 @@ dependencies = [ [[package]] name = "aws-sdk-kms" -version = "1.41.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "178910fefe72743b62b9c4670c14a038ebfdb265ff7feccf43827af6a8899e14" +checksum = "704ab31904cf70104a3bb023079e201b1353cf132ca674b26ba6f23acbbb53c9" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1286,9 +1313,9 @@ dependencies = [ [[package]] name = "aws-sdk-sso" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5879bec6e74b648ce12f6085e7245417bc5f6d672781028384d2e494be3eb6d" +checksum = "af0a3f676cba2c079c9563acc9233998c8951cdbe38629a0bef3c8c1b02f3658" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1308,9 +1335,9 @@ dependencies = [ [[package]] name = "aws-sdk-ssooidc" -version = "1.41.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef4cd9362f638c22a3b959fd8df292e7e47fdf170270f86246b97109b5f2f7d" +checksum = "c91b6a04495547162cf52b075e3c15a17ab6608bf9c5785d3e5a5509b3f09f5c" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1330,9 +1357,9 @@ dependencies = [ [[package]] name = "aws-sdk-sts" -version = "1.40.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e2735d2ab28b35ecbb5496c9d41857f52a0d6a0075bbf6a8af306045ea6f6" +checksum = "99c56bcd6a56cab7933980a54148b476a5a69a7694e3874d9aa2a566f150447d" dependencies = [ "aws-credential-types", "aws-runtime", @@ -1545,7 +1572,7 @@ dependencies = [ "sync_wrapper 1.0.1", "tokio", "tokio-tungstenite 0.21.0", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -1686,19 +1713,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "bls12_381" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" -dependencies = [ - "ff", - "group", - "pairing", - "rand_core", - "subtle", -] - [[package]] name = "blst" version = "0.3.13" @@ -1752,9 +1766,9 @@ checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" [[package]] name = "bytemuck" -version = "1.17.1" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" +checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" [[package]] name = "byteorder" @@ -1855,65 +1869,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" -[[package]] -name = "cast" -version = "0.2.0" -dependencies = [ - "alloy-chains", - "alloy-consensus", - "alloy-contract", - "alloy-dyn-abi", - "alloy-json-abi", - "alloy-json-rpc", - "alloy-network", - "alloy-primitives", - "alloy-provider", - "alloy-rlp", - "alloy-rpc-types", - "alloy-serde", - "alloy-signer", - "alloy-signer-local", - "alloy-sol-types", - "alloy-transport", - "anvil", - "async-trait", - "aws-sdk-kms", - "chrono", - "clap", - "clap_complete", - "clap_complete_fig", - "comfy-table", - "criterion", - "dunce", - "evm-disassembler", - "evmole", - "eyre", - "foundry-block-explorers", - "foundry-cli", - "foundry-common", - "foundry-compilers", - "foundry-config", - "foundry-evm", - "foundry-test-utils", - "foundry-wallets", - "futures", - "indicatif", - "itertools 0.13.0", - "rand", - "rayon", - "regex", - "rpassword", - "semver 1.0.23", - "serde", - "serde_json", - "tempfile", - "tikv-jemallocator", - "tokio", - "tracing", - "vergen", - "yansi", -] - [[package]] name = "cast" version = "0.3.0" @@ -1931,12 +1886,10 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.15" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ - "jobserver", - "libc", "shlex", ] @@ -2048,9 +2001,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -2058,9 +2011,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -2073,9 +2026,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.24" +version = "4.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7db6eca8c205649e8d3ccd05aa5042b1800a784e56bc7c43524fde8abbfa9b" +checksum = "205d5ef6d485fa47606b98b0ddc4ead26eb850aaa86abfb562a94fb3280ecba0" dependencies = [ "clap", ] @@ -2314,18 +2267,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "convert_case" version = "0.6.0" @@ -2353,9 +2294,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -2376,7 +2317,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" dependencies = [ "anes", - "cast 0.3.0", + "cast", "ciborium", "clap", "criterion-plot", @@ -2403,7 +2344,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ - "cast 0.3.0", + "cast", "itertools 0.10.5", ] @@ -2563,9 +2504,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.0.1" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", "crossbeam-utils", @@ -2671,10 +2612,8 @@ version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "convert_case 0.4.0", "proc-macro2", "quote", - "rustc_version 0.4.1", "syn 2.0.77", ] @@ -2693,7 +2632,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ - "convert_case 0.6.0", + "convert_case", "proc-macro2", "quote", "syn 2.0.77", @@ -2898,7 +2837,7 @@ dependencies = [ "console_error_panic_hook", "pest", "pest_derive", - "quick-xml", + "quick-xml 0.18.1", "wasm-bindgen", ] @@ -3049,17 +2988,6 @@ dependencies = [ "uint", ] -[[package]] -name = "ethereum_ssz" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3627f83d8b87b432a5fad9934b4565260722a141a2c40f371f8080adec9425" -dependencies = [ - "ethereum-types", - "itertools 0.10.5", - "smallvec", -] - [[package]] name = "ethers-contract-abigen" version = "2.0.14" @@ -3135,11 +3063,12 @@ dependencies = [ [[package]] name = "evmole" -version = "0.3.8" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffb1f458e6901be6a6aaa485ce3a5d81478644edde1ffbe95da114ad9c94467" +checksum = "2dc8472e812ff8f53a76946fa70b1cc4bf75c78755beb09df4d1376764769c7d" dependencies = [ - "ruint", + "alloy-dyn-abi", + "alloy-primitives", ] [[package]] @@ -3202,7 +3131,6 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ - "bitvec", "rand_core", "subtle", ] @@ -3241,7 +3169,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" dependencies = [ - "arbitrary", "byteorder", "rand", "rustc-hex", @@ -3337,6 +3264,7 @@ dependencies = [ "humantime-serde", "hyper 1.4.1", "indicatif", + "inferno", "itertools 0.13.0", "mockall", "opener", @@ -3518,9 +3446,9 @@ dependencies = [ [[package]] name = "foundry-block-explorers" -version = "0.5.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1580bdb99a6a531b44ac5cda229069cacc11ae7d54faa45676e1bee9ee7da1c" +checksum = "461772f04e5457d6b6501f6aff3a615a032d01368c1cc91c13a06eff172962b6" dependencies = [ "alloy-chains", "alloy-json-abi", @@ -3534,6 +3462,65 @@ dependencies = [ "tracing", ] +[[package]] +name = "foundry-cast" +version = "0.2.0" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-contract", + "alloy-dyn-abi", + "alloy-json-abi", + "alloy-json-rpc", + "alloy-network", + "alloy-primitives", + "alloy-provider", + "alloy-rlp", + "alloy-rpc-types", + "alloy-serde", + "alloy-signer", + "alloy-signer-local", + "alloy-sol-types", + "alloy-transport", + "anvil", + "async-trait", + "aws-sdk-kms", + "chrono", + "clap", + "clap_complete", + "clap_complete_fig", + "comfy-table", + "criterion", + "dunce", + "evm-disassembler", + "evmole", + "eyre", + "foundry-block-explorers", + "foundry-cli", + "foundry-common", + "foundry-compilers", + "foundry-config", + "foundry-evm", + "foundry-test-utils", + "foundry-wallets", + "futures", + "indicatif", + "itertools 0.13.0", + "rand", + "rayon", + "regex", + "rpassword", + "semver 1.0.23", + "serde", + "serde_json", + "tempfile", + "tikv-jemallocator", + "tokio", + "tracing", + "vergen", + "yansi", +] + [[package]] name = "foundry-cheatcodes" version = "0.2.0" @@ -3665,7 +3652,7 @@ dependencies = [ "similar-asserts", "thiserror", "tokio", - "tower", + "tower 0.4.13", "tracing", "url", "walkdir", @@ -3693,9 +3680,9 @@ dependencies = [ [[package]] name = "foundry-compilers" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eaa24a47bb84e1db38c84f03e8c90ca81050bd20beac8bdc99aae8afd0b8784" +checksum = "3372aaa89b1653b61fb297dbc24e74ad727ff76cc4415f1a0ec5f802d24e0797" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3731,9 +3718,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3588ee6a986f89040d1158fb90459731580b404fb72b8c6c832c0ddbc95fed58" +checksum = "76c4f9ac0ed5e695bbeb48ff0758ba31ce3d0d52b752aaa466f311f48ed772c0" dependencies = [ "foundry-compilers-artifacts-solc", "foundry-compilers-artifacts-vyper", @@ -3741,9 +3728,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-solc" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a149c5e8c326c7bae8f73cacb28c637f4bc2e535f950eec10348494990e9636f" +checksum = "5ad6beeb057a8a58993d13841cffcb99e8aefdeb52ed9b368c85518747b467f9" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3765,9 +3752,9 @@ dependencies = [ [[package]] name = "foundry-compilers-artifacts-vyper" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8645c9e7c070c81bf8c90f456416953234334f097b67445c773af98df74e27b0" +checksum = "442e5eb231aad523f0f3e26f9475ad9eab1aa2173a5991df1b7fa51f589f309f" dependencies = [ "alloy-json-abi", "alloy-primitives", @@ -3780,9 +3767,9 @@ dependencies = [ [[package]] name = "foundry-compilers-core" -version = "0.10.3" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66492aeb708f3d142c078457dba5f52b04ca5031012d48903a0bcb37d205d595" +checksum = "92049644ce2745c36be16f6406592155d4be2314306af2543c7d30a32b00d6ed" dependencies = [ "alloy-primitives", "cfg-if", @@ -4000,9 +3987,9 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.2.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e1217b5063138a87feb51bd9ac71857d370f06f1aa3d8c22b73aae0e49f4c3" +checksum = "c88cb03fc4bd87856fc4d0ad38fd067f85c7c6306bf794202fc50a897449837b" dependencies = [ "alloy-primitives", "alloy-provider", @@ -4050,6 +4037,7 @@ dependencies = [ "alloy-provider", "eyre", "fd-lock", + "foundry-block-explorers", "foundry-common", "foundry-compilers", "foundry-config", @@ -4058,6 +4046,7 @@ dependencies = [ "regex", "serde_json", "snapbox", + "tokio", "tracing", "tracing-subscriber", ] @@ -4237,9 +4226,9 @@ checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" [[package]] name = "gcloud-sdk" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77045256cd0d2075e09d62c4c9f27c2b664e2cc806d7ddf3a4293bb0c20b4728" +checksum = "6d92f38cbe5b8796d2ab3f3c5f3bc286aa778015d5c5f67e2d0cbbe5d348473f" dependencies = [ "async-trait", "bytes", @@ -4256,7 +4245,7 @@ dependencies = [ "serde_json", "tokio", "tonic", - "tower", + "tower 0.5.0", "tower-layer", "tower-util", "tracing", @@ -4445,9 +4434,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.10" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d5b8722112fa2fa87135298780bc833b0e9f6c56cc82795d209804b3a03484" +checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af" dependencies = [ "bstr", "gix-trace", @@ -4505,9 +4494,9 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" +checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b" [[package]] name = "gix-utils" @@ -4868,16 +4857,16 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", "hyper 1.4.1", "hyper-util", "rustls 0.23.12", - "rustls-native-certs 0.7.3", + "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -4929,7 +4918,7 @@ dependencies = [ "pin-project-lite", "socket2", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -5085,6 +5074,23 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "inferno" +version = "0.11.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" +dependencies = [ + "ahash", + "is-terminal", + "itoa", + "log", + "num-format", + "once_cell", + "quick-xml 0.26.0", + "rgb", + "str_stack", +] + [[package]] name = "inlinable_string" version = "0.1.15" @@ -5156,9 +5162,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is-terminal" @@ -5210,15 +5216,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - [[package]] name = "js-sys" version = "0.3.70" @@ -5307,21 +5304,6 @@ dependencies = [ "libc", ] -[[package]] -name = "kzg-rs" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9920cd4460ce3cbca19c62f3bb9a9611562478a4dc9d2c556f4a7d049c5b6b" -dependencies = [ - "bls12_381", - "glob", - "hex", - "once_cell", - "serde", - "serde_derive", - "serde_yaml", -] - [[package]] name = "lalrpop" version = "0.20.2" @@ -5983,6 +5965,37 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "op-alloy-consensus" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7fbb0f5c3754c22c6ea30e100dca6aea73b747e693e27763e23ca92fb02f2f" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 1.0.0", + "serde", + "spin", +] + +[[package]] +name = "op-alloy-rpc-types" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fbb93dcb71aba9cd555784375011efce1fdaaea67e01972a0a9bc9eb90c626" +dependencies = [ + "alloy-network", + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "op-alloy-consensus", + "serde", + "serde_json", +] + [[package]] name = "open-fastrlp" version = "0.1.4" @@ -6100,15 +6113,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "pairing" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" -dependencies = [ - "group", -] - [[package]] name = "parity-scale-codec" version = "3.6.12" @@ -6137,9 +6141,9 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -6164,17 +6168,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core", - "subtle", -] - [[package]] name = "paste" version = "1.0.15" @@ -6194,9 +6187,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac", - "password-hash", - "sha2", ] [[package]] @@ -6259,9 +6249,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", @@ -6270,9 +6260,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" dependencies = [ "pest", "pest_generator", @@ -6280,9 +6270,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" dependencies = [ "pest", "pest_meta", @@ -6293,9 +6283,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" dependencies = [ "once_cell", "pest", @@ -6608,6 +6598,28 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -6683,13 +6695,13 @@ dependencies = [ [[package]] name = "proptest-derive" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf16337405ca084e9c78985114633b6827711d22b9e6ef6c6c0d665eb3f0b6e" +checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.77", ] [[package]] @@ -6777,11 +6789,20 @@ dependencies = [ "memchr", ] +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + [[package]] name = "quinn" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d2fb862b7ba45e615c1429def928f2e15f815bdf933b27a2d3824e224c1f46" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -6797,9 +6818,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.7" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0a9b3a42929fad8a7c3de7f86ce0814cfa893328157672680e9fb1145549c5" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", @@ -6814,15 +6835,15 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7022,7 +7043,7 @@ dependencies = [ "http-body 1.0.1", "http-body-util", "hyper 1.4.1", - "hyper-rustls 0.27.2", + "hyper-rustls 0.27.3", "hyper-tls", "hyper-util", "ipnet", @@ -7060,8 +7081,9 @@ dependencies = [ [[package]] name = "revm" -version = "13.0.0" -source = "git+https://github.com/bluealloy/revm?rev=caadc71#caadc71fd28929cd751cc997b4b454d8813c434b" +version = "14.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f719e28cc6fdd086f8bc481429e587740d20ad89729cec3f5f5dd7b655474df" dependencies = [ "auto_impl", "cfg-if", @@ -7074,9 +7096,9 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.5.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec16f9b9d3cdaaf2f4b7ceaf004eb2c89df04e7ea29622584c0a6ec676bd0a83" +checksum = "48184032103bb23788e42e42c7c85207f5b0b8a248b09ea8f5233077f35ab56e" dependencies = [ "alloy-primitives", "alloy-rpc-types", @@ -7091,8 +7113,9 @@ dependencies = [ [[package]] name = "revm-interpreter" -version = "9.0.0" -source = "git+https://github.com/bluealloy/revm?rev=caadc71#caadc71fd28929cd751cc997b4b454d8813c434b" +version = "10.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "959ecbc36802de6126852479844737f20194cf8e6718e0c30697d306a2cca916" dependencies = [ "revm-primitives", "serde", @@ -7100,8 +7123,9 @@ dependencies = [ [[package]] name = "revm-precompile" -version = "10.0.0" -source = "git+https://github.com/bluealloy/revm?rev=caadc71#caadc71fd28929cd751cc997b4b454d8813c434b" +version = "11.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e25f604cb9db593ca3013be8c00f310d6790ccb1b7d8fbbdd4660ec8888043a" dependencies = [ "aurora-engine-modexp", "blst", @@ -7119,8 +7143,9 @@ dependencies = [ [[package]] name = "revm-primitives" -version = "8.0.0" -source = "git+https://github.com/bluealloy/revm?rev=caadc71#caadc71fd28929cd751cc997b4b454d8813c434b" +version = "9.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ccb981ede47ccf87c68cebf1ba30cdbb7ec935233ea305f3dfff4c1e10ae541" dependencies = [ "alloy-eips", "alloy-primitives", @@ -7129,13 +7154,10 @@ dependencies = [ "bitvec", "c-kzg", "cfg-if", - "derive_more 0.99.18", "dyn-clone", "enumn", "hashbrown 0.14.5", "hex", - "kzg-rs", - "once_cell", "serde", ] @@ -7149,6 +7171,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "rgb" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f86ae463694029097b846d8f99fd5536740602ae00022c0c50c5600720b2f71" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -7295,9 +7326,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.6.0", "errno", @@ -7358,6 +7389,19 @@ dependencies = [ "security-framework", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" +dependencies = [ + "openssl-probe", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -7513,11 +7557,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -7600,9 +7644,9 @@ dependencies = [ [[package]] name = "secp256k1" -version = "0.29.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "rand", "secp256k1-sys", @@ -7688,18 +7732,18 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -7719,9 +7763,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "indexmap 2.5.0", "itoa", @@ -7782,19 +7826,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap 2.5.0", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "serial_test" version = "3.1.1" @@ -7947,9 +7978,9 @@ dependencies = [ [[package]] name = "similar-asserts" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e041bb827d1bfca18f213411d51b665309f1afb37a04a5d1464530e13779fc0f" +checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" dependencies = [ "console", "similar", @@ -8039,17 +8070,19 @@ dependencies = [ [[package]] name = "soldeer" -version = "0.3.1" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6191e16cbc3b4ed14cafc642bc2b7f966eddb7c1f3a9f60549f6a1e526016127" +checksum = "b9ed763c2bb43241ca0fb6c00feea54187895b7f4eb1090654fbf82807127369" dependencies = [ "chrono", "clap", "const-hex", + "dunce", "email-address-parser", "futures", "home", "ignore", + "path-slash", "regex", "reqwest", "rpassword", @@ -8062,7 +8095,7 @@ dependencies = [ "toml_edit", "uuid 1.10.0", "yansi", - "zip 2.2.0", + "zip", "zip-extract", ] @@ -8071,6 +8104,9 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] name = "spki" @@ -8088,6 +8124,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "str_stack" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091b6114800a5f2141aee1d1b9d6ca3592ac062dc5decb3764ec5895a47b4eb" + [[package]] name = "string_cache" version = "0.8.7" @@ -8163,9 +8205,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "svm-rs" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00d3230221bec82c4a79cd7af637ac29f04f369e95e476bc492f22882bb83c91" +checksum = "4aebac1b1ef2b46e2e2bdf3c09db304800f2a77c1fa902bd5231490203042be8" dependencies = [ "const-hex", "dirs 5.0.1", @@ -8178,14 +8220,14 @@ dependencies = [ "tempfile", "thiserror", "url", - "zip 2.2.0", + "zip", ] [[package]] name = "svm-rs-builds" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fe4ebbe1038a5a517a07948c2487b3ccf79a4908953cc8f0047cf652233546" +checksum = "f2fa0f145894cb4d1c14446f08098ee5f21fc37ccbd1a7dd9dd355bbc806de3b" dependencies = [ "build_const", "const-hex", @@ -8218,9 +8260,9 @@ dependencies = [ [[package]] name = "syn-solidity" -version = "0.7.7" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c837dc8852cb7074e46b444afb81783140dab12c58867b49fb3898fbafedf7ea" +checksum = "f1e1355d44af21638c8e05d45097db6cb5ec2aa3e970c51cb2901605cf3344fa" dependencies = [ "paste", "proc-macro2", @@ -8513,9 +8555,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" dependencies = [ "futures-core", "pin-project-lite", @@ -8553,9 +8595,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -8635,7 +8677,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.0", "tokio-stream", - "tower", + "tower 0.4.13", "tower-layer", "tower-service", "tracing", @@ -8667,6 +8709,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b837f86b25d7c0d7988f00a54e74739be6477f2aac6201b8f429a7569991b7" +dependencies = [ + "tower-layer", + "tower-service", +] + [[package]] name = "tower-http" version = "0.5.2" @@ -8896,7 +8948,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ - "arbitrary", "byteorder", "crunchy", "hex", @@ -8983,12 +9034,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -9709,25 +9754,6 @@ dependencies = [ "syn 2.0.77", ] -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "zstd", -] - [[package]] name = "zip" version = "2.2.0" @@ -9735,6 +9761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" dependencies = [ "arbitrary", + "bzip2", "crc32fast", "crossbeam-utils", "displaydoc", @@ -9747,13 +9774,13 @@ dependencies = [ [[package]] name = "zip-extract" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e109e5a291403b4c1e514d39f8a22d3f98d257e691a52bb1f16051bb1ffed63e" +checksum = "25a8c9e90f27d1435088a7b540b6cc8ae6ee525d992a695f16012d2f365b3d3c" dependencies = [ "log", "thiserror", - "zip 0.6.6", + "zip", ] [[package]] @@ -9769,32 +9796,3 @@ dependencies = [ "once_cell", "simd-adler32", ] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.13+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index df99f6b4665f..fe8730a6bae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -157,54 +157,58 @@ foundry-wallets = { path = "crates/wallets" } foundry-linking = { path = "crates/linking" } # solc & compilation utilities -foundry-block-explorers = { version = "0.5.1", default-features = false } -foundry-compilers = { version = "0.10.3", default-features = false } -foundry-fork-db = "0.2" +foundry-block-explorers = { version = "0.7.1", default-features = false } +foundry-compilers = { version = "0.11.0", default-features = false } +foundry-fork-db = "0.3" solang-parser = "=0.3.3" ## revm # no default features to avoid c-kzg -revm = { version = "13.0.0", default-features = false } -revm-primitives = { version = "8.0.0", default-features = false } -revm-inspectors = { version = "0.5", features = ["serde"] } +revm = { version = "14.0.1", default-features = false } +revm-primitives = { version = "9.0.1", default-features = false } +revm-inspectors = { version = "0.6", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { version = "0.2.1", default-features = false } -alloy-contract = { version = "0.2.1", default-features = false } -alloy-eips = { version = "0.2.1", default-features = false } -alloy-genesis = { version = "0.2.1", default-features = false } -alloy-json-rpc = { version = "0.2.1", default-features = false } -alloy-network = { version = "0.2.1", default-features = false } -alloy-provider = { version = "0.2.1", default-features = false } -alloy-pubsub = { version = "0.2.1", default-features = false } -alloy-rpc-client = { version = "0.2.1", default-features = false } -alloy-rpc-types = { version = "0.2.1", default-features = true } -alloy-serde = { version = "0.2.1", default-features = false } -alloy-signer = { version = "0.2.1", default-features = false } -alloy-signer-aws = { version = "0.2.1", default-features = false } -alloy-signer-gcp = { version = "0.2.1", default-features = false } -alloy-signer-ledger = { version = "0.2.1", default-features = false } -alloy-signer-local = { version = "0.2.1", default-features = false } -alloy-signer-trezor = { version = "0.2.1", default-features = false } -alloy-transport = { version = "0.2.1", default-features = false } -alloy-transport-http = { version = "0.2.1", default-features = false } -alloy-transport-ipc = { version = "0.2.1", default-features = false } -alloy-transport-ws = { version = "0.2.1", default-features = false } +alloy-consensus = { version = "0.3.1", default-features = false } +alloy-contract = { version = "0.3.1", default-features = false } +alloy-eips = { version = "0.3.1", default-features = false } +alloy-genesis = { version = "0.3.1", default-features = false } +alloy-json-rpc = { version = "0.3.1", default-features = false } +alloy-network = { version = "0.3.1", default-features = false } +alloy-provider = { version = "0.3.1", default-features = false } +alloy-pubsub = { version = "0.3.1", default-features = false } +alloy-rpc-client = { version = "0.3.1", default-features = false } +alloy-rpc-types = { version = "0.3.1", default-features = true } +alloy-serde = { version = "0.3.1", default-features = false } +alloy-signer = { version = "0.3.1", default-features = false } +alloy-signer-aws = { version = "0.3.1", default-features = false } +alloy-signer-gcp = { version = "0.3.1", default-features = false } +alloy-signer-ledger = { version = "0.3.1", default-features = false } +alloy-signer-local = { version = "0.3.1", default-features = false } +alloy-signer-trezor = { version = "0.3.1", default-features = false } +alloy-transport = { version = "0.3.1", default-features = false } +alloy-transport-http = { version = "0.3.1", default-features = false } +alloy-transport-ipc = { version = "0.3.1", default-features = false } +alloy-transport-ws = { version = "0.3.1", default-features = false } -alloy-dyn-abi = "0.7.7" -alloy-json-abi = "0.7.7" -alloy-primitives = { version = "0.7.7", features = ["getrandom", "rand"] } -alloy-sol-macro-expander = "0.7.7" -alloy-sol-macro-input = "0.7.7" -alloy-sol-types = "0.7.7" -syn-solidity = "0.7.7" +## alloy-core +alloy-dyn-abi = "0.8.1" +alloy-json-abi = "0.8.1" +alloy-primitives = { version = "0.8.1", features = ["getrandom", "rand"] } +alloy-sol-macro-expander = "0.8.1" +alloy-sol-macro-input = "0.8.1" +alloy-sol-types = "0.8.1" +syn-solidity = "0.8.1" alloy-chains = "0.1" -alloy-rlp = "0.3.3" -alloy-trie = "0.4.1" +alloy-rlp = "0.3" +alloy-trie = "0.5.0" + +## op-alloy for tests in anvil +op-alloy-rpc-types = "0.2.8" ## misc async-trait = "0.1" @@ -252,38 +256,39 @@ yansi = { version = "1.0", features = ["detect-tty", "detect-env"] } tempfile = "3.10" tokio = "1" rayon = "1" - +evmole = "0.5" axum = "0.7" hyper = "1.0" reqwest = { version = "0.12", default-features = false } tower = "0.4" tower-http = "0.5" # soldeer -soldeer = "=0.3.1" +soldeer = "=0.3.4" proptest = "1" comfy-table = "7" [patch.crates-io] -alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "511ae98" } -revm = { git = "https://github.com/bluealloy/revm", rev = "caadc71" } -revm-primitives = { git = "https://github.com/bluealloy/revm", rev = "caadc71" } +# https://github.com/alloy-rs/alloy/pull/1229 + https://github.com/alloy-rs/alloy/pull/1243 +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-contract = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-eips = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-genesis = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-json-rpc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-network-primitives = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-pubsub = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-rpc-types-eth = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-serde = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer-aws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer-gcp = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer-ledger = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer-local = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-signer-trezor = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-transport = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-transport-ipc = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } +alloy-transport-ws = { git = "https://github.com/alloy-rs/alloy", rev = "7fab7ee" } diff --git a/Dockerfile b/Dockerfile index aba0b3986d32..cbdf08b25846 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,7 +19,7 @@ COPY . . RUN git update-index --force-write-index RUN --mount=type=cache,target=/root/.cargo/registry --mount=type=cache,target=/root/.cargo/git --mount=type=cache,target=/opt/foundry/target \ - source $HOME/.profile && cargo build --release --features cast/aws-kms,forge/aws-kms \ + source $HOME/.profile && cargo build --release --features foundry-cast/aws-kms,forge/aws-kms \ && mkdir out \ && mv target/release/forge out/forge \ && mv target/release/cast out/cast \ diff --git a/crates/anvil/Cargo.toml b/crates/anvil/Cargo.toml index a987879852b1..77faec1f1e52 100644 --- a/crates/anvil/Cargo.toml +++ b/crates/anvil/Cargo.toml @@ -122,6 +122,7 @@ alloy-pubsub.workspace = true foundry-test-utils.workspace = true similar-asserts.workspace = true tokio = { workspace = true, features = ["full"] } +op-alloy-rpc-types.workspace = true [features] default = ["cli", "jemalloc"] diff --git a/crates/anvil/core/src/eth/mod.rs b/crates/anvil/core/src/eth/mod.rs index 7c86baad32ae..11342b817757 100644 --- a/crates/anvil/core/src/eth/mod.rs +++ b/crates/anvil/core/src/eth/mod.rs @@ -1,4 +1,4 @@ -use crate::eth::subscription::SubscriptionId; +use crate::{eth::subscription::SubscriptionId, types::ReorgOptions}; use alloy_primitives::{Address, Bytes, TxHash, B256, B64, U256}; use alloy_rpc_types::{ anvil::{Forking, MineOptions}, @@ -768,6 +768,10 @@ pub enum EthRequest { serde(rename = "anvil_removePoolTransactions", with = "sequence") )] RemovePoolTransactions(Address), + + /// Reorg the chain + #[cfg_attr(feature = "serde", serde(rename = "anvil_reorg",))] + Reorg(ReorgOptions), } /// Represents ethereum JSON-RPC API @@ -1595,4 +1599,79 @@ true}]}"#; let value: serde_json::Value = serde_json::from_str(s).unwrap(); let _req = serde_json::from_value::(value).unwrap(); } + + #[test] + fn test_serde_anvil_reorg() { + // TransactionData::JSON + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 100 + }, + 1 + ], + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 200 + }, + 2 + ] + ] + ] + } + "#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + // TransactionData::Raw + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", + 1 + ] + ] + ] + } + "#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + // TransactionData::Raw and TransactionData::JSON + let s = r#" + { + "method": "anvil_reorg", + "params": [ + 5, + [ + [ + "0x19d55c67e1ba8f1bbdfed75f8ad524ebf087e4ecb848a2d19881d7a5e3d2c54e1732cb1b462da3b3fdb05bdf4c4d3c8e3c9fcebdc2ab5fa5d59a3f752888f27e1b", + 1 + ], + [ + { + "from": "0x976EA74026E726554dB657fA54763abd0C3a0aa9", + "to": "0x1199bc69f16FDD6690DC40339EC445FaE1b6DD11", + "value": 200 + }, + 2 + ] + ] + ] + } + "#; + let value: serde_json::Value = serde_json::from_str(s).unwrap(); + let _req = serde_json::from_value::(value).unwrap(); + } } diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index 4320b423af00..7d80450f379e 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -300,7 +300,6 @@ pub fn to_alloy_transaction_with_hash_and_sender( transaction_type: None, max_fee_per_blob_gas: None, blob_versioned_hashes: None, - other: Default::default(), authorization_list: None, }, TypedTransaction::EIP2930(t) => RpcTransaction { @@ -328,7 +327,6 @@ pub fn to_alloy_transaction_with_hash_and_sender( transaction_type: Some(1), max_fee_per_blob_gas: None, blob_versioned_hashes: None, - other: Default::default(), authorization_list: None, }, TypedTransaction::EIP1559(t) => RpcTransaction { @@ -356,7 +354,6 @@ pub fn to_alloy_transaction_with_hash_and_sender( transaction_type: Some(2), max_fee_per_blob_gas: None, blob_versioned_hashes: None, - other: Default::default(), authorization_list: None, }, TypedTransaction::EIP4844(t) => RpcTransaction { @@ -384,7 +381,6 @@ pub fn to_alloy_transaction_with_hash_and_sender( transaction_type: Some(3), max_fee_per_blob_gas: Some(t.tx().tx().max_fee_per_blob_gas), blob_versioned_hashes: Some(t.tx().tx().blob_versioned_hashes.clone()), - other: Default::default(), authorization_list: None, }, TypedTransaction::EIP7702(t) => RpcTransaction { @@ -433,7 +429,6 @@ pub fn to_alloy_transaction_with_hash_and_sender( transaction_type: None, max_fee_per_blob_gas: None, blob_versioned_hashes: None, - other: Default::default(), authorization_list: None, }, } @@ -674,6 +669,36 @@ pub enum TypedTransaction { Deposit(DepositTransaction), } +/// This is a function that demotes TypedTransaction to TransactionRequest for greater flexibility +/// over the type. +/// +/// This function is purely for convience and specific use cases, e.g. RLP encoded transactions +/// decode to TypedTransactions where the API over TypedTransctions is quite strict. +impl TryFrom for TransactionRequest { + type Error = ConversionError; + + fn try_from(value: TypedTransaction) -> Result { + let from = value.recover().map_err(|_| ConversionError::InvalidSignature)?; + let essentials = value.essentials(); + let tx_type = value.r#type(); + Ok(Self { + from: Some(from), + to: Some(value.kind()), + gas_price: essentials.gas_price, + max_fee_per_gas: essentials.max_fee_per_gas, + max_priority_fee_per_gas: essentials.max_priority_fee_per_gas, + max_fee_per_blob_gas: essentials.max_fee_per_blob_gas, + gas: Some(essentials.gas_limit), + value: Some(essentials.value), + input: essentials.input.into(), + nonce: Some(essentials.nonce), + chain_id: essentials.chain_id, + transaction_type: tx_type, + ..Default::default() + }) + } +} + impl TypedTransaction { /// Returns true if the transaction uses dynamic fees: EIP1559 or EIP4844 pub fn is_dynamic_fee(&self) -> bool { diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs index 5c4f35481011..6cc7bfa5aa63 100644 --- a/crates/anvil/core/src/eth/transaction/optimism.rs +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -186,6 +186,44 @@ impl Transaction for DepositTransactionRequest { fn gas_price(&self) -> Option { None } + + fn ty(&self) -> u8 { + 0x7E + } + + // Below fields are not found in a `DepositTransactionRequest` + + fn access_list(&self) -> Option<&alloy_rpc_types::AccessList> { + None + } + + fn authorization_list(&self) -> Option<&[revm::primitives::SignedAuthorization]> { + None + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + None + } + + fn effective_tip_per_gas(&self, _base_fee: u64) -> Option { + None + } + + fn max_fee_per_blob_gas(&self) -> Option { + None + } + + fn max_fee_per_gas(&self) -> u128 { + 0 + } + + fn max_priority_fee_per_gas(&self) -> Option { + None + } + + fn priority_fee_or_price(&self) -> u128 { + 0 + } } impl SignableTransaction for DepositTransactionRequest { diff --git a/crates/anvil/core/src/types.rs b/crates/anvil/core/src/types.rs index 348686abc4ad..ca756358840a 100644 --- a/crates/anvil/core/src/types.rs +++ b/crates/anvil/core/src/types.rs @@ -1,5 +1,7 @@ -use alloy_primitives::{B256, U256}; +use alloy_primitives::{Bytes, B256, U256}; +use alloy_rpc_types::TransactionRequest; +use serde::Deserialize; #[cfg(feature = "serde")] use serde::Serializer; @@ -26,3 +28,19 @@ impl serde::Serialize for Work { } } } + +/// Represents the options used in `anvil_reorg` +#[derive(Debug, Clone, Deserialize)] +pub struct ReorgOptions { + // The depth of the reorg + pub depth: u64, + // List of transaction requests and blocks pairs to be mined into the new chain + pub tx_block_pairs: Vec<(TransactionData, u64)>, +} + +#[derive(Debug, Clone, Deserialize)] +#[serde(untagged)] +pub enum TransactionData { + JSON(TransactionRequest), + Raw(Bytes), +} diff --git a/crates/anvil/src/anvil.rs b/crates/anvil/src/anvil.rs index 1aa204587b20..38bbbbbd62e3 100644 --- a/crates/anvil/src/anvil.rs +++ b/crates/anvil/src/anvil.rs @@ -33,14 +33,13 @@ pub enum AnvilSubcommand { GenerateFigSpec, } -#[tokio::main] -async fn main() -> eyre::Result<()> { +fn main() -> eyre::Result<()> { utils::load_dotenv(); - let mut app = Anvil::parse(); - app.node.evm_opts.resolve_rpc_alias(); + let mut args = Anvil::parse(); + args.node.evm_opts.resolve_rpc_alias(); - if let Some(ref cmd) = app.cmd { + if let Some(cmd) = &args.cmd { match cmd { AnvilSubcommand::Completions { shell } => { clap_complete::generate( @@ -61,9 +60,7 @@ async fn main() -> eyre::Result<()> { } let _ = fdlimit::raise_fd_limit(); - app.node.run().await?; - - Ok(()) + tokio::runtime::Builder::new_multi_thread().enable_all().build()?.block_on(args.node.run()) } #[cfg(test)] diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index d5770f7036a5..cafdf1695a3b 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -1075,7 +1075,6 @@ impl NodeConfig { let provider = Arc::new( ProviderBuilder::new(ð_rpc_url) .timeout(self.fork_request_timeout) - // .timeout_retry(self.fork_request_retries) .initial_backoff(self.fork_retry_backoff.as_millis() as u64) .compute_units_per_second(self.compute_units_per_second) .max_retry(self.fork_request_retries) @@ -1199,7 +1198,7 @@ latest block number: {latest_block}" } } - let block_hash = block.header.hash.unwrap_or_default(); + let block_hash = block.header.hash; let chain_id = if let Some(chain_id) = self.chain_id { chain_id @@ -1218,7 +1217,7 @@ latest block number: {latest_block}" }; let override_chain_id = self.chain_id; // apply changes such as difficulty -> prevrandao and chain specifics for current chain id - apply_chain_and_block_specific_env_changes(env, &block); + apply_chain_and_block_specific_env_changes::(env, &block); let meta = BlockchainDbMeta::new(*env.env.clone(), eth_rpc_url.clone()); let block_chain_db = if self.fork_chain_id.is_some() { @@ -1289,18 +1288,19 @@ async fn derive_block_and_transactions( .ok_or(eyre::eyre!("Failed to get fork block by number"))?; // Filter out transactions that are after the fork transaction - let filtered_transactions: Vec<&Transaction> = transaction_block - .transactions - .as_transactions() - .ok_or(eyre::eyre!("Failed to get transactions from full fork block"))? - .iter() - .take_while_inclusive(|&transaction| transaction.hash != transaction_hash.0) - .collect(); + let filtered_transactions: Vec<&alloy_serde::WithOtherFields> = + transaction_block + .transactions + .as_transactions() + .ok_or(eyre::eyre!("Failed to get transactions from full fork block"))? + .iter() + .take_while_inclusive(|&transaction| transaction.hash != transaction_hash.0) + .collect(); // Convert the transactions to PoolTransactions let force_transactions = filtered_transactions .iter() - .map(|&transaction| PoolTransaction::try_from(transaction.clone())) + .map(|&transaction| PoolTransaction::try_from(transaction.clone().inner)) .collect::, _>>()?; Ok((transaction_block_number.saturating_sub(1), Some(force_transactions))) } @@ -1461,7 +1461,7 @@ async fn find_latest_fork_block, T: Transport + Clone // leeway for _ in 0..2 { if let Some(block) = provider.get_block(num.into(), false.into()).await? { - if block.header.hash.is_some() { + if !block.header.hash.is_zero() { break; } } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 786fdf208fdc..df0e0cf6801c 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -34,7 +34,7 @@ use crate::{ use alloy_consensus::{transaction::eip4844::TxEip4844Variant, Account, TxEnvelope}; use alloy_dyn_abi::TypedData; use alloy_eips::eip2718::Encodable2718; -use alloy_network::eip2718::Decodable2718; +use alloy_network::{eip2718::Decodable2718, BlockResponse}; use alloy_primitives::{Address, Bytes, Parity, TxHash, TxKind, B256, B64, U256, U64}; use alloy_rpc_types::{ anvil::{ @@ -48,7 +48,7 @@ use alloy_rpc_types::{ parity::LocalizedTransactionTrace, }, txpool::{TxpoolContent, TxpoolInspect, TxpoolInspectSummary, TxpoolStatus}, - AccessList, AccessListResult, Block, BlockId, BlockNumberOrTag as BlockNumber, + AccessList, AccessListResult, AnyNetworkBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, FilteredParams, Index, Log, Transaction, }; @@ -64,7 +64,7 @@ use anvil_core::{ }, EthRequest, }, - types::Work, + types::{ReorgOptions, TransactionData, Work}, }; use anvil_rpc::{error::RpcError, response::ResponseResult}; use foundry_common::provider::ProviderBuilder; @@ -79,7 +79,12 @@ use foundry_evm::{ }; use futures::channel::{mpsc::Receiver, oneshot}; use parking_lot::RwLock; -use std::{collections::HashSet, future::Future, sync::Arc, time::Duration}; +use std::{ + collections::{HashMap, HashSet}, + future::Future, + sync::Arc, + time::Duration, +}; /// The client version: `anvil/v{major}.{minor}.{patch}` pub const CLIENT_VERSION: &str = concat!("anvil/v", env!("CARGO_PKG_VERSION")); @@ -440,6 +445,9 @@ impl EthApi { EthRequest::RemovePoolTransactions(address) => { self.anvil_remove_pool_transactions(address).await.to_rpc_result() } + EthRequest::Reorg(reorg_options) => { + self.anvil_reorg(reorg_options).await.to_rpc_result() + } } } @@ -713,7 +721,7 @@ impl EthApi { /// Returns block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash(&self, hash: B256) -> Result> { + pub async fn block_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash(hash).await } @@ -721,7 +729,7 @@ impl EthApi { /// Returns a _full_ block with given hash. /// /// Handler for ETH RPC call: `eth_getBlockByHash` - pub async fn block_by_hash_full(&self, hash: B256) -> Result> { + pub async fn block_by_hash_full(&self, hash: B256) -> Result> { node_info!("eth_getBlockByHash"); self.backend.block_by_hash_full(hash).await } @@ -729,7 +737,7 @@ impl EthApi { /// Returns block with given number. /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number(&self, number: BlockNumber) -> Result> { + pub async fn block_by_number(&self, number: BlockNumber) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { return Ok(Some(self.pending_block().await)); @@ -741,7 +749,10 @@ impl EthApi { /// Returns a _full_ block with given number /// /// Handler for ETH RPC call: `eth_getBlockByNumber` - pub async fn block_by_number_full(&self, number: BlockNumber) -> Result> { + pub async fn block_by_number_full( + &self, + number: BlockNumber, + ) -> Result> { node_info!("eth_getBlockByNumber"); if number == BlockNumber::Pending { return Ok(self.pending_block_full().await); @@ -770,7 +781,7 @@ impl EthApi { pub async fn block_transaction_count_by_hash(&self, hash: B256) -> Result> { node_info!("eth_getBlockTransactionCountByHash"); let block = self.backend.block_by_hash(hash).await?; - let txs = block.map(|b| match b.transactions { + let txs = block.map(|b| match b.transactions() { BlockTransactions::Full(txs) => U256::from(txs.len()), BlockTransactions::Hashes(txs) => U256::from(txs.len()), BlockTransactions::Uncle => U256::from(0), @@ -792,7 +803,7 @@ impl EthApi { return Ok(Some(U256::from(block.transactions.len()))); } let block = self.backend.block_by_number(block_number).await?; - let txs = block.map(|b| match b.transactions { + let txs = block.map(|b| match b.transactions() { BlockTransactions::Full(txs) => U256::from(txs.len()), BlockTransactions::Hashes(txs) => U256::from(txs.len()), BlockTransactions::Uncle => U256::from(0), @@ -995,6 +1006,7 @@ impl EthApi { if data.is_empty() { return Err(BlockchainError::EmptyRawTransactionData); } + let transaction = TypedTransaction::decode_2718(&mut data) .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; @@ -1229,7 +1241,7 @@ impl EthApi { &self, block_hash: B256, idx: Index, - ) -> Result> { + ) -> Result> { node_info!("eth_getUncleByBlockHashAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Hash(block_hash.into()))).await?; @@ -1249,7 +1261,7 @@ impl EthApi { &self, block_number: BlockNumber, idx: Index, - ) -> Result> { + ) -> Result> { node_info!("eth_getUncleByBlockNumberAndIndex"); let number = self.backend.ensure_block_number(Some(BlockId::Number(block_number))).await?; if let Some(fork) = self.get_fork() { @@ -1915,6 +1927,124 @@ impl EthApi { Ok(()) } + /// Reorg the chain to a specific depth and mine new blocks back to the cannonical height. + /// + /// e.g depth = 3 + /// A -> B -> C -> D -> E + /// A -> B -> C' -> D' -> E' + /// + /// Depth specifies the height to reorg the chain back to. Depth must not exceed the current + /// chain height, i.e. can't reorg past the genesis block. + /// + /// Optionally supply a list of transaction and block pairs that will populate the reorged + /// blocks. The maximum block number of the pairs must not exceed the specified depth. + /// + /// Handler for RPC call: `anvil_reorg` + pub async fn anvil_reorg(&self, options: ReorgOptions) -> Result<()> { + node_info!("anvil_reorg"); + let depth = options.depth; + let tx_block_pairs = options.tx_block_pairs; + + // Check reorg depth doesn't exceed current chain height + let current_height = self.backend.best_number(); + let common_height = current_height.checked_sub(depth).ok_or(BlockchainError::RpcError( + RpcError::invalid_params(format!( + "Reorg depth must not exceed current chain height: current height {current_height}, depth {depth}" + )), + ))?; + + // Get the common ancestor block + let common_block = + self.backend.get_block(common_height).ok_or(BlockchainError::BlockNotFound)?; + + // Convert the transaction requests to pool transactions if they exist, otherwise use empty + // hashmap + let block_pool_txs = if tx_block_pairs.is_empty() { + HashMap::new() + } else { + let mut pairs = tx_block_pairs; + + // Check the maximum block supplied number will not exceed the reorged chain height + if let Some((_, num)) = pairs.iter().find(|(_, num)| *num >= depth) { + return Err(BlockchainError::RpcError(RpcError::invalid_params(format!( + "Block number for reorg tx will exceed the reorged chain height. Block number {num} must not exceed (depth-1) {}", + depth-1 + )))); + } + + // Sort by block number to make it easier to manage new nonces + pairs.sort_by_key(|a| a.1); + + // Manage nonces for each signer + // address -> cumulative nonce + let mut nonces: HashMap = HashMap::new(); + + let mut txs: HashMap>> = HashMap::new(); + for pair in pairs { + let (tx_data, block_index) = pair; + + let mut tx_req = match tx_data { + TransactionData::JSON(req) => WithOtherFields::new(req), + TransactionData::Raw(bytes) => { + let mut data = bytes.as_ref(); + let decoded = TypedTransaction::decode_2718(&mut data) + .map_err(|_| BlockchainError::FailedToDecodeSignedTransaction)?; + let request = + TransactionRequest::try_from(decoded.clone()).map_err(|_| { + BlockchainError::RpcError(RpcError::invalid_params( + "Failed to convert raw transaction", + )) + })?; + WithOtherFields::new(request) + } + }; + + let from = tx_req.from.map(Ok).unwrap_or_else(|| { + self.accounts()?.first().cloned().ok_or(BlockchainError::NoSignerAvailable) + })?; + + // Get the nonce at the common block + let curr_nonce = nonces.entry(from).or_insert( + self.get_transaction_count(from, Some(common_block.header.number.into())) + .await?, + ); + + // Estimate gas + if tx_req.gas.is_none() { + if let Ok(gas) = self.estimate_gas(tx_req.clone(), None, None).await { + tx_req.gas = Some(gas.to()); + } + } + + // Build typed transaction request + let typed = self.build_typed_tx_request(tx_req, *curr_nonce)?; + + // Increment nonce + *curr_nonce += 1; + + // Handle signer and convert to pending transaction + let pending = if self.is_impersonated(from) { + let bypass_signature = self.impersonated_signature(&typed); + let transaction = sign::build_typed_transaction(typed, bypass_signature)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::with_impersonated(transaction, from) + } else { + let transaction = self.sign_request(&from, typed)?; + self.ensure_typed_transaction_supported(&transaction)?; + PendingTransaction::new(transaction)? + }; + + let pooled = PoolTransaction::new(pending); + txs.entry(block_index).or_default().push(Arc::new(pooled)); + } + + txs + }; + + self.backend.reorg(depth, block_pool_txs, common_block).await?; + Ok(()) + } + /// Snapshot the state of the blockchain at the current block. /// /// Handler for RPC call: `evm_snapshot` @@ -2011,7 +2141,10 @@ impl EthApi { /// **Note**: This behaves exactly as [Self::evm_mine] but returns different output, for /// compatibility reasons, this is a separate call since `evm_mine` is not an anvil original. /// and `ganache` may change the `0x0` placeholder. - pub async fn evm_mine_detailed(&self, opts: Option) -> Result> { + pub async fn evm_mine_detailed( + &self, + opts: Option, + ) -> Result> { node_info!("evm_mine_detailed"); let mined_blocks = self.do_evm_mine(opts).await?; @@ -2024,7 +2157,7 @@ impl EthApi { if let Some(mut block) = self.backend.block_by_number_full(BlockNumber::Number(block_num)).await? { - let mut block_txs = match block.transactions { + let block_txs = match block.transactions_mut() { BlockTransactions::Full(txs) => txs, BlockTransactions::Hashes(_) | BlockTransactions::Uncle => unreachable!(), }; @@ -2056,7 +2189,7 @@ impl EthApi { } } } - block.transactions = BlockTransactions::Full(block_txs); + block.transactions = BlockTransactions::Full(block_txs.to_vec()); blocks.push(block); } } @@ -2507,19 +2640,19 @@ impl EthApi { } /// Returns the pending block with tx hashes - async fn pending_block(&self) -> Block { + async fn pending_block(&self) -> AnyNetworkBlock { let transactions = self.pool.ready_transactions().collect::>(); let info = self.backend.pending_block(transactions).await; self.backend.convert_block(info.block) } /// Returns the full pending block with `Transaction` objects - async fn pending_block_full(&self) -> Option { + async fn pending_block_full(&self) -> Option { let transactions = self.pool.ready_transactions().collect::>(); let BlockInfo { block, transactions, receipts: _ } = self.backend.pending_block(transactions).await; - let partial_block = self.backend.convert_block(block.clone()); + let mut partial_block = self.backend.convert_block(block.clone()); let mut block_transactions = Vec::with_capacity(block.transactions.len()); let base_fee = self.backend.base_fee(); @@ -2534,10 +2667,12 @@ impl EthApi { Some(info), Some(base_fee), ); - block_transactions.push(tx.inner); + block_transactions.push(tx); } - Some(partial_block.into_full_block(block_transactions)) + partial_block.transactions = BlockTransactions::from(block_transactions); + + Some(partial_block) } fn build_typed_tx_request( diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index 0a63ad453522..c661eeaa89b8 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -3,6 +3,7 @@ use crate::eth::{backend::db::Db, error::BlockchainError, pool::transactions::PoolTransaction}; use alloy_consensus::Account; use alloy_eips::eip2930::AccessListResult; +use alloy_network::BlockResponse; use alloy_primitives::{Address, Bytes, StorageValue, B256, U256}; use alloy_provider::{ ext::{DebugApi, TraceApi}, @@ -14,7 +15,7 @@ use alloy_rpc_types::{ geth::{GethDebugTracingOptions, GethTrace}, parity::LocalizedTransactionTrace as Trace, }, - Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + AnyNetworkBlock, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, EIP1186AccountProofResponse, FeeHistory, Filter, Log, Transaction, }; use alloy_serde::WithOtherFields; @@ -82,12 +83,12 @@ impl ClientFork { .get_block(block_number, false.into()) .await? .ok_or(BlockchainError::BlockNotFound)?; - let block_hash = block.header.hash.ok_or(BlockchainError::BlockNotFound)?; + let block_hash = block.header.hash; let timestamp = block.header.timestamp; let base_fee = block.header.base_fee_per_gas; let total_difficulty = block.header.total_difficulty.unwrap_or_default(); - let number = block.header.number.ok_or(BlockchainError::BlockNotFound)?; + let number = block.header.number; self.config.write().update_block(number, block_hash, timestamp, base_fee, total_difficulty); self.clear_cached_storage(); @@ -283,10 +284,10 @@ impl ClientFork { index: usize, ) -> Result>, TransportError> { if let Some(block) = self.block_by_number(number).await? { - match block.transactions { + match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(WithOtherFields::new(tx.clone()))); + return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { @@ -307,10 +308,10 @@ impl ClientFork { index: usize, ) -> Result>, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { - match block.transactions { + match block.transactions() { BlockTransactions::Full(txs) => { if let Some(tx) = txs.get(index) { - return Ok(Some(WithOtherFields::new(tx.clone()))); + return Ok(Some(tx.clone())); } } BlockTransactions::Hashes(hashes) => { @@ -440,7 +441,10 @@ impl ClientFork { Ok(None) } - pub async fn block_by_hash(&self, hash: B256) -> Result, TransportError> { + pub async fn block_by_hash( + &self, + hash: B256, + ) -> Result, TransportError> { if let Some(mut block) = self.storage_read().blocks.get(&hash).cloned() { block.transactions.convert_to_hashes(); return Ok(Some(block)); @@ -452,7 +456,10 @@ impl ClientFork { })) } - pub async fn block_by_hash_full(&self, hash: B256) -> Result, TransportError> { + pub async fn block_by_hash_full( + &self, + hash: B256, + ) -> Result, TransportError> { if let Some(block) = self.storage_read().blocks.get(&hash).cloned() { return Ok(Some(self.convert_to_full_block(block))); } @@ -462,7 +469,7 @@ impl ClientFork { pub async fn block_by_number( &self, block_number: u64, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(mut block) = self .storage_read() .hashes @@ -483,7 +490,7 @@ impl ClientFork { pub async fn block_by_number_full( &self, block_number: u64, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self .storage_read() .hashes @@ -500,19 +507,17 @@ impl ClientFork { async fn fetch_full_block( &self, block_id: impl Into, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self.provider().get_block(block_id.into(), true.into()).await? { - let hash = block.header.hash.unwrap(); - let block_number = block.header.number.unwrap(); + let hash = block.header.hash; + let block_number = block.header.number; let mut storage = self.storage_write(); // also insert all transactions - let block_txs = match block.clone().transactions { - BlockTransactions::Full(txs) => txs, + let block_txs = match block.transactions() { + BlockTransactions::Full(txs) => txs.to_owned(), _ => vec![], }; - storage - .transactions - .extend(block_txs.iter().map(|tx| (tx.hash, WithOtherFields::new(tx.clone())))); + storage.transactions.extend(block_txs.iter().map(|tx| (tx.hash, tx.clone()))); storage.hashes.insert(block_number, hash); storage.blocks.insert(hash, block.clone()); return Ok(Some(block)); @@ -525,7 +530,7 @@ impl ClientFork { &self, hash: B256, index: usize, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_hash(hash).await? { return self.uncles_by_block_and_index(block, index).await; } @@ -536,7 +541,7 @@ impl ClientFork { &self, number: u64, index: usize, - ) -> Result, TransportError> { + ) -> Result, TransportError> { if let Some(block) = self.block_by_number(number).await? { return self.uncles_by_block_and_index(block, index).await; } @@ -545,11 +550,11 @@ impl ClientFork { async fn uncles_by_block_and_index( &self, - block: Block, + block: AnyNetworkBlock, index: usize, - ) -> Result, TransportError> { - let block_hash = block.header.hash.expect("Missing block hash"); - let block_number = block.header.number.expect("Missing block number"); + ) -> Result, TransportError> { + let block_hash = block.header.hash; + let block_number = block.header.number; if let Some(uncles) = self.storage_read().uncles.get(&block_hash) { return Ok(uncles.get(index).cloned()); } @@ -568,7 +573,7 @@ impl ClientFork { } /// Converts a block of hashes into a full block - fn convert_to_full_block(&self, block: Block) -> Block { + fn convert_to_full_block(&self, mut block: AnyNetworkBlock) -> AnyNetworkBlock { let storage = self.storage.read(); let block_txs_len = match block.transactions { BlockTransactions::Full(ref txs) => txs.len(), @@ -579,11 +584,13 @@ impl ClientFork { let mut transactions = Vec::with_capacity(block_txs_len); for tx in block.transactions.hashes() { if let Some(tx) = storage.transactions.get(&tx).cloned() { - transactions.push(tx.inner); + transactions.push(tx); } } // TODO: fix once blocks have generic transactions - block.into_full_block(transactions) + block.inner.transactions = BlockTransactions::Full(transactions); + + block } } @@ -668,8 +675,8 @@ impl ClientForkConfig { /// This is used as a cache so repeated requests to the same data are not sent to the remote client #[derive(Clone, Debug, Default)] pub struct ForkedStorage { - pub uncles: HashMap>, - pub blocks: HashMap, + pub uncles: HashMap>, + pub blocks: HashMap, pub hashes: HashMap, pub transactions: HashMap>, pub transaction_receipts: HashMap, diff --git a/crates/anvil/src/eth/backend/info.rs b/crates/anvil/src/eth/backend/info.rs index 448dc660a109..3ac359619d83 100644 --- a/crates/anvil/src/eth/backend/info.rs +++ b/crates/anvil/src/eth/backend/info.rs @@ -2,7 +2,8 @@ use crate::mem::Backend; use alloy_primitives::B256; -use alloy_rpc_types::Block as AlloyBlock; +use alloy_rpc_types::{Block as AlloyBlock, Transaction}; +use alloy_serde::WithOtherFields; use anvil_core::eth::{block::Block, transaction::TypedReceipt}; use std::{fmt, sync::Arc}; @@ -42,7 +43,10 @@ impl StorageInfo { } /// Returns the block with the given hash in the format of the ethereum API - pub fn eth_block(&self, hash: B256) -> Option { + pub fn eth_block( + &self, + hash: B256, + ) -> Option>>> { let block = self.block(hash)?; Some(self.backend.convert_block(block)) } diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index ebb9d922a162..eeafccecabc8 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -30,7 +30,7 @@ use crate::{ storage::{BlockchainStorage, InMemoryBlockStates, MinedBlockOutcome}, }, revm::{db::DatabaseRef, primitives::AccountInfo}, - NodeConfig, PrecompileFactory, + ForkChoice, NodeConfig, PrecompileFactory, }; use alloy_consensus::{Account, Header, Receipt, ReceiptWithBloom}; use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; @@ -48,9 +48,10 @@ use alloy_rpc_types::{ }, parity::LocalizedTransactionTrace, }, - AccessList, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, - EIP1186AccountProofResponse as AccountProof, EIP1186StorageProof as StorageProof, Filter, - FilteredParams, Header as AlloyHeader, Index, Log, Transaction, TransactionReceipt, + AccessList, AnyNetworkBlock, Block as AlloyBlock, BlockId, BlockNumberOrTag as BlockNumber, + BlockTransactions, EIP1186AccountProofResponse as AccountProof, + EIP1186StorageProof as StorageProof, Filter, FilteredParams, Header as AlloyHeader, Index, Log, + Transaction, TransactionReceipt, }; use alloy_serde::WithOtherFields; use alloy_trie::{proof::ProofRetainer, HashBuilder, Nibbles}; @@ -426,37 +427,51 @@ impl Backend { .ok_or(BlockchainError::BlockNotFound)?; // update all settings related to the forked block { - let mut env = self.env.write(); - env.cfg.chain_id = fork.chain_id(); - - env.block = BlockEnv { - number: U256::from(fork_block_number), - timestamp: U256::from(fork_block.header.timestamp), - gas_limit: U256::from(fork_block.header.gas_limit), - difficulty: fork_block.header.difficulty, - prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), - // Keep previous `coinbase` and `basefee` value - coinbase: env.block.coinbase, - basefee: env.block.basefee, - ..env.block.clone() - }; + if let Some(fork_url) = forking.json_rpc_url { + // Set the fork block number + let mut node_config = self.node_config.write().await; + node_config.fork_choice = Some(ForkChoice::Block(fork_block_number)); - self.time.reset(env.block.timestamp.to::()); + let mut env = self.env.read().clone(); + let (forked_db, client_fork_config) = + node_config.setup_fork_db_config(fork_url, &mut env, &self.fees).await; - // this is the base fee of the current block, but we need the base fee of - // the next block - let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - fork_block.header.gas_used, - fork_block.header.gas_limit, - fork_block.header.base_fee_per_gas.unwrap_or_default(), - ); + *self.db.write().await = Box::new(forked_db); + let fork = ClientFork::new(client_fork_config, Arc::clone(&self.db)); + *self.fork.write() = Some(fork); + *self.env.write() = env; + } else { + let mut env = self.env.write(); + env.cfg.chain_id = fork.chain_id(); + env.block = BlockEnv { + number: U256::from(fork_block_number), + timestamp: U256::from(fork_block.header.timestamp), + gas_limit: U256::from(fork_block.header.gas_limit), + difficulty: fork_block.header.difficulty, + prevrandao: Some(fork_block.header.mix_hash.unwrap_or_default()), + // Keep previous `coinbase` and `basefee` value + coinbase: env.block.coinbase, + basefee: env.block.basefee, + ..env.block.clone() + }; - self.fees.set_base_fee(next_block_base_fee); + // this is the base fee of the current block, but we need the base fee of + // the next block + let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( + fork_block.header.gas_used, + fork_block.header.gas_limit, + fork_block.header.base_fee_per_gas.unwrap_or_default(), + ); + + self.fees.set_base_fee(next_block_base_fee); + } + + // reset the time to the timestamp of the forked block + self.time.reset(fork_block.header.timestamp); // also reset the total difficulty self.blockchain.storage.write().total_difficulty = fork.total_difficulty(); } - // reset storage *self.blockchain.storage.write() = BlockchainStorage::forked( fork.block_number(), @@ -1125,6 +1140,13 @@ impl Backend { }).await? } + /// ## EVM settings + /// + /// This modifies certain EVM settings to mirror geth's `SkipAccountChecks` when transacting requests, see also: : + /// + /// - `disable_eip3607` is set to `true` + /// - `disable_base_fee` is set to `true` + /// - `nonce` is set to `None` fn build_call_env( &self, request: WithOtherFields, @@ -1139,10 +1161,11 @@ impl Backend { gas, value, input, - nonce, access_list, blob_versioned_hashes, authorization_list, + // nonce is always ignored for calls + nonce: _, sidecar: _, chain_id: _, transaction_type: _, @@ -1190,7 +1213,8 @@ impl Backend { value: value.unwrap_or_default(), data: input.into_input().unwrap_or_default(), chain_id: None, - nonce, + // set nonce to None so that the correct nonce is chosen by the EVM + nonce: None, access_list: access_list.unwrap_or_default().into(), blob_hashes: blob_versioned_hashes.unwrap_or_default(), optimism: OptimismFields { enveloped_tx: Some(Bytes::new()), ..Default::default() }, @@ -1514,7 +1538,10 @@ impl Backend { } } - pub async fn block_by_hash(&self, hash: B256) -> Result, BlockchainError> { + pub async fn block_by_hash( + &self, + hash: B256, + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.mined_block_by_hash(hash) { return Ok(tx); @@ -1530,7 +1557,7 @@ impl Backend { pub async fn block_by_hash_full( &self, hash: B256, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by hash {:?}", hash); if let tx @ Some(_) = self.get_full_block(hash) { return Ok(tx); @@ -1543,7 +1570,7 @@ impl Backend { Ok(None) } - fn mined_block_by_hash(&self, hash: B256) -> Option { + fn mined_block_by_hash(&self, hash: B256) -> Option { let block = self.blockchain.get_block_by_hash(&hash)?; Some(self.convert_block(block)) } @@ -1579,7 +1606,7 @@ impl Backend { pub async fn block_by_number( &self, number: BlockNumber, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.mined_block_by_number(number) { return Ok(tx); @@ -1598,7 +1625,7 @@ impl Backend { pub async fn block_by_number_full( &self, number: BlockNumber, - ) -> Result, BlockchainError> { + ) -> Result, BlockchainError> { trace!(target: "backend", "get block by number {:?}", number); if let tx @ Some(_) = self.get_full_block(number) { return Ok(tx); @@ -1651,22 +1678,24 @@ impl Backend { self.blockchain.get_block_by_hash(&hash) } - pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { + pub fn mined_block_by_number(&self, number: BlockNumber) -> Option { let block = self.get_block(number)?; let mut block = self.convert_block(block); block.transactions.convert_to_hashes(); Some(block) } - pub fn get_full_block(&self, id: impl Into) -> Option { + pub fn get_full_block(&self, id: impl Into) -> Option { let block = self.get_block(id)?; let transactions = self.mined_transactions_in_block(&block)?; - let block = self.convert_block(block); - Some(block.into_full_block(transactions.into_iter().map(|t| t.inner).collect())) + let mut block = self.convert_block(block); + block.inner.transactions = BlockTransactions::Full(transactions); + + Some(block) } /// Takes a block as it's stored internally and returns the eth api conform block format. - pub fn convert_block(&self, block: Block) -> AlloyBlock { + pub fn convert_block(&self, block: Block) -> AnyNetworkBlock { let size = U256::from(alloy_rlp::encode(&block).len() as u32); let Block { header, transactions, .. } = block; @@ -1696,16 +1725,16 @@ impl Backend { parent_beacon_block_root, } = header; - let mut block = AlloyBlock { + let block = AlloyBlock { header: AlloyHeader { - hash: Some(hash), + hash, parent_hash, uncles_hash: ommers_hash, miner: beneficiary, state_root, transactions_root, receipts_root, - number: Some(number), + number, gas_used, gas_limit, extra_data: extra_data.0.into(), @@ -1728,9 +1757,10 @@ impl Backend { ), uncles: vec![], withdrawals: None, - other: Default::default(), }; + let mut block = WithOtherFields::new(block); + // If Arbitrum, apply chain specifics to converted block. if let Ok( NamedChain::Arbitrum | @@ -1740,7 +1770,7 @@ impl Backend { ) = NamedChain::try_from(self.env.read().env.cfg.chain_id) { // Block number is the best number. - block.header.number = Some(self.best_number()); + block.header.number = self.best_number(); // Set `l1BlockNumber` field. block.other.insert("l1BlockNumber".to_string(), number.into()); } @@ -1760,13 +1790,13 @@ impl Backend { let current = self.best_number(); let requested = match block_id.map(Into::into).unwrap_or(BlockId::Number(BlockNumber::Latest)) { - BlockId::Hash(hash) => self - .block_by_hash(hash.block_hash) - .await? - .ok_or(BlockchainError::BlockNotFound)? - .header - .number - .ok_or(BlockchainError::BlockNotFound)?, + BlockId::Hash(hash) => { + self.block_by_hash(hash.block_hash) + .await? + .ok_or(BlockchainError::BlockNotFound)? + .header + .number + } BlockId::Number(num) => match num { BlockNumber::Latest | BlockNumber::Pending => self.best_number(), BlockNumber::Earliest => U64::ZERO.to::(), @@ -1832,7 +1862,7 @@ impl Backend { if let Some((block_hash, block)) = self .block_by_number(BlockNumber::Number(block_number.to::())) .await? - .and_then(|block| Some((block.header.hash?, block))) + .map(|block| (block.header.hash, block)) { if let Some(state) = self.states.write().get(&block_hash) { let block = BlockEnv { @@ -2281,8 +2311,8 @@ impl Backend { number: BlockNumber, index: Index, ) -> Result>, BlockchainError> { - if let Some(hash) = self.mined_block_by_number(number).and_then(|b| b.header.hash) { - return Ok(self.mined_transaction_by_block_hash_and_index(hash, index)); + if let Some(block) = self.mined_block_by_number(number) { + return Ok(self.mined_transaction_by_block_hash_and_index(block.header.hash, index)); } if let Some(fork) = self.get_fork() { @@ -2440,6 +2470,76 @@ impl Backend { .lock() .retain(|tx| tx.unbounded_send(notification.clone()).is_ok()); } + + /// Reorg the chain to a common height and execute blocks to build new chain. + /// + /// The state of the chain is rewound using `rewind` to the common block, including the db, + /// storage, and env. + /// + /// Finally, `do_mine_block` is called to create the new chain. + pub async fn reorg( + &self, + depth: u64, + tx_pairs: HashMap>>, + common_block: Block, + ) -> Result<(), BlockchainError> { + // Get the database at the common block + let common_state = { + let mut state = self.states.write(); + let state_db = state + .get(&common_block.header.hash_slow()) + .ok_or(BlockchainError::DataUnavailable)?; + let db_full = state_db.maybe_as_full_db().ok_or(BlockchainError::DataUnavailable)?; + db_full.clone() + }; + + { + // Set state to common state + self.db.write().await.clear(); + for (address, acc) in common_state { + for (key, value) in acc.storage { + self.db.write().await.set_storage_at(address, key, value)?; + } + self.db.write().await.insert_account(address, acc.info); + } + } + + { + // Unwind the storage back to the common ancestor + self.blockchain + .storage + .write() + .unwind_to(common_block.header.number, common_block.header.hash_slow()); + + // Set environment back to common block + let mut env = self.env.write(); + env.block = BlockEnv { + number: U256::from(common_block.header.number), + timestamp: U256::from(common_block.header.timestamp), + gas_limit: U256::from(common_block.header.gas_limit), + difficulty: common_block.header.difficulty, + prevrandao: Some(common_block.header.mix_hash), + coinbase: env.block.coinbase, + basefee: env.block.basefee, + ..env.block.clone() + }; + self.time.reset(env.block.timestamp.to::()); + } + + // Create the new reorged chain, filling the blocks with transactions if supplied + for i in 0..depth { + let to_be_mined = tx_pairs.get(&i).cloned().unwrap_or_else(Vec::new); + let outcome = self.do_mine_block(to_be_mined).await; + node_info!( + " Mined reorg block number {}. With {} valid txs and with invalid {} txs", + outcome.block_number, + outcome.included.len(), + outcome.invalid.len() + ); + } + + Ok(()) + } } /// Get max nonce from transaction pool by address diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index aaa2f29e725a..2b5886a6f176 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -271,6 +271,23 @@ impl BlockchainStorage { } } + /// Unwind the chain state back to the given block in storage. + /// + /// The block identified by `block_number` and `block_hash` is __non-inclusive__, i.e. it will + /// remain in the state. + pub fn unwind_to(&mut self, block_number: u64, block_hash: B256) { + let best_num: u64 = self.best_number.try_into().unwrap_or(0); + for i in (block_number + 1)..=best_num { + if let Some(hash) = self.hashes.remove(&U64::from(i)) { + if let Some(block) = self.blocks.remove(&hash) { + self.remove_block_transactions_by_number(block.header.number); + } + } + } + self.best_hash = block_hash; + self.best_number = U64::from(block_number); + } + #[allow(unused)] pub fn empty() -> Self { Self { diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index b8b475deba00..ada9e6c53233 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -231,6 +231,12 @@ pub enum InvalidTransactionError { /// Thrown when there are no `blob_hashes` in the transaction. #[error("There should be at least one blob in a Blob transaction.")] EmptyBlobs, + /// Thrown when an access list is used before the berlin hard fork. + #[error("EIP-7702 authorization lists are not supported before the Prague hardfork")] + AuthorizationListNotSupported, + /// Forwards error from the revm + #[error(transparent)] + Revm(revm::primitives::InvalidTransaction), } impl From for InvalidTransactionError { @@ -263,7 +269,14 @@ impl From for InvalidTransactionError { InvalidTransaction::BlobVersionNotSupported => Self::BlobVersionNotSupported, InvalidTransaction::EmptyBlobs => Self::EmptyBlobs, InvalidTransaction::TooManyBlobs { max, have } => Self::TooManyBlobs(max, have), - _ => todo!(), + InvalidTransaction::AuthorizationListNotSupported => { + Self::AuthorizationListNotSupported + } + InvalidTransaction::AuthorizationListInvalidFields | + InvalidTransaction::InvalidAuthorizationList(_) | + InvalidTransaction::OptimismError(_) | + InvalidTransaction::EofCrateShouldHaveToAddress | + InvalidTransaction::EmptyAuthorizationList => Self::Revm(err), } } } diff --git a/crates/anvil/src/eth/otterscan/api.rs b/crates/anvil/src/eth/otterscan/api.rs index 0195cee69ba8..e73fe4dd6647 100644 --- a/crates/anvil/src/eth/otterscan/api.rs +++ b/crates/anvil/src/eth/otterscan/api.rs @@ -3,20 +3,23 @@ use crate::eth::{ macros::node_info, EthApi, }; +use alloy_network::BlockResponse; use alloy_primitives::{Address, Bytes, B256, U256}; use alloy_rpc_types::{ trace::{ otterscan::{ BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions, - OtsReceipt, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, + OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts, }, parity::{ Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace, RewardAction, TraceOutput, }, }, - Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + AnyNetworkBlock, Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions, + Transaction, }; +use alloy_serde::WithOtherFields; use itertools::Itertools; use futures::future::join_all; @@ -84,7 +87,10 @@ impl EthApi { /// /// As a faster alternative to `eth_getBlockByNumber` (by excluding uncle block /// information), which is not relevant in the context of an anvil node - pub async fn erigon_get_header_by_number(&self, number: BlockNumber) -> Result> { + pub async fn erigon_get_header_by_number( + &self, + number: BlockNumber, + ) -> Result> { node_info!("ots_getApiLevel"); self.backend.block_by_number(number).await @@ -172,7 +178,7 @@ impl EthApi { number: u64, page: usize, page_size: usize, - ) -> Result { + ) -> Result>> { node_info!("ots_getBlockTransactions"); match self.backend.block_by_number_full(number.into()).await? { @@ -346,7 +352,7 @@ impl EthApi { /// based on the existing list. /// /// Therefore we keep it simple by keeping the data in the response - pub async fn build_ots_block_details(&self, block: Block) -> Result { + pub async fn build_ots_block_details(&self, block: AnyNetworkBlock) -> Result { if block.transactions.is_uncle() { return Err(BlockchainError::DataUnavailable); } @@ -369,8 +375,18 @@ impl EthApi { .iter() .fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price); + let Block { header, uncles, transactions, size, withdrawals } = block.inner; + + let block = OtsSlimBlock { + header, + uncles, + transaction_count: transactions.len(), + size, + withdrawals, + }; + Ok(BlockDetails { - block: block.into(), + block, total_fees: U256::from(total_fees), // issuance has no meaningful value in anvil's backend. just default to 0 issuance: Default::default(), @@ -383,44 +399,47 @@ impl EthApi { /// [`ots_getBlockTransactions`]: https://github.com/otterscan/otterscan/blob/develop/docs/custom-jsonrpc.md#ots_getblockdetails pub async fn build_ots_block_tx( &self, - mut block: Block, + mut block: AnyNetworkBlock, page: usize, page_size: usize, - ) -> Result { + ) -> Result>> { if block.transactions.is_uncle() { return Err(BlockchainError::DataUnavailable); } - block.transactions = match block.transactions { + block.transactions = match block.transactions() { BlockTransactions::Full(txs) => BlockTransactions::Full( - txs.into_iter().skip(page * page_size).take(page_size).collect(), + txs.iter().skip(page * page_size).take(page_size).cloned().collect(), ), BlockTransactions::Hashes(txs) => BlockTransactions::Hashes( - txs.into_iter().skip(page * page_size).take(page_size).collect(), + txs.iter().skip(page * page_size).take(page_size).cloned().collect(), ), BlockTransactions::Uncle => unreachable!(), }; let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash)); - let receipts = join_all(receipt_futs) - .await - .into_iter() - .map(|r| match r { - Ok(Some(r)) => { - let timestamp = - self.backend.get_block(r.block_number.unwrap()).unwrap().header.timestamp; - let receipt = r.map_inner(OtsReceipt::from); - let res = OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }; - Ok(res) - } - _ => Err(BlockchainError::DataUnavailable), - }) - .collect::>>()?; + let receipts = join_all(receipt_futs.map(|r| async { + if let Ok(Some(r)) = r.await { + let block = self.block_by_number(r.block_number.unwrap().into()).await?; + let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + let receipt = r.map_inner(OtsReceipt::from); + Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) + } else { + Err(BlockchainError::BlockNotFound) + } + })) + .await + .into_iter() + .collect::>>()?; - let fullblock: OtsBlock = block.into(); + let transaction_count = block.transactions().len(); + let fullblock = OtsBlock { block: block.inner, transaction_count }; - Ok(OtsBlockTransactions { fullblock, receipts }) + let ots_block_txs = + OtsBlockTransactions::> { fullblock, receipts }; + + Ok(ots_block_txs) } pub async fn build_ots_search_transactions( @@ -438,23 +457,18 @@ impl EthApi { Ok(Some(t)) => Ok(t.inner), _ => Err(BlockchainError::DataUnavailable), }) - .collect::>()?; - - let receipts = join_all(hashes.iter().map(|hash| async { - match self.transaction_receipt(*hash).await { - Ok(Some(receipt)) => { - let timestamp = self - .backend - .get_block(receipt.block_number.unwrap()) - .unwrap() - .header - .timestamp; - let receipt = receipt.map_inner(OtsReceipt::from); - let res = OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }; - Ok(res) - } - Ok(None) => Err(BlockchainError::DataUnavailable), - Err(e) => Err(e), + .collect::>>()?; + + let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash)); + + let receipts = join_all(receipt_futs.map(|r| async { + if let Ok(Some(r)) = r.await { + let block = self.block_by_number(r.block_number.unwrap().into()).await?; + let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp; + let receipt = r.map_inner(OtsReceipt::from); + Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) }) + } else { + Err(BlockchainError::BlockNotFound) } })) .await diff --git a/crates/anvil/src/eth/pool/transactions.rs b/crates/anvil/src/eth/pool/transactions.rs index 502ee1e7886f..f0987572ba0f 100644 --- a/crates/anvil/src/eth/pool/transactions.rs +++ b/crates/anvil/src/eth/pool/transactions.rs @@ -85,6 +85,14 @@ pub struct PoolTransaction { // == impl PoolTransaction == impl PoolTransaction { + pub fn new(transaction: PendingTransaction) -> Self { + Self { + pending_transaction: transaction, + requires: vec![], + provides: vec![], + priority: TransactionPriority(0), + } + } /// Returns the hash of this transaction pub fn hash(&self) -> TxHash { *self.pending_transaction.hash() @@ -121,7 +129,6 @@ impl TryFrom for PoolTransaction { }) } } - /// A waiting pool of transaction that are pending, but not yet ready to be included in a new block. /// /// Keeps a set of transactions that are waiting for other transactions diff --git a/crates/anvil/src/tasks/mod.rs b/crates/anvil/src/tasks/mod.rs index 8cf13e844927..f9ba36b60f6c 100644 --- a/crates/anvil/src/tasks/mod.rs +++ b/crates/anvil/src/tasks/mod.rs @@ -6,7 +6,7 @@ use crate::{shutdown::Shutdown, tasks::block_listener::BlockListener, EthApi}; use alloy_network::AnyNetwork; use alloy_primitives::B256; use alloy_provider::Provider; -use alloy_rpc_types::{anvil::Forking, Block}; +use alloy_rpc_types::{anvil::Forking, AnyNetworkBlock}; use alloy_transport::Transport; use futures::StreamExt; use std::{fmt, future::Future}; @@ -77,7 +77,7 @@ impl TaskManager { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.header.number, + block_number: Some(block.header.number), })) .await; } @@ -135,7 +135,7 @@ impl TaskManager { let _ = api .anvil_reset(Some(Forking { json_rpc_url: None, - block_number: block.header.number, + block_number: Some(block.header.number), })) .await; } @@ -149,7 +149,7 @@ impl TaskManager { where P: Provider + 'static, T: Transport + Clone, - F: Fn(Block) -> Fut + Unpin + Send + Sync + 'static, + F: Fn(AnyNetworkBlock) -> Fut + Unpin + Send + Sync + 'static, Fut: Future + Send, { let shutdown = self.on_shutdown.clone(); diff --git a/crates/anvil/tests/it/abi.rs b/crates/anvil/tests/it/abi.rs index 3356b02f10e4..9bd3b84e9d93 100644 --- a/crates/anvil/tests/it/abi.rs +++ b/crates/anvil/tests/it/abi.rs @@ -16,7 +16,7 @@ sol!( sol!( #[sol(rpc)] - MulticallContract, + Multicall, "test-data/multicall.json" ); diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 1b94f5e439e7..0e8001853a95 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -1,20 +1,26 @@ //! tests for custom anvil endpoints use crate::{ - abi::{Greeter, MulticallContract, BUSD}, + abi::{self, Greeter, Multicall, BUSD}, fork::fork_config, utils::http_provider_with_signer, }; -use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{address, fixed_bytes, Address, U256}; +use alloy_consensus::{SignableTransaction, TxEip1559}; +use alloy_network::{EthereumWallet, TransactionBuilder, TxSignerSync}; +use alloy_primitives::{address, fixed_bytes, Address, Bytes, TxKind, U256}; use alloy_provider::{ext::TxPoolApi, Provider}; use alloy_rpc_types::{ - anvil::{ForkedNetwork, Forking, Metadata, NodeEnvironment, NodeForkConfig, NodeInfo}, + anvil::{ + ForkedNetwork, Forking, Metadata, MineOptions, NodeEnvironment, NodeForkConfig, NodeInfo, + }, BlockId, BlockNumberOrTag, TransactionRequest, }; use alloy_serde::WithOtherFields; use anvil::{eth::api::CLIENT_VERSION, spawn, EthereumHardfork, NodeConfig}; -use anvil_core::eth::EthRequest; +use anvil_core::{ + eth::EthRequest, + types::{ReorgOptions, TransactionData}, +}; use foundry_evm::revm::primitives::SpecId; use std::{ str::FromStr, @@ -292,13 +298,13 @@ async fn test_set_next_timestamp() { let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.number, 1); assert_eq!(block.header.timestamp, next_timestamp.as_secs()); api.evm_mine(None).await.unwrap(); let next = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(next.header.number.unwrap(), 2); + assert_eq!(next.header.number, 2); assert!(next.header.timestamp > block.header.timestamp); } @@ -441,7 +447,7 @@ async fn can_get_node_info() { let expected_node_info = NodeInfo { current_block_number: 0_u64, current_block_timestamp: 1, - current_block_hash: block.header.hash.unwrap(), + current_block_hash: block.header.hash, hard_fork: hard_fork.to_string(), transaction_order: "fees".to_owned(), environment: NodeEnvironment { @@ -474,7 +480,7 @@ async fn can_get_metadata() { provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); let expected_metadata = Metadata { - latest_block_hash: block.header.hash.unwrap(), + latest_block_hash: block.header.hash, latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), @@ -500,7 +506,7 @@ async fn can_get_metadata_on_fork() { provider.get_block(BlockId::from(block_number), false.into()).await.unwrap().unwrap(); let expected_metadata = Metadata { - latest_block_hash: block.header.hash.unwrap(), + latest_block_hash: block.header.hash, latest_block_number: block_number, chain_id, client_version: CLIENT_VERSION.to_string(), @@ -508,7 +514,7 @@ async fn can_get_metadata_on_fork() { forked_network: Some(ForkedNetwork { chain_id, fork_block_number: block_number, - fork_block_hash: block.header.hash.unwrap(), + fork_block_hash: block.header.hash, }), snapshots: Default::default(), }; @@ -612,21 +618,21 @@ async fn test_fork_revert_call_latest_block_timestamp() { api.evm_revert(snapshot_id).await.unwrap(); let multicall_contract = - MulticallContract::new(address!("eefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); + Multicall::new(address!("eefba1e63905ef1d7acba5a8513c70307c1ce441"), &provider); - let MulticallContract::getCurrentBlockTimestampReturn { timestamp } = + let Multicall::getCurrentBlockTimestampReturn { timestamp } = multicall_contract.getCurrentBlockTimestamp().call().await.unwrap(); assert_eq!(timestamp, U256::from(latest_block.header.timestamp)); - let MulticallContract::getCurrentBlockDifficultyReturn { difficulty } = + let Multicall::getCurrentBlockDifficultyReturn { difficulty } = multicall_contract.getCurrentBlockDifficulty().call().await.unwrap(); assert_eq!(difficulty, U256::from(latest_block.header.difficulty)); - let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit } = + let Multicall::getCurrentBlockGasLimitReturn { gaslimit } = multicall_contract.getCurrentBlockGasLimit().call().await.unwrap(); assert_eq!(gaslimit, U256::from(latest_block.header.gas_limit)); - let MulticallContract::getCurrentBlockCoinbaseReturn { coinbase } = + let Multicall::getCurrentBlockCoinbaseReturn { coinbase } = multicall_contract.getCurrentBlockCoinbase().call().await.unwrap(); assert_eq!(coinbase, latest_block.header.miner); } @@ -658,3 +664,132 @@ async fn can_remove_pool_transactions() { let final_txs = provider.txpool_inspect().await.unwrap(); assert_eq!(final_txs.pending.len(), 0); } + +#[tokio::test(flavor = "multi_thread")] +async fn test_reorg() { + let (api, handle) = spawn(NodeConfig::test()).await; + let provider = handle.ws_provider(); + + let accounts = handle.dev_wallets().collect::>(); + + // Test calls + // Populate chain + for i in 0..10 { + let tx = TransactionRequest::default() + .to(accounts[0].address()) + .value(U256::from(i)) + .from(accounts[1].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + + let tx = TransactionRequest::default() + .to(accounts[1].address()) + .value(U256::from(i)) + .from(accounts[2].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + // Define transactions + let mut txs = vec![]; + for i in 0..3 { + let from = accounts[i].address(); + let to = accounts[i + 1].address(); + for j in 0..5 { + let tx = TransactionRequest::default().from(from).to(to).value(U256::from(j)); + txs.push((TransactionData::JSON(tx), i as u64)); + } + } + + let prev_height = provider.get_block_number().await.unwrap(); + api.anvil_reorg(ReorgOptions { depth: 7, tx_block_pairs: txs }).await.unwrap(); + + let reorged_height = provider.get_block_number().await.unwrap(); + assert_eq!(reorged_height, prev_height); + + // The first 3 reorged blocks should have 5 transactions each + for num in 14..17 { + let block = provider.get_block_by_number(num.into(), true).await.unwrap(); + let block = block.unwrap(); + assert_eq!(block.transactions.len(), 5); + } + + // Verify that historic blocks are still accessible + for num in (0..14).rev() { + let _ = provider.get_block_by_number(num.into(), true).await.unwrap(); + } + + // Send a few more transaction to verify the chain can still progress + for i in 0..3 { + let tx = TransactionRequest::default() + .to(accounts[0].address()) + .value(U256::from(i)) + .from(accounts[1].address()); + let tx = WithOtherFields::new(tx); + api.send_transaction(tx).await.unwrap(); + } + + // Test reverting code + let greeter = abi::Greeter::deploy(provider.clone(), "Reorg".to_string()).await.unwrap(); + api.anvil_reorg(ReorgOptions { depth: 5, tx_block_pairs: vec![] }).await.unwrap(); + let code = api.get_code(*greeter.address(), Some(BlockId::latest())).await.unwrap(); + assert_eq!(code, Bytes::default()); + + // Test reverting contract storage + let storage = + abi::SimpleStorage::deploy(provider.clone(), "initial value".to_string()).await.unwrap(); + api.evm_mine(Some(MineOptions::Options { timestamp: None, blocks: Some(5) })).await.unwrap(); + let _ = storage + .setValue("ReorgMe".to_string()) + .from(accounts[0].address()) + .send() + .await + .unwrap() + .get_receipt() + .await + .unwrap(); + api.anvil_reorg(ReorgOptions { depth: 3, tx_block_pairs: vec![] }).await.unwrap(); + let value = storage.getValue().call().await.unwrap()._0; + assert_eq!("initial value".to_string(), value); + + api.mine_one().await; + api.mine_one().await; + + // Test raw transaction data + let mut tx = TxEip1559 { + chain_id: api.chain_id(), + to: TxKind::Call(accounts[1].address()), + value: U256::from(100), + max_priority_fee_per_gas: 1000000000000, + max_fee_per_gas: 10000000000000, + gas_limit: 21000, + ..Default::default() + }; + let signature = accounts[5].sign_transaction_sync(&mut tx).unwrap(); + let tx = tx.into_signed(signature); + let mut encoded = vec![]; + tx.tx().encode_with_signature(tx.signature(), &mut encoded, false); + + let pre_bal = provider.get_balance(accounts[5].address()).await.unwrap(); + api.anvil_reorg(ReorgOptions { + depth: 1, + tx_block_pairs: vec![(TransactionData::Raw(encoded.into()), 0)], + }) + .await + .unwrap(); + let post_bal = provider.get_balance(accounts[5].address()).await.unwrap(); + assert_ne!(pre_bal, post_bal); + + // Test reorg depth exceeding current height + let res = api.anvil_reorg(ReorgOptions { depth: 100, tx_block_pairs: vec![] }).await; + assert!(res.is_err()); + + // Test reorg tx pairs exceeds chain length + let res = api + .anvil_reorg(ReorgOptions { + depth: 1, + tx_block_pairs: vec![(TransactionData::JSON(TransactionRequest::default()), 10)], + }) + .await; + assert!(res.is_err()); +} diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index 8843efddc419..f9aaf0dbab02 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -1,7 +1,7 @@ //! general eth api tests use crate::{ - abi::{MulticallContract, SimpleStorage}, + abi::{Multicall, SimpleStorage}, utils::{connect_pubsub_with_wallet, http_provider_with_signer}, }; use alloy_network::{EthereumWallet, TransactionBuilder}; @@ -118,11 +118,8 @@ async fn can_get_block_by_number() { let block = provider.get_block(BlockId::number(1), true.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); - let block = provider - .get_block(BlockId::hash(block.header.hash.unwrap()), true.into()) - .await - .unwrap() - .unwrap(); + let block = + provider.get_block(BlockId::hash(block.header.hash), true.into()).await.unwrap().unwrap(); assert_eq!(block.transactions.len(), 1); } @@ -138,7 +135,7 @@ async fn can_get_pending_block() { let provider = connect_pubsub_with_wallet(&handle.http_endpoint(), signer).await; let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.number, 1); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); @@ -153,12 +150,12 @@ async fn can_get_pending_block() { assert_eq!(num, 0); let block = provider.get_block(BlockId::pending(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); assert_eq!(block.transactions, BlockTransactions::Hashes(vec![*pending.tx_hash()])); let block = provider.get_block(BlockId::pending(), true.into()).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), 1); + assert_eq!(block.header.number, 1); assert_eq!(block.transactions.len(), 1); } @@ -210,7 +207,7 @@ async fn can_call_on_pending_block() { api.anvil_set_auto_mine(false).await.unwrap(); - let _contract_pending = MulticallContract::deploy_builder(&provider) + let _contract_pending = Multicall::deploy_builder(&provider) .from(wallet.address()) .send() .await @@ -219,13 +216,13 @@ async fn can_call_on_pending_block() { .await .unwrap(); let contract_address = sender.create(0); - let contract = MulticallContract::new(contract_address, &provider); + let contract = Multicall::new(contract_address, &provider); let num = provider.get_block_number().await.unwrap(); assert_eq!(num, 0); // Ensure that we can get the block_number from the pending contract - let MulticallContract::aggregateReturn { blockNumber: ret_block_number, .. } = + let Multicall::aggregateReturn { blockNumber: ret_block_number, .. } = contract.aggregate(vec![]).block(BlockId::pending()).call().await.unwrap(); assert_eq!(ret_block_number, U256::from(1)); @@ -244,31 +241,28 @@ async fn can_call_on_pending_block() { let block_number = BlockNumberOrTag::Number(anvil_block_number as u64); let block = api.block_by_number(block_number).await.unwrap().unwrap(); - let MulticallContract::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = - contract - .getCurrentBlockTimestamp() - .block(BlockId::number(anvil_block_number as u64)) - .call() - .await - .unwrap(); + let Multicall::getCurrentBlockTimestampReturn { timestamp: ret_timestamp, .. } = contract + .getCurrentBlockTimestamp() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); assert_eq!(block.header.timestamp, ret_timestamp.to::()); - let MulticallContract::getCurrentBlockGasLimitReturn { gaslimit: ret_gas_limit, .. } = - contract - .getCurrentBlockGasLimit() - .block(BlockId::number(anvil_block_number as u64)) - .call() - .await - .unwrap(); + let Multicall::getCurrentBlockGasLimitReturn { gaslimit: ret_gas_limit, .. } = contract + .getCurrentBlockGasLimit() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); assert_eq!(block.header.gas_limit, ret_gas_limit.to::()); - let MulticallContract::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = - contract - .getCurrentBlockCoinbase() - .block(BlockId::number(anvil_block_number as u64)) - .call() - .await - .unwrap(); + let Multicall::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = contract + .getCurrentBlockCoinbase() + .block(BlockId::number(anvil_block_number as u64)) + .call() + .await + .unwrap(); assert_eq!(block.header.miner, ret_coinbase); } } @@ -316,7 +310,7 @@ async fn can_call_with_state_override() { api.anvil_set_auto_mine(true).await.unwrap(); - let multicall_contract = MulticallContract::deploy(&provider).await.unwrap(); + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let init_value = "toto".to_string(); @@ -383,18 +377,12 @@ async fn can_mine_while_mining() { let total_blocks = 200; - let block_number = api - .block_by_number(BlockNumberOrTag::Latest) - .await - .unwrap() - .unwrap() - .header - .number - .unwrap(); + let block_number = + api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; assert_eq!(block_number, 0); let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), 0); + assert_eq!(block.header.number, 0); let result = join!( api.anvil_mine(Some(U256::from(total_blocks / 2)), None), @@ -404,16 +392,10 @@ async fn can_mine_while_mining() { result.1.unwrap(); tokio::time::sleep(Duration::from_millis(100)).await; - let block_number = api - .block_by_number(BlockNumberOrTag::Latest) - .await - .unwrap() - .unwrap() - .header - .number - .unwrap(); + let block_number = + api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap().header.number; assert_eq!(block_number, total_blocks); let block = api.block_by_number(BlockNumberOrTag::Number(block_number)).await.unwrap().unwrap(); - assert_eq!(block.header.number.unwrap(), total_blocks); + assert_eq!(block.header.number, total_blocks); } diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index 884bc0605466..d8465ec5cace 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -4,6 +4,7 @@ use crate::{ abi::{Greeter, ERC721}, utils::{http_provider, http_provider_with_signer}, }; +use alloy_chains::NamedChain; use alloy_network::{EthereumWallet, ReceiptResponse, TransactionBuilder}; use alloy_primitives::{address, b256, bytes, Address, Bytes, TxHash, TxKind, U256}; use alloy_provider::Provider; @@ -17,7 +18,7 @@ use alloy_signer_local::PrivateKeySigner; use anvil::{eth::EthApi, spawn, NodeConfig, NodeHandle}; use foundry_common::provider::get_http_provider; use foundry_config::Config; -use foundry_test_utils::rpc::{self, next_http_rpc_endpoint}; +use foundry_test_utils::rpc::{self, next_http_rpc_endpoint, next_rpc_endpoint}; use futures::StreamExt; use std::{sync::Arc, thread::sleep, time::Duration}; @@ -476,6 +477,37 @@ async fn can_reset_properly() { assert!(fork_tx_provider.get_transaction_by_hash(tx.transaction_hash).await.unwrap().is_none()) } +// Ref: +#[tokio::test(flavor = "multi_thread")] +async fn can_reset_fork_to_new_fork() { + let eth_rpc_url = next_rpc_endpoint(NamedChain::Mainnet); + let (api, handle) = spawn(NodeConfig::test().with_eth_rpc_url(Some(eth_rpc_url))).await; + let provider = handle.http_provider(); + + let op = address!("C0d3c0d3c0D3c0D3C0d3C0D3C0D3c0d3c0d30007"); // L2CrossDomainMessenger - Dead on mainnet. + + let tx = TransactionRequest::default().with_to(op).with_input("0x54fd4d50"); + + let tx = WithOtherFields::new(tx); + + let mainnet_call_output = provider.call(&tx).await.unwrap(); + + assert_eq!(mainnet_call_output, Bytes::new()); // 0x + + let optimism = next_rpc_endpoint(NamedChain::Optimism); + + api.anvil_reset(Some(Forking { + json_rpc_url: Some(optimism.to_string()), + block_number: Some(124659890), + })) + .await + .unwrap(); + + let code = provider.get_code_at(op).await.unwrap(); + + assert_ne!(code, Bytes::new()); +} + #[tokio::test(flavor = "multi_thread")] async fn test_fork_timestamp() { let start = std::time::Instant::now(); @@ -734,7 +766,7 @@ async fn test_fork_init_base_fee() { let block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); // - assert_eq!(block.header.number.unwrap(), 13184859u64); + assert_eq!(block.header.number, 13184859u64); let init_base_fee = block.header.base_fee_per_gas.unwrap(); assert_eq!(init_base_fee, 63739886069u128); @@ -850,7 +882,7 @@ async fn test_fork_uncles_fetch() { let count = provider.get_uncle_count(block_with_uncles.into()).await.unwrap(); assert_eq!(count as usize, block.uncles.len()); - let hash = BlockId::hash(block.header.hash.unwrap()); + let hash = BlockId::hash(block.header.hash); let count = provider.get_uncle_count(hash).await.unwrap(); assert_eq!(count as usize, block.uncles.len()); @@ -861,15 +893,15 @@ async fn test_fork_uncles_fetch() { .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash); // Try with block hash let uncle = provider - .get_uncle(BlockId::hash(block.header.hash.unwrap()), uncle_idx as u64) + .get_uncle(BlockId::hash(block.header.hash), uncle_idx as u64) .await .unwrap() .unwrap(); - assert_eq!(*uncle_hash, uncle.header.hash.unwrap()); + assert_eq!(*uncle_hash, uncle.header.hash); } } @@ -905,11 +937,8 @@ async fn test_fork_block_transaction_count() { api.block_transaction_count_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); assert_eq!(latest_txs.to::(), 1); let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - let latest_txs = api - .block_transaction_count_by_hash(latest_block.header.hash.unwrap()) - .await - .unwrap() - .unwrap(); + let latest_txs = + api.block_transaction_count_by_hash(latest_block.header.hash).await.unwrap().unwrap(); assert_eq!(latest_txs.to::(), 1); // check txs count on an older block: 420000 has 3 txs on mainnet @@ -1173,7 +1202,7 @@ async fn test_arbitrum_fork_block_number() { // test block by number API call returns proper block number and `l1BlockNumber` is set let block_by_number = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert_eq!(block_by_number.header.number.unwrap(), initial_block_number + 1); + assert_eq!(block_by_number.header.number, initial_block_number + 1); assert!(block_by_number.other.get("l1BlockNumber").is_some()); // revert to recorded snapshot and check block number @@ -1283,7 +1312,7 @@ async fn test_immutable_fork_transaction_hash() { let tx = api .backend .mined_block_by_number(BlockNumberOrTag::Number(fork_block_number)) - .and_then(|b| b.header.hash) + .map(|b| b.header.hash) .and_then(|hash| { api.backend.mined_transaction_by_block_hash_and_index(hash, expected.1.into()) }) diff --git a/crates/anvil/tests/it/ipc.rs b/crates/anvil/tests/it/ipc.rs index 786217ecd6ab..4f13f8aaf0a1 100644 --- a/crates/anvil/tests/it/ipc.rs +++ b/crates/anvil/tests/it/ipc.rs @@ -54,7 +54,7 @@ async fn test_sub_new_heads_ipc() { let blocks = provider.subscribe_blocks().await.unwrap().into_stream(); let blocks = blocks.take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).collect::>(); + let block_numbers = blocks.into_iter().map(|b| b.header.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } diff --git a/crates/anvil/tests/it/logs.rs b/crates/anvil/tests/it/logs.rs index 40d032d7ccdc..1ce4ac64ffae 100644 --- a/crates/anvil/tests/it/logs.rs +++ b/crates/anvil/tests/it/logs.rs @@ -60,8 +60,7 @@ async fn get_past_events() { .unwrap() .unwrap() .header - .hash - .unwrap(); + .hash; let filter = Filter::new() .address(simple_storage_address) @@ -197,8 +196,7 @@ async fn watch_events() { .unwrap() .unwrap() .header - .hash - .unwrap(); + .hash; assert_eq!(log.1.block_hash.unwrap(), hash); } } diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 4ca74f9feb3f..6446caf9ce68 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -5,10 +5,11 @@ use alloy_eips::eip2718::Encodable2718; use alloy_network::{EthereumWallet, TransactionBuilder}; use alloy_primitives::{b256, Address, TxHash, TxKind, U256}; use alloy_provider::Provider; -use alloy_rpc_types::{optimism::OptimismTransactionFields, TransactionRequest}; +use alloy_rpc_types::TransactionRequest; use alloy_serde::WithOtherFields; use anvil::{spawn, EthereumHardfork, NodeConfig}; use anvil_core::eth::transaction::optimism::DepositTransaction; +use op_alloy_rpc_types::OptimismTransactionFields; #[tokio::test(flavor = "multi_thread")] async fn test_deposits_not_supported_if_optimism_disabled() { @@ -32,6 +33,7 @@ async fn test_deposits_not_supported_if_optimism_disabled() { )), mint: Some(0), is_system_tx: Some(true), + deposit_receipt_version: None, } .into(), }; @@ -72,6 +74,7 @@ async fn test_send_value_deposit_transaction() { )), mint: Some(0), is_system_tx: Some(true), + deposit_receipt_version: None, } .into(), }; @@ -126,6 +129,7 @@ async fn test_send_value_raw_deposit_transaction() { )), mint: Some(0), is_system_tx: Some(true), + deposit_receipt_version: None, } .into(), }; diff --git a/crates/anvil/tests/it/otterscan.rs b/crates/anvil/tests/it/otterscan.rs index 2f2c0e3c1ce8..55474079fb0e 100644 --- a/crates/anvil/tests/it/otterscan.rs +++ b/crates/anvil/tests/it/otterscan.rs @@ -1,6 +1,6 @@ //! Tests for otterscan endpoints. -use crate::abi::MulticallContract; +use crate::abi::Multicall; use alloy_primitives::{address, Address, Bytes, U256}; use alloy_provider::Provider; use alloy_rpc_types::{ @@ -18,10 +18,10 @@ async fn erigon_get_header_by_number() { api.mine_one().await; let res0 = api.erigon_get_header_by_number(0.into()).await.unwrap().unwrap(); - assert_eq!(res0.header.number, Some(0)); + assert_eq!(res0.header.number, 0); let res1 = api.erigon_get_header_by_number(1.into()).await.unwrap().unwrap(); - assert_eq!(res1.header.number, Some(1)); + assert_eq!(res1.header.number, 1); } #[tokio::test(flavor = "multi_thread")] @@ -37,13 +37,8 @@ async fn ots_get_internal_operations_contract_deploy() { let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); - let contract_receipt = MulticallContract::deploy_builder(&provider) - .send() - .await - .unwrap() - .get_receipt() - .await - .unwrap(); + let contract_receipt = + Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); let res = api.ots_get_internal_operations(contract_receipt.transaction_hash).await.unwrap(); assert_eq!( @@ -181,7 +176,7 @@ async fn ots_has_code() { // no code in the address before deploying assert!(!api.ots_has_code(contract_address, BlockNumberOrTag::Number(1)).await.unwrap()); - let contract_builder = MulticallContract::deploy_builder(&provider); + let contract_builder = Multicall::deploy_builder(&provider); let contract_receipt = contract_builder.send().await.unwrap().get_receipt().await.unwrap(); let num = provider.get_block_number().await.unwrap(); @@ -501,13 +496,8 @@ async fn ots_get_contract_creator() { let provider = handle.http_provider(); let sender = handle.dev_accounts().next().unwrap(); - let receipt = MulticallContract::deploy_builder(&provider) - .send() - .await - .unwrap() - .get_receipt() - .await - .unwrap(); + let receipt = + Multicall::deploy_builder(&provider).send().await.unwrap().get_receipt().await.unwrap(); let contract_address = receipt.contract_address.unwrap(); let creator = api.ots_get_contract_creator(contract_address).await.unwrap().unwrap(); diff --git a/crates/anvil/tests/it/pubsub.rs b/crates/anvil/tests/it/pubsub.rs index fec22ca41cd4..a343f7c7089d 100644 --- a/crates/anvil/tests/it/pubsub.rs +++ b/crates/anvil/tests/it/pubsub.rs @@ -23,7 +23,7 @@ async fn test_sub_new_heads() { api.anvil_set_interval_mining(1).unwrap(); let blocks = blocks.into_stream().take(3).collect::>().await; - let block_numbers = blocks.into_iter().map(|b| b.header.number.unwrap()).collect::>(); + let block_numbers = blocks.into_iter().map(|b| b.header.number).collect::>(); assert_eq!(block_numbers, vec![1, 2, 3]); } @@ -233,7 +233,7 @@ async fn test_subscriptions() { let (_api, handle) = spawn(NodeConfig::test().with_blocktime(Some(std::time::Duration::from_secs(1)))).await; let provider = connect_pubsub(&handle.ws_endpoint()).await; - let sub_id: U256 = provider.raw_request("eth_subscribe".into(), ["newHeads"]).await.unwrap(); + let sub_id = provider.raw_request("eth_subscribe".into(), ["newHeads"]).await.unwrap(); let stream: Subscription = provider.get_subscription(sub_id).await.unwrap(); let blocks = stream .into_stream() @@ -241,7 +241,7 @@ async fn test_subscriptions() { .collect::>() .await .into_iter() - .map(|b| b.header.number.unwrap()) + .map(|b| b.header.number) .collect::>(); assert_eq!(blocks, vec![1, 2, 3]) @@ -261,7 +261,7 @@ async fn test_sub_new_heads_fast() { let mut block_numbers = Vec::new(); for _ in 0..num { api.mine_one().await; - let block_number = blocks.next().await.unwrap().header.number.unwrap(); + let block_number = blocks.next().await.unwrap().header.number; block_numbers.push(block_number); } diff --git a/crates/anvil/tests/it/traces.rs b/crates/anvil/tests/it/traces.rs index 782e68d729d1..aaa2ca298d14 100644 --- a/crates/anvil/tests/it/traces.rs +++ b/crates/anvil/tests/it/traces.rs @@ -1,5 +1,5 @@ use crate::{ - abi::{MulticallContract, SimpleStorage}, + abi::{Multicall, SimpleStorage}, fork::fork_config, utils::http_provider_with_signer, }; @@ -155,7 +155,7 @@ async fn test_call_tracer_debug_trace_call() { let deployer: EthereumWallet = wallets[0].clone().into(); let provider = http_provider_with_signer(&handle.http_endpoint(), deployer); - let multicall_contract = MulticallContract::deploy(&provider).await.unwrap(); + let multicall_contract = Multicall::deploy(&provider).await.unwrap(); let simple_storage_contract = SimpleStorage::deploy(&provider, "init value".to_string()).await.unwrap(); @@ -163,7 +163,7 @@ async fn test_call_tracer_debug_trace_call() { let set_value = simple_storage_contract.setValue("bar".to_string()); let set_value_calldata = set_value.calldata(); - let internal_call_tx_builder = multicall_contract.aggregate(vec![MulticallContract::Call { + let internal_call_tx_builder = multicall_contract.aggregate(vec![Multicall::Call { target: *simple_storage_contract.address(), callData: set_value_calldata.to_owned(), }]); @@ -766,15 +766,15 @@ async fn test_trace_filter() { for i in 0..=5 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); - assert_eq!(traces.len(), 5); + assert_eq!(traces.len(), 6); // Test for the following actions: // Create (deploy the contract) @@ -804,11 +804,11 @@ async fn test_trace_filter() { for i in 0..=5 { let tx = TransactionRequest::default().to(to_two).value(U256::from(i)).from(from_two); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); - assert_eq!(traces.len(), 8); + assert_eq!(traces.len(), 9); // Test Range Error let latest = provider.get_block_number().await.unwrap(); @@ -854,7 +854,7 @@ async fn test_trace_filter() { for i in 0..=10 { let tx = TransactionRequest::default().to(to).value(U256::from(i)).from(from); let tx = WithOtherFields::new(tx); - api.send_transaction(tx).await.unwrap(); + provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); } let traces = api.trace_filter(tracer).await.unwrap(); diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 330eb7a3928f..0827bbac1de8 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -1,5 +1,5 @@ use crate::{ - abi::{Greeter, MulticallContract, SimpleStorage}, + abi::{Greeter, Multicall, SimpleStorage}, utils::{connect_pubsub, http_provider_with_signer}, }; use alloy_network::{EthereumWallet, TransactionBuilder}; @@ -477,7 +477,7 @@ async fn get_blocktimestamp_works() { let (api, handle) = spawn(NodeConfig::test()).await; let provider = handle.http_provider(); - let contract = MulticallContract::deploy(provider.clone()).await.unwrap(); + let contract = Multicall::deploy(provider.clone()).await.unwrap(); let timestamp = contract.getCurrentBlockTimestamp().call().await.unwrap().timestamp; @@ -557,8 +557,7 @@ async fn call_past_state() { .unwrap() .unwrap() .header - .hash - .unwrap(); + .hash; let value = contract.getValue().block(BlockId::Hash(hash.into())).call().await.unwrap(); assert_eq!(value._0, "initial value"); } @@ -996,7 +995,7 @@ async fn test_tx_access_list() { let sender = Address::random(); let other_acc = Address::random(); - let multicall = MulticallContract::deploy(provider.clone()).await.unwrap(); + let multicall = Multicall::deploy(provider.clone()).await.unwrap(); let simple_storage = SimpleStorage::deploy(provider.clone(), "foo".to_string()).await.unwrap(); // when calling `setValue` on SimpleStorage, both the `lastSender` and `_value` storages are @@ -1044,7 +1043,7 @@ async fn test_tx_access_list() { // With a subcall to another contract, the AccessList should be the same as when calling the // subcontract directly (given that the proxy contract doesn't read/write any state) - let subcall_tx = multicall.aggregate(vec![MulticallContract::Call { + let subcall_tx = multicall.aggregate(vec![Multicall::Call { target: *simple_storage.address(), callData: set_value_calldata.to_owned(), }]); diff --git a/crates/cast/Cargo.toml b/crates/cast/Cargo.toml index edc3fbe8d608..46f8eac95cd8 100644 --- a/crates/cast/Cargo.toml +++ b/crates/cast/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "cast" +name = "foundry-cast" description = "Command-line tool for performing Ethereum RPC calls" version.workspace = true @@ -13,6 +13,9 @@ repository.workspace = true [lints] workspace = true +[lib] +name = "cast" + [[bin]] name = "cast" path = "bin/main.rs" @@ -79,7 +82,7 @@ tempfile.workspace = true tokio = { workspace = true, features = ["macros", "signal"] } tracing.workspace = true yansi.workspace = true -evmole = "0.3.1" +evmole.workspace = true [target.'cfg(unix)'.dependencies] tikv-jemallocator = { workspace = true, optional = true } diff --git a/crates/cast/bin/opts.rs b/crates/cast/bin/args.rs similarity index 96% rename from crates/cast/bin/opts.rs rename to crates/cast/bin/args.rs index 727458505968..fdb6a113354a 100644 --- a/crates/cast/bin/opts.rs +++ b/crates/cast/bin/args.rs @@ -831,6 +831,48 @@ pub enum CastSubcommand { rpc: RpcOpts, }, + /// Get the codehash for an account. + #[command()] + Codehash { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address to get the codehash for. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// The storage slot numbers (hex or decimal). + #[arg(value_parser = parse_slot)] + slots: Vec, + + #[command(flatten)] + rpc: RpcOpts, + }, + + /// Get the storage root for an account. + #[command(visible_alias = "sr")] + StorageRoot { + /// The block height to query at. + /// + /// Can also be the tags earliest, finalized, safe, latest, or pending. + #[arg(long, short = 'B')] + block: Option, + + /// The address to get the storage root for. + #[arg(value_parser = NameOrAddress::from_str)] + who: NameOrAddress, + + /// The storage slot numbers (hex or decimal). + #[arg(value_parser = parse_slot)] + slots: Vec, + + #[command(flatten)] + rpc: RpcOpts, + }, + /// Get the source code of a contract from Etherscan. #[command(visible_aliases = &["et", "src"])] EtherscanSource { diff --git a/crates/cast/bin/cmd/access_list.rs b/crates/cast/bin/cmd/access_list.rs index fcaf848acbd4..553d9c9ad407 100644 --- a/crates/cast/bin/cmd/access_list.rs +++ b/crates/cast/bin/cmd/access_list.rs @@ -1,5 +1,4 @@ use crate::tx::{CastTxBuilder, SenderKind}; -use alloy_primitives::TxKind; use alloy_rpc_types::BlockId; use cast::Cast; use clap::Parser; @@ -55,15 +54,10 @@ impl AccessListArgs { let provider = utils::get_provider(&config)?; let sender = SenderKind::from_wallet_opts(eth.wallet).await?; - let tx_kind = if let Some(to) = to { - TxKind::Call(to.resolve(&provider).await?) - } else { - TxKind::Create - }; - let (tx, _) = CastTxBuilder::new(&provider, tx, &config) .await? - .with_tx_kind(tx_kind) + .with_to(to) + .await? .with_code_sig_and_args(None, sig, args) .await? .build_raw(sender) diff --git a/crates/cast/bin/cmd/call.rs b/crates/cast/bin/cmd/call.rs index 9a553e38507c..5b1e1ff65fc1 100644 --- a/crates/cast/bin/cmd/call.rs +++ b/crates/cast/bin/cmd/call.rs @@ -143,12 +143,6 @@ impl CallArgs { let sender = SenderKind::from_wallet_opts(eth.wallet).await?; let from = sender.address(); - let tx_kind = if let Some(to) = to { - TxKind::Call(to.resolve(&provider).await?) - } else { - TxKind::Create - }; - let code = if let Some(CallSubcommands::Create { code, sig: create_sig, @@ -168,7 +162,8 @@ impl CallArgs { let (tx, func) = CastTxBuilder::new(&provider, tx, &config) .await? - .with_tx_kind(tx_kind) + .with_to(to) + .await? .with_code_sig_and_args(code, sig, args) .await? .build_raw(sender) @@ -192,6 +187,7 @@ impl CallArgs { let value = tx.value.unwrap_or_default(); let input = tx.inner.input.into_input().unwrap_or_default(); + let tx_kind = tx.inner.to.expect("set by builder"); let trace = match tx_kind { TxKind::Create => { diff --git a/crates/cast/bin/cmd/estimate.rs b/crates/cast/bin/cmd/estimate.rs index 84812c8cc444..315ba91dc564 100644 --- a/crates/cast/bin/cmd/estimate.rs +++ b/crates/cast/bin/cmd/estimate.rs @@ -1,5 +1,5 @@ use crate::tx::{CastTxBuilder, SenderKind}; -use alloy_primitives::{TxKind, U256}; +use alloy_primitives::U256; use alloy_provider::Provider; use alloy_rpc_types::BlockId; use clap::Parser; @@ -73,12 +73,6 @@ impl EstimateArgs { let provider = utils::get_provider(&config)?; let sender = SenderKind::from_wallet_opts(eth.wallet).await?; - let tx_kind = if let Some(to) = to { - TxKind::Call(to.resolve(&provider).await?) - } else { - TxKind::Create - }; - let code = if let Some(EstimateSubcommands::Create { code, sig: create_sig, @@ -98,7 +92,8 @@ impl EstimateArgs { let (tx, _) = CastTxBuilder::new(&provider, tx, &config) .await? - .with_tx_kind(tx_kind) + .with_to(to) + .await? .with_code_sig_and_args(code, sig, args) .await? .build_raw(sender) diff --git a/crates/cast/bin/cmd/interface.rs b/crates/cast/bin/cmd/interface.rs index caf9ac5ddfe4..b8de8d84deab 100644 --- a/crates/cast/bin/cmd/interface.rs +++ b/crates/cast/bin/cmd/interface.rs @@ -6,7 +6,7 @@ use foundry_block_explorers::Client; use foundry_cli::opts::EtherscanOpts; use foundry_common::{compile::ProjectCompiler, fs}; use foundry_compilers::{info::ContractInfo, utils::canonicalize}; -use foundry_config::{find_project_root_path, load_config_with_root, Config}; +use foundry_config::{load_config_with_root, try_find_project_root, Config}; use itertools::Itertools; use serde_json::Value; use std::{ @@ -118,8 +118,8 @@ fn load_abi_from_file(path: &str, name: Option) -> Result Result> { - let root = find_project_root_path(None)?; - let config = load_config_with_root(Some(root)); + let root = try_find_project_root(None)?; + let config = load_config_with_root(Some(&root)); let project = config.project()?; let compiler = ProjectCompiler::new().quiet(true); diff --git a/crates/cast/bin/cmd/mktx.rs b/crates/cast/bin/cmd/mktx.rs index 4c7a55cb122e..8a45b7363f5c 100644 --- a/crates/cast/bin/cmd/mktx.rs +++ b/crates/cast/bin/cmd/mktx.rs @@ -6,7 +6,7 @@ use clap::Parser; use eyre::Result; use foundry_cli::{ opts::{EthereumOpts, TransactionOpts}, - utils::{self, get_provider}, + utils::get_provider, }; use foundry_common::ens::NameOrAddress; use foundry_config::Config; @@ -83,9 +83,6 @@ impl MakeTxArgs { }; let config = Config::from(ð); - let provider = utils::get_provider(&config)?; - - let tx_kind = tx::resolve_tx_kind(&provider, &code, &to).await?; // Retrieve the signer, and bail if it can't be constructed. let signer = eth.wallet.signer().await?; @@ -97,7 +94,8 @@ impl MakeTxArgs { let (tx, _) = CastTxBuilder::new(provider, tx, &config) .await? - .with_tx_kind(tx_kind) + .with_to(to) + .await? .with_code_sig_and_args(code, sig, args) .await? .with_blob_data(blob_data)? diff --git a/crates/cast/bin/cmd/run.rs b/crates/cast/bin/cmd/run.rs index 7a5e7980ceee..b67c0ed1188a 100644 --- a/crates/cast/bin/cmd/run.rs +++ b/crates/cast/bin/cmd/run.rs @@ -176,11 +176,11 @@ impl RunArgs { let pb = init_progress(block.transactions.len() as u64, "tx"); pb.set_position(0); - let BlockTransactions::Full(txs) = block.transactions else { + let BlockTransactions::Full(ref txs) = block.transactions else { return Err(eyre::eyre!("Could not get block txs")) }; - for (index, tx) in txs.into_iter().enumerate() { + for (index, tx) in txs.iter().enumerate() { // System transactions such as on L2s don't contain any pricing info so // we skip them otherwise this would cause // reverts @@ -194,7 +194,7 @@ impl RunArgs { break; } - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.inner); if let Some(to) = tx.to { trace!(tx=?tx.hash,?to, "executing previous call transaction"); @@ -231,7 +231,7 @@ impl RunArgs { let result = { executor.set_trace_printer(self.trace_printer); - configure_tx_env(&mut env, &tx); + configure_tx_env(&mut env, &tx.inner); if let Some(to) = tx.to { trace!(tx=?tx.hash, to=?to, "executing call transaction"); diff --git a/crates/cast/bin/cmd/send.rs b/crates/cast/bin/cmd/send.rs index 573bb2bd1e20..cf3582fe03a6 100644 --- a/crates/cast/bin/cmd/send.rs +++ b/crates/cast/bin/cmd/send.rs @@ -122,11 +122,11 @@ impl SendTxArgs { let config = Config::from(ð); let provider = utils::get_provider(&config)?; - let tx_kind = tx::resolve_tx_kind(&provider, &code, &to).await?; let builder = CastTxBuilder::new(&provider, tx, &config) .await? - .with_tx_kind(tx_kind) + .with_to(to) + .await? .with_code_sig_and_args(code, sig, args) .await? .with_blob_data(blob_data)?; diff --git a/crates/cast/bin/cmd/storage.rs b/crates/cast/bin/cmd/storage.rs index 117130943dc5..a8ced363912a 100644 --- a/crates/cast/bin/cmd/storage.rs +++ b/crates/cast/bin/cmd/storage.rs @@ -1,4 +1,4 @@ -use crate::opts::parse_slot; +use crate::args::parse_slot; use alloy_network::AnyNetwork; use alloy_primitives::{Address, B256, U256}; use alloy_provider::Provider; diff --git a/crates/cast/bin/main.rs b/crates/cast/bin/main.rs index 501955827fce..27a052fe6ccc 100644 --- a/crates/cast/bin/main.rs +++ b/crates/cast/bin/main.rs @@ -23,25 +23,28 @@ use foundry_common::{ use foundry_config::Config; use std::time::Instant; +pub mod args; pub mod cmd; -pub mod opts; pub mod tx; -use opts::{Cast as Opts, CastSubcommand, ToBaseArgs}; +use args::{Cast as CastArgs, CastSubcommand, ToBaseArgs}; #[cfg(all(feature = "jemalloc", unix))] #[global_allocator] static ALLOC: tikv_jemallocator::Jemalloc = tikv_jemallocator::Jemalloc; -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { handler::install(); utils::load_dotenv(); utils::subscriber(); utils::enable_paint(); + let args = CastArgs::parse(); + main_args(args) +} - let opts = Opts::parse(); - match opts.cmd { +#[tokio::main] +async fn main_args(args: CastArgs) -> Result<()> { + match args.cmd { // Constants CastSubcommand::MaxInt { r#type } => { println!("{}", SimpleCast::max_int(&r#type)?); @@ -255,13 +258,14 @@ async fn main() -> Result<()> { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; let number = match block { - Some(id) => provider - .get_block(id, false.into()) - .await? - .ok_or_else(|| eyre::eyre!("block {id:?} not found"))? - .header - .number - .ok_or_else(|| eyre::eyre!("block {id:?} has no block number"))?, + Some(id) => { + provider + .get_block(id, false.into()) + .await? + .ok_or_else(|| eyre::eyre!("block {id:?} not found"))? + .header + .number + } None => Cast::new(provider).block_number().await?, }; println!("{number}"); @@ -305,25 +309,24 @@ async fn main() -> Result<()> { println!("{}", SimpleCast::disassemble(&bytecode)?); } CastSubcommand::Selectors { bytecode, resolve } => { - let selectors_and_args = SimpleCast::extract_selectors(&bytecode)?; - if resolve { - let selectors_it = selectors_and_args.iter().map(|r| &r.0); - let resolve_results = - decode_selectors(SelectorType::Function, selectors_it).await?; + let functions = SimpleCast::extract_functions(&bytecode)?; + let max_args_len = functions.iter().map(|r| r.1.len()).max().unwrap_or(0); + let max_mutability_len = functions.iter().map(|r| r.2.len()).max().unwrap_or(0); - let max_args_len = selectors_and_args.iter().map(|r| r.1.len()).max().unwrap_or(0); - for ((selector, arguments), func_names) in - selectors_and_args.into_iter().zip(resolve_results.into_iter()) - { - let resolved = match func_names { - Some(v) => v.join("|"), - None => String::new(), - }; - println!("{selector}\t{arguments:max_args_len$}\t{resolved}"); - } + let resolve_results = if resolve { + let selectors_it = functions.iter().map(|r| &r.0); + let ds = decode_selectors(SelectorType::Function, selectors_it).await?; + ds.into_iter().map(|v| v.unwrap_or_default().join("|")).collect() } else { - for (selector, arguments) in selectors_and_args { - println!("{selector}\t{arguments}"); + vec![] + }; + for (pos, (selector, arguments, state_mutability)) in functions.into_iter().enumerate() + { + if resolve { + let resolved = &resolve_results[pos]; + println!("{selector}\t{arguments:max_args_len$}\t{state_mutability:max_mutability_len$}\t{resolved}"); + } else { + println!("{selector}\t{arguments:max_args_len$}\t{state_mutability}"); } } } @@ -359,6 +362,18 @@ async fn main() -> Result<()> { let who = who.resolve(&provider).await?; println!("{}", Cast::new(provider).nonce(who, block).await?); } + CastSubcommand::Codehash { block, who, slots, rpc } => { + let config = Config::from(&rpc); + let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; + println!("{}", Cast::new(provider).codehash(who, slots, block).await?); + } + CastSubcommand::StorageRoot { block, who, slots, rpc } => { + let config = Config::from(&rpc); + let provider = utils::get_provider(&config)?; + let who = who.resolve(&provider).await?; + println!("{}", Cast::new(provider).storage_root(who, slots, block).await?); + } CastSubcommand::Proof { address, slots, rpc, block } => { let config = Config::from(&rpc); let provider = utils::get_provider(&config)?; @@ -556,11 +571,11 @@ async fn main() -> Result<()> { } CastSubcommand::Wallet { command } => command.run().await?, CastSubcommand::Completions { shell } => { - generate(shell, &mut Opts::command(), "cast", &mut std::io::stdout()) + generate(shell, &mut CastArgs::command(), "cast", &mut std::io::stdout()) } CastSubcommand::GenerateFigSpec => clap_complete::generate( clap_complete_fig::Fig, - &mut Opts::command(), + &mut CastArgs::command(), "cast", &mut std::io::stdout(), ), diff --git a/crates/cast/bin/tx.rs b/crates/cast/bin/tx.rs index f6a9cbd53de9..419ad5bb6b8d 100644 --- a/crates/cast/bin/tx.rs +++ b/crates/cast/bin/tx.rs @@ -103,29 +103,14 @@ corresponds to the sender, or let foundry automatically detect it by not specify Ok(()) } -/// Ensures the transaction is either a contract deployment or a recipient address is specified -pub async fn resolve_tx_kind, T: Transport + Clone>( - provider: &P, - code: &Option, - to: &Option, -) -> Result { - if code.is_some() { - Ok(TxKind::Create) - } else if let Some(to) = to { - Ok(TxKind::Call(to.resolve(provider).await?)) - } else { - eyre::bail!("Must specify a recipient address or contract code to deploy"); - } -} - /// Initial state. #[derive(Debug)] pub struct InitState; /// State with known [TxKind]. #[derive(Debug)] -pub struct TxKindState { - kind: TxKind, +pub struct ToState { + to: Option
, } /// State with known input for the transaction. @@ -211,8 +196,9 @@ where } /// Sets [TxKind] for this builder and changes state to [TxKindState]. - pub fn with_tx_kind(self, kind: TxKind) -> CastTxBuilder { - CastTxBuilder { + pub async fn with_to(self, to: Option) -> Result> { + let to = if let Some(to) = to { Some(to.resolve(&self.provider).await?) } else { None }; + Ok(CastTxBuilder { provider: self.provider, tx: self.tx, legacy: self.legacy, @@ -220,13 +206,13 @@ where chain: self.chain, etherscan_api_key: self.etherscan_api_key, auth: self.auth, - state: TxKindState { kind }, + state: ToState { to }, _t: self._t, - } + }) } } -impl CastTxBuilder +impl CastTxBuilder where P: Provider, T: Transport + Clone, @@ -244,7 +230,7 @@ where parse_function_args( &sig, args, - self.state.kind.to().cloned(), + self.state.to, self.chain, &self.provider, self.etherscan_api_key.as_deref(), @@ -254,7 +240,7 @@ where (Vec::new(), None) }; - let input = if let Some(code) = code { + let input = if let Some(code) = &code { let mut code = hex::decode(code)?; code.append(&mut args); code @@ -262,6 +248,16 @@ where args }; + if self.state.to.is_none() && code.is_none() { + let has_value = self.tx.value.map_or(false, |v| !v.is_zero()); + let has_auth = self.auth.is_some(); + // We only allow user to omit the recipient address if transaction is an EIP-7702 tx + // without a value. + if !has_auth || has_value { + eyre::bail!("Must specify a recipient address or contract code to deploy"); + } + } + Ok(CastTxBuilder { provider: self.provider, tx: self.tx, @@ -270,7 +266,7 @@ where chain: self.chain, etherscan_api_key: self.etherscan_api_key, auth: self.auth, - state: InputState { kind: self.state.kind, input, func }, + state: InputState { kind: self.state.to.into(), input, func }, _t: self._t, }) } diff --git a/crates/cast/src/lib.rs b/crates/cast/src/lib.rs index 95952bfbb7ce..73ab5b28bda1 100644 --- a/crates/cast/src/lib.rs +++ b/crates/cast/src/lib.rs @@ -494,6 +494,72 @@ where Ok(self.provider.get_transaction_count(who).block_id(block.unwrap_or_default()).await?) } + /// #Example + /// + /// ``` + /// use alloy_primitives::{Address, FixedBytes}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use cast::Cast; + /// use std::str::FromStr; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; + /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; + /// let codehash = cast.codehash(addr, slots, None).await?; + /// println!("{}", codehash); + /// # Ok(()) + /// # } + pub async fn codehash( + &self, + who: Address, + slots: Vec, + block: Option, + ) -> Result { + Ok(self + .provider + .get_proof(who, slots) + .block_id(block.unwrap_or_default()) + .await? + .code_hash + .to_string()) + } + + /// #Example + /// + /// ``` + /// use alloy_primitives::{Address, FixedBytes}; + /// use alloy_provider::{network::AnyNetwork, ProviderBuilder, RootProvider}; + /// use cast::Cast; + /// use std::str::FromStr; + /// + /// # async fn foo() -> eyre::Result<()> { + /// let provider = + /// ProviderBuilder::<_, _, AnyNetwork>::default().on_builtin("http://localhost:8545").await?; + /// let cast = Cast::new(provider); + /// let addr = Address::from_str("0x7eD52863829AB99354F3a0503A622e82AcD5F7d3")?; + /// let slots = vec![FixedBytes::from_str("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")?]; + /// let storage_root = cast.storage_root(addr, slots, None).await?; + /// println!("{}", storage_root); + /// # Ok(()) + /// # } + pub async fn storage_root( + &self, + who: Address, + slots: Vec, + block: Option, + ) -> Result { + Ok(self + .provider + .get_proof(who, slots) + .block_id(block.unwrap_or_default()) + .await? + .storage_hash + .to_string()) + } + /// # Example /// /// ``` @@ -872,7 +938,7 @@ where BlockId::Hash(hash) => { let block = self.provider.get_block_by_hash(hash.block_hash, false.into()).await?; - Ok(block.map(|block| block.header.number.unwrap()).map(BlockNumberOrTag::from)) + Ok(block.map(|block| block.header.number).map(BlockNumberOrTag::from)) } }, None => Ok(None), @@ -937,7 +1003,7 @@ where Either::Right(futures::future::pending()) } => { if let (Some(block), Some(to_block)) = (block, to_block_number) { - if block.header.number.map_or(false, |bn| bn > to_block) { + if block.header.number > to_block { break; } } @@ -1043,7 +1109,7 @@ impl SimpleCast { if MAX { let mut max = U256::MAX; if n < 255 { - max &= U256::from(1).wrapping_shl(n); + max &= U256::from(1).wrapping_shl(n).wrapping_sub(U256::from(1)); } Ok(max.to_string()) } else { @@ -1103,7 +1169,7 @@ impl SimpleCast { pub fn to_ascii(hex: &str) -> Result { let bytes = hex::decode(hex)?; if !bytes.iter().all(u8::is_ascii) { - return Err(eyre::eyre!("Invalid ASCII bytes")) + return Err(eyre::eyre!("Invalid ASCII bytes")); } Ok(String::from_utf8(bytes).unwrap()) } @@ -1405,7 +1471,7 @@ impl SimpleCast { let base_in = Base::unwrap_or_detect(base_in, value)?; let base_out: Base = base_out.parse()?; if base_in == base_out { - return Ok(value.to_string()) + return Ok(value.to_string()); } let mut n = NumberWithBase::parse_int(value, Some(&base_in.to_string()))?; @@ -1469,7 +1535,7 @@ impl SimpleCast { let s = if let Some(stripped) = s.strip_prefix("000000000000000000000000") { stripped } else { - return Err(eyre::eyre!("Not convertible to address, there are non-zero bytes")) + return Err(eyre::eyre!("Not convertible to address, there are non-zero bytes")); }; let lowercase_address_string = format!("0x{s}"); @@ -1925,7 +1991,7 @@ impl SimpleCast { } if optimize == 0 { let selector = get_func(signature)?.selector(); - return Ok((selector.to_string(), String::from(signature))) + return Ok((selector.to_string(), String::from(signature))); } let Some((name, params)) = signature.split_once('(') else { eyre::bail!("invalid function signature"); @@ -1947,7 +2013,7 @@ impl SimpleCast { if selector.iter().take_while(|&&byte| byte == 0).count() == optimize { found.store(true, Ordering::Relaxed); - return Some((nonce, hex::encode_prefixed(selector), input)) + return Some((nonce, hex::encode_prefixed(selector), input)); } nonce += nonce_step; @@ -1961,7 +2027,7 @@ impl SimpleCast { } } - /// Extracts function selectors and arguments from bytecode + /// Extracts function selectors, arguments and state mutability from bytecode /// /// # Example /// @@ -1969,16 +2035,21 @@ impl SimpleCast { /// use cast::SimpleCast as Cast; /// /// let bytecode = "6080604052348015600e575f80fd5b50600436106026575f3560e01c80632125b65b14602a575b5f80fd5b603a6035366004603c565b505050565b005b5f805f60608486031215604d575f80fd5b833563ffffffff81168114605f575f80fd5b925060208401356001600160a01b03811681146079575f80fd5b915060408401356001600160e01b03811681146093575f80fd5b80915050925092509256"; - /// let selectors = Cast::extract_selectors(bytecode)?; - /// assert_eq!(selectors, vec![("0x2125b65b".to_string(), "uint32,address,uint224".to_string())]); + /// let functions = Cast::extract_functions(bytecode)?; + /// assert_eq!(functions, vec![("0x2125b65b".to_string(), "uint32,address,uint224".to_string(), "pure")]); /// # Ok::<(), eyre::Report>(()) /// ``` - pub fn extract_selectors(bytecode: &str) -> Result> { + pub fn extract_functions(bytecode: &str) -> Result> { let code = hex::decode(strip_0x(bytecode))?; - let s = evmole::function_selectors(&code, 0); - - Ok(s.iter() - .map(|s| (hex::encode_prefixed(s), evmole::function_arguments(&code, s, 0))) + Ok(evmole::function_selectors(&code, 0) + .into_iter() + .map(|s| { + ( + hex::encode_prefixed(s), + evmole::function_arguments(&code, &s, 0), + evmole::function_state_mutability(&code, &s, 0).as_json_str(), + ) + }) .collect()) } diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index bb52936a9f73..01fdaef867d4 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -5,7 +5,10 @@ use alloy_primitives::{b256, B256}; use anvil::{EthereumHardfork, NodeConfig}; use foundry_test_utils::{ casttest, file, - rpc::{next_http_rpc_endpoint, next_rpc_endpoint, next_ws_rpc_endpoint}, + rpc::{ + next_http_rpc_endpoint, next_mainnet_etherscan_api_key, next_rpc_endpoint, + next_ws_rpc_endpoint, + }, str, util::OutputExt, }; @@ -1115,20 +1118,22 @@ casttest!(interface_no_constructor, |prj, cmd| { let path = prj.root().join("interface.json"); fs::write(&path, interface).unwrap(); // Call `cast find-block` - cmd.args(["interface"]).arg(&path).assert_success().stdout_eq(str![[ + cmd.arg("interface").arg(&path).assert_success().stdout_eq(str![[ r#"// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; -interface Interface { +library IIntegrationManager { type SpendAssetsHandleType is uint8; +} +interface Interface { function getIntegrationManager() external view returns (address integrationManager_); function lend(address _vaultProxy, bytes memory, bytes memory _assetData) external; function parseAssetsForAction(address, bytes4 _selector, bytes memory _actionData) external view returns ( - SpendAssetsHandleType spendAssetsHandleType_, + IIntegrationManager.SpendAssetsHandleType spendAssetsHandleType_, address[] memory spendAssets_, uint256[] memory spendAssetAmounts_, address[] memory incomingAssets_, @@ -1144,11 +1149,15 @@ interface Interface { // tests that fetches WETH interface from etherscan // casttest!(fetch_weth_interface_from_etherscan, |_prj, cmd| { - let weth_address = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; - let api_key = "ZUB97R31KSYX7NYVW6224Q6EYY6U56H591"; - cmd.args(["interface", "--etherscan-api-key", api_key, weth_address]) - .assert_success() - .stdout_eq(str![[r#"// SPDX-License-Identifier: UNLICENSED + cmd.args([ + "interface", + "--etherscan-api-key", + &next_mainnet_etherscan_api_key(), + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", + ]) + .assert_success() + .stdout_eq(str![[r#" +// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.4; interface WETH9 { diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 05a4a18ec4bc..f41ad8106872 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -2966,6 +2966,26 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "assumeNoRevert", + "description": "Discard this run's fuzz inputs and generate new ones if next call reverted.", + "declaration": "function assumeNoRevert() external pure;", + "visibility": "external", + "mutability": "pure", + "signature": "assumeNoRevert()", + "selector": "0x285b366a", + "selectorBytes": [ + 40, + 91, + 54, + 106 + ] + }, + "group": "testing", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "blobBaseFee", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 176eded68a5c..fa2244a379d1 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -741,6 +741,10 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function assume(bool condition) external pure; + /// Discard this run's fuzz inputs and generate new ones if next call reverted. + #[cheatcode(group = Testing, safety = Safe)] + function assumeNoRevert() external pure; + /// Writes a breakpoint to jump to in the debugger. #[cheatcode(group = Testing, safety = Safe)] function breakpoint(string calldata char) external; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 7fad3efdbc22..f9e0757a02b2 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -9,9 +9,12 @@ use crate::{ }, inspector::utils::CommonCreateInput, script::{Broadcast, ScriptWallets}, - test::expect::{ - self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, - ExpectedRevert, ExpectedRevertKind, + test::{ + assume::AssumeNoRevert, + expect::{ + self, ExpectedCallData, ExpectedCallTracker, ExpectedCallType, ExpectedEmit, + ExpectedRevert, ExpectedRevertKind, + }, }, utils::IgnoredTraces, CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm, @@ -25,7 +28,7 @@ use foundry_config::Config; use foundry_evm_core::{ abi::Vm::stopExpectSafeMemoryCall, backend::{DatabaseExt, RevertDiagnostic}, - constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS}, + constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, utils::new_evm_with_existing_context, InspectorExt, }; @@ -300,6 +303,9 @@ pub struct Cheatcodes { /// Expected revert information pub expected_revert: Option, + /// Assume next call can revert and discard fuzz run if it does. + pub assume_no_revert: Option, + /// Additional diagnostic for reverts pub fork_revert_diagnostic: Option, @@ -394,6 +400,7 @@ impl Cheatcodes { gas_price: Default::default(), prank: Default::default(), expected_revert: Default::default(), + assume_no_revert: Default::default(), fork_revert_diagnostic: Default::default(), accesses: Default::default(), recorded_account_diffs_stack: Default::default(), @@ -1117,6 +1124,19 @@ impl Inspector for Cheatcodes { } } + // Handle assume not revert cheatcode. + if let Some(assume_no_revert) = &self.assume_no_revert { + if ecx.journaled_state.depth() == assume_no_revert.depth && !cheatcode_call { + // Discard run if we're at the same depth as cheatcode and call reverted. + if outcome.result.is_revert() { + outcome.result.output = Error::from(MAGIC_ASSUME).abi_encode().into(); + return outcome; + } + // Call didn't revert, reset `assume_no_revert` state. + self.assume_no_revert = None; + } + } + // Handle expected reverts if let Some(expected_revert) = &self.expected_revert { if ecx.journaled_state.depth() <= expected_revert.depth { diff --git a/crates/cheatcodes/src/json.rs b/crates/cheatcodes/src/json.rs index 48c14e2a3ddc..dad879e83b2d 100644 --- a/crates/cheatcodes/src/json.rs +++ b/crates/cheatcodes/src/json.rs @@ -569,6 +569,9 @@ pub(super) fn json_value_to_token(value: &Value) -> Result { Value::String(string) => { if let Some(mut val) = string.strip_prefix("0x") { let s; + if val.len() == 39 { + return Err(format!("Cannot parse \"{val}\" as an address. If you want to specify address, prepend zero to the value.").into()) + } if val.len() % 2 != 0 { s = format!("0{val}"); val = &s[..]; diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index 279150dff89f..0945f37a89c2 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -3,25 +3,15 @@ use chrono::DateTime; use std::env; -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Error, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_primitives::Address; use alloy_sol_types::SolValue; -use foundry_evm_core::constants::{MAGIC_ASSUME, MAGIC_SKIP}; +use foundry_evm_core::constants::MAGIC_SKIP; pub(crate) mod assert; +pub(crate) mod assume; pub(crate) mod expect; -impl Cheatcode for assumeCall { - fn apply(&self, _state: &mut Cheatcodes) -> Result { - let Self { condition } = self; - if *condition { - Ok(Default::default()) - } else { - Err(Error::from(MAGIC_ASSUME)) - } - } -} - impl Cheatcode for breakpoint_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char } = self; diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs new file mode 100644 index 000000000000..e100eeb9d1c4 --- /dev/null +++ b/crates/cheatcodes/src/test/assume.rs @@ -0,0 +1,29 @@ +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; +use foundry_evm_core::{backend::DatabaseExt, constants::MAGIC_ASSUME}; +use spec::Vm::{assumeCall, assumeNoRevertCall}; +use std::fmt::Debug; + +#[derive(Clone, Debug)] +pub struct AssumeNoRevert { + /// The call depth at which the cheatcode was added. + pub depth: u64, +} + +impl Cheatcode for assumeCall { + fn apply(&self, _state: &mut Cheatcodes) -> Result { + let Self { condition } = self; + if *condition { + Ok(Default::default()) + } else { + Err(Error::from(MAGIC_ASSUME)) + } + } +} + +impl Cheatcode for assumeNoRevertCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + ccx.state.assume_no_revert = + Some(AssumeNoRevert { depth: ccx.ecx.journaled_state.depth() }); + Ok(Default::default()) + } +} diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index 83899041515d..df9c480e3aa4 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -464,14 +464,16 @@ fn expect_emit( address: Option
, anonymous: bool, ) -> Result { - state.expected_emits.push_back(ExpectedEmit { - depth, - checks, - address, - found: false, - log: None, - anonymous, - }); + let expected_emit = ExpectedEmit { depth, checks, address, found: false, log: None, anonymous }; + if let Some(found_emit_pos) = state.expected_emits.iter().position(|emit| emit.found) { + // The order of emits already found (back of queue) should not be modified, hence push any + // new emit before first found emit. + state.expected_emits.insert(found_emit_pos, expected_emit); + } else { + // If no expected emits then push new one at the back of queue. + state.expected_emits.push_back(expected_emit); + } + Ok(Default::default()) } @@ -494,15 +496,25 @@ pub(crate) fn handle_expect_emit( return } - // If there's anything to fill, we need to pop back. - // Otherwise, if there are any events that are unmatched, we try to match to match them - // in the order declared, so we start popping from the front (like a queue). - let mut event_to_fill_or_check = - if state.expected_emits.iter().any(|expected| expected.log.is_none()) { - state.expected_emits.pop_back() - } else { - state.expected_emits.pop_front() - } + let should_fill_logs = state.expected_emits.iter().any(|expected| expected.log.is_none()); + let index_to_fill_or_check = if should_fill_logs { + // If there's anything to fill, we start with the last event to match in the queue + // (without taking into account events already matched). + state + .expected_emits + .iter() + .position(|emit| emit.found) + .unwrap_or(state.expected_emits.len()) + .saturating_sub(1) + } else { + // Otherwise, if all expected logs are filled, we start to check any unmatched event + // in the declared order, so we start from the front (like a queue). + 0 + }; + + let mut event_to_fill_or_check = state + .expected_emits + .remove(index_to_fill_or_check) .expect("we should have an emit to fill or check"); let Some(expected) = &event_to_fill_or_check.log else { @@ -510,7 +522,8 @@ pub(crate) fn handle_expect_emit( // filled. if event_to_fill_or_check.anonymous || log.topics().first().is_some() { event_to_fill_or_check.log = Some(log.data.clone()); - state.expected_emits.push_back(event_to_fill_or_check); + // If we only filled the expected log then we put it back at the same position. + state.expected_emits.insert(index_to_fill_or_check, event_to_fill_or_check); } else { interpreter.instruction_result = InstructionResult::Revert; interpreter.next_action = InterpreterAction::Return { diff --git a/crates/chisel/bin/main.rs b/crates/chisel/bin/main.rs index d2b3a726f48e..7a0703e85c2e 100644 --- a/crates/chisel/bin/main.rs +++ b/crates/chisel/bin/main.rs @@ -94,15 +94,16 @@ pub enum ChiselSubcommand { ClearCache, } -#[tokio::main] -async fn main() -> eyre::Result<()> { +fn main() -> eyre::Result<()> { handler::install(); utils::subscriber(); utils::load_dotenv(); - - // Parse command args let args = Chisel::parse(); + main_args(args) +} +#[tokio::main] +async fn main_args(args: Chisel) -> eyre::Result<()> { // Keeps track of whether or not an interrupt was the last input let mut interrupt = false; diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index d575f9ae896a..ebb4da9f1d45 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -146,7 +146,7 @@ impl CoreBuildArgs { /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see - /// `find_project_root_path` and merges the cli `BuildArgs` into it before returning + /// `find_project_root` and merges the cli `BuildArgs` into it before returning /// [`foundry_config::Config::project()`]). pub fn project(&self) -> Result> { let config = self.try_load_config_emit_warnings()?; diff --git a/crates/cli/src/opts/build/paths.rs b/crates/cli/src/opts/build/paths.rs index 9553eedb0d98..aa070800a600 100644 --- a/crates/cli/src/opts/build/paths.rs +++ b/crates/cli/src/opts/build/paths.rs @@ -8,7 +8,7 @@ use foundry_config::{ value::{Dict, Map, Value}, Metadata, Profile, Provider, }, - find_project_root_path, remappings_from_env_var, Config, + find_project_root, remappings_from_env_var, Config, }; use serde::Serialize; use std::path::PathBuf; @@ -66,15 +66,13 @@ pub struct ProjectPathsArgs { impl ProjectPathsArgs { /// Returns the root directory to use for configuring the project. /// - /// This will be the `--root` argument if provided, otherwise see [find_project_root_path()] + /// This will be the `--root` argument if provided, otherwise see [`find_project_root`]. /// /// # Panics /// - /// If the project root directory cannot be found: [find_project_root_path()] + /// Panics if the project root directory cannot be found. See [`find_project_root`]. pub fn project_root(&self) -> PathBuf { - self.root - .clone() - .unwrap_or_else(|| find_project_root_path(None).expect("Failed to find project root")) + self.root.clone().unwrap_or_else(|| find_project_root(None)) } /// Returns the remappings to add to the config diff --git a/crates/cli/src/utils/mod.rs b/crates/cli/src/utils/mod.rs index 15864b016504..f5bee0a77d1d 100644 --- a/crates/cli/src/utils/mod.rs +++ b/crates/cli/src/utils/mod.rs @@ -191,9 +191,9 @@ pub fn load_dotenv() { }; // we only want the .env file of the cwd and project root - // `find_project_root_path` calls `current_dir` internally so both paths are either both `Ok` or + // `find_project_root` calls `current_dir` internally so both paths are either both `Ok` or // both `Err` - if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), find_project_root_path(None)) { + if let (Ok(cwd), Ok(prj_root)) = (std::env::current_dir(), try_find_project_root(None)) { load(&prj_root); if cwd != prj_root { // prj root and cwd can be identical @@ -602,7 +602,7 @@ mod tests { assert!(!p.is_sol_test()); } - // loads .env from cwd and project dir, See [`find_project_root_path()`] + // loads .env from cwd and project dir, See [`find_project_root()`] #[test] fn can_load_dotenv() { let temp = tempdir().unwrap(); diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index f13e0f0218d0..64c5207690d7 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -3,10 +3,10 @@ use alloy_consensus::{AnyReceiptEnvelope, Eip658Value, Receipt, ReceiptWithBloom, TxType}; use alloy_primitives::{hex, Address, Bloom, Bytes, FixedBytes, Uint, B256, I256, U256, U64}; use alloy_rpc_types::{ - AccessListItem, AnyTransactionReceipt, Block, BlockTransactions, Log, Transaction, - TransactionReceipt, + AccessListItem, AnyNetworkBlock, AnyTransactionReceipt, Block, BlockTransactions, Log, + Transaction, TransactionReceipt, }; -use alloy_serde::OtherFields; +use alloy_serde::{OtherFields, WithOtherFields}; use serde::Deserialize; /// length of the name column for pretty formatting `{:>20}{value}` @@ -267,7 +267,7 @@ transactionIndex: {}", } } -impl UIfmt for Block { +impl UIfmt for Block { fn pretty(&self) -> String { format!( " @@ -338,7 +338,7 @@ to {} transactionIndex {} type {} value {} -yParity {}{}", +yParity {}", self.access_list.as_deref().map(Vec::as_slice).unwrap_or(&[]).pretty(), self.block_hash.pretty(), self.block_number.pretty(), @@ -356,7 +356,6 @@ yParity {}{}", self.transaction_type.unwrap(), self.value.pretty(), self.signature.map(|s| s.v).pretty(), - self.other.pretty() ), Some(2) => format!( " @@ -377,7 +376,7 @@ to {} transactionIndex {} type {} value {} -yParity {}{}", +yParity {}", self.access_list.as_deref().map(Vec::as_slice).unwrap_or(&[]).pretty(), self.block_hash.pretty(), self.block_number.pretty(), @@ -396,7 +395,6 @@ yParity {}{}", self.transaction_type.unwrap(), self.value.pretty(), self.signature.map(|s| s.v).pretty(), - self.other.pretty() ), Some(3) => format!( " @@ -419,7 +417,7 @@ to {} transactionIndex {} type {} value {} -yParity {}{}", +yParity {}", self.access_list.as_deref().map(Vec::as_slice).unwrap_or(&[]).pretty(), self.blob_versioned_hashes.as_deref().unwrap_or(&[]).pretty(), self.block_hash.pretty(), @@ -440,7 +438,6 @@ yParity {}{}", self.transaction_type.unwrap(), self.value.pretty(), self.signature.map(|s| s.v).pretty(), - self.other.pretty() ), Some(4) => format!( " @@ -462,7 +459,7 @@ to {} transactionIndex {} type {} value {} -yParity {}{}", +yParity {}", self.access_list.as_deref().map(Vec::as_slice).unwrap_or(&[]).pretty(), self.authorization_list .as_ref() @@ -485,7 +482,6 @@ yParity {}{}", self.transaction_type.unwrap(), self.value.pretty(), self.signature.map(|s| s.v).pretty(), - self.other.pretty() ), _ => format!( " @@ -502,7 +498,7 @@ s {} to {} transactionIndex {} v {} -value {}{}", +value {}", self.block_hash.pretty(), self.block_number.pretty(), self.from.pretty(), @@ -517,12 +513,17 @@ value {}{}", self.transaction_index.pretty(), self.signature.map(|s| s.v).pretty(), self.value.pretty(), - self.other.pretty() ), } } } +impl UIfmt for WithOtherFields { + fn pretty(&self) -> String { + format!("{}{}", self.inner.pretty(), self.other.pretty()) + } +} + /// Various numerical ethereum types used for pretty printing #[derive(Clone, Debug, Deserialize)] #[serde(untagged)] @@ -570,17 +571,12 @@ pub fn get_pretty_tx_attr(transaction: &Transaction, attr: &str) -> Option Some(transaction.transaction_index.pretty()), "v" => transaction.signature.map(|s| s.v.pretty()), "value" => Some(transaction.value.pretty()), - other => { - if let Some(value) = transaction.other.get(other) { - return Some(value.to_string().trim_matches('"').to_string()) - } - None - } + _ => None, } } /// Returns the `UiFmt::pretty()` formatted attribute of the given block -pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { +pub fn get_pretty_block_attr(block: &AnyNetworkBlock, attr: &str) -> Option { match attr { "baseFeePerGas" | "base_fee_per_gas" => Some(block.header.base_fee_per_gas.pretty()), "difficulty" => Some(block.header.difficulty.pretty()), @@ -611,7 +607,7 @@ pub fn get_pretty_block_attr(block: &Block, attr: &str) -> Option { } } -fn pretty_block_basics(block: &Block) -> String { +fn pretty_block_basics(block: &Block) -> String { format!( " baseFeePerGas {} @@ -633,7 +629,7 @@ size {} stateRoot {} timestamp {} ({}) withdrawalsRoot {} -totalDifficulty {}{}", +totalDifficulty {}", block.header.base_fee_per_gas.pretty(), block.header.difficulty.pretty(), block.header.extra_data.pretty(), @@ -657,7 +653,6 @@ totalDifficulty {}{}", .to_rfc2822(), block.header.withdrawals_root.pretty(), block.header.total_difficulty.pretty(), - block.other.pretty() ) } @@ -711,7 +706,7 @@ mod tests { } "#; - let tx: Transaction = serde_json::from_str(s).unwrap(); + let tx: WithOtherFields = serde_json::from_str(s).unwrap(); assert_eq!(tx.pretty().trim(), r" blockHash 0x02b853cf50bc1c335b70790f93d5a390a35a166bea9c895e685cc866e4961cae @@ -1058,7 +1053,7 @@ value 0".to_string(); } ); - let block: Block = serde_json::from_value(json).unwrap(); + let block: AnyNetworkBlock = serde_json::from_value(json).unwrap(); assert_eq!(None, get_pretty_block_attr(&block, "")); assert_eq!(Some("7".to_string()), get_pretty_block_attr(&block, "baseFeePerGas")); diff --git a/crates/common/src/ens.rs b/crates/common/src/ens.rs index e120ad4c2a8a..f84ea84cecab 100644 --- a/crates/common/src/ens.rs +++ b/crates/common/src/ens.rs @@ -1,6 +1,6 @@ //! ENS Name resolving utilities. -#![allow(missing_docs, elided_named_lifetimes)] +#![allow(missing_docs)] use self::EnsResolver::EnsResolverInstance; use alloy_primitives::{address, Address, Keccak256, B256}; diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 4136a9d8e71c..1c9796a2af91 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -2,7 +2,6 @@ //! //! Foundry configuration. -#![allow(elided_named_lifetimes)] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] @@ -525,7 +524,7 @@ impl Config { /// Returns the current `Config` /// - /// See `Config::figment` + /// See [`figment`](Self::figment) for more details. #[track_caller] pub fn load() -> Self { Self::from_provider(Self::figment()) @@ -533,7 +532,7 @@ impl Config { /// Returns the current `Config` with the given `providers` preset /// - /// See `Config::to_figment` + /// See [`figment`](Self::figment) for more details. #[track_caller] pub fn load_with_providers(providers: FigmentProviders) -> Self { Self::default().to_figment(providers).extract().unwrap() @@ -541,10 +540,10 @@ impl Config { /// Returns the current `Config` /// - /// See `Config::figment_with_root` + /// See [`figment_with_root`](Self::figment_with_root) for more details. #[track_caller] - pub fn load_with_root(root: impl Into) -> Self { - Self::from_provider(Self::figment_with_root(root)) + pub fn load_with_root(root: impl AsRef) -> Self { + Self::from_provider(Self::figment_with_root(root.as_ref())) } /// Extract a `Config` from `provider`, panicking if extraction fails. @@ -1131,7 +1130,7 @@ impl Config { pub fn get_rpc_url_or<'a>( &'a self, fallback: impl Into>, - ) -> Result, UnresolvedEnvVarError> { + ) -> Result, UnresolvedEnvVarError> { if let Some(url) = self.get_rpc_url() { url } else { @@ -1411,8 +1410,8 @@ impl Config { /// /// let my_config = Config::figment_with_root(".").extract::(); /// ``` - pub fn figment_with_root(root: impl Into) -> Figment { - Self::with_root(root).into() + pub fn figment_with_root(root: impl AsRef) -> Figment { + Self::with_root(root.as_ref()).into() } /// Creates a new Config that adds additional context extracted from the provided root. @@ -1423,10 +1422,13 @@ impl Config { /// use foundry_config::Config; /// let my_config = Config::with_root("."); /// ``` - pub fn with_root(root: impl Into) -> Self { + pub fn with_root(root: impl AsRef) -> Self { + Self::_with_root(root.as_ref()) + } + + fn _with_root(root: &Path) -> Self { // autodetect paths - let root = root.into(); - let paths = ProjectPathsConfig::builder().build_with_root::<()>(&root); + let paths = ProjectPathsConfig::builder().build_with_root::<()>(root); let artifacts: PathBuf = paths.artifacts.file_name().unwrap().into(); Self { root: paths.root.into(), @@ -1436,7 +1438,7 @@ impl Config { remappings: paths .remappings .into_iter() - .map(|r| RelativeRemapping::new(r, &root)) + .map(|r| RelativeRemapping::new(r, root)) .collect(), fs_permissions: FsPermissions::new([PathPermission::read(artifacts)]), ..Self::default() @@ -1485,7 +1487,7 @@ impl Config { /// /// **Note:** the closure will only be invoked if the `foundry.toml` file exists, See /// [Self::get_config_path()] and if the closure returns `true`. - pub fn update_at(root: impl Into, f: F) -> eyre::Result<()> + pub fn update_at(root: &Path, f: F) -> eyre::Result<()> where F: FnOnce(&Self, &mut toml_edit::DocumentMut) -> bool, { diff --git a/crates/config/src/macros.rs b/crates/config/src/macros.rs index d4f3a59ba1f2..b9261f03453a 100644 --- a/crates/config/src/macros.rs +++ b/crates/config/src/macros.rs @@ -61,9 +61,8 @@ macro_rules! impl_figment_convert { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { let root = args.root.clone() - .unwrap_or_else(|| $crate::find_project_root_path(None) - .unwrap_or_else(|e| panic!("could not find project root: {e}"))); - $crate::Config::figment_with_root(root).merge(args) + .unwrap_or_else(|| $crate::find_project_root(None)); + $crate::Config::figment_with_root(&root).merge(args) } } @@ -194,7 +193,7 @@ macro_rules! impl_figment_convert_cast { ($name:ty) => { impl<'a> From<&'a $name> for $crate::figment::Figment { fn from(args: &'a $name) -> Self { - $crate::Config::with_root($crate::find_project_root_path(None).unwrap()) + $crate::Config::with_root(&$crate::find_project_root(None)) .to_figment($crate::FigmentProviders::Cast) .merge(args) } diff --git a/crates/config/src/utils.rs b/crates/config/src/utils.rs index dff36e4fda40..b1434bc7a62c 100644 --- a/crates/config/src/utils.rs +++ b/crates/config/src/utils.rs @@ -2,7 +2,6 @@ use crate::Config; use alloy_primitives::U256; -use eyre::WrapErr; use figment::value::Value; use foundry_compilers::artifacts::{ remappings::{Remapping, RemappingError}, @@ -11,6 +10,7 @@ use foundry_compilers::artifacts::{ use revm_primitives::SpecId; use serde::{de::Error, Deserialize, Deserializer}; use std::{ + io, path::{Path, PathBuf}, str::FromStr, }; @@ -21,36 +21,31 @@ pub fn load_config() -> Config { load_config_with_root(None) } -/// Loads the config for the current project workspace or the provided root path -pub fn load_config_with_root(root: Option) -> Config { - if let Some(root) = root { - Config::load_with_root(root) - } else { - Config::load_with_root(find_project_root_path(None).unwrap()) - } - .sanitized() +/// Loads the config for the current project workspace or the provided root path. +/// +/// # Panics +/// +/// Panics if the project root cannot be found. See [`find_project_root`]. +#[track_caller] +pub fn load_config_with_root(root: Option<&Path>) -> Config { + let root = match root { + Some(root) => root, + None => &find_project_root(None), + }; + Config::load_with_root(root).sanitized() } -/// Returns the path of the top-level directory of the working git tree. If there is no working -/// tree, an error is returned. -pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result { - let path = relative_to.as_ref(); - let path = std::process::Command::new("git") - .args(["rev-parse", "--show-toplevel"]) - .current_dir(path) - .output() - .wrap_err_with(|| { - format!("Failed detect git root path in current dir: {}", path.display()) - })? - .stdout; - let path = std::str::from_utf8(&path)?.trim_end_matches('\n'); - Ok(PathBuf::from(path)) +/// Returns the path of the top-level directory of the working git tree. +pub fn find_git_root(relative_to: &Path) -> io::Result> { + let root = + if relative_to.is_absolute() { relative_to } else { &dunce::canonicalize(relative_to)? }; + Ok(root.ancestors().find(|p| p.join(".git").is_dir()).map(Path::to_path_buf)) } -/// Returns the root path to set for the project root +/// Returns the root path to set for the project root. /// -/// traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, -/// but only until the root dir of the current repo so that +/// Traverse the dir tree up and look for a `foundry.toml` file starting at the given path or cwd, +/// but only until the root dir of the current repo so that: /// /// ```text /// -- foundry.toml @@ -60,29 +55,37 @@ pub fn find_git_root_path(relative_to: impl AsRef) -> eyre::Result) -> std::io::Result { - let cwd = &std::env::current_dir()?; - let cwd = path.unwrap_or(cwd); - let boundary = find_git_root_path(cwd) - .ok() - .filter(|p| !p.as_os_str().is_empty()) - .unwrap_or_else(|| cwd.clone()); - let mut cwd = cwd.as_path(); - // traverse as long as we're in the current git repo cwd - while cwd.starts_with(&boundary) { - let file_path = cwd.join(Config::FILE_NAME); - if file_path.is_file() { - return Ok(cwd.to_path_buf()) - } - if let Some(parent) = cwd.parent() { - cwd = parent; - } else { - break - } - } - // no foundry.toml found - Ok(boundary) +/// +/// will still detect `repo` as root. +/// +/// Returns `repo` or `cwd` if no `foundry.toml` is found in the tree. +/// +/// # Panics +/// +/// Panics if: +/// - `cwd` is `Some` and is not a valid directory; +/// - `cwd` is `None` and the [`std::env::current_dir`] call fails. +#[track_caller] +pub fn find_project_root(cwd: Option<&Path>) -> PathBuf { + try_find_project_root(cwd).expect("Could not find project root") +} + +/// Returns the root path to set for the project root. +/// +/// Same as [`find_project_root`], but returns an error instead of panicking. +pub fn try_find_project_root(cwd: Option<&Path>) -> io::Result { + let cwd = match cwd { + Some(path) => path, + None => &std::env::current_dir()?, + }; + let boundary = find_git_root(cwd)?; + let found = cwd + .ancestors() + // Don't look outside of the git repo if it exists. + .take_while(|p| if let Some(boundary) = &boundary { p.starts_with(boundary) } else { true }) + .find(|p| p.join(Config::FILE_NAME).is_file()) + .map(Path::to_path_buf); + Ok(found.or(boundary).unwrap_or_else(|| cwd.to_path_buf())) } /// Returns all [`Remapping`]s contained in the `remappings` str separated by newlines @@ -160,7 +163,7 @@ pub fn foundry_toml_dirs(root: impl AsRef) -> Vec { .into_iter() .filter_map(Result::ok) .filter(|e| e.file_type().is_dir()) - .filter_map(|e| foundry_compilers::utils::canonicalize(e.path()).ok()) + .filter_map(|e| dunce::canonicalize(e.path()).ok()) .filter(|p| p.join(Config::FILE_NAME).exists()) .collect() } diff --git a/crates/doc/src/preprocessor/infer_hyperlinks.rs b/crates/doc/src/preprocessor/infer_hyperlinks.rs index 5108ee374b4c..2d080278911b 100644 --- a/crates/doc/src/preprocessor/infer_hyperlinks.rs +++ b/crates/doc/src/preprocessor/infer_hyperlinks.rs @@ -1,5 +1,3 @@ -#![allow(elided_named_lifetimes)] - use super::{Preprocessor, PreprocessorId}; use crate::{Comments, Document, ParseItem, ParseSource}; use forge_fmt::solang_ext::SafeUnwrap; @@ -228,7 +226,7 @@ impl<'a> InlineLink<'a> { }) } - fn captures(s: &'a str) -> impl Iterator + '_ { + fn captures(s: &'a str) -> impl Iterator + 'a { RE_INLINE_LINK.captures(s).map(Self::from_capture).into_iter().flatten() } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 72364bed7ccc..e506416aa2f6 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -9,9 +9,7 @@ use crate::{ }; use alloy_genesis::GenesisAccount; use alloy_primitives::{keccak256, uint, Address, TxKind, B256, U256}; -use alloy_rpc_types::{ - Block, BlockNumberOrTag, BlockTransactions, Transaction, TransactionRequest, -}; +use alloy_rpc_types::{Block, BlockNumberOrTag, Transaction, TransactionRequest}; use alloy_serde::WithOtherFields; use eyre::Context; use foundry_common::{is_known_system_sender, SYSTEM_TRANSACTION_TYPE}; @@ -830,7 +828,7 @@ impl Backend { &self, id: LocalForkId, transaction: B256, - ) -> eyre::Result<(u64, Block)> { + ) -> eyre::Result<(u64, Block>)> { let fork = self.inner.get_fork_by_id(id)?; let tx = fork.db.db.get_transaction(transaction)?; @@ -841,12 +839,13 @@ impl Backend { // we need to subtract 1 here because we want the state before the transaction // was mined let fork_block = tx_block - 1; - Ok((fork_block, block)) + Ok((fork_block, block.inner)) } else { let block = fork.db.db.get_full_block(BlockNumberOrTag::Latest)?; - let number = - block.header.number.ok_or_else(|| BackendError::msg("missing block number"))?; + let number = block.header.number; + + let block = block.inner; Ok((number, block)) } @@ -871,33 +870,31 @@ impl Backend { let fork = self.inner.get_fork_by_id_mut(id)?; let full_block = fork.db.db.get_full_block(env.block.number.to::())?; - if let BlockTransactions::Full(txs) = full_block.transactions { - for tx in txs.into_iter() { - // System transactions such as on L2s don't contain any pricing info so we skip them - // otherwise this would cause reverts - if is_known_system_sender(tx.from) || - tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) - { - trace!(tx=?tx.hash, "skipping system transaction"); - continue; - } + for tx in full_block.transactions.clone().into_transactions() { + // System transactions such as on L2s don't contain any pricing info so we skip them + // otherwise this would cause reverts + if is_known_system_sender(tx.from) || + tx.transaction_type == Some(SYSTEM_TRANSACTION_TYPE) + { + trace!(tx=?tx.hash, "skipping system transaction"); + continue; + } - if tx.hash == tx_hash { - // found the target transaction - return Ok(Some(tx)) - } - trace!(tx=?tx.hash, "committing transaction"); - - commit_transaction( - WithOtherFields::new(tx), - env.clone(), - journaled_state, - fork, - &fork_id, - &persistent_accounts, - &mut NoOpInspector, - )?; + if tx.hash == tx_hash { + // found the target transaction + return Ok(Some(tx.inner)) } + trace!(tx=?tx.hash, "committing transaction"); + + commit_transaction( + tx, + env.clone(), + journaled_state, + fork, + &fork_id, + &persistent_accounts, + &mut NoOpInspector, + )?; } Ok(None) @@ -1212,7 +1209,7 @@ impl DatabaseExt for Backend { // roll the fork to the transaction's block or latest if it's pending self.roll_fork(Some(id), fork_block, env, journaled_state)?; - update_env_block(env, fork_block, &block); + update_env_block(env, &block); // replay all transactions that came before let env = env.clone(); @@ -1242,10 +1239,10 @@ impl DatabaseExt for Backend { // This is a bit ambiguous because the user wants to transact an arbitrary transaction in the current context, but we're assuming the user wants to transact the transaction as it was mined. Usually this is used in a combination of a fork at the transaction's parent transaction in the block and then the transaction is transacted: // So we modify the env to match the transaction's block - let (fork_block, block) = + let (_fork_block, block) = self.get_block_number_and_block_for_transaction(id, transaction)?; let mut env = env.clone(); - update_env_block(&mut env, fork_block, &block); + update_env_block(&mut env, &block); let env = self.env_with_handler_cfg(env); let fork = self.inner.get_fork_by_id_mut(id)?; @@ -1897,14 +1894,14 @@ fn is_contract_in_state(journaled_state: &JournaledState, acc: Address) -> bool } /// Updates the env's block with the block's data -fn update_env_block(env: &mut Env, fork_block: u64, block: &Block) { +fn update_env_block(env: &mut Env, block: &Block) { env.block.timestamp = U256::from(block.header.timestamp); env.block.coinbase = block.header.miner; env.block.difficulty = block.header.difficulty; env.block.prevrandao = Some(block.header.mix_hash.unwrap_or_default()); env.block.basefee = U256::from(block.header.base_fee_per_gas.unwrap_or_default()); env.block.gas_limit = U256::from(block.header.gas_limit); - env.block.number = U256::from(block.header.number.unwrap_or(fork_block)); + env.block.number = U256::from(block.header.number); } /// Executes the given transaction and commits state changes to the database _and_ the journaled @@ -1918,7 +1915,7 @@ fn commit_transaction>( persistent_accounts: &HashSet
, inspector: I, ) -> eyre::Result<()> { - configure_tx_env(&mut env.env, &tx); + configure_tx_env(&mut env.env, &tx.inner); let now = Instant::now(); let res = { diff --git a/crates/evm/core/src/fork/init.rs b/crates/evm/core/src/fork/init.rs index f60b99cb9708..b32fece5a037 100644 --- a/crates/evm/core/src/fork/init.rs +++ b/crates/evm/core/src/fork/init.rs @@ -1,7 +1,10 @@ use crate::utils::apply_chain_and_block_specific_env_changes; use alloy_primitives::{Address, U256}; -use alloy_provider::{Network, Provider}; -use alloy_rpc_types::{Block, BlockNumberOrTag}; +use alloy_provider::{ + network::{BlockResponse, HeaderResponse}, + Network, Provider, +}; +use alloy_rpc_types::BlockNumberOrTag; use alloy_transport::Transport; use eyre::WrapErr; use foundry_common::NON_ARCHIVE_NODE_WARNING; @@ -18,7 +21,7 @@ pub async fn environment>( pin_block: Option, origin: Address, disable_block_gas_limit: bool, -) -> eyre::Result<(Env, Block)> { +) -> eyre::Result<(Env, N::BlockResponse)> { let block_number = if let Some(pin_block) = pin_block { pin_block } else { @@ -61,25 +64,25 @@ pub async fn environment>( let mut env = Env { cfg, block: BlockEnv { - number: U256::from(block.header.number.expect("block number not found")), - timestamp: U256::from(block.header.timestamp), - coinbase: block.header.miner, - difficulty: block.header.difficulty, - prevrandao: Some(block.header.mix_hash.unwrap_or_default()), - basefee: U256::from(block.header.base_fee_per_gas.unwrap_or_default()), - gas_limit: U256::from(block.header.gas_limit), + number: U256::from(block.header().number()), + timestamp: U256::from(block.header().timestamp()), + coinbase: block.header().coinbase(), + difficulty: block.header().difficulty(), + prevrandao: block.header().mix_hash(), + basefee: U256::from(block.header().base_fee_per_gas().unwrap_or_default()), + gas_limit: U256::from(block.header().gas_limit()), ..Default::default() }, tx: TxEnv { caller: origin, gas_price: U256::from(gas_price.unwrap_or(fork_gas_price)), chain_id: Some(override_chain_id.unwrap_or(rpc_chain_id)), - gas_limit: block.header.gas_limit as u64, + gas_limit: block.header().gas_limit() as u64, ..Default::default() }, }; - apply_chain_and_block_specific_env_changes(&mut env, &block); + apply_chain_and_block_specific_env_changes::(&mut env, &block); Ok((env, block)) } diff --git a/crates/evm/core/src/fork/multi.rs b/crates/evm/core/src/fork/multi.rs index 001c65469968..a39b543ea254 100644 --- a/crates/evm/core/src/fork/multi.rs +++ b/crates/evm/core/src/fork/multi.rs @@ -5,6 +5,7 @@ use super::CreateFork; use alloy_primitives::U256; +use alloy_provider::network::{BlockResponse, HeaderResponse}; use alloy_transport::layers::RetryBackoffService; use foundry_common::provider::{ runtime_transport::RuntimeTransport, ProviderBuilder, RetryProvider, @@ -524,7 +525,7 @@ async fn create_fork(mut fork: CreateFork) -> eyre::Result<(ForkId, CreatedFork, // We need to use the block number from the block because the env's number can be different on // some L2s (e.g. Arbitrum). - let number = block.header.number.unwrap_or(meta.block_env.number.to()); + let number = block.header().number(); // Determine the cache path if caching is enabled. let cache_path = if fork.enable_caching { diff --git a/crates/evm/core/src/opts.rs b/crates/evm/core/src/opts.rs index f9e674e09a26..7a41d075641b 100644 --- a/crates/evm/core/src/opts.rs +++ b/crates/evm/core/src/opts.rs @@ -2,7 +2,7 @@ use super::fork::environment; use crate::fork::CreateFork; use alloy_primitives::{Address, B256, U256}; use alloy_provider::Provider; -use alloy_rpc_types::Block; +use alloy_rpc_types::AnyNetworkBlock; use eyre::WrapErr; use foundry_common::{provider::ProviderBuilder, ALCHEMY_FREE_TIER_CUPS}; use foundry_config::{Chain, Config}; @@ -86,7 +86,7 @@ impl EvmOpts { pub async fn fork_evm_env( &self, fork_url: impl AsRef, - ) -> eyre::Result<(revm::primitives::Env, Block)> { + ) -> eyre::Result<(revm::primitives::Env, AnyNetworkBlock)> { let fork_url = fork_url.as_ref(); let provider = ProviderBuilder::new(fork_url) .compute_units_per_second(self.get_compute_units_per_second()) diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 21284e78035b..27db37eaf1e2 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -2,7 +2,11 @@ pub use crate::ic::*; use crate::{constants::DEFAULT_CREATE2_DEPLOYER, precompiles::ALPHANET_P256, InspectorExt}; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Selector, TxKind, U256}; -use alloy_rpc_types::{Block, Transaction}; +use alloy_provider::{ + network::{BlockResponse, HeaderResponse}, + Network, +}; +use alloy_rpc_types::Transaction; use foundry_config::NamedChain; use revm::{ db::WrapDatabaseRef, @@ -24,9 +28,12 @@ pub use revm::primitives::EvmState as StateChangeset; /// - applies chain specifics: on Arbitrum `block.number` is the L1 block /// /// Should be called with proper chain id (retrieved from provider if not provided). -pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::Env, block: &Block) { +pub fn apply_chain_and_block_specific_env_changes( + env: &mut revm::primitives::Env, + block: &N::BlockResponse, +) { if let Ok(chain) = NamedChain::try_from(env.cfg.chain_id) { - let block_number = block.header.number.unwrap_or_default(); + let block_number = block.header().number(); match chain { NamedChain::Mainnet => { @@ -43,10 +50,14 @@ pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::En NamedChain::ArbitrumTestnet => { // on arbitrum `block.number` is the L1 block which is included in the // `l1BlockNumber` field - if let Some(l1_block_number) = block.other.get("l1BlockNumber").cloned() { - if let Ok(l1_block_number) = serde_json::from_value::(l1_block_number) { - env.block.number = l1_block_number; - } + if let Some(l1_block_number) = block + .other_fields() + .and_then(|other| other.get("l1BlockNumber").cloned()) + .and_then(|l1_block_number| { + serde_json::from_value::(l1_block_number).ok() + }) + { + env.block.number = l1_block_number; } } _ => {} @@ -54,7 +65,7 @@ pub fn apply_chain_and_block_specific_env_changes(env: &mut revm::primitives::En } // if difficulty is `0` we assume it's past merge - if block.header.difficulty.is_zero() { + if block.header().difficulty().is_zero() { env.block.difficulty = env.block.prevrandao.unwrap_or_default().into(); } } diff --git a/crates/evm/traces/src/folded_stack_trace.rs b/crates/evm/traces/src/folded_stack_trace.rs new file mode 100644 index 000000000000..de76f8e80408 --- /dev/null +++ b/crates/evm/traces/src/folded_stack_trace.rs @@ -0,0 +1,301 @@ +use alloy_primitives::hex::ToHexExt; +use revm_inspectors::tracing::{ + types::{CallTraceNode, CallTraceStep, DecodedTraceStep, TraceMemberOrder}, + CallTraceArena, +}; + +/// Builds a folded stack trace from a call trace arena. +pub fn build(arena: &CallTraceArena) -> Vec { + let mut fst = EvmFoldedStackTraceBuilder::default(); + fst.process_call_node(arena.nodes(), 0); + fst.build() +} + +/// Wrapper for building a folded stack trace using EVM call trace node. +#[derive(Default)] +pub struct EvmFoldedStackTraceBuilder { + /// Raw folded stack trace builder. + fst: FoldedStackTraceBuilder, +} + +impl EvmFoldedStackTraceBuilder { + /// Returns the folded stack trace. + pub fn build(self) -> Vec { + self.fst.build() + } + + /// Creates an entry for a EVM CALL in the folded stack trace. This method recursively processes + /// all the children nodes of the call node and at the end it exits. + pub fn process_call_node(&mut self, nodes: &[CallTraceNode], idx: usize) { + let node = &nodes[idx]; + + let func_name = if node.trace.kind.is_any_create() { + let default_contract_name = "Contract".to_string(); + let contract_name = node.trace.decoded.label.as_ref().unwrap_or(&default_contract_name); + format!("new {contract_name}") + } else { + let selector = node + .selector() + .map(|selector| selector.encode_hex_with_prefix()) + .unwrap_or("fallback".to_string()); + let signature = + node.trace.decoded.call_data.as_ref().map(|dc| &dc.signature).unwrap_or(&selector); + + if let Some(label) = &node.trace.decoded.label { + format!("{label}.{signature}") + } else { + signature.clone() + } + }; + + self.fst.enter(func_name, node.trace.gas_used as i64); + + // Track internal function step exits to do in this call context. + let mut step_exits = vec![]; + + // Process children nodes. + for order in &node.ordering { + match order { + TraceMemberOrder::Call(child_idx) => { + let child_node_idx = node.children[*child_idx]; + self.process_call_node(nodes, child_node_idx); + } + TraceMemberOrder::Step(step_idx) => { + self.exit_previous_steps(&mut step_exits, *step_idx); + self.process_step(&node.trace.steps, *step_idx, &mut step_exits) + } + TraceMemberOrder::Log(_) => {} + } + } + + // Exit pending internal function calls if any. + for _ in 0..step_exits.len() { + self.fst.exit(); + } + + // Exit from this call context in the folded stack trace. + self.fst.exit(); + } + + /// Creates an entry for an internal function call in the folded stack trace. This method only + /// enters the function in the folded stack trace, we cannot exit since we need to exit at a + /// future step. Hence, we keep track of the step end index in the `step_exits`. + fn process_step( + &mut self, + steps: &[CallTraceStep], + step_idx: usize, + step_exits: &mut Vec, + ) { + let step = &steps[step_idx]; + if let Some(decoded_step) = &step.decoded { + match decoded_step { + DecodedTraceStep::InternalCall(decoded_internal_call, step_end_idx) => { + let gas_used = steps[*step_end_idx].gas_used.saturating_sub(step.gas_used); + self.fst.enter(decoded_internal_call.func_name.clone(), gas_used as i64); + step_exits.push(*step_end_idx); + } + DecodedTraceStep::Line(_) => {} + } + } + } + + /// Exits all the previous internal calls that should end before starting step_idx. + fn exit_previous_steps(&mut self, step_exits: &mut Vec, step_idx: usize) { + let initial_length = step_exits.len(); + step_exits.retain(|&number| number > step_idx); + + let num_exits = initial_length - step_exits.len(); + for _ in 0..num_exits { + self.fst.exit(); + } + } +} + +/// Helps to translate a function enter-exit flow into a folded stack trace. +/// +/// Example: +/// fn top() { child_a(); child_b() } // consumes 500 gas +/// fn child_a() {} // consumes 100 gas +/// fn child_b() {} // consumes 200 gas +/// +/// For execution of the `top` function looks like: +/// 1. enter `top` +/// 2. enter `child_a` +/// 3. exit `child_a` +/// 4. enter `child_b` +/// 5. exit `child_b` +/// 6. exit `top` +/// +/// The translated folded stack trace lines look like: +/// 1. top +/// 2. top;child_a +/// 3. top;child_b +/// +/// Including the gas consumed by the function by itself. +/// 1. top 200 // 500 - 100 - 200 +/// 2. top;child_a 100 +/// 3. top;child_b 200 +#[derive(Debug, Default)] +pub struct FoldedStackTraceBuilder { + /// Trace entries. + traces: Vec, + /// Number of exits to be done before entering a new function. + exits: usize, +} + +#[derive(Debug, Default)] +struct TraceEntry { + /// Names of all functions in the call stack of this trace. + names: Vec, + /// Gas consumed by this function, allowed to be negative due to refunds. + gas: i64, +} + +impl FoldedStackTraceBuilder { + /// Enter execution of a function call that consumes `gas`. + pub fn enter(&mut self, label: String, gas: i64) { + let mut names = self.traces.last().map(|entry| entry.names.clone()).unwrap_or_default(); + + while self.exits > 0 { + names.pop(); + self.exits -= 1; + } + + names.push(label); + self.traces.push(TraceEntry { names, gas }); + } + + /// Exit execution of a function call. + pub fn exit(&mut self) { + self.exits += 1; + } + + /// Returns folded stack trace. + pub fn build(mut self) -> Vec { + self.subtract_children(); + self.build_without_subtraction() + } + + /// Internal method to build the folded stack trace without subtracting gas consumed by + /// the children function calls. + fn build_without_subtraction(&mut self) -> Vec { + let mut lines = Vec::new(); + for TraceEntry { names, gas } in self.traces.iter() { + lines.push(format!("{} {}", names.join(";"), gas)); + } + lines + } + + /// Subtracts gas consumed by the children function calls from the parent function calls. + fn subtract_children(&mut self) { + // Iterate over each trace to find the children and subtract their values from the parents. + for i in 0..self.traces.len() { + let (left, right) = self.traces.split_at_mut(i); + let TraceEntry { names, gas } = &right[0]; + if names.len() > 1 { + let parent_trace_to_match = &names[..names.len() - 1]; + for parent in left.iter_mut().rev() { + if parent.names == parent_trace_to_match { + parent.gas -= gas; + break; + } + } + } + } + } +} + +mod tests { + #[test] + fn test_fst_1() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 500); + trace.enter("child_a".to_string(), 100); + trace.exit(); + trace.enter("child_b".to_string(), 200); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 500", // + "top;child_a 100", + "top;child_b 200", + ] + ); + assert_eq!( + trace.build(), + vec![ + "top 200", // 500 - 100 - 200 + "top;child_a 100", + "top;child_b 200", + ] + ); + } + + #[test] + fn test_fst_2() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 500); + trace.enter("child_a".to_string(), 300); + trace.enter("child_b".to_string(), 100); + trace.exit(); + trace.exit(); + trace.enter("child_c".to_string(), 100); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 500", // + "top;child_a 300", + "top;child_a;child_b 100", + "top;child_c 100", + ] + ); + + assert_eq!( + trace.build(), + vec![ + "top 100", // 500 - 300 - 100 + "top;child_a 200", // 300 - 100 + "top;child_a;child_b 100", + "top;child_c 100", + ] + ); + } + + #[test] + fn test_fst_3() { + let mut trace = super::FoldedStackTraceBuilder::default(); + trace.enter("top".to_string(), 1700); + trace.enter("child_a".to_string(), 500); + trace.exit(); + trace.enter("child_b".to_string(), 500); + trace.enter("child_c".to_string(), 500); + trace.exit(); + trace.exit(); + trace.exit(); + trace.enter("top2".to_string(), 1700); + + assert_eq!( + trace.build_without_subtraction(), + vec![ + "top 1700", // + "top;child_a 500", + "top;child_b 500", + "top;child_b;child_c 500", + "top2 1700", + ] + ); + + assert_eq!( + trace.build(), + vec![ + "top 700", // + "top;child_a 500", + "top;child_b 0", + "top;child_b;child_c 500", + "top2 1700", + ] + ); + } +} diff --git a/crates/evm/traces/src/lib.rs b/crates/evm/traces/src/lib.rs index 4a80b164ad33..fb1665a5a6cd 100644 --- a/crates/evm/traces/src/lib.rs +++ b/crates/evm/traces/src/lib.rs @@ -42,6 +42,8 @@ pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder}; pub mod debug; pub use debug::DebugTraceIdentifier; +pub mod folded_stack_trace; + pub type Traces = Vec<(TraceKind, SparsedTraceArena)>; /// Trace arena keeping track of ignored trace items. diff --git a/crates/forge/Cargo.toml b/crates/forge/Cargo.toml index 4f9110ff60e7..77034d6194de 100644 --- a/crates/forge/Cargo.toml +++ b/crates/forge/Cargo.toml @@ -78,6 +78,7 @@ dialoguer = { version = "0.11", default-features = false } dunce.workspace = true futures.workspace = true indicatif = "0.17" +inferno = { version = "0.11.19", default-features = false } itertools.workspace = true parking_lot.workspace = true regex = { version = "1", default-features = false } diff --git a/crates/forge/bin/cmd/bind.rs b/crates/forge/bin/cmd/bind.rs index f9e9a22a42e1..d5d23633243f 100644 --- a/crates/forge/bin/cmd/bind.rs +++ b/crates/forge/bin/cmd/bind.rs @@ -104,10 +104,10 @@ impl BindArgs { let _ = ProjectCompiler::new().compile(&project)?; } - if !self.alloy { + if self.ethers { eprintln!( - "Warning: `--ethers` (default) bindings are deprecated and will be removed in the future. \ - Consider using `--alloy` instead." + "Warning: `--ethers` bindings are deprecated and will be removed in the future. \ + Consider using `--alloy` (default) instead." ); } @@ -191,7 +191,7 @@ impl BindArgs { fn get_json_files(&self, artifacts: &Path) -> Result> { let filter = self.get_filter()?; let alloy_filter = self.get_alloy_filter()?; - let is_alloy = self.alloy; + let is_alloy = !self.ethers; Ok(json_files(artifacts) .filter_map(|path| { // Ignore the build info JSON. @@ -264,7 +264,7 @@ impl BindArgs { /// Check that the existing bindings match the expected abigen output fn check_existing_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - if !self.alloy { + if self.ethers { return self.check_ethers(artifacts, bindings_root); } @@ -316,7 +316,7 @@ impl BindArgs { /// Generate the bindings fn generate_bindings(&self, artifacts: &Path, bindings_root: &Path) -> Result<()> { - if !self.alloy { + if self.ethers { return self.generate_ethers(artifacts, bindings_root); } diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index f242d3d191d6..53bc5bc2001b 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -117,7 +117,7 @@ impl BuildArgs { /// Returns the `Project` for the current workspace /// /// This loads the `foundry_config::Config` for the current workspace (see - /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning + /// [`utils::find_project_root`] and merges the cli `BuildArgs` into it before returning /// [`foundry_config::Config::project()`] pub fn project(&self) -> Result { self.args.project() diff --git a/crates/forge/bin/cmd/clone.rs b/crates/forge/bin/cmd/clone.rs index 65b3cf01d6d0..98ef95d40e2d 100644 --- a/crates/forge/bin/cmd/clone.rs +++ b/crates/forge/bin/cmd/clone.rs @@ -546,7 +546,7 @@ fn dump_sources(meta: &Metadata, root: &PathBuf, no_reorg: bool) -> Result Result { +pub fn compile_project(root: &Path, quiet: bool) -> Result { let mut config = Config::load_with_root(root).sanitized(); config.extra_output.push(ContractOutputSelection::StorageLayout); let project = config.project()?; @@ -612,7 +612,7 @@ mod tests { use super::*; use alloy_primitives::hex; use foundry_compilers::Artifact; - use foundry_test_utils::rpc::next_etherscan_api_key; + use foundry_test_utils::rpc::next_mainnet_etherscan_api_key; use std::collections::BTreeMap; fn assert_successful_compilation(root: &PathBuf) -> ProjectCompileOutput { @@ -690,7 +690,7 @@ mod tests { // create folder if not exists std::fs::create_dir_all(&data_folder).unwrap(); // create metadata.json and creation_data.json - let client = Client::new(Chain::mainnet(), next_etherscan_api_key()).unwrap(); + let client = Client::new(Chain::mainnet(), next_mainnet_etherscan_api_key()).unwrap(); let meta = client.contract_source_code(address).await.unwrap(); // dump json let json = serde_json::to_string_pretty(&meta).unwrap(); diff --git a/crates/forge/bin/cmd/doc/mod.rs b/crates/forge/bin/cmd/doc/mod.rs index ad4375bfb750..ad61facf5c95 100644 --- a/crates/forge/bin/cmd/doc/mod.rs +++ b/crates/forge/bin/cmd/doc/mod.rs @@ -6,7 +6,7 @@ use forge_doc::{ }; use foundry_cli::opts::GH_REPO_PREFIX_REGEX; use foundry_common::compile::ProjectCompiler; -use foundry_config::{find_project_root_path, load_config_with_root, Config}; +use foundry_config::{find_project_root, load_config_with_root, Config}; use std::{path::PathBuf, process::Command}; mod server; @@ -139,7 +139,10 @@ impl DocArgs { } pub fn config(&self) -> Result { - let root = self.root.clone().unwrap_or(find_project_root_path(None)?); + let root = match &self.root { + Some(root) => root, + None => &find_project_root(None), + }; Ok(load_config_with_root(Some(root))) } } diff --git a/crates/forge/bin/cmd/selectors.rs b/crates/forge/bin/cmd/selectors.rs index 04d19295c69c..7b7bce2e5cd1 100644 --- a/crates/forge/bin/cmd/selectors.rs +++ b/crates/forge/bin/cmd/selectors.rs @@ -330,7 +330,7 @@ impl SelectorsSubcommands { for error in abi.errors() { if error.selector().as_slice().starts_with(selector_bytes.as_slice()) { table.add_row([ - "Event", + "Error", &error.signature(), &hex::encode_prefixed(error.selector()), contract.as_str(), diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index d8a16a8cc860..08e498798034 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -1,7 +1,7 @@ use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; use alloy_primitives::U256; use clap::{Parser, ValueHint}; -use eyre::Result; +use eyre::{Context, OptionExt, Result}; use forge::{ decode::decode_console_logs, gas_report::GasReport, @@ -9,7 +9,7 @@ use forge::{ result::{SuiteResult, TestOutcome, TestStatus}, traces::{ debug::{ContractSources, DebugTraceIdentifier}, - decode_trace_arena, + decode_trace_arena, folded_stack_trace, identifier::SignaturesIdentifier, render_trace_arena, CallTraceDecoderBuilder, InternalTraceMode, TraceKind, }, @@ -86,6 +86,14 @@ pub struct TestArgs { #[arg(long, value_name = "TEST_FUNCTION")] debug: Option, + /// Generate a flamegraph for a single test. Implies `--decode-internal`. + #[arg(long, conflicts_with = "flamechart")] + flamegraph: bool, + + /// Generate a flamechart for a single test. Implies `--decode-internal`. + #[arg(long, conflicts_with = "flamegraph")] + flamechart: bool, + /// Whether to identify internal functions in traces. /// /// If no argument is passed to this flag, it will trace internal functions scope and decode @@ -259,7 +267,7 @@ impl TestArgs { /// configured filter will be executed /// /// Returns the test results for all matching tests. - pub async fn execute_tests(self) -> Result { + pub async fn execute_tests(mut self) -> Result { // Merge all configs. let (mut config, mut evm_opts) = self.load_config_and_evm_opts_emit_warnings()?; @@ -313,14 +321,22 @@ impl TestArgs { .profiles(profiles) .build(&output, project_root)?; + let should_debug = self.debug.is_some(); + let should_draw = self.flamegraph || self.flamechart; + // Determine print verbosity and executor verbosity. let verbosity = evm_opts.verbosity; - if self.gas_report && evm_opts.verbosity < 3 { + if (self.gas_report && evm_opts.verbosity < 3) || self.flamegraph || self.flamechart { evm_opts.verbosity = 3; } let env = evm_opts.evm_env().await?; + // Enable internal tracing for more informative flamegraph. + if should_draw { + self.decode_internal = Some(None); + } + // Choose the internal function tracing mode, if --decode-internal is provided. let decode_internal = if let Some(maybe_fn) = self.decode_internal.as_ref() { if maybe_fn.is_some() { @@ -335,7 +351,6 @@ impl TestArgs { }; // Prepare the test builder. - let should_debug = self.debug.is_some(); let config = Arc::new(config); let runner = MultiContractRunnerBuilder::new(config.clone()) .set_debug(should_debug) @@ -371,18 +386,60 @@ impl TestArgs { )?; let libraries = runner.libraries.clone(); - let outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; + let mut outcome = self.run_tests(runner, config, verbosity, &filter, &output).await?; + + if should_draw { + let (suite_name, test_name, mut test_result) = + outcome.remove_first().ok_or_eyre("no tests were executed")?; + + let arena = test_result + .traces + .iter_mut() + .find_map( + |(kind, arena)| { + if *kind == TraceKind::Execution { + Some(arena) + } else { + None + } + }, + ) + .unwrap(); + + // Decode traces. + let decoder = outcome.last_run_decoder.as_ref().unwrap(); + decode_trace_arena(arena, decoder).await?; + let mut fst = folded_stack_trace::build(arena); + + let label = if self.flamegraph { "flamegraph" } else { "flamechart" }; + let contract = suite_name.split(':').last().unwrap(); + let test_name = test_name.trim_end_matches("()"); + let file_name = format!("cache/{label}_{contract}_{test_name}.svg"); + let file = std::fs::File::create(&file_name).wrap_err("failed to create file")?; + + let mut options = inferno::flamegraph::Options::default(); + options.title = format!("{label} {contract}::{test_name}"); + options.count_name = "gas".to_string(); + if self.flamechart { + options.flame_chart = true; + fst.reverse(); + } + + // Generate SVG. + inferno::flamegraph::from_lines(&mut options, fst.iter().map(|s| s.as_str()), file) + .wrap_err("failed to write svg")?; + println!("\nSaved to {file_name}"); + + // Open SVG in default program. + if opener::open(&file_name).is_err() { + println!("\nFailed to open {file_name}. Please open it manually."); + } + } if should_debug { // Get first non-empty suite result. We will have only one such entry. - let Some((_, test_result)) = outcome - .results - .iter() - .find(|(_, r)| !r.test_results.is_empty()) - .map(|(_, r)| (r, r.test_results.values().next().unwrap())) - else { - return Err(eyre::eyre!("no tests were executed")); - }; + let (_, _, test_result) = + outcome.remove_first().ok_or_eyre("no tests were executed")?; let sources = ContractSources::from_project_output(&output, project.root(), Some(&libraries))?; @@ -422,13 +479,17 @@ impl TestArgs { trace!(target: "forge::test", "running all tests"); let num_filtered = runner.matching_test_functions(filter).count(); - if (self.debug.is_some() || self.decode_internal.as_ref().map_or(false, |v| v.is_some())) && + if (self.debug.is_some() || + self.decode_internal.as_ref().map_or(false, |v| v.is_some()) || + self.flamegraph || + self.flamechart) && num_filtered != 1 { eyre::bail!( - "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to run the debugger.\n\n\ + "{num_filtered} tests matched your criteria, but exactly 1 test must match in order to {action}.\n\n\ Use --match-contract and --match-path to further limit the search.\n\ - Filter used:\n{filter}" + Filter used:\n{filter}", + action = if self.flamegraph {"generate a flamegraph"} else if self.flamechart {"generate a flamechart"} else {"run the debugger"}, ); } @@ -496,7 +557,11 @@ impl TestArgs { decoder.clear_addresses(); // We identify addresses if we're going to print *any* trace or gas report. - let identify_addresses = verbosity >= 3 || self.gas_report || self.debug.is_some(); + let identify_addresses = verbosity >= 3 || + self.gas_report || + self.debug.is_some() || + self.flamegraph || + self.flamechart; // Print suite header. println!(); diff --git a/crates/forge/bin/main.rs b/crates/forge/bin/main.rs index 81ee0b86775c..f92d3db8085b 100644 --- a/crates/forge/bin/main.rs +++ b/crates/forge/bin/main.rs @@ -81,7 +81,7 @@ fn main() -> Result<()> { Ok(()) } ForgeSubcommand::Clean { root } => { - let config = utils::load_config_with_root(root); + let config = utils::load_config_with_root(root.as_deref()); let project = config.project()?; config.cleanup(&project)?; Ok(()) diff --git a/crates/forge/src/lib.rs b/crates/forge/src/lib.rs index 6ad91ab6620c..6c6552d05347 100644 --- a/crates/forge/src/lib.rs +++ b/crates/forge/src/lib.rs @@ -115,6 +115,7 @@ impl TestOptions { .unwrap(); self.fuzzer_with_cases( fuzz_config.runs, + fuzz_config.max_test_rejects, Some(Box::new(FileFailurePersistence::Direct(failure_persist_path.leak()))), ) } @@ -128,7 +129,7 @@ impl TestOptions { /// - `test_fn` is the name of the test function declared inside the test contract. pub fn invariant_runner(&self, contract_id: &str, test_fn: &str) -> TestRunner { let invariant = self.invariant_config(contract_id, test_fn); - self.fuzzer_with_cases(invariant.runs, None) + self.fuzzer_with_cases(invariant.runs, invariant.max_assume_rejects, None) } /// Returns a "fuzz" configuration setup. Parameters are used to select tight scoped fuzz @@ -156,12 +157,13 @@ impl TestOptions { pub fn fuzzer_with_cases( &self, cases: u32, + max_global_rejects: u32, file_failure_persistence: Option>, ) -> TestRunner { let config = proptest::test_runner::Config { failure_persistence: file_failure_persistence, cases, - max_global_rejects: self.fuzz.max_test_rejects, + max_global_rejects, // Disable proptest shrink: for fuzz tests we provide single counterexample, // for invariant tests we shrink outside proptest. max_shrink_iters: 0, diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 145595905baf..802ad3884cc6 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -1,7 +1,5 @@ //! Forge test runner for multiple contracts. -#![allow(elided_named_lifetimes)] - use crate::{ progress::TestsProgress, result::SuiteResult, runner::LIBRARY_DEPLOYER, ContractRunner, TestFilter, TestOptions, @@ -86,28 +84,28 @@ pub struct MultiContractRunner { impl MultiContractRunner { /// Returns an iterator over all contracts that match the filter. - pub fn matching_contracts<'a>( + pub fn matching_contracts<'a: 'b, 'b>( &'a self, - filter: &'a dyn TestFilter, - ) -> impl Iterator { + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { self.contracts.iter().filter(|&(id, c)| matches_contract(id, &c.abi, filter)) } /// Returns an iterator over all test functions that match the filter. - pub fn matching_test_functions<'a>( + pub fn matching_test_functions<'a: 'b, 'b>( &'a self, - filter: &'a dyn TestFilter, - ) -> impl Iterator { + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { self.matching_contracts(filter) .flat_map(|(_, c)| c.abi.functions()) .filter(|func| is_matching_test(func, filter)) } /// Returns an iterator over all test functions in contracts that match the filter. - pub fn all_test_functions<'a>( + pub fn all_test_functions<'a: 'b, 'b>( &'a self, - filter: &'a dyn TestFilter, - ) -> impl Iterator { + filter: &'b dyn TestFilter, + ) -> impl Iterator + 'b { self.contracts .iter() .filter(|(id, _)| filter.matches_path(&id.source) && filter.matches_contract(&id.name)) diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index dad73bac26ad..b6e8860cc799 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -184,6 +184,18 @@ impl TestOutcome { // TODO: Avoid process::exit std::process::exit(1); } + + /// Removes first test result, if any. + pub fn remove_first(&mut self) -> Option<(String, String, TestResult)> { + self.results.iter_mut().find_map(|(suite_name, suite)| { + if let Some(test_name) = suite.test_results.keys().next().cloned() { + let result = suite.test_results.remove(&test_name).unwrap(); + Some((suite_name.clone(), test_name, result)) + } else { + None + } + }) + } } /// A set of test results for a single test suite, which is all the tests in a single contract. diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 1a8d2cb7e08c..4e860bc64900 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -7,7 +7,7 @@ use foundry_config::{ }; use foundry_test_utils::{ foundry_compilers::PathStyle, - rpc::next_etherscan_api_key, + rpc::next_mainnet_etherscan_api_key, util::{pretty_err, read_string, OutputExt, TestCommand}, }; use semver::Version; @@ -538,7 +538,7 @@ forgetest!(can_clone, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_etherscan_api_key().as_str(), + next_mainnet_etherscan_api_key().as_str(), "0x044b75f554b886A065b9567891e45c79542d7357", ]) .arg(prj.root()) @@ -567,7 +567,7 @@ forgetest!(can_clone_quiet, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_etherscan_api_key().as_str(), + next_mainnet_etherscan_api_key().as_str(), "--quiet", "0xDb53f47aC61FE54F456A4eb3E09832D08Dd7BEec", ]) @@ -585,7 +585,7 @@ forgetest!(can_clone_no_remappings_txt, |prj, cmd| { cmd.args([ "clone", "--etherscan-api-key", - next_etherscan_api_key().as_str(), + next_mainnet_etherscan_api_key().as_str(), "--no-remappings-txt", "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", ]) @@ -620,7 +620,7 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { .args([ "clone", "--etherscan-api-key", - next_etherscan_api_key().as_str(), + next_mainnet_etherscan_api_key().as_str(), "--keep-directory-structure", "0x33e690aEa97E4Ef25F0d140F1bf044d663091DAf", ]) diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 5f810d328b07..9e3103079877 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -196,7 +196,7 @@ forgetest_init!(can_override_config, |prj, cmd| { // remappings work let remappings_txt = prj.create_file("remappings.txt", "ds-test/=lib/forge-std/lib/ds-test/from-file/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); + let config = forge_utils::load_config_with_root(Some(prj.root())); assert_eq!( format!( "ds-test/={}/", @@ -207,7 +207,7 @@ forgetest_init!(can_override_config, |prj, cmd| { // env vars work std::env::set_var("DAPP_REMAPPINGS", "ds-test/=lib/forge-std/lib/ds-test/from-env/"); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); + let config = forge_utils::load_config_with_root(Some(prj.root())); assert_eq!( format!( "ds-test/={}/", @@ -284,7 +284,7 @@ Installing solmate in [..] (url: Some("https://github.com/transmissions11/solmat "remappings.txt", "solmate/=lib/solmate/src/\nsolmate-contracts/=lib/solmate/src/", ); - let config = forge_utils::load_config_with_root(Some(prj.root().into())); + let config = forge_utils::load_config_with_root(Some(prj.root())); // trailing slashes are removed on windows `to_slash_lossy` let path = prj.root().join("lib/solmate/src/").to_slash_lossy().into_owned(); #[cfg(windows)] diff --git a/crates/forge/tests/cli/soldeer.rs b/crates/forge/tests/cli/soldeer.rs index 21666edbb493..6c57f69f3084 100644 --- a/crates/forge/tests/cli/soldeer.rs +++ b/crates/forge/tests/cli/soldeer.rs @@ -7,7 +7,6 @@ use std::{ use foundry_test_utils::forgesoldeer; use std::io::Write; - forgesoldeer!(install_dependency, |prj, cmd| { let command = "install"; let dependency = "forge-std~1.8.1"; @@ -15,8 +14,18 @@ forgesoldeer!(install_dependency, |prj, cmd| { let foundry_file = prj.root().join("foundry.toml"); cmd.arg("soldeer").args([command, dependency]).assert_success().stdout_eq(str![[r#" -🦌 Running [..]oldeer install 🦌 -... +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started HTTP download of forge-std~1.8.1 +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +The dependency forge-std~1.8.1 was unzipped! +Writing forge-std~1.8.1 to the lock file. +Added forge-std~1.8.1 to remappings + "#]]); // Making sure the path was created to the dependency and that foundry.toml exists @@ -27,12 +36,17 @@ forgesoldeer!(install_dependency, |prj, cmd| { // Making sure the lock contents are the right ones let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" + // checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" + // integrity = "6a52f0c34d935e508af46a6d12a3a741798252f20a66f6bbee86c23dd6ef7c8d" + // "#; let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); assert!(actual_lock_contents.contains("forge-std")); - assert!(actual_lock_contents - .contains("0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0")); - assert!(actual_lock_contents.contains("1.8.1")); // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] @@ -56,9 +70,36 @@ forgesoldeer!(install_dependency_git, |prj, cmd| { let foundry_file = prj.root().join("foundry.toml"); - cmd.arg("soldeer").args([command, dependency, git]).assert_success().stdout_eq(str![[r#" -🦌 Running [..]oldeer install 🦌 -... + cmd.arg("soldeer") + .args([command, dependency, git]) + .assert_success() + .stdout_eq(str![[r#" +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started GIT download of forge-std~1.8.1 +Successfully downloaded forge-std~1.8.1 the dependency via git +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +Writing forge-std~1.8.1 to the lock file. +Added forge-std~1.8.1 to remappings + +"#]]) + .stdout_eq(str![[r#" +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started GIT download of forge-std~1.8.1 +Successfully downloaded forge-std~1.8.1 the dependency via git +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +Writing forge-std~1.8.1 to the lock file. +Added forge-std~1.8.1 to remappings + "#]]); // Making sure the path was created to the dependency and that README.md exists @@ -68,11 +109,16 @@ forgesoldeer!(install_dependency_git, |prj, cmd| { // Making sure the lock contents are the right ones let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://gitlab.com/mario4582928/Mario.git" + // checksum = "22868f426bd4dd0e682b5ec5f9bd55507664240c" + // "#; let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); assert!(actual_lock_contents.contains("forge-std")); - assert!(actual_lock_contents.contains("22868f426bd4dd0e682b5ec5f9bd55507664240c")); - assert!(actual_lock_contents.contains("1.8.1")); // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] @@ -102,8 +148,18 @@ forgesoldeer!(install_dependency_git_commit, |prj, cmd| { .args([command, dependency, git, rev_flag, commit]) .assert_success() .stdout_eq(str![[r#" -🦌 Running [..]oldeer install 🦌 -... +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started GIT download of forge-std~1.8.1 +Successfully downloaded forge-std~1.8.1 the dependency via git +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +Writing forge-std~1.8.1 to the lock file. +Added forge-std~1.8.1 to remappings + "#]]); // Making sure the path was created to the dependency and that README.md exists @@ -114,12 +170,16 @@ forgesoldeer!(install_dependency_git_commit, |prj, cmd| { // Making sure the lock contents are the right ones let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://gitlab.com/mario4582928/Mario.git" + // checksum = "7a0663eaf7488732f39550be655bad6694974cb3" + // "#; let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); assert!(actual_lock_contents.contains("forge-std")); - assert!(actual_lock_contents.contains("7a0663eaf7488732f39550be655bad6694974cb3")); - assert!(actual_lock_contents.contains("https://gitlab.com/mario4582928/Mario.git")); - assert!(actual_lock_contents.contains("1.8.1")); // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] @@ -157,20 +217,10 @@ mario-custom-branch = { version = "1.0", git = "https://gitlab.com/mario4582928/ eprintln!("Couldn't write to file: {e}"); } - cmd.arg("soldeer").arg(command).assert_success().stdout_eq(str![[r#" -🦌 Running [..]oldeer update 🦌 -... - -"#]]); + cmd.arg("soldeer").arg(command).assert_success(); // Making sure the path was created to the dependency and that foundry.toml exists // meaning that the dependencies were installed correctly - let path_dep_forge = - prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); - assert!(path_dep_forge.exists()); - - // Making sure the lock contents are the right ones - let path_lock_file = prj.root().join("soldeer.lock"); let dep1 = prj.root().join("dependencies").join("@tt-1.6.1"); let dep2 = prj.root().join("dependencies").join("forge-std-1.8.1"); let dep3 = prj.root().join("dependencies").join("mario-1.0"); @@ -178,13 +228,58 @@ mario-custom-branch = { version = "1.0", git = "https://gitlab.com/mario4582928/ let dep5 = prj.root().join("dependencies").join("mario-custom-tag-1.0"); let dep6 = prj.root().join("dependencies").join("mario-custom-branch-1.0"); + assert!(dep1.exists()); + assert!(dep2.exists()); + assert!(dep3.exists()); + assert!(dep4.exists()); + assert!(dep5.exists()); + assert!(dep6.exists()); + + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "@tt" + // version = "1.6.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/@openzeppelin-contracts/3_3_0-rc_2_22-01-2024_13:12:57_contracts.zip" + // checksum = "3aa5b07e796ce2ae54bbab3a5280912444ae75807136a513fa19ff3a314c323f" + // integrity = "24e7847580674bd0a4abf222b82fac637055141704c75a3d679f637acdcfe817" + + // [[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" + // checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" + // integrity = "6a52f0c34d935e508af46a6d12a3a741798252f20a66f6bbee86c23dd6ef7c8d" + + // [[dependencies]] + // name = "mario" + // version = "1.0" + // source = "https://gitlab.com/mario4582928/Mario.git" + // checksum = "22868f426bd4dd0e682b5ec5f9bd55507664240c" + + // [[dependencies]] + // name = "mario-custom-branch" + // version = "1.0" + // source = "https://gitlab.com/mario4582928/Mario.git" + // checksum = "84c3b38dba44a4c29ec44f45a31e1e59d36aa77b" + + // [[dependencies]] + // name = "mario-custom-tag" + // version = "1.0" + // source = "https://gitlab.com/mario4582928/Mario.git" + // checksum = "a366c4b560022d12e668d6c1756c6382e2352d0f" + + // [[dependencies]] + // name = "solmate" + // version = "6.7.0" + // source = "https://soldeer-revisions.s3.amazonaws.com/solmate/6_7_0_22-01-2024_13:21:00_solmate.zip" + // checksum = "dd0f08cdaaaad1de0ac45993d4959351ba89c2d9325a0b5df5570357064f2c33" + // integrity = "ec330877af853f9d34b2b1bf692fb33c9f56450625f5c4abdcf0d3405839730e" + // "#; + + // assert_data_eq!(lock_contents, read_file_to_string(&path_lock_file)); let actual_lock_contents = read_file_to_string(&path_lock_file); - assert!(actual_lock_contents.contains("@tt")); assert!(actual_lock_contents.contains("forge-std")); - assert!(actual_lock_contents.contains("mario")); - assert!(actual_lock_contents.contains("solmate")); - assert!(actual_lock_contents.contains("mario-custom-tag")); - assert!(actual_lock_contents.contains("mario-custom-branch")); // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] @@ -204,12 +299,6 @@ mario-custom-branch = { version = "1.0", git = "https://gitlab.com/mario4582928/ "#; assert_data_eq!(read_file_to_string(&foundry_file), foundry_contents); - assert!(dep1.exists()); - assert!(dep2.exists()); - assert!(dep3.exists()); - assert!(dep4.exists()); - assert!(dep5.exists()); - assert!(dep6.exists()); }); forgesoldeer!(update_dependencies_simple_version, |prj, cmd| { @@ -230,8 +319,11 @@ forge-std = "1.8.1" } cmd.arg("soldeer").arg(command).assert_success().stdout_eq(str![[r#" -🦌 Running [..]oldeer update 🦌 -... +🦌 Running Soldeer update 🦌 +Started HTTP download of forge-std~1.8.1 +Dependency forge-std~1.8.1 downloaded! +The dependency forge-std~1.8.1 was unzipped! +Writing forge-std~1.8.1 to the lock file. "#]]); @@ -243,12 +335,17 @@ forge-std = "1.8.1" // Making sure the lock contents are the right ones let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" + // checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" + // integrity = "6a52f0c34d935e508af46a6d12a3a741798252f20a66f6bbee86c23dd6ef7c8d" + // "#; let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); assert!(actual_lock_contents.contains("forge-std")); - assert!(actual_lock_contents - .contains("0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0")); - assert!(actual_lock_contents.contains("1.8.1")); // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] @@ -268,20 +365,11 @@ forge-std = "1.8.1" forgesoldeer!(login, |prj, cmd| { let command = "login"; - cmd.arg("soldeer") - .arg(command) - .assert_failure() - .stderr_eq(str![[r#" -Error: -Failed to run [..] + let output = cmd.arg("soldeer").arg(command).execute(); -"#]]) - .stdout_eq(str![[r#" -🦌 Running [..]oldeer login 🦌 -... -ℹ️ If you do not have an account, please go to soldeer.xyz to create one. -📧 Please enter your email: -"#]]); + // On login, we can only check if the prompt is displayed in the stdout + let stdout = String::from_utf8(output.stdout).expect("Could not parse the output"); + assert!(stdout.contains("Please enter your email")); }); forgesoldeer!(install_dependency_with_remappings_config, |prj, cmd| { @@ -301,8 +389,20 @@ remappings_regenerate = true eprintln!("Couldn't write to file: {e}"); } - cmd.arg("soldeer").args([command, dependency]); - cmd.execute(); + cmd.arg("soldeer").args([command, dependency]).assert_success().stdout_eq(str![[r#" +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started HTTP download of forge-std~1.8.1 +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +The dependency forge-std~1.8.1 was unzipped! +Writing forge-std~1.8.1 to the lock file. +Added all dependencies to remapppings + +"#]]); // Making sure the path was created to the dependency and that foundry.toml exists // meaning that the dependencies were installed correctly @@ -310,6 +410,20 @@ remappings_regenerate = true prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); assert!(path_dep_forge.exists()); + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" + // checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" + // integrity = "6a52f0c34d935e508af46a6d12a3a741798252f20a66f6bbee86c23dd6ef7c8d" + // "#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); + assert!(actual_lock_contents.contains("forge-std")); + // Making sure the foundry contents are the right ones let foundry_contents = r#"[profile.default] src = "src" @@ -349,8 +463,20 @@ remappings_regenerate = true eprintln!("Couldn't write to file: {e}"); } - cmd.arg("soldeer").args([command, dependency]); - cmd.execute(); + cmd.arg("soldeer").args([command, dependency]).assert_success().stdout_eq(str![[r#" +🦌 Running Soldeer install 🦌 +No config file found. If you wish to proceed, please select how you want Soldeer to be configured: +1. Using foundry.toml +2. Using soldeer.toml +(Press 1 or 2), default is foundry.toml +Started HTTP download of forge-std~1.8.1 +Dependency forge-std~1.8.1 downloaded! +Adding dependency forge-std-1.8.1 to the config file +The dependency forge-std~1.8.1 was unzipped! +Writing forge-std~1.8.1 to the lock file. +Added all dependencies to remapppings + +"#]]); // Making sure the path was created to the dependency and that foundry.toml exists // meaning that the dependencies were installed correctly @@ -358,11 +484,24 @@ remappings_regenerate = true prj.root().join("dependencies").join("forge-std-1.8.1").join("foundry.toml"); assert!(path_dep_forge.exists()); + // Making sure the lock contents are the right ones + let path_lock_file = prj.root().join("soldeer.lock"); + // let lock_contents = r#"[[dependencies]] + // name = "forge-std" + // version = "1.8.1" + // source = "https://soldeer-revisions.s3.amazonaws.com/forge-std/v1_8_1_23-03-2024_00:05:44_forge-std-v1.8.1.zip" + // checksum = "0f7cd44f5670c31a9646d4031e70c66321cd3ed6ebac3c7278e4e57e4e5c5bd0" + // integrity = "6a52f0c34d935e508af46a6d12a3a741798252f20a66f6bbee86c23dd6ef7c8d" + // "#; + + let actual_lock_contents = read_file_to_string(&path_lock_file); + // assert_data_eq!(lock_contents, actual_lock_contents); + assert!(actual_lock_contents.contains("forge-std")); + // Making sure the foundry contents are the right ones - let remappings_content = "@custom-f@forge-std-1.8.1/=dependencies/forge-std-1.8.1/\n"; + let remappings_content = r#"@custom-f@forge-std-1.8.1/=dependencies/forge-std-1.8.1/ +"#; let remappings_file = prj.root().join("remappings.txt"); - println!("ddd {:?}", read_file_to_string(&remappings_file)); - assert_data_eq!(read_file_to_string(&remappings_file), remappings_content); }); diff --git a/crates/forge/tests/cli/svm.rs b/crates/forge/tests/cli/svm.rs index ab8db41f8a8b..e0c10a052e93 100644 --- a/crates/forge/tests/cli/svm.rs +++ b/crates/forge/tests/cli/svm.rs @@ -11,7 +11,7 @@ use svm::Platform; /// 3. svm bumped in foundry-compilers /// 4. foundry-compilers update with any breaking changes /// 5. upgrade the `LATEST_SOLC` -const LATEST_SOLC: Version = Version::new(0, 8, 26); +const LATEST_SOLC: Version = Version::new(0, 8, 27); macro_rules! ensure_svm_releases { ($($test:ident => $platform:ident),* $(,)?) => {$( diff --git a/crates/forge/tests/cli/test_cmd.rs b/crates/forge/tests/cli/test_cmd.rs index 23ffa890d603..3e3335f0c23f 100644 --- a/crates/forge/tests/cli/test_cmd.rs +++ b/crates/forge/tests/cli/test_cmd.rs @@ -1834,3 +1834,88 @@ contract CounterTest is DSTest { ... "#]]); }); + +forgetest_init!(test_assume_no_revert, |prj, cmd| { + prj.wipe_contracts(); + prj.insert_ds_test(); + prj.insert_vm(); + prj.clear(); + + let config = Config { + fuzz: { FuzzConfig { runs: 100, seed: Some(U256::from(100)), ..Default::default() } }, + ..Default::default() + }; + prj.write_config(config); + + prj.add_source( + "Counter.t.sol", + r#"pragma solidity 0.8.24; +import {Vm} from "./Vm.sol"; +import {DSTest} from "./test.sol"; +contract CounterWithRevert { + error CountError(); + error CheckError(); + + function count(uint256 a) public pure returns (uint256) { + if (a > 1000 || a < 10) { + revert CountError(); + } + return 99999999; + } + function check(uint256 a) public pure { + if (a == 99999999) { + revert CheckError(); + } + } + function dummy() public pure {} +} + +contract CounterRevertTest is DSTest { + Vm vm = Vm(HEVM_ADDRESS); + + function test_assume_no_revert_pass(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + assertEq(a, 99999999); + } + function test_assume_no_revert_fail_assert(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test should fail on next assertion. + assertEq(a, 1); + } + function test_assume_no_revert_fail_in_2nd_call(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test should revert here (not in scope of `assumeNoRevert` cheatcode). + counter.check(a); + assertEq(a, 99999999); + } + function test_assume_no_revert_fail_in_3rd_call(uint256 a) public { + CounterWithRevert counter = new CounterWithRevert(); + vm.assumeNoRevert(); + a = counter.count(a); + // Test `assumeNoRevert` applied to non reverting call should not be available for next reverting call. + vm.assumeNoRevert(); + counter.dummy(); + // Test will revert here (not in scope of `assumeNoRevert` cheatcode). + counter.check(a); + assertEq(a, 99999999); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).with_no_redact().assert_failure().stdout_eq(str![[r#" +... +[FAIL. Reason: assertion failed; counterexample: [..]] test_assume_no_revert_fail_assert(uint256) [..] +[FAIL. Reason: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_2nd_call(uint256) [..] +[FAIL. Reason: CheckError(); counterexample: [..]] test_assume_no_revert_fail_in_3rd_call(uint256) [..] +[PASS] test_assume_no_revert_pass(uint256) [..] +... +"#]]); +}); diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 2b28570e75b3..7e615b5659dd 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -2,7 +2,7 @@ use foundry_compilers::artifacts::{BytecodeHash, EvmVersion}; use foundry_config::Config; use foundry_test_utils::{ forgetest_async, - rpc::{next_etherscan_api_key, next_http_archive_rpc_endpoint}, + rpc::{next_http_archive_rpc_endpoint, next_mainnet_etherscan_api_key}, util::OutputExt, TestCommand, TestProject, }; @@ -19,7 +19,7 @@ fn test_verify_bytecode( verifier_url: &str, expected_matches: (&str, &str), ) { - let etherscan_key = next_etherscan_api_key(); + let etherscan_key = next_mainnet_etherscan_api_key(); let rpc_url = next_http_archive_rpc_endpoint(); // fetch and flatten source code @@ -73,7 +73,7 @@ fn test_verify_bytecode_with_ignore( ignore: &str, chain: &str, ) { - let etherscan_key = next_etherscan_api_key(); + let etherscan_key = next_mainnet_etherscan_api_key(); let rpc_url = next_http_archive_rpc_endpoint(); // fetch and flatten source code diff --git a/crates/forge/tests/it/fuzz.rs b/crates/forge/tests/it/fuzz.rs index f1c5edaaa58d..b0bd57a051c2 100644 --- a/crates/forge/tests/it/fuzz.rs +++ b/crates/forge/tests/it/fuzz.rs @@ -7,7 +7,7 @@ use forge::{ fuzz::CounterExample, result::{SuiteResult, TestStatus}, }; -use foundry_test_utils::Filter; +use foundry_test_utils::{forgetest_init, str, Filter}; use std::collections::BTreeMap; #[tokio::test(flavor = "multi_thread")] @@ -176,3 +176,29 @@ async fn test_scrape_bytecode() { } } } + +// tests that inline max-test-rejects config is properly applied +forgetest_init!(test_inline_max_test_rejects, |prj, cmd| { + prj.wipe_contracts(); + + prj.add_test( + "Contract.t.sol", + r#" +import {Test} from "forge-std/Test.sol"; + +contract InlineMaxRejectsTest is Test { + /// forge-config: default.fuzz.max-test-rejects = 1 + function test_fuzz_bound(uint256 a) public { + vm.assume(a == 0); + } +} + "#, + ) + .unwrap(); + + cmd.args(["test"]).assert_failure().stdout_eq(str![[r#" +... +[FAIL. Reason: The `vm.assume` cheatcode rejected too many inputs (1 allowed)] test_fuzz_bound(uint256) (runs: 0, [AVG_GAS]) +... +"#]]); +}); diff --git a/crates/forge/tests/it/repros.rs b/crates/forge/tests/it/repros.rs index 3cce4bed203e..73310f011760 100644 --- a/crates/forge/tests/it/repros.rs +++ b/crates/forge/tests/it/repros.rs @@ -378,3 +378,6 @@ test_repro!(8383, false, None, |res| { // https://github.com/foundry-rs/foundry/issues/1543 test_repro!(1543); + +// https://github.com/foundry-rs/foundry/issues/6643 +test_repro!(6643); diff --git a/crates/test-utils/Cargo.toml b/crates/test-utils/Cargo.toml index ca0f60d0d64c..6e5e900ce33c 100644 --- a/crates/test-utils/Cargo.toml +++ b/crates/test-utils/Cargo.toml @@ -32,6 +32,10 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] } rand.workspace = true snapbox = { version = "0.6.9", features = ["json", "regex"] } +[dev-dependencies] +tokio.workspace = true +foundry-block-explorers.workspace = true + [features] # feature for integration tests that test external projects external-integration-tests = [] diff --git a/crates/test-utils/src/rpc.rs b/crates/test-utils/src/rpc.rs index 96415af3fb73..305c8a1c87bd 100644 --- a/crates/test-utils/src/rpc.rs +++ b/crates/test-utils/src/rpc.rs @@ -51,9 +51,7 @@ static ALCHEMY_KEYS: LazyLock> = LazyLock::new(|| { "UVatYU2Ax0rX6bDiqddeTRDdcCxzdpoE", "bVjX9v-FpmUhf5R_oHIgwJx2kXvYPRbx", ]; - keys.shuffle(&mut rand::thread_rng()); - keys }); @@ -69,19 +67,18 @@ static ETHERSCAN_MAINNET_KEYS: LazyLock> = LazyLock::new(|| { "C7I2G4JTA5EPYS42Z8IZFEIMQNI5GXIJEV", "A15KZUMZXXCK1P25Y1VP1WGIVBBHIZDS74", "3IA6ASNQXN8WKN7PNFX7T72S9YG56X9FPG", + "ZUB97R31KSYX7NYVW6224Q6EYY6U56H591", + // Optimism + // "JQNGFHINKS1W7Y5FRXU4SPBYF43J3NYK46", ]; - keys.shuffle(&mut rand::thread_rng()); - keys }); -/// counts how many times a rpc endpoint was requested for _mainnet_ -static NEXT_RPC_ENDPOINT: AtomicUsize = AtomicUsize::new(0); - -// returns the current value of the atomic counter and increments it +/// Returns the next index to use. fn next() -> usize { - NEXT_RPC_ENDPOINT.fetch_add(1, Ordering::SeqCst) + static NEXT_INDEX: AtomicUsize = AtomicUsize::new(0); + NEXT_INDEX.fetch_add(1, Ordering::SeqCst) } fn num_keys() -> usize { @@ -125,7 +122,7 @@ pub fn next_ws_archive_rpc_endpoint() -> String { } /// Returns the next etherscan api key -pub fn next_etherscan_api_key() -> String { +pub fn next_mainnet_etherscan_api_key() -> String { let idx = next() % ETHERSCAN_MAINNET_KEYS.len(); ETHERSCAN_MAINNET_KEYS[idx].to_string() } @@ -178,15 +175,48 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String { #[cfg(test)] mod tests { use super::*; - use std::collections::HashSet; - - #[test] - #[ignore] - fn can_rotate_unique() { - let mut keys = HashSet::new(); - for _ in 0..100 { - keys.insert(next_http_rpc_endpoint()); + use alloy_primitives::address; + use foundry_config::Chain; + + #[tokio::test] + #[ignore = "run manually"] + async fn test_etherscan_keys() { + let address = address!("dAC17F958D2ee523a2206206994597C13D831ec7"); + let mut first_abi = None; + let mut failed = Vec::new(); + for (i, &key) in ETHERSCAN_MAINNET_KEYS.iter().enumerate() { + eprintln!("trying key {i} ({key})"); + + let client = foundry_block_explorers::Client::builder() + .chain(Chain::mainnet()) + .unwrap() + .with_api_key(key) + .build() + .unwrap(); + + let mut fail = |e: &str| { + eprintln!("key {i} ({key}) failed: {e}"); + failed.push(key); + }; + + let abi = match client.contract_abi(address).await { + Ok(abi) => abi, + Err(e) => { + fail(&e.to_string()); + continue; + } + }; + + if let Some(first_abi) = &first_abi { + if abi != *first_abi { + fail("abi mismatch"); + } + } else { + first_abi = Some(abi); + } + } + if !failed.is_empty() { + panic!("failed keys: {failed:#?}"); } - assert_eq!(keys.len(), num_keys()); } } diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 8fffd84d6a65..e08cf3a5a028 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -459,7 +459,7 @@ impl VerifyBytecodeArgs { transaction.input = Bytes::from(local_bytecode_vec); } - configure_tx_env(&mut env, &transaction); + configure_tx_env(&mut env, &transaction.inner); let fork_address = crate::utils::deploy_contract( &mut executor, diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 1b7c7fadacad..aaf8a0e016b7 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -2,7 +2,7 @@ use crate::{bytecode::VerifyBytecodeArgs, types::VerificationType}; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{Address, Bytes, U256}; use alloy_provider::Provider; -use alloy_rpc_types::{Block, BlockId, Transaction}; +use alloy_rpc_types::{AnyNetworkBlock, BlockId, Transaction}; use clap::ValueEnum; use eyre::{OptionExt, Result}; use foundry_block_explorers::{ @@ -346,7 +346,7 @@ pub async fn get_tracing_executor( Ok((env, executor)) } -pub fn configure_env_block(env: &mut Env, block: &Block) { +pub fn configure_env_block(env: &mut Env, block: &AnyNetworkBlock) { env.block.timestamp = U256::from(block.header.timestamp); env.block.coinbase = block.header.miner; env.block.difficulty = block.header.difficulty; diff --git a/deny.toml b/deny.toml index 18fcd55652da..e908828cc2a0 100644 --- a/deny.toml +++ b/deny.toml @@ -8,6 +8,8 @@ ignore = [ # https://github.com/watchexec/watchexec/issues/852 "RUSTSEC-2024-0350", "RUSTSEC-2024-0351", + # proc-macro-error is unmaintained + "RUSTSEC-2024-0370", ] # This section is considered when running `cargo deny check bans`. @@ -50,6 +52,7 @@ allow = [ "BSL-1.0", "0BSD", "MPL-2.0", + "CDDL-1.0", ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index e76678b22b4b..d3a14f7d6856 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -144,6 +144,7 @@ interface Vm { function assertTrue(bool condition) external pure; function assertTrue(bool condition, string calldata error) external pure; function assume(bool condition) external pure; + function assumeNoRevert() external pure; function blobBaseFee(uint256 newBlobBaseFee) external; function blobhashes(bytes32[] calldata hashes) external; function breakpoint(string calldata char) external; diff --git a/testdata/default/repros/Issue6643.t.sol b/testdata/default/repros/Issue6643.t.sol new file mode 100644 index 000000000000..36b684c133dc --- /dev/null +++ b/testdata/default/repros/Issue6643.t.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract Counter { + event TestEvent(uint256 n); + event AnotherTestEvent(uint256 n); + + constructor() { + emit TestEvent(1); + } + + function f() external { + emit TestEvent(2); + } + + function g() external { + emit AnotherTestEvent(1); + this.f(); + emit AnotherTestEvent(2); + } +} + +// https://github.com/foundry-rs/foundry/issues/6643 +contract Issue6643Test is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + Counter public counter; + + event TestEvent(uint256 n); + event AnotherTestEvent(uint256 n); + + function setUp() public { + counter = new Counter(); + } + + function test_Bug1() public { + // part1 + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + // part2 + vm.expectEmit(); + emit TestEvent(2); + counter.f(); + // part3 + vm.expectEmit(); + emit AnotherTestEvent(1); + vm.expectEmit(); + emit TestEvent(2); + vm.expectEmit(); + emit AnotherTestEvent(2); + counter.g(); + } + + function test_Bug2() public { + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + vm.expectEmit(); + emit TestEvent(1); + new Counter(); + } +} From 227834aae6dbb19f9c83c2c5e6ff9b03840e2359 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 10 Sep 2024 13:10:11 +0000 Subject: [PATCH 48/64] assert accurate gas measurement --- testdata/default/cheats/GasSnapshot.t.sol | 56 +++++++++++++++++++++-- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 28006231a6ff..4ddfdd5feb3c 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,6 +7,24 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); + function testAssertAccurateGasMeasurement() public { + Flare f = new Flare(); + + vm.startSnapshotGas("caseA"); + + f.update(2); + + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + + vm.startSnapshotGas("caseB"); + + f.update(3); + + gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + // Writes to `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup1() public { uint256 a = 123; @@ -86,7 +104,10 @@ contract GasSnapshotTest is DSTest { f.run(256); - uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + uint256 gasUsed = vm.stopSnapshotGas( + "CustomGroup", + "testSnapshotGasSectionGroupName" + ); assertGt(gasUsed, 0); } @@ -110,11 +131,40 @@ contract GasSnapshotTest is DSTest { } contract Flare { + TargetA public target; bytes32[] public data; - function run(uint256 n) public { - for (uint256 i = 0; i < n; i++) { + 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_; + } } From ad1dfdc457bca3d34c681fe65229f9d8eb4117cb Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 10 Sep 2024 13:19:57 +0000 Subject: [PATCH 49/64] clean up --- testdata/default/cheats/GasSnapshot.t.sol | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 4ddfdd5feb3c..cb0866397d1d 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -10,19 +10,12 @@ contract GasSnapshotTest is DSTest { function testAssertAccurateGasMeasurement() public { Flare f = new Flare(); - vm.startSnapshotGas("caseA"); + vm.startSnapshotGas("testAssertAccurateGasMeasurement"); f.update(2); uint256 gasUsed = vm.stopSnapshotGas(); assertGt(gasUsed, 0); - - vm.startSnapshotGas("caseB"); - - f.update(3); - - gasUsed = vm.stopSnapshotGas(); - assertGt(gasUsed, 0); } // Writes to `GasSnapshotTest` group with custom names. @@ -104,10 +97,7 @@ contract GasSnapshotTest is DSTest { f.run(256); - uint256 gasUsed = vm.stopSnapshotGas( - "CustomGroup", - "testSnapshotGasSectionGroupName" - ); + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); } From d902a0dec1ae37bdf36e38d2d978133f3ab1f78b Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 10 Sep 2024 14:52:32 +0000 Subject: [PATCH 50/64] only measure top-level calls, may be naive --- crates/cheatcodes/src/inspector.rs | 5 ++++- testdata/default/cheats/GasSnapshot.t.sol | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index f9e0757a02b2..565eca8b7f8f 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1202,7 +1202,10 @@ impl Inspector for Cheatcodes { // Store the total gas used for all active gas records started by `startSnapshotGas`. self.gas_metering.gas_records.iter_mut().for_each(|record| { - record.gas_used = record.gas_used.saturating_add(gas.spent()); + // Only record top-level gas usage, excluding root call. + if ecx.journaled_state.depth() == 1 { + record.gas_used = record.gas_used.saturating_add(gas.spent()); + } }); // If `startStateDiffRecording` has been called, update the `reverted` status of the diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index cb0866397d1d..c8d7603ea4a4 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -7,7 +7,7 @@ import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - function testAssertAccurateGasMeasurement() public { + function testAssertGasMeasurement() public { Flare f = new Flare(); vm.startSnapshotGas("testAssertAccurateGasMeasurement"); From ebc4efd3f2bd46bac86f19e5bf3493011b15adc4 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Tue, 10 Sep 2024 16:30:50 +0000 Subject: [PATCH 51/64] record internal calls --- crates/cheatcodes/src/evm.rs | 4 +++ crates/cheatcodes/src/inspector.rs | 44 ++++++++++++++++++----- testdata/default/cheats/GasSnapshot.t.sol | 35 +++++++++++++++--- testdata/foundry.toml | 2 +- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 14f3721a210d..4fe2b68e4766 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -806,6 +806,8 @@ fn inner_start_gas_snapshot( ccx.state.gas_metering.last_snapshot_name = Some(name); + ccx.state.gas_metering.start(); + Ok(Default::default()) } @@ -834,6 +836,8 @@ fn inner_stop_gas_snapshot( .or_default() .insert(name.clone(), value.to_string()); + ccx.state.gas_metering.stop(); + Ok(value.abi_encode()) } else { bail!("no gas snapshot was started with the name: {name} in group: {group}"); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 565eca8b7f8f..747ebfec948b 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -227,7 +227,7 @@ 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 name of the last snapshot taken. @@ -237,11 +237,35 @@ pub struct GasMetering { /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option, + /// True if gas metering is enabled. + pub recording: bool, + /// Stores recorded gas frames. + pub recorded_frames: Vec, /// Gas records for the active snapshots. pub gas_records: Vec, } impl GasMetering { + /// Start the gas recording. + pub fn start(&mut self) { + self.recording = true; + self.recorded_frames.clear(); + } + + /// Stop the gas recording. + pub fn stop(&mut self) { + // self.gas_records.iter_mut().for_each(|record| { + // record.gas_used = record + // .gas_used + // .saturating_add(self.recorded_frames.iter().map(|gas| gas.spent()).sum::()); + // }); + + println!("Gas frames: {:?}", self.recorded_frames); + + self.recording = false; + self.recorded_frames.clear(); + } + /// Resume paused gas metering. pub fn resume(&mut self) { if self.paused { @@ -1057,6 +1081,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); + } + if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -1200,14 +1228,6 @@ impl Inspector for Cheatcodes { 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| { - // Only record top-level gas usage, excluding root call. - if ecx.journaled_state.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 { @@ -1455,6 +1475,12 @@ impl Cheatcodes { } } + #[cold] + fn meter_gas_record(&mut self, interpreter: &mut Interpreter) { + // Record gas for current frame. + self.gas_metering.recorded_frames.push(interpreter.gas.spent()); + } + #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index c8d7603ea4a4..ba907e8429b4 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -5,17 +5,44 @@ import "ds-test/test.sol"; import "cheats/Vm.sol"; contract GasSnapshotTest is DSTest { + uint256 public slot0; + Vm constant vm = Vm(HEVM_ADDRESS); - function testAssertGasMeasurement() public { + function testGasExternal() public { Flare f = new Flare(); - vm.startSnapshotGas("testAssertAccurateGasMeasurement"); + vm.startSnapshotGas("testAssertGasExternal"); f.update(2); - uint256 gasUsed = vm.stopSnapshotGas(); - assertGt(gasUsed, 0); + vm.stopSnapshotGas(); + } + + function testGasInternal() public { + vm.startSnapshotGas("testAssertGasInternalA"); + + slot0 = 1; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalB"); + + slot0 = 2; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalC"); + + slot0 = 0; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalD"); + + slot0 = 1; + + vm.stopSnapshotGas(); } // Writes to `GasSnapshotTest` group with custom names. 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 From e966de12ad7a3d621bcd8c940550fffc5648d968 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 16 Sep 2024 15:58:45 +0000 Subject: [PATCH 52/64] experiment with tracking --- crates/cheatcodes/src/inspector.rs | 48 +++++++++++++++++------ testdata/default/cheats/GasSnapshot.t.sol | 15 ++++++- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 747ebfec948b..979d9c6649ce 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -23,7 +23,7 @@ use crate::{ use alloy_primitives::{hex, Address, Bytes, Log, TxKind, B256, U256}; use alloy_rpc_types::request::{TransactionInput, TransactionRequest}; use alloy_sol_types::{SolCall, SolInterface, SolValue}; -use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; +use foundry_common::{evm::Breakpoints, shell::println, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_config::Config; use foundry_evm_core::{ abi::Vm::stopExpectSafeMemoryCall, @@ -239,6 +239,10 @@ pub struct GasMetering { /// True if gas metering is enabled. pub recording: bool, + /// Stores the last frame. + pub last_recorded_frame: u64, + /// Stores the last depth. + pub last_recorded_depth: u64, /// Stores recorded gas frames. pub recorded_frames: Vec, /// Gas records for the active snapshots. @@ -254,14 +258,13 @@ impl GasMetering { /// Stop the gas recording. pub fn stop(&mut self) { - // self.gas_records.iter_mut().for_each(|record| { - // record.gas_used = record - // .gas_used - // .saturating_add(self.recorded_frames.iter().map(|gas| gas.spent()).sum::()); - // }); + self.gas_records.iter_mut().for_each(|record| { + record.gas_used = self.recorded_frames.iter().sum::(); + }); - println!("Gas frames: {:?}", self.recorded_frames); + println!("gas records: {:?}", self.gas_records); + // reduce sum of gas frames to a single value self.recording = false; self.recorded_frames.clear(); } @@ -1080,9 +1083,9 @@ impl Inspector for Cheatcodes { } #[inline] - fn step_end(&mut self, interpreter: &mut Interpreter, _ecx: &mut EvmContext) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext) { if self.gas_metering.recording { - self.meter_gas_record(interpreter); + self.meter_gas_record(interpreter, ecx); } if self.gas_metering.paused { @@ -1476,9 +1479,30 @@ impl Cheatcodes { } #[cold] - fn meter_gas_record(&mut self, interpreter: &mut Interpreter) { - // Record gas for current frame. - self.gas_metering.recorded_frames.push(interpreter.gas.spent()); + fn meter_gas_record( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext, + ) { + println!( + "{:?} - {:?} @ {}", + interpreter.instruction_result, + interpreter.gas.spent(), + ecx.journaled_state.depth() + ); + + if self.gas_metering.last_recorded_depth != ecx.journaled_state.depth() { + self.gas_metering.last_recorded_frame = 0; + self.gas_metering.last_recorded_depth = ecx.journaled_state.depth(); + } + + if matches!(interpreter.instruction_result, InstructionResult::Continue) { + self.gas_metering + .recorded_frames + .push(interpreter.gas.spent() - self.gas_metering.last_recorded_frame); + + self.gas_metering.last_recorded_frame = interpreter.gas.spent(); + } } #[cold] diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index ba907e8429b4..e3f47c24c04a 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -45,6 +45,16 @@ contract GasSnapshotTest is DSTest { vm.stopSnapshotGas(); } + function testGasComplex() public { + TargetB target = new TargetB(); + + vm.startSnapshotGas("testAssertGasComplexA"); + + target.update(1); + + vm.stopSnapshotGas(); + } + // Writes to `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup1() public { uint256 a = 123; @@ -124,7 +134,10 @@ contract GasSnapshotTest is DSTest { f.run(256); - uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + uint256 gasUsed = vm.stopSnapshotGas( + "CustomGroup", + "testSnapshotGasSectionGroupName" + ); assertGt(gasUsed, 0); } From a38f087764992a4ede0a9c321538e99ad52e7289 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 20 Sep 2024 15:12:35 +0200 Subject: [PATCH 53/64] fix formatter --- testdata/default/cheats/GasSnapshot.t.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index e3f47c24c04a..eca27002a554 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -134,10 +134,7 @@ contract GasSnapshotTest is DSTest { f.run(256); - uint256 gasUsed = vm.stopSnapshotGas( - "CustomGroup", - "testSnapshotGasSectionGroupName" - ); + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); } From 3b234ed0ec6979ad672fa082446c45cf9e7e7dbf Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 20 Sep 2024 15:13:26 +0200 Subject: [PATCH 54/64] update cheats --- crates/cheatcodes/assets/cheatcodes.json | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 16c4644df75a..7ad90736c78a 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -3583,7 +3583,9 @@ ] }, "group": "evm", - "status": "deprecated", + "status": { + "deprecated": "replaced by `deleteStateSnapshot`" + }, "safety": "unsafe" }, { @@ -3603,7 +3605,9 @@ ] }, "group": "evm", - "status": "deprecated", + "status": { + "deprecated": "replaced by `deleteStateSnapshots`" + }, "safety": "unsafe" }, { @@ -7305,7 +7309,9 @@ ] }, "group": "evm", - "status": "deprecated", + "status": { + "deprecated": "replaced by `revertToState`" + }, "safety": "unsafe" }, { @@ -7325,7 +7331,9 @@ ] }, "group": "evm", - "status": "deprecated", + "status": { + "deprecated": "replaced by `revertToStateAndDelete`" + }, "safety": "unsafe" }, { @@ -8345,7 +8353,9 @@ ] }, "group": "evm", - "status": "deprecated", + "status": { + "deprecated": "replaced by `snapshotState`" + }, "safety": "unsafe" }, { From 222c5c774467a7f114cc74a88f7869026a180b21 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 14:05:08 +0200 Subject: [PATCH 55/64] start tracking depth --- crates/cheatcodes/src/evm.rs | 3 +++ crates/cheatcodes/src/inspector.rs | 6 ++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index bb12c08ae213..c467d7d62058 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -72,6 +72,8 @@ pub struct GasRecord { pub name: String, /// The total gas used in the gas snapshot. pub gas_used: u64, + /// Depth of the gas snapshot. + pub depth: u64, } impl Cheatcode for addrCall { @@ -830,6 +832,7 @@ fn inner_start_gas_snapshot( group: group.to_string(), name: name.clone(), gas_used: 0, + depth: 0, }); ccx.state.gas_metering.last_snapshot_name = Some(name); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6b3083db4de4..d6ad3796478d 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1352,10 +1352,8 @@ impl Inspector for Cheatcodes { // Store the total gas used for all active gas records started by `startSnapshotGas`. self.gas_metering.gas_records.iter_mut().for_each(|record| { - // Only record top-level gas usage, excluding root call. - if ecx.journaled_state.depth() == 1 { - record.gas_used = record.gas_used.saturating_add(gas.spent()); - } + record.gas_used = record.gas_used.saturating_add(gas.spent()); + record.depth = ecx.journaled_state.depth(); }); // If `startStateDiffRecording` has been called, update the `reverted` status of the From f9cecae90b412f5d83d66e9946105a22708ae7b3 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 14:08:34 +0200 Subject: [PATCH 56/64] add measurement to create_end --- crates/cheatcodes/src/inspector.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index d6ad3796478d..8cc5c31b09f1 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -791,6 +791,12 @@ 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| { + record.gas_used = record.gas_used.saturating_add(outcome.result.gas.spent()); + record.depth = ecx.journaled_state.depth(); + }); + // 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 { From 7556786815e9d3913b1b195b11e470c9470a4e9c Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 14:58:52 +0200 Subject: [PATCH 57/64] process feedback --- crates/cheatcodes/src/evm.rs | 13 +++++- crates/cheatcodes/src/inspector.rs | 74 ++++++++++++++++++------------ 2 files changed, 56 insertions(+), 31 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index c467d7d62058..40bd38ed4404 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -72,7 +72,16 @@ pub struct GasRecord { pub name: String, /// The total gas used in the gas snapshot. pub gas_used: u64, - /// Depth of the gas snapshot. + /// Depth at which the gas snapshot was taken. + pub depth: u64, +} + +/// Used for capturing gas frames. +#[derive(Clone, Debug)] +pub struct GasFrame { + /// The total gas used in the frame. + pub gas_used: u64, + /// Depth at which the frame was taken. pub depth: u64, } @@ -832,7 +841,7 @@ fn inner_start_gas_snapshot( group: group.to_string(), name: name.clone(), gas_used: 0, - depth: 0, + depth: ccx.ecx.journaled_state.depth(), }); ccx.state.gas_metering.last_snapshot_name = Some(name); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 8cc5c31b09f1..5a807ce88437 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, GasRecord, RecordAccess, + DealRecord, GasFrame, 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}; @@ -244,7 +244,7 @@ pub struct GasMetering { /// Stores the last depth. pub last_recorded_depth: u64, /// Stores recorded gas frames. - pub recorded_frames: Vec, + pub recorded_frames: Vec, /// Gas records for the active snapshots. pub gas_records: Vec, } @@ -258,11 +258,13 @@ impl GasMetering { /// Stop the gas recording. pub fn stop(&mut self) { - self.gas_records.iter_mut().for_each(|record| { - record.gas_used = self.recorded_frames.iter().sum::(); - }); + println!("recorded frames: {:?}", self.recorded_frames); + + // self.gas_records.iter_mut().for_each(|record| { + // record.gas_used = self.recorded_frames.iter().sum::(); + // }); - println!("gas records: {:?}", self.gas_records); + // println!("gas records: {:?}", self.gas_records); // reduce sum of gas frames to a single value self.recording = false; @@ -791,10 +793,11 @@ impl Cheatcodes { } } - // Store the total gas used for all active gas records started by `startSnapshotGas` + // Store the total gas used for all active gas records started by `startSnapshotGas`. self.gas_metering.gas_records.iter_mut().for_each(|record| { - record.gas_used = record.gas_used.saturating_add(outcome.result.gas.spent()); - record.depth = ecx.journaled_state.depth(); + 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 @@ -1358,8 +1361,9 @@ impl Inspector for Cheatcodes { // Store the total gas used for all active gas records started by `startSnapshotGas`. self.gas_metering.gas_records.iter_mut().for_each(|record| { - record.gas_used = record.gas_used.saturating_add(gas.spent()); - record.depth = ecx.journaled_state.depth(); + 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 @@ -1615,24 +1619,36 @@ impl Cheatcodes { interpreter: &mut Interpreter, ecx: &mut EvmContext, ) { - println!( - "{:?} - {:?} @ {}", - interpreter.instruction_result, - interpreter.gas.spent(), - ecx.journaled_state.depth() - ); + let target_depth = self.gas_metering.gas_records.last().unwrap().depth; - if self.gas_metering.last_recorded_depth != ecx.journaled_state.depth() { - self.gas_metering.last_recorded_frame = 0; - self.gas_metering.last_recorded_depth = ecx.journaled_state.depth(); - } - - if matches!(interpreter.instruction_result, InstructionResult::Continue) { - self.gas_metering - .recorded_frames - .push(interpreter.gas.spent() - self.gas_metering.last_recorded_frame); + if ecx.journaled_state.depth() == target_depth && + matches!(interpreter.instruction_result, InstructionResult::Continue) + { + match interpreter.current_opcode() { + op::CALL | + op::CREATE | + op::CREATE2 | + op::STATICCALL | + op::DELEGATECALL | + op::EXTSTATICCALL | + op::EXTDELEGATECALL => { + println!("ignored: {:?}", interpreter.current_opcode()); + // This is already handled in `call_end` / `create_end`. + } + _ => { + println!( + "{:?} - {:?} - {:?} @ {}", + interpreter.instruction_result, + interpreter.current_opcode(), + interpreter.gas.spent(), + target_depth + ); - self.gas_metering.last_recorded_frame = interpreter.gas.spent(); + self.gas_metering + .recorded_frames + .push(GasFrame { depth: target_depth, gas_used: interpreter.gas.spent() }); + } + } } } From 45db733a79ff930c8667d4179fea1d5710984a54 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 15:00:21 +0200 Subject: [PATCH 58/64] fix depth --- crates/cheatcodes/src/evm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 40bd38ed4404..8bf80870a58d 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -841,7 +841,7 @@ fn inner_start_gas_snapshot( group: group.to_string(), name: name.clone(), gas_used: 0, - depth: ccx.ecx.journaled_state.depth(), + depth: ccx.ecx.journaled_state.depth() - 1, }); ccx.state.gas_metering.last_snapshot_name = Some(name); From 7d8b793ad6eb828a435c741c3fba6a2830e2368b Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 15:34:04 +0200 Subject: [PATCH 59/64] fix frame recording, remove record_frames --- crates/cheatcodes/src/evm.rs | 9 ----- crates/cheatcodes/src/inspector.rs | 58 +++++++++++++++--------------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 8bf80870a58d..31fd7803ceb5 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -76,15 +76,6 @@ pub struct GasRecord { pub depth: u64, } -/// Used for capturing gas frames. -#[derive(Clone, Debug)] -pub struct GasFrame { - /// The total gas used in the frame. - pub gas_used: u64, - /// Depth at which the frame was taken. - pub depth: u64, -} - impl Cheatcode for addrCall { fn apply(&self, _state: &mut Cheatcodes) -> Result { let Self { privateKey } = self; diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 5a807ce88437..5cc117cea9c3 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, GasFrame, GasRecord, RecordAccess, + DealRecord, GasRecord, RecordAccess, }, inspector::utils::CommonCreateInput, script::{Broadcast, ScriptWallets}, @@ -239,12 +239,8 @@ pub struct GasMetering { /// True if gas metering is enabled. pub recording: bool, - /// Stores the last frame. - pub last_recorded_frame: u64, - /// Stores the last depth. - pub last_recorded_depth: u64, - /// Stores recorded gas frames. - pub recorded_frames: Vec, + /// The gas used in the last frame. + pub last_gas_used: u64, /// Gas records for the active snapshots. pub gas_records: Vec, } @@ -253,22 +249,18 @@ impl GasMetering { /// Start the gas recording. pub fn start(&mut self) { self.recording = true; - self.recorded_frames.clear(); } /// Stop the gas recording. pub fn stop(&mut self) { - println!("recorded frames: {:?}", self.recorded_frames); + println!("gas records: {:?}", self.gas_records); // self.gas_records.iter_mut().for_each(|record| { - // record.gas_used = self.recorded_frames.iter().sum::(); + // record.gas_used.saturating_add(self.recorded_frames.iter().sum::()); // }); - // println!("gas records: {:?}", self.gas_records); - // reduce sum of gas frames to a single value self.recording = false; - self.recorded_frames.clear(); } /// Resume paused gas metering. @@ -1619,11 +1611,7 @@ impl Cheatcodes { interpreter: &mut Interpreter, ecx: &mut EvmContext, ) { - let target_depth = self.gas_metering.gas_records.last().unwrap().depth; - - if ecx.journaled_state.depth() == target_depth && - matches!(interpreter.instruction_result, InstructionResult::Continue) - { + if matches!(interpreter.instruction_result, InstructionResult::Continue) { match interpreter.current_opcode() { op::CALL | op::CREATE | @@ -1632,21 +1620,31 @@ impl Cheatcodes { op::DELEGATECALL | op::EXTSTATICCALL | op::EXTDELEGATECALL => { - println!("ignored: {:?}", interpreter.current_opcode()); - // This is already handled in `call_end` / `create_end`. + // Reset gas used when entering a new frame. + self.gas_metering.last_gas_used = 0; } _ => { - println!( - "{:?} - {:?} - {:?} @ {}", - interpreter.instruction_result, - interpreter.current_opcode(), - interpreter.gas.spent(), - target_depth - ); + 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; + } - self.gas_metering - .recorded_frames - .push(GasFrame { depth: target_depth, gas_used: interpreter.gas.spent() }); + // 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(); + } + }); } } } From f09dc7b9a158df601cdb78dbc19831ed5d289f33 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 16:41:25 +0200 Subject: [PATCH 60/64] clean up --- crates/cheatcodes/src/inspector.rs | 7 ----- testdata/default/cheats/GasSnapshot.t.sol | 33 +++++++++++++++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 5cc117cea9c3..b0ce8f5ede7f 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -253,13 +253,6 @@ impl GasMetering { /// Stop the gas recording. pub fn stop(&mut self) { - println!("gas records: {:?}", self.gas_records); - - // self.gas_records.iter_mut().for_each(|record| { - // record.gas_used.saturating_add(self.recorded_frames.iter().sum::()); - // }); - - // reduce sum of gas frames to a single value self.recording = false; } diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index eca27002a554..148d61c626b2 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -3,9 +3,11 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; import "cheats/Vm.sol"; +import "../logs/console.sol"; contract GasSnapshotTest is DSTest { uint256 public slot0; + uint256 public cachedGas = 0; Vm constant vm = Vm(HEVM_ADDRESS); @@ -48,11 +50,38 @@ contract GasSnapshotTest is DSTest { function testGasComplex() public { TargetB target = new TargetB(); + // Warm up the cache. + target.update(1); + + // Start a cheatcode snapshot. vm.startSnapshotGas("testAssertGasComplexA"); - target.update(1); + target.update(2); - vm.stopSnapshotGas(); + uint256 gasA = vm.stopSnapshotGas(); + console.log("gas native A", gasA); + + // Start a comparitive Solidity snapshot. + + // Warm up the cache. + cachedGas = 1; + + // Start the Solidity snapshot. + cachedGas = gasleft(); + + target.update(3); + + uint256 gasAfter = gasleft(); + + console.log("gas solidity", cachedGas - gasAfter - 100); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("testAssertGasComplexB"); + + target.update(4); + + uint256 gasB = vm.stopSnapshotGas(); + console.log("gas native B", gasB); } // Writes to `GasSnapshotTest` group with custom names. From dc32333227b3eba0074a7e32e1f276d845092246 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 17:19:05 +0200 Subject: [PATCH 61/64] make sure to clean up gas records after processing --- crates/cheatcodes/src/evm.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 31fd7803ceb5..900a80e3a597 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -847,8 +847,6 @@ fn inner_stop_gas_snapshot( group: Option, name: Option, ) -> Result { - ccx.state.gas_metering.stop(); - let group = group.as_deref().unwrap_or( ccx.state.config.running_contract.as_deref().expect("expected running contract"), ); @@ -869,6 +867,13 @@ fn inner_stop_gas_snapshot( .or_default() .insert(name.clone(), value.to_string()); + ccx.state.gas_metering.stop(); + + ccx.state + .gas_metering + .gas_records + .retain(|record| record.group != group || record.name != name); + Ok(value.abi_encode()) } else { bail!("no gas snapshot was started with the name: {name} in group: {group}"); From 29c78daf506ea77ab7c0f6fc1ae406c0a6a12537 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 18:05:55 +0200 Subject: [PATCH 62/64] fix bug, allow to close snapshot that use custom group using shorthand stopSnapshotGas() --- testdata/default/cheats/GasSnapshot.t.sol | 85 +++++++++++++++-------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 148d61c626b2..0b8630e7bced 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -6,11 +6,20 @@ import "cheats/Vm.sol"; import "../logs/console.sol"; contract GasSnapshotTest is DSTest { - uint256 public slot0; - uint256 public cachedGas = 0; - Vm constant vm = Vm(HEVM_ADDRESS); + /// @notice Gas overhead for the Solidity snapshotting function itself. + uint256 private constant GAS_CALIBRATION = 100; + + /// @notice Transient variable for the start gas. + uint256 private cachedGas; + + /// @notice Transient variable for the snapshot name. + string private cachedName; + + /// @notice Arbitrary slot to write to. + uint256 private slot; + function testGasExternal() public { Flare f = new Flare(); @@ -24,64 +33,58 @@ contract GasSnapshotTest is DSTest { function testGasInternal() public { vm.startSnapshotGas("testAssertGasInternalA"); - slot0 = 1; + slot = 1; vm.stopSnapshotGas(); vm.startSnapshotGas("testAssertGasInternalB"); - slot0 = 2; + slot = 2; vm.stopSnapshotGas(); vm.startSnapshotGas("testAssertGasInternalC"); - slot0 = 0; + slot = 0; vm.stopSnapshotGas(); vm.startSnapshotGas("testAssertGasInternalD"); - slot0 = 1; + slot = 1; + + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalE"); + + slot = 2; vm.stopSnapshotGas(); } - function testGasComplex() public { + function testGasComparison() public { TargetB target = new TargetB(); // Warm up the cache. target.update(1); // Start a cheatcode snapshot. - vm.startSnapshotGas("testAssertGasComplexA"); + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonA"); target.update(2); uint256 gasA = vm.stopSnapshotGas(); - console.log("gas native A", gasA); + console.log("gas A", gasA); // Start a comparitive Solidity snapshot. - - // Warm up the cache. - cachedGas = 1; - - // Start the Solidity snapshot. - cachedGas = gasleft(); + _snapStart("testGasComparisonB"); target.update(3); - uint256 gasAfter = gasleft(); - - console.log("gas solidity", cachedGas - gasAfter - 100); - - // Start a cheatcode snapshot. - vm.startSnapshotGas("testAssertGasComplexB"); + uint256 gasB = _snapEnd(); + console.log("gas B", gasB); - target.update(4); - - uint256 gasB = vm.stopSnapshotGas(); - console.log("gas native B", gasB); + vm.snapshotValue("ComparisonGroup", "testGasComparisonB", gasB); } // Writes to `GasSnapshotTest` group with custom names. @@ -131,10 +134,10 @@ contract GasSnapshotTest is DSTest { } // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. - function testSnapshotGasSectionDefaultStop() public { + function testSnapshotGasSectionDefaultGroupStop() public { Flare f = new Flare(); - vm.startSnapshotGas("testSnapshotGasSectionDefault"); + vm.startSnapshotGas("testSnapshotGasSection"); f.run(256); @@ -143,6 +146,19 @@ contract GasSnapshotTest is DSTest { assertGt(gasUsed, 0); } + // Writes to `GasSnapshotTest` group with `testSnapshotGasCustom` name. + function testSnapshotGasSectionCustomGroupStop() public { + Flare f = new Flare(); + + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); + + f.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 { Flare f = new Flare(); @@ -184,6 +200,19 @@ contract GasSnapshotTest is DSTest { vm.snapshotGasLastCall("CustomGroup", "testSnapshotGasGroupName"); } + + function _snapStart(string memory name) internal { + // Warm up cachedGas so the only sstore after calling `gasleft` is exactly 100 gas + cachedGas = 1; + cachedName = name; + cachedGas = gasleft(); + } + + function _snapEnd() internal returns (uint256 gasUsed) { + uint256 newGasLeft = gasleft(); + gasUsed = cachedGas - newGasLeft - GAS_CALIBRATION; + cachedGas = 0; + } } contract Flare { From 1bc6e5432682878d9e25c781129f123f443013ce Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 19:08:48 +0200 Subject: [PATCH 63/64] add additional tests and comparisons --- crates/cheatcodes/src/evm.rs | 64 ++++++--- crates/cheatcodes/src/inspector.rs | 13 +- testdata/default/cheats/GasSnapshot.t.sol | 158 +++++++++++++--------- 3 files changed, 151 insertions(+), 84 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 900a80e3a597..01a17036c740 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -580,10 +580,7 @@ impl Cheatcode for startSnapshotGas_1Call { impl Cheatcode for stopSnapshotGas_0Call { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; - let Some(name) = &ccx.state.gas_metering.last_snapshot_name else { - bail!("no gas snapshot was started yet"); - }; - inner_stop_gas_snapshot(ccx, None, Some(name.clone())) + inner_stop_gas_snapshot(ccx, None, None) } } @@ -813,21 +810,22 @@ fn inner_start_gas_snapshot( 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(); - if ccx - .state - .gas_metering - .gas_records - .iter() - .any(|record| record.group == group && record.name == name) - { - bail!("gas snapshot already active: {name} in group: {group}"); - } - ccx.state.gas_metering.gas_records.push(GasRecord { group: group.to_string(), name: name.clone(), @@ -835,6 +833,7 @@ fn inner_start_gas_snapshot( 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(); @@ -847,10 +846,27 @@ fn inner_stop_gas_snapshot( group: Option, name: Option, ) -> Result { - 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(); + // 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 @@ -867,12 +883,22 @@ fn inner_stop_gas_snapshot( .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); + .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 { diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index b0ce8f5ede7f..62cf1c444ce6 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -230,6 +230,8 @@ pub struct GasMetering { /// 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, @@ -1606,11 +1608,12 @@ impl Cheatcodes { ) { if matches!(interpreter.instruction_result, InstructionResult::Continue) { match interpreter.current_opcode() { - op::CALL | op::CREATE | + op::CALL | + op::CALLCODE | + op::DELEGATECALL | op::CREATE2 | op::STATICCALL | - op::DELEGATECALL | op::EXTSTATICCALL | op::EXTDELEGATECALL => { // Reset gas used when entering a new frame. @@ -1641,6 +1644,12 @@ impl Cheatcodes { } } } + + println!( + "RECORD: {:?} @ {:?}", + revm::interpreter::OpCode::new(interpreter.current_opcode()).unwrap().as_str(), + self.gas_metering.gas_records + ); } #[cold] diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 0b8630e7bced..2ae10c09a35d 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -3,29 +3,21 @@ pragma solidity 0.8.18; import "ds-test/test.sol"; import "cheats/Vm.sol"; -import "../logs/console.sol"; contract GasSnapshotTest is DSTest { Vm constant vm = Vm(HEVM_ADDRESS); - /// @notice Gas overhead for the Solidity snapshotting function itself. - uint256 private constant GAS_CALIBRATION = 100; + Flare public flare; + uint256 public slot; - /// @notice Transient variable for the start gas. - uint256 private cachedGas; - - /// @notice Transient variable for the snapshot name. - string private cachedName; - - /// @notice Arbitrary slot to write to. - uint256 private slot; + function setUp() public { + flare = new Flare(); + } function testGasExternal() public { - Flare f = new Flare(); - vm.startSnapshotGas("testAssertGasExternal"); - f.update(2); + flare.update(2); vm.stopSnapshotGas(); } @@ -62,31 +54,6 @@ contract GasSnapshotTest is DSTest { vm.stopSnapshotGas(); } - function testGasComparison() public { - TargetB target = new TargetB(); - - // Warm up the cache. - target.update(1); - - // Start a cheatcode snapshot. - vm.startSnapshotGas("ComparisonGroup", "testGasComparisonA"); - - target.update(2); - - uint256 gasA = vm.stopSnapshotGas(); - console.log("gas A", gasA); - - // Start a comparitive Solidity snapshot. - _snapStart("testGasComparisonB"); - - target.update(3); - - uint256 gasB = _snapEnd(); - console.log("gas B", gasB); - - vm.snapshotValue("ComparisonGroup", "testGasComparisonB", gasB); - } - // Writes to `GasSnapshotTest` group with custom names. function testSnapshotValueDefaultGroup1() public { uint256 a = 123; @@ -135,11 +102,9 @@ contract GasSnapshotTest is DSTest { // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. function testSnapshotGasSectionDefaultGroupStop() public { - Flare f = new Flare(); - vm.startSnapshotGas("testSnapshotGasSection"); - f.run(256); + flare.run(256); // vm.stopSnapshotGas() will use the last snapshot name. uint256 gasUsed = vm.stopSnapshotGas(); @@ -148,11 +113,9 @@ contract GasSnapshotTest is DSTest { // Writes to `GasSnapshotTest` group with `testSnapshotGasCustom` name. function testSnapshotGasSectionCustomGroupStop() public { - Flare f = new Flare(); - vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); - f.run(256); + flare.run(256); // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. uint256 gasUsed = vm.stopSnapshotGas(); @@ -161,11 +124,9 @@ contract GasSnapshotTest is DSTest { // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. function testSnapshotGasSectionName() public { - Flare f = new Flare(); - vm.startSnapshotGas("testSnapshotGasSectionName"); - f.run(256); + flare.run(256); uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); assertGt(gasUsed, 0); @@ -173,11 +134,9 @@ contract GasSnapshotTest is DSTest { // Writes to `CustomGroup` group with `testSnapshotGasSection` name. function testSnapshotGasSectionGroupName() public { - Flare f = new Flare(); - vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); - f.run(256); + flare.run(256); uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); assertGt(gasUsed, 0); @@ -185,33 +144,104 @@ contract GasSnapshotTest is DSTest { // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. function testSnapshotGasLastCallName() public { - Flare f = new Flare(); - - f.run(1); + flare.run(1); vm.snapshotGasLastCall("testSnapshotGasName"); } // Writes to `CustomGroup` group with `testSnapshotGas` name. function testSnapshotGasLastCallGroupName() public { - Flare f = new Flare(); - - f.run(1); + 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 _snapStart(string memory name) internal { - // Warm up cachedGas so the only sstore after calling `gasleft` is exactly 100 gas + 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 testGasComparisonCreate() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonCreateA"); + new TargetEmpty(); + vm.stopSnapshotGas(); + + // 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; - cachedName = name; cachedGas = gasleft(); } + // Internal function to end a Solidity snapshot. function _snapEnd() internal returns (uint256 gasUsed) { - uint256 newGasLeft = gasleft(); - gasUsed = cachedGas - newGasLeft - GAS_CALIBRATION; - cachedGas = 0; + gasUsed = cachedGas - gasleft() - 100; + cachedGas = 2; } } @@ -253,3 +283,5 @@ contract TargetB { x = x_; } } + +contract TargetEmpty {} From 98c876060392e3b7104827ec27e2c68868032ada Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Mon, 23 Sep 2024 19:25:00 +0200 Subject: [PATCH 64/64] register overhead --- crates/cheatcodes/src/evm.rs | 4 +++- crates/cheatcodes/src/inspector.rs | 3 ++- testdata/default/cheats/GasSnapshot.t.sol | 6 ++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index 01a17036c740..5970d5758405 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -875,7 +875,9 @@ fn inner_stop_gas_snapshot( .iter_mut() .find(|record| record.group == group && record.name == name) { - let value = record.gas_used; + // 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 diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 62cf1c444ce6..2a0b0a656e57 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1646,7 +1646,8 @@ impl Cheatcodes { } println!( - "RECORD: {:?} @ {:?}", + "RECORD [{:?}]: {:?} @ {:?}", + ecx.journaled_state.depth(), revm::interpreter::OpCode::new(interpreter.current_opcode()).unwrap().as_str(), self.gas_metering.gas_records ); diff --git a/testdata/default/cheats/GasSnapshot.t.sol b/testdata/default/cheats/GasSnapshot.t.sol index 2ae10c09a35d..3a91493a50d4 100644 --- a/testdata/default/cheats/GasSnapshot.t.sol +++ b/testdata/default/cheats/GasSnapshot.t.sol @@ -220,12 +220,14 @@ contract GasComparisonTest is DSTest { vm.snapshotValue("ComparisonGroup", "testGasComparisonExternalB", _snapEnd()); } - function testGasComparisonCreate() public { + 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(); @@ -240,7 +242,7 @@ contract GasComparisonTest is DSTest { // Internal function to end a Solidity snapshot. function _snapEnd() internal returns (uint256 gasUsed) { - gasUsed = cachedGas - gasleft() - 100; + gasUsed = cachedGas - gasleft() - 174; cachedGas = 2; } }