diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index deda608395ae5..0422c484bfc6f 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -463,6 +463,16 @@ "name": "depth", "ty": "uint64", "description": "Call depth traversed during the recording of state differences" + }, + { + "name": "oldNonce", + "ty": "uint64", + "description": "The previous nonce of the accessed account." + }, + { + "name": "newNonce", + "ty": "uint64", + "description": "The new nonce of the accessed account." } ] }, diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index c3722aae73ab7..9f00928ef2418 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -278,6 +278,10 @@ interface Vm { StorageAccess[] storageAccesses; /// Call depth traversed during the recording of state differences uint64 depth; + /// The previous nonce of the accessed account. + uint64 oldNonce; + /// The new nonce of the accessed account. + uint64 newNonce; } /// The result of the `stopDebugTraceRecording` call diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index e225a20cd9b2a..3dc8fb77d36d0 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -115,6 +115,16 @@ struct BalanceDiff { new_value: U256, } +/// Nonce diff info. +#[derive(Serialize, Default)] +#[serde(rename_all = "camelCase")] +struct NonceDiff { + /// Initial nonce value. + previous_value: u64, + /// Current nonce value. + new_value: u64, +} + /// Account state diff info. #[derive(Serialize, Default)] #[serde(rename_all = "camelCase")] @@ -123,6 +133,8 @@ struct AccountStateDiffs { label: Option, /// Account balance changes. balance_diff: Option, + /// Account nonce changes. + nonce_diff: Option, /// State changes, per slot. state_diff: BTreeMap, } @@ -143,6 +155,12 @@ impl Display for AccountStateDiffs { balance_diff.previous_value, balance_diff.new_value )?; } + // Print nonce diff if changed. + if let Some(nonce_diff) = &self.nonce_diff + && nonce_diff.previous_value != nonce_diff.new_value + { + writeln!(f, "- nonce diff: {} → {}", nonce_diff.previous_value, nonce_diff.new_value)?; + } // Print state diff if any. if !&self.state_diff.is_empty() { writeln!(f, "- state diff:")?; @@ -1209,6 +1227,7 @@ fn get_recorded_state_diffs(state: &mut Cheatcodes) -> BTreeMap BTreeMap crate::Vm::AccountAccessKind::Call, @@ -962,6 +965,8 @@ impl Cheatcodes { initialized, oldBalance: old_balance, newBalance: U256::ZERO, // updated on call_end + oldNonce: old_nonce, + newNonce: 0, // updated on call_end value: call.call_value(), data: call.input.bytes(ecx), reverted: false, @@ -1327,6 +1332,7 @@ impl Inspector> for Cheatcodes { { debug_assert!(access_is_call(call_access.kind)); call_access.newBalance = acc.info.balance; + call_access.newNonce = acc.info.nonce; } // Merge the last depth's AccountAccesses into the AccountAccesses at the // current depth, or push them back onto the pending @@ -1642,6 +1648,8 @@ impl Inspector> for Cheatcodes { initialized: true, oldBalance: U256::ZERO, // updated on create_end newBalance: U256::ZERO, // updated on create_end + oldNonce: 0, // new contract starts with nonce 0 + newNonce: 1, // updated on create_end (contracts start with nonce 1) value: input.value(), data: input.init_code(), reverted: false, @@ -1748,6 +1756,7 @@ impl Inspector> for Cheatcodes { && let Ok(created_acc) = ecx.journaled_state.load_account(address) { create_access.newBalance = created_acc.info.balance; + create_access.newNonce = created_acc.info.nonce; create_access.deployedCode = created_acc.info.code.clone().unwrap_or_default().original_bytes(); } @@ -1945,13 +1954,15 @@ impl Cheatcodes { // Ensure that we're not selfdestructing a context recording was initiated on let Some(last) = account_accesses.last_mut() else { return }; - // get previous balance and initialized status of the target account + // get previous balance, nonce and initialized status of the target account let target = try_or_return!(interpreter.stack.peek(0)); let target = Address::from_word(B256::from(target)); - let (initialized, old_balance) = ecx + let (initialized, old_balance, old_nonce) = ecx .journaled_state .load_account(target) - .map(|account| (account.info.exists(), account.info.balance)) + .map(|account| { + (account.info.exists(), account.info.balance, account.info.nonce) + }) .unwrap_or_default(); // load balance of this account @@ -1972,6 +1983,8 @@ impl Cheatcodes { initialized, oldBalance: old_balance, newBalance: old_balance + value, + oldNonce: old_nonce, + newNonce: old_nonce, // nonce doesn't change on selfdestruct value, data: Bytes::new(), reverted: false, @@ -2059,12 +2072,15 @@ impl Cheatcodes { Address::from_word(B256::from(try_or_return!(interpreter.stack.peek(0)))); let initialized; let balance; + let nonce; if let Ok(acc) = ecx.journaled_state.load_account(address) { initialized = acc.info.exists(); balance = acc.info.balance; + nonce = acc.info.nonce; } else { initialized = false; balance = U256::ZERO; + nonce = 0; } let curr_depth = ecx .journaled_state @@ -2082,6 +2098,8 @@ impl Cheatcodes { initialized, oldBalance: balance, newBalance: balance, + oldNonce: nonce, + newNonce: nonce, // EXT* operations don't change nonce value: U256::ZERO, data: Bytes::new(), reverted: false, @@ -2360,6 +2378,8 @@ fn append_storage_access( // The remaining fields are defaults oldBalance: U256::ZERO, newBalance: U256::ZERO, + oldNonce: 0, + newNonce: 0, value: U256::ZERO, data: Bytes::new(), deployedCode: Bytes::new(), diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 5959572375115..dfbef7a7df762 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -19,7 +19,7 @@ interface Vm { struct FfiResult { int32 exitCode; bytes stdout; bytes stderr; } struct ChainInfo { uint256 forkId; uint256 chainId; } struct Chain { string name; uint256 chainId; string chainAlias; string rpcUrl; } - struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; } + struct AccountAccess { ChainInfo chainInfo; AccountAccessKind kind; address account; address accessor; bool initialized; uint256 oldBalance; uint256 newBalance; bytes deployedCode; uint256 value; bytes data; bool reverted; StorageAccess[] storageAccesses; uint64 depth; uint64 oldNonce; uint64 newNonce; } struct StorageAccess { address account; bytes32 slot; bool isWrite; bytes32 previousValue; bytes32 newValue; bool reverted; } struct Gas { uint64 gasLimit; uint64 gasTotalUsed; uint64 gasMemoryUsed; int64 gasRefunded; uint64 gasRemaining; } struct DebugStep { uint256[] stack; bytes memoryInput; uint8 opcode; uint64 depth; bool isOutOfGas; address contractAddr; } diff --git a/testdata/default/cheats/RecordAccountAccesses.t.sol b/testdata/default/cheats/RecordAccountAccesses.t.sol index 63100f51f1284..ace0dac7b7708 100644 --- a/testdata/default/cheats/RecordAccountAccesses.t.sol +++ b/testdata/default/cheats/RecordAccountAccesses.t.sol @@ -269,7 +269,7 @@ contract RecordAccountAccessesTest is DSTest { ); string memory diffsJson = cheats.getStateDiffJson(); assertEq( - "{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}", + "{\"0x5991a2df15a8f6a256d3ec51e99254cd3fb576a9\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x00000000000000000000000000000000000000000000000000000000000004d3\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000162e\"}}},\"0xc7183455a4c133ae270771860664b6b7ec320bb1\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x000000000000000000000000000000000000000000000000000000000000162e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x00000000000000000000000000000000000000000000000000000000000004d2\"}}}}", diffsJson ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); @@ -347,7 +347,10 @@ contract RecordAccountAccessesTest is DSTest { string memory expectedStateDiff = "0x000000000000000000000000000000000000162e\n- balance diff: 0 \xE2\x86\x92 1000000000000000000\n\n"; expectedStateDiff = string.concat(expectedStateDiff, callerAddress); - expectedStateDiff = string.concat(expectedStateDiff, "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n\n"); + expectedStateDiff = string.concat( + expectedStateDiff, + "\n- balance diff: 0 \xE2\x86\x92 2000000000000000000\n- nonce diff: 0 \xE2\x86\x92 1\n\n" + ); assertEq(expectedStateDiff, cheats.getStateDiff()); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); @@ -362,6 +365,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "", @@ -381,6 +386,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 1 ether, data: "", @@ -399,6 +406,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "hello world", @@ -417,6 +426,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 1 ether, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0, data: "", @@ -435,6 +446,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 0, newBalance: 2 ether, + oldNonce: 0, + newNonce: 1, deployedCode: address(caller).code, value: 2 ether, data: abi.encodePacked(type(SelfCaller).creationCode, abi.encode("hello2 world2")), @@ -453,6 +466,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: 2 ether, newBalance: 2 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0.2 ether, data: "", @@ -474,7 +489,7 @@ contract RecordAccountAccessesTest is DSTest { cheats.getStateDiff() ); assertEq( - "{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"stateDiff\":{}}}", + "{\"0x00000000000000000000000000000000000004d2\":{\"label\":null,\"balanceDiff\":{\"previousValue\":\"0x0\",\"newValue\":\"0x16345785d8a0000\"},\"nonceDiff\":null,\"stateDiff\":{}}}", cheats.getStateDiffJson() ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); @@ -489,6 +504,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: true, oldBalance: initBalance, newBalance: initBalance, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 1 ether, data: abi.encodeCall(this.revertingCall, (address(1234), "")), @@ -507,6 +524,8 @@ contract RecordAccountAccessesTest is DSTest { initialized: false, oldBalance: 0, newBalance: 0.1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", value: 0.1 ether, data: "", @@ -548,6 +567,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: false, value: 0, @@ -581,6 +602,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: shouldRevert ? 0 : 0.9 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 1 ether, @@ -614,6 +637,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.1 ether, @@ -647,6 +672,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.01 ether, @@ -680,6 +707,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0.01 ether, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.001 ether, @@ -713,6 +742,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.09 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.1 ether, @@ -746,6 +777,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.01 ether, @@ -779,6 +812,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0.01 ether, newBalance: 0.01 ether, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0.001 ether, @@ -800,7 +835,7 @@ contract RecordAccountAccessesTest is DSTest { cheats.getStateDiff() ); assertEq( - "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}", + "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":\"NestedStorer\",\"balanceDiff\":null,\"nonceDiff\":null,\"stateDiff\":{\"0x4566fa0cd03218c55bba914d793f5e6b9113172c1f684bb5f464c08c867e8977\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xbf57896b60daefa2c41de2feffecfc11debd98ea8c913a5170f60e53959ac00a\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xc664893a982d78bbeab379feef216ff517b7ea73626b280723be1ace370364cd\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"},\"0xdc5330afa9872081253545dca3f448752688ff1b098b38c1abe4c4cdff4b0b0e\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x0000000000000000000000000000000000000000000000000000000000000001\"}}}}", cheats.getStateDiffJson() ); Vm.AccountAccess[] memory called = filterExtcodesizeForLegacyTests(cheats.stopAndReturnStateDiff()); @@ -828,6 +863,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -873,6 +910,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -906,6 +945,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Resume, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -952,6 +993,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: storer.code, initialized: true, value: 0, @@ -972,6 +1015,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 0, @@ -1004,6 +1049,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: address(hypotheticalStorer).code, initialized: true, value: 0, @@ -1050,6 +1097,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: address(hypotheticalAddress).code, initialized: true, value: 0, @@ -1068,6 +1117,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Call, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", initialized: true, value: 0, @@ -1098,6 +1149,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: "", initialized: true, value: 1 ether, @@ -1116,6 +1169,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.SelfDestruct, oldBalance: startingBalance - 1 ether, newBalance: startingBalance, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: true, value: 1 ether, @@ -1134,6 +1189,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Create, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 1, deployedCode: "", initialized: true, value: 1 ether, @@ -1152,6 +1209,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.SelfDestruct, oldBalance: 0, newBalance: 1 ether, + oldNonce: 0, + newNonce: 0, deployedCode: hex"", initialized: false, value: 1 ether, @@ -1248,6 +1307,8 @@ contract RecordAccountAccessesTest is DSTest { kind: Vm.AccountAccessKind.Resume, oldBalance: 0, newBalance: 0, + oldNonce: 0, + newNonce: 0, deployedCode: "", initialized: expected.initialized, value: 0, diff --git a/testdata/default/repros/Issue9643.t.sol b/testdata/default/repros/Issue9643.t.sol index 7a985138dc335..f7f71cdd2a3e0 100644 --- a/testdata/default/repros/Issue9643.t.sol +++ b/testdata/default/repros/Issue9643.t.sol @@ -42,7 +42,7 @@ contract Issue9643Test is DSTest { proxied.setCounter(42); string memory rawDiff = vm.getStateDiffJson(); assertEq( - "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"balanceDiff\":null,\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}}}", + "{\"0x2e234dae75c793f67a35089c9d99245e1c58470b\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{\"0x0000000000000000000000000000000000000000000000000000000000000000\":{\"previousValue\":\"0x0000000000000000000000000000000000000000000000000000000000000000\",\"newValue\":\"0x000000000000000000000000000000000000000000000000000000000000002a\"}}},\"0x5615deb798bb3e4dfa0139dfa1b3d433cc23b72f\":{\"label\":null,\"balanceDiff\":null,\"nonceDiff\":{\"previousValue\":0,\"newValue\":1},\"stateDiff\":{}}}", rawDiff ); }