diff --git a/Cargo.lock b/Cargo.lock index 5c3a4af06567e..b1ac957d09f36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -584,7 +584,7 @@ dependencies = [ "alloy-rlp", "alloy-serde", "alloy-sol-types", - "itertools 0.14.0", + "itertools 0.13.0", "serde", "serde_json", "thiserror 2.0.12", @@ -990,9 +990,9 @@ dependencies = [ [[package]] name = "anstyle-svg" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c681338396641f4e32a29f045d0c70950da7207b4376685b51396c481ee36f1a" +checksum = "0a43964079ef399480603125d5afae2b219aceffb77478956e25f17b9bc3435c" dependencies = [ "anstyle", "anstyle-lossy", @@ -3184,9 +3184,9 @@ dependencies = [ [[package]] name = "derive-where" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e73f2692d4bd3cac41dca28934a39894200c9fabf49586d77d0e5954af1d7902" +checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" dependencies = [ "proc-macro2", "quote", @@ -4248,7 +4248,7 @@ dependencies = [ "fs_extra", "futures-util", "home", - "itertools 0.14.0", + "itertools 0.13.0", "path-slash", "rand 0.8.5", "rayon", @@ -5755,9 +5755,9 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libmimalloc-sys" -version = "0.1.42" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9d6fac27761dabcd4ee73571cdb06b7022dc99089acbe5435691edffaac0f4" +checksum = "bf88cd67e9de251c1781dbe2f641a1a3ad66eaae831b8a2c38fbdc5ddae16d4d" dependencies = [ "cc", "libc", @@ -6066,9 +6066,9 @@ dependencies = [ [[package]] name = "mimalloc" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995942f432bbb4822a7e9c3faa87a695185b0d09273ba85f097b54f4e458f2af" +checksum = "b1791cbe101e95af5764f06f20f6760521f7158f69dbf9d6baf941ee1bf6bc40" dependencies = [ "libmimalloc-sys", ] @@ -6150,9 +6150,9 @@ checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "newtype-uuid" -version = "1.2.2" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8ba303c7a8f8fdee1fe1513cfd918f50f1c69bf65c91b39217bfc2b2af5c081" +checksum = "d5825f69cf354438a53f85b959f0baa6a7ada1f4bd923f63e42c34090bf288fb" dependencies = [ "uuid 1.17.0", ] @@ -6745,9 +6745,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ "memchr", "thiserror 2.0.12", @@ -6756,9 +6756,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -6766,9 +6766,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", @@ -6779,11 +6779,10 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2 0.10.9", ] @@ -7146,7 +7145,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools 0.13.0", "proc-macro2", "quote", "syn 2.0.103", @@ -7985,9 +7984,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.27" +version = "0.23.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" +checksum = "7160e3e10bf4535308537f3c4e1641468cd0e485175d6163087c0393c7d46643" dependencies = [ "aws-lc-rs", "log", @@ -8567,12 +8566,9 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" [[package]] name = "smallvec" @@ -8681,7 +8677,7 @@ dependencies = [ "derive_builder", "derive_more 2.0.1", "dunce", - "itertools 0.14.0", + "itertools 0.13.0", "itoa", "lasso", "match_cfg", @@ -8693,7 +8689,7 @@ dependencies = [ "solar-config", "solar-data-structures", "solar-macros", - "thiserror 2.0.12", + "thiserror 1.0.69", "tracing", "unicode-width 0.2.0", ] @@ -8718,7 +8714,7 @@ dependencies = [ "alloy-primitives", "bitflags 2.9.1", "bumpalo", - "itertools 0.14.0", + "itertools 0.13.0", "memchr", "num-bigint", "num-rational", @@ -9029,7 +9025,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "tempfile", - "thiserror 2.0.12", + "thiserror 1.0.69", "url", "zip", ] diff --git a/Cargo.toml b/Cargo.toml index d635ea33ac0fc..34188737a34ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -204,7 +204,7 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.18.0", default-features = false } -foundry-compilers = { version = "0.17.1", default-features = false } +foundry-compilers = { version = "0.17.3", default-features = false } foundry-fork-db = "0.15" solang-parser = { version = "=0.3.9", package = "foundry-solang-parser" } solar-ast = { version = "=0.1.4", default-features = false } @@ -406,5 +406,5 @@ zip-extract = "=0.2.1" # revm-inspectors = { git = "https://github.com/paradigmxyz/revm-inspectors.git", rev = "a625c04" } ## foundry -# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "855dee4" } +# foundry-compilers = { git = "https://github.com/foundry-rs/compilers.git", rev = "e4a9b04" } # foundry-fork-db = { git = "https://github.com/foundry-rs/foundry-fork-db", rev = "811a61a" } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 57af0e68eef84..3fac38aaf5d66 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -3412,6 +3412,8 @@ enum GasEstimationCallResult { } /// Converts the result of a call to revm EVM into a [`GasEstimationCallResult`]. +/// +/// Expected to stay up to date with: impl TryFrom, u128, State)>> for GasEstimationCallResult { type Error = BlockchainError; @@ -3425,8 +3427,15 @@ impl TryFrom, u128, State)>> for GasEs Ok((exit, output, gas, _)) => match exit { return_ok!() | InstructionResult::CallOrCreate => Ok(Self::Success(gas)), + // Revert opcodes: InstructionResult::Revert => Ok(Self::Revert(output.map(|o| o.into_data()))), + InstructionResult::CallTooDeep | + InstructionResult::OutOfFunds | + InstructionResult::CreateInitCodeStartingEF00 | + InstructionResult::InvalidEOFInitCode | + InstructionResult::InvalidExtDelegateCallTarget => Ok(Self::EvmError(exit)), + // Out of gas errors: InstructionResult::OutOfGas | InstructionResult::MemoryOOG | InstructionResult::MemoryLimitOOG | @@ -3434,11 +3443,10 @@ impl TryFrom, u128, State)>> for GasEs InstructionResult::InvalidOperandOOG | InstructionResult::ReentrancySentryOOG => Ok(Self::OutOfGas), + // Other errors: InstructionResult::OpcodeNotFound | InstructionResult::CallNotAllowedInsideStatic | InstructionResult::StateChangeDuringStaticCall | - InstructionResult::InvalidExtDelegateCallTarget | - InstructionResult::InvalidEXTCALLTarget | InstructionResult::InvalidFEOpcode | InstructionResult::InvalidJump | InstructionResult::NotActivated | @@ -3453,17 +3461,12 @@ impl TryFrom, u128, State)>> for GasEs InstructionResult::CreateContractStartingWithEF | InstructionResult::CreateInitCodeSizeLimit | InstructionResult::FatalExternalError | - InstructionResult::OutOfFunds | - InstructionResult::CallTooDeep => Ok(Self::EvmError(exit)), - - // Handle Revm EOF InstructionResults: not supported InstructionResult::ReturnContractInNotInitEOF | InstructionResult::EOFOpcodeDisabledInLegacy | InstructionResult::SubRoutineStackOverflow | - InstructionResult::CreateInitCodeStartingEF00 | - InstructionResult::InvalidEOFInitCode | InstructionResult::EofAuxDataOverflow | - InstructionResult::EofAuxDataTooSmall => Ok(Self::EvmError(exit)), + InstructionResult::EofAuxDataTooSmall | + InstructionResult::InvalidEXTCALLTarget => Ok(Self::EvmError(exit)), }, } } diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index c83f39bd59034..e2eb84ec522bf 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -661,271 +661,6 @@ impl Cheatcodes { } } - // common create functionality for both legacy and EOF. - fn create_common(&mut self, ecx: Ecx, mut input: Input) -> Option - where - Input: CommonCreateInput, - { - // Check if we should intercept this create - if self.intercept_next_create_call { - // Reset the flag - self.intercept_next_create_call = false; - - // Get initcode from the input - let output = input.init_code(); - - // Return a revert with the initcode as error data - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output, - gas: Gas::new(input.gas_limit()), - }, - address: None, - }); - } - - let gas = Gas::new(input.gas_limit()); - let curr_depth = ecx.journaled_state.depth(); - - // Apply our prank - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth >= prank.depth && input.caller() == prank.prank_caller { - let mut prank_applied = false; - - // At the target depth we set `msg.sender` - if curr_depth == prank.depth { - input.set_caller(prank.new_caller); - prank_applied = true; - } - - // At the target depth, or deeper, we set `tx.origin` - if let Some(new_origin) = prank.new_origin { - ecx.tx.caller = new_origin; - prank_applied = true; - } - - // If prank applied for first time, then update - if prank_applied { - if let Some(applied_prank) = prank.first_time_applied() { - self.pranks.insert(curr_depth, applied_prank); - } - } - } - } - - // Apply EIP-2930 access list - self.apply_accesslist(ecx); - - // Apply our broadcast - if let Some(broadcast) = &self.broadcast { - if curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { - if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { - return Some(CreateOutcome { - result: InterpreterResult { - result: InstructionResult::Revert, - output: Error::encode(err), - gas, - }, - address: None, - }); - } - - ecx.tx.caller = broadcast.new_origin; - - if curr_depth == broadcast.depth { - input.set_caller(broadcast.new_origin); - let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit()); - - let account = &ecx.journaled_state.inner.state()[&broadcast.new_origin]; - self.broadcastable_transactions.push_back(BroadcastableTransaction { - rpc: ecx.journaled_state.database.active_fork_url(), - transaction: TransactionRequest { - from: Some(broadcast.new_origin), - to: None, - value: Some(input.value()), - input: TransactionInput::new(input.init_code()), - nonce: Some(account.info.nonce), - gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, - ..Default::default() - } - .into(), - }); - - input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); - } - } - } - - // Allow cheatcodes from the address of the new contract - let address = input.allow_cheatcodes(self, ecx); - - // If `recordAccountAccesses` has been called, record the create - if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { - recorded_account_diffs_stack.push(vec![AccountAccess { - chainInfo: crate::Vm::ChainInfo { - forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), - chainId: U256::from(ecx.cfg.chain_id), - }, - accessor: input.caller(), - account: address, - kind: crate::Vm::AccountAccessKind::Create, - initialized: true, - oldBalance: U256::ZERO, // updated on (eof)create_end - newBalance: U256::ZERO, // updated on (eof)create_end - value: input.value(), - data: input.init_code(), - reverted: false, - deployedCode: Bytes::new(), // updated on (eof)create_end - storageAccesses: vec![], // updated on (eof)create_end - depth: curr_depth as u64, - }]); - } - - None - } - - // common create_end functionality for both legacy and EOF. - fn create_end_common( - &mut self, - ecx: Ecx, - call: Option<&CreateInputs>, - outcome: &mut CreateOutcome, - ) { - let curr_depth = ecx.journaled_state.depth(); - - // Clean up pranks - if let Some(prank) = &self.get_prank(curr_depth) { - if curr_depth == prank.depth { - ecx.tx.caller = prank.prank_origin; - - // Clean single-call prank once we have returned to the original depth - if prank.single_call { - std::mem::take(&mut self.pranks); - } - } - } - - // Clean up broadcasts - if let Some(broadcast) = &self.broadcast { - if curr_depth == broadcast.depth { - ecx.tx.caller = broadcast.original_origin; - - // Clean single-call broadcast once we have returned to the original depth - if broadcast.single_call { - std::mem::take(&mut self.broadcast); - } - } - } - - // Handle expected reverts - if let Some(expected_revert) = &self.expected_revert { - if curr_depth <= expected_revert.depth && - matches!(expected_revert.kind, ExpectedRevertKind::Default) - { - let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); - return match revert_handlers::handle_expect_revert( - false, - true, - self.config.internal_expect_revert, - &expected_revert, - outcome.result.result, - outcome.result.output.clone(), - &self.config.available_artifacts, - ) { - Ok((address, retdata)) => { - expected_revert.actual_count += 1; - if expected_revert.actual_count < expected_revert.count { - self.expected_revert = Some(expected_revert.clone()); - } - - outcome.result.result = InstructionResult::Return; - outcome.result.output = retdata; - outcome.address = address; - } - Err(err) => { - outcome.result.result = InstructionResult::Revert; - outcome.result.output = err.abi_encode().into(); - } - }; - } - } - - // 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 { - // The root call cannot be recorded. - if curr_depth > 0 { - if let Some(last_depth) = &mut recorded_account_diffs_stack.pop() { - // Update the reverted status of all deeper calls if this call reverted, in - // accordance with EVM behavior - if outcome.result.is_revert() { - last_depth.iter_mut().for_each(|element| { - element.reverted = true; - element - .storageAccesses - .iter_mut() - .for_each(|storage_access| storage_access.reverted = true); - }) - } - - if let Some(create_access) = last_depth.first_mut() { - // Assert that we're at the correct depth before recording post-create state - // changes. Depending on what depth the cheat was called at, there - // may not be any pending calls to update if execution has - // percolated up to a higher depth. - let depth = ecx.journaled_state.depth(); - if create_access.depth == depth as u64 { - debug_assert_eq!( - create_access.kind as u8, - crate::Vm::AccountAccessKind::Create as u8 - ); - if let Some(address) = outcome.address { - if let Ok(created_acc) = ecx.journaled_state.load_account(address) { - create_access.newBalance = created_acc.info.balance; - create_access.deployedCode = created_acc - .info - .code - .clone() - .unwrap_or_default() - .original_bytes(); - } - } - } - // Merge the last depth's AccountAccesses into the AccountAccesses at the - // current depth, or push them back onto the pending - // vector if higher depths were not recorded. This - // preserves ordering of accesses. - if let Some(last) = recorded_account_diffs_stack.last_mut() { - last.append(last_depth); - } else { - recorded_account_diffs_stack.push(last_depth.clone()); - } - } - } - } - } - - // Match the create against expected_creates - if !self.expected_creates.is_empty() { - if let (Some(address), Some(call)) = (outcome.address, call) { - if let Ok(created_acc) = ecx.journaled_state.load_account(address) { - let bytecode = - created_acc.info.code.clone().unwrap_or_default().original_bytes(); - if let Some((index, _)) = - self.expected_creates.iter().find_position(|expected_create| { - expected_create.deployer == call.caller && - expected_create.create_scheme.eq(call.scheme.into()) && - expected_create.bytecode == bytecode - }) - { - self.expected_creates.swap_remove(index); - } - } - } - } - } - pub fn call_with_executor( &mut self, ecx: Ecx, @@ -1812,12 +1547,260 @@ impl Inspector> for Cheatcodes { } } - fn create(&mut self, ecx: Ecx, call: &mut CreateInputs) -> Option { - self.create_common(ecx, call) + fn create(&mut self, ecx: Ecx, mut input: &mut CreateInputs) -> Option { + // Check if we should intercept this create + if self.intercept_next_create_call { + // Reset the flag + self.intercept_next_create_call = false; + + // Get initcode from the input + let output = input.init_code(); + + // Return a revert with the initcode as error data + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output, + gas: Gas::new(input.gas_limit()), + }, + address: None, + }); + } + + let gas = Gas::new(input.gas_limit()); + let curr_depth = ecx.journaled_state.depth(); + + // Apply our prank + if let Some(prank) = &self.get_prank(curr_depth) { + if curr_depth >= prank.depth && input.caller() == prank.prank_caller { + let mut prank_applied = false; + + // At the target depth we set `msg.sender` + if curr_depth == prank.depth { + input.set_caller(prank.new_caller); + prank_applied = true; + } + + // At the target depth, or deeper, we set `tx.origin` + if let Some(new_origin) = prank.new_origin { + ecx.tx.caller = new_origin; + prank_applied = true; + } + + // If prank applied for first time, then update + if prank_applied { + if let Some(applied_prank) = prank.first_time_applied() { + self.pranks.insert(curr_depth, applied_prank); + } + } + } + } + + // Apply EIP-2930 access list + self.apply_accesslist(ecx); + + // Apply our broadcast + if let Some(broadcast) = &self.broadcast { + if curr_depth >= broadcast.depth && input.caller() == broadcast.original_caller { + if let Err(err) = ecx.journaled_state.load_account(broadcast.new_origin) { + return Some(CreateOutcome { + result: InterpreterResult { + result: InstructionResult::Revert, + output: Error::encode(err), + gas, + }, + address: None, + }); + } + + ecx.tx.caller = broadcast.new_origin; + + if curr_depth == broadcast.depth { + input.set_caller(broadcast.new_origin); + let is_fixed_gas_limit = check_if_fixed_gas_limit(&ecx, input.gas_limit()); + + let account = &ecx.journaled_state.inner.state()[&broadcast.new_origin]; + self.broadcastable_transactions.push_back(BroadcastableTransaction { + rpc: ecx.journaled_state.database.active_fork_url(), + transaction: TransactionRequest { + from: Some(broadcast.new_origin), + to: None, + value: Some(input.value()), + input: TransactionInput::new(input.init_code()), + nonce: Some(account.info.nonce), + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, + ..Default::default() + } + .into(), + }); + + input.log_debug(self, &input.scheme().unwrap_or(CreateScheme::Create)); + } + } + } + + // Allow cheatcodes from the address of the new contract + let address = input.allow_cheatcodes(self, ecx); + + // If `recordAccountAccesses` has been called, record the create + if let Some(recorded_account_diffs_stack) = &mut self.recorded_account_diffs_stack { + recorded_account_diffs_stack.push(vec![AccountAccess { + chainInfo: crate::Vm::ChainInfo { + forkId: ecx.journaled_state.db().active_fork_id().unwrap_or_default(), + chainId: U256::from(ecx.cfg.chain_id), + }, + accessor: input.caller(), + account: address, + kind: crate::Vm::AccountAccessKind::Create, + initialized: true, + oldBalance: U256::ZERO, // updated on create_end + newBalance: U256::ZERO, // updated on create_end + value: input.value(), + data: input.init_code(), + reverted: false, + deployedCode: Bytes::new(), // updated on create_end + storageAccesses: vec![], // updated on create_end + depth: curr_depth as u64, + }]); + } + + None } fn create_end(&mut self, ecx: Ecx, call: &CreateInputs, outcome: &mut CreateOutcome) { - self.create_end_common(ecx, Some(call), outcome) + let call = Some(call); + let curr_depth = ecx.journaled_state.depth(); + + // Clean up pranks + if let Some(prank) = &self.get_prank(curr_depth) { + if curr_depth == prank.depth { + ecx.tx.caller = prank.prank_origin; + + // Clean single-call prank once we have returned to the original depth + if prank.single_call { + std::mem::take(&mut self.pranks); + } + } + } + + // Clean up broadcasts + if let Some(broadcast) = &self.broadcast { + if curr_depth == broadcast.depth { + ecx.tx.caller = broadcast.original_origin; + + // Clean single-call broadcast once we have returned to the original depth + if broadcast.single_call { + std::mem::take(&mut self.broadcast); + } + } + } + + // Handle expected reverts + if let Some(expected_revert) = &self.expected_revert { + if curr_depth <= expected_revert.depth && + matches!(expected_revert.kind, ExpectedRevertKind::Default) + { + let mut expected_revert = std::mem::take(&mut self.expected_revert).unwrap(); + return match revert_handlers::handle_expect_revert( + false, + true, + self.config.internal_expect_revert, + &expected_revert, + outcome.result.result, + outcome.result.output.clone(), + &self.config.available_artifacts, + ) { + Ok((address, retdata)) => { + expected_revert.actual_count += 1; + if expected_revert.actual_count < expected_revert.count { + self.expected_revert = Some(expected_revert.clone()); + } + + outcome.result.result = InstructionResult::Return; + outcome.result.output = retdata; + outcome.address = address; + } + Err(err) => { + outcome.result.result = InstructionResult::Revert; + outcome.result.output = err.abi_encode().into(); + } + }; + } + } + + // 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 { + // The root call cannot be recorded. + if curr_depth > 0 { + if let Some(last_depth) = &mut recorded_account_diffs_stack.pop() { + // Update the reverted status of all deeper calls if this call reverted, in + // accordance with EVM behavior + if outcome.result.is_revert() { + last_depth.iter_mut().for_each(|element| { + element.reverted = true; + element + .storageAccesses + .iter_mut() + .for_each(|storage_access| storage_access.reverted = true); + }) + } + + if let Some(create_access) = last_depth.first_mut() { + // Assert that we're at the correct depth before recording post-create state + // changes. Depending on what depth the cheat was called at, there + // may not be any pending calls to update if execution has + // percolated up to a higher depth. + let depth = ecx.journaled_state.depth(); + if create_access.depth == depth as u64 { + debug_assert_eq!( + create_access.kind as u8, + crate::Vm::AccountAccessKind::Create as u8 + ); + if let Some(address) = outcome.address { + if let Ok(created_acc) = ecx.journaled_state.load_account(address) { + create_access.newBalance = created_acc.info.balance; + create_access.deployedCode = created_acc + .info + .code + .clone() + .unwrap_or_default() + .original_bytes(); + } + } + } + // Merge the last depth's AccountAccesses into the AccountAccesses at the + // current depth, or push them back onto the pending + // vector if higher depths were not recorded. This + // preserves ordering of accesses. + if let Some(last) = recorded_account_diffs_stack.last_mut() { + last.append(last_depth); + } else { + recorded_account_diffs_stack.push(last_depth.clone()); + } + } + } + } + } + + // Match the create against expected_creates + if !self.expected_creates.is_empty() { + if let (Some(address), Some(call)) = (outcome.address, call) { + if let Ok(created_acc) = ecx.journaled_state.load_account(address) { + let bytecode = + created_acc.info.code.clone().unwrap_or_default().original_bytes(); + if let Some((index, _)) = + self.expected_creates.iter().find_position(|expected_create| { + expected_create.deployer == call.caller && + expected_create.create_scheme.eq(call.scheme.into()) && + expected_create.bytecode == bytecode + }) + { + self.expected_creates.swap_remove(index); + } + } + } + } } } diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index c82f9023fafd4..58d1f2f90d7b7 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -1,7 +1,7 @@ use super::Ecx; use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; -use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; +use revm::interpreter::{CreateInputs, CreateScheme}; /// Common behaviour of legacy and EOF create inputs. pub(crate) trait CommonCreateInput { @@ -13,7 +13,6 @@ pub(crate) trait CommonCreateInput { fn set_caller(&mut self, caller: Address); fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address; - fn computed_created_address(&self) -> Option
; } impl CommonCreateInput for &mut CreateInputs { @@ -54,44 +53,4 @@ impl CommonCreateInput for &mut CreateInputs { cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); created_address } - fn computed_created_address(&self) -> Option
{ - None - } -} - -impl CommonCreateInput for &mut EOFCreateInputs { - fn caller(&self) -> Address { - self.caller - } - fn gas_limit(&self) -> u64 { - self.gas_limit - } - fn value(&self) -> U256 { - self.value - } - fn init_code(&self) -> Bytes { - match &self.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { initcode, .. } => initcode.raw.clone(), - } - } - fn scheme(&self) -> Option { - None - } - fn set_caller(&mut self, caller: Address) { - self.caller = caller; - } - fn log_debug(&self, cheatcode: &mut Cheatcodes, _scheme: &CreateScheme) { - debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable eofcreate"); - } - fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: Ecx) -> Address { - let created_address = - <&mut EOFCreateInputs as CommonCreateInput>::computed_created_address(self) - .unwrap_or_default(); - cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); - created_address - } - fn computed_created_address(&self) -> Option
{ - self.kind.created_address().copied() - } } diff --git a/crates/debugger/src/op.rs b/crates/debugger/src/op.rs index 8e2edce964ae9..8abf33d6f533d 100644 --- a/crates/debugger/src/op.rs +++ b/crates/debugger/src/op.rs @@ -1,6 +1,3 @@ -use alloy_primitives::Bytes; -use revm::bytecode::opcode; - /// Named parameter of an EVM opcode. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub(crate) struct OpcodeParam { @@ -14,32 +11,8 @@ impl OpcodeParam { /// Returns the list of named parameters for the given opcode, accounts for special opcodes /// requiring immediate bytes to determine stack items. #[inline] - pub(crate) fn of(op: u8, immediate: Option<&Bytes>) -> Option> { - match op { - // Handle special cases requiring immediate bytes - opcode::DUPN => immediate - .and_then(|i| i.first().copied()) - .map(|i| vec![Self { name: "dup_value", index: i as usize }]), - opcode::SWAPN => immediate.and_then(|i| { - i.first().map(|i| { - vec![ - Self { name: "a", index: 1 }, - Self { name: "swap_value", index: *i as usize }, - ] - }) - }), - opcode::EXCHANGE => immediate.and_then(|i| { - i.first().map(|imm| { - let n = (imm >> 4) + 1; - let m = (imm & 0xf) + 1; - vec![ - Self { name: "value1", index: n as usize }, - Self { name: "value2", index: m as usize }, - ] - }) - }), - _ => Some(MAP[op as usize].to_vec()), - } + pub(crate) fn of(op: u8) -> &'static [Self] { + MAP[op as usize] } } @@ -68,16 +41,21 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { } // https://www.evm.codes - // https://github.com/smlxl/evm.codes - // https://github.com/klkvr/evm.codes - // https://github.com/klkvr/evm.codes/blob/HEAD/opcodes.json - // jq -rf opcodes.jq opcodes.json - /* - def mkargs(input): - input | split(" | ") | to_entries | map("\(.key): \"\(.value)\"") | join(", "); - - to_entries[] | "0x\(.key)(\(mkargs(.value.input)))," - */ + // https://raw.githubusercontent.com/duneanalytics/evm.codes/refs/heads/main/opcodes.json + // + // jq -r ' + // def mkargs(input): + // input + // | split(" | ") + // | to_entries + // | map("\(.key): \"\(.value)\"") + // | join(", "); + // to_entries[] + // | "0x\(.key)(\(mkargs(.value.input)))," + // ' opcodes.json + // + // NOTE: the labels generated for `DUPN` and `SWAPN` have incorrect indices and have been + // manually adjusted in the `map!` macro below. map! { 0x00(), 0x01(0: "a", 1: "b"), @@ -152,7 +130,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x46(), 0x47(), 0x48(), - 0x49(), + 0x49(0: "index"), 0x4a(), 0x4b(), 0x4c(), @@ -171,11 +149,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0x59(), 0x5a(), 0x5b(), - 0x5c(), - 0x5d(), - 0x5e(), - - // PUSHN + 0x5c(0: "key"), + 0x5d(0: "key", 1: "value"), + 0x5e(0: "destOffset", 1: "offset", 2: "size"), 0x5f(), 0x60(), 0x61(), @@ -297,7 +273,7 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xd0(0: "offset"), 0xd1(), 0xd2(), - 0xd3(0: "memOffset", 1: "offset", 2: "size"), + 0xd3(0: "mem_offset", 1: "offset", 2: "size"), 0xd4(), 0xd5(), 0xd6(), @@ -322,9 +298,9 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xe9(), 0xea(), 0xeb(), - 0xec(0: "value", 1: "salt", 2: "offset", 3: "size"), + 0xec(0: "value", 1: "salt", 2: "input_offset", 3: "input_size"), 0xed(), - 0xee(0: "offset", 1: "size"), + 0xee(0: "aux_data_offset", 1: "aux_data_size"), 0xef(), 0xf0(0: "value", 1: "offset", 2: "size"), 0xf1(0: "gas", 1: "address", 2: "value", 3: "argsOffset", 4: "argsSize", 5: "retOffset", 6: "retSize"), @@ -334,10 +310,10 @@ const fn map_opcode(op: u8) -> &'static [OpcodeParam] { 0xf5(0: "value", 1: "offset", 2: "size", 3: "salt"), 0xf6(), 0xf7(0: "offset"), - 0xf8(0: "address", 1: "argsOffset", 2: "argsSize", 3: "value"), - 0xf9(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xf8(0: "target_address", 1: "input_offset", 2: "input_size", 3: "value"), + 0xf9(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfa(0: "gas", 1: "address", 2: "argsOffset", 3: "argsSize", 4: "retOffset", 5: "retSize"), - 0xfb(0: "address", 1: "argsOffset", 2: "argsSize"), + 0xfb(0: "target_address", 1: "input_offset", 2: "input_size"), 0xfc(), 0xfd(0: "offset", 1: "size"), 0xfe(), diff --git a/crates/debugger/src/tui/context.rs b/crates/debugger/src/tui/context.rs index a2a4c987a78d6..cf4c6204b79bb 100644 --- a/crates/debugger/src/tui/context.rs +++ b/crates/debugger/src/tui/context.rs @@ -330,25 +330,11 @@ fn pretty_opcode(step: &CallTraceStep) -> String { } fn is_jump(step: &CallTraceStep, prev: &CallTraceStep) -> bool { - if !matches!( - prev.op, - OpCode::JUMP | - OpCode::JUMPI | - OpCode::JUMPF | - OpCode::RJUMP | - OpCode::RJUMPI | - OpCode::RJUMPV | - OpCode::CALLF | - OpCode::RETF - ) { + if !matches!(prev.op, OpCode::JUMP | OpCode::JUMPI) { return false } let immediate_len = prev.immediate_bytes.as_ref().map_or(0, |b| b.len()); - if step.pc != prev.pc + 1 + immediate_len { - true - } else { - step.code_section_idx != prev.code_section_idx - } + step.pc != prev.pc + 1 + immediate_len } diff --git a/crates/debugger/src/tui/draw.rs b/crates/debugger/src/tui/draw.rs index 580d6f958ac44..00f51c4c5654c 100644 --- a/crates/debugger/src/tui/draw.rs +++ b/crates/debugger/src/tui/draw.rs @@ -375,11 +375,10 @@ impl TUIContext<'_> { .collect::>(); let title = format!( - "Address: {} | PC: {} | Gas used in call: {} | Code section: {}", + "Address: {} | PC: {} | Gas used in call: {}", self.address(), self.current_step().pc, self.current_step().gas_used, - self.current_step().code_section_idx, ); let block = Block::default().title(title).borders(Borders::ALL); let list = List::new(items) @@ -398,7 +397,7 @@ impl TUIContext<'_> { let min_len = decimal_digits(stack_len).max(2); - let params = OpcodeParam::of(step.op.get(), step.immediate_bytes.as_ref()); + let params = OpcodeParam::of(step.op.get()); let text: Vec> = stack .map(|stack| { @@ -408,10 +407,7 @@ impl TUIContext<'_> { .enumerate() .skip(self.draw_memory.current_stack_startline) .map(|(i, stack_item)| { - let param = params - .as_ref() - .and_then(|params| params.iter().find(|param| param.index == i)); - + let param = params.iter().find(|param| param.index == i); let mut spans = Vec::with_capacity(1 + 32 * 2 + 3); // Stack index. diff --git a/crates/evm/core/src/buffer.rs b/crates/evm/core/src/buffer.rs index 5cce0a91ad97b..e600708f43173 100644 --- a/crates/evm/core/src/buffer.rs +++ b/crates/evm/core/src/buffer.rs @@ -75,13 +75,6 @@ pub fn get_buffer_accesses(op: u8, stack: &[U256]) -> Option { opcode::CALL | opcode::CALLCODE => (Some((BufferKind::Memory, 4, 5)), None), opcode::DELEGATECALL | opcode::STATICCALL => (Some((BufferKind::Memory, 3, 4)), None), opcode::MCOPY => (Some((BufferKind::Memory, 2, 3)), Some((1, 3))), - opcode::RETURNDATALOAD => (Some((BufferKind::Returndata, 1, -1)), None), - opcode::EOFCREATE => (Some((BufferKind::Memory, 3, 4)), None), - opcode::RETURNCONTRACT => (Some((BufferKind::Memory, 1, 2)), None), - opcode::DATACOPY => (None, Some((1, 3))), - opcode::EXTCALL | opcode::EXTSTATICCALL | opcode::EXTDELEGATECALL => { - (Some((BufferKind::Memory, 2, 3)), None) - } _ => Default::default(), }; diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index 520de5f45e2aa..f3c0fa3d13f4b 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -22,8 +22,8 @@ use revm::{ }, context_interface::CreateScheme, interpreter::{ - CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, EOFCreateInputs, - EOFCreateKind, Gas, InstructionResult, Interpreter, InterpreterResult, + CallInputs, CallOutcome, CallScheme, CreateInputs, CreateOutcome, Gas, InstructionResult, + Interpreter, InterpreterResult, }, state::{Account, AccountStatus}, Inspector, @@ -578,32 +578,6 @@ impl InspectorStackRefMut<'_> { outcome.clone() } - fn do_eofcreate_end( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: &mut CreateOutcome, - ) -> CreateOutcome { - let result = outcome.result.result; - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.cheatcodes, &mut self.printer], - |inspector| { - let previous_outcome = outcome.clone(); - inspector.eofcreate_end(ecx, call, outcome); - - // If the inspector returns a different status or a revert with a non-empty message, - // we assume it wants to tell us something - let different = outcome.result.result != result || - (outcome.result.result == InstructionResult::Revert && - outcome.output() != previous_outcome.output()); - different.then_some(outcome.clone()) - }, - ); - - outcome.clone() - } - fn transact_inner( &mut self, ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, @@ -1029,77 +1003,6 @@ impl Inspector> for InspectorStackRefMut<'_> self.top_level_frame_end(ecx, outcome.result.result); } } - - fn eofcreate( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - if self.in_inner_context && ecx.journaled_state.depth == 1 { - self.adjust_evm_data_for_inner_context(ecx); - return None; - } - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_start(ecx); - } - - call_inspectors!( - #[ret] - [&mut self.tracer, &mut self.coverage, &mut self.cheatcodes], - |inspector| inspector.eofcreate(ecx, create).map(Some), - ); - - if matches!(create.kind, EOFCreateKind::Tx { .. }) && - self.enable_isolation && - !self.in_inner_context && - ecx.journaled_state.depth == 1 - { - let init_code = match &mut create.kind { - EOFCreateKind::Tx { initdata } => initdata.clone(), - EOFCreateKind::Opcode { .. } => unreachable!(), - }; - - let (result, address) = self.transact_inner( - ecx, - TxKind::Create, - create.caller, - init_code, - create.gas_limit, - create.value, - ); - return Some(CreateOutcome { result, address }); - } - - None - } - - fn eofcreate_end( - &mut self, - ecx: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: &mut CreateOutcome, - ) { - // We are processing inner context outputs in the outer context, so need to avoid processing - // twice. - if self.in_inner_context && ecx.journaled_state.depth == 1 { - return; - } - - self.do_eofcreate_end(ecx, call, outcome); - - if ecx.journaled_state.depth == 0 { - self.top_level_frame_end(ecx, outcome.result.result); - } - } - - fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { - Inspector::>::selfdestruct( - inspector, contract, target, value, - ) - }); - } } impl InspectorExt for InspectorStackRefMut<'_> { @@ -1185,23 +1088,6 @@ impl Inspector> for InspectorStack { self.as_mut().create_end(context, call, outcome) } - fn eofcreate( - &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, - create: &mut EOFCreateInputs, - ) -> Option { - self.as_mut().eofcreate(context, create) - } - - fn eofcreate_end( - &mut self, - context: &mut EthEvmContext<&mut dyn DatabaseExt>, - call: &EOFCreateInputs, - outcome: &mut CreateOutcome, - ) { - self.as_mut().eofcreate_end(context, call, outcome) - } - fn initialize_interp( &mut self, interpreter: &mut Interpreter,