diff --git a/crates/era-cheatcodes/src/cheatcodes.rs b/crates/era-cheatcodes/src/cheatcodes.rs index 145880058..af0f924ca 100644 --- a/crates/era-cheatcodes/src/cheatcodes.rs +++ b/crates/era-cheatcodes/src/cheatcodes.rs @@ -55,6 +55,7 @@ use zksync_utils::{h256_to_u256, u256_to_h256}; type EraDb = StorageView>>; // address(uint160(uint256(keccak256('hevm cheat code')))) +// 0x7109709ecfa91a80626ff3989d68f67f5b1dd12d const CHEATCODE_ADDRESS: H160 = H160([ 113, 9, 112, 158, 207, 169, 26, 128, 98, 111, 243, 152, 157, 104, 246, 127, 91, 29, 209, 45, ]); @@ -197,7 +198,6 @@ struct BroadcastOpts { original_caller: H160, new_origin: H160, depth: usize, - nonce: u64, } impl DynTracer, SimpleMemory> @@ -303,7 +303,7 @@ impl DynTracer, SimpleMemory> if let Opcode::FarCall(_call) = data.opcode.variant.opcode { let current = state.vm_local_state.callstack.current; if current.code_address != CHEATCODE_ADDRESS { - if let Some(broadcast) = &mut self.permanent_actions.broadcast { + if let Some(broadcast) = self.permanent_actions.broadcast.as_ref() { let prev_cs = state .vm_local_state .callstack @@ -319,6 +319,7 @@ impl DynTracer, SimpleMemory> origin: broadcast.new_origin, }); + let new_origin = broadcast.new_origin; let revm_db_for_era = storage .borrow_mut() .storage_handle @@ -339,25 +340,37 @@ impl DynTracer, SimpleMemory> FarCallABI::from_u256(src0.value) }; + let gas_limit = current.ergs_remaining; + let (nonce, _) = Self::get_nonce(new_origin, &mut storage.borrow_mut()); + let tx = BroadcastableTransaction { rpc, transaction: ethers::types::transaction::eip2718::TypedTransaction::Legacy( TransactionRequest { - from: Some(broadcast.new_origin), + from: Some(new_origin), to: Some(ethers::types::NameOrAddress::Address( current.code_address, )), - gas: None, //FIXME: call.gas_limit if set from script + //FIXME: set only if set manually by user + gas: Some(gas_limit.into()), + //FIXME: retrieve proper value value: Some(farcall_abi.ergs_passed.into()), data: Some(get_calldata(&state, &memory).into()), - nonce: Some(broadcast.nonce.into()), + nonce: Some(nonce.into()), ..Default::default() }, ), }; + tracing::debug!(?tx, "storing for broadcast"); + self.broadcastable_transactions.push(tx); - broadcast.nonce += 1; + //FIXME: detect if this is a deployment and increase the other nonce too + self.set_nonce( + new_origin, + (Some(nonce + 1), None), + &mut storage.borrow_mut(), + ); } } return @@ -726,10 +739,8 @@ impl CheatcodeTracer { } getNonce_0(getNonce_0Call { account }) => { tracing::info!("👷 Getting nonce for {account:?}"); - let mut storage = storage.borrow_mut(); - let nonce_key = get_nonce_key(&account.to_h160()); - let full_nonce = storage.read_value(&nonce_key); - let (account_nonce, _) = decompose_full_nonce(h256_to_u256(full_nonce)); + let (account_nonce, _) = + Self::get_nonce(account.to_h160(), &mut storage.borrow_mut()); tracing::info!( "👷 Nonces for account {:?} are {}", account, @@ -904,36 +915,21 @@ impl CheatcodeTracer { } setNonce(setNonceCall { account, newNonce: new_nonce }) => { tracing::info!("👷 Setting nonce for {account:?} to {new_nonce}"); - let mut storage = storage.borrow_mut(); - let nonce_key = get_nonce_key(&account.to_h160()); - let full_nonce = storage.read_value(&nonce_key); - let (mut account_nonce, mut deployment_nonce) = - decompose_full_nonce(h256_to_u256(full_nonce)); - if account_nonce.as_u64() >= new_nonce { - tracing::error!( - "SetNonce cheatcode failed: Account nonce is already set to a higher value ({}, requested {})", - account_nonce, - new_nonce - ); - return - } - account_nonce = new_nonce.into(); - if deployment_nonce.as_u64() >= new_nonce { - tracing::error!( - "SetNonce cheatcode failed: Deployment nonce is already set to a higher value ({}, requested {})", - deployment_nonce, - new_nonce - ); - return - } - deployment_nonce = new_nonce.into(); - let enforced_full_nonce = nonces_to_full_nonce(account_nonce, deployment_nonce); - tracing::info!( - "👷 Nonces for account {:?} have been set to {}", - account, - new_nonce + let new_full_nonce = self.set_nonce( + account.to_h160(), + (Some(new_nonce.into()), Some(new_nonce.into())), + &mut storage.borrow_mut(), ); - self.write_storage(nonce_key, u256_to_h256(enforced_full_nonce), &mut storage); + + if new_full_nonce.is_some() { + tracing::info!( + "👷 Nonces for account {:?} have been set to {}", + account, + new_nonce + ); + } else { + tracing::error!("👷 Setting nonces failed") + } } startBroadcast_0(startBroadcast_0Call {}) => { tracing::info!("👷 Starting broadcast with default origin"); @@ -1188,6 +1184,52 @@ impl CheatcodeTracer { }); } + /// Returns a given account's nonce + /// + /// The first item of the tuple represents the total number of transactions, + /// meanwhile the second represents the number of contract deployed + fn get_nonce(account: H160, storage: &mut RefMut) -> (U256, U256) { + let key = get_nonce_key(&account); + let full_nonce = storage.read_value(&key); + + return decompose_full_nonce(h256_to_u256(full_nonce)) + } + + /// Sets a given account's nonces + /// + /// Returns the new nonce + fn set_nonce( + &mut self, + account: H160, + (tx_nonce, deploy_nonce): (Option, Option), + storage: &mut RefMut, + ) -> Option<(U256, U256)> { + let key = get_nonce_key(&account); + let (mut account_nonce, mut deployment_nonce) = Self::get_nonce(account, storage); + if let Some(tx_nonce) = tx_nonce { + if account_nonce >= tx_nonce { + tracing::error!(?account, value = ?account_nonce, requested = ?tx_nonce, "account nonce is already set to a higher value"); + return None + } + + account_nonce = tx_nonce; + } + + if let Some(deploy_nonce) = deploy_nonce { + if deployment_nonce >= deploy_nonce { + tracing::error!(?account, value = ?deployment_nonce, requested = ?deploy_nonce, "deployment nonce is already set to a higher value"); + return None + } + + deployment_nonce = deploy_nonce; + } + + let new_full_nonce = nonces_to_full_nonce(account_nonce, deployment_nonce); + self.write_storage(key, u256_to_h256(new_full_nonce), storage); + + return Some((account_nonce, deployment_nonce)) + } + fn add_trimmed_return_data(&mut self, data: &[u8]) { let data_length = data.len(); let mut data: Vec = data @@ -1336,7 +1378,6 @@ impl CheatcodeTracer { original_origin: original_tx_origin.into(), original_caller: state.vm_local_state.callstack.current.msg_sender.into(), depth, - nonce, }) } diff --git a/crates/era-cheatcodes/tests/src/cheatcodes/Broadcast.t.sol b/crates/era-cheatcodes/tests/src/cheatcodes/Broadcast.t.sol index ad27ca3c3..67e6d0a71 100644 --- a/crates/era-cheatcodes/tests/src/cheatcodes/Broadcast.t.sol +++ b/crates/era-cheatcodes/tests/src/cheatcodes/Broadcast.t.sol @@ -16,6 +16,10 @@ contract ATest is Test { return b; } + function pt(uint256 a) public payable returns (uint256) { + return t(a); + } + function inc() public returns (uint256) { changed += 1; } @@ -62,4 +66,52 @@ contract BroadcastTest is Test { ); require(success, "stopBroadcast failed"); } + + function test_BroadcastValue() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startBroadcast(address)", ACCOUNT_A) + ); + require(success, "startBroadcast failed"); + + ATest test = new ATest(); + test.pt{ value: 42 }(16); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("stopBroadcast()") + ); + require(success, "stopBroadcast failed"); + } + + function test_BroadcastGasLimit() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startBroadcast()") + ); + require(success, "startBroadcast failed"); + + ATest test = new ATest(); + test.t{gas: 12345678}(12345678); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("stopBroadcast()") + ); + require(success, "stopBroadcast failed"); + } + + function test_BroadcastNonces() public { + (bool success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("startBroadcast(address)", ACCOUNT_B) + ); + require(success, "startBroadcast failed"); + + ATest test = new ATest(); + test.t(1); + test.t(2); + test.t(3); + test.t(4); + + (success, ) = Constants.CHEATCODE_ADDRESS.call( + abi.encodeWithSignature("stopBroadcast()") + ); + require(success, "stopBroadcast failed"); + } }