diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 6bd4a67b3..55bacbb62 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -10399,6 +10399,46 @@ "status": "stable", "safety": "safe" }, + { + "func": { + "id": "zkGetDeploymentNonce", + "description": "Gets the deployment nonce of a zksync account.", + "declaration": "function zkGetDeploymentNonce(address account) external view returns (uint64 nonce);", + "visibility": "external", + "mutability": "view", + "signature": "zkGetDeploymentNonce(address)", + "selector": "0xa607a7c5", + "selectorBytes": [ + 166, + 7, + 167, + 197 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, + { + "func": { + "id": "zkGetTransactionNonce", + "description": "Gets the transaction nonce of a zksync account.", + "declaration": "function zkGetTransactionNonce(address account) external view returns (uint64 nonce);", + "visibility": "external", + "mutability": "view", + "signature": "zkGetTransactionNonce(address)", + "selector": "0xe092ad12", + "selectorBytes": [ + 224, + 146, + 173, + 18 + ] + }, + "group": "evm", + "status": "stable", + "safety": "safe" + }, { "func": { "id": "zkRegisterContract", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index 36455cf79..b37b27d85 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -908,6 +908,14 @@ interface Vm { #[cheatcode(group = Testing, safety = Safe)] function zkRegisterContract(string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode) external pure; + /// Gets the transaction nonce of a zksync account. + #[cheatcode(group = Evm, safety = Safe)] + function zkGetTransactionNonce(address account) external view returns (uint64 nonce); + + /// Gets the deployment nonce of a zksync account. + #[cheatcode(group = Evm, safety = Safe)] + function zkGetDeploymentNonce(address account) external view returns (uint64 nonce); + /// If the condition is false, discard this run's fuzz inputs and generate new ones. #[cheatcode(group = Testing, safety = Safe)] function assume(bool condition) external pure; diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index f284a7374..1dae12f08 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -143,6 +143,20 @@ impl Cheatcode for getNonce_1Call { } } +impl Cheatcode for zkGetTransactionNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + get_nonce(ccx, account) + } +} + +impl Cheatcode for zkGetDeploymentNonceCall { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { account } = self; + get_nonce(ccx, account) + } +} + impl Cheatcode for loadCall { fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; diff --git a/crates/forge/tests/fixtures/zk/ScriptSetup.s.sol b/crates/forge/tests/fixtures/zk/ScriptSetup.s.sol index b1b52aee1..f77eccc14 100644 --- a/crates/forge/tests/fixtures/zk/ScriptSetup.s.sol +++ b/crates/forge/tests/fixtures/zk/ScriptSetup.s.sol @@ -4,26 +4,42 @@ pragma solidity ^0.8.13; import {Script} from "forge-std/Script.sol"; import {Greeter} from "../src/Greeter.sol"; +interface VmExt { + function zkGetTransactionNonce( + address account + ) external view returns (uint64 nonce); + function zkGetDeploymentNonce( + address account + ) external view returns (uint64 nonce); +} + contract ScriptSetupNonce is Script { + VmExt internal constant vmExt = VmExt(VM_ADDRESS); + function setUp() public { - uint256 initial_nonce = checkNonce(address(tx.origin)); + uint256 initialNonceTx = checkTxNonce(address(tx.origin)); + uint256 initialNonceDeploy = checkDeployNonce(address(tx.origin)); // Perform transactions and deploy contracts in setup to increment nonce and verify broadcast nonce matches onchain new Greeter(); new Greeter(); new Greeter(); new Greeter(); - assert(checkNonce(address(tx.origin)) == initial_nonce); + assert(checkTxNonce(address(tx.origin)) == initialNonceTx); + assert(checkDeployNonce(address(tx.origin)) == initialNonceDeploy); } function run() public { // Get initial nonce - uint256 initial_nonce = checkNonce(address(tx.origin)); - assert(initial_nonce == vm.getNonce(address(tx.origin))); + uint256 initialNonceTx = checkTxNonce(tx.origin); + uint256 initialNonceDeploy = checkDeployNonce(tx.origin); + assert(initialNonceTx == vmExt.zkGetTransactionNonce(tx.origin)); + assert(initialNonceDeploy == vmExt.zkGetDeploymentNonce(tx.origin)); // Create and interact with non-broadcasted contract to verify nonce is not incremented Greeter notBroadcastGreeter = new Greeter(); notBroadcastGreeter.greeting("john"); - assert(checkNonce(address(tx.origin)) == initial_nonce); + assert(checkTxNonce(tx.origin) == initialNonceTx); + assert(checkDeployNonce(tx.origin) == initialNonceDeploy); // Start broadcasting transactions vm.startBroadcast(); @@ -33,28 +49,51 @@ contract ScriptSetupNonce is Script { // Deploy checker and verify nonce NonceChecker checker = new NonceChecker(); + + vm.stopBroadcast(); + // We expect the nonce to be incremented by 1 because the check is done in an external // call - checker.assertNonce(vm.getNonce(address(tx.origin)) + 1); - vm.stopBroadcast(); + checker.assertTxNonce( + vmExt.zkGetTransactionNonce(address(tx.origin)) + 1 + ); + checker.assertDeployNonce( + vmExt.zkGetDeploymentNonce(address(tx.origin)) + ); } - function checkNonce(address addr) public returns (uint256) { + function checkTxNonce(address addr) public returns (uint256) { // We prank here to avoid accidentally "polluting" the nonce of `addr` during the call // for example when `addr` is `tx.origin` vm.prank(address(this), address(this)); - return NonceLib.getNonce(addr); + return NonceLib.getTxNonce(addr); + } + + function checkDeployNonce(address addr) public returns (uint256) { + // We prank here to avoid accidentally "polluting" the nonce of `addr` during the call + // for example when `addr` is `tx.origin` + vm.prank(address(this), address(this)); + return NonceLib.getDeployNonce(addr); } } contract NonceChecker { - function checkNonce() public returns (uint256) { - return NonceLib.getNonce(address(tx.origin)); + function checkTxNonce() public returns (uint256) { + return NonceLib.getTxNonce(address(tx.origin)); + } + + function checkDeployNonce() public returns (uint256) { + return NonceLib.getDeployNonce(address(tx.origin)); + } + + function assertTxNonce(uint256 expected) public { + uint256 real_nonce = checkTxNonce(); + require(real_nonce == expected, "tx nonce mismatch"); } - function assertNonce(uint256 expected) public { - uint256 real_nonce = checkNonce(); - require(real_nonce == expected, "Nonce mismatch"); + function assertDeployNonce(uint256 expected) public { + uint256 real_nonce = checkDeployNonce(); + require(real_nonce == expected, "deploy nonce mismatch"); } } @@ -62,8 +101,19 @@ library NonceLib { address constant NONCE_HOLDER = address(0x8003); /// Retrieve tx nonce for `addr` from the NONCE_HOLDER system contract - function getNonce(address addr) internal returns (uint256) { - (bool success, bytes memory data) = NONCE_HOLDER.call(abi.encodeWithSignature("getMinNonce(address)", addr)); + function getTxNonce(address addr) internal returns (uint256) { + (bool success, bytes memory data) = NONCE_HOLDER.call( + abi.encodeWithSignature("getMinNonce(address)", addr) + ); + require(success, "Failed to get nonce"); + return abi.decode(data, (uint256)); + } + + /// Retrieve tx nonce for `addr` from the NONCE_HOLDER system contract + function getDeployNonce(address addr) internal returns (uint256) { + (bool success, bytes memory data) = NONCE_HOLDER.call( + abi.encodeWithSignature("getDeploymentNonce(address)", addr) + ); require(success, "Failed to get nonce"); return abi.decode(data, (uint256)); } diff --git a/crates/forge/tests/it/zk/mod.rs b/crates/forge/tests/it/zk/mod.rs index 0a0500422..c9c59280c 100644 --- a/crates/forge/tests/it/zk/mod.rs +++ b/crates/forge/tests/it/zk/mod.rs @@ -20,4 +20,5 @@ mod paymaster; mod proxy; mod repros; mod script; +mod sender; mod traces; diff --git a/crates/forge/tests/it/zk/nonce.rs b/crates/forge/tests/it/zk/nonce.rs index 3dd857893..27f8c7851 100644 --- a/crates/forge/tests/it/zk/nonce.rs +++ b/crates/forge/tests/it/zk/nonce.rs @@ -13,7 +13,7 @@ forgetest_async!(setup_block_on_script_test, |prj, cmd| { "./script/ScriptSetup.s.sol", "ScriptSetupNonce", None, - 4, + 3, Some(&["-vvvvv"]), ) .await; diff --git a/crates/forge/tests/it/zk/sender.rs b/crates/forge/tests/it/zk/sender.rs new file mode 100644 index 000000000..287e8d903 --- /dev/null +++ b/crates/forge/tests/it/zk/sender.rs @@ -0,0 +1,25 @@ +//! Forge tests for basic zkysnc functionality. + +use crate::{config::*, test_helpers::TEST_DATA_DEFAULT}; +use forge::revm::primitives::SpecId; +use foundry_test_utils::Filter; + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_correctly_updates_sender_nonce() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new( + "testZkTxSenderNoncesAreConsistent|testZkTxSenderNoncesAreConsistentInBroadcast", + "ZkTxSenderTest", + ".*", + ); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; +} + +#[tokio::test(flavor = "multi_thread")] +async fn test_zk_correctly_updates_sender_balance() { + let runner = TEST_DATA_DEFAULT.runner_zksync(); + let filter = Filter::new("testZkTxSenderBalancesAreConsistent", "ZkTxSenderTest", ".*"); + + TestConfig::with_filter(runner, filter).spec_id(SpecId::SHANGHAI).run().await; +} diff --git a/crates/strategy/zksync/src/cheatcode/context.rs b/crates/strategy/zksync/src/cheatcode/context.rs index 7f69a155d..08b4bf7ef 100644 --- a/crates/strategy/zksync/src/cheatcode/context.rs +++ b/crates/strategy/zksync/src/cheatcode/context.rs @@ -10,7 +10,7 @@ use foundry_zksync_compilers::dual_compiled_contracts::{ use foundry_zksync_core::{vm::ZkEnv, ZkPaymasterData, H256}; use revm::primitives::Bytecode; -use super::types::{ZkPersistNonceUpdate, ZkStartupMigration}; +use super::types::ZkStartupMigration; /// Context for [ZksyncCheatcodeInspectorStrategyRunner]. #[derive(Debug, Default, Clone)] @@ -48,9 +48,6 @@ pub struct ZksyncCheatcodeInspectorStrategyContext { /// providing the necessary level of isolation. pub persisted_factory_deps: HashMap>, - /// Nonce update persistence behavior in zkEVM for the tx caller. - pub zk_persist_nonce_update: ZkPersistNonceUpdate, - /// Stores the factory deps that were detected as part of CREATE2 deployer call. /// Must be cleared every call. pub set_deployer_call_input_factory_deps: Vec>, @@ -108,7 +105,6 @@ impl ZksyncCheatcodeInspectorStrategyContext { zk_startup_migration: ZkStartupMigration::Defer, zk_use_factory_deps: Default::default(), persisted_factory_deps: Default::default(), - zk_persist_nonce_update: Default::default(), set_deployer_call_input_factory_deps: Default::default(), zk_env, } diff --git a/crates/strategy/zksync/src/cheatcode/runner/cheatcode_handlers.rs b/crates/strategy/zksync/src/cheatcode/runner/cheatcode_handlers.rs index ffd99282d..ea274ab30 100644 --- a/crates/strategy/zksync/src/cheatcode/runner/cheatcode_handlers.rs +++ b/crates/strategy/zksync/src/cheatcode/runner/cheatcode_handlers.rs @@ -8,8 +8,9 @@ use foundry_cheatcodes::{ createFork_0Call, createFork_1Call, createFork_2Call, createSelectFork_0Call, createSelectFork_1Call, createSelectFork_2Call, dealCall, etchCall, getCodeCall, getNonce_0Call, mockCallRevert_0Call, mockCall_0Call, resetNonceCall, rollCall, - selectForkCall, setNonceCall, setNonceUnsafeCall, warpCall, zkRegisterContractCall, - zkUseFactoryDepCall, zkUsePaymasterCall, zkVmCall, zkVmSkipCall, + selectForkCall, setNonceCall, setNonceUnsafeCall, warpCall, zkGetDeploymentNonceCall, + zkGetTransactionNonceCall, zkRegisterContractCall, zkUseFactoryDepCall, zkUsePaymasterCall, + zkVmCall, zkVmSkipCall, }, }; use foundry_evm::backend::LocalForkId; @@ -100,6 +101,26 @@ impl ZksyncCheatcodeInspectorStrategyRunner { let nonce = foundry_zksync_core::cheatcodes::get_nonce(account, ccx.ecx); Ok(nonce.abi_encode()) } + t if using_zk_vm && is::(t) => { + let &zkGetTransactionNonceCall { account } = + cheatcode.as_any().downcast_ref().unwrap(); + + info!(?account, "cheatcode zkGetTransactionNonce"); + + let (tx_nonce, _) = + foundry_zksync_core::cheatcodes::get_full_nonce(account, ccx.ecx); + Ok(tx_nonce.abi_encode()) + } + t if using_zk_vm && is::(t) => { + let &zkGetDeploymentNonceCall { account } = + cheatcode.as_any().downcast_ref().unwrap(); + + info!(?account, "cheatcode zkGetDeploymentNonce"); + + let (_, deploy_nonce) = + foundry_zksync_core::cheatcodes::get_full_nonce(account, ccx.ecx); + Ok(deploy_nonce.abi_encode()) + } t if using_zk_vm && is::(t) => { let mockCall_0Call { callee, data, returnData } = cheatcode.as_any().downcast_ref().unwrap(); diff --git a/crates/strategy/zksync/src/cheatcode/runner/mod.rs b/crates/strategy/zksync/src/cheatcode/runner/mod.rs index b90ed80f8..6ef9e685f 100644 --- a/crates/strategy/zksync/src/cheatcode/runner/mod.rs +++ b/crates/strategy/zksync/src/cheatcode/runner/mod.rs @@ -60,8 +60,6 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner debug!("allowing startup storage migration"); ctx.zk_startup_migration.allow(); - debug!("allowing persisting next nonce update"); - ctx.zk_persist_nonce_update.persist_next(); } fn apply_full( @@ -101,7 +99,7 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner let init_code = input.init_code(); let to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS.to_address())); - let mut nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; + let mut nonce = foundry_zksync_core::tx_nonce(broadcast.new_origin, ecx_inner) as u64; let find_contract = ctx .dual_compiled_contracts .find_bytecode(&init_code.0) @@ -196,6 +194,18 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner rpc, transaction: TransactionMaybeSigned::Unsigned(tx), }); + + // Explicitly increment tx nonce if calls are not isolated. + // This isn't needed in EVM, but required in zkEVM as the nonces are split. + if !config.evm_opts.isolate { + let tx_nonce = nonce; + foundry_zksync_core::set_tx_nonce( + broadcast.new_origin, + U256::from(tx_nonce.saturating_add(1)), + ecx_inner, + ); + debug!(target: "cheatcodes", address=%broadcast.new_origin, new_tx_nonce=tx_nonce+1, tx_nonce, "incremented zksync tx nonce"); + } } fn record_broadcastable_call_transactions( @@ -227,7 +237,7 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner let is_fixed_gas_limit = foundry_cheatcodes::check_if_fixed_gas_limit(ecx_inner, call.gas_limit); - let nonce = foundry_zksync_core::nonce(broadcast.new_origin, ecx_inner) as u64; + let tx_nonce = foundry_zksync_core::tx_nonce(broadcast.new_origin, ecx_inner); let factory_deps = &mut ctx.set_deployer_call_input_factory_deps; let injected_factory_deps = ctx @@ -270,7 +280,7 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner to: Some(TxKind::from(Some(call.target_address))), value: call.transfer_value(), input: TransactionInput::new(call.input.clone()), - nonce: Some(nonce), + nonce: Some(tx_nonce as u64), chain_id: Some(ecx_inner.env.cfg.chain_id), gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, ..Default::default() @@ -293,6 +303,16 @@ impl CheatcodeInspectorStrategyRunner for ZksyncCheatcodeInspectorStrategyRunner transaction: TransactionMaybeSigned::Unsigned(tx), }); debug!(target: "cheatcodes", tx=?broadcastable_transactions.back().unwrap(), "broadcastable call"); + + // Explicitly increment tx nonce if calls are not isolated. + if !config.evm_opts.isolate { + foundry_zksync_core::set_tx_nonce( + broadcast.new_origin, + U256::from(tx_nonce.saturating_add(1)), + ecx_inner, + ); + debug!(target: "cheatcodes", address=%broadcast.new_origin, new_tx_nonce=tx_nonce+1, tx_nonce, "incremented zksync tx nonce"); + } } fn post_initialize_interp( @@ -504,14 +524,12 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategyRunner { ctx.zk_use_factory_deps.clear(); tracing::debug!(contract = contract.name, "using dual compiled contract"); - let zk_persist_nonce_update = ctx.zk_persist_nonce_update.check(); let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { mocked_calls: state.mocked_calls.clone(), expected_calls: Some(&mut state.expected_calls), accesses: state.accesses.as_mut(), persisted_factory_deps: Some(&mut ctx.persisted_factory_deps), paymaster_data: ctx.paymaster_params.take(), - persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, zk_env: ctx.zk_env.clone(), }; @@ -683,7 +701,6 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategyRunner { } info!("running call in zkEVM {:#?}", call); - let zk_persist_nonce_update = ctx.zk_persist_nonce_update.check(); // NOTE(zk): Clear injected factory deps here even though it's actually used in broadcast. // To be consistent with where we clear factory deps in try_create_in_zk. @@ -695,7 +712,6 @@ impl CheatcodeInspectorStrategyExt for ZksyncCheatcodeInspectorStrategyRunner { accesses: state.accesses.as_mut(), persisted_factory_deps: Some(&mut ctx.persisted_factory_deps), paymaster_data: ctx.paymaster_params.take(), - persist_nonce_update: state.broadcast.is_some() || zk_persist_nonce_update, zk_env: ctx.zk_env.clone(), }; diff --git a/crates/strategy/zksync/src/cheatcode/types.rs b/crates/strategy/zksync/src/cheatcode/types.rs index c07650b8f..fdb5b2191 100644 --- a/crates/strategy/zksync/src/cheatcode/types.rs +++ b/crates/strategy/zksync/src/cheatcode/types.rs @@ -1,36 +1,3 @@ -/// Allows overriding nonce update behavior for the tx caller in the zkEVM. -/// -/// Since each CREATE or CALL is executed as a separate transaction within zkEVM, we currently skip -/// persisting nonce updates as it erroneously increments the tx nonce. However, under certain -/// situations, e.g. deploying contracts, transacts, etc. the nonce updates must be persisted. -#[derive(Default, Debug, Clone)] -pub enum ZkPersistNonceUpdate { - /// Never update the nonce. This is currently the default behavior. - #[default] - Never, - /// Override the default behavior, and persist nonce update for tx caller for the next - /// zkEVM execution _only_. - PersistNext, -} - -impl ZkPersistNonceUpdate { - /// Persist nonce update for the tx caller for next execution. - pub fn persist_next(&mut self) { - *self = Self::PersistNext; - } - - /// Retrieve if a nonce update must be persisted, or not. Resets the state to default. - pub fn check(&mut self) -> bool { - let persist_nonce_update = match self { - Self::Never => false, - Self::PersistNext => true, - }; - *self = Default::default(); - - persist_nonce_update - } -} - /// Setting for migrating the database to zkEVM storage when starting in ZKsync mode. /// The migration is performed on the DB via the inspector so must only be performed once. #[derive(Debug, Default, Clone)] diff --git a/crates/strategy/zksync/src/executor/runner.rs b/crates/strategy/zksync/src/executor/runner.rs index 347d9ae1b..03231d880 100644 --- a/crates/strategy/zksync/src/executor/runner.rs +++ b/crates/strategy/zksync/src/executor/runner.rs @@ -67,7 +67,7 @@ impl ExecutorStrategyRunner for ZksyncExecutorStrategyRunner { let full_nonce = executor.backend.storage(address, slot)?; let full_nonce = foundry_zksync_core::state::parse_full_nonce(full_nonce); let new_full_nonce = - foundry_zksync_core::state::new_full_nonce(nonce, full_nonce.deploy_nonce); + foundry_zksync_core::state::new_full_nonce(nonce as u128, full_nonce.deploy_nonce); executor.backend.insert_account_storage(address, slot, new_full_nonce)?; Ok(()) diff --git a/crates/zksync/core/src/cheatcodes.rs b/crates/zksync/core/src/cheatcodes.rs index ad0f33aa6..457c99e79 100644 --- a/crates/zksync/core/src/cheatcodes.rs +++ b/crates/zksync/core/src/cheatcodes.rs @@ -106,6 +106,22 @@ where tx_nonce.to_ru256() } +/// Gets the full nonce for a specific address. +pub fn get_full_nonce(address: Address, ecx: &mut InnerEvmContext) -> (rU256, rU256) +where + DB: Database, + ::Error: Debug, +{ + let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); + ecx.load_account(nonce_addr).expect("account could not be loaded"); + let zk_address = address.to_h160(); + let nonce_key = get_nonce_key(&zk_address).key().to_ru256(); + let full_nonce = ecx.sload(nonce_addr, nonce_key).unwrap_or_default(); + + let (tx_nonce, deploy_nonce) = decompose_full_nonce(full_nonce.to_u256()); + (tx_nonce.to_ru256(), deploy_nonce.to_ru256()) +} + /// Sets code for a specific address. pub fn etch(address: Address, bytecode: &[u8], ecx: &mut InnerEvmContext) where diff --git a/crates/zksync/core/src/lib.rs b/crates/zksync/core/src/lib.rs index d5c7d8844..994d84dbe 100644 --- a/crates/zksync/core/src/lib.rs +++ b/crates/zksync/core/src/lib.rs @@ -33,7 +33,7 @@ use std::fmt::Debug; use zksync_types::bytecode::BytecodeHash; pub use utils::{fix_l2_gas_limit, fix_l2_gas_price}; -pub use vm::{balance, encode_create_params, nonce}; +pub use vm::{balance, deploy_nonce, encode_create_params, tx_nonce}; pub use vm::{SELECTOR_CONTRACT_DEPLOYER_CREATE, SELECTOR_CONTRACT_DEPLOYER_CREATE2}; pub use zksync_multivm::interface::{Call, CallType}; @@ -52,6 +52,11 @@ type Result = std::result::Result; /// Represents an empty code pub const EMPTY_CODE: [u8; 32] = [0; 32]; +/// Represents ZKsync hash for [EMPTY_CODE] +pub const EMPTY_CODE_HASH: H256 = H256( + alloy_primitives::b256!("01000001f862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925").0, +); + /// The minimum possible address that is not reserved in the zkSync space. const MIN_VALID_ADDRESS: u32 = 2u32.pow(16); @@ -248,7 +253,7 @@ where DB: Database, DB::Error: Debug, { - //ensure nonce is _only_ tx nonce + // ensure nonce is _only_ tx nonce let (tx_nonce, _deploy_nonce) = decompose_full_nonce(nonce.to_u256()); let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); @@ -264,6 +269,26 @@ where ecx.sstore(nonce_addr, nonce_key, updated_nonce.to_ru256()).expect("failed storing value"); } +/// Increment transaction nonce for a specific address. +pub fn increment_tx_nonce(address: Address, ecx: &mut InnerEvmContext) +where + DB: Database, + DB::Error: Debug, +{ + let nonce_addr = NONCE_HOLDER_ADDRESS.to_address(); + ecx.load_account(nonce_addr).expect("account could not be loaded"); + let nonce_key = get_nonce_key(address); + ecx.touch(&nonce_addr); + + // We make sure to keep the old deployment nonce + let (tx_nonce, deploy_nonce) = ecx + .sload(nonce_addr, nonce_key) + .map(|v| decompose_full_nonce(v.to_u256())) + .unwrap_or_default(); + let updated_nonce = nonces_to_full_nonce(tx_nonce.saturating_add(1u32.into()), deploy_nonce); + ecx.sstore(nonce_addr, nonce_key, updated_nonce.to_ru256()).expect("failed storing value"); +} + /// Sets deployment nonce for a specific address. pub fn set_deployment_nonce(address: Address, nonce: rU256, ecx: &mut InnerEvmContext) where diff --git a/crates/zksync/core/src/state.rs b/crates/zksync/core/src/state.rs index ab95a9a45..b5a14e27d 100644 --- a/crates/zksync/core/src/state.rs +++ b/crates/zksync/core/src/state.rs @@ -24,21 +24,22 @@ pub fn get_nonce_storage(address: rAddress) -> (rAddress, rU256) { } /// Returns full nonce value -pub fn new_full_nonce(tx_nonce: u64, deploy_nonce: u64) -> rU256 { +pub fn new_full_nonce(tx_nonce: u128, deploy_nonce: u128) -> rU256 { nonces_to_full_nonce(tx_nonce.into(), deploy_nonce.into()).to_ru256() } /// Represents a ZKSync account nonce with two 64-bit transaction and deployment nonces. +/// Note that the nonces have a theoretical size of 128-bits but the server only accepts 32-bits. #[derive(Default, Debug, Clone, Copy)] pub struct FullNonce { /// Transaction nonce. - pub tx_nonce: u64, + pub tx_nonce: u128, /// Deployment nonce. - pub deploy_nonce: u64, + pub deploy_nonce: u128, } /// Decomposes a full nonce into transaction and deploy nonces. pub fn parse_full_nonce(full_nonce: rU256) -> FullNonce { let (tx, deploy) = decompose_full_nonce(full_nonce.to_u256()); - FullNonce { tx_nonce: tx.as_u64(), deploy_nonce: deploy.as_u64() } + FullNonce { tx_nonce: tx.as_u128(), deploy_nonce: deploy.as_u128() } } diff --git a/crates/zksync/core/src/vm/db.rs b/crates/zksync/core/src/vm/db.rs index 74017abcd..8e5ece0ad 100644 --- a/crates/zksync/core/src/vm/db.rs +++ b/crates/zksync/core/src/vm/db.rs @@ -13,13 +13,14 @@ use zksync_basic_types::{L2ChainId, H160, H256, U256}; use zksync_types::{ get_code_key, get_nonce_key, get_system_context_init_logs, h256_to_u256, utils::{decompose_full_nonce, storage_key_for_eth_balance}, - Nonce, StorageKey, StorageLog, StorageValue, + StorageKey, StorageLog, StorageValue, }; use zksync_vm_interface::storage::ReadStorage; use crate::{ convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, hash_bytecode, + state::FullNonce, }; /// Default chain id @@ -138,22 +139,31 @@ where self.read_db(*code_key.address(), h256_to_u256(*code_key.key())) } + /// Returns the [FullNonce] for a given account from NonceHolder storage. + pub fn get_full_nonce(&mut self, address: Address) -> FullNonce { + let address = address.to_h160(); + let nonce_key = get_nonce_key(&address); + let nonce_storage = self.read_db(*nonce_key.address(), h256_to_u256(*nonce_key.key())); + let (tx_nonce, deploy_nonce) = decompose_full_nonce(h256_to_u256(nonce_storage)); + FullNonce { tx_nonce: tx_nonce.as_u128(), deploy_nonce: deploy_nonce.as_u128() } + } + /// Returns the nonce for a given account from NonceHolder storage. - pub fn get_tx_nonce(&mut self, address: Address) -> Nonce { + pub fn get_tx_nonce(&mut self, address: Address) -> u128 { let address = address.to_h160(); let nonce_key = get_nonce_key(&address); let nonce_storage = self.read_db(*nonce_key.address(), h256_to_u256(*nonce_key.key())); let (tx_nonce, _deploy_nonce) = decompose_full_nonce(h256_to_u256(nonce_storage)); - Nonce(tx_nonce.as_u32()) + tx_nonce.as_u128() } /// Returns the deployment nonce for a given account from NonceHolder storage. - pub fn get_deploy_nonce(&mut self, address: Address) -> Nonce { + pub fn get_deploy_nonce(&mut self, address: Address) -> u128 { let address = address.to_h160(); let nonce_key = get_nonce_key(&address); let nonce_storage = self.read_db(*nonce_key.address(), h256_to_u256(*nonce_key.key())); let (_tx_nonce, deploy_nonce) = decompose_full_nonce(h256_to_u256(nonce_storage)); - Nonce(deploy_nonce.as_u32()) + deploy_nonce.as_u128() } /// Returns the nonce for a given account from NonceHolder storage. diff --git a/crates/zksync/core/src/vm/inspect.rs b/crates/zksync/core/src/vm/inspect.rs index abe56230b..1236f6510 100644 --- a/crates/zksync/core/src/vm/inspect.rs +++ b/crates/zksync/core/src/vm/inspect.rs @@ -27,10 +27,11 @@ use zksync_multivm::{ use zksync_types::{ get_nonce_key, h256_to_address, h256_to_u256, l2::L2Tx, transaction_request::PaymasterParams, u256_to_h256, PackedEthSignature, StorageKey, Transaction, ACCOUNT_CODE_STORAGE_ADDRESS, - CONTRACT_DEPLOYER_ADDRESS, NONCE_HOLDER_ADDRESS, + CONTRACT_DEPLOYER_ADDRESS, }; use zksync_vm_interface::storage::{ReadStorage, StoragePtr, WriteStorage}; +use core::convert::Into; use std::{ collections::HashMap, fmt::Debug, @@ -40,7 +41,8 @@ use std::{ use crate::{ be_words_to_bytes, convert::{ConvertAddress, ConvertH160, ConvertH256, ConvertRU256, ConvertU256}, - fix_l2_gas_limit, fix_l2_gas_price, is_system_address, + fix_l2_gas_limit, fix_l2_gas_price, increment_tx_nonce, is_system_address, + state::{new_full_nonce, parse_full_nonce, FullNonce}, vm::{ db::{ZKVMData, DEFAULT_CHAIN_ID}, env::{create_l1_batch_env, create_system_env}, @@ -102,6 +104,7 @@ where ); tx.common_data.fee.gas_limit = new_gas_limit; + let initiator_address = tx.initiator_account(); info!("executing batched tx ({}/{})", idx + 1, total_txns); let mut result = inspect(tx, ecx, ccx, call_ctx.clone())?; @@ -149,6 +152,11 @@ where } _ => unreachable!("aggregated result must only contain success"), } + + // Increment the nonce manually if there are multiple batches + if total_txns > 1 { + increment_tx_nonce(initiator_address.to_address(), ecx); + } } Ok(aggregated_result.expect("must have result")) @@ -184,11 +192,9 @@ where .with_extra_factory_deps(persisted_factory_deps) .with_storage_accesses(ccx.accesses.take()); - let is_create = call_ctx.is_create; info!(?call_ctx, "executing transaction in zk vm"); let initiator_address = tx.common_data.initiator_address; - let persist_nonce_update = ccx.persist_nonce_update; if tx.common_data.signature.is_empty() { // FIXME: This is a hack to make sure that the signature is not empty. @@ -203,12 +209,12 @@ where let InnerZkVmResult { tx_result, bytecodes, - modified_storage, + mut modified_storage, call_traces, recorded_immutables, create_outcome, gas_usage, - } = inspect_inner(tx, storage_ptr, chain_id, ccx, call_ctx); + } = inspect_inner(tx, storage_ptr, chain_id, ccx, call_ctx.clone()); info!( reserved=?gas_usage.bootloader_debug.reserved_gas, limit=?gas_usage.limit, execution=?gas_usage.execution, pubdata=?gas_usage.pubdata, refunded=?gas_usage.refunded, @@ -251,7 +257,7 @@ where }; // in zkEVM the output is the 0-padded address, we replace this with the deployed // bytecode so the traces can pick it up correctly - let output = if is_create { + let output = if call_ctx.is_create { let create_result = match (address, create_outcome) { (Some(address), Some(create_outcome)) => { if address == create_outcome.address { @@ -329,23 +335,30 @@ where let mut storage: rHashMap> = Default::default(); let mut codes: rHashMap = Default::default(); - // We skip nonce updates when should_update_nonce is false to avoid nonce mismatch - let filtered = modified_storage.iter().filter(|(k, _)| { - !(k.address() == &NONCE_HOLDER_ADDRESS && - get_nonce_key(&initiator_address) == **k && - !persist_nonce_update) - }); - - for (k, v) in filtered { + + let initiator_nonce_key = get_nonce_key(&initiator_address); + + // NOTE(zk): We need to revert the tx nonce of the initiator as we intercept and dispatch + // CALLs and CREATEs to the zkEVM. The CREATEs always increment the deployment nonce + // which must be persisted, but the tx nonce increase must be reverted. + if let Some(initiator_nonce) = modified_storage.get_mut(&initiator_nonce_key) { + let FullNonce { tx_nonce, deploy_nonce } = parse_full_nonce(initiator_nonce.to_ru256()); + let new_tx_nonce = tx_nonce.saturating_sub(1); + error!(address=?initiator_address, from=?tx_nonce, to=?new_tx_nonce, deploy_nonce, "reverting initiator tx nonce for CALL"); + *initiator_nonce = new_full_nonce(new_tx_nonce, deploy_nonce).to_h256(); + } + + for (k, v) in modified_storage { let address = k.address().to_address(); let index = k.key().to_ru256(); era_db.load_account(address); let previous = era_db.sload(address, index); let entry = storage.entry(address).or_default(); + entry.insert(index, StorageSlot::new_changed(previous, v.to_ru256())); if k.address() == &ACCOUNT_CODE_STORAGE_ADDRESS { - if let Some(bytecode) = bytecodes.get(&h256_to_u256(*v)) { + if let Some(bytecode) = bytecodes.get(&h256_to_u256(v)) { let bytecode = bytecode.iter().flat_map(|x| u256_to_h256(*x).to_fixed_bytes()).collect_vec(); let bytecode = Bytecode::new_raw(Bytes::from(bytecode)); @@ -354,14 +367,14 @@ where } else { // We populate bytecodes for all non-system addresses if !is_system_address(k.key().to_h160().to_address()) { - if let Some(bytecode) = (&mut era_db).load_factory_dep(*v) { + if let Some(bytecode) = (&mut era_db).load_factory_dep(v) { let hash = B256::from_slice(v.as_bytes()); let bytecode = Bytecode::new_raw(Bytes::from(bytecode)); codes.insert(k.key().to_h160().to_address(), (hash, bytecode)); } else { warn!( "no bytecode was found for {:?}, requested by account {:?}", - *v, + v, k.key().to_h160() ); } diff --git a/crates/zksync/core/src/vm/mod.rs b/crates/zksync/core/src/vm/mod.rs index ec3f13fdf..16b1f07a8 100644 --- a/crates/zksync/core/src/vm/mod.rs +++ b/crates/zksync/core/src/vm/mod.rs @@ -12,6 +12,7 @@ pub use inspect::{ batch_factory_dependencies, inspect, inspect_as_batch, ZKVMExecutionResult, ZKVMResult, }; pub use runner::{ - balance, call, code_hash, create, encode_create_params, nonce, transact, ZkCreateInputs, + balance, call, code_hash, create, deploy_nonce, encode_create_params, transact, tx_nonce, + ZkCreateInputs, }; pub use tracers::cheatcode::CheatcodeTracerContext; diff --git a/crates/zksync/core/src/vm/runner.rs b/crates/zksync/core/src/vm/runner.rs index 647d0d9d9..cf60a3174 100644 --- a/crates/zksync/core/src/vm/runner.rs +++ b/crates/zksync/core/src/vm/runner.rs @@ -12,6 +12,7 @@ use zksync_types::{ U256, }; +use core::convert::Into; use std::{cmp::min, fmt::Debug}; use crate::{ @@ -60,7 +61,7 @@ where let tx = L2Tx::new( Some(transact_to), env.tx.data.to_vec(), - nonce, + (nonce as u32).into(), Fee { gas_limit, max_fee_per_gas, @@ -89,7 +90,6 @@ where let mut ccx = CheatcodeTracerContext { persisted_factory_deps, - persist_nonce_update: true, zk_env: zk_env.clone(), ..Default::default() }; @@ -122,13 +122,22 @@ where B256::from(ZKVMData::new(ecx).get_code_hash(address).0) } -/// Retrieves nonce for a given address. -pub fn nonce(address: Address, ecx: &mut InnerEvmContext) -> u32 +/// Retrieves transaction nonce for a given address. +pub fn tx_nonce(address: Address, ecx: &mut InnerEvmContext) -> u128 where DB: Database, ::Error: Debug, { - ZKVMData::new(ecx).get_tx_nonce(address).0 + ZKVMData::new(ecx).get_tx_nonce(address) +} + +/// Retrieves deployment nonce for a given address. +pub fn deploy_nonce(address: Address, ecx: &mut InnerEvmContext) -> u128 +where + DB: Database, + ::Error: Debug, +{ + ZKVMData::new(ecx).get_deploy_nonce(address) } /// EraVM equivalent of [`CreateInputs`] @@ -158,6 +167,8 @@ where let ZkCreateInputs { create_input, factory_deps, value, msg_sender } = inputs; info!("create tx {}", hex::encode(&create_input)); + // We're using `tx.origin` as the initiator so the zkEVM validation does not fail when using + // `msg.sender` as it's not EOA. The nonce and balance changes thus need to be adapted. let caller = ecx.env.tx.caller; let nonce = ZKVMData::new(ecx).get_tx_nonce(caller); @@ -176,7 +187,7 @@ where let tx = L2Tx::new( Some(CONTRACT_DEPLOYER_ADDRESS), create_input, - nonce, + (nonce as u32).into(), Fee { gas_limit, max_fee_per_gas, @@ -218,8 +229,10 @@ where ::Error: Debug, { info!(?call, "call tx {}", hex::encode(&call.input)); + // We're using `tx.origin` as the initiator so the zkEVM validation does not fail when using + // `msg.sender` as it's not EOA. The nonce and balance changes thus need to be adapted. let caller = ecx.env.tx.caller; - let nonce: zksync_types::Nonce = ZKVMData::new(ecx).get_tx_nonce(caller); + let nonce = ZKVMData::new(ecx).get_tx_nonce(caller); let paymaster_params = if let Some(paymaster_data) = &ccx.paymaster_data { PaymasterParams { @@ -236,7 +249,7 @@ where let tx = L2Tx::new( Some(call.bytecode_address.to_h160()), call.input.to_vec(), - nonce, + (nonce as u32).into(), Fee { gas_limit, max_fee_per_gas, diff --git a/crates/zksync/core/src/vm/tracers/cheatcode.rs b/crates/zksync/core/src/vm/tracers/cheatcode.rs index 863648940..1a4583601 100644 --- a/crates/zksync/core/src/vm/tracers/cheatcode.rs +++ b/crates/zksync/core/src/vm/tracers/cheatcode.rs @@ -94,8 +94,6 @@ pub struct CheatcodeTracerContext<'a> { pub persisted_factory_deps: Option<&'a mut HashMap>>, /// Paymaster data pub paymaster_data: Option, - /// Whether to persist nonce update for the tx caller, or not. - pub persist_nonce_update: bool, /// Era Vm environment pub zk_env: ZkEnv, } @@ -175,8 +173,11 @@ impl CheatcodeTracer { value: rU256, ) -> bool { // The following addresses are expected to have empty bytecode - let ignored_known_addresses = - [foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS, self.call_context.tx_caller]; + let ignored_known_addresses = [ + foundry_evm_abi::HARDHAT_CONSOLE_ADDRESS, + self.call_context.tx_caller, + self.call_context.msg_sender, + ]; // Skip empty code check for empty calldata with non-zero value (Transfers) if calldata.is_empty() && !value.is_zero() { @@ -340,7 +341,10 @@ impl DynTracer> for Cheatcode } } - // Override msg.sender for the transaction + // Override msg.sender for the execute transaction. + // The same cannot be done for `validateTransaction` due to the many safeguards around + // correct nonce update in the bootloader. So we handle it by modifying the storage + // post-execution. if let Opcode::FarCall(_call) = data.opcode.variant.opcode { let calldata = get_calldata(&state, memory); let current = state.vm_local_state.callstack.current; diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 20c28d893..6492497f5 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -513,6 +513,8 @@ interface Vm { function writeLine(string calldata path, string calldata data) external; function writeToml(string calldata json, string calldata path) external; function writeToml(string calldata json, string calldata path, string calldata valueKey) external; + function zkGetDeploymentNonce(address account) external view returns (uint64 nonce); + function zkGetTransactionNonce(address account) external view returns (uint64 nonce); function zkRegisterContract(string calldata name, bytes32 evmBytecodeHash, bytes calldata evmDeployedBytecode, bytes calldata evmBytecode, bytes32 zkBytecodeHash, bytes calldata zkDeployedBytecode) external pure; function zkUseFactoryDep(string calldata name) external pure; function zkUsePaymaster(address paymaster_address, bytes calldata paymaster_input) external pure; diff --git a/testdata/zk/Cheatcodes.t.sol b/testdata/zk/Cheatcodes.t.sol index 888c15bb2..b8b48c9d9 100644 --- a/testdata/zk/Cheatcodes.t.sol +++ b/testdata/zk/Cheatcodes.t.sol @@ -322,9 +322,17 @@ contract UsesCheatcodes { return vm.getNonce(target); } + function getZkTransactionNonce(Vm vm, address target) public view returns (uint64) { + return vm.zkGetTransactionNonce(target); + } + + function getZkDeploymentNonce(Vm vm, address target) public view returns (uint64) { + return vm.zkGetDeploymentNonce(target); + } + function getZkBalance(Vm vm, address target) public view returns (uint256) { vm.zkVm(true); - getNonce(vm, target); + getZkTransactionNonce(vm, target); return target.balance; } } @@ -346,12 +354,12 @@ contract ZkCheatcodesInZkVmTest is DSTest { address target = address(this); vm.expectRevert(); - helper.getNonce(vm, target); + helper.getZkTransactionNonce(vm, target); } function testCallVmAfterDisableZkVm() external { address target = address(this); - uint64 expected = vm.getNonce(target); + uint64 expected = vm.zkGetTransactionNonce(target); vm.zkVm(false); uint64 got = helper.getNonce(vm, target); diff --git a/testdata/zk/Contracts.t.sol b/testdata/zk/Contracts.t.sol index 0113def5e..b2761d0ee 100644 --- a/testdata/zk/Contracts.t.sol +++ b/testdata/zk/Contracts.t.sol @@ -225,25 +225,27 @@ contract ZkContractsTest is DSTest { (bool success,) = address(vm).call(abi.encodeWithSignature("zkVm(bool)", true)); require(success, "zkVm() call failed"); address sender = address(this); - uint64 startingNonce = vm.getNonce(sender); + uint64 startingTxNonce = vm.zkGetTransactionNonce(sender); + uint64 startingDeployNonce = vm.zkGetDeploymentNonce(sender); //this ensures calls & deployments increase the nonce vm.startBroadcast(sender); Greeter greeter = new Greeter(); - assert(vm.getNonce(sender) == startingNonce + 1); + assert(vm.zkGetDeploymentNonce(sender) == startingDeployNonce + 1); greeter.setAge(42); - assert(vm.getNonce(sender) == startingNonce + 2); + assert(vm.zkGetTransactionNonce(sender) == startingTxNonce + 2); // static-call, nonce shouldn't change uint256 age = greeter.getAge(); assert(age == 42); - assert(vm.getNonce(sender) == startingNonce + 2); + assert(vm.zkGetDeploymentNonce(sender) == startingDeployNonce + 1); + assert(vm.zkGetTransactionNonce(sender) == startingTxNonce + 2); uint256 num = greeter.greeting2("zksync", 2); assert(num == 4); - assert(vm.getNonce(sender) == startingNonce + 3); + assert(vm.zkGetTransactionNonce(sender) == startingTxNonce + 3); vm.stopBroadcast(); } } diff --git a/testdata/zk/ZkTxSender.t.sol b/testdata/zk/ZkTxSender.t.sol new file mode 100644 index 000000000..aa07a1c44 --- /dev/null +++ b/testdata/zk/ZkTxSender.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity ^0.8.13; + +import "ds-test/test.sol"; +import "../cheats/Vm.sol"; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + +contract PayableCounter { + uint256 public number; + + constructor() payable {} + + function setNumber(uint256 newNumber) public { + number = newNumber; + } +} + +contract ZkTxSenderTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testZkTxSenderNoncesAreConsistent() public { + address msgSender = msg.sender; + address thisAddr = address(this); + + assertEq(msgSender, tx.origin, "msg.sender and tx.origin must be same for top level"); + + uint256 thisAddrTxNonce = vm.zkGetTransactionNonce(thisAddr); + uint256 thisAddrDeployNonce = vm.zkGetDeploymentNonce(thisAddr); + uint256 msgSenderTxNonce = vm.zkGetTransactionNonce(msgSender); + uint256 msgSenderDeployNonce = vm.zkGetDeploymentNonce(msgSender); + + Counter counter = new Counter(); + assertEq(msgSenderTxNonce, vm.zkGetTransactionNonce(msgSender), "deployment#1: msg.sender tx nonce mismatch"); + assertEq( + msgSenderDeployNonce, vm.zkGetDeploymentNonce(msgSender), "deployment#1: msg.sender deploy nonce mismatch" + ); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "deployment#1: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce + 1, vm.zkGetDeploymentNonce(thisAddr), "deployment#1: self deploy nonce mismatch"); + + new Counter(); + assertEq(msgSenderTxNonce, vm.zkGetTransactionNonce(msgSender), "deployment#2: msg.sender tx nonce mismatch"); + assertEq( + msgSenderDeployNonce, vm.zkGetDeploymentNonce(msgSender), "deployment#2: msg.sender deploy nonce mismatch" + ); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "deployment#2: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce + 2, vm.zkGetDeploymentNonce(thisAddr), "deployment#2: self deploy nonce mismatch"); + + counter.setNumber(0); + assertEq(msgSenderTxNonce, vm.zkGetTransactionNonce(msgSender), "tx: msg.sender tx nonce mismatch"); + assertEq(msgSenderDeployNonce, vm.zkGetDeploymentNonce(msgSender), "tx: msg.sender deploy nonce mismatch"); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "tx: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce + 2, vm.zkGetDeploymentNonce(thisAddr), "tx: self deploy nonce mismatch"); + } + + function testZkTxSenderNoncesAreConsistentInBroadcast() public { + address msgSender = msg.sender; + address thisAddr = address(this); + + assertEq(msgSender, tx.origin, "msg.sender and tx.origin must be same for top level"); + + // Start broadcasting on msg.sender + vm.startBroadcast(msgSender); + + uint256 thisAddrTxNonce = vm.zkGetTransactionNonce(thisAddr); + uint256 thisAddrDeployNonce = vm.zkGetDeploymentNonce(thisAddr); + uint256 msgSenderTxNonce = vm.zkGetTransactionNonce(msgSender); + uint256 msgSenderDeployNonce = vm.zkGetDeploymentNonce(msgSender); + + Counter counter = new Counter(); + assertEq( + msgSenderTxNonce + 1, vm.zkGetTransactionNonce(msgSender), "deployment#1: msg.sender tx nonce mismatch" + ); + assertEq( + msgSenderDeployNonce + 1, + vm.zkGetDeploymentNonce(msgSender), + "deployment#1: msg.sender deploy nonce mismatch" + ); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "deployment#1: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce, vm.zkGetDeploymentNonce(thisAddr), "deployment#1: self deploy nonce mismatch"); + + new Counter(); + assertEq( + msgSenderTxNonce + 2, vm.zkGetTransactionNonce(msgSender), "deployment#2: msg.sender tx nonce mismatch" + ); + assertEq( + msgSenderDeployNonce + 2, + vm.zkGetDeploymentNonce(msgSender), + "deployment#2: msg.sender deploy nonce mismatch" + ); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "deployment#2: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce, vm.zkGetDeploymentNonce(thisAddr), "deployment#2: self deploy nonce mismatch"); + + counter.setNumber(0); + assertEq(msgSenderTxNonce + 3, vm.zkGetTransactionNonce(msgSender), "tx: msg.sender tx nonce mismatch"); + assertEq(msgSenderDeployNonce + 2, vm.zkGetDeploymentNonce(msgSender), "tx: msg.sender deploy nonce mismatch"); + assertEq(thisAddrTxNonce, vm.zkGetTransactionNonce(thisAddr), "tx: self tx nonce mismatch"); + assertEq(thisAddrDeployNonce, vm.zkGetDeploymentNonce(thisAddr), "tx: self deploy nonce mismatch"); + + vm.stopBroadcast(); + } + + function testZkTxSenderBalancesAreConsistent() public { + address thisAddr = address(this); + address msgSender = msg.sender; + + assertEq(msgSender, tx.origin, "msg.sender and tx.origin must be same for top level"); + + uint256 thisAddrBalance = thisAddr.balance; + uint256 msgSenderBalance = msgSender.balance; + + PayableCounter counter = new PayableCounter{value: 1 ether}(); + assertEq(thisAddrBalance - 1 ether, thisAddr.balance); + assertEq(msgSenderBalance, msgSender.balance); + assertEq(1 ether, address(counter).balance); + + counter.setNumber(0); + assertEq(thisAddrBalance - 1 ether, thisAddr.balance); + assertEq(msgSenderBalance, msgSender.balance); + assertEq(1 ether, address(counter).balance); + } +}