From b4709253670c246381beef61b79875d3ec32ba1d Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 1 Dec 2021 21:29:18 -0800 Subject: [PATCH 01/34] first pass --- evm-adapters/src/call_tracing.rs | 90 +++++++++++++++++++ evm-adapters/src/lib.rs | 2 + .../sputnik/cheatcodes/cheatcode_handler.rs | 79 +++++++++++++++- .../cheatcodes/memory_stackstate_owned.rs | 6 +- evm-adapters/src/sputnik/mod.rs | 10 +++ evm-adapters/testdata/Trace.sol | 23 +++++ 6 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 evm-adapters/src/call_tracing.rs create mode 100644 evm-adapters/testdata/Trace.sol diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs new file mode 100644 index 0000000000000..2e62a0a9f77ad --- /dev/null +++ b/evm-adapters/src/call_tracing.rs @@ -0,0 +1,90 @@ +use ethers::abi::FunctionExt; +use std::collections::BTreeMap; +use ethers::{ + abi::{Abi, RawLog}, + types::{Address, H160}, +}; + +/// Call trace of a tx +#[derive(Clone, Default, Debug)] +pub struct CallTrace { + pub depth: usize, + pub location: usize, + /// Successful + pub success: bool, + /// Callee + pub addr: H160, + /// Creation + pub created: bool, + /// Call data, including function selector (if applicable) + pub data: Vec, + /// Gas cost + pub cost: u64, + /// Output + pub output: Vec, + /// Logs + pub logs: Vec, + /// inner calls + pub inner: Vec, +} + +impl CallTrace { + pub fn add_trace(&mut self, new_trace: Self) { + if new_trace.depth == 0 { + // overwrite + self.update(new_trace); + } else if self.depth == new_trace.depth - 1 { + self.inner.push(new_trace); + } else { + self.inner.last_mut().expect("Disconnected trace").add_trace(new_trace); + } + } + + fn update(&mut self, new_trace: Self) { + self.success = new_trace.success; + self.addr = new_trace.addr; + self.cost = new_trace.cost; + self.output = new_trace.output; + self.logs = new_trace.logs; + self.data = new_trace.data; + self.addr = new_trace.addr; + // we dont update inner because the temporary new_trace doesnt track inner calls + } + + pub fn update_trace(&mut self, new_trace: Self) { + if new_trace.depth == 0 { + // overwrite + self.update(new_trace); + } else if self.depth == new_trace.depth - 1 { + self.inner[new_trace.location].update(new_trace); + } else { + self.inner.last_mut().expect("Disconnected trace update").update_trace(new_trace); + } + } + + pub fn location(&self, new_trace: &Self) -> usize { + if new_trace.depth == 0 { + 0 + } else if self.depth == new_trace.depth - 1 { + self.inner.len() + } else { + self.inner.last().expect("Disconnected trace location").location(new_trace) + } + } + + pub fn pretty_print(&self, contracts: &BTreeMap)>) { + if let Some((name, (abi, _addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| { + addr == &self.addr + }) { + for (func_name, overloaded_funcs) in abi.functions.iter() { + for func in overloaded_funcs.iter() { + if func.selector() == self.data[0..4] { + println!("{}.{}({:?})", name, func_name, func.decode_input(&self.data[4..]).unwrap()); + } + } + } + self.inner.iter().for_each(|inner| inner.pretty_print(contracts)); + } + + } +} \ No newline at end of file diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 3e05a95c760a2..a833556f972e8 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -12,6 +12,8 @@ pub use blocking_provider::BlockingProvider; pub mod fuzz; +pub mod call_tracing; + use ethers::{ abi::{Detokenize, Tokenize}, contract::{decode_function_data, encode_function_data}, diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index adbab59e401bf..983f987ff651f 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -6,6 +6,7 @@ use super::{ use crate::{ sputnik::{Executor, SputnikExecutor}, Evm, + call_tracing::CallTrace, }; use sputnik::{ @@ -163,6 +164,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Vec { + let logs = self.state().substate.logs().to_vec(); + logs.into_iter() + .map(|log| RawLog { topics: log.topics, data: log.data }) + .collect() + } + fn logs(&self) -> Vec { let logs = self.state().substate.logs().to_vec(); logs.into_iter() @@ -428,6 +436,32 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } + fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> CallTrace { + let mut trace: CallTrace = Default::default(); + trace.depth = self.state().metadata().depth().unwrap_or(0); + trace.addr = address; + trace.created = creation; + trace.data = input; + trace.location = self.state_mut().trace.location(&trace); + // we should probably delay having the input and other stuff so + // we minimize the size of the clone + self.state_mut().trace.add_trace(trace.clone()); + trace + } + + fn fill_trace( + &mut self, + mut new_trace: CallTrace, + success: bool, + output: Option>, + ) { + new_trace.logs = self.raw_logs(); + new_trace.output = output.unwrap_or(vec![]); + new_trace.cost = self.handler.used_gas(); + new_trace.success = success; + self.state_mut().trace.update_trace(new_trace); + } + // NB: This function is copy-pasted from uptream's call_inner #[allow(clippy::too_many_arguments)] fn call_inner( @@ -441,11 +475,19 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> take_stipend: bool, context: Context, ) -> Capture<(ExitReason, Vec), Infallible> { + let trace = self.start_trace(code_address, input.clone(), false); + macro_rules! try_or_fail { ( $e:expr ) => { match $e { Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), Vec::new())), + Err(e) => { + // if let Some(call_trace) = trace { + // self.state_mut().trace.success = false; + // } + self.fill_trace(trace, false, None); + return Capture::Exit((e.into(), Vec::new())) + }, } }; } @@ -485,6 +527,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())) } @@ -494,6 +537,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().transfer(transfer) { Ok(()) => (), Err(e) => { + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitReason::Error(e), Vec::new())) } @@ -517,6 +561,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } let _ = self.state_mut().metadata_mut().gasometer_mut().record_cost(cost); + self.fill_trace(trace, true, Some(output.clone())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(exit_status), output)) } @@ -528,6 +573,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } PrecompileFailure::Fatal { exit_status } => ExitReason::Fatal(exit_status), }; + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((e, Vec::new())) } @@ -543,18 +589,22 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // reason); match reason { ExitReason::Succeed(s) => { + self.fill_trace(trace, true, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) } ExitReason::Error(e) => { + self.fill_trace(trace, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Error(e), Vec::new())) } ExitReason::Revert(e) => { + self.fill_trace(trace, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Reverted); Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) } ExitReason::Fatal(e) => { + self.fill_trace(trace, false, Some(runtime.machine().return_value())); self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Fatal(e), Vec::new())) @@ -1048,4 +1098,31 @@ mod tests { }; assert_eq!(reason, "ffi disabled: run again with --ffi if you want to allow tests to call external scripts"); } + + #[test] + fn tracing() { + use std::collections::BTreeMap; + + let config = Config::istanbul(); + let vicinity = new_vicinity(); + let backend = new_backend(&vicinity, Default::default()); + let gas_limit = 10_000_000; + let precompiles = PRECOMPILES_MAP.clone(); + let mut evm = + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false); + + let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); + let (addr, _, _, _) = + evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); + + // after the evm call is done, we call `logs` and print it all to the user + let (_, _, _, logs) = evm + .call::<(), _, _>(Address::zero(), addr, "recurseCall(uint256,uint256)", (U256::from(3u32), U256::from(0u32)), 0u32.into()) + .unwrap(); + + let mut mapping = BTreeMap::new(); + mapping.insert("RecursiveCall".to_string(), (compiled.abi.expect("No abi").clone(), addr, vec![])); + evm.state().trace.pretty_print(&mapping); + + } } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index c285d43e62cf8..1a71fb3bc61ae 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -4,6 +4,8 @@ use sputnik::{ ExitError, Transfer, }; +use crate::call_tracing::CallTrace; + use ethers::types::{H160, H256, U256}; /// This struct implementation is copied from [upstream](https://github.com/rust-blockchain/evm/blob/5ecf36ce393380a89c6f1b09ef79f686fe043624/src/executor/stack/state.rs#L412) and modified to own the Backend type. @@ -15,6 +17,8 @@ use ethers::types::{H160, H256, U256}; pub struct MemoryStackStateOwned<'config, B> { pub backend: B, pub substate: MemoryStackSubstate<'config>, + pub trace: CallTrace, + pub current_trace: CallTrace, } impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { @@ -25,7 +29,7 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { - Self { backend, substate: MemoryStackSubstate::new(metadata) } + Self { backend, substate: MemoryStackSubstate::new(metadata), trace: Default::default(), current_trace: Default::default() } } } diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 6f573a4b46f80..86b1ebcc94451 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -8,6 +8,7 @@ pub mod cheatcodes; pub mod state; use ethers::{ + abi::RawLog, providers::Middleware, types::{Address, H160, H256, U256}, }; @@ -81,6 +82,10 @@ pub trait SputnikExecutor { fn create_address(&self, caller: CreateScheme) -> Address; + /// Returns a vector of sraw logs that occurred during the previous VM + /// execution + fn raw_logs(&self) -> Vec; + /// Returns a vector of string parsed logs that occurred during the previous VM /// execution fn logs(&self) -> Vec; @@ -142,6 +147,11 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor fn logs(&self) -> Vec { vec![] } + + fn raw_logs(&self) -> Vec { + vec![] + } + fn clear_logs(&mut self) {} } diff --git a/evm-adapters/testdata/Trace.sol b/evm-adapters/testdata/Trace.sol new file mode 100644 index 0000000000000..79446ce1a71bd --- /dev/null +++ b/evm-adapters/testdata/Trace.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.8.0; + + +interface RecursiveCallee { + function recurseCall(uint256 neededDepth, uint256 depth) external returns (uint256); + function someCall() external; +} + +contract RecursiveCall { + event Depth(uint256 depth); + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + RecursiveCallee(address(this)).recurseCall(neededDepth, depth + 1); + RecursiveCallee(address(this)).someCall(); + emit Depth(depth); + return depth; + } + + function someCall() public { + } +} \ No newline at end of file From bfa42a818e828878a1d7a39e105387ebc78ba875 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 1 Dec 2021 22:56:14 -0800 Subject: [PATCH 02/34] fixes --- evm-adapters/src/call_tracing.rs | 47 +++++++++++++++++-- .../sputnik/cheatcodes/cheatcode_handler.rs | 18 +++++-- 2 files changed, 55 insertions(+), 10 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 2e62a0a9f77ad..f7da85dd374b1 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -2,7 +2,7 @@ use ethers::abi::FunctionExt; use std::collections::BTreeMap; use ethers::{ abi::{Abi, RawLog}, - types::{Address, H160}, + types::{Address, H160, H256}, }; /// Call trace of a tx @@ -32,7 +32,7 @@ impl CallTrace { pub fn add_trace(&mut self, new_trace: Self) { if new_trace.depth == 0 { // overwrite - self.update(new_trace); + // self.update(new_trace); } else if self.depth == new_trace.depth - 1 { self.inner.push(new_trace); } else { @@ -53,7 +53,6 @@ impl CallTrace { pub fn update_trace(&mut self, new_trace: Self) { if new_trace.depth == 0 { - // overwrite self.update(new_trace); } else if self.depth == new_trace.depth - 1 { self.inner[new_trace.location].update(new_trace); @@ -72,19 +71,57 @@ impl CallTrace { } } + pub fn inner_number_of_logs(&self) -> usize { + // only count child logs + let mut total = 0; + if self.inner.len() > 0 { + self.inner.iter().for_each(|inner| { + total += inner.inner_number_of_logs(); + }); + } + total += self.logs.len(); + total + } + + pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { + if self.depth == depth && self.location == location { + return Some(&self) + } else { + if self.depth != depth { + for inner in self.inner.iter() { + if let Some(trace) = inner.get_trace(depth, location) { + return Some(trace); + } + } + } + } + return None; + } + pub fn pretty_print(&self, contracts: &BTreeMap)>) { if let Some((name, (abi, _addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| { addr == &self.addr }) { + let indent = "\t".repeat(self.depth); for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == self.data[0..4] { - println!("{}.{}({:?})", name, func_name, func.decode_input(&self.data[4..]).unwrap()); + println!("{}{}.{}({:?})", indent, name, func_name, func.decode_input(&self.data[4..]).unwrap()); } } } + self.inner.iter().for_each(|inner| inner.pretty_print(contracts)); + + self.logs.iter().for_each(|log| { + for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + println!("{}emit {}({:?})", indent, event_name, event.parse_log(log.clone()).unwrap()); + } + } + } + }); } - } } \ No newline at end of file diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 983f987ff651f..dc7124387b4c6 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -438,7 +438,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> CallTrace { let mut trace: CallTrace = Default::default(); - trace.depth = self.state().metadata().depth().unwrap_or(0); + // depth only starts tracking at first child substate and is 0. so add 1 when depth is some. + trace.depth = if let Some(depth) = self.state().metadata().depth() { + depth + 1 + } else { + 0 + }; trace.addr = address; trace.created = creation; trace.data = input; @@ -455,7 +460,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> success: bool, output: Option>, ) { - new_trace.logs = self.raw_logs(); + if let Some(trace) = self.state().trace.get_trace(new_trace.depth, new_trace.location) { + let num = trace.inner_number_of_logs(); + new_trace.logs = self.raw_logs()[num..].to_vec(); + } else { + new_trace.logs = self.raw_logs().to_vec(); + } + new_trace.output = output.unwrap_or(vec![]); new_trace.cost = self.handler.used_gas(); new_trace.success = success; @@ -482,9 +493,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - // if let Some(call_trace) = trace { - // self.state_mut().trace.success = false; - // } self.fill_trace(trace, false, None); return Capture::Exit((e.into(), Vec::new())) }, From 7106cd79feca1c436f8504c17db3865ba37b22f5 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 1 Dec 2021 23:02:32 -0800 Subject: [PATCH 03/34] fmt --- evm-adapters/src/call_tracing.rs | 200 +++++++++--------- .../sputnik/cheatcodes/cheatcode_handler.rs | 39 ++-- .../cheatcodes/memory_stackstate_owned.rs | 7 +- evm-adapters/src/sputnik/mod.rs | 2 +- 4 files changed, 130 insertions(+), 118 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index f7da85dd374b1..92855e1957778 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -1,15 +1,14 @@ -use ethers::abi::FunctionExt; -use std::collections::BTreeMap; use ethers::{ - abi::{Abi, RawLog}, - types::{Address, H160, H256}, + abi::{Abi, FunctionExt, RawLog}, + types::{Address, H160, H256}, }; +use std::collections::BTreeMap; /// Call trace of a tx #[derive(Clone, Default, Debug)] pub struct CallTrace { - pub depth: usize, - pub location: usize, + pub depth: usize, + pub location: usize, /// Successful pub success: bool, /// Callee @@ -29,99 +28,110 @@ pub struct CallTrace { } impl CallTrace { - pub fn add_trace(&mut self, new_trace: Self) { - if new_trace.depth == 0 { - // overwrite - // self.update(new_trace); - } else if self.depth == new_trace.depth - 1 { - self.inner.push(new_trace); - } else { - self.inner.last_mut().expect("Disconnected trace").add_trace(new_trace); - } - } + pub fn add_trace(&mut self, new_trace: Self) { + if new_trace.depth == 0 { + // overwrite + // self.update(new_trace); + } else if self.depth == new_trace.depth - 1 { + self.inner.push(new_trace); + } else { + self.inner.last_mut().expect("Disconnected trace").add_trace(new_trace); + } + } - fn update(&mut self, new_trace: Self) { - self.success = new_trace.success; - self.addr = new_trace.addr; - self.cost = new_trace.cost; - self.output = new_trace.output; - self.logs = new_trace.logs; - self.data = new_trace.data; - self.addr = new_trace.addr; - // we dont update inner because the temporary new_trace doesnt track inner calls - } + fn update(&mut self, new_trace: Self) { + self.success = new_trace.success; + self.addr = new_trace.addr; + self.cost = new_trace.cost; + self.output = new_trace.output; + self.logs = new_trace.logs; + self.data = new_trace.data; + self.addr = new_trace.addr; + // we dont update inner because the temporary new_trace doesnt track inner calls + } - pub fn update_trace(&mut self, new_trace: Self) { - if new_trace.depth == 0 { - self.update(new_trace); - } else if self.depth == new_trace.depth - 1 { - self.inner[new_trace.location].update(new_trace); - } else { - self.inner.last_mut().expect("Disconnected trace update").update_trace(new_trace); - } - } + pub fn update_trace(&mut self, new_trace: Self) { + if new_trace.depth == 0 { + self.update(new_trace); + } else if self.depth == new_trace.depth - 1 { + self.inner[new_trace.location].update(new_trace); + } else { + self.inner.last_mut().expect("Disconnected trace update").update_trace(new_trace); + } + } - pub fn location(&self, new_trace: &Self) -> usize { - if new_trace.depth == 0 { - 0 - } else if self.depth == new_trace.depth - 1 { - self.inner.len() - } else { - self.inner.last().expect("Disconnected trace location").location(new_trace) - } - } + pub fn location(&self, new_trace: &Self) -> usize { + if new_trace.depth == 0 { + 0 + } else if self.depth == new_trace.depth - 1 { + self.inner.len() + } else { + self.inner.last().expect("Disconnected trace location").location(new_trace) + } + } - pub fn inner_number_of_logs(&self) -> usize { - // only count child logs - let mut total = 0; - if self.inner.len() > 0 { - self.inner.iter().for_each(|inner| { - total += inner.inner_number_of_logs(); - }); - } - total += self.logs.len(); - total - } + pub fn inner_number_of_logs(&self) -> usize { + // only count child logs + let mut total = 0; + if self.inner.len() > 0 { + self.inner.iter().for_each(|inner| { + total += inner.inner_number_of_logs(); + }); + } + total += self.logs.len(); + total + } - pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { - if self.depth == depth && self.location == location { - return Some(&self) - } else { - if self.depth != depth { - for inner in self.inner.iter() { - if let Some(trace) = inner.get_trace(depth, location) { - return Some(trace); - } - } - } - } - return None; - } + pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { + if self.depth == depth && self.location == location { + return Some(&self) + } else { + if self.depth != depth { + for inner in self.inner.iter() { + if let Some(trace) = inner.get_trace(depth, location) { + return Some(trace) + } + } + } + } + return None + } - pub fn pretty_print(&self, contracts: &BTreeMap)>) { - if let Some((name, (abi, _addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| { - addr == &self.addr - }) { - let indent = "\t".repeat(self.depth); - for (func_name, overloaded_funcs) in abi.functions.iter() { - for func in overloaded_funcs.iter() { - if func.selector() == self.data[0..4] { - println!("{}{}.{}({:?})", indent, name, func_name, func.decode_input(&self.data[4..]).unwrap()); - } - } - } - - self.inner.iter().for_each(|inner| inner.pretty_print(contracts)); + pub fn pretty_print(&self, contracts: &BTreeMap)>) { + if let Some((name, (abi, _addr, _other))) = + contracts.iter().find(|(_key, (_abi, addr, _other))| addr == &self.addr) + { + let indent = "\t".repeat(self.depth); + for (func_name, overloaded_funcs) in abi.functions.iter() { + for func in overloaded_funcs.iter() { + if func.selector() == self.data[0..4] { + println!( + "{}{}.{}({:?})", + indent, + name, + func_name, + func.decode_input(&self.data[4..]).unwrap() + ); + } + } + } - self.logs.iter().for_each(|log| { - for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - println!("{}emit {}({:?})", indent, event_name, event.parse_log(log.clone()).unwrap()); - } - } - } - }); - } - } -} \ No newline at end of file + self.inner.iter().for_each(|inner| inner.pretty_print(contracts)); + + self.logs.iter().for_each(|log| { + for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + println!( + "{}emit {}({:?})", + indent, + event_name, + event.parse_log(log.clone()).unwrap() + ); + } + } + } + }); + } + } +} diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index dc7124387b4c6..4df36892ce2a0 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -4,9 +4,9 @@ use super::{ HEVMCalls, HevmConsoleEvents, }; use crate::{ + call_tracing::CallTrace, sputnik::{Executor, SputnikExecutor}, Evm, - call_tracing::CallTrace, }; use sputnik::{ @@ -166,9 +166,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Vec { let logs = self.state().substate.logs().to_vec(); - logs.into_iter() - .map(|log| RawLog { topics: log.topics, data: log.data }) - .collect() + logs.into_iter().map(|log| RawLog { topics: log.topics, data: log.data }).collect() } fn logs(&self) -> Vec { @@ -439,11 +437,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> CallTrace { let mut trace: CallTrace = Default::default(); // depth only starts tracking at first child substate and is 0. so add 1 when depth is some. - trace.depth = if let Some(depth) = self.state().metadata().depth() { - depth + 1 - } else { - 0 - }; + trace.depth = if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; trace.addr = address; trace.created = creation; trace.data = input; @@ -454,19 +448,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> trace } - fn fill_trace( - &mut self, - mut new_trace: CallTrace, - success: bool, - output: Option>, - ) { + fn fill_trace(&mut self, mut new_trace: CallTrace, success: bool, output: Option>) { if let Some(trace) = self.state().trace.get_trace(new_trace.depth, new_trace.location) { let num = trace.inner_number_of_logs(); new_trace.logs = self.raw_logs()[num..].to_vec(); } else { new_trace.logs = self.raw_logs().to_vec(); } - + new_trace.output = output.unwrap_or(vec![]); new_trace.cost = self.handler.used_gas(); new_trace.success = success; @@ -495,7 +484,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Err(e) => { self.fill_trace(trace, false, None); return Capture::Exit((e.into(), Vec::new())) - }, + } } }; } @@ -1120,17 +1109,25 @@ mod tests { Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); - let (addr, _, _, _) = + let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); // after the evm call is done, we call `logs` and print it all to the user let (_, _, _, logs) = evm - .call::<(), _, _>(Address::zero(), addr, "recurseCall(uint256,uint256)", (U256::from(3u32), U256::from(0u32)), 0u32.into()) + .call::<(), _, _>( + Address::zero(), + addr, + "recurseCall(uint256,uint256)", + (U256::from(3u32), U256::from(0u32)), + 0u32.into(), + ) .unwrap(); let mut mapping = BTreeMap::new(); - mapping.insert("RecursiveCall".to_string(), (compiled.abi.expect("No abi").clone(), addr, vec![])); + mapping.insert( + "RecursiveCall".to_string(), + (compiled.abi.expect("No abi").clone(), addr, vec![]), + ); evm.state().trace.pretty_print(&mapping); - } } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 1a71fb3bc61ae..43e0a17a0d039 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -29,7 +29,12 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { - Self { backend, substate: MemoryStackSubstate::new(metadata), trace: Default::default(), current_trace: Default::default() } + Self { + backend, + substate: MemoryStackSubstate::new(metadata), + trace: Default::default(), + current_trace: Default::default(), + } } } diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 86b1ebcc94451..d4ffd2d68cca5 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -151,7 +151,7 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor fn raw_logs(&self) -> Vec { vec![] } - + fn clear_logs(&mut self) {} } From f83b2267f016fad17509cfa6066b3ed3ce230711 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 1 Dec 2021 23:32:25 -0800 Subject: [PATCH 04/34] better fmting --- evm-adapters/src/call_tracing.rs | 39 ++++++++++++++++--- .../sputnik/cheatcodes/cheatcode_handler.rs | 2 +- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 92855e1957778..6ed4cc86c08c1 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -82,6 +82,18 @@ impl CallTrace { total } + pub fn inner_number_of_inners(&self) -> usize { + // only count child logs + let mut total = 0; + if self.inner.len() > 0 { + self.inner.iter().for_each(|inner| { + total += inner.inner_number_of_inners(); + }); + } + total += self.inner.len(); + total + } + pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { if self.depth == depth && self.location == location { return Some(&self) @@ -97,17 +109,21 @@ impl CallTrace { return None } - pub fn pretty_print(&self, contracts: &BTreeMap)>) { + pub fn pretty_print( + &self, + contracts: &BTreeMap)>, + left: String, + ) { if let Some((name, (abi, _addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| addr == &self.addr) { - let indent = "\t".repeat(self.depth); + // let indent = "\t".repeat(self.depth); for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == self.data[0..4] { println!( "{}{}.{}({:?})", - indent, + left, name, func_name, func.decode_input(&self.data[4..]).unwrap() @@ -116,15 +132,26 @@ impl CallTrace { } } - self.inner.iter().for_each(|inner| inner.pretty_print(contracts)); + self.inner.iter().enumerate().for_each(|(i, inner)| { + // let inners = inner.inner_number_of_inners(); + if i == self.inner.len() - 1 && self.logs.len() == 0 { + inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "└─ "); + } else { + inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "├─ "); + } + }); - self.logs.iter().for_each(|log| { + self.logs.iter().enumerate().for_each(|(i, log)| { for (event_name, overloaded_events) in abi.events.iter() { for event in overloaded_events.iter() { if event.signature() == log.topics[0] { + let mut right = "├─ "; + if i == self.logs.len() - 1 { + right = "└─ "; + } println!( "{}emit {}({:?})", - indent, + left.to_string().replace("├─ ", "| ") + right, event_name, event.parse_log(log.clone()).unwrap() ); diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 4df36892ce2a0..3479b552a528e 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -1128,6 +1128,6 @@ mod tests { "RecursiveCall".to_string(), (compiled.abi.expect("No abi").clone(), addr, vec![]), ); - evm.state().trace.pretty_print(&mapping); + evm.state().trace.pretty_print(&mapping, "".to_string()); } } From bf17ebb0107e99e9e7c5a794c1f480ead4400f27 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 2 Dec 2021 00:57:31 -0800 Subject: [PATCH 05/34] updates colored prints, better dev ux, verbosity > 2 trace printing --- Cargo.lock | 2 + cli/src/forge.rs | 8 +++ evm-adapters/Cargo.toml | 2 + evm-adapters/src/call_tracing.rs | 61 +++++++++++++++---- evm-adapters/src/lib.rs | 5 ++ .../sputnik/cheatcodes/cheatcode_handler.rs | 14 ++++- evm-adapters/src/sputnik/evm.rs | 7 ++- evm-adapters/src/sputnik/mod.rs | 7 ++- forge/src/multi_runner.rs | 2 +- forge/src/runner.rs | 10 ++- 10 files changed, 97 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5da58013619f7..22ebf590494bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1216,6 +1216,7 @@ dependencies = [ name = "evm-adapters" version = "0.1.0" dependencies = [ + "ansi_term 0.12.1", "bytes", "ethers", "evm", @@ -1228,6 +1229,7 @@ dependencies = [ "parking_lot", "proptest", "revm_precompiles", + "serde", "serde_json", "thiserror", "tokio", diff --git a/cli/src/forge.rs b/cli/src/forge.rs index f0e403fdbebdf..440143ce5b65b 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -319,8 +319,16 @@ fn test>( } println!(); + if verbosity > 2 { + println!("{:?}", runner.contracts); + if let Some(trace) = &result.trace { + trace.pretty_print(&runner.contracts, "".to_string()); + } + println!(); + } } } + } } diff --git a/evm-adapters/Cargo.toml b/evm-adapters/Cargo.toml index 0bcf0612f0a09..cdb7641ef0fb8 100644 --- a/evm-adapters/Cargo.toml +++ b/evm-adapters/Cargo.toml @@ -26,6 +26,8 @@ parking_lot = "0.11.2" futures = "0.3.17" revm_precompiles = "0.1.0" serde_json = "1.0.72" +serde = "1.0.130" +ansi_term = "0.12.1" [dev-dependencies] evmodin = { git = "https://github.com/vorot93/evmodin", features = ["util"] } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 6ed4cc86c08c1..5a2744de4c6fe 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -1,11 +1,14 @@ use ethers::{ abi::{Abi, FunctionExt, RawLog}, - types::{Address, H160, H256}, + types::{Address, H160}, }; +use serde::{Serialize, Deserialize}; use std::collections::BTreeMap; +use ansi_term::Colour; + /// Call trace of a tx -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, Deserialize, Serialize)] pub struct CallTrace { pub depth: usize, pub location: usize, @@ -22,6 +25,7 @@ pub struct CallTrace { /// Output pub output: Vec, /// Logs + #[serde(skip)] pub logs: Vec, /// inner calls pub inner: Vec, @@ -114,18 +118,20 @@ impl CallTrace { contracts: &BTreeMap)>, left: String, ) { - if let Some((name, (abi, _addr, _other))) = + if let Some((name, (abi, addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| addr == &self.addr) { + let color = if self.success { Colour::Green } else { Colour::Red }; // let indent = "\t".repeat(self.depth); for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == self.data[0..4] { println!( - "{}{}.{}({:?})", + "{}[{}] {}::{}({:?})", left, - name, - func_name, + self.cost, + color.paint(name), + color.paint(func_name), func.decode_input(&self.data[4..]).unwrap() ); } @@ -143,22 +149,51 @@ impl CallTrace { self.logs.iter().enumerate().for_each(|(i, log)| { for (event_name, overloaded_events) in abi.events.iter() { + let mut found = false; + let mut right = "├─ "; + if i == self.logs.len() - 1 { + right = "└─ "; + } for event in overloaded_events.iter() { if event.signature() == log.topics[0] { - let mut right = "├─ "; - if i == self.logs.len() - 1 { - right = "└─ "; - } + found = true; println!( - "{}emit {}({:?})", + "{}emit {}({})", left.to_string().replace("├─ ", "| ") + right, - event_name, - event.parse_log(log.clone()).unwrap() + Colour::Cyan.paint(event_name), + Colour::Cyan.paint(format!("{:?}", event.parse_log(log.clone()).unwrap())) ); } } + if !found { + println!("{}emit {}", left.to_string().replace("├─ ", "| ") + right, Colour::Blue.paint(format!("{:?}", log))) + } + } + }); + } else { + if self.data.len() >= 4 { + println!("{}{:x}::{}({})", left, self.addr, hex::encode(&self.data[0..4]), hex::encode(&self.data[4..])); + } else { + println!("{}{:x}::({})", left, self.addr, hex::encode(&self.data)); + } + + self.inner.iter().enumerate().for_each(|(i, inner)| { + // let inners = inner.inner_number_of_inners(); + if i == self.inner.len() - 1 && self.logs.len() == 0 { + inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "└─ "); + } else { + inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "├─ "); } }); + + let mut right = "├─ "; + + self.logs.iter().enumerate().for_each(|(i, log)| { + if i == self.logs.len() - 1 { + right = "└─ "; + } + println!("{}emit {}", left.to_string().replace("├─ ", "| ") + right, Colour::Cyan.paint(format!("{:?}", log))) + }); } } } diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index a833556f972e8..c4024a125659c 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -8,6 +8,9 @@ pub mod sputnik; pub mod evmodin; mod blocking_provider; +use crate::call_tracing::CallTrace; +use std::collections::BTreeMap; +use ethers::abi::Abi; pub use blocking_provider::BlockingProvider; pub mod fuzz; @@ -92,6 +95,8 @@ pub trait Evm { } } + fn trace(&self) -> Option { None } + /// Executes the specified EVM call against the state // TODO: Should we just make this take a `TransactionRequest` or other more // ergonomic type? diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 3479b552a528e..e6b5d1fc35c21 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -21,13 +21,14 @@ use sputnik::{ use std::{process::Command, rc::Rc}; use ethers::{ - abi::{RawLog, Token}, + abi::{Abi, RawLog, Token}, contract::EthLogDecode, core::{abi::AbiDecode, k256::ecdsa::SigningKey, utils}, signers::{LocalWallet, Signer}, types::{Address, H160, H256, U256}, }; use std::convert::Infallible; +use std::collections::BTreeMap; use once_cell::sync::Lazy; @@ -169,6 +170,10 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Option { + Some(self.state().trace.clone()) + } + fn logs(&self) -> Vec { let logs = self.state().substate.logs().to_vec(); logs.into_iter() @@ -451,7 +456,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn fill_trace(&mut self, mut new_trace: CallTrace, success: bool, output: Option>) { if let Some(trace) = self.state().trace.get_trace(new_trace.depth, new_trace.location) { let num = trace.inner_number_of_logs(); - new_trace.logs = self.raw_logs()[num..].to_vec(); + if self.raw_logs().len() > num { + new_trace.logs = self.raw_logs()[num..].to_vec(); + } else { + new_trace.logs = self.raw_logs(); + } + } else { new_trace.logs = self.raw_logs().to_vec(); } diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 4852dea7143a1..f8cb040559e6a 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -1,4 +1,5 @@ -use crate::{Evm, FAUCET_ACCOUNT}; +use ethers::abi::Abi; +use crate::{Evm, FAUCET_ACCOUNT, call_tracing::CallTrace}; use ethers::types::{Address, Bytes, U256}; @@ -100,6 +101,10 @@ where self.executor.state() } + fn trace(&self) -> Option { + self.executor.trace() + } + /// Deploys the provided contract bytecode fn deploy( &mut self, diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index d4ffd2d68cca5..db836954a718e 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -8,7 +8,7 @@ pub mod cheatcodes; pub mod state; use ethers::{ - abi::RawLog, + abi::{Abi, RawLog}, providers::Middleware, types::{Address, H160, H256, U256}, }; @@ -19,6 +19,8 @@ use sputnik::{ Config, CreateScheme, ExitError, ExitReason, ExitSucceed, }; +use crate::call_tracing::CallTrace; + pub use sputnik as sputnik_evm; use sputnik_evm::executor::stack::PrecompileSet; @@ -86,6 +88,9 @@ pub trait SputnikExecutor { /// execution fn raw_logs(&self) -> Vec; + /// Gets a trace + fn trace(&self) -> Option { None } + /// Returns a vector of string parsed logs that occurred during the previous VM /// execution fn logs(&self) -> Vec; diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 313e5dd7c5031..164a817218f7c 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -113,7 +113,7 @@ impl MultiContractRunnerBuilder { pub struct MultiContractRunner { /// Mapping of contract name to compiled bytecode, deployed address and logs emitted during /// deployment - contracts: BTreeMap)>, + pub contracts: BTreeMap)>, /// The EVM instance used in the test runner evm: E, /// The fuzzer which will be used to run parametric tests (w/ non-0 solidity args) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 3dcc42ac3d181..ee63519e10bf4 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -1,3 +1,4 @@ +use evm_adapters::call_tracing::CallTrace; use ethers::{ abi::{Abi, Function, Token}, types::{Address, Bytes}, @@ -7,7 +8,7 @@ use evm_adapters::{fuzz::FuzzedExecutor, Evm, EvmError}; use eyre::{Context, Result}; use regex::Regex; -use std::{collections::HashMap, fmt, time::Instant}; +use std::{collections::{HashMap}, fmt, time::Instant}; use proptest::test_runner::{TestError, TestRunner}; use serde::{Deserialize, Serialize}; @@ -46,6 +47,9 @@ pub struct TestResult { /// Any captured & parsed as strings logs along the test's execution which should /// be printed to the user. pub logs: Vec, + + /// Trace + pub trace: Option, } use std::marker::PhantomData; @@ -195,7 +199,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let duration = Instant::now().duration_since(start); tracing::debug!(?duration, %success, %gas_used); - Ok(TestResult { success, reason, gas_used: Some(gas_used), counterexample: None, logs }) + Ok(TestResult { success, reason, gas_used: Some(gas_used), counterexample: None, logs, trace: self.evm.trace() }) } #[tracing::instrument(name = "fuzz-test", skip_all, fields(name = %func.signature()))] @@ -235,7 +239,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { // TODO: How can we have proptest also return us the gas_used and the revert reason // from that call? - Ok(TestResult { success, reason: None, gas_used: None, counterexample, logs: vec![] }) + Ok(TestResult { success, reason: None, gas_used: None, counterexample, logs: vec![], trace: None }) } } From 23687c480d64c572ad04b270d318c78b3082ee31 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 2 Dec 2021 00:58:14 -0800 Subject: [PATCH 06/34] fmt --- cli/src/forge.rs | 3 +- evm-adapters/src/call_tracing.rs | 57 ++++++++++++------- evm-adapters/src/lib.rs | 8 ++- .../sputnik/cheatcodes/cheatcode_handler.rs | 4 +- evm-adapters/src/sputnik/evm.rs | 2 +- evm-adapters/src/sputnik/mod.rs | 4 +- forge/src/runner.rs | 22 +++++-- 7 files changed, 65 insertions(+), 35 deletions(-) diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 440143ce5b65b..ff89b860c0112 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -324,11 +324,10 @@ fn test>( if let Some(trace) = &result.trace { trace.pretty_print(&runner.contracts, "".to_string()); } - println!(); + println!(); } } } - } } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 5a2744de4c6fe..46a84f55782a3 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -2,7 +2,7 @@ use ethers::{ abi::{Abi, FunctionExt, RawLog}, types::{Address, H160}, }; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use ansi_term::Colour; @@ -121,7 +121,7 @@ impl CallTrace { if let Some((name, (abi, addr, _other))) = contracts.iter().find(|(_key, (_abi, addr, _other))| addr == &self.addr) { - let color = if self.success { Colour::Green } else { Colour::Red }; + let color = if self.success { Colour::Green } else { Colour::Red }; // let indent = "\t".repeat(self.depth); for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { @@ -149,35 +149,46 @@ impl CallTrace { self.logs.iter().enumerate().for_each(|(i, log)| { for (event_name, overloaded_events) in abi.events.iter() { - let mut found = false; - let mut right = "├─ "; + let mut found = false; + let mut right = "├─ "; if i == self.logs.len() - 1 { right = "└─ "; } for event in overloaded_events.iter() { if event.signature() == log.topics[0] { - found = true; + found = true; println!( "{}emit {}({})", left.to_string().replace("├─ ", "| ") + right, - Colour::Cyan.paint(event_name), - Colour::Cyan.paint(format!("{:?}", event.parse_log(log.clone()).unwrap())) + Colour::Cyan.paint(event_name), + Colour::Cyan + .paint(format!("{:?}", event.parse_log(log.clone()).unwrap())) ); } } if !found { - println!("{}emit {}", left.to_string().replace("├─ ", "| ") + right, Colour::Blue.paint(format!("{:?}", log))) + println!( + "{}emit {}", + left.to_string().replace("├─ ", "| ") + right, + Colour::Blue.paint(format!("{:?}", log)) + ) } } }); } else { - if self.data.len() >= 4 { - println!("{}{:x}::{}({})", left, self.addr, hex::encode(&self.data[0..4]), hex::encode(&self.data[4..])); - } else { - println!("{}{:x}::({})", left, self.addr, hex::encode(&self.data)); - } - - self.inner.iter().enumerate().for_each(|(i, inner)| { + if self.data.len() >= 4 { + println!( + "{}{:x}::{}({})", + left, + self.addr, + hex::encode(&self.data[0..4]), + hex::encode(&self.data[4..]) + ); + } else { + println!("{}{:x}::({})", left, self.addr, hex::encode(&self.data)); + } + + self.inner.iter().enumerate().for_each(|(i, inner)| { // let inners = inner.inner_number_of_inners(); if i == self.inner.len() - 1 && self.logs.len() == 0 { inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "└─ "); @@ -186,13 +197,17 @@ impl CallTrace { } }); - let mut right = "├─ "; - + let mut right = "├─ "; + self.logs.iter().enumerate().for_each(|(i, log)| { - if i == self.logs.len() - 1 { - right = "└─ "; - } - println!("{}emit {}", left.to_string().replace("├─ ", "| ") + right, Colour::Cyan.paint(format!("{:?}", log))) + if i == self.logs.len() - 1 { + right = "└─ "; + } + println!( + "{}emit {}", + left.to_string().replace("├─ ", "| ") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) }); } } diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index c4024a125659c..8c085e6c698be 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -9,9 +9,9 @@ pub mod evmodin; mod blocking_provider; use crate::call_tracing::CallTrace; -use std::collections::BTreeMap; -use ethers::abi::Abi; pub use blocking_provider::BlockingProvider; +use ethers::abi::Abi; +use std::collections::BTreeMap; pub mod fuzz; @@ -95,7 +95,9 @@ pub trait Evm { } } - fn trace(&self) -> Option { None } + fn trace(&self) -> Option { + None + } /// Executes the specified EVM call against the state // TODO: Should we just make this take a `TransactionRequest` or other more diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index e6b5d1fc35c21..4683018e197ec 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -27,8 +27,7 @@ use ethers::{ signers::{LocalWallet, Signer}, types::{Address, H160, H256, U256}, }; -use std::convert::Infallible; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, convert::Infallible}; use once_cell::sync::Lazy; @@ -461,7 +460,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } else { new_trace.logs = self.raw_logs(); } - } else { new_trace.logs = self.raw_logs().to_vec(); } diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index f8cb040559e6a..965906bb008e8 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -1,5 +1,5 @@ +use crate::{call_tracing::CallTrace, Evm, FAUCET_ACCOUNT}; use ethers::abi::Abi; -use crate::{Evm, FAUCET_ACCOUNT, call_tracing::CallTrace}; use ethers::types::{Address, Bytes, U256}; diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index db836954a718e..f98a93889d36d 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -89,7 +89,9 @@ pub trait SputnikExecutor { fn raw_logs(&self) -> Vec; /// Gets a trace - fn trace(&self) -> Option { None } + fn trace(&self) -> Option { + None + } /// Returns a vector of string parsed logs that occurred during the previous VM /// execution diff --git a/forge/src/runner.rs b/forge/src/runner.rs index ee63519e10bf4..3118559c3cbdf 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -1,14 +1,14 @@ -use evm_adapters::call_tracing::CallTrace; use ethers::{ abi::{Abi, Function, Token}, types::{Address, Bytes}, }; +use evm_adapters::call_tracing::CallTrace; use evm_adapters::{fuzz::FuzzedExecutor, Evm, EvmError}; use eyre::{Context, Result}; use regex::Regex; -use std::{collections::{HashMap}, fmt, time::Instant}; +use std::{collections::HashMap, fmt, time::Instant}; use proptest::test_runner::{TestError, TestRunner}; use serde::{Deserialize, Serialize}; @@ -199,7 +199,14 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let duration = Instant::now().duration_since(start); tracing::debug!(?duration, %success, %gas_used); - Ok(TestResult { success, reason, gas_used: Some(gas_used), counterexample: None, logs, trace: self.evm.trace() }) + Ok(TestResult { + success, + reason, + gas_used: Some(gas_used), + counterexample: None, + logs, + trace: self.evm.trace(), + }) } #[tracing::instrument(name = "fuzz-test", skip_all, fields(name = %func.signature()))] @@ -239,7 +246,14 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { // TODO: How can we have proptest also return us the gas_used and the revert reason // from that call? - Ok(TestResult { success, reason: None, gas_used: None, counterexample, logs: vec![], trace: None }) + Ok(TestResult { + success, + reason: None, + gas_used: None, + counterexample, + logs: vec![], + trace: None, + }) } } From f4671a923444cab9e8b2afc44e6f43aa013ba71a Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:10:55 -0800 Subject: [PATCH 07/34] updates --- cli/src/forge.rs | 4 +- evm-adapters/src/call_tracing.rs | 510 +++++++++++++----- evm-adapters/src/evmodin.rs | 8 + evm-adapters/src/lib.rs | 8 +- .../sputnik/cheatcodes/cheatcode_handler.rs | 225 ++++++-- .../cheatcodes/memory_stackstate_owned.rs | 13 +- evm-adapters/src/sputnik/evm.rs | 10 +- evm-adapters/src/sputnik/mod.rs | 4 +- evm-adapters/testdata/Trace.sol | 51 +- forge/src/multi_runner.rs | 90 +++- forge/src/runner.rs | 4 +- 11 files changed, 679 insertions(+), 248 deletions(-) diff --git a/cli/src/forge.rs b/cli/src/forge.rs index ff89b860c0112..d2eb635a55055 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -106,6 +106,7 @@ fn main() -> eyre::Result<()> { &cfg, &precompiles, ffi, + verbosity > 2 ); test(builder, project, evm, pattern, json, verbosity, allow_failure)?; @@ -320,9 +321,8 @@ fn test>( println!(); if verbosity > 2 { - println!("{:?}", runner.contracts); if let Some(trace) = &result.trace { - trace.pretty_print(&runner.contracts, "".to_string()); + trace.pretty_print(0, &runner.known_contracts, &runner.evm, "".to_string()); } println!(); } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 46a84f55782a3..98981ae3b8908 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -1,214 +1,436 @@ use ethers::{ abi::{Abi, FunctionExt, RawLog}, - types::{Address, H160}, + types::{H160}, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use ansi_term::Colour; -/// Call trace of a tx -#[derive(Clone, Default, Debug, Deserialize, Serialize)] -pub struct CallTrace { - pub depth: usize, - pub location: usize, - /// Successful - pub success: bool, - /// Callee - pub addr: H160, - /// Creation - pub created: bool, - /// Call data, including function selector (if applicable) - pub data: Vec, - /// Gas cost - pub cost: u64, - /// Output - pub output: Vec, - /// Logs - #[serde(skip)] - pub logs: Vec, - /// inner calls - pub inner: Vec, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CallTraceArena { + pub arena: Vec, + pub entry: usize, } -impl CallTrace { - pub fn add_trace(&mut self, new_trace: Self) { - if new_trace.depth == 0 { +impl Default for CallTraceArena { + fn default() -> Self { + CallTraceArena { + arena: vec![ + CallTraceNode { + parent: None, + children: vec![], + idx: 0, + trace: CallTrace::default() + } + ], + entry: 0 + } + } +} + +impl CallTraceArena { + pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { + if new_trace.depth == 0 { // overwrite - // self.update(new_trace); - } else if self.depth == new_trace.depth - 1 { - self.inner.push(new_trace); + self.update(new_trace.clone()); + new_trace + } else if self.arena[entry].trace.depth == new_trace.depth - 1 { + new_trace.idx = self.arena.len(); + new_trace.location = self.arena[entry].children.len(); + let node = CallTraceNode { + parent: Some(entry), + children: vec![], + idx: self.arena.len(), + trace: new_trace.clone() + }; + self.arena.push(node); + self.arena[entry].children.push(new_trace.idx); + new_trace } else { - self.inner.last_mut().expect("Disconnected trace").add_trace(new_trace); + self.push_trace(*self.arena[entry].children.last().expect("Disconnected trace"), new_trace) } - } + } - fn update(&mut self, new_trace: Self) { - self.success = new_trace.success; - self.addr = new_trace.addr; - self.cost = new_trace.cost; - self.output = new_trace.output; - self.logs = new_trace.logs; - self.data = new_trace.data; - self.addr = new_trace.addr; - // we dont update inner because the temporary new_trace doesnt track inner calls - } + pub fn update(&mut self, trace: CallTrace) { + let node = &mut self.arena[trace.idx]; + node.trace.update(trace); + } - pub fn update_trace(&mut self, new_trace: Self) { - if new_trace.depth == 0 { - self.update(new_trace); - } else if self.depth == new_trace.depth - 1 { - self.inner[new_trace.location].update(new_trace); - } else { - self.inner.last_mut().expect("Disconnected trace update").update_trace(new_trace); - } - } + pub fn inner_number_of_logs(&self, node_idx: usize) -> usize { + self.arena[node_idx].children.iter().fold(0, |accum, idx| { + accum + self.arena[*idx].trace.prelogs.len() + self.arena[*idx].trace.logs.len() + self.inner_number_of_logs(*idx) + }) //+ self.arena[node_idx].trace.prelogs.len() + self.arena[node_idx].trace.logs.len() + } - pub fn location(&self, new_trace: &Self) -> usize { - if new_trace.depth == 0 { - 0 - } else if self.depth == new_trace.depth - 1 { - self.inner.len() - } else { - self.inner.last().expect("Disconnected trace location").location(new_trace) - } - } + pub fn inner_number_of_inners(&self, node_idx: usize) -> usize { + self.arena[node_idx].children.iter().fold(0, |accum, idx| { + accum + self.arena[*idx].children.len() + self.inner_number_of_inners(*idx) + }) + } - pub fn inner_number_of_logs(&self) -> usize { - // only count child logs - let mut total = 0; - if self.inner.len() > 0 { - self.inner.iter().for_each(|inner| { - total += inner.inner_number_of_logs(); - }); - } - total += self.logs.len(); - total - } + pub fn next_log_index(&self) -> usize { + self.inner_number_of_logs(self.entry) + } - pub fn inner_number_of_inners(&self) -> usize { - // only count child logs - let mut total = 0; - if self.inner.len() > 0 { - self.inner.iter().for_each(|inner| { - total += inner.inner_number_of_inners(); - }); - } - total += self.inner.len(); - total - } + pub fn print_logs_in_order(&self, node_idx: usize) { + self.arena[node_idx].trace.prelogs.iter().for_each(|(raw, _loc)| println!("prelog {}", raw.topics[0])); + self.arena[node_idx].children.iter().for_each(|idx| { + self.print_logs_in_order(*idx); + }); + self.arena[node_idx].trace.logs.iter().for_each(|raw| println!("log {}", raw.topics[0])); + } - pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { - if self.depth == depth && self.location == location { - return Some(&self) - } else { - if self.depth != depth { - for inner in self.inner.iter() { - if let Some(trace) = inner.get_trace(depth, location) { - return Some(trace) - } - } - } - } - return None - } - pub fn pretty_print( + pub fn pretty_print<'a, S: Clone, E: crate::Evm>( &self, - contracts: &BTreeMap)>, + idx: usize, + contracts: &BTreeMap)>, + evm: &'a E, left: String, ) { - if let Some((name, (abi, addr, _other))) = - contracts.iter().find(|(_key, (_abi, addr, _other))| addr == &self.addr) + let trace = &self.arena[idx].trace; + + if trace.created { + if let Some((name, (_abi, _code))) = + contracts.iter().find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) + { + println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + return; + } else { + println!("{}→ new @{:?}", left, trace.addr); + return; + } + } + // fuzzy find contracts + if let Some((name, (abi, _code))) = + contracts.iter().find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) { - let color = if self.success { Colour::Green } else { Colour::Red }; - // let indent = "\t".repeat(self.depth); + // color the printout by success + let color = if trace.success { Colour::Green } else { Colour::Red }; + + let mut decoded_output = None; + // search thru abi functions to find matching for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { - if func.selector() == self.data[0..4] { + if func.selector() == trace.data[0..4] { + let params = func.decode_input(&trace.data[4..]).expect("Bad func data decode"); + let strings = params.iter().map(|param| { + format!("{:?}", param) + }).collect::>().join(", "); println!( - "{}[{}] {}::{}({:?})", + "{}[{}] {}::{}({})", left, - self.cost, + trace.cost, color.paint(name), color.paint(func_name), - func.decode_input(&self.data[4..]).unwrap() + strings ); + + decoded_output = Some(func.decode_output(&trace.output[..]).expect("Bad func output decode")); } } } - self.inner.iter().enumerate().for_each(|(i, inner)| { - // let inners = inner.inner_number_of_inners(); - if i == self.inner.len() - 1 && self.logs.len() == 0 { - inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "└─ "); - } else { - inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "├─ "); - } - }); + let children_idxs = &self.arena[idx].children; + if children_idxs.len() == 0 { + trace.prelogs.iter().for_each(|(log, loc)| { + if *loc == 0 { + let mut found = false; + let right = " ├─ "; + 'outer: for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + found = true; + let params = event.parse_log(log.clone()).expect("Bad event").params; + let strings = params.iter().map(|param| { + format!("{}: {:?}", param.name, param.value) + }).collect::>().join(", "); + println!( + "{}emit {}({})", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(event_name), + strings + ); + break 'outer; + } + } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + } + } + }); + } else { + children_idxs.iter().enumerate().for_each(|(i, child_idx)| { + let child_location = self.arena[*child_idx].trace.location; + trace.prelogs.iter().for_each(|(log, loc)| { + if loc == &child_location { + let mut found = false; + let right = " ├─ "; + 'outer: for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + found = true; + let params = event.parse_log(log.clone()).expect("Bad event").params; + let strings = params.iter().map(|param| { + format!("{}: {:?}", param.name, param.value) + }).collect::>().join(", "); + println!( + "{}emit {}({})", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(event_name), + strings + ); + break 'outer; + } + } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + } + } + }); + if i == children_idxs.len() - 1 && trace.logs.len() == 0 && decoded_output.is_none() { + self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " └─ "); + } else { + self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ "); + } + }); + } - self.logs.iter().enumerate().for_each(|(i, log)| { - for (event_name, overloaded_events) in abi.events.iter() { - let mut found = false; - let mut right = "├─ "; - if i == self.logs.len() - 1 { - right = "└─ "; - } + trace.logs.iter().enumerate().for_each(|(i, log)| { + let mut found = false; + let mut right = " ├─ "; + if i == trace.logs.len() - 1 && decoded_output.is_none() { + right = " └─ "; + } + 'outer: for (event_name, overloaded_events) in abi.events.iter() { for event in overloaded_events.iter() { if event.signature() == log.topics[0] { found = true; + let params = event.parse_log(log.clone()).expect("Bad event").params; + let strings = params.iter().map(|param| { + format!("{}: {:?}", param.name, param.value) + }).collect::>().join(", "); println!( "{}emit {}({})", - left.to_string().replace("├─ ", "| ") + right, + left.to_string().replace("├─", "│") + right, Colour::Cyan.paint(event_name), - Colour::Cyan - .paint(format!("{:?}", event.parse_log(log.clone()).unwrap())) + strings ); + break 'outer; } } - if !found { - println!( - "{}emit {}", - left.to_string().replace("├─ ", "| ") + right, - Colour::Blue.paint(format!("{:?}", log)) - ) - } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) } }); + + if let Some(decoded) = decoded_output { + let strings = decoded.iter().map(|param| { + format!("{:?}", param) + }).collect::>().join(", "); + println!( + "{} └─ {} {}", + left.to_string().replace("├─", "│").replace("└─", " "), + Colour::Green.paint("←"), + if strings.len() == 0 { "()" } else { &*strings } + ); + } } else { - if self.data.len() >= 4 { + if trace.data.len() >= 4 { println!( "{}{:x}::{}({})", left, - self.addr, - hex::encode(&self.data[0..4]), - hex::encode(&self.data[4..]) + trace.addr, + hex::encode(&trace.data[0..4]), + hex::encode(&trace.data[4..]) ); } else { - println!("{}{:x}::({})", left, self.addr, hex::encode(&self.data)); + println!("{}{:x}::({})", left, trace.addr, hex::encode(&trace.data)); } - self.inner.iter().enumerate().for_each(|(i, inner)| { + let children_idxs = &self.arena[idx].children; + children_idxs.iter().enumerate().for_each(|(i, child_idx)| { // let inners = inner.inner_number_of_inners(); - if i == self.inner.len() - 1 && self.logs.len() == 0 { - inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "└─ "); + if i == children_idxs.len() - 1 && trace.logs.len() == 0 { + self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " _└─ "); } else { - inner.pretty_print(contracts, left.to_string().replace("├─ ", "| ") + "├─ "); + self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " _├─ "); } }); - let mut right = "├─ "; + let mut right = " _├─ "; - self.logs.iter().enumerate().for_each(|(i, log)| { - if i == self.logs.len() - 1 { - right = "└─ "; + trace.logs.iter().enumerate().for_each(|(i, log)| { + if i == trace.logs.len() - 1 { + right = " _└─ "; } println!( "{}emit {}", - left.to_string().replace("├─ ", "| ") + right, + left.to_string().replace("├─", "│") + right, Colour::Cyan.paint(format!("{:?}", log)) ) }); } } } + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CallTraceNode { + pub parent: Option, + pub children: Vec, + pub idx: usize, + pub trace: CallTrace, +} + + +/// Call trace of a tx +#[derive(Clone, Default, Debug, Deserialize, Serialize)] +pub struct CallTrace { + pub depth: usize, + pub location: usize, + pub idx: usize, + /// Successful + pub success: bool, + /// Callee + pub addr: H160, + /// Creation + pub created: bool, + /// Call data, including function selector (if applicable) + pub data: Vec, + /// Gas cost + pub cost: u64, + /// Output + pub output: Vec, + /// Logs emitted before inner + #[serde(skip)] + pub prelogs: Vec<(RawLog, usize)>, + /// Logs + #[serde(skip)] + pub logs: Vec, + /// inner calls + pub inner: Vec, +} + +impl CallTrace { + + fn update(&mut self, new_trace: Self) { + self.success = new_trace.success; + self.addr = new_trace.addr; + self.cost = new_trace.cost; + self.output = new_trace.output; + self.logs = new_trace.logs; + self.data = new_trace.data; + self.addr = new_trace.addr; + // we dont update inner because the temporary new_trace doesnt track inner calls + } + + // pub fn inner_number_of_logs(&self) -> usize { + // // only count child logs + // let mut total = 0; + // if self.inner.len() > 0 { + // self.inner.iter().for_each(|inner| { + // total += inner.inner_number_of_logs(); + // }); + // } + // total += self.logs.len() + self.prelogs.len(); + // total + // } + + // pub fn logs_up_to_depth_loc(&self, depth: usize, loc: usize) -> usize { + // if depth == 0 { + // return 0; + // } + // let target = self.get_trace(depth, loc).expect("huh?"); + // let parent = self.get_trace(target.depth - 1, target.parent_location).expect("huh?"); + // let siblings = &parent.inner[..loc]; + // let mut total = target.logs.len() + target.prelogs.len(); + // for sibling in siblings.iter() { + // total += sibling.logs.len() + sibling.prelogs.len(); + // } + // total += self.logs_up_to_depth_loc(target.depth - 1, target.parent_location); + // total + // } + + // pub fn next_log_index(&self, depth: usize, loc: usize) -> usize { + // let total = self.logs_up_to_depth_loc(depth, loc); + // let target = self.get_trace(depth, loc).expect("huh?"); + // total + target.inner_number_of_logs() + // } + + // pub fn inner_number_of_inners(&self) -> usize { + // // only count child logs + // let mut total = 0; + // if self.inner.len() > 0 { + // self.inner.iter().for_each(|inner| { + // total += inner.inner_number_of_inners(); + // }); + // } + // total += self.inner.len(); + // total + // } + + // pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { + // if self.depth == depth && self.location == location { + // return Some(&self) + // } else { + // if self.depth != depth { + // for inner in self.inner.iter() { + // if let Some(trace) = inner.get_trace(depth, location) { + // return Some(trace) + // } + // } + // } + // } + // return None + // } + + // pub fn get_trace_mut(&mut self, depth: usize, location: usize) -> Option<&mut CallTrace> { + // if self.depth == depth && self.location == location { + // return Some(self) + // } else { + // if self.depth != depth { + // for inner in self.inner.iter_mut() { + // if let Some(trace) = inner.get_trace_mut(depth, location) { + // return Some(trace) + // } + // } + // } + // } + // return None + // } +} + +// very simple fuzzy matching to account for immutables. Will fail for small contracts that are basically all immutable vars +fn diff_score(bytecode1: &Vec, bytecode2: &Vec) -> f64 { + let cutoff_len = usize::min(bytecode1.len(), bytecode2.len()); + let b1 = &bytecode1[..cutoff_len]; + let b2 = &bytecode2[..cutoff_len]; + if cutoff_len == 0 { + return 1.0 + } + + let mut diff_chars = 0; + for i in 0..cutoff_len { + if b1[i] != b2[i] { + diff_chars += 1; + } + } + + diff_chars as f64 / cutoff_len as f64 +} diff --git a/evm-adapters/src/evmodin.rs b/evm-adapters/src/evmodin.rs index bf5395770d2ee..beb846246287f 100644 --- a/evm-adapters/src/evmodin.rs +++ b/evm-adapters/src/evmodin.rs @@ -69,6 +69,14 @@ impl Evm for EvmOdin { &self.host } + fn code(&self, address: Address) -> Vec { + if let Some(bytes) = self.host.get_code(&address) { + bytes.to_vec() + } else { + vec![] + } + } + #[allow(unused)] fn deploy( &mut self, diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 8c085e6c698be..2ed14d99a25da 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -8,10 +8,8 @@ pub mod sputnik; pub mod evmodin; mod blocking_provider; -use crate::call_tracing::CallTrace; +use crate::call_tracing::CallTraceArena; pub use blocking_provider::BlockingProvider; -use ethers::abi::Abi; -use std::collections::BTreeMap; pub mod fuzz; @@ -68,6 +66,8 @@ pub trait Evm { /// Gets a reference to the current state of the EVM fn state(&self) -> &State; + fn code(&self, address: Address) -> Vec; + /// Sets the balance at the specified address fn set_balance(&mut self, address: Address, amount: U256); @@ -95,7 +95,7 @@ pub trait Evm { } } - fn trace(&self) -> Option { + fn trace(&self) -> Option { None } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 4683018e197ec..c37f98d8d61b0 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -4,7 +4,7 @@ use super::{ HEVMCalls, HevmConsoleEvents, }; use crate::{ - call_tracing::CallTrace, + call_tracing::{CallTrace, CallTraceArena}, sputnik::{Executor, SputnikExecutor}, Evm, }; @@ -60,6 +60,7 @@ pub static CONSOLE_ADDRESS: Lazy
= Lazy::new(|| { pub struct CheatcodeHandler { handler: H, enable_ffi: bool, + enable_trace: bool, console_logs: Vec, } @@ -169,7 +170,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Option { + fn trace(&self) -> Option { Some(self.state().trace.clone()) } @@ -235,6 +236,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> config: &'a Config, precompiles: &'b P, enable_ffi: bool, + enable_trace: bool, ) -> Self { // make this a cheatcode-enabled backend let backend = CheatcodeBackend { backend, cheats: Default::default() }; @@ -246,7 +248,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> // create the executor and wrap it with the cheatcode handler let executor = StackExecutor::new_with_precompiles(state, config, precompiles); - let executor = CheatcodeHandler { handler: executor, enable_ffi, console_logs: Vec::new() }; + let executor = CheatcodeHandler { handler: executor, enable_ffi, enable_trace, console_logs: Vec::new() }; let mut evm = Executor::from_executor(executor, gas_limit); @@ -438,36 +440,80 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> CallTrace { - let mut trace: CallTrace = Default::default(); - // depth only starts tracking at first child substate and is 0. so add 1 when depth is some. - trace.depth = if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; - trace.addr = address; - trace.created = creation; - trace.data = input; - trace.location = self.state_mut().trace.location(&trace); - // we should probably delay having the input and other stuff so - // we minimize the size of the clone - self.state_mut().trace.add_trace(trace.clone()); - trace - } - - fn fill_trace(&mut self, mut new_trace: CallTrace, success: bool, output: Option>) { - if let Some(trace) = self.state().trace.get_trace(new_trace.depth, new_trace.location) { - let num = trace.inner_number_of_logs(); - if self.raw_logs().len() > num { - new_trace.logs = self.raw_logs()[num..].to_vec(); - } else { - new_trace.logs = self.raw_logs(); + fn start_trace(&mut self, address: H160, input: Vec, creation: bool, prelogs: usize) -> Option { + if self.enable_trace { + let mut trace: CallTrace = Default::default(); + // depth only starts tracking at first child substate and is 0. so add 1 when depth is some. + trace.depth = if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; + trace.addr = address; + trace.created = creation; + trace.data = input; + + // we should probably delay having the input and other stuff so + // we minimize the size of the clone + trace = self.state_mut().trace.push_trace(0, trace.clone()); + + + if trace.depth > 0 && prelogs > 0 { + let trace_index = trace.idx; + let parent_index = self.state().trace.arena[trace_index].parent.expect("No parent"); + let next_log_index = self.state().trace.next_log_index(); + + let child_logs = self.state().trace.inner_number_of_logs(parent_index); + + if self.raw_logs().len() >= prelogs { + if prelogs.saturating_sub(child_logs).saturating_sub(self.state().trace.arena[parent_index].trace.prelogs.len().saturating_sub(1)) > 0 { + let prelogs = self.raw_logs()[ + self.state().trace.arena[parent_index].trace.prelogs.len() + child_logs + .. + prelogs + ].to_vec(); + let parent = &mut self.state_mut().trace.arena[parent_index]; + + if prelogs.len() > 0 { + prelogs.into_iter().for_each(|prelog| { + parent.trace.prelogs.push((prelog, trace.location)); + }); + } + } + } + } else if trace.depth == 0 { + if self.raw_logs().len() >= prelogs { + let prelogs = self.raw_logs()[0..prelogs].to_vec(); + prelogs.into_iter().for_each(|prelog| { + self.state_mut().trace.arena[0].trace.prelogs.push((prelog, trace.location)); + }) + } } + Some(trace) } else { - new_trace.logs = self.raw_logs().to_vec(); + None } + } - new_trace.output = output.unwrap_or(vec![]); - new_trace.cost = self.handler.used_gas(); - new_trace.success = success; - self.state_mut().trace.update_trace(new_trace); + fn fill_trace(&mut self, mut new_trace: Option, prelogs: usize, success: bool, output: Option>) { + if let Some(mut new_trace) = new_trace { + let mut next = self.raw_logs().len().saturating_sub(1); + + if new_trace.depth > 0 && prelogs > 0 && next > prelogs { + next = self.raw_logs().len() - prelogs; + } else if new_trace.depth == 0 { + next += 1; + } + + { + if self.raw_logs().len() > next { + let new_logs = self.raw_logs()[next..].to_vec(); + self.state_mut().trace.arena[new_trace.idx].trace.logs.extend(new_logs); + } + } + + let used_gas = self.handler.used_gas(); + let trace = &mut self.state_mut().trace.arena[new_trace.idx].trace; + trace.output = output.unwrap_or(vec![]); + trace.cost = used_gas; + trace.success = success; + } } // NB: This function is copy-pasted from uptream's call_inner @@ -483,14 +529,15 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> take_stipend: bool, context: Context, ) -> Capture<(ExitReason, Vec), Infallible> { - let trace = self.start_trace(code_address, input.clone(), false); + let prelogs = self.raw_logs().len(); + let trace = self.start_trace(code_address, input.clone(), false, prelogs); macro_rules! try_or_fail { ( $e:expr ) => { match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, false, None); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((e.into(), Vec::new())) } } @@ -526,13 +573,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } let code = self.code(code_address); - self.handler.enter_substate(gas_limit, is_static); self.state_mut().touch(context.address); if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { - self.fill_trace(trace, false, None); + self.fill_trace(trace, prelogs, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())) } @@ -542,7 +588,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().transfer(transfer) { Ok(()) => (), Err(e) => { - self.fill_trace(trace, false, None); + self.fill_trace(trace, prelogs, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitReason::Error(e), Vec::new())) } @@ -561,12 +607,15 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> for Log { address, topics, data } in logs { match self.log(address, topics, data) { Ok(_) => continue, - Err(error) => return Capture::Exit((ExitReason::Error(error), output)), + Err(error) => { + self.fill_trace(trace, prelogs, false, Some(output.clone())); + return Capture::Exit((ExitReason::Error(error), output)) + }, } } let _ = self.state_mut().metadata_mut().gasometer_mut().record_cost(cost); - self.fill_trace(trace, true, Some(output.clone())); + self.fill_trace(trace, prelogs, true, Some(output.clone())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(exit_status), output)) } @@ -578,7 +627,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } PrecompileFailure::Fatal { exit_status } => ExitReason::Fatal(exit_status), }; - self.fill_trace(trace, false, None); + self.fill_trace(trace, prelogs, false, None); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((e, Vec::new())) } @@ -594,22 +643,22 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // reason); match reason { ExitReason::Succeed(s) => { - self.fill_trace(trace, true, Some(runtime.machine().return_value())); + self.fill_trace(trace, prelogs, true, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) } ExitReason::Error(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Error(e), Vec::new())) } ExitReason::Revert(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Reverted); Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) } ExitReason::Fatal(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Fatal(e), Vec::new())) @@ -627,11 +676,19 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> target_gas: Option, take_l64: bool, ) -> Capture<(ExitReason, Option, Vec), Infallible> { + let prelogs = self.raw_logs().len(); + let address = self.create_address(scheme); + + let trace = self.start_trace(address, init_code.clone(), true, prelogs); + macro_rules! try_or_fail { ( $e:expr ) => { match $e { Ok(v) => v, - Err(e) => return Capture::Exit((e.into(), None, Vec::new())), + Err(e) => { + self.fill_trace(trace, prelogs, false, None); + return Capture::Exit((e.into(), None, Vec::new())) + }, } }; } @@ -649,18 +706,18 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> gas - gas / 64 } - let address = self.create_address(scheme); - self.state_mut().metadata_mut().access_address(caller); self.state_mut().metadata_mut().access_address(address); if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())) } } if self.balance(caller) < value { + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())) } @@ -689,11 +746,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> { if self.code_size(address) != U256::zero() { let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } if self.handler.nonce(address) > U256::zero() { let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } @@ -706,6 +765,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Ok(()) => (), Err(e) => { let _ = self.handler.exit_substate(StackExitKind::Reverted); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } @@ -728,6 +788,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Err(e) = check_first_byte(self.config(), &out) { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((e.into(), None, Vec::new())) } @@ -735,6 +796,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if out.len() > limit { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit(( ExitError::CreateContractLimit.into(), None, @@ -746,12 +808,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().metadata_mut().gasometer_mut().record_deposit(out.len()) { Ok(()) => { let e = self.handler.exit_substate(StackExitKind::Succeeded); - self.state_mut().set_code(address, out); + self.state_mut().set_code(address, out.clone()); try_or_fail!(e); + self.fill_trace(trace, prelogs, true, Some(out)); Capture::Exit((ExitReason::Succeed(s), Some(address), Vec::new())) } Err(e) => { let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } @@ -759,15 +823,18 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ExitReason::Error(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } ExitReason::Revert(e) => { let _ = self.handler.exit_substate(StackExitKind::Reverted); + self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); Capture::Exit((ExitReason::Revert(e), None, runtime.machine().return_value())) } ExitReason::Fatal(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); + self.fill_trace(trace, prelogs, false, None); Capture::Exit((ExitReason::Fatal(e), None, Vec::new())) } } @@ -916,7 +983,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a init_code: Vec, target_gas: Option, ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { - self.handler.create(caller, scheme, value, init_code, target_gas) + self.create_inner(caller, scheme, value, init_code, target_gas, true) + // self.handler.create(caller, scheme, value, init_code, target_gas) } fn pre_validate( @@ -953,7 +1021,7 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); let compiled = COMPILED.find("DebugLogs").expect("could not find contract"); let (addr, _, _, _) = @@ -994,7 +1062,7 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); let compiled = COMPILED.find("ConsoleLogs").expect("could not find contract"); let (addr, _, _, _) = @@ -1018,7 +1086,7 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); let compiled = COMPILED.find("DebugLogs").expect("could not find contract"); let (addr, _, _, _) = @@ -1043,7 +1111,7 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); let compiled = COMPILED.find("CheatCodes").expect("could not find contract"); let (addr, _, _, _) = @@ -1089,7 +1157,7 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, false); let compiled = COMPILED.find("CheatCodes").expect("could not find contract"); let (addr, _, _, _) = @@ -1105,7 +1173,7 @@ mod tests { } #[test] - fn tracing() { + fn tracing_call() { use std::collections::BTreeMap; let config = Config::istanbul(); @@ -1114,9 +1182,9 @@ mod tests { let gas_limit = 10_000_000; let precompiles = PRECOMPILES_MAP.clone(); let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); - let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); + let compiled = COMPILED.find("Trace").expect("could not find contract"); let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); @@ -1126,16 +1194,61 @@ mod tests { Address::zero(), addr, "recurseCall(uint256,uint256)", + (U256::from(2u32), U256::from(0u32)), + 0u32.into(), + ) + .unwrap(); + + let mut mapping = BTreeMap::new(); + mapping.insert( + "Trace".to_string(), + (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ); + let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); + mapping.insert( + "RecursiveCall".to_string(), + (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ); + evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); + } + + #[test] + fn tracing_create() { + use std::collections::BTreeMap; + + let config = Config::istanbul(); + let vicinity = new_vicinity(); + let backend = new_backend(&vicinity, Default::default()); + let gas_limit = 10_000_000; + let precompiles = PRECOMPILES_MAP.clone(); + let mut evm = + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); + + let compiled = COMPILED.find("Trace").expect("could not find contract"); + let (addr, _, _, _) = + evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); + + // after the evm call is done, we call `logs` and print it all to the user + let (_, _, _, logs) = evm + .call::<(), _, _>( + Address::zero(), + addr, + "recurseCreate(uint256,uint256)", (U256::from(3u32), U256::from(0u32)), 0u32.into(), ) .unwrap(); let mut mapping = BTreeMap::new(); + mapping.insert( + "Trace".to_string(), + (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ); + let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); mapping.insert( "RecursiveCall".to_string(), - (compiled.abi.expect("No abi").clone(), addr, vec![]), + (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), ); - evm.state().trace.pretty_print(&mapping, "".to_string()); + evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); } } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 43e0a17a0d039..1bf8284275937 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -4,9 +4,9 @@ use sputnik::{ ExitError, Transfer, }; -use crate::call_tracing::CallTrace; +use crate::call_tracing::CallTraceArena; -use ethers::types::{H160, H256, U256}; +use ethers::{abi::RawLog, types::{H160, H256, U256}}; /// This struct implementation is copied from [upstream](https://github.com/rust-blockchain/evm/blob/5ecf36ce393380a89c6f1b09ef79f686fe043624/src/executor/stack/state.rs#L412) and modified to own the Backend type. /// @@ -17,10 +17,14 @@ use ethers::types::{H160, H256, U256}; pub struct MemoryStackStateOwned<'config, B> { pub backend: B, pub substate: MemoryStackSubstate<'config>, - pub trace: CallTrace, - pub current_trace: CallTrace, + pub trace: CallTraceArena, } +// pub struct IndexedLog { +// pub index: usize, +// pub log: RawLog, +// } + impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn deposit(&mut self, address: H160, value: U256) { self.substate.deposit(address, value, &self.backend); @@ -33,7 +37,6 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { backend, substate: MemoryStackSubstate::new(metadata), trace: Default::default(), - current_trace: Default::default(), } } } diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 965906bb008e8..0d2b889f7d0f1 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -1,6 +1,4 @@ -use crate::{call_tracing::CallTrace, Evm, FAUCET_ACCOUNT}; -use ethers::abi::Abi; - +use crate::{call_tracing::CallTraceArena, Evm, FAUCET_ACCOUNT}; use ethers::types::{Address, Bytes, U256}; use sputnik::{ @@ -101,7 +99,11 @@ where self.executor.state() } - fn trace(&self) -> Option { + fn code(&self, address: Address) -> Vec { + self.executor.state().code(address) + } + + fn trace(&self) -> Option { self.executor.trace() } diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index f98a93889d36d..1a22aded7b230 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -19,7 +19,7 @@ use sputnik::{ Config, CreateScheme, ExitError, ExitReason, ExitSucceed, }; -use crate::call_tracing::CallTrace; +use crate::call_tracing::CallTraceArena; pub use sputnik as sputnik_evm; use sputnik_evm::executor::stack::PrecompileSet; @@ -89,7 +89,7 @@ pub trait SputnikExecutor { fn raw_logs(&self) -> Vec; /// Gets a trace - fn trace(&self) -> Option { + fn trace(&self) -> Option { None } diff --git a/evm-adapters/testdata/Trace.sol b/evm-adapters/testdata/Trace.sol index 79446ce1a71bd..002f833089b4a 100644 --- a/evm-adapters/testdata/Trace.sol +++ b/evm-adapters/testdata/Trace.sol @@ -3,21 +3,68 @@ pragma solidity ^0.8.0; interface RecursiveCallee { function recurseCall(uint256 neededDepth, uint256 depth) external returns (uint256); + function recurseCreate(uint256 neededDepth, uint256 depth) external returns (uint256); function someCall() external; } contract RecursiveCall { event Depth(uint256 depth); + event ChildDepth(uint256 childDepth); + event CreatedChild(uint256 childDepth); + Trace factory; + + constructor(address _factory) { + factory = Trace(_factory); + } + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { if (depth == neededDepth) { return neededDepth; } - RecursiveCallee(address(this)).recurseCall(neededDepth, depth + 1); + uint256 childDepth = RecursiveCallee(address(this)).recurseCall(neededDepth, depth + 1); + emit ChildDepth(childDepth); RecursiveCallee(address(this)).someCall(); emit Depth(depth); return depth; } - function someCall() public { + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (depth == neededDepth) { + return neededDepth; + } + RecursiveCall child = factory.create(); + emit CreatedChild(depth + 1); + uint256 childDepth = child.recurseCreate(neededDepth, depth + 1); + emit Depth(depth); + return depth; + } + + function someCall() public {} +} + +contract Trace { + RecursiveCall first; + + function create() public returns (RecursiveCall) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + return first; + } + return new RecursiveCall(address(this)); + } + + + function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + } + return first.recurseCall(neededDepth, depth); + } + + function recurseCreate(uint256 neededDepth, uint256 depth) public returns (uint256) { + if (address(first) == address(0)) { + first = new RecursiveCall(address(this)); + } + return first.recurseCreate(neededDepth, depth); } } \ No newline at end of file diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 164a817218f7c..e9c982d4bc01c 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -1,3 +1,4 @@ +use ethers::prelude::artifacts::CompactContract; use crate::{runner::TestResult, ContractRunner}; use evm_adapters::Evm; @@ -58,33 +59,66 @@ impl MultiContractRunnerBuilder { // This is just the contracts compiled, but we need to merge this with the read cached // artifacts let contracts = output.into_artifacts(); - let contracts: BTreeMap)> = contracts - .map(|(fname, contract)| { - let (abi, bytecode) = contract.into_inner(); - (fname, abi.unwrap(), bytecode.unwrap()) - }) - // Only take contracts with empty constructors. - .filter(|(_, abi, _)| { - abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) - }) - // Only take contracts which contain a `test` function - .filter(|(_, abi, _)| abi.functions().any(|func| func.name.starts_with("test"))) - // deploy the contracts - .map(|(name, abi, bytecode)| { - let span = tracing::trace_span!("deploying", ?name); - let _enter = span.enter(); - - let (addr, _, _, logs) = evm - .deploy(sender, bytecode, 0.into()) - .wrap_err(format!("could not deploy {}", name))?; - - evm.set_balance(addr, initial_balance); - Ok((name, (abi, addr, logs))) - }) - .collect::>>()?; + let mut known_contracts: BTreeMap)> = Default::default(); + let mut deployed_contracts: BTreeMap)> = Default::default(); + + use std::any::Any; + for (fname, contract) in contracts { + let c: &dyn Any = &contract as &dyn Any; + let compact_contract = c.downcast_ref::().expect("Wasn't a compact contract"); + let runtime_code = compact_contract.bin_runtime.as_ref().unwrap().clone(); + let bytecode = compact_contract.bin.as_ref().unwrap(); + let abi = compact_contract.abi.as_ref().unwrap(); + if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) { + if abi.functions().any(|func| func.name.starts_with("test")) { + let span = tracing::trace_span!("deploying", ?fname); + let _enter = span.enter(); + + let (addr, _, _, logs) = evm + .deploy(sender, bytecode.clone(), 0u32.into()) + .wrap_err(format!("could not deploy {}", fname))?; + + evm.set_balance(addr, initial_balance); + deployed_contracts.insert(fname.clone(), (abi.clone(), addr, logs)); + } + } + let split = fname.split(":").collect::>(); + let contract_name = if split.len() > 1 { + split[1] + } else { + split[0] + }; + known_contracts.insert(contract_name.to_string(), (abi.clone(), runtime_code.to_vec())); + } + + // let contracts: BTreeMap)> = contracts + // .map(|(fname, contract)| { + // let (abi, bytecode) = contract.into_inner(); + // (fname, abi.unwrap(), bytecode.unwrap()) + // }) + // // Only take contracts with empty constructors. + // .filter(|(_, abi, _)| { + // abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) + // }) + // // Only take contracts which contain a `test` function + // .filter(|(_, abi, _)| abi.functions().any(|func| func.name.starts_with("test"))) + // // deploy the contracts + // .map(|(name, abi, bytecode)| { + // let span = tracing::trace_span!("deploying", ?name); + // let _enter = span.enter(); + + // let (addr, _, _, logs) = evm + // .deploy(sender, bytecode, 0.into()) + // .wrap_err(format!("could not deploy {}", name))?; + + // evm.set_balance(addr, initial_balance); + // Ok((name, (abi, addr, logs))) + // }) + // .collect::>>()?; Ok(MultiContractRunner { - contracts, + contracts: deployed_contracts, + known_contracts, evm, state: PhantomData, sender: self.sender, @@ -114,8 +148,10 @@ pub struct MultiContractRunner { /// Mapping of contract name to compiled bytecode, deployed address and logs emitted during /// deployment pub contracts: BTreeMap)>, + /// Compiled contracts by name that have an Abi and runtime bytecode + pub known_contracts: BTreeMap)>, /// The EVM instance used in the test runner - evm: E, + pub evm: E, /// The fuzzer which will be used to run parametric tests (w/ non-0 solidity args) fuzzer: Option, /// The address which will be used as the `from` field in all EVM calls @@ -232,7 +268,7 @@ mod tests { // important to instantiate the VM with cheatcodes let precompiles = PRECOMPILES_MAP.clone(); let evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false); + Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, false); let mut runner = runner(evm); let results = runner.test(Regex::new(".*").unwrap()).unwrap(); diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 3118559c3cbdf..c6e8ff3c82f0f 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -2,7 +2,7 @@ use ethers::{ abi::{Abi, Function, Token}, types::{Address, Bytes}, }; -use evm_adapters::call_tracing::CallTrace; +use evm_adapters::call_tracing::CallTraceArena; use evm_adapters::{fuzz::FuzzedExecutor, Evm, EvmError}; @@ -49,7 +49,7 @@ pub struct TestResult { pub logs: Vec, /// Trace - pub trace: Option, + pub trace: Option, } use std::marker::PhantomData; From f397646d05fe9b10864d18f8f3aee06e7388c393 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:17:51 -0800 Subject: [PATCH 08/34] fmt --- cli/src/forge.rs | 9 +- evm-adapters/src/call_tracing.rs | 418 ++++++++++-------- .../sputnik/cheatcodes/cheatcode_handler.rs | 84 +++- .../cheatcodes/memory_stackstate_owned.rs | 11 +- forge/src/multi_runner.rs | 24 +- 5 files changed, 322 insertions(+), 224 deletions(-) diff --git a/cli/src/forge.rs b/cli/src/forge.rs index c764627ce9606..3d9b5cc5cc9d6 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -106,7 +106,7 @@ fn main() -> eyre::Result<()> { &cfg, &precompiles, ffi, - verbosity > 2 + verbosity > 2, ); test(builder, project, evm, pattern, json, verbosity, allow_failure)?; @@ -331,7 +331,12 @@ fn test>( println!(); if verbosity > 2 { if let Some(trace) = &result.trace { - trace.pretty_print(0, &runner.known_contracts, &runner.evm, "".to_string()); + trace.pretty_print( + 0, + &runner.known_contracts, + &runner.evm, + "".to_string(), + ); } println!(); } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 98981ae3b8908..1942810039c6b 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -1,6 +1,6 @@ use ethers::{ abi::{Abi, FunctionExt, RawLog}, - types::{H160}, + types::H160, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -9,78 +9,85 @@ use ansi_term::Colour; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CallTraceArena { - pub arena: Vec, - pub entry: usize, + pub arena: Vec, + pub entry: usize, } impl Default for CallTraceArena { - fn default() -> Self { - CallTraceArena { - arena: vec![ - CallTraceNode { - parent: None, - children: vec![], - idx: 0, - trace: CallTrace::default() - } - ], - entry: 0 - } - } + fn default() -> Self { + CallTraceArena { + arena: vec![CallTraceNode { + parent: None, + children: vec![], + idx: 0, + trace: CallTrace::default(), + }], + entry: 0, + } + } } impl CallTraceArena { - pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { - if new_trace.depth == 0 { + pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { + if new_trace.depth == 0 { // overwrite self.update(new_trace.clone()); new_trace } else if self.arena[entry].trace.depth == new_trace.depth - 1 { - new_trace.idx = self.arena.len(); - new_trace.location = self.arena[entry].children.len(); - let node = CallTraceNode { - parent: Some(entry), - children: vec![], - idx: self.arena.len(), - trace: new_trace.clone() - }; + new_trace.idx = self.arena.len(); + new_trace.location = self.arena[entry].children.len(); + let node = CallTraceNode { + parent: Some(entry), + children: vec![], + idx: self.arena.len(), + trace: new_trace.clone(), + }; self.arena.push(node); self.arena[entry].children.push(new_trace.idx); new_trace } else { - self.push_trace(*self.arena[entry].children.last().expect("Disconnected trace"), new_trace) + self.push_trace( + *self.arena[entry].children.last().expect("Disconnected trace"), + new_trace, + ) } - } - - pub fn update(&mut self, trace: CallTrace) { - let node = &mut self.arena[trace.idx]; - node.trace.update(trace); - } + } - pub fn inner_number_of_logs(&self, node_idx: usize) -> usize { - self.arena[node_idx].children.iter().fold(0, |accum, idx| { - accum + self.arena[*idx].trace.prelogs.len() + self.arena[*idx].trace.logs.len() + self.inner_number_of_logs(*idx) - }) //+ self.arena[node_idx].trace.prelogs.len() + self.arena[node_idx].trace.logs.len() - } + pub fn update(&mut self, trace: CallTrace) { + let node = &mut self.arena[trace.idx]; + node.trace.update(trace); + } - pub fn inner_number_of_inners(&self, node_idx: usize) -> usize { - self.arena[node_idx].children.iter().fold(0, |accum, idx| { - accum + self.arena[*idx].children.len() + self.inner_number_of_inners(*idx) - }) - } + pub fn inner_number_of_logs(&self, node_idx: usize) -> usize { + self.arena[node_idx].children.iter().fold(0, |accum, idx| { + accum + + self.arena[*idx].trace.prelogs.len() + + self.arena[*idx].trace.logs.len() + + self.inner_number_of_logs(*idx) + }) //+ self.arena[node_idx].trace.prelogs.len() + self.arena[node_idx].trace.logs.len() + } - pub fn next_log_index(&self) -> usize { - self.inner_number_of_logs(self.entry) - } + pub fn inner_number_of_inners(&self, node_idx: usize) -> usize { + self.arena[node_idx].children.iter().fold(0, |accum, idx| { + accum + self.arena[*idx].children.len() + self.inner_number_of_inners(*idx) + }) + } - pub fn print_logs_in_order(&self, node_idx: usize) { - self.arena[node_idx].trace.prelogs.iter().for_each(|(raw, _loc)| println!("prelog {}", raw.topics[0])); - self.arena[node_idx].children.iter().for_each(|idx| { - self.print_logs_in_order(*idx); - }); - self.arena[node_idx].trace.logs.iter().for_each(|raw| println!("log {}", raw.topics[0])); - } + pub fn next_log_index(&self) -> usize { + self.inner_number_of_logs(self.entry) + } + pub fn print_logs_in_order(&self, node_idx: usize) { + self.arena[node_idx] + .trace + .prelogs + .iter() + .for_each(|(raw, _loc)| println!("prelog {}", raw.topics[0])); + self.arena[node_idx].children.iter().for_each(|idx| { + self.print_logs_in_order(*idx); + }); + self.arena[node_idx].trace.logs.iter().for_each(|raw| println!("log {}", raw.topics[0])); + } pub fn pretty_print<'a, S: Clone, E: crate::Evm>( &self, @@ -89,35 +96,40 @@ impl CallTraceArena { evm: &'a E, left: String, ) { - let trace = &self.arena[idx].trace; - - if trace.created { - if let Some((name, (_abi, _code))) = - contracts.iter().find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) - { - println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - return; - } else { - println!("{}→ new @{:?}", left, trace.addr); - return; - } - } - // fuzzy find contracts - if let Some((name, (abi, _code))) = - contracts.iter().find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) + let trace = &self.arena[idx].trace; + + if trace.created { + if let Some((name, (_abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) + { + println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + return + } else { + println!("{}→ new @{:?}", left, trace.addr); + return + } + } + // fuzzy find contracts + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) { - // color the printout by success + // color the printout by success let color = if trace.success { Colour::Green } else { Colour::Red }; - - let mut decoded_output = None; - // search thru abi functions to find matching + + let mut decoded_output = None; + // search thru abi functions to find matching for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == trace.data[0..4] { - let params = func.decode_input(&trace.data[4..]).expect("Bad func data decode"); - let strings = params.iter().map(|param| { - format!("{:?}", param) - }).collect::>().join(", "); + let params = + func.decode_input(&trace.data[4..]).expect("Bad func data decode"); + let strings = params + .iter() + .map(|param| format!("{:?}", param)) + .collect::>() + .join(", "); println!( "{}[{}] {}::{}({})", left, @@ -127,89 +139,112 @@ impl CallTraceArena { strings ); - decoded_output = Some(func.decode_output(&trace.output[..]).expect("Bad func output decode")); + decoded_output = Some( + func.decode_output(&trace.output[..]).expect("Bad func output decode"), + ); } } } let children_idxs = &self.arena[idx].children; if children_idxs.len() == 0 { - trace.prelogs.iter().for_each(|(log, loc)| { - if *loc == 0 { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - found = true; - let params = event.parse_log(log.clone()).expect("Bad event").params; - let strings = params.iter().map(|param| { - format!("{}: {:?}", param.name, param.value) - }).collect::>().join(", "); - println!( - "{}emit {}({})", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), - strings - ); - break 'outer; - } - } - } - if !found { - println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) - ) - } - } - }); + trace.prelogs.iter().for_each(|(log, loc)| { + if *loc == 0 { + let mut found = false; + let right = " ├─ "; + 'outer: for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + found = true; + let params = + event.parse_log(log.clone()).expect("Bad event").params; + let strings = params + .iter() + .map(|param| format!("{}: {:?}", param.name, param.value)) + .collect::>() + .join(", "); + println!( + "{}emit {}({})", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(event_name), + strings + ); + break 'outer + } + } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + } + } + }); } else { - children_idxs.iter().enumerate().for_each(|(i, child_idx)| { - let child_location = self.arena[*child_idx].trace.location; - trace.prelogs.iter().for_each(|(log, loc)| { - if loc == &child_location { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - found = true; - let params = event.parse_log(log.clone()).expect("Bad event").params; - let strings = params.iter().map(|param| { - format!("{}: {:?}", param.name, param.value) - }).collect::>().join(", "); - println!( - "{}emit {}({})", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), - strings - ); - break 'outer; - } - } - } - if !found { - println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) - ) - } - } - }); - if i == children_idxs.len() - 1 && trace.logs.len() == 0 && decoded_output.is_none() { - self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " └─ "); - } else { - self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ "); - } - }); + children_idxs.iter().enumerate().for_each(|(i, child_idx)| { + let child_location = self.arena[*child_idx].trace.location; + trace.prelogs.iter().for_each(|(log, loc)| { + if loc == &child_location { + let mut found = false; + let right = " ├─ "; + 'outer: for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + found = true; + let params = + event.parse_log(log.clone()).expect("Bad event").params; + let strings = params + .iter() + .map(|param| { + format!("{}: {:?}", param.name, param.value) + }) + .collect::>() + .join(", "); + println!( + "{}emit {}({})", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(event_name), + strings + ); + break 'outer + } + } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + } + } + }); + if i == children_idxs.len() - 1 && + trace.logs.len() == 0 && + decoded_output.is_none() + { + self.pretty_print( + *child_idx, + contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " └─ ", + ); + } else { + self.pretty_print( + *child_idx, + contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", + ); + } + }); } trace.logs.iter().enumerate().for_each(|(i, log)| { - let mut found = false; - let mut right = " ├─ "; + let mut found = false; + let mut right = " ├─ "; if i == trace.logs.len() - 1 && decoded_output.is_none() { right = " └─ "; } @@ -218,16 +253,18 @@ impl CallTraceArena { if event.signature() == log.topics[0] { found = true; let params = event.parse_log(log.clone()).expect("Bad event").params; - let strings = params.iter().map(|param| { - format!("{}: {:?}", param.name, param.value) - }).collect::>().join(", "); + let strings = params + .iter() + .map(|param| format!("{}: {:?}", param.name, param.value)) + .collect::>() + .join(", "); println!( "{}emit {}({})", left.to_string().replace("├─", "│") + right, Colour::Cyan.paint(event_name), strings ); - break 'outer; + break 'outer } } } @@ -241,15 +278,17 @@ impl CallTraceArena { }); if let Some(decoded) = decoded_output { - let strings = decoded.iter().map(|param| { - format!("{:?}", param) - }).collect::>().join(", "); - println!( - "{} └─ {} {}", - left.to_string().replace("├─", "│").replace("└─", " "), - Colour::Green.paint("←"), - if strings.len() == 0 { "()" } else { &*strings } - ); + let strings = decoded + .iter() + .map(|param| format!("{:?}", param)) + .collect::>() + .join(", "); + println!( + "{} └─ {} {}", + left.to_string().replace("├─", "│").replace("└─", " "), + Colour::Green.paint("←"), + if strings.len() == 0 { "()" } else { &*strings } + ); } } else { if trace.data.len() >= 4 { @@ -268,9 +307,19 @@ impl CallTraceArena { children_idxs.iter().enumerate().for_each(|(i, child_idx)| { // let inners = inner.inner_number_of_inners(); if i == children_idxs.len() - 1 && trace.logs.len() == 0 { - self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " _└─ "); + self.pretty_print( + *child_idx, + contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " _└─ ", + ); } else { - self.pretty_print(*child_idx, contracts, evm, left.to_string().replace("├─", "│").replace("└─", " ") + " _├─ "); + self.pretty_print( + *child_idx, + contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " _├─ ", + ); } }); @@ -292,13 +341,12 @@ impl CallTraceArena { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CallTraceNode { - pub parent: Option, - pub children: Vec, - pub idx: usize, - pub trace: CallTrace, + pub parent: Option, + pub children: Vec, + pub idx: usize, + pub trace: CallTrace, } - /// Call trace of a tx #[derive(Clone, Default, Debug, Deserialize, Serialize)] pub struct CallTrace { @@ -328,7 +376,6 @@ pub struct CallTrace { } impl CallTrace { - fn update(&mut self, new_trace: Self) { self.success = new_trace.success; self.addr = new_trace.addr; @@ -416,21 +463,22 @@ impl CallTrace { // } } -// very simple fuzzy matching to account for immutables. Will fail for small contracts that are basically all immutable vars +// very simple fuzzy matching to account for immutables. Will fail for small contracts that are +// basically all immutable vars fn diff_score(bytecode1: &Vec, bytecode2: &Vec) -> f64 { - let cutoff_len = usize::min(bytecode1.len(), bytecode2.len()); - let b1 = &bytecode1[..cutoff_len]; - let b2 = &bytecode2[..cutoff_len]; - if cutoff_len == 0 { - return 1.0 - } + let cutoff_len = usize::min(bytecode1.len(), bytecode2.len()); + let b1 = &bytecode1[..cutoff_len]; + let b2 = &bytecode2[..cutoff_len]; + if cutoff_len == 0 { + return 1.0 + } - let mut diff_chars = 0; - for i in 0..cutoff_len { - if b1[i] != b2[i] { - diff_chars += 1; - } - } + let mut diff_chars = 0; + for i in 0..cutoff_len { + if b1[i] != b2[i] { + diff_chars += 1; + } + } - diff_chars as f64 / cutoff_len as f64 + diff_chars as f64 / cutoff_len as f64 } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 51dc70c651abe..be1b5724fff9c 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -248,7 +248,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> // create the executor and wrap it with the cheatcode handler let executor = StackExecutor::new_with_precompiles(state, config, precompiles); - let executor = CheatcodeHandler { handler: executor, enable_ffi, enable_trace, console_logs: Vec::new() }; + let executor = CheatcodeHandler { + handler: executor, + enable_ffi, + enable_trace, + console_logs: Vec::new(), + }; let mut evm = Executor::from_executor(executor, gas_limit); @@ -440,11 +445,19 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn start_trace(&mut self, address: H160, input: Vec, creation: bool, prelogs: usize) -> Option { + fn start_trace( + &mut self, + address: H160, + input: Vec, + creation: bool, + prelogs: usize, + ) -> Option { if self.enable_trace { let mut trace: CallTrace = Default::default(); - // depth only starts tracking at first child substate and is 0. so add 1 when depth is some. - trace.depth = if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; + // depth only starts tracking at first child substate and is 0. so add 1 when depth is + // some. + trace.depth = + if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; trace.addr = address; trace.created = creation; trace.data = input; @@ -453,7 +466,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // we minimize the size of the clone trace = self.state_mut().trace.push_trace(0, trace.clone()); - if trace.depth > 0 && prelogs > 0 { let trace_index = trace.idx; let parent_index = self.state().trace.arena[trace_index].parent.expect("No parent"); @@ -462,12 +474,21 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let child_logs = self.state().trace.inner_number_of_logs(parent_index); if self.raw_logs().len() >= prelogs { - if prelogs.saturating_sub(child_logs).saturating_sub(self.state().trace.arena[parent_index].trace.prelogs.len().saturating_sub(1)) > 0 { - let prelogs = self.raw_logs()[ - self.state().trace.arena[parent_index].trace.prelogs.len() + child_logs - .. - prelogs - ].to_vec(); + if prelogs.saturating_sub(child_logs).saturating_sub( + self.state().trace.arena[parent_index] + .trace + .prelogs + .len() + .saturating_sub(1), + ) > 0 + { + let prelogs = self.raw_logs()[self.state().trace.arena[parent_index] + .trace + .prelogs + .len() + + child_logs.. + prelogs] + .to_vec(); let parent = &mut self.state_mut().trace.arena[parent_index]; if prelogs.len() > 0 { @@ -481,7 +502,10 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if self.raw_logs().len() >= prelogs { let prelogs = self.raw_logs()[0..prelogs].to_vec(); prelogs.into_iter().for_each(|prelog| { - self.state_mut().trace.arena[0].trace.prelogs.push((prelog, trace.location)); + self.state_mut().trace.arena[0] + .trace + .prelogs + .push((prelog, trace.location)); }) } } @@ -491,10 +515,16 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn fill_trace(&mut self, mut new_trace: Option, prelogs: usize, success: bool, output: Option>) { + fn fill_trace( + &mut self, + mut new_trace: Option, + prelogs: usize, + success: bool, + output: Option>, + ) { if let Some(mut new_trace) = new_trace { let mut next = self.raw_logs().len().saturating_sub(1); - + if new_trace.depth > 0 && prelogs > 0 && next > prelogs { next = self.raw_logs().len() - prelogs; } else if new_trace.depth == 0 { @@ -537,7 +567,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, prelogs, false, None); return Capture::Exit((e.into(), Vec::new())) } } @@ -610,7 +640,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Err(error) => { self.fill_trace(trace, prelogs, false, Some(output.clone())); return Capture::Exit((ExitReason::Error(error), output)) - }, + } } } @@ -688,7 +718,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Err(e) => { self.fill_trace(trace, prelogs, false, None); return Capture::Exit((e.into(), None, Vec::new())) - }, + } } }; } @@ -1202,12 +1232,18 @@ mod tests { let mut mapping = BTreeMap::new(); mapping.insert( "Trace".to_string(), - (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ( + compiled.abi.expect("No abi").clone(), + compiled.bin_runtime.expect("No runtime").to_vec(), + ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); mapping.insert( "RecursiveCall".to_string(), - (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ( + compiled.abi.expect("No abi").clone(), + compiled.bin_runtime.expect("No runtime").to_vec(), + ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); } @@ -1242,12 +1278,18 @@ mod tests { let mut mapping = BTreeMap::new(); mapping.insert( "Trace".to_string(), - (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ( + compiled.abi.expect("No abi").clone(), + compiled.bin_runtime.expect("No runtime").to_vec(), + ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); mapping.insert( "RecursiveCall".to_string(), - (compiled.abi.expect("No abi").clone(), compiled.bin_runtime.expect("No runtime").to_vec()), + ( + compiled.abi.expect("No abi").clone(), + compiled.bin_runtime.expect("No runtime").to_vec(), + ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 1bf8284275937..23e2630598c6d 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -6,7 +6,10 @@ use sputnik::{ use crate::call_tracing::CallTraceArena; -use ethers::{abi::RawLog, types::{H160, H256, U256}}; +use ethers::{ + abi::RawLog, + types::{H160, H256, U256}, +}; /// This struct implementation is copied from [upstream](https://github.com/rust-blockchain/evm/blob/5ecf36ce393380a89c6f1b09ef79f686fe043624/src/executor/stack/state.rs#L412) and modified to own the Backend type. /// @@ -33,11 +36,7 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { - Self { - backend, - substate: MemoryStackSubstate::new(metadata), - trace: Default::default(), - } + Self { backend, substate: MemoryStackSubstate::new(metadata), trace: Default::default() } } } diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index e9c982d4bc01c..89809383166c4 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -1,5 +1,5 @@ -use ethers::prelude::artifacts::CompactContract; use crate::{runner::TestResult, ContractRunner}; +use ethers::prelude::artifacts::CompactContract; use evm_adapters::Evm; use ethers::{ @@ -60,12 +60,14 @@ impl MultiContractRunnerBuilder { // artifacts let contracts = output.into_artifacts(); let mut known_contracts: BTreeMap)> = Default::default(); - let mut deployed_contracts: BTreeMap)> = Default::default(); + let mut deployed_contracts: BTreeMap)> = + Default::default(); use std::any::Any; for (fname, contract) in contracts { let c: &dyn Any = &contract as &dyn Any; - let compact_contract = c.downcast_ref::().expect("Wasn't a compact contract"); + let compact_contract = + c.downcast_ref::().expect("Wasn't a compact contract"); let runtime_code = compact_contract.bin_runtime.as_ref().unwrap().clone(); let bytecode = compact_contract.bin.as_ref().unwrap(); let abi = compact_contract.abi.as_ref().unwrap(); @@ -83,11 +85,7 @@ impl MultiContractRunnerBuilder { } } let split = fname.split(":").collect::>(); - let contract_name = if split.len() > 1 { - split[1] - } else { - split[0] - }; + let contract_name = if split.len() > 1 { split[1] } else { split[0] }; known_contracts.insert(contract_name.to_string(), (abi.clone(), runtime_code.to_vec())); } @@ -267,8 +265,14 @@ mod tests { let backend = new_backend(&env, Default::default()); // important to instantiate the VM with cheatcodes let precompiles = PRECOMPILES_MAP.clone(); - let evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, false); + let evm = Executor::new_with_cheatcodes( + backend, + gas_limit, + &config, + &precompiles, + false, + false, + ); let mut runner = runner(evm); let results = runner.test(Regex::new(".*").unwrap()).unwrap(); From 95cb702a17989c1e89c9830332ea8d7acb4fab31 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:30:06 -0800 Subject: [PATCH 09/34] fix after master merge --- forge/src/multi_runner.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 89809383166c4..cd413d1274971 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -68,8 +68,8 @@ impl MultiContractRunnerBuilder { let c: &dyn Any = &contract as &dyn Any; let compact_contract = c.downcast_ref::().expect("Wasn't a compact contract"); - let runtime_code = compact_contract.bin_runtime.as_ref().unwrap().clone(); - let bytecode = compact_contract.bin.as_ref().unwrap(); + let runtime_code = compact_contract.bin_runtime.as_ref().unwrap().clone().into_bytes().expect("Linking not supported in tracing"); + let bytecode = compact_contract.bin.as_ref().unwrap().clone().into_bytes().expect("Linking not supported in tracing"); let abi = compact_contract.abi.as_ref().unwrap(); if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) { if abi.functions().any(|func| func.name.starts_with("test")) { From 0594faa1db09df5507d819f4052ab7ae839a78f3 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:41:22 -0800 Subject: [PATCH 10/34] fix tests post master merge --- .../src/sputnik/cheatcodes/cheatcode_handler.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index be1b5724fff9c..08a88603be4f2 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -1216,7 +1216,7 @@ mod tests { let compiled = COMPILED.find("Trace").expect("could not find contract"); let (addr, _, _, _) = - evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); + evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); // after the evm call is done, we call `logs` and print it all to the user let (_, _, _, logs) = evm @@ -1234,7 +1234,7 @@ mod tests { "Trace".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").to_vec(), + compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec(), ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); @@ -1242,7 +1242,7 @@ mod tests { "RecursiveCall".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").to_vec(), + compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec(), ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); @@ -1262,7 +1262,7 @@ mod tests { let compiled = COMPILED.find("Trace").expect("could not find contract"); let (addr, _, _, _) = - evm.deploy(Address::zero(), compiled.bin.unwrap().clone(), 0.into()).unwrap(); + evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); // after the evm call is done, we call `logs` and print it all to the user let (_, _, _, logs) = evm @@ -1280,7 +1280,7 @@ mod tests { "Trace".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").to_vec(), + compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec() ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); @@ -1288,7 +1288,7 @@ mod tests { "RecursiveCall".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").to_vec(), + compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec() ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); From 34f75e2544772b1616ef8b74a50e97508efd4565 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:44:35 -0800 Subject: [PATCH 11/34] warning fixes --- .../src/sputnik/cheatcodes/cheatcode_handler.rs | 13 ++++++------- .../sputnik/cheatcodes/memory_stackstate_owned.rs | 1 - evm-adapters/src/sputnik/mod.rs | 2 +- forge/src/multi_runner.rs | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 08a88603be4f2..1ef491ab39cc0 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -21,13 +21,13 @@ use sputnik::{ use std::{process::Command, rc::Rc}; use ethers::{ - abi::{Abi, RawLog, Token}, + abi::{RawLog, Token}, contract::EthLogDecode, core::{abi::AbiDecode, k256::ecdsa::SigningKey, utils}, signers::{LocalWallet, Signer}, types::{Address, H160, H256, U256}, }; -use std::{collections::BTreeMap, convert::Infallible}; +use std::convert::Infallible; use once_cell::sync::Lazy; @@ -469,7 +469,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if trace.depth > 0 && prelogs > 0 { let trace_index = trace.idx; let parent_index = self.state().trace.arena[trace_index].parent.expect("No parent"); - let next_log_index = self.state().trace.next_log_index(); let child_logs = self.state().trace.inner_number_of_logs(parent_index); @@ -517,12 +516,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn fill_trace( &mut self, - mut new_trace: Option, + new_trace: Option, prelogs: usize, success: bool, output: Option>, ) { - if let Some(mut new_trace) = new_trace { + if let Some(new_trace) = new_trace { let mut next = self.raw_logs().len().saturating_sub(1); if new_trace.depth > 0 && prelogs > 0 && next > prelogs { @@ -1219,7 +1218,7 @@ mod tests { evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); // after the evm call is done, we call `logs` and print it all to the user - let (_, _, _, logs) = evm + let (_, _, _, _) = evm .call::<(), _, _>( Address::zero(), addr, @@ -1265,7 +1264,7 @@ mod tests { evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); // after the evm call is done, we call `logs` and print it all to the user - let (_, _, _, logs) = evm + let (_, _, _, _) = evm .call::<(), _, _>( Address::zero(), addr, diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 23e2630598c6d..b29831e01d289 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -7,7 +7,6 @@ use sputnik::{ use crate::call_tracing::CallTraceArena; use ethers::{ - abi::RawLog, types::{H160, H256, U256}, }; diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 1a22aded7b230..9f11df5269594 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -8,7 +8,7 @@ pub mod cheatcodes; pub mod state; use ethers::{ - abi::{Abi, RawLog}, + abi::RawLog, providers::Middleware, types::{Address, H160, H256, U256}, }; diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index cd413d1274971..d3e1e00a2657e 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -5,7 +5,7 @@ use evm_adapters::Evm; use ethers::{ abi::Abi, prelude::ArtifactOutput, - solc::{Artifact, Project}, + solc::Project, types::{Address, U256}, }; From 62406e1d940e99b483bc7b10f4dcc921c84401d8 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 00:45:47 -0800 Subject: [PATCH 12/34] fmt --- .../sputnik/cheatcodes/cheatcode_handler.rs | 50 ++++++++++++++++--- .../cheatcodes/memory_stackstate_owned.rs | 4 +- forge/src/multi_runner.rs | 16 +++++- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 1ef491ab39cc0..8b59197a96570 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -1214,8 +1214,13 @@ mod tests { Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); let compiled = COMPILED.find("Trace").expect("could not find contract"); - let (addr, _, _, _) = - evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); + let (addr, _, _, _) = evm + .deploy( + Address::zero(), + compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), + 0.into(), + ) + .unwrap(); // after the evm call is done, we call `logs` and print it all to the user let (_, _, _, _) = evm @@ -1233,7 +1238,13 @@ mod tests { "Trace".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec(), + compiled + .bin_runtime + .expect("No runtime") + .clone() + .into_bytes() + .expect("Linking?") + .to_vec(), ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); @@ -1241,7 +1252,13 @@ mod tests { "RecursiveCall".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec(), + compiled + .bin_runtime + .expect("No runtime") + .clone() + .into_bytes() + .expect("Linking?") + .to_vec(), ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); @@ -1260,8 +1277,13 @@ mod tests { Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); let compiled = COMPILED.find("Trace").expect("could not find contract"); - let (addr, _, _, _) = - evm.deploy(Address::zero(), compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), 0.into()).unwrap(); + let (addr, _, _, _) = evm + .deploy( + Address::zero(), + compiled.bin.unwrap().clone().into_bytes().expect("shouldn't be linked"), + 0.into(), + ) + .unwrap(); // after the evm call is done, we call `logs` and print it all to the user let (_, _, _, _) = evm @@ -1279,7 +1301,13 @@ mod tests { "Trace".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec() + compiled + .bin_runtime + .expect("No runtime") + .clone() + .into_bytes() + .expect("Linking?") + .to_vec(), ), ); let compiled = COMPILED.find("RecursiveCall").expect("could not find contract"); @@ -1287,7 +1315,13 @@ mod tests { "RecursiveCall".to_string(), ( compiled.abi.expect("No abi").clone(), - compiled.bin_runtime.expect("No runtime").clone().into_bytes().expect("Linking?").to_vec() + compiled + .bin_runtime + .expect("No runtime") + .clone() + .into_bytes() + .expect("Linking?") + .to_vec(), ), ); evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index b29831e01d289..642673c60c65a 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -6,9 +6,7 @@ use sputnik::{ use crate::call_tracing::CallTraceArena; -use ethers::{ - types::{H160, H256, U256}, -}; +use ethers::types::{H160, H256, U256}; /// This struct implementation is copied from [upstream](https://github.com/rust-blockchain/evm/blob/5ecf36ce393380a89c6f1b09ef79f686fe043624/src/executor/stack/state.rs#L412) and modified to own the Backend type. /// diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index d3e1e00a2657e..0cc7b1697fce0 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -68,8 +68,20 @@ impl MultiContractRunnerBuilder { let c: &dyn Any = &contract as &dyn Any; let compact_contract = c.downcast_ref::().expect("Wasn't a compact contract"); - let runtime_code = compact_contract.bin_runtime.as_ref().unwrap().clone().into_bytes().expect("Linking not supported in tracing"); - let bytecode = compact_contract.bin.as_ref().unwrap().clone().into_bytes().expect("Linking not supported in tracing"); + let runtime_code = compact_contract + .bin_runtime + .as_ref() + .unwrap() + .clone() + .into_bytes() + .expect("Linking not supported in tracing"); + let bytecode = compact_contract + .bin + .as_ref() + .unwrap() + .clone() + .into_bytes() + .expect("Linking not supported in tracing"); let abi = compact_contract.abi.as_ref().unwrap(); if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) { if abi.functions().any(|func| func.name.starts_with("test")) { From 79d210f8c38a1029114cd77700643be2f84703f3 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 17:00:34 -0800 Subject: [PATCH 13/34] lots of fixes --- cli/src/forge.rs | 3 + evm-adapters/src/call_tracing.rs | 489 +++++++++--------- evm-adapters/src/lib.rs | 3 +- .../sputnik/cheatcodes/cheatcode_handler.rs | 124 +++-- .../cheatcodes/memory_stackstate_owned.rs | 27 +- evm-adapters/src/sputnik/evm.rs | 8 +- evm-adapters/src/sputnik/mod.rs | 4 +- forge/src/runner.rs | 13 +- 8 files changed, 361 insertions(+), 310 deletions(-) diff --git a/cli/src/forge.rs b/cli/src/forge.rs index 3d9b5cc5cc9d6..ead99742bbe9a 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -330,10 +330,13 @@ fn test>( println!(); if verbosity > 2 { + let mut identified = Default::default(); if let Some(trace) = &result.trace { + // deploy -> setup -> test -> failed -> setup -> test -> failed trace.pretty_print( 0, &runner.known_contracts, + &mut identified, &runner.evm, "".to_string(), ); diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 1942810039c6b..46bd1f76663b4 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -27,6 +27,11 @@ impl Default for CallTraceArena { } } +pub enum Output { + Token(Vec), + Raw(Vec), +} + impl CallTraceArena { pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { if new_trace.depth == 0 { @@ -93,33 +98,155 @@ impl CallTraceArena { &self, idx: usize, contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, evm: &'a E, left: String, ) { let trace = &self.arena[idx].trace; - if trace.created { - if let Some((name, (_abi, _code))) = contracts + // color the printout by success + let color = if trace.success { Colour::Green } else { Colour::Red }; + + let maybe_found; + { + if let Some((name, abi)) = identified_contracts.get(&trace.addr) { + maybe_found = Some((name.clone(), abi.clone())); + } else { + maybe_found = None; + } + } + if let Some((name, abi)) = maybe_found { + if trace.created { + println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + Self::print_logs(trace, &abi, &left); + println!( + "{} └─ {} {} bytes of code", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() / 2 + ); + return; + } + let output = Self::print_func_call(trace, &abi, &name, color, &left); + self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + Self::print_logs(trace, &abi, &left); + Self::print_output(color, output, left); + } else { + if trace.created { + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + Self::print_logs(trace, &abi, &left); + println!( + "{} └─ {} {} bytes of code", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() / 2 + ); + return + } else { + println!("{}→ new @{:?}", left, trace.addr); + self.print_unknown(color, idx, trace, contracts, identified_contracts, evm, left.clone()); + return + } + } + + if let Some((name, (abi, _code))) = contracts .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) { - println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - return + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + // re-enter this function at this level if we found the contract + self.pretty_print(idx, contracts, identified_contracts, evm, left); } else { - println!("{}→ new @{:?}", left, trace.addr); - return + self.print_unknown(color, idx, trace, contracts, identified_contracts, evm, left.clone()); } } - // fuzzy find contracts - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.05) - { - // color the printout by success - let color = if trace.success { Colour::Green } else { Colour::Red }; + } - let mut decoded_output = None; - // search thru abi functions to find matching + pub fn print_unknown<'a, S: Clone, E: crate::Evm>( + &self, + color: Colour, + idx: usize, + trace: &CallTrace, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + left: String + ) { + if trace.data.len() >= 4 { + println!( + "{}{:x}::{}({})", + left, + trace.addr, + hex::encode(&trace.data[0..4]), + hex::encode(&trace.data[4..]) + ); + } else { + println!("{}{:x}::({})", left, trace.addr, hex::encode(&trace.data)); + } + + let children_idxs = &self.arena[idx].children; + children_idxs.iter().enumerate().for_each(|(i, child_idx)| { + // let inners = inner.inner_number_of_inners(); + if i == children_idxs.len() - 1 && trace.logs.len() == 0 { + self.pretty_print( + *child_idx, + contracts, + identified_contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " └─ ", + ); + } else { + self.pretty_print( + *child_idx, + contracts, + identified_contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", + ); + } + }); + + let mut right = " ├─ "; + + trace.logs.iter().enumerate().for_each(|(i, log)| { + if i == trace.logs.len() - 1 { + right = " └─ "; + } + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + }); + + if !trace.created { + println!( + "{} └─ {} {}", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + if trace.output.len() == 0 { "()".to_string() } else { "0x".to_string() + &hex::encode(&trace.output) } + ); + } else { + println!( + "{} └─ {} {} bytes of code", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() / 2 + ); + } + + } + + /// Prints function call, optionally returning the decoded output + pub fn print_func_call(trace: &CallTrace, abi: &Abi, name: &String, color: Colour, left: &String) -> Output { + if trace.data.len() >= 4 { for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == trace.data[0..4] { @@ -130,6 +257,7 @@ impl CallTraceArena { .map(|param| format!("{:?}", param)) .collect::>() .join(", "); + println!( "{}[{}] {}::{}({})", left, @@ -139,120 +267,48 @@ impl CallTraceArena { strings ); - decoded_output = Some( + return Output::Token( func.decode_output(&trace.output[..]).expect("Bad func output decode"), ); } } } + } else { + // fallback function? + println!( + "{}[{}] {}::fallback()", + left, + trace.cost, + color.paint(name), + ); - let children_idxs = &self.arena[idx].children; - if children_idxs.len() == 0 { - trace.prelogs.iter().for_each(|(log, loc)| { - if *loc == 0 { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - found = true; - let params = - event.parse_log(log.clone()).expect("Bad event").params; - let strings = params - .iter() - .map(|param| format!("{}: {:?}", param.name, param.value)) - .collect::>() - .join(", "); - println!( - "{}emit {}({})", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), - strings - ); - break 'outer - } - } - } - if !found { - println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) - ) - } - } - }); - } else { - children_idxs.iter().enumerate().for_each(|(i, child_idx)| { - let child_location = self.arena[*child_idx].trace.location; - trace.prelogs.iter().for_each(|(log, loc)| { - if loc == &child_location { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - found = true; - let params = - event.parse_log(log.clone()).expect("Bad event").params; - let strings = params - .iter() - .map(|param| { - format!("{}: {:?}", param.name, param.value) - }) - .collect::>() - .join(", "); - println!( - "{}emit {}({})", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), - strings - ); - break 'outer - } - } - } - if !found { - println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) - ) - } - } - }); - if i == children_idxs.len() - 1 && - trace.logs.len() == 0 && - decoded_output.is_none() - { - self.pretty_print( - *child_idx, - contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " └─ ", - ); - } else { - self.pretty_print( - *child_idx, - contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", - ); - } - }); - } + return Output::Raw(trace.output[..].to_vec()); + } + + + println!( + "{}[{}] {}::{}({})", + left, + trace.cost, + color.paint(name), + if trace.data.len() >= 4 { hex::encode(&trace.data[0..4]) } else { hex::encode(&trace.data[..]) }, + if trace.data.len() >= 4 { hex::encode(&trace.data[4..]) } else { hex::encode(&vec![][..])} + ); + + Output::Raw(trace.output[..].to_vec()) + } - trace.logs.iter().enumerate().for_each(|(i, log)| { + pub fn print_prelogs(prelogs: &Vec<(RawLog, usize)>, location: usize, abi: &Abi, left: &String) { + prelogs.iter().for_each(|(log, loc)| { + if *loc == location { let mut found = false; - let mut right = " ├─ "; - if i == trace.logs.len() - 1 && decoded_output.is_none() { - right = " └─ "; - } + let right = " ├─ "; 'outer: for (event_name, overloaded_events) in abi.events.iter() { for event in overloaded_events.iter() { if event.signature() == log.topics[0] { found = true; - let params = event.parse_log(log.clone()).expect("Bad event").params; + let params = + event.parse_log(log.clone()).expect("Bad event").params; let strings = params .iter() .map(|param| format!("{}: {:?}", param.name, param.value)) @@ -275,10 +331,76 @@ impl CallTraceArena { Colour::Cyan.paint(format!("{:?}", log)) ) } + } + }); + } + + pub fn print_children_and_prelogs<'a, S: Clone, E: crate::Evm>( + &self, + idx: usize, + trace: &CallTrace, + abi: &Abi, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + left: String + ) { + let children_idxs = &self.arena[idx].children; + if children_idxs.len() == 0 { + Self::print_prelogs(&trace.prelogs, 0, abi, &left); + } else { + children_idxs.iter().for_each(|child_idx| { + let child_location = self.arena[*child_idx].trace.location; + Self::print_prelogs(&trace.prelogs, child_location, abi, &left); + self.pretty_print( + *child_idx, + contracts, + identified_contracts, + evm, + left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", + ); }); + } + } + + pub fn print_logs(trace: &CallTrace, abi: &Abi, left: &String) { + trace.logs.iter().for_each(|log| { + let mut found = false; + let right = " ├─ "; + 'outer: for (event_name, overloaded_events) in abi.events.iter() { + for event in overloaded_events.iter() { + if event.signature() == log.topics[0] { + found = true; + let params = event.parse_log(log.clone()).expect("Bad event").params; + let strings = params + .iter() + .map(|param| format!("{}: {:?}", param.name, param.value)) + .collect::>() + .join(", "); + println!( + "{}emit {}({})", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(event_name), + strings + ); + break 'outer + } + } + } + if !found { + println!( + "{}emit {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", log)) + ) + } + }); + } - if let Some(decoded) = decoded_output { - let strings = decoded + pub fn print_output(color: Colour, output: Output, left: String) { + match output { + Output::Token(token) => { + let strings = token .iter() .map(|param| format!("{:?}", param)) .collect::>() @@ -286,55 +408,18 @@ impl CallTraceArena { println!( "{} └─ {} {}", left.to_string().replace("├─", "│").replace("└─", " "), - Colour::Green.paint("←"), + color.paint("←"), if strings.len() == 0 { "()" } else { &*strings } ); } - } else { - if trace.data.len() >= 4 { + Output::Raw(bytes) => { println!( - "{}{:x}::{}({})", - left, - trace.addr, - hex::encode(&trace.data[0..4]), - hex::encode(&trace.data[4..]) + "{} └─ {} {}", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + if bytes.len() == 0 { "()".to_string() } else { "0x".to_string() + &hex::encode(&bytes) } ); - } else { - println!("{}{:x}::({})", left, trace.addr, hex::encode(&trace.data)); } - - let children_idxs = &self.arena[idx].children; - children_idxs.iter().enumerate().for_each(|(i, child_idx)| { - // let inners = inner.inner_number_of_inners(); - if i == children_idxs.len() - 1 && trace.logs.len() == 0 { - self.pretty_print( - *child_idx, - contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " _└─ ", - ); - } else { - self.pretty_print( - *child_idx, - contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " _├─ ", - ); - } - }); - - let mut right = " _├─ "; - - trace.logs.iter().enumerate().for_each(|(i, log)| { - if i == trace.logs.len() - 1 { - right = " _└─ "; - } - println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) - ) - }); } } } @@ -371,8 +456,6 @@ pub struct CallTrace { /// Logs #[serde(skip)] pub logs: Vec, - /// inner calls - pub inner: Vec, } impl CallTrace { @@ -384,83 +467,7 @@ impl CallTrace { self.logs = new_trace.logs; self.data = new_trace.data; self.addr = new_trace.addr; - // we dont update inner because the temporary new_trace doesnt track inner calls } - - // pub fn inner_number_of_logs(&self) -> usize { - // // only count child logs - // let mut total = 0; - // if self.inner.len() > 0 { - // self.inner.iter().for_each(|inner| { - // total += inner.inner_number_of_logs(); - // }); - // } - // total += self.logs.len() + self.prelogs.len(); - // total - // } - - // pub fn logs_up_to_depth_loc(&self, depth: usize, loc: usize) -> usize { - // if depth == 0 { - // return 0; - // } - // let target = self.get_trace(depth, loc).expect("huh?"); - // let parent = self.get_trace(target.depth - 1, target.parent_location).expect("huh?"); - // let siblings = &parent.inner[..loc]; - // let mut total = target.logs.len() + target.prelogs.len(); - // for sibling in siblings.iter() { - // total += sibling.logs.len() + sibling.prelogs.len(); - // } - // total += self.logs_up_to_depth_loc(target.depth - 1, target.parent_location); - // total - // } - - // pub fn next_log_index(&self, depth: usize, loc: usize) -> usize { - // let total = self.logs_up_to_depth_loc(depth, loc); - // let target = self.get_trace(depth, loc).expect("huh?"); - // total + target.inner_number_of_logs() - // } - - // pub fn inner_number_of_inners(&self) -> usize { - // // only count child logs - // let mut total = 0; - // if self.inner.len() > 0 { - // self.inner.iter().for_each(|inner| { - // total += inner.inner_number_of_inners(); - // }); - // } - // total += self.inner.len(); - // total - // } - - // pub fn get_trace(&self, depth: usize, location: usize) -> Option<&CallTrace> { - // if self.depth == depth && self.location == location { - // return Some(&self) - // } else { - // if self.depth != depth { - // for inner in self.inner.iter() { - // if let Some(trace) = inner.get_trace(depth, location) { - // return Some(trace) - // } - // } - // } - // } - // return None - // } - - // pub fn get_trace_mut(&mut self, depth: usize, location: usize) -> Option<&mut CallTrace> { - // if self.depth == depth && self.location == location { - // return Some(self) - // } else { - // if self.depth != depth { - // for inner in self.inner.iter_mut() { - // if let Some(trace) = inner.get_trace_mut(depth, location) { - // return Some(trace) - // } - // } - // } - // } - // return None - // } } // very simple fuzzy matching to account for immutables. Will fail for small contracts that are diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 002309de40fc1..4a7ebad92b1ae 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -95,10 +95,11 @@ pub trait Evm { } } - fn trace(&self) -> Option { + fn traces(&self) -> Option> { None } + fn reset_traces(&mut self) {} /// Executes the specified EVM call against the state // TODO: Should we just make this take a `TransactionRequest` or other more // ergonomic type? diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 8b59197a96570..13e95b3d37e07 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -124,8 +124,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor (s, v), - Capture::Trap(_) => unreachable!(), + Capture::Exit((s, v)) => { + self.state_mut().increment_call_index(); + (s, v) + }, + Capture::Trap(_) => { + self.state_mut().increment_call_index(); + unreachable!() + }, } } @@ -152,8 +158,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor s, - Capture::Trap(_) => unreachable!(), + Capture::Exit((s, _, _)) => { + self.state_mut().increment_call_index(); + s + }, + Capture::Trap(_) => { + self.state_mut().increment_call_index(); + unreachable!() + }, } } @@ -170,8 +182,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Option { - Some(self.state().trace.clone()) + fn traces(&self) -> Option> { + Some(self.state().traces.clone()) + } + + fn reset_traces(&mut self) { + self.state_mut().reset_traces(); } fn logs(&self) -> Vec { @@ -464,35 +480,36 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // we should probably delay having the input and other stuff so // we minimize the size of the clone - trace = self.state_mut().trace.push_trace(0, trace.clone()); + trace = self.state_mut().trace_mut().push_trace(0, trace.clone()); if trace.depth > 0 && prelogs > 0 { let trace_index = trace.idx; - let parent_index = self.state().trace.arena[trace_index].parent.expect("No parent"); - - let child_logs = self.state().trace.inner_number_of_logs(parent_index); + let parent_index = self.state().trace().arena[trace_index].parent.expect("No parent"); + let child_logs = self.state().trace().inner_number_of_logs(parent_index); if self.raw_logs().len() >= prelogs { if prelogs.saturating_sub(child_logs).saturating_sub( - self.state().trace.arena[parent_index] + self.state().trace().arena[parent_index] .trace .prelogs .len() .saturating_sub(1), ) > 0 { - let prelogs = self.raw_logs()[self.state().trace.arena[parent_index] + let prelogs = self.state().substate.logs().to_vec()[self.state().trace().arena[parent_index] .trace .prelogs .len() + child_logs.. prelogs] .to_vec(); - let parent = &mut self.state_mut().trace.arena[parent_index]; + let parent = &mut self.state_mut().trace_mut().arena[parent_index]; if prelogs.len() > 0 { prelogs.into_iter().for_each(|prelog| { - parent.trace.prelogs.push((prelog, trace.location)); + if prelog.address == parent.trace.addr { + parent.trace.prelogs.push((RawLog { topics: prelog.topics, data: prelog.data }, trace.location)); + } }); } } @@ -501,7 +518,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if self.raw_logs().len() >= prelogs { let prelogs = self.raw_logs()[0..prelogs].to_vec(); prelogs.into_iter().for_each(|prelog| { - self.state_mut().trace.arena[0] + self.state_mut().trace_mut().arena[0] .trace .prelogs .push((prelog, trace.location)); @@ -517,28 +534,19 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn fill_trace( &mut self, new_trace: Option, - prelogs: usize, success: bool, output: Option>, ) { if let Some(new_trace) = new_trace { - let mut next = self.raw_logs().len().saturating_sub(1); - - if new_trace.depth > 0 && prelogs > 0 && next > prelogs { - next = self.raw_logs().len() - prelogs; - } else if new_trace.depth == 0 { - next += 1; - } - - { - if self.raw_logs().len() > next { - let new_logs = self.raw_logs()[next..].to_vec(); - self.state_mut().trace.arena[new_trace.idx].trace.logs.extend(new_logs); - } - } - + let logs = self.state().substate.logs().to_vec(); + let applicable_logs = logs.into_iter().filter(|log| log.address == new_trace.addr).collect::>(); + let prelogs = self.state_mut().trace_mut().arena[new_trace.idx].trace.prelogs.len(); + let curr_logs = self.state_mut().trace_mut().arena[new_trace.idx].trace.logs.len(); + applicable_logs[prelogs+curr_logs..].to_vec().into_iter().for_each(|log| { + self.state_mut().trace_mut().arena[new_trace.idx].trace.logs.push(RawLog { topics: log.topics, data: log.data }); + }); let used_gas = self.handler.used_gas(); - let trace = &mut self.state_mut().trace.arena[new_trace.idx].trace; + let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; trace.output = output.unwrap_or(vec![]); trace.cost = used_gas; trace.success = success; @@ -566,7 +574,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((e.into(), Vec::new())) } } @@ -607,7 +615,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())) } @@ -617,7 +625,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().transfer(transfer) { Ok(()) => (), Err(e) => { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitReason::Error(e), Vec::new())) } @@ -637,14 +645,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.log(address, topics, data) { Ok(_) => continue, Err(error) => { - self.fill_trace(trace, prelogs, false, Some(output.clone())); + self.fill_trace(trace, false, Some(output.clone())); return Capture::Exit((ExitReason::Error(error), output)) } } } let _ = self.state_mut().metadata_mut().gasometer_mut().record_cost(cost); - self.fill_trace(trace, prelogs, true, Some(output.clone())); + self.fill_trace(trace, true, Some(output.clone())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(exit_status), output)) } @@ -656,7 +664,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } PrecompileFailure::Fatal { exit_status } => ExitReason::Fatal(exit_status), }; - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((e, Vec::new())) } @@ -672,22 +680,22 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // reason); match reason { ExitReason::Succeed(s) => { - self.fill_trace(trace, prelogs, true, Some(runtime.machine().return_value())); + self.fill_trace(trace, true, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) } ExitReason::Error(e) => { - self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Error(e), Vec::new())) } ExitReason::Revert(e) => { - self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, false, Some(runtime.machine().return_value())); let _ = self.handler.exit_substate(StackExitKind::Reverted); Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) } ExitReason::Fatal(e) => { - self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, false, Some(runtime.machine().return_value())); self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Fatal(e), Vec::new())) @@ -715,7 +723,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((e.into(), None, Vec::new())) } } @@ -740,13 +748,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())) } } if self.balance(caller) < value { - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())) } @@ -775,13 +783,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> { if self.code_size(address) != U256::zero() { let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } if self.handler.nonce(address) > U256::zero() { let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } @@ -794,7 +802,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Ok(()) => (), Err(e) => { let _ = self.handler.exit_substate(StackExitKind::Reverted); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } @@ -817,7 +825,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Err(e) = check_first_byte(self.config(), &out) { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit((e.into(), None, Vec::new())) } @@ -825,7 +833,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if out.len() > limit { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); return Capture::Exit(( ExitError::CreateContractLimit.into(), None, @@ -839,12 +847,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let e = self.handler.exit_substate(StackExitKind::Succeeded); self.state_mut().set_code(address, out.clone()); try_or_fail!(e); - self.fill_trace(trace, prelogs, true, Some(out)); + self.fill_trace(trace, true, Some(out)); Capture::Exit((ExitReason::Succeed(s), Some(address), Vec::new())) } Err(e) => { let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } @@ -852,18 +860,18 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ExitReason::Error(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } ExitReason::Revert(e) => { let _ = self.handler.exit_substate(StackExitKind::Reverted); - self.fill_trace(trace, prelogs, false, Some(runtime.machine().return_value())); + self.fill_trace(trace, false, Some(runtime.machine().return_value())); Capture::Exit((ExitReason::Revert(e), None, runtime.machine().return_value())) } ExitReason::Fatal(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, prelogs, false, None); + self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Fatal(e), None, Vec::new())) } } @@ -1261,7 +1269,8 @@ mod tests { .to_vec(), ), ); - evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); + let mut identified = Default::default(); + evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, "".to_string()); } #[test] @@ -1324,6 +1333,7 @@ mod tests { .to_vec(), ), ); - evm.state().trace.pretty_print(0, &mapping, &evm, "".to_string()); + let mut identified = Default::default(); + evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, "".to_string()); } } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 642673c60c65a..61e0dc635a4dc 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -17,23 +17,36 @@ use ethers::types::{H160, H256, U256}; pub struct MemoryStackStateOwned<'config, B> { pub backend: B, pub substate: MemoryStackSubstate<'config>, - pub trace: CallTraceArena, + pub call_index: usize, + pub traces: Vec, } -// pub struct IndexedLog { -// pub index: usize, -// pub log: RawLog, -// } - impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn deposit(&mut self, address: H160, value: U256) { self.substate.deposit(address, value, &self.backend); } + + pub fn increment_call_index(&mut self) { + self.traces.push(Default::default()); + self.call_index += 1; + } + pub fn trace_mut(&mut self) -> &mut CallTraceArena { + &mut self.traces[self.call_index] + } + + pub fn trace(&self) -> &CallTraceArena { + &self.traces[self.call_index] + } + + pub fn reset_traces(&mut self) { + self.traces = vec![Default::default()]; + self.call_index = 0; + } } impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { - Self { backend, substate: MemoryStackSubstate::new(metadata), trace: Default::default() } + Self { backend, substate: MemoryStackSubstate::new(metadata), call_index: 0, traces: vec![Default::default()] } } } diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index fa332575d5ed7..0b75e4019ca70 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -103,8 +103,12 @@ where self.executor.state().code(address) } - fn trace(&self) -> Option { - self.executor.trace() + fn traces(&self) -> Option> { + self.executor.traces() + } + + fn reset_traces(&mut self) { + self.executor.reset_traces() } /// Deploys the provided contract bytecode diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 9f11df5269594..f5950d6dfc2b6 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -89,10 +89,12 @@ pub trait SputnikExecutor { fn raw_logs(&self) -> Vec; /// Gets a trace - fn trace(&self) -> Option { + fn traces(&self) -> Option> { None } + fn reset_traces(&mut self) {} + /// Returns a vector of string parsed logs that occurred during the previous VM /// execution fn logs(&self) -> Vec; diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 3aa111e13542b..539f6b54f434e 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -173,6 +173,8 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { logs.extend_from_slice(&setup_logs); } + self.evm.reset_traces(); + let (status, reason, gas_used, logs) = match self.evm.call::<(), _, _>( self.sender, self.address, @@ -195,6 +197,15 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { } }, }; + + let mut trace = None; + if let Some(traces) = self.evm.traces() { + if traces.len() > 0 { + trace = Some(traces[0].clone()); + } + } + self.evm.reset_traces(); + let success = self.evm.check_success(self.address, &status, should_fail); let duration = Instant::now().duration_since(start); tracing::debug!(?duration, %success, %gas_used); @@ -205,7 +216,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { gas_used: Some(gas_used), counterexample: None, logs, - trace: self.evm.trace(), + trace, }) } From 4b91be7346f74fbf54fd2b918217960ef5765001 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 17:00:49 -0800 Subject: [PATCH 14/34] fmt --- evm-adapters/src/call_tracing.rs | 111 ++++++++++++++---- .../sputnik/cheatcodes/cheatcode_handler.rs | 65 ++++++---- .../cheatcodes/memory_stackstate_owned.rs | 7 +- forge/src/runner.rs | 2 +- 4 files changed, 133 insertions(+), 52 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 46bd1f76663b4..5ef227d3495e7 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -118,7 +118,15 @@ impl CallTraceArena { if let Some((name, abi)) = maybe_found { if trace.created { println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + self.print_children_and_prelogs( + idx, + trace, + &abi, + contracts, + identified_contracts, + evm, + left.clone(), + ); Self::print_logs(trace, &abi, &left); println!( "{} └─ {} {} bytes of code", @@ -126,10 +134,18 @@ impl CallTraceArena { color.paint("←"), trace.output.len() / 2 ); - return; + return } let output = Self::print_func_call(trace, &abi, &name, color, &left); - self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + self.print_children_and_prelogs( + idx, + trace, + &abi, + contracts, + identified_contracts, + evm, + left.clone(), + ); Self::print_logs(trace, &abi, &left); Self::print_output(color, output, left); } else { @@ -140,7 +156,15 @@ impl CallTraceArena { { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - self.print_children_and_prelogs(idx, trace, &abi, contracts, identified_contracts, evm, left.clone()); + self.print_children_and_prelogs( + idx, + trace, + &abi, + contracts, + identified_contracts, + evm, + left.clone(), + ); Self::print_logs(trace, &abi, &left); println!( "{} └─ {} {} bytes of code", @@ -151,7 +175,15 @@ impl CallTraceArena { return } else { println!("{}→ new @{:?}", left, trace.addr); - self.print_unknown(color, idx, trace, contracts, identified_contracts, evm, left.clone()); + self.print_unknown( + color, + idx, + trace, + contracts, + identified_contracts, + evm, + left.clone(), + ); return } } @@ -164,7 +196,15 @@ impl CallTraceArena { // re-enter this function at this level if we found the contract self.pretty_print(idx, contracts, identified_contracts, evm, left); } else { - self.print_unknown(color, idx, trace, contracts, identified_contracts, evm, left.clone()); + self.print_unknown( + color, + idx, + trace, + contracts, + identified_contracts, + evm, + left.clone(), + ); } } } @@ -177,7 +217,7 @@ impl CallTraceArena { contracts: &BTreeMap)>, identified_contracts: &mut BTreeMap, evm: &'a E, - left: String + left: String, ) { if trace.data.len() >= 4 { println!( @@ -231,7 +271,11 @@ impl CallTraceArena { "{} └─ {} {}", left.to_string().replace("├─", "│").replace("└─", " "), color.paint("←"), - if trace.output.len() == 0 { "()".to_string() } else { "0x".to_string() + &hex::encode(&trace.output) } + if trace.output.len() == 0 { + "()".to_string() + } else { + "0x".to_string() + &hex::encode(&trace.output) + } ); } else { println!( @@ -241,11 +285,16 @@ impl CallTraceArena { trace.output.len() / 2 ); } - } /// Prints function call, optionally returning the decoded output - pub fn print_func_call(trace: &CallTrace, abi: &Abi, name: &String, color: Colour, left: &String) -> Output { + pub fn print_func_call( + trace: &CallTrace, + abi: &Abi, + name: &String, + color: Colour, + left: &String, + ) -> Output { if trace.data.len() >= 4 { for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { @@ -269,36 +318,43 @@ impl CallTraceArena { return Output::Token( func.decode_output(&trace.output[..]).expect("Bad func output decode"), - ); + ) } } } } else { // fallback function? - println!( - "{}[{}] {}::fallback()", - left, - trace.cost, - color.paint(name), - ); + println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); - return Output::Raw(trace.output[..].to_vec()); + return Output::Raw(trace.output[..].to_vec()) } - println!( "{}[{}] {}::{}({})", left, trace.cost, color.paint(name), - if trace.data.len() >= 4 { hex::encode(&trace.data[0..4]) } else { hex::encode(&trace.data[..]) }, - if trace.data.len() >= 4 { hex::encode(&trace.data[4..]) } else { hex::encode(&vec![][..])} + if trace.data.len() >= 4 { + hex::encode(&trace.data[0..4]) + } else { + hex::encode(&trace.data[..]) + }, + if trace.data.len() >= 4 { + hex::encode(&trace.data[4..]) + } else { + hex::encode(&vec![][..]) + } ); Output::Raw(trace.output[..].to_vec()) } - pub fn print_prelogs(prelogs: &Vec<(RawLog, usize)>, location: usize, abi: &Abi, left: &String) { + pub fn print_prelogs( + prelogs: &Vec<(RawLog, usize)>, + location: usize, + abi: &Abi, + left: &String, + ) { prelogs.iter().for_each(|(log, loc)| { if *loc == location { let mut found = false; @@ -307,8 +363,7 @@ impl CallTraceArena { for event in overloaded_events.iter() { if event.signature() == log.topics[0] { found = true; - let params = - event.parse_log(log.clone()).expect("Bad event").params; + let params = event.parse_log(log.clone()).expect("Bad event").params; let strings = params .iter() .map(|param| format!("{}: {:?}", param.name, param.value)) @@ -343,7 +398,7 @@ impl CallTraceArena { contracts: &BTreeMap)>, identified_contracts: &mut BTreeMap, evm: &'a E, - left: String + left: String, ) { let children_idxs = &self.arena[idx].children; if children_idxs.len() == 0 { @@ -417,7 +472,11 @@ impl CallTraceArena { "{} └─ {} {}", left.to_string().replace("├─", "│").replace("└─", " "), color.paint("←"), - if bytes.len() == 0 { "()".to_string() } else { "0x".to_string() + &hex::encode(&bytes) } + if bytes.len() == 0 { + "()".to_string() + } else { + "0x".to_string() + &hex::encode(&bytes) + } ); } } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 13e95b3d37e07..491f3c4418096 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -127,11 +127,11 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor { self.state_mut().increment_call_index(); (s, v) - }, + } Capture::Trap(_) => { self.state_mut().increment_call_index(); unreachable!() - }, + } } } @@ -161,11 +161,11 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor { self.state_mut().increment_call_index(); s - }, + } Capture::Trap(_) => { self.state_mut().increment_call_index(); unreachable!() - }, + } } } @@ -484,7 +484,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if trace.depth > 0 && prelogs > 0 { let trace_index = trace.idx; - let parent_index = self.state().trace().arena[trace_index].parent.expect("No parent"); + let parent_index = + self.state().trace().arena[trace_index].parent.expect("No parent"); let child_logs = self.state().trace().inner_number_of_logs(parent_index); if self.raw_logs().len() >= prelogs { @@ -496,19 +497,24 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> .saturating_sub(1), ) > 0 { - let prelogs = self.state().substate.logs().to_vec()[self.state().trace().arena[parent_index] - .trace - .prelogs - .len() + - child_logs.. - prelogs] - .to_vec(); + let prelogs = + self.state().substate.logs().to_vec()[self.state().trace().arena + [parent_index] + .trace + .prelogs + .len() + + child_logs.. + prelogs] + .to_vec(); let parent = &mut self.state_mut().trace_mut().arena[parent_index]; if prelogs.len() > 0 { prelogs.into_iter().for_each(|prelog| { if prelog.address == parent.trace.addr { - parent.trace.prelogs.push((RawLog { topics: prelog.topics, data: prelog.data }, trace.location)); + parent.trace.prelogs.push(( + RawLog { topics: prelog.topics, data: prelog.data }, + trace.location, + )); } }); } @@ -531,19 +537,18 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn fill_trace( - &mut self, - new_trace: Option, - success: bool, - output: Option>, - ) { + fn fill_trace(&mut self, new_trace: Option, success: bool, output: Option>) { if let Some(new_trace) = new_trace { let logs = self.state().substate.logs().to_vec(); - let applicable_logs = logs.into_iter().filter(|log| log.address == new_trace.addr).collect::>(); + let applicable_logs = + logs.into_iter().filter(|log| log.address == new_trace.addr).collect::>(); let prelogs = self.state_mut().trace_mut().arena[new_trace.idx].trace.prelogs.len(); let curr_logs = self.state_mut().trace_mut().arena[new_trace.idx].trace.logs.len(); - applicable_logs[prelogs+curr_logs..].to_vec().into_iter().for_each(|log| { - self.state_mut().trace_mut().arena[new_trace.idx].trace.logs.push(RawLog { topics: log.topics, data: log.data }); + applicable_logs[prelogs + curr_logs..].to_vec().into_iter().for_each(|log| { + self.state_mut().trace_mut().arena[new_trace.idx] + .trace + .logs + .push(RawLog { topics: log.topics, data: log.data }); }); let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; @@ -1270,7 +1275,13 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, "".to_string()); + evm.traces().expect("no traces")[1].pretty_print( + 0, + &mapping, + &mut identified, + &evm, + "".to_string(), + ); } #[test] @@ -1334,6 +1345,12 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, "".to_string()); + evm.traces().expect("no traces")[1].pretty_print( + 0, + &mapping, + &mut identified, + &evm, + "".to_string(), + ); } } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 61e0dc635a4dc..f3e0f1e8b2f5d 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -46,7 +46,12 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { - Self { backend, substate: MemoryStackSubstate::new(metadata), call_index: 0, traces: vec![Default::default()] } + Self { + backend, + substate: MemoryStackSubstate::new(metadata), + call_index: 0, + traces: vec![Default::default()], + } } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 539f6b54f434e..d76b0bb0cd035 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -201,7 +201,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let mut trace = None; if let Some(traces) = self.evm.traces() { if traces.len() > 0 { - trace = Some(traces[0].clone()); + trace = Some(traces[0].clone()); } } self.evm.reset_traces(); From 3f8c2b71e0a4d1b6f4e861cbdea41b98dd69d512 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 17:30:13 -0800 Subject: [PATCH 15/34] fix --- evm-adapters/src/call_tracing.rs | 4 ++-- .../src/sputnik/cheatcodes/cheatcode_handler.rs | 15 ++++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 5ef227d3495e7..949ba302fd985 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -372,7 +372,7 @@ impl CallTraceArena { println!( "{}emit {}({})", left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), + Colour::Blue.paint(event_name), strings ); break 'outer @@ -383,7 +383,7 @@ impl CallTraceArena { println!( "{}emit {}", left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) + Colour::Blue.paint(format!("{:?}", log)) ) } } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 491f3c4418096..6c4f68ff7f75b 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -539,12 +539,17 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn fill_trace(&mut self, new_trace: Option, success: bool, output: Option>) { if let Some(new_trace) = new_trace { - let logs = self.state().substate.logs().to_vec(); - let applicable_logs = - logs.into_iter().filter(|log| log.address == new_trace.addr).collect::>(); let prelogs = self.state_mut().trace_mut().arena[new_trace.idx].trace.prelogs.len(); - let curr_logs = self.state_mut().trace_mut().arena[new_trace.idx].trace.logs.len(); - applicable_logs[prelogs + curr_logs..].to_vec().into_iter().for_each(|log| { + let child_logs = self.state().trace().inner_number_of_logs(new_trace.idx); + + let logs = self.state().substate.logs().to_vec(); + let applicable_logs = logs[prelogs + child_logs..] + .to_vec() + .into_iter() + .filter(|log| log.address == new_trace.addr) + .collect::>(); + + applicable_logs.into_iter().for_each(|log| { self.state_mut().trace_mut().arena[new_trace.idx] .trace .logs From cc7e2edea5df6f7e41bc60689b58d0191f7257ef Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 17:31:05 -0800 Subject: [PATCH 16/34] cyan color --- evm-adapters/src/call_tracing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 949ba302fd985..5ef227d3495e7 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -372,7 +372,7 @@ impl CallTraceArena { println!( "{}emit {}({})", left.to_string().replace("├─", "│") + right, - Colour::Blue.paint(event_name), + Colour::Cyan.paint(event_name), strings ); break 'outer @@ -383,7 +383,7 @@ impl CallTraceArena { println!( "{}emit {}", left.to_string().replace("├─", "│") + right, - Colour::Blue.paint(format!("{:?}", log)) + Colour::Cyan.paint(format!("{:?}", log)) ) } } From f558e824e1bfbb0b335e55ed7916b5986e0d9a07 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 18:09:19 -0800 Subject: [PATCH 17/34] fixes --- evm-adapters/src/call_tracing.rs | 43 +++++++++++++------ .../sputnik/cheatcodes/cheatcode_handler.rs | 32 ++++++++------ evm-adapters/src/sputnik/cheatcodes/mod.rs | 1 + 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 5ef227d3495e7..90a17179e06ac 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -7,6 +7,9 @@ use std::collections::BTreeMap; use ansi_term::Colour; +#[cfg(feature = "sputnik")] +use crate::sputnik::cheatcodes::{cheatcode_handler::CHEATCODE_ADDRESS, HEVM_ABI}; + #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CallTraceArena { pub arena: Vec, @@ -104,9 +107,18 @@ impl CallTraceArena { ) { let trace = &self.arena[idx].trace; - // color the printout by success + #[cfg(feature = "sputnik")] + identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); + + #[cfg(feature = "sputnik")] + let color = if trace.addr == *CHEATCODE_ADDRESS { Colour::Blue} else { + if trace.success { Colour::Green } else { Colour::Red } + }; + + #[cfg(not(feature = "sputnik"))] let color = if trace.success { Colour::Green } else { Colour::Red }; + let maybe_found; { if let Some((name, abi)) = identified_contracts.get(&trace.addr) { @@ -299,14 +311,17 @@ impl CallTraceArena { for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { if func.selector() == trace.data[0..4] { - let params = - func.decode_input(&trace.data[4..]).expect("Bad func data decode"); - let strings = params - .iter() - .map(|param| format!("{:?}", param)) - .collect::>() - .join(", "); - + let mut strings = "".to_string(); + if trace.data[4..].len() > 0 { + let params = + func.decode_input(&trace.data[4..]).expect("Bad func data decode"); + strings = params + .iter() + .map(|param| format!("{:?}", param)) + .collect::>() + .join(", "); + } + println!( "{}[{}] {}::{}({})", left, @@ -316,9 +331,13 @@ impl CallTraceArena { strings ); - return Output::Token( - func.decode_output(&trace.output[..]).expect("Bad func output decode"), - ) + if trace.output.len() > 0 { + return Output::Token( + func.decode_output(&trace.output[..]).expect("Bad func output decode"), + ) + } else { + return Output::Raw(vec![]) + } } } } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 6c4f68ff7f75b..3db0d3e72f1c8 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -313,6 +313,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ) -> Capture<(ExitReason, Vec), Infallible> { let mut res = vec![]; + let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), false, 0); // Get a mutable ref to the state so we can apply the cheats let state = self.state_mut(); let decoded = match HEVMCalls::decode(&input) { @@ -320,6 +321,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Err(err) => return evm_error(&err.to_string()), }; + + match decoded { HEVMCalls::Warp(inner) => { state.backend.cheats.block_timestamp = Some(inner.0); @@ -448,6 +451,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } }; + self.fill_trace(trace, true, Some(res.clone())); + // TODO: Add more cheat codes. Capture::Exit((ExitReason::Succeed(ExitSucceed::Stopped), res)) } @@ -543,18 +548,21 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let child_logs = self.state().trace().inner_number_of_logs(new_trace.idx); let logs = self.state().substate.logs().to_vec(); - let applicable_logs = logs[prelogs + child_logs..] - .to_vec() - .into_iter() - .filter(|log| log.address == new_trace.addr) - .collect::>(); - - applicable_logs.into_iter().for_each(|log| { - self.state_mut().trace_mut().arena[new_trace.idx] - .trace - .logs - .push(RawLog { topics: log.topics, data: log.data }); - }); + if logs.len() > prelogs + child_logs { + let applicable_logs = logs[prelogs + child_logs..] + .to_vec() + .into_iter() + .filter(|log| log.address == new_trace.addr) + .collect::>(); + + applicable_logs.into_iter().for_each(|log| { + self.state_mut().trace_mut().arena[new_trace.idx] + .trace + .logs + .push(RawLog { topics: log.topics, data: log.data }); + }); + } + let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; trace.output = output.unwrap_or(vec![]); diff --git a/evm-adapters/src/sputnik/cheatcodes/mod.rs b/evm-adapters/src/sputnik/cheatcodes/mod.rs index 0d050ce2f2bed..520fe5ad5e2ee 100644 --- a/evm-adapters/src/sputnik/cheatcodes/mod.rs +++ b/evm-adapters/src/sputnik/cheatcodes/mod.rs @@ -56,6 +56,7 @@ ethers::contract::abigen!( ]"#, ); pub use hevm_mod::HEVMCalls; +pub use hevm_mod::HEVM_ABI; ethers::contract::abigen!( HevmConsole, From ba514cb2f84b5cfb7dd14a220f48084362581321 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 21:46:31 -0800 Subject: [PATCH 18/34] prettier raw logs + parse setup contracts --- cli/src/forge.rs | 37 +++- evm-adapters/src/call_tracing.rs | 191 ++++++++++++++++-- .../sputnik/cheatcodes/cheatcode_handler.rs | 4 +- evm-adapters/src/sputnik/cheatcodes/mod.rs | 3 +- forge/src/runner.rs | 20 +- 5 files changed, 210 insertions(+), 45 deletions(-) diff --git a/cli/src/forge.rs b/cli/src/forge.rs index ead99742bbe9a..6523239f430c5 100644 --- a/cli/src/forge.rs +++ b/cli/src/forge.rs @@ -330,18 +330,33 @@ fn test>( println!(); if verbosity > 2 { - let mut identified = Default::default(); - if let Some(trace) = &result.trace { - // deploy -> setup -> test -> failed -> setup -> test -> failed - trace.pretty_print( - 0, - &runner.known_contracts, - &mut identified, - &runner.evm, - "".to_string(), - ); + if let Some(traces) = &result.traces { + let mut identified = Default::default(); + if traces.len() > 1 { + traces[0].update_identified( + 0, + &runner.known_contracts, + &mut identified, + &runner.evm, + ); + traces[1].pretty_print( + 0, + &runner.known_contracts, + &mut identified, + &runner.evm, + "".to_string(), + ); + } else { + traces[0].pretty_print( + 0, + &runner.known_contracts, + &mut identified, + &runner.evm, + "".to_string(), + ); + } + println!(); } - println!(); } } } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 90a17179e06ac..25280e8c91cbc 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -97,6 +97,60 @@ impl CallTraceArena { self.arena[node_idx].trace.logs.iter().for_each(|raw| println!("log {}", raw.topics[0])); } + pub fn update_identified<'a, S: Clone, E: crate::Evm>( + &self, + idx: usize, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + ) { + let trace = &self.arena[idx].trace; + + #[cfg(feature = "sputnik")] + identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); + + let maybe_found; + { + if let Some((name, abi)) = identified_contracts.get(&trace.addr) { + maybe_found = Some((name.clone(), abi.clone())); + } else { + maybe_found = None; + } + } + if let Some((_name, _abi)) = maybe_found { + if trace.created { + self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + return + } + self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + } else { + if trace.created { + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.30) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + return + } else { + self.update_unknown(idx, trace, contracts, identified_contracts, evm); + return + } + } + + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.30) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + // re-enter this function at this level if we found the contract + self.update_identified(idx, contracts, identified_contracts, evm); + } else { + self.update_unknown(idx, trace, contracts, identified_contracts, evm); + } + } + } + pub fn pretty_print<'a, S: Clone, E: crate::Evm>( &self, idx: usize, @@ -111,14 +165,19 @@ impl CallTraceArena { identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); #[cfg(feature = "sputnik")] - let color = if trace.addr == *CHEATCODE_ADDRESS { Colour::Blue} else { - if trace.success { Colour::Green } else { Colour::Red } + let color = if trace.addr == *CHEATCODE_ADDRESS { + Colour::Blue + } else { + if trace.success { + Colour::Green + } else { + Colour::Red + } }; #[cfg(not(feature = "sputnik"))] let color = if trace.success { Colour::Green } else { Colour::Red }; - let maybe_found; { if let Some((name, abi)) = identified_contracts.get(&trace.addr) { @@ -265,16 +324,34 @@ impl CallTraceArena { } }); - let mut right = " ├─ "; - - trace.logs.iter().enumerate().for_each(|(i, log)| { - if i == trace.logs.len() - 1 { - right = " └─ "; + trace.logs.iter().enumerate().for_each(|(_i, log)| { + for (i, topic) in log.topics.iter().enumerate() { + let right = if i == log.topics.len() - 1 && log.data.len() == 0 { + " └─ " + } else { + " ├─ " + }; + if i == 0 { + println!( + "{}{} {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint("emit topic 0:"), + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } else { + println!( + "{} {} {}", + left.to_string().replace("├─", "│") + " │ " + right, + Colour::Cyan.paint(format!("topic {}:", i)), + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } } println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) + "{} {} {}", + left.to_string().replace("├─", "│") + " │ " + " └─ ", + Colour::Cyan.paint("data:"), + Colour::Cyan.paint("0x".to_string() + &hex::encode(&log.data)) ) }); @@ -299,6 +376,25 @@ impl CallTraceArena { } } + pub fn update_unknown<'a, S: Clone, E: crate::Evm>( + &self, + idx: usize, + trace: &CallTrace, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + ) { + let children_idxs = &self.arena[idx].children; + children_idxs.iter().enumerate().for_each(|(i, child_idx)| { + // let inners = inner.inner_number_of_inners(); + if i == children_idxs.len() - 1 && trace.logs.len() == 0 { + self.update_identified(*child_idx, contracts, identified_contracts, evm); + } else { + self.update_identified(*child_idx, contracts, identified_contracts, evm); + } + }); + } + /// Prints function call, optionally returning the decoded output pub fn print_func_call( trace: &CallTrace, @@ -321,7 +417,7 @@ impl CallTraceArena { .collect::>() .join(", "); } - + println!( "{}[{}] {}::{}({})", left, @@ -333,7 +429,8 @@ impl CallTraceArena { if trace.output.len() > 0 { return Output::Token( - func.decode_output(&trace.output[..]).expect("Bad func output decode"), + func.decode_output(&trace.output[..]) + .expect("Bad func output decode"), ) } else { return Output::Raw(vec![]) @@ -399,10 +496,31 @@ impl CallTraceArena { } } if !found { + for (i, topic) in log.topics.iter().enumerate() { + let right = if i == log.topics.len() - 1 && log.data.len() == 0 { + " └─ " + } else { + " ├─" + }; + if i == 0 { + println!( + "{}emit topic 0: {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } else { + println!( + "{}topic {}: {}", + left.to_string().replace("├─", "│") + right, + i, + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } + } println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) + "{}data {}", + left.to_string().replace("├─", "│") + " └─ ", + Colour::Cyan.paint(hex::encode(&log.data)) ) } } @@ -437,6 +555,19 @@ impl CallTraceArena { } } + pub fn update_children_and_prelogs<'a, S: Clone, E: crate::Evm>( + &self, + idx: usize, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + ) { + let children_idxs = &self.arena[idx].children; + children_idxs.iter().for_each(|child_idx| { + self.update_identified(*child_idx, contracts, identified_contracts, evm); + }); + } + pub fn print_logs(trace: &CallTrace, abi: &Abi, left: &String) { trace.logs.iter().for_each(|log| { let mut found = false; @@ -462,10 +593,31 @@ impl CallTraceArena { } } if !found { + for (i, topic) in log.topics.iter().enumerate() { + let right = if i == log.topics.len() - 1 && log.data.len() == 0 { + " └─ " + } else { + " ├─" + }; + if i == 0 { + println!( + "{}emit topic 0: {}", + left.to_string().replace("├─", "│") + right, + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } else { + println!( + "{}topic {}: {}", + left.to_string().replace("├─", "│") + right, + i, + Colour::Cyan.paint(format!("{:?}", topic)) + ) + } + } println!( - "{}emit {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", log)) + "{}data {}", + left.to_string().replace("├─", "│") + " └─ ", + Colour::Cyan.paint(hex::encode(&log.data)) ) } }); @@ -565,5 +717,6 @@ fn diff_score(bytecode1: &Vec, bytecode2: &Vec) -> f64 { } } + // println!("diff_score {}", diff_chars as f64 / cutoff_len as f64); diff_chars as f64 / cutoff_len as f64 } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 3db0d3e72f1c8..6598fbaabe242 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -321,8 +321,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Err(err) => return evm_error(&err.to_string()), }; - - match decoded { HEVMCalls::Warp(inner) => { state.backend.cheats.block_timestamp = Some(inner.0); @@ -562,7 +560,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> .push(RawLog { topics: log.topics, data: log.data }); }); } - + let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; trace.output = output.unwrap_or(vec![]); diff --git a/evm-adapters/src/sputnik/cheatcodes/mod.rs b/evm-adapters/src/sputnik/cheatcodes/mod.rs index 520fe5ad5e2ee..bf2ae89d785e9 100644 --- a/evm-adapters/src/sputnik/cheatcodes/mod.rs +++ b/evm-adapters/src/sputnik/cheatcodes/mod.rs @@ -55,8 +55,7 @@ ethers::contract::abigen!( etch(address,bytes) ]"#, ); -pub use hevm_mod::HEVMCalls; -pub use hevm_mod::HEVM_ABI; +pub use hevm_mod::{HEVMCalls, HEVM_ABI}; ethers::contract::abigen!( HevmConsole, diff --git a/forge/src/runner.rs b/forge/src/runner.rs index d76b0bb0cd035..9420794917e36 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -48,8 +48,8 @@ pub struct TestResult { /// be printed to the user. pub logs: Vec, - /// Trace - pub trace: Option, + /// Traces + pub traces: Option>, } use std::marker::PhantomData; @@ -162,6 +162,8 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let mut logs = self.init_logs.to_vec(); + self.evm.reset_traces(); + // call the setup function in each test to reset the test's state. if setup { tracing::trace!("setting up"); @@ -173,8 +175,6 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { logs.extend_from_slice(&setup_logs); } - self.evm.reset_traces(); - let (status, reason, gas_used, logs) = match self.evm.call::<(), _, _>( self.sender, self.address, @@ -198,10 +198,10 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { }, }; - let mut trace = None; - if let Some(traces) = self.evm.traces() { - if traces.len() > 0 { - trace = Some(traces[0].clone()); + let mut traces = None; + if let Some(evm_traces) = self.evm.traces() { + if evm_traces.len() > 0 { + traces = Some(evm_traces.clone()); } } self.evm.reset_traces(); @@ -216,7 +216,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { gas_used: Some(gas_used), counterexample: None, logs, - trace, + traces, }) } @@ -263,7 +263,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { gas_used: None, counterexample, logs: vec![], - trace: None, + traces: None, }) } } From 36b4cf677d577cfccb050f5214cf24b435011e38 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 11 Dec 2021 22:04:23 -0800 Subject: [PATCH 19/34] update diff_score threshold --- evm-adapters/src/call_tracing.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 25280e8c91cbc..06bbda2a1ecbc 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -127,7 +127,7 @@ impl CallTraceArena { if trace.created { if let Some((name, (abi, _code))) = contracts .iter() - .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.30) + .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); @@ -140,7 +140,7 @@ impl CallTraceArena { if let Some((name, (abi, _code))) = contracts .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.30) + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); // re-enter this function at this level if we found the contract From c827d338091ee1f95e662e5fcb269839e99d3b36 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 16 Dec 2021 11:01:32 -0800 Subject: [PATCH 20/34] better printing --- evm-adapters/src/call_tracing.rs | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 06bbda2a1ecbc..d51f7218deab3 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -188,7 +188,7 @@ impl CallTraceArena { } if let Some((name, abi)) = maybe_found { if trace.created { - println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); self.print_children_and_prelogs( idx, trace, @@ -226,7 +226,7 @@ impl CallTraceArena { .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - println!("{}{} {}@{:?}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); self.print_children_and_prelogs( idx, trace, @@ -245,7 +245,7 @@ impl CallTraceArena { ); return } else { - println!("{}→ new @{:?}", left, trace.addr); + println!("{}→ new @{}", left, trace.addr); self.print_unknown( color, idx, @@ -292,14 +292,15 @@ impl CallTraceArena { ) { if trace.data.len() >= 4 { println!( - "{}{:x}::{}({})", + "{}[{}] {:x}::{}(0x{})", left, + trace.cost, trace.addr, hex::encode(&trace.data[0..4]), hex::encode(&trace.data[4..]) ); } else { - println!("{}{:x}::({})", left, trace.addr, hex::encode(&trace.data)); + println!("{}{:x}::(0x{})", left, trace.addr, hex::encode(&trace.data)); } let children_idxs = &self.arena[idx].children; @@ -336,14 +337,14 @@ impl CallTraceArena { "{}{} {}", left.to_string().replace("├─", "│") + right, Colour::Cyan.paint("emit topic 0:"), - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } else { println!( "{} {} {}", left.to_string().replace("├─", "│") + " │ " + right, Colour::Cyan.paint(format!("topic {}:", i)), - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } } @@ -413,7 +414,7 @@ impl CallTraceArena { func.decode_input(&trace.data[4..]).expect("Bad func data decode"); strings = params .iter() - .map(|param| format!("{:?}", param)) + .map(|param| format!("{}", param)) .collect::>() .join(", "); } @@ -482,7 +483,7 @@ impl CallTraceArena { let params = event.parse_log(log.clone()).expect("Bad event").params; let strings = params .iter() - .map(|param| format!("{}: {:?}", param.name, param.value)) + .map(|param| format!("{}: {}", param.name, param.value)) .collect::>() .join(", "); println!( @@ -506,14 +507,14 @@ impl CallTraceArena { println!( "{}emit topic 0: {}", left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } else { println!( "{}topic {}: {}", left.to_string().replace("├─", "│") + right, i, - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } } @@ -579,7 +580,7 @@ impl CallTraceArena { let params = event.parse_log(log.clone()).expect("Bad event").params; let strings = params .iter() - .map(|param| format!("{}: {:?}", param.name, param.value)) + .map(|param| format!("{}: {}", param.name, param.value)) .collect::>() .join(", "); println!( @@ -603,14 +604,14 @@ impl CallTraceArena { println!( "{}emit topic 0: {}", left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } else { println!( "{}topic {}: {}", left.to_string().replace("├─", "│") + right, i, - Colour::Cyan.paint(format!("{:?}", topic)) + Colour::Cyan.paint(format!("0x{}", topic)) ) } } @@ -628,7 +629,7 @@ impl CallTraceArena { Output::Token(token) => { let strings = token .iter() - .map(|param| format!("{:?}", param)) + .map(|param| format!("{}", param)) .collect::>() .join(", "); println!( From 58c6c1b440b12b40614fae9cb1433fdc293daa19 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 22 Dec 2021 15:08:42 -0700 Subject: [PATCH 21/34] remove integration tests --- integration-tests/testdata/drai | 1 - integration-tests/testdata/geb | 1 - integration-tests/testdata/guni-lev | 1 - integration-tests/testdata/lootloose | 1 - integration-tests/testdata/multicall | 1 - integration-tests/testdata/solidity-stringutils | 1 - integration-tests/testdata/solmate | 1 - integration-tests/testdata/vaults | 1 - 8 files changed, 8 deletions(-) delete mode 160000 integration-tests/testdata/drai delete mode 160000 integration-tests/testdata/geb delete mode 160000 integration-tests/testdata/guni-lev delete mode 160000 integration-tests/testdata/lootloose delete mode 160000 integration-tests/testdata/multicall delete mode 160000 integration-tests/testdata/solidity-stringutils delete mode 160000 integration-tests/testdata/solmate delete mode 160000 integration-tests/testdata/vaults diff --git a/integration-tests/testdata/drai b/integration-tests/testdata/drai deleted file mode 160000 index f31ce4fb15bbb..0000000000000 --- a/integration-tests/testdata/drai +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f31ce4fb15bbb06c94eefea2a3a43384c75b95cf diff --git a/integration-tests/testdata/geb b/integration-tests/testdata/geb deleted file mode 160000 index 50aa78bd61ccd..0000000000000 --- a/integration-tests/testdata/geb +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 50aa78bd61ccd57fd2d5e9d7ab0f0ce75c231fef diff --git a/integration-tests/testdata/guni-lev b/integration-tests/testdata/guni-lev deleted file mode 160000 index e333f4200538e..0000000000000 --- a/integration-tests/testdata/guni-lev +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e333f4200538ede70ebe7357608401df94657a65 diff --git a/integration-tests/testdata/lootloose b/integration-tests/testdata/lootloose deleted file mode 160000 index 7b639efe97836..0000000000000 --- a/integration-tests/testdata/lootloose +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7b639efe97836155a6a6fc626bf1018d4f8b2495 diff --git a/integration-tests/testdata/multicall b/integration-tests/testdata/multicall deleted file mode 160000 index 1e1b443626408..0000000000000 --- a/integration-tests/testdata/multicall +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1e1b44362640820bef92d0ccf5eeee25d9b41474 diff --git a/integration-tests/testdata/solidity-stringutils b/integration-tests/testdata/solidity-stringutils deleted file mode 160000 index 01e955c1d60fe..0000000000000 --- a/integration-tests/testdata/solidity-stringutils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 01e955c1d60fe8ac6c7a40f208d3b64758fae40c diff --git a/integration-tests/testdata/solmate b/integration-tests/testdata/solmate deleted file mode 160000 index 938b2d6925e24..0000000000000 --- a/integration-tests/testdata/solmate +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 938b2d6925e24de7542bce4b08f3f03988a82245 diff --git a/integration-tests/testdata/vaults b/integration-tests/testdata/vaults deleted file mode 160000 index 3d92dbb11ec11..0000000000000 --- a/integration-tests/testdata/vaults +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3d92dbb11ec119c3275dfa5ea6b9bda5dd1fb3aa From 1f759c04af5245dcf1a6117f67894a0da4d9bcae Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 22 Dec 2021 17:32:07 -0700 Subject: [PATCH 22/34] improvements --- Cargo.lock | 2 +- cli/src/cmd/test.rs | 10 +- evm-adapters/src/call_tracing.rs | 601 ++++++------------ .../sputnik/cheatcodes/cheatcode_handler.rs | 96 +-- .../cheatcodes/memory_stackstate_owned.rs | 7 +- 5 files changed, 238 insertions(+), 478 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc0386306de21..593118d67d135 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1336,7 +1336,7 @@ dependencies = [ name = "evm-adapters" version = "0.1.0" dependencies = [ - "ansi_term 0.12.1", + "ansi_term", "bytes", "ethers", "evm", diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index b4c3589671835..3683e671ebfb9 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -158,8 +158,14 @@ impl Cmd for TestArgs { let backend = Arc::new(backend); let precompiles = PRECOMPILES_MAP.clone(); - let evm = - Executor::new_with_cheatcodes(backend, env.gas_limit, &cfg, &precompiles, ffi, verbosity > 2,); + let evm = Executor::new_with_cheatcodes( + backend, + env.gas_limit, + &cfg, + &precompiles, + ffi, + verbosity > 2, + ); test(builder, project, evm, pattern, json, verbosity, allow_failure) } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index d51f7218deab3..5102561fbb425 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -24,6 +24,8 @@ impl Default for CallTraceArena { children: vec![], idx: 0, trace: CallTrace::default(), + logs: vec![], + ordering: vec![], }], entry: 0, } @@ -44,11 +46,14 @@ impl CallTraceArena { } else if self.arena[entry].trace.depth == new_trace.depth - 1 { new_trace.idx = self.arena.len(); new_trace.location = self.arena[entry].children.len(); + self.arena[entry].ordering.push(LogCallOrder::Call(new_trace.location)); let node = CallTraceNode { parent: Some(entry), children: vec![], idx: self.arena.len(), trace: new_trace.clone(), + logs: vec![], + ordering: vec![], }; self.arena.push(node); self.arena[entry].children.push(new_trace.idx); @@ -66,37 +71,6 @@ impl CallTraceArena { node.trace.update(trace); } - pub fn inner_number_of_logs(&self, node_idx: usize) -> usize { - self.arena[node_idx].children.iter().fold(0, |accum, idx| { - accum + - self.arena[*idx].trace.prelogs.len() + - self.arena[*idx].trace.logs.len() + - self.inner_number_of_logs(*idx) - }) //+ self.arena[node_idx].trace.prelogs.len() + self.arena[node_idx].trace.logs.len() - } - - pub fn inner_number_of_inners(&self, node_idx: usize) -> usize { - self.arena[node_idx].children.iter().fold(0, |accum, idx| { - accum + self.arena[*idx].children.len() + self.inner_number_of_inners(*idx) - }) - } - - pub fn next_log_index(&self) -> usize { - self.inner_number_of_logs(self.entry) - } - - pub fn print_logs_in_order(&self, node_idx: usize) { - self.arena[node_idx] - .trace - .prelogs - .iter() - .for_each(|(raw, _loc)| println!("prelog {}", raw.topics[0])); - self.arena[node_idx].children.iter().for_each(|idx| { - self.print_logs_in_order(*idx); - }); - self.arena[node_idx].trace.logs.iter().for_each(|raw| println!("log {}", raw.topics[0])); - } - pub fn update_identified<'a, S: Clone, E: crate::Evm>( &self, idx: usize, @@ -119,10 +93,10 @@ impl CallTraceArena { } if let Some((_name, _abi)) = maybe_found { if trace.created { - self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); return } - self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); } else { if trace.created { if let Some((name, (abi, _code))) = contracts @@ -130,10 +104,10 @@ impl CallTraceArena { .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - self.update_children_and_prelogs(idx, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); return } else { - self.update_unknown(idx, trace, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); return } } @@ -146,11 +120,24 @@ impl CallTraceArena { // re-enter this function at this level if we found the contract self.update_identified(idx, contracts, identified_contracts, evm); } else { - self.update_unknown(idx, trace, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); } } } + pub fn update_children<'a, S: Clone, E: crate::Evm>( + &self, + idx: usize, + contracts: &BTreeMap)>, + identified_contracts: &mut BTreeMap, + evm: &'a E, + ) { + let children_idxs = &self.arena[idx].children; + children_idxs.iter().for_each(|child_idx| { + self.update_identified(*child_idx, contracts, identified_contracts, evm); + }); + } + pub fn pretty_print<'a, S: Clone, E: crate::Evm>( &self, idx: usize, @@ -178,145 +165,139 @@ impl CallTraceArena { #[cfg(not(feature = "sputnik"))] let color = if trace.success { Colour::Green } else { Colour::Red }; - let maybe_found; + let mut abi = None; + let mut name = None; { - if let Some((name, abi)) = identified_contracts.get(&trace.addr) { - maybe_found = Some((name.clone(), abi.clone())); - } else { - maybe_found = None; + if let Some((name_, abi_)) = identified_contracts.get(&trace.addr) { + abi = Some(abi_.clone()); + name = Some(name_.clone()); } } - if let Some((name, abi)) = maybe_found { - if trace.created { - println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - self.print_children_and_prelogs( - idx, - trace, - &abi, - contracts, - identified_contracts, - evm, - left.clone(), - ); - Self::print_logs(trace, &abi, &left); - println!( - "{} └─ {} {} bytes of code", - left.to_string().replace("├─", "│").replace("└─", " "), - color.paint("←"), - trace.output.len() / 2 - ); - return - } - let output = Self::print_func_call(trace, &abi, &name, color, &left); - self.print_children_and_prelogs( - idx, - trace, - &abi, - contracts, - identified_contracts, - evm, - left.clone(), - ); - Self::print_logs(trace, &abi, &left); - Self::print_output(color, output, left); - } else { - if trace.created { - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + // was this a contract creation? + if trace.created { + match (abi, name) { + (Some(abi), Some(name)) => { + // if we have a match already, print it like normal println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); - self.print_children_and_prelogs( + self.print_children_and_logs( idx, - trace, - &abi, + Some(&abi), contracts, identified_contracts, evm, left.clone(), ); - Self::print_logs(trace, &abi, &left); println!( "{} └─ {} {} bytes of code", left.to_string().replace("├─", "│").replace("└─", " "), color.paint("←"), - trace.output.len() / 2 + trace.output.len() ); - return - } else { - println!("{}→ new @{}", left, trace.addr); - self.print_unknown( - color, + } + _ => { + // otherwise, try to identify it and print + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + println!( + "{}{} {}@{}", + left, + Colour::Yellow.paint("→ new"), + name, + trace.addr + ); + self.print_children_and_logs( + idx, + Some(&abi), + contracts, + identified_contracts, + evm, + left.clone(), + ); + println!( + "{} └─ {} {} bytes of code", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() + ); + } else { + // we couldn't identify, print the children and logs without the abi + println!("{}→ new @{}", left, trace.addr); + self.print_children_and_logs( + idx, + None, + contracts, + identified_contracts, + evm, + left.clone(), + ); + println!( + "{} └─ {} {} bytes of code", + left.to_string().replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() + ); + } + } + } + } else { + match (abi, name) { + (Some(abi), Some(name)) => { + let output = + Self::print_func_call(trace, Some(&abi), Some(&name), color, &left); + self.print_children_and_logs( idx, - trace, + Some(&abi), contracts, identified_contracts, evm, left.clone(), ); - return + Self::print_output(color, output, left); + } + _ => { + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + // re-enter this function at this level if we found the contract + self.pretty_print(idx, contracts, identified_contracts, evm, left); + } else { + let output = Self::print_func_call(trace, None, None, color, &left); + self.print_children_and_logs( + idx, + None, + contracts, + identified_contracts, + evm, + left.clone(), + ); + Self::print_output(color, output, left); + } } - } - - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - // re-enter this function at this level if we found the contract - self.pretty_print(idx, contracts, identified_contracts, evm, left); - } else { - self.print_unknown( - color, - idx, - trace, - contracts, - identified_contracts, - evm, - left.clone(), - ); } } } - pub fn print_unknown<'a, S: Clone, E: crate::Evm>( + pub fn print_children_and_logs<'a, S: Clone, E: crate::Evm>( &self, - color: Colour, - idx: usize, - trace: &CallTrace, + node_idx: usize, + abi: Option<&Abi>, contracts: &BTreeMap)>, identified_contracts: &mut BTreeMap, evm: &'a E, left: String, ) { - if trace.data.len() >= 4 { - println!( - "{}[{}] {:x}::{}(0x{})", - left, - trace.cost, - trace.addr, - hex::encode(&trace.data[0..4]), - hex::encode(&trace.data[4..]) - ); - } else { - println!("{}{:x}::(0x{})", left, trace.addr, hex::encode(&trace.data)); - } - - let children_idxs = &self.arena[idx].children; - children_idxs.iter().enumerate().for_each(|(i, child_idx)| { - // let inners = inner.inner_number_of_inners(); - if i == children_idxs.len() - 1 && trace.logs.len() == 0 { - self.pretty_print( - *child_idx, - contracts, - identified_contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " └─ ", - ); - } else { + self.arena[node_idx].ordering.iter().for_each(|ordering| match ordering { + LogCallOrder::Log(index) => { + self.print_log(&self.arena[node_idx].logs[*index], abi, &left); + } + LogCallOrder::Call(index) => { self.pretty_print( - *child_idx, + self.arena[node_idx].children[*index], contracts, identified_contracts, evm, @@ -324,133 +305,69 @@ impl CallTraceArena { ); } }); - - trace.logs.iter().enumerate().for_each(|(_i, log)| { - for (i, topic) in log.topics.iter().enumerate() { - let right = if i == log.topics.len() - 1 && log.data.len() == 0 { - " └─ " - } else { - " ├─ " - }; - if i == 0 { - println!( - "{}{} {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint("emit topic 0:"), - Colour::Cyan.paint(format!("0x{}", topic)) - ) - } else { - println!( - "{} {} {}", - left.to_string().replace("├─", "│") + " │ " + right, - Colour::Cyan.paint(format!("topic {}:", i)), - Colour::Cyan.paint(format!("0x{}", topic)) - ) - } - } - println!( - "{} {} {}", - left.to_string().replace("├─", "│") + " │ " + " └─ ", - Colour::Cyan.paint("data:"), - Colour::Cyan.paint("0x".to_string() + &hex::encode(&log.data)) - ) - }); - - if !trace.created { - println!( - "{} └─ {} {}", - left.to_string().replace("├─", "│").replace("└─", " "), - color.paint("←"), - if trace.output.len() == 0 { - "()".to_string() - } else { - "0x".to_string() + &hex::encode(&trace.output) - } - ); - } else { - println!( - "{} └─ {} {} bytes of code", - left.to_string().replace("├─", "│").replace("└─", " "), - color.paint("←"), - trace.output.len() / 2 - ); - } - } - - pub fn update_unknown<'a, S: Clone, E: crate::Evm>( - &self, - idx: usize, - trace: &CallTrace, - contracts: &BTreeMap)>, - identified_contracts: &mut BTreeMap, - evm: &'a E, - ) { - let children_idxs = &self.arena[idx].children; - children_idxs.iter().enumerate().for_each(|(i, child_idx)| { - // let inners = inner.inner_number_of_inners(); - if i == children_idxs.len() - 1 && trace.logs.len() == 0 { - self.update_identified(*child_idx, contracts, identified_contracts, evm); - } else { - self.update_identified(*child_idx, contracts, identified_contracts, evm); - } - }); } /// Prints function call, optionally returning the decoded output pub fn print_func_call( trace: &CallTrace, - abi: &Abi, - name: &String, + abi: Option<&Abi>, + name: Option<&String>, color: Colour, left: &String, ) -> Output { - if trace.data.len() >= 4 { - for (func_name, overloaded_funcs) in abi.functions.iter() { - for func in overloaded_funcs.iter() { - if func.selector() == trace.data[0..4] { - let mut strings = "".to_string(); - if trace.data[4..].len() > 0 { - let params = - func.decode_input(&trace.data[4..]).expect("Bad func data decode"); - strings = params - .iter() - .map(|param| format!("{}", param)) - .collect::>() - .join(", "); - } - - println!( - "{}[{}] {}::{}({})", - left, - trace.cost, - color.paint(name), - color.paint(func_name), - strings - ); - - if trace.output.len() > 0 { - return Output::Token( - func.decode_output(&trace.output[..]) - .expect("Bad func output decode"), - ) - } else { - return Output::Raw(vec![]) + match (abi, name) { + (Some(abi), Some(name)) => { + if trace.data.len() >= 4 { + for (func_name, overloaded_funcs) in abi.functions.iter() { + for func in overloaded_funcs.iter() { + if func.selector() == trace.data[0..4] { + let mut strings = "".to_string(); + if trace.data[4..].len() > 0 { + let params = func + .decode_input(&trace.data[4..]) + .expect("Bad func data decode"); + strings = params + .iter() + .map(|param| format!("{}", param)) + .collect::>() + .join(", "); + } + + println!( + "{}[{}] {}::{}({})", + left, + trace.cost, + color.paint(name), + color.paint(func_name), + strings + ); + + if trace.output.len() > 0 { + return Output::Token( + func.decode_output(&trace.output[..]) + .expect("Bad func output decode"), + ) + } else { + return Output::Raw(vec![]) + } + } } } + } else { + // fallback function + println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); + + return Output::Raw(trace.output[..].to_vec()) } } - } else { - // fallback function? - println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); - - return Output::Raw(trace.output[..].to_vec()) + _ => {} } println!( "{}[{}] {}::{}({})", left, trace.cost, - color.paint(name), + color.paint(format!("{}", trace.addr)), if trace.data.len() >= 4 { hex::encode(&trace.data[0..4]) } else { @@ -466,117 +383,12 @@ impl CallTraceArena { Output::Raw(trace.output[..].to_vec()) } - pub fn print_prelogs( - prelogs: &Vec<(RawLog, usize)>, - location: usize, - abi: &Abi, - left: &String, - ) { - prelogs.iter().for_each(|(log, loc)| { - if *loc == location { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { - for event in overloaded_events.iter() { - if event.signature() == log.topics[0] { - found = true; - let params = event.parse_log(log.clone()).expect("Bad event").params; - let strings = params - .iter() - .map(|param| format!("{}: {}", param.name, param.value)) - .collect::>() - .join(", "); - println!( - "{}emit {}({})", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(event_name), - strings - ); - break 'outer - } - } - } - if !found { - for (i, topic) in log.topics.iter().enumerate() { - let right = if i == log.topics.len() - 1 && log.data.len() == 0 { - " └─ " - } else { - " ├─" - }; - if i == 0 { - println!( - "{}emit topic 0: {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("0x{}", topic)) - ) - } else { - println!( - "{}topic {}: {}", - left.to_string().replace("├─", "│") + right, - i, - Colour::Cyan.paint(format!("0x{}", topic)) - ) - } - } - println!( - "{}data {}", - left.to_string().replace("├─", "│") + " └─ ", - Colour::Cyan.paint(hex::encode(&log.data)) - ) - } - } - }); - } - - pub fn print_children_and_prelogs<'a, S: Clone, E: crate::Evm>( - &self, - idx: usize, - trace: &CallTrace, - abi: &Abi, - contracts: &BTreeMap)>, - identified_contracts: &mut BTreeMap, - evm: &'a E, - left: String, - ) { - let children_idxs = &self.arena[idx].children; - if children_idxs.len() == 0 { - Self::print_prelogs(&trace.prelogs, 0, abi, &left); - } else { - children_idxs.iter().for_each(|child_idx| { - let child_location = self.arena[*child_idx].trace.location; - Self::print_prelogs(&trace.prelogs, child_location, abi, &left); - self.pretty_print( - *child_idx, - contracts, - identified_contracts, - evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", - ); - }); - } - } - - pub fn update_children_and_prelogs<'a, S: Clone, E: crate::Evm>( - &self, - idx: usize, - contracts: &BTreeMap)>, - identified_contracts: &mut BTreeMap, - evm: &'a E, - ) { - let children_idxs = &self.arena[idx].children; - children_idxs.iter().for_each(|child_idx| { - self.update_identified(*child_idx, contracts, identified_contracts, evm); - }); - } - - pub fn print_logs(trace: &CallTrace, abi: &Abi, left: &String) { - trace.logs.iter().for_each(|log| { - let mut found = false; - let right = " ├─ "; - 'outer: for (event_name, overloaded_events) in abi.events.iter() { + pub fn print_log(&self, log: &RawLog, abi: Option<&Abi>, left: &String) { + let right = " ├─ "; + if let Some(abi) = abi { + for (event_name, overloaded_events) in abi.events.iter() { for event in overloaded_events.iter() { if event.signature() == log.topics[0] { - found = true; let params = event.parse_log(log.clone()).expect("Bad event").params; let strings = params .iter() @@ -589,39 +401,31 @@ impl CallTraceArena { Colour::Cyan.paint(event_name), strings ); - break 'outer - } - } - } - if !found { - for (i, topic) in log.topics.iter().enumerate() { - let right = if i == log.topics.len() - 1 && log.data.len() == 0 { - " └─ " - } else { - " ├─" - }; - if i == 0 { - println!( - "{}emit topic 0: {}", - left.to_string().replace("├─", "│") + right, - Colour::Cyan.paint(format!("0x{}", topic)) - ) - } else { - println!( - "{}topic {}: {}", - left.to_string().replace("├─", "│") + right, - i, - Colour::Cyan.paint(format!("0x{}", topic)) - ) + return } } - println!( - "{}data {}", - left.to_string().replace("├─", "│") + " └─ ", - Colour::Cyan.paint(hex::encode(&log.data)) - ) } - }); + } + // we didnt decode the log, print it as an unknown log + for (i, topic) in log.topics.iter().enumerate() { + let right = if i == log.topics.len() - 1 && log.data.len() == 0 { + " └─ " + } else { + " ├─" + }; + println!( + "{}{}topic {}: {}", + left.to_string().replace("├─", "│") + right, + if i == 0 { " emit " } else { " " }, + i, + Colour::Cyan.paint(format!("0x{}", hex::encode(&topic))) + ) + } + println!( + "{} data: {}", + left.to_string().replace("├─", "│").replace("└─", " ") + " │ ", /* left.to_string().replace("├─", "│") + " └─ ", */ + Colour::Cyan.paint(format!("0x{}", hex::encode(&log.data))) + ) } pub fn print_output(color: Colour, output: Output, left: String) { @@ -661,6 +465,16 @@ pub struct CallTraceNode { pub children: Vec, pub idx: usize, pub trace: CallTrace, + /// Logs + #[serde(skip)] + pub logs: Vec, + pub ordering: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum LogCallOrder { + Log(usize), + Call(usize), } /// Call trace of a tx @@ -681,12 +495,6 @@ pub struct CallTrace { pub cost: u64, /// Output pub output: Vec, - /// Logs emitted before inner - #[serde(skip)] - pub prelogs: Vec<(RawLog, usize)>, - /// Logs - #[serde(skip)] - pub logs: Vec, } impl CallTrace { @@ -695,7 +503,6 @@ impl CallTrace { self.addr = new_trace.addr; self.cost = new_trace.cost; self.output = new_trace.output; - self.logs = new_trace.logs; self.data = new_trace.data; self.addr = new_trace.addr; } diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index a8a86cd732e34..f9431a014b3b6 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -4,7 +4,7 @@ use super::{ HEVMCalls, HevmConsoleEvents, }; use crate::{ - call_tracing::{CallTrace, CallTraceArena}, + call_tracing::{CallTrace, CallTraceArena, LogCallOrder}, sputnik::{Executor, SputnikExecutor}, Evm, }; @@ -271,7 +271,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> // create the memory stack state (owned, so that we can modify the backend via // self.state_mut on the transact_call fn) let metadata = StackSubstateMetadata::new(gas_limit, config); - let state = MemoryStackStateOwned::new(metadata, backend); + let state = MemoryStackStateOwned::new(metadata, backend, enable_trace); // create the executor and wrap it with the cheatcode handler let executor = StackExecutor::new_with_precompiles(state, config, precompiles); @@ -524,56 +524,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // we should probably delay having the input and other stuff so // we minimize the size of the clone trace = self.state_mut().trace_mut().push_trace(0, trace.clone()); - - if trace.depth > 0 && prelogs > 0 { - let trace_index = trace.idx; - let parent_index = - self.state().trace().arena[trace_index].parent.expect("No parent"); - - let child_logs = self.state().trace().inner_number_of_logs(parent_index); - if self.raw_logs().len() >= prelogs { - if prelogs.saturating_sub(child_logs).saturating_sub( - self.state().trace().arena[parent_index] - .trace - .prelogs - .len() - .saturating_sub(1), - ) > 0 - { - let prelogs = - self.state().substate.logs().to_vec()[self.state().trace().arena - [parent_index] - .trace - .prelogs - .len() + - child_logs.. - prelogs] - .to_vec(); - let parent = &mut self.state_mut().trace_mut().arena[parent_index]; - - if prelogs.len() > 0 { - prelogs.into_iter().for_each(|prelog| { - if prelog.address == parent.trace.addr { - parent.trace.prelogs.push(( - RawLog { topics: prelog.topics, data: prelog.data }, - trace.location, - )); - } - }); - } - } - } - } else if trace.depth == 0 { - if self.raw_logs().len() >= prelogs { - let prelogs = self.raw_logs()[0..prelogs].to_vec(); - prelogs.into_iter().for_each(|prelog| { - self.state_mut().trace_mut().arena[0] - .trace - .prelogs - .push((prelog, trace.location)); - }) - } - } + self.state_mut().trace_index = trace.idx; Some(trace) } else { None @@ -582,25 +533,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> fn fill_trace(&mut self, new_trace: Option, success: bool, output: Option>) { if let Some(new_trace) = new_trace { - let prelogs = self.state_mut().trace_mut().arena[new_trace.idx].trace.prelogs.len(); - let child_logs = self.state().trace().inner_number_of_logs(new_trace.idx); - - let logs = self.state().substate.logs().to_vec(); - if logs.len() > prelogs + child_logs { - let applicable_logs = logs[prelogs + child_logs..] - .to_vec() - .into_iter() - .filter(|log| log.address == new_trace.addr) - .collect::>(); - - applicable_logs.into_iter().for_each(|log| { - self.state_mut().trace_mut().arena[new_trace.idx] - .trace - .logs - .push(RawLog { topics: log.topics, data: log.data }); - }); - } - let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; trace.output = output.unwrap_or(vec![]); @@ -622,8 +554,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> take_stipend: bool, context: Context, ) -> Capture<(ExitReason, Vec), Infallible> { - let prelogs = self.raw_logs().len(); - let trace = self.start_trace(code_address, input.clone(), false, prelogs); + let pre_index = self.state().trace_index; + let trace = self.start_trace(code_address, input.clone(), false, 0); macro_rules! try_or_fail { ( $e:expr ) => { @@ -732,6 +664,9 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let config = self.config().clone(); let mut runtime = Runtime::new(Rc::new(code), Rc::new(input), context, &config); let reason = self.execute(&mut runtime); + + self.state_mut().trace_index = pre_index; + // // log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, // reason); match reason { @@ -769,10 +704,11 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> target_gas: Option, take_l64: bool, ) -> Capture<(ExitReason, Option, Vec), Infallible> { - let prelogs = self.raw_logs().len(); + let pre_index = self.state().trace_index; + let address = self.create_address(scheme); - let trace = self.start_trace(address, init_code.clone(), true, prelogs); + let trace = self.start_trace(address, init_code.clone(), true, 0); macro_rules! try_or_fail { ( $e:expr ) => { @@ -872,7 +808,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let reason = self.execute(&mut runtime); // log::debug!(target: "evm", "Create execution using address {}: {:?}", address, reason); - + self.state_mut().trace_index = pre_index; match reason { ExitReason::Succeed(s) => { let out = runtime.machine().return_value(); @@ -955,7 +891,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a // to the state. // NB: This is very similar to how Optimism's custom intercept logic to "predeploys" work // (e.g. with the StateManager) - if code_address == *CHEATCODE_ADDRESS { self.apply_cheatcode(input, context.caller) } else if code_address == *CONSOLE_ADDRESS { @@ -1141,6 +1076,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a } fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + if self.state().trace_enabled { + let index = self.state().trace_index; + let node = &mut self.state_mut().traces.last_mut().expect("no traces").arena[index]; + node.ordering.push(LogCallOrder::Log(node.logs.len())); + node.logs.push(RawLog { topics: topics.clone(), data: data.clone() }); + } + self.handler.log(address, topics, data) } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index ea4ca752cee22..f6c7c6fa72e3e 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -17,7 +17,9 @@ use ethers::types::{H160, H256, U256}; pub struct MemoryStackStateOwned<'config, B> { pub backend: B, pub substate: MemoryStackSubstate<'config>, + pub trace_enabled: bool, pub call_index: usize, + pub trace_index: usize, pub traces: Vec, pub expected_revert: Option>, pub next_msg_sender: Option, @@ -48,15 +50,18 @@ impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { } impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { - pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { + pub fn new(metadata: StackSubstateMetadata<'config>, backend: B, trace_enabled: bool) -> Self { Self { backend, substate: MemoryStackSubstate::new(metadata), + trace_enabled, call_index: 0, + trace_index: 1, traces: vec![Default::default()], expected_revert: None, next_msg_sender: None, msg_sender: None, + } } } From 637c50a947da0f6d471cb83f37a98365f0826531 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 22 Dec 2021 17:58:39 -0700 Subject: [PATCH 23/34] improvements + fmt + clippy --- evm-adapters/src/call_tracing.rs | 119 +++++++++--------- .../sputnik/cheatcodes/cheatcode_handler.rs | 37 +++--- forge/src/multi_runner.rs | 71 +++-------- forge/src/runner.rs | 4 +- 4 files changed, 92 insertions(+), 139 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 5102561fbb425..a49a2d57768a1 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -154,12 +154,10 @@ impl CallTraceArena { #[cfg(feature = "sputnik")] let color = if trace.addr == *CHEATCODE_ADDRESS { Colour::Blue + } else if trace.success { + Colour::Green } else { - if trace.success { - Colour::Green - } else { - Colour::Red - } + Colour::Red }; #[cfg(not(feature = "sputnik"))] @@ -189,7 +187,7 @@ impl CallTraceArena { ); println!( "{} └─ {} {} bytes of code", - left.to_string().replace("├─", "│").replace("└─", " "), + left.replace("├─", "│").replace("└─", " "), color.paint("←"), trace.output.len() ); @@ -210,7 +208,7 @@ impl CallTraceArena { ); self.print_children_and_logs( idx, - Some(&abi), + Some(abi), contracts, identified_contracts, evm, @@ -218,7 +216,7 @@ impl CallTraceArena { ); println!( "{} └─ {} {} bytes of code", - left.to_string().replace("├─", "│").replace("└─", " "), + left.replace("├─", "│").replace("└─", " "), color.paint("←"), trace.output.len() ); @@ -235,7 +233,7 @@ impl CallTraceArena { ); println!( "{} └─ {} {} bytes of code", - left.to_string().replace("├─", "│").replace("└─", " "), + left.replace("├─", "│").replace("└─", " "), color.paint("←"), trace.output.len() ); @@ -301,7 +299,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - left.to_string().replace("├─", "│").replace("└─", " ") + " ├─ ", + left.replace("├─", "│").replace("└─", " ") + " ├─ ", ); } }); @@ -313,54 +311,51 @@ impl CallTraceArena { abi: Option<&Abi>, name: Option<&String>, color: Colour, - left: &String, + left: &str, ) -> Output { - match (abi, name) { - (Some(abi), Some(name)) => { - if trace.data.len() >= 4 { - for (func_name, overloaded_funcs) in abi.functions.iter() { - for func in overloaded_funcs.iter() { - if func.selector() == trace.data[0..4] { - let mut strings = "".to_string(); - if trace.data[4..].len() > 0 { - let params = func - .decode_input(&trace.data[4..]) - .expect("Bad func data decode"); - strings = params - .iter() - .map(|param| format!("{}", param)) - .collect::>() - .join(", "); - } - - println!( - "{}[{}] {}::{}({})", - left, - trace.cost, - color.paint(name), - color.paint(func_name), - strings - ); - - if trace.output.len() > 0 { - return Output::Token( - func.decode_output(&trace.output[..]) - .expect("Bad func output decode"), - ) - } else { - return Output::Raw(vec![]) - } + if let (Some(abi), Some(name)) = (abi, name) { + if trace.data.len() >= 4 { + for (func_name, overloaded_funcs) in abi.functions.iter() { + for func in overloaded_funcs.iter() { + if func.selector() == trace.data[0..4] { + let mut strings = "".to_string(); + if !trace.data[4..].is_empty() { + let params = func + .decode_input(&trace.data[4..]) + .expect("Bad func data decode"); + strings = params + .iter() + .map(|param| format!("{}", param)) + .collect::>() + .join(", "); + } + + println!( + "{}[{}] {}::{}({})", + left, + trace.cost, + color.paint(name), + color.paint(func_name), + strings + ); + + if !trace.output.is_empty() { + return Output::Token( + func.decode_output(&trace.output[..]) + .expect("Bad func output decode"), + ) + } else { + return Output::Raw(vec![]) } } } - } else { - // fallback function - println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); - - return Output::Raw(trace.output[..].to_vec()) } + } else { + // fallback function + println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); + + return Output::Raw(trace.output[..].to_vec()) } - _ => {} } println!( @@ -383,7 +378,7 @@ impl CallTraceArena { Output::Raw(trace.output[..].to_vec()) } - pub fn print_log(&self, log: &RawLog, abi: Option<&Abi>, left: &String) { + pub fn print_log(&self, log: &RawLog, abi: Option<&Abi>, left: &str) { let right = " ├─ "; if let Some(abi) = abi { for (event_name, overloaded_events) in abi.events.iter() { @@ -397,7 +392,7 @@ impl CallTraceArena { .join(", "); println!( "{}emit {}({})", - left.to_string().replace("├─", "│") + right, + left.replace("├─", "│") + right, Colour::Cyan.paint(event_name), strings ); @@ -408,14 +403,14 @@ impl CallTraceArena { } // we didnt decode the log, print it as an unknown log for (i, topic) in log.topics.iter().enumerate() { - let right = if i == log.topics.len() - 1 && log.data.len() == 0 { + let right = if i == log.topics.len() - 1 && log.data.is_empty() { " └─ " } else { " ├─" }; println!( "{}{}topic {}: {}", - left.to_string().replace("├─", "│") + right, + left.replace("├─", "│") + right, if i == 0 { " emit " } else { " " }, i, Colour::Cyan.paint(format!("0x{}", hex::encode(&topic))) @@ -423,7 +418,7 @@ impl CallTraceArena { } println!( "{} data: {}", - left.to_string().replace("├─", "│").replace("└─", " ") + " │ ", /* left.to_string().replace("├─", "│") + " └─ ", */ + left.replace("├─", "│").replace("└─", " ") + " │ ", Colour::Cyan.paint(format!("0x{}", hex::encode(&log.data))) ) } @@ -438,17 +433,17 @@ impl CallTraceArena { .join(", "); println!( "{} └─ {} {}", - left.to_string().replace("├─", "│").replace("└─", " "), + left.replace("├─", "│").replace("└─", " "), color.paint("←"), - if strings.len() == 0 { "()" } else { &*strings } + if strings.is_empty() { "()" } else { &*strings } ); } Output::Raw(bytes) => { println!( "{} └─ {} {}", - left.to_string().replace("├─", "│").replace("└─", " "), + left.replace("├─", "│").replace("└─", " "), color.paint("←"), - if bytes.len() == 0 { + if bytes.is_empty() { "()".to_string() } else { "0x".to_string() + &hex::encode(&bytes) @@ -510,7 +505,7 @@ impl CallTrace { // very simple fuzzy matching to account for immutables. Will fail for small contracts that are // basically all immutable vars -fn diff_score(bytecode1: &Vec, bytecode2: &Vec) -> f64 { +fn diff_score(bytecode1: &[u8], bytecode2: &[u8]) -> f64 { let cutoff_len = usize::min(bytecode1.len(), bytecode2.len()); let b1 = &bytecode1[..cutoff_len]; let b2 = &bytecode2[..cutoff_len]; diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index f9431a014b3b6..1b6b0c64d8bc2 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -325,7 +325,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ) -> Capture<(ExitReason, Vec), Infallible> { let mut res = vec![]; - let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), false, 0); + let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), false); // Get a mutable ref to the state so we can apply the cheats let state = self.state_mut(); let decoded = match HEVMCalls::decode(&input) { @@ -504,22 +504,21 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn start_trace( - &mut self, - address: H160, - input: Vec, - creation: bool, - prelogs: usize, - ) -> Option { + fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> Option { if self.enable_trace { - let mut trace: CallTrace = Default::default(); - // depth only starts tracking at first child substate and is 0. so add 1 when depth is - // some. - trace.depth = - if let Some(depth) = self.state().metadata().depth() { depth + 1 } else { 0 }; - trace.addr = address; - trace.created = creation; - trace.data = input; + let mut trace: CallTrace = CallTrace { + // depth only starts tracking at first child substate and is 0. so add 1 when depth + // is some. + depth: if let Some(depth) = self.state().metadata().depth() { + depth + 1 + } else { + 0 + }, + addr: address, + created: creation, + data: input, + ..Default::default() + }; // we should probably delay having the input and other stuff so // we minimize the size of the clone @@ -535,7 +534,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(new_trace) = new_trace { let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; - trace.output = output.unwrap_or(vec![]); + trace.output = output.unwrap_or_default(); trace.cost = used_gas; trace.success = success; } @@ -555,7 +554,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> context: Context, ) -> Capture<(ExitReason, Vec), Infallible> { let pre_index = self.state().trace_index; - let trace = self.start_trace(code_address, input.clone(), false, 0); + let trace = self.start_trace(code_address, input.clone(), false); macro_rules! try_or_fail { ( $e:expr ) => { @@ -708,7 +707,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let address = self.create_address(scheme); - let trace = self.start_trace(address, init_code.clone(), true, 0); + let trace = self.start_trace(address, init_code.clone(), true); macro_rules! try_or_fail { ( $e:expr ) => { diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index 6e8d1ab425f19..ee94b883b3edf 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -1,5 +1,6 @@ use crate::{runner::TestResult, ContractRunner}; -use ethers::prelude::artifacts::CompactContract; +use ethers::solc::Artifact; + use evm_adapters::Evm; use ethers::{ @@ -12,7 +13,7 @@ use ethers::{ use proptest::test_runner::TestRunner; use regex::Regex; -use eyre::{Context, Result}; +use eyre::Result; use std::{collections::BTreeMap, marker::PhantomData}; /// Builder used for instantiating the multi-contract runner @@ -61,69 +62,27 @@ impl MultiContractRunnerBuilder { let mut deployed_contracts: BTreeMap)> = Default::default(); - use std::any::Any; for (fname, contract) in contracts { - let c: &dyn Any = &contract as &dyn Any; - let compact_contract = - c.downcast_ref::().expect("Wasn't a compact contract"); - let runtime_code = compact_contract - .bin_runtime - .as_ref() - .unwrap() - .clone() - .into_bytes() - .expect("Linking not supported in tracing"); - let bytecode = compact_contract - .bin - .as_ref() - .unwrap() - .clone() - .into_bytes() - .expect("Linking not supported in tracing"); - let abi = compact_contract.abi.as_ref().unwrap(); - if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) { - if abi.functions().any(|func| func.name.starts_with("test")) { + let (maybe_abi, maybe_deploy_bytes, maybe_runtime_bytes) = contract.into_parts(); + if let (Some(abi), Some(bytecode)) = (maybe_abi, maybe_deploy_bytes) { + if abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) && + abi.functions().any(|func| func.name.starts_with("test")) + { let span = tracing::trace_span!("deploying", ?fname); let _enter = span.enter(); - - let (addr, _, _, logs) = evm - .deploy(sender, bytecode.clone(), 0u32.into()) - .wrap_err(format!("could not deploy {}", fname))?; - + let (addr, _, _, logs) = evm.deploy(sender, bytecode.clone(), 0u32.into())?; evm.set_balance(addr, initial_balance); deployed_contracts.insert(fname.clone(), (abi.clone(), addr, logs)); } + + let split = fname.split(':').collect::>(); + let contract_name = if split.len() > 1 { split[1] } else { split[0] }; + if let Some(runtime_code) = maybe_runtime_bytes { + known_contracts.insert(contract_name.to_string(), (abi, runtime_code.to_vec())); + } } - let split = fname.split(":").collect::>(); - let contract_name = if split.len() > 1 { split[1] } else { split[0] }; - known_contracts.insert(contract_name.to_string(), (abi.clone(), runtime_code.to_vec())); } - // let contracts: BTreeMap)> = contracts - // .map(|(fname, contract)| { - // let (abi, bytecode) = contract.into_inner(); - // (fname, abi.unwrap(), bytecode.unwrap()) - // }) - // // Only take contracts with empty constructors. - // .filter(|(_, abi, _)| { - // abi.constructor.as_ref().map(|c| c.inputs.is_empty()).unwrap_or(true) - // }) - // // Only take contracts which contain a `test` function - // .filter(|(_, abi, _)| abi.functions().any(|func| func.name.starts_with("test"))) - // // deploy the contracts - // .map(|(name, abi, bytecode)| { - // let span = tracing::trace_span!("deploying", ?name); - // let _enter = span.enter(); - - // let (addr, _, _, logs) = evm - // .deploy(sender, bytecode, 0.into()) - // .wrap_err(format!("could not deploy {}", name))?; - - // evm.set_balance(addr, initial_balance); - // Ok((name, (abi, addr, logs))) - // }) - // .collect::>>()?; - Ok(MultiContractRunner { contracts: deployed_contracts, known_contracts, diff --git a/forge/src/runner.rs b/forge/src/runner.rs index c5af13bdedf7d..64e44ce865ca7 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -267,8 +267,8 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let mut traces = None; if let Some(evm_traces) = self.evm.traces() { - if evm_traces.len() > 0 { - traces = Some(evm_traces.clone()); + if !evm_traces.is_empty() { + traces = Some(evm_traces); } } self.evm.reset_traces(); From 08ee0da8efce7667b6c7d43d34c377b854dab253 Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 22 Dec 2021 21:18:44 -0700 Subject: [PATCH 24/34] fixes --- cli/src/cmd/test.rs | 34 ++-- evm-adapters/src/call_tracing.rs | 153 +++++++++++------- evm-adapters/src/evmodin.rs | 2 + evm-adapters/src/lib.rs | 3 + .../sputnik/cheatcodes/cheatcode_handler.rs | 6 + evm-adapters/src/sputnik/evm.rs | 4 + evm-adapters/src/sputnik/mod.rs | 3 + forge/src/multi_runner.rs | 5 +- forge/src/runner.rs | 62 ++++++- 9 files changed, 177 insertions(+), 95 deletions(-) diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index 3683e671ebfb9..35e200ae0b834 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -326,31 +326,15 @@ fn test>( println!(); if verbosity > 2 { - if let Some(traces) = &result.traces { - let mut identified = Default::default(); - if traces.len() > 1 { - traces[0].update_identified( - 0, - &runner.known_contracts, - &mut identified, - &runner.evm, - ); - traces[1].pretty_print( - 0, - &runner.known_contracts, - &mut identified, - &runner.evm, - "".to_string(), - ); - } else { - traces[0].pretty_print( - 0, - &runner.known_contracts, - &mut identified, - &runner.evm, - "".to_string(), - ); - } + if let (Some(traces), Some(identified_contracts)) = (&result.traces, &result.identified_contracts) { + let mut ident = identified_contracts.clone(); + traces[2].pretty_print( + 0, + &runner.known_contracts, + &mut ident, + &runner.evm, + "".to_string(), + ); println!(); } } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index a49a2d57768a1..15d2d8de29abf 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -19,14 +19,7 @@ pub struct CallTraceArena { impl Default for CallTraceArena { fn default() -> Self { CallTraceArena { - arena: vec![CallTraceNode { - parent: None, - children: vec![], - idx: 0, - trace: CallTrace::default(), - logs: vec![], - ordering: vec![], - }], + arena: vec![Default::default()], entry: 0, } } @@ -39,30 +32,31 @@ pub enum Output { impl CallTraceArena { pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { - if new_trace.depth == 0 { - // overwrite - self.update(new_trace.clone()); - new_trace - } else if self.arena[entry].trace.depth == new_trace.depth - 1 { - new_trace.idx = self.arena.len(); - new_trace.location = self.arena[entry].children.len(); - self.arena[entry].ordering.push(LogCallOrder::Call(new_trace.location)); - let node = CallTraceNode { - parent: Some(entry), - children: vec![], - idx: self.arena.len(), - trace: new_trace.clone(), - logs: vec![], - ordering: vec![], - }; - self.arena.push(node); - self.arena[entry].children.push(new_trace.idx); - new_trace - } else { - self.push_trace( - *self.arena[entry].children.last().expect("Disconnected trace"), - new_trace, - ) + match new_trace.depth { + 0 => { + self.update(new_trace.clone()); + new_trace + } + _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { + new_trace.idx = self.arena.len(); + new_trace.location = self.arena[entry].children.len(); + self.arena[entry].ordering.push(LogCallOrder::Call(new_trace.location)); + let node = CallTraceNode { + parent: Some(entry), + idx: self.arena.len(), + trace: new_trace.clone(), + ..Default::default() + }; + self.arena.push(node); + self.arena[entry].children.push(new_trace.idx); + new_trace + } + _ => { + self.push_trace( + *self.arena[entry].children.last().expect("Disconnected trace"), + new_trace, + ) + } } } @@ -83,42 +77,40 @@ impl CallTraceArena { #[cfg(feature = "sputnik")] identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); - let maybe_found; + + let mut abi = None; + let mut name = None; { - if let Some((name, abi)) = identified_contracts.get(&trace.addr) { - maybe_found = Some((name.clone(), abi.clone())); - } else { - maybe_found = None; + if let Some((name_, abi_)) = identified_contracts.get(&trace.addr) { + abi = Some(abi_.clone()); + name = Some(name_.clone()); } } - if let Some((_name, _abi)) = maybe_found { - if trace.created { - self.update_children(idx, contracts, identified_contracts, evm); - return - } - self.update_children(idx, contracts, identified_contracts, evm); - } else { - if trace.created { - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - self.update_children(idx, contracts, identified_contracts, evm); - return - } else { + if trace.created { + match (abi, name) { + (Some(_abi), Some(_name)) => { self.update_children(idx, contracts, identified_contracts, evm); - return + } + _ => { + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + self.update_children(idx, contracts, identified_contracts, evm); + } else { + self.update_children(idx, contracts, identified_contracts, evm); + } } } - + } else { if let Some((name, (abi, _code))) = contracts .iter() .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); // re-enter this function at this level if we found the contract - self.update_identified(idx, contracts, identified_contracts, evm); + self.update_children(idx, contracts, identified_contracts, evm); } else { self.update_children(idx, contracts, identified_contracts, evm); } @@ -324,8 +316,8 @@ impl CallTraceArena { .decode_input(&trace.data[4..]) .expect("Bad func data decode"); strings = params - .iter() - .map(|param| format!("{}", param)) + .into_iter() + .map(|param| format_token(param)) .collect::>() .join(", "); } @@ -386,8 +378,8 @@ impl CallTraceArena { if event.signature() == log.topics[0] { let params = event.parse_log(log.clone()).expect("Bad event").params; let strings = params - .iter() - .map(|param| format!("{}: {}", param.name, param.value)) + .into_iter() + .map(|param| format!("{}: {}", param.name, format_token(param.value))) .collect::>() .join(", "); println!( @@ -410,7 +402,7 @@ impl CallTraceArena { }; println!( "{}{}topic {}: {}", - left.replace("├─", "│") + right, + if i == 0 { left.replace("├─", "│") + right} else { left.replace("├─", "│") + " │ "}, if i == 0 { " emit " } else { " " }, i, Colour::Cyan.paint(format!("0x{}", hex::encode(&topic))) @@ -454,7 +446,7 @@ impl CallTraceArena { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct CallTraceNode { pub parent: Option, pub children: Vec, @@ -503,6 +495,43 @@ impl CallTrace { } } +fn format_token(param: ethers::abi::Token) -> String { + use ethers::abi::Token; + match param { + Token::Address(addr) => format!("{:?}", addr), + Token::FixedBytes(bytes) => format!("0x{}", hex::encode(&bytes)), + Token::Bytes(bytes) => format!("0x{}", hex::encode(&bytes)), + Token::Int(num) => { + if num.bit(255) { + format!("-{}", (num & ethers::types::U256::from(1u32) << 255u32).to_string()) + } else { + format!("{}", num.to_string()) + } + }, + Token::Uint(num) => format!("{}", num.to_string()), + Token::Bool(b) => format!("{}", b), + Token::String(s) => s, + Token::FixedArray(tokens) => { + let string = tokens.into_iter().map(|token| { + format_token(token) + }).collect::>().join(", "); + format!("[{}]", string) + }, + Token::Array(tokens) => { + let string = tokens.into_iter().map(|token| { + format_token(token) + }).collect::>().join(", "); + format!("[{}]", string) + }, + Token::Tuple(tokens) => { + let string = tokens.into_iter().map(|token| { + format_token(token) + }).collect::>().join(", "); + format!("({})", string) + }, + } +} + // very simple fuzzy matching to account for immutables. Will fail for small contracts that are // basically all immutable vars fn diff_score(bytecode1: &[u8], bytecode2: &[u8]) -> f64 { diff --git a/evm-adapters/src/evmodin.rs b/evm-adapters/src/evmodin.rs index 4ff24c5c07054..f4ca222de4c58 100644 --- a/evm-adapters/src/evmodin.rs +++ b/evm-adapters/src/evmodin.rs @@ -63,6 +63,8 @@ impl Evm for EvmOdin { self.host = state; } + fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { false } + fn initialize_contracts>(&mut self, contracts: I) { contracts.into_iter().for_each(|(address, bytecode)| { self.host.set_code(address, bytecode.0); diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 3201682c0d658..4cb6564631500 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -76,6 +76,9 @@ pub trait Evm { /// Resets the EVM's state to the provided value fn reset(&mut self, state: State); + /// Turns on/off tracing + fn set_tracing_enabled(&mut self, enabled: bool) -> bool; + /// Performs a [`call_unchecked`](Self::call_unchecked), checks if execution reverted, and /// proceeds to return the decoded response to the user. fn call( diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 1b6b0c64d8bc2..d2e62f2bc06d4 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -92,6 +92,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor bool { + let curr = self.state_mut().trace_enabled; + self.state_mut().trace_enabled = enabled; + curr + } + fn gas_left(&self) -> U256 { // NB: We do this to avoid `function cannot return without recursing` U256::from(self.state().metadata().gasometer().gas()) diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 492b21be02712..46c3c6477bd0d 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -83,6 +83,10 @@ where *_state = state; } + fn set_tracing_enabled(&mut self, enabled: bool) -> bool { + self.executor.set_tracing_enabled(enabled) + } + /// given an iterator of contract address to contract bytecode, initializes /// the state with the contract deployed at the specified address fn initialize_contracts>(&mut self, contracts: T) { diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 4b8d833b65b1f..51b3138e994a3 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -63,6 +63,7 @@ pub trait SputnikExecutor { fn state(&self) -> &S; fn state_mut(&mut self) -> &mut S; fn expected_revert(&self) -> Option<&[u8]>; + fn set_tracing_enabled(&mut self, enabled: bool) -> bool; fn gas_left(&self) -> U256; fn transact_call( &mut self, @@ -125,6 +126,8 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor None } + fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { false } + fn gas_left(&self) -> U256 { // NB: We do this to avoid `function cannot return without recursing` U256::from(self.state().metadata().gasometer().gas()) diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index ee94b883b3edf..f63a6a2dc06e5 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -86,6 +86,7 @@ impl MultiContractRunnerBuilder { Ok(MultiContractRunner { contracts: deployed_contracts, known_contracts, + identified_contracts: Default::default(), evm, state: PhantomData, sender: self.sender, @@ -120,6 +121,8 @@ pub struct MultiContractRunner { pub contracts: BTreeMap)>, /// Compiled contracts by name that have an Abi and runtime bytecode pub known_contracts: BTreeMap)>, + /// Identified contracts by test + pub identified_contracts: BTreeMap>, /// The EVM instance used in the test runner pub evm: E, /// The fuzzer which will be used to run parametric tests (w/ non-0 solidity args) @@ -176,7 +179,7 @@ where ) -> Result> { let mut runner = ContractRunner::new(&mut self.evm, contract, address, self.sender, init_logs); - runner.run_tests(pattern, self.fuzzer.as_mut(), init_state) + runner.run_tests(pattern, self.fuzzer.as_mut(), init_state, Some(&self.known_contracts)) } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 64e44ce865ca7..76d1e8a4d8870 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -6,7 +6,7 @@ use evm_adapters::call_tracing::CallTraceArena; use evm_adapters::{ fuzz::{FuzzTestResult, FuzzedCases, FuzzedExecutor}, - Evm, EvmError, + Evm, EvmError }; use eyre::{Context, Result}; use regex::Regex; @@ -59,6 +59,9 @@ pub struct TestResult { /// Traces pub traces: Option>, + + /// Identified contracts + pub identified_contracts: Option>, } impl TestResult { @@ -168,6 +171,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { regex: &Regex, fuzzer: Option<&mut TestRunner>, init_state: &S, + known_contracts: Option<&BTreeMap)>>, ) -> Result> { tracing::info!("starting tests"); let start = Instant::now(); @@ -187,7 +191,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { .map(|func| { // Before each test run executes, ensure we're at our initial state. self.evm.reset(init_state.clone()); - let result = self.run_test(func, needs_setup)?; + let result = self.run_test(func, needs_setup, known_contracts)?; Ok((func.signature(), result)) }) .collect::>>()?; @@ -218,7 +222,12 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { } #[tracing::instrument(name = "test", skip_all, fields(name = %func.signature()))] - pub fn run_test(&mut self, func: &Function, setup: bool) -> Result { + pub fn run_test( + &mut self, + func: &Function, + setup: bool, + known_contracts: Option<&BTreeMap)>>, + ) -> Result { let start = Instant::now(); // the expected result depends on the function name // DAppTools' ds-test will not revert inside its `assertEq`-like functions @@ -242,6 +251,27 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { logs.extend_from_slice(&setup_logs); } + let mut traces: Option> = None; + let mut identified_contracts: Option> = None; + let mut ident = BTreeMap::new(); + if let Some(evm_traces) = self.evm.traces() { + if !evm_traces.is_empty() { + let mut ident = BTreeMap::new(); + let evm_traces = evm_traces.into_iter().map(|trace: CallTraceArena| { + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + trace + }).collect(); + traces = Some(evm_traces); + } + } + + self.evm.reset_traces(); + let (status, reason, gas_used, logs) = match self.evm.call::<(), _, _>( self.sender, self.address, @@ -265,12 +295,25 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { }, }; - let mut traces = None; if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() { - traces = Some(evm_traces); + let evm_traces = evm_traces.into_iter().map(|trace: CallTraceArena| { + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + trace + }).collect::>(); + identified_contracts = Some(ident); + if let Some(ref mut traces) = traces { + traces.extend(evm_traces); + } } } + + self.evm.reset_traces(); let success = self.evm.check_success(self.address, &status, should_fail); @@ -285,6 +328,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { logs, kind: TestKind::Standard(gas_used), traces, + identified_contracts, }) } @@ -295,6 +339,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { setup: bool, runner: TestRunner, ) -> Result { + let prev = self.evm.set_tracing_enabled(false); let start = Instant::now(); let should_fail = func.name.starts_with("testFail"); tracing::debug!(func = ?func.signature(), should_fail, "fuzzing"); @@ -329,6 +374,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let duration = Instant::now().duration_since(start); tracing::debug!(?duration, %success); + self.evm.set_tracing_enabled(prev); // from that call? Ok(TestResult { success, @@ -338,6 +384,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { logs: vec![], kind: TestKind::Fuzz(cases), traces: None, + identified_contracts: None, }) } } @@ -399,6 +446,7 @@ mod tests { &Regex::from_str("testGreeting").unwrap(), Some(&mut fuzzer), &init_state, + None, ) .unwrap(); assert!(results["testGreeting()"].success); @@ -428,7 +476,7 @@ mod tests { cfg.failure_persistence = None; let mut fuzzer = TestRunner::new(cfg); let results = runner - .run_tests(&Regex::from_str("testFuzz.*").unwrap(), Some(&mut fuzzer), &init_state) + .run_tests(&Regex::from_str("testFuzz.*").unwrap(), Some(&mut fuzzer), &init_state, None) .unwrap(); for (_, res) in results { assert!(!res.success); @@ -537,7 +585,7 @@ mod tests { let mut runner = ContractRunner::new(&mut evm, compiled.abi.as_ref().unwrap(), addr, None, &[]); - let res = runner.run_tests(&".*".parse().unwrap(), None, &init_state).unwrap(); + let res = runner.run_tests(&".*".parse().unwrap(), None, &init_state, None).unwrap(); assert!(!res.is_empty()); assert!(res.iter().all(|(_, result)| result.success)); } From 6271684c1b6fc662c65446fe48d070e9f99f5fd1 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 07:55:17 -0700 Subject: [PATCH 25/34] more cleanup --- cli/src/cmd/test.rs | 4 +- evm-adapters/src/call_tracing.rs | 78 ++++++++++++++------------------ evm-adapters/src/evmodin.rs | 4 +- evm-adapters/src/sputnik/mod.rs | 4 +- forge/src/runner.rs | 52 ++++++++++++--------- 5 files changed, 74 insertions(+), 68 deletions(-) diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index 35e200ae0b834..b98732154f391 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -326,7 +326,9 @@ fn test>( println!(); if verbosity > 2 { - if let (Some(traces), Some(identified_contracts)) = (&result.traces, &result.identified_contracts) { + if let (Some(traces), Some(identified_contracts)) = + (&result.traces, &result.identified_contracts) + { let mut ident = identified_contracts.clone(); traces[2].pretty_print( 0, diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 15d2d8de29abf..9cf0208fcdf1c 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -18,10 +18,7 @@ pub struct CallTraceArena { impl Default for CallTraceArena { fn default() -> Self { - CallTraceArena { - arena: vec![Default::default()], - entry: 0, - } + CallTraceArena { arena: vec![Default::default()], entry: 0 } } } @@ -51,12 +48,10 @@ impl CallTraceArena { self.arena[entry].children.push(new_trace.idx); new_trace } - _ => { - self.push_trace( - *self.arena[entry].children.last().expect("Disconnected trace"), - new_trace, - ) - } + _ => self.push_trace( + *self.arena[entry].children.last().expect("Disconnected trace"), + new_trace, + ), } } @@ -77,7 +72,6 @@ impl CallTraceArena { #[cfg(feature = "sputnik")] identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); - let mut abi = None; let mut name = None; { @@ -88,33 +82,31 @@ impl CallTraceArena { } if trace.created { match (abi, name) { - (Some(_abi), Some(_name)) => { - self.update_children(idx, contracts, identified_contracts, evm); - } + (Some(_abi), Some(_name)) => {} _ => { if let Some((name, (abi, _code))) = contracts .iter() .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - self.update_children(idx, contracts, identified_contracts, evm); - } else { - self.update_children(idx, contracts, identified_contracts, evm); } } } } else { - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - // re-enter this function at this level if we found the contract - self.update_children(idx, contracts, identified_contracts, evm); - } else { - self.update_children(idx, contracts, identified_contracts, evm); + match (abi, name) { + (Some(_abi), Some(_name)) => {} + _ => { + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + } + } } } + + self.update_children(idx, contracts, identified_contracts, evm); } pub fn update_children<'a, S: Clone, E: crate::Evm>( @@ -317,7 +309,7 @@ impl CallTraceArena { .expect("Bad func data decode"); strings = params .into_iter() - .map(|param| format_token(param)) + .map(format_token) .collect::>() .join(", "); } @@ -402,7 +394,11 @@ impl CallTraceArena { }; println!( "{}{}topic {}: {}", - if i == 0 { left.replace("├─", "│") + right} else { left.replace("├─", "│") + " │ "}, + if i == 0 { + left.replace("├─", "│") + right + } else { + left.replace("├─", "│") + " │ " + }, if i == 0 { " emit " } else { " " }, i, Colour::Cyan.paint(format!("0x{}", hex::encode(&topic))) @@ -503,32 +499,26 @@ fn format_token(param: ethers::abi::Token) -> String { Token::Bytes(bytes) => format!("0x{}", hex::encode(&bytes)), Token::Int(num) => { if num.bit(255) { - format!("-{}", (num & ethers::types::U256::from(1u32) << 255u32).to_string()) + format!("-{}", (num & ethers::types::U256::from(1u32) << 255u32)) } else { - format!("{}", num.to_string()) + num.to_string() } - }, - Token::Uint(num) => format!("{}", num.to_string()), + } + Token::Uint(num) => num.to_string(), Token::Bool(b) => format!("{}", b), Token::String(s) => s, Token::FixedArray(tokens) => { - let string = tokens.into_iter().map(|token| { - format_token(token) - }).collect::>().join(", "); + let string = tokens.into_iter().map(format_token).collect::>().join(", "); format!("[{}]", string) - }, + } Token::Array(tokens) => { - let string = tokens.into_iter().map(|token| { - format_token(token) - }).collect::>().join(", "); + let string = tokens.into_iter().map(format_token).collect::>().join(", "); format!("[{}]", string) - }, + } Token::Tuple(tokens) => { - let string = tokens.into_iter().map(|token| { - format_token(token) - }).collect::>().join(", "); + let string = tokens.into_iter().map(format_token).collect::>().join(", "); format!("({})", string) - }, + } } } diff --git a/evm-adapters/src/evmodin.rs b/evm-adapters/src/evmodin.rs index f4ca222de4c58..54f51f4178536 100644 --- a/evm-adapters/src/evmodin.rs +++ b/evm-adapters/src/evmodin.rs @@ -63,7 +63,9 @@ impl Evm for EvmOdin { self.host = state; } - fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { false } + fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { + false + } fn initialize_contracts>(&mut self, contracts: I) { contracts.into_iter().for_each(|(address, bytecode)| { diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index 51b3138e994a3..d1eb4608a0137 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -126,7 +126,9 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor None } - fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { false } + fn set_tracing_enabled(&mut self, _enabled: bool) -> bool { + false + } fn gas_left(&self) -> U256 { // NB: We do this to avoid `function cannot return without recursing` diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 76d1e8a4d8870..1467ecef71866 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -6,7 +6,7 @@ use evm_adapters::call_tracing::CallTraceArena; use evm_adapters::{ fuzz::{FuzzTestResult, FuzzedCases, FuzzedExecutor}, - Evm, EvmError + Evm, EvmError, }; use eyre::{Context, Result}; use regex::Regex; @@ -257,15 +257,18 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() { let mut ident = BTreeMap::new(); - let evm_traces = evm_traces.into_iter().map(|trace: CallTraceArena| { - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - trace - }).collect(); + let evm_traces = evm_traces + .into_iter() + .map(|trace: CallTraceArena| { + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + trace + }) + .collect(); traces = Some(evm_traces); } } @@ -297,15 +300,18 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() { - let evm_traces = evm_traces.into_iter().map(|trace: CallTraceArena| { - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - trace - }).collect::>(); + let evm_traces = evm_traces + .into_iter() + .map(|trace: CallTraceArena| { + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + trace + }) + .collect::>(); identified_contracts = Some(ident); if let Some(ref mut traces) = traces { traces.extend(evm_traces); @@ -313,7 +319,6 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { } } - self.evm.reset_traces(); let success = self.evm.check_success(self.address, &status, should_fail); @@ -476,7 +481,12 @@ mod tests { cfg.failure_persistence = None; let mut fuzzer = TestRunner::new(cfg); let results = runner - .run_tests(&Regex::from_str("testFuzz.*").unwrap(), Some(&mut fuzzer), &init_state, None) + .run_tests( + &Regex::from_str("testFuzz.*").unwrap(), + Some(&mut fuzzer), + &init_state, + None, + ) .unwrap(); for (_, res) in results { assert!(!res.success); From ee8889262be1888a31f993e82267f527246ff211 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 08:20:19 -0700 Subject: [PATCH 26/34] cleanup and verbosity > 3 setup print --- cli/src/cmd/test.rs | 28 +++++++++++++++++++++------- forge/src/runner.rs | 43 ++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index b98732154f391..79e1167e8971b 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -330,13 +330,27 @@ fn test>( (&result.traces, &result.identified_contracts) { let mut ident = identified_contracts.clone(); - traces[2].pretty_print( - 0, - &runner.known_contracts, - &mut ident, - &runner.evm, - "".to_string(), - ); + if verbosity > 3 { + // print setup calls as well + traces.iter().for_each(|trace| { + trace.pretty_print( + 0, + &runner.known_contracts, + &mut ident, + &runner.evm, + "".to_string(), + ); + }); + } else if !traces.is_empty() { + traces.last().expect("no last but not empty").pretty_print( + 0, + &runner.known_contracts, + &mut ident, + &runner.evm, + "".to_string(), + ); + } + println!(); } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 1467ecef71866..fd0ffeebefc2d 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -256,20 +256,14 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let mut ident = BTreeMap::new(); if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() { - let mut ident = BTreeMap::new(); - let evm_traces = evm_traces - .into_iter() - .map(|trace: CallTraceArena| { - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - trace - }) - .collect(); - traces = Some(evm_traces); + let trace = evm_traces.into_iter().next().expect("no trace"); + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + traces = Some(vec![trace]); } } @@ -300,21 +294,16 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() { - let evm_traces = evm_traces - .into_iter() - .map(|trace: CallTraceArena| { - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - trace - }) - .collect::>(); + let trace = evm_traces.into_iter().next().expect("no trace"); + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); identified_contracts = Some(ident); if let Some(ref mut traces) = traces { - traces.extend(evm_traces); + traces.push(trace); } } } From dc10a70f4d066021ffea722adadb9d974f57cd7b Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 08:34:06 -0700 Subject: [PATCH 27/34] refactor printing --- evm-adapters/src/call_tracing.rs | 248 ++++++++++++++++--------------- 1 file changed, 126 insertions(+), 122 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 9cf0208fcdf1c..d3c296e51fc3c 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -167,7 +167,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - left.clone(), + &left, ); println!( "{} └─ {} {} bytes of code", @@ -196,7 +196,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - left.clone(), + &left, ); println!( "{} └─ {} {} bytes of code", @@ -206,14 +206,19 @@ impl CallTraceArena { ); } else { // we couldn't identify, print the children and logs without the abi - println!("{}→ new @{}", left, trace.addr); + println!( + "{}{} @{}", + left, + Colour::Yellow.paint("→ new"), + trace.addr + ); self.print_children_and_logs( idx, None, contracts, identified_contracts, evm, - left.clone(), + &left, ); println!( "{} └─ {} {} bytes of code", @@ -227,15 +232,14 @@ impl CallTraceArena { } else { match (abi, name) { (Some(abi), Some(name)) => { - let output = - Self::print_func_call(trace, Some(&abi), Some(&name), color, &left); + let output = trace.print_func_call(Some(&abi), Some(&name), color, &left); self.print_children_and_logs( idx, Some(&abi), contracts, identified_contracts, evm, - left.clone(), + &left, ); Self::print_output(color, output, left); } @@ -248,14 +252,14 @@ impl CallTraceArena { // re-enter this function at this level if we found the contract self.pretty_print(idx, contracts, identified_contracts, evm, left); } else { - let output = Self::print_func_call(trace, None, None, color, &left); + let output = trace.print_func_call(None, None, color, &left); self.print_children_and_logs( idx, None, contracts, identified_contracts, evm, - left.clone(), + &left, ); Self::print_output(color, output, left); } @@ -271,11 +275,11 @@ impl CallTraceArena { contracts: &BTreeMap)>, identified_contracts: &mut BTreeMap, evm: &'a E, - left: String, + left: &str, ) { self.arena[node_idx].ordering.iter().for_each(|ordering| match ordering { LogCallOrder::Log(index) => { - self.print_log(&self.arena[node_idx].logs[*index], abi, &left); + self.arena[node_idx].print_log(*index, abi, left); } LogCallOrder::Call(index) => { self.pretty_print( @@ -289,80 +293,49 @@ impl CallTraceArena { }); } - /// Prints function call, optionally returning the decoded output - pub fn print_func_call( - trace: &CallTrace, - abi: Option<&Abi>, - name: Option<&String>, - color: Colour, - left: &str, - ) -> Output { - if let (Some(abi), Some(name)) = (abi, name) { - if trace.data.len() >= 4 { - for (func_name, overloaded_funcs) in abi.functions.iter() { - for func in overloaded_funcs.iter() { - if func.selector() == trace.data[0..4] { - let mut strings = "".to_string(); - if !trace.data[4..].is_empty() { - let params = func - .decode_input(&trace.data[4..]) - .expect("Bad func data decode"); - strings = params - .into_iter() - .map(format_token) - .collect::>() - .join(", "); - } - - println!( - "{}[{}] {}::{}({})", - left, - trace.cost, - color.paint(name), - color.paint(func_name), - strings - ); - - if !trace.output.is_empty() { - return Output::Token( - func.decode_output(&trace.output[..]) - .expect("Bad func output decode"), - ) - } else { - return Output::Raw(vec![]) - } - } + pub fn print_output(color: Colour, output: Output, left: String) { + match output { + Output::Token(token) => { + let strings = + token.into_iter().map(format_token).collect::>().join(", "); + println!( + "{} └─ {} {}", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + if strings.is_empty() { "()" } else { &*strings } + ); + } + Output::Raw(bytes) => { + println!( + "{} └─ {} {}", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + if bytes.is_empty() { + "()".to_string() + } else { + "0x".to_string() + &hex::encode(&bytes) } - } - } else { - // fallback function - println!("{}[{}] {}::fallback()", left, trace.cost, color.paint(name),); - - return Output::Raw(trace.output[..].to_vec()) + ); } } - - println!( - "{}[{}] {}::{}({})", - left, - trace.cost, - color.paint(format!("{}", trace.addr)), - if trace.data.len() >= 4 { - hex::encode(&trace.data[0..4]) - } else { - hex::encode(&trace.data[..]) - }, - if trace.data.len() >= 4 { - hex::encode(&trace.data[4..]) - } else { - hex::encode(&vec![][..]) - } - ); - - Output::Raw(trace.output[..].to_vec()) } +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct CallTraceNode { + pub parent: Option, + pub children: Vec, + pub idx: usize, + pub trace: CallTrace, + /// Logs + #[serde(skip)] + pub logs: Vec, + pub ordering: Vec, +} - pub fn print_log(&self, log: &RawLog, abi: Option<&Abi>, left: &str) { +impl CallTraceNode { + pub fn print_log(&self, index: usize, abi: Option<&Abi>, left: &str) { + let log = &self.logs[index]; let right = " ├─ "; if let Some(abi) = abi { for (event_name, overloaded_events) in abi.events.iter() { @@ -410,48 +383,6 @@ impl CallTraceArena { Colour::Cyan.paint(format!("0x{}", hex::encode(&log.data))) ) } - - pub fn print_output(color: Colour, output: Output, left: String) { - match output { - Output::Token(token) => { - let strings = token - .iter() - .map(|param| format!("{}", param)) - .collect::>() - .join(", "); - println!( - "{} └─ {} {}", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - if strings.is_empty() { "()" } else { &*strings } - ); - } - Output::Raw(bytes) => { - println!( - "{} └─ {} {}", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - if bytes.is_empty() { - "()".to_string() - } else { - "0x".to_string() + &hex::encode(&bytes) - } - ); - } - } - } -} - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct CallTraceNode { - pub parent: Option, - pub children: Vec, - pub idx: usize, - pub trace: CallTrace, - /// Logs - #[serde(skip)] - pub logs: Vec, - pub ordering: Vec, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -489,6 +420,79 @@ impl CallTrace { self.data = new_trace.data; self.addr = new_trace.addr; } + + /// Prints function call, returning the decoded or raw output + pub fn print_func_call( + &self, + abi: Option<&Abi>, + name: Option<&String>, + color: Colour, + left: &str, + ) -> Output { + if let (Some(abi), Some(name)) = (abi, name) { + if self.data.len() >= 4 { + for (func_name, overloaded_funcs) in abi.functions.iter() { + for func in overloaded_funcs.iter() { + if func.selector() == self.data[0..4] { + let mut strings = "".to_string(); + if !self.data[4..].is_empty() { + let params = func + .decode_input(&self.data[4..]) + .expect("Bad func data decode"); + strings = params + .into_iter() + .map(format_token) + .collect::>() + .join(", "); + } + + println!( + "{}[{}] {}::{}({})", + left, + self.cost, + color.paint(name), + color.paint(func_name), + strings + ); + + if !self.output.is_empty() { + return Output::Token( + func.decode_output(&self.output[..]) + .expect("Bad func output decode"), + ) + } else { + return Output::Raw(vec![]) + } + } + } + } + } else { + // fallback function + println!("{}[{}] {}::fallback()", left, self.cost, color.paint(name),); + + return Output::Raw(self.output[..].to_vec()) + } + } + + println!( + "{}[{}] {}::{}({})", + left, + self.cost, + color.paint(format!("{}", self.addr)), + if self.data.len() >= 4 { + hex::encode(&self.data[0..4]) + } else { + hex::encode(&self.data[..]) + }, + if self.data.len() >= 4 { + hex::encode(&self.data[4..]) + } else { + hex::encode(&vec![][..]) + } + ); + + Output::Raw(self.output[..].to_vec()) + } } fn format_token(param: ethers::abi::Token) -> String { From c93da0a721b5ee52581074a936bd13f8ba883958 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 09:12:14 -0700 Subject: [PATCH 28/34] documentation + cleanup --- cli/src/cmd/test.rs | 4 +- evm-adapters/src/call_tracing.rs | 156 +++++++++++++----- .../sputnik/cheatcodes/cheatcode_handler.rs | 80 +++++---- 3 files changed, 155 insertions(+), 85 deletions(-) diff --git a/cli/src/cmd/test.rs b/cli/src/cmd/test.rs index 79e1167e8971b..d61dd2c5bd27b 100644 --- a/cli/src/cmd/test.rs +++ b/cli/src/cmd/test.rs @@ -338,7 +338,7 @@ fn test>( &runner.known_contracts, &mut ident, &runner.evm, - "".to_string(), + "", ); }); } else if !traces.is_empty() { @@ -347,7 +347,7 @@ fn test>( &runner.known_contracts, &mut ident, &runner.evm, - "".to_string(), + "", ); } diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index d3c296e51fc3c..496196697ef17 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -11,8 +11,11 @@ use ansi_term::Colour; use crate::sputnik::cheatcodes::{cheatcode_handler::CHEATCODE_ADDRESS, HEVM_ABI}; #[derive(Debug, Clone, Serialize, Deserialize)] +/// An arena of `CallTraceNode`s pub struct CallTraceArena { + /// The arena of nodes pub arena: Vec, + /// The entry index, denoting the first node's index in the arena pub entry: usize, } @@ -22,18 +25,55 @@ impl Default for CallTraceArena { } } +/// Function output type pub enum Output { + /// Decoded vec of tokens Token(Vec), + /// Not decoded raw bytes Raw(Vec), } +impl Output { + /// Prints the output of a function call + pub fn print(self, color: Colour, left: &str) { + match self { + Output::Token(token) => { + let strings = + token.into_iter().map(format_token).collect::>().join(", "); + println!( + "{} └─ {} {}", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + if strings.is_empty() { "()" } else { &*strings } + ); + } + Output::Raw(bytes) => { + println!( + "{} └─ {} {}", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + if bytes.is_empty() { + "()".to_string() + } else { + "0x".to_string() + &hex::encode(&bytes) + } + ); + } + } + } +} + impl CallTraceArena { + /// Pushes a new trace into the arena, returning the trace that was passed in with updated + /// values pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { match new_trace.depth { + // The entry node, just update it 0 => { self.update(new_trace.clone()); new_trace } + // we found the parent node, add the new trace as a child _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { new_trace.idx = self.arena.len(); new_trace.location = self.arena[entry].children.len(); @@ -48,6 +88,7 @@ impl CallTraceArena { self.arena[entry].children.push(new_trace.idx); new_trace } + // we haven't found the parent node, go deeper _ => self.push_trace( *self.arena[entry].children.last().expect("Disconnected trace"), new_trace, @@ -55,11 +96,27 @@ impl CallTraceArena { } } + /// Updates the values in the calltrace held by the arena based on the passed in trace pub fn update(&mut self, trace: CallTrace) { let node = &mut self.arena[trace.idx]; node.trace.update(trace); } + /// Updates `identified_contracts` for future use so that after an `evm.reset_state()`, we + /// already know which contract corresponds to which address. + /// + /// `idx` is the call arena index to start at. Generally this will be 0, but if you want to + /// update a subset of the tree, you can pass in a different index + /// + /// `contracts` are the known contracts of (name => (abi, runtime_code)). It is used to identify + /// a deployed contract. + /// + /// `identified_contracts` are the identified contract addresses built up from comparing + /// deployed contracts against `contracts` + /// + /// `evm` is the evm that we used so that we can grab deployed code if needed. A lot of times, + /// the evm state is reset so we wont have any code but it can be useful if we want to + /// pretty print right after a test. pub fn update_identified<'a, S: Clone, E: crate::Evm>( &self, idx: usize, @@ -84,6 +141,8 @@ impl CallTraceArena { match (abi, name) { (Some(_abi), Some(_name)) => {} _ => { + // if its a creation call, check the output instead of asking the evm for the + // runtime code if let Some((name, (abi, _code))) = contracts .iter() .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) @@ -96,6 +155,7 @@ impl CallTraceArena { match (abi, name) { (Some(_abi), Some(_name)) => {} _ => { + // check the code at the address and try to find the corresponding contract if let Some((name, (abi, _code))) = contracts .iter() .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) @@ -106,9 +166,11 @@ impl CallTraceArena { } } + // update all children nodes self.update_children(idx, contracts, identified_contracts, evm); } + /// Updates all children nodes by recursing into `update_identified` pub fn update_children<'a, S: Clone, E: crate::Evm>( &self, idx: usize, @@ -122,13 +184,30 @@ impl CallTraceArena { }); } + /// Pretty print a CallTraceArena + /// + /// `idx` is the call arena index to start at. Generally this will be 0, but if you want to + /// print a subset of the tree, you can pass in a different index + /// + /// `contracts` are the known contracts of (name => (abi, runtime_code)). It is used to identify + /// a deployed contract. + /// + /// `identified_contracts` are the identified contract addresses built up from comparing + /// deployed contracts against `contracts` + /// + /// `evm` is the evm that we used so that we can grab deployed code if needed. A lot of times, + /// the evm state is reset so we wont have any code but it can be useful if we want to + /// pretty print right after a test. + /// + /// For a user, `left` input should generally be `""`. Left is used recursively + /// to build the tree print out structure and is built up as we recurse down the tree. pub fn pretty_print<'a, S: Clone, E: crate::Evm>( &self, idx: usize, contracts: &BTreeMap)>, identified_contracts: &mut BTreeMap, evm: &'a E, - left: String, + left: &str, ) { let trace = &self.arena[idx].trace; @@ -136,6 +215,7 @@ impl CallTraceArena { identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); #[cfg(feature = "sputnik")] + // color the trace function call & output by success let color = if trace.addr == *CHEATCODE_ADDRESS { Colour::Blue } else if trace.success { @@ -159,7 +239,8 @@ impl CallTraceArena { if trace.created { match (abi, name) { (Some(abi), Some(name)) => { - // if we have a match already, print it like normal + // if we have identified the address already, decode and print with the provided + // name and abi println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); self.print_children_and_logs( idx, @@ -167,7 +248,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - &left, + left, ); println!( "{} └─ {} {} bytes of code", @@ -196,7 +277,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - &left, + left, ); println!( "{} └─ {} {} bytes of code", @@ -218,7 +299,7 @@ impl CallTraceArena { contracts, identified_contracts, evm, - &left, + left, ); println!( "{} └─ {} {} bytes of code", @@ -232,16 +313,18 @@ impl CallTraceArena { } else { match (abi, name) { (Some(abi), Some(name)) => { - let output = trace.print_func_call(Some(&abi), Some(&name), color, &left); + // print the function call, grab the output, print the children and logs, and + // finally output + let output = trace.print_func_call(Some(&abi), Some(&name), color, left); self.print_children_and_logs( idx, Some(&abi), contracts, identified_contracts, evm, - &left, + left, ); - Self::print_output(color, output, left); + output.print(color, left); } _ => { if let Some((name, (abi, _code))) = contracts @@ -249,25 +332,27 @@ impl CallTraceArena { .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) { identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - // re-enter this function at this level if we found the contract + // re-enter this function at this node level if we found the contract self.pretty_print(idx, contracts, identified_contracts, evm, left); } else { - let output = trace.print_func_call(None, None, color, &left); + // we couldn't identify it, print without abi and name + let output = trace.print_func_call(None, None, color, left); self.print_children_and_logs( idx, None, contracts, identified_contracts, evm, - &left, + left, ); - Self::print_output(color, output, left); + output.print(color, left); } } } } } + /// Prints child calls and logs in order pub fn print_children_and_logs<'a, S: Clone, E: crate::Evm>( &self, node_idx: usize, @@ -277,6 +362,9 @@ impl CallTraceArena { evm: &'a E, left: &str, ) { + // Ordering stores a vec of `LogCallOrder` which is populated based on if + // a log or a call was called first. This makes it such that we always print + // logs and calls in the correct order self.arena[node_idx].ordering.iter().for_each(|ordering| match ordering { LogCallOrder::Log(index) => { self.arena[node_idx].print_log(*index, abi, left); @@ -287,53 +375,33 @@ impl CallTraceArena { contracts, identified_contracts, evm, - left.replace("├─", "│").replace("└─", " ") + " ├─ ", + &(left.replace("├─", "│").replace("└─", " ") + " ├─ "), ); } }); } - - pub fn print_output(color: Colour, output: Output, left: String) { - match output { - Output::Token(token) => { - let strings = - token.into_iter().map(format_token).collect::>().join(", "); - println!( - "{} └─ {} {}", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - if strings.is_empty() { "()" } else { &*strings } - ); - } - Output::Raw(bytes) => { - println!( - "{} └─ {} {}", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - if bytes.is_empty() { - "()".to_string() - } else { - "0x".to_string() + &hex::encode(&bytes) - } - ); - } - } - } } #[derive(Default, Debug, Clone, Serialize, Deserialize)] +/// A node in the arena pub struct CallTraceNode { + /// Parent node index in the arena pub parent: Option, + /// Children node indexes in the arena pub children: Vec, + /// This node's index in the arena pub idx: usize, + /// The call trace pub trace: CallTrace, /// Logs #[serde(skip)] pub logs: Vec, + /// Ordering of child calls and logs pub ordering: Vec, } impl CallTraceNode { + /// Prints a log at a particular index, optionally decoding if abi is provided pub fn print_log(&self, index: usize, abi: Option<&Abi>, left: &str) { let log = &self.logs[index]; let right = " ├─ "; @@ -386,6 +454,10 @@ impl CallTraceNode { } #[derive(Debug, Clone, Serialize, Deserialize)] +/// Ordering enum for calls and logs +/// +/// i.e. if Call 0 occurs before Log 0, it will be pushed into the `CallTraceNode`'s ordering before +/// the log. pub enum LogCallOrder { Log(usize), Call(usize), @@ -412,6 +484,7 @@ pub struct CallTrace { } impl CallTrace { + /// Updates a trace given another trace fn update(&mut self, new_trace: Self) { self.success = new_trace.success; self.addr = new_trace.addr; @@ -430,6 +503,7 @@ impl CallTrace { left: &str, ) -> Output { if let (Some(abi), Some(name)) = (abi, name) { + // Is data longer than 4, meaning we can attempt to decode it if self.data.len() >= 4 { for (func_name, overloaded_funcs) in abi.functions.iter() { for func in overloaded_funcs.iter() { @@ -474,6 +548,7 @@ impl CallTrace { } } + // We couldn't decode the function call, so print it as an abstract call println!( "{}[{}] {}::{}({})", left, @@ -495,6 +570,7 @@ impl CallTrace { } } +// Gets pretty print strings for tokens fn format_token(param: ethers::abi::Token) -> String { use ethers::abi::Token; match param { diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index d2e62f2bc06d4..638eb266cd125 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -330,7 +330,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> msg_sender: H160, ) -> Capture<(ExitReason, Vec), Infallible> { let mut res = vec![]; - + let pre_index = self.state().trace_index; let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), false); // Get a mutable ref to the state so we can apply the cheats let state = self.state_mut(); @@ -495,7 +495,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } }; - self.fill_trace(trace, true, Some(res.clone())); + self.fill_trace(&trace, true, Some(res.clone()), pre_index); // TODO: Add more cheat codes. Capture::Exit((ExitReason::Succeed(ExitSucceed::Stopped), res)) @@ -536,7 +536,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn fill_trace(&mut self, new_trace: Option, success: bool, output: Option>) { + fn fill_trace( + &mut self, + new_trace: &Option, + success: bool, + output: Option>, + pre_trace_index: usize, + ) { + self.state_mut().trace_index = pre_trace_index; if let Some(new_trace) = new_trace { let used_gas = self.handler.used_gas(); let trace = &mut self.state_mut().trace_mut().arena[new_trace.idx].trace; @@ -567,7 +574,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); return Capture::Exit((e.into(), Vec::new())) } } @@ -608,7 +615,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitError::CallTooDeep.into(), Vec::new())) } @@ -618,7 +625,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().transfer(transfer) { Ok(()) => (), Err(e) => { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Reverted); return Capture::Exit((ExitReason::Error(e), Vec::new())) } @@ -638,14 +645,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.log(address, topics, data) { Ok(_) => continue, Err(error) => { - self.fill_trace(trace, false, Some(output.clone())); + self.fill_trace(&trace, false, Some(output.clone()), pre_index); return Capture::Exit((ExitReason::Error(error), output)) } } } let _ = self.state_mut().metadata_mut().gasometer_mut().record_cost(cost); - self.fill_trace(trace, true, Some(output.clone())); + self.fill_trace(&trace, true, Some(output.clone()), pre_index); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(exit_status), output)) } @@ -657,7 +664,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } PrecompileFailure::Fatal { exit_status } => ExitReason::Fatal(exit_status), }; - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((e, Vec::new())) } @@ -670,28 +677,26 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let mut runtime = Runtime::new(Rc::new(code), Rc::new(input), context, &config); let reason = self.execute(&mut runtime); - self.state_mut().trace_index = pre_index; - // // log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, // reason); match reason { ExitReason::Succeed(s) => { - self.fill_trace(trace, true, Some(runtime.machine().return_value())); + self.fill_trace(&trace, true, Some(runtime.machine().return_value()), pre_index); let _ = self.handler.exit_substate(StackExitKind::Succeeded); Capture::Exit((ExitReason::Succeed(s), runtime.machine().return_value())) } ExitReason::Error(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(&trace, false, Some(runtime.machine().return_value()), pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Error(e), Vec::new())) } ExitReason::Revert(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(&trace, false, Some(runtime.machine().return_value()), pre_index); let _ = self.handler.exit_substate(StackExitKind::Reverted); Capture::Exit((ExitReason::Revert(e), runtime.machine().return_value())) } ExitReason::Fatal(e) => { - self.fill_trace(trace, false, Some(runtime.machine().return_value())); + self.fill_trace(&trace, false, Some(runtime.machine().return_value()), pre_index); self.state_mut().metadata_mut().gasometer_mut().fail(); let _ = self.handler.exit_substate(StackExitKind::Failed); Capture::Exit((ExitReason::Fatal(e), Vec::new())) @@ -720,7 +725,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match $e { Ok(v) => v, Err(e) => { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); return Capture::Exit((e.into(), None, Vec::new())) } } @@ -745,13 +750,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> if let Some(depth) = self.state().metadata().depth() { if depth > self.config().call_stack_limit { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())) } } if self.balance(caller) < value { - self.fill_trace(trace, false, None); + self.fill_trace(&trace, false, None, pre_index); return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())) } @@ -779,14 +784,14 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> { if self.code_size(address) != U256::zero() { + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } if self.handler.nonce(address) > U256::zero() { + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())) } @@ -798,8 +803,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().transfer(transfer) { Ok(()) => (), Err(e) => { + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Reverted); - self.fill_trace(trace, false, None); return Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } @@ -821,16 +826,16 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> // As of EIP-3541 code starting with 0xef cannot be deployed if let Err(e) = check_first_byte(self.config(), &out) { self.state_mut().metadata_mut().gasometer_mut().fail(); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); return Capture::Exit((e.into(), None, Vec::new())) } if let Some(limit) = self.config().create_contract_limit { if out.len() > limit { self.state_mut().metadata_mut().gasometer_mut().fail(); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); return Capture::Exit(( ExitError::CreateContractLimit.into(), None, @@ -841,34 +846,35 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> match self.state_mut().metadata_mut().gasometer_mut().record_deposit(out.len()) { Ok(()) => { + self.fill_trace(&trace, true, Some(out.clone()), pre_index); let e = self.handler.exit_substate(StackExitKind::Succeeded); - self.state_mut().set_code(address, out.clone()); + self.state_mut().set_code(address, out); + // this may overwrite the trace and thats okay try_or_fail!(e); - self.fill_trace(trace, true, Some(out)); Capture::Exit((ExitReason::Succeed(s), Some(address), Vec::new())) } Err(e) => { + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } } } ExitReason::Error(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Error(e), None, Vec::new())) } ExitReason::Revert(e) => { + self.fill_trace(&trace, false, Some(runtime.machine().return_value()), pre_index); let _ = self.handler.exit_substate(StackExitKind::Reverted); - self.fill_trace(trace, false, Some(runtime.machine().return_value())); Capture::Exit((ExitReason::Revert(e), None, runtime.machine().return_value())) } ExitReason::Fatal(e) => { self.state_mut().metadata_mut().gasometer_mut().fail(); + self.fill_trace(&trace, false, None, pre_index); let _ = self.handler.exit_substate(StackExitKind::Failed); - self.fill_trace(trace, false, None); Capture::Exit((ExitReason::Fatal(e), None, Vec::new())) } } @@ -1364,13 +1370,7 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print( - 0, - &mapping, - &mut identified, - &evm, - "".to_string(), - ); + evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, ""); } #[test] @@ -1434,12 +1434,6 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print( - 0, - &mapping, - &mut identified, - &evm, - "".to_string(), - ); + evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, ""); } } From 6c825a010e6434e8e42ab734108e5c26f75b7f36 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 09:53:48 -0700 Subject: [PATCH 29/34] fix negative number printing --- evm-adapters/src/call_tracing.rs | 5 +++-- evm-adapters/testdata/Trace.sol | 6 ++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 496196697ef17..cd27aa11b2777 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -577,9 +577,10 @@ fn format_token(param: ethers::abi::Token) -> String { Token::Address(addr) => format!("{:?}", addr), Token::FixedBytes(bytes) => format!("0x{}", hex::encode(&bytes)), Token::Bytes(bytes) => format!("0x{}", hex::encode(&bytes)), - Token::Int(num) => { + Token::Int(mut num) => { if num.bit(255) { - format!("-{}", (num & ethers::types::U256::from(1u32) << 255u32)) + num = num - 1; + format!("-{}", num.overflowing_neg().0) } else { num.to_string() } diff --git a/evm-adapters/testdata/Trace.sol b/evm-adapters/testdata/Trace.sol index 002f833089b4a..5775f9b471345 100644 --- a/evm-adapters/testdata/Trace.sol +++ b/evm-adapters/testdata/Trace.sol @@ -5,6 +5,7 @@ interface RecursiveCallee { function recurseCall(uint256 neededDepth, uint256 depth) external returns (uint256); function recurseCreate(uint256 neededDepth, uint256 depth) external returns (uint256); function someCall() external; + function negativeNum() external returns (int256); } contract RecursiveCall { @@ -19,6 +20,7 @@ contract RecursiveCall { function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { if (depth == neededDepth) { + RecursiveCallee(address(this)).negativeNum(); return neededDepth; } uint256 childDepth = RecursiveCallee(address(this)).recurseCall(neededDepth, depth + 1); @@ -40,6 +42,10 @@ contract RecursiveCall { } function someCall() public {} + + function negativeNum() public returns (int256) { + return -1000000000; + } } contract Trace { From 54a975f5377049bf16f08fbf25c02d6867aed300 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 10:13:59 -0700 Subject: [PATCH 30/34] fix tests to match master and fix tracing_enabled --- evm-adapters/src/lib.rs | 8 ++ .../sputnik/cheatcodes/cheatcode_handler.rs | 67 ++----------- evm-adapters/src/sputnik/evm.rs | 95 +++++++++++-------- .../src/sputnik/forked_backend/rpc.rs | 2 +- forge/src/multi_runner.rs | 29 +----- forge/src/runner.rs | 75 +++++---------- 6 files changed, 100 insertions(+), 176 deletions(-) diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 4cb6564631500..810ca3523cfda 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -79,6 +79,14 @@ pub trait Evm { /// Turns on/off tracing fn set_tracing_enabled(&mut self, enabled: bool) -> bool; + /// returns whether tracing is enabled + fn tracing_enabled(&mut self) -> bool { + // kind of hack that uses an existing method to reduce code + let curr = self.set_tracing_enabled(false); + self.set_tracing_enabled(curr); + curr + } + /// Performs a [`call_unchecked`](Self::call_unchecked), checks if execution reverted, and /// proceeds to return the decoded response to the user. fn call( diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 638eb266cd125..5e6bca115831a 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -1125,14 +1125,9 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a #[cfg(test)] mod tests { - use sputnik::Config; - use crate::{ fuzz::FuzzedExecutor, - sputnik::{ - helpers::{new_backend, new_vicinity}, - Executor, PRECOMPILES_MAP, - }, + sputnik::helpers::{vm, vm_tracing}, test_helpers::COMPILED, Evm, }; @@ -1141,14 +1136,7 @@ mod tests { #[test] fn ds_test_logs() { - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); - + let mut evm = vm(); let compiled = COMPILED.find("DebugLogs").expect("could not find contract"); let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()).unwrap(); @@ -1182,13 +1170,7 @@ mod tests { #[test] fn console_logs() { - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); + let mut evm = vm(); let compiled = COMPILED.find("ConsoleLogs").expect("could not find contract"); let (addr, _, _, _) = @@ -1213,13 +1195,7 @@ mod tests { #[test] fn logs_external_contract() { - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); + let mut evm = vm(); let compiled = COMPILED.find("DebugLogs").expect("could not find contract"); let (addr, _, _, _) = @@ -1238,14 +1214,7 @@ mod tests { #[test] fn cheatcodes() { - let config = Config::london(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, true, false); - + let mut evm = vm(); let compiled = COMPILED.find("CheatCodes").expect("could not find contract"); let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()).unwrap(); @@ -1288,13 +1257,8 @@ mod tests { #[test] fn ffi_fails_if_disabled() { - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, false); + let mut evm = vm(); + evm.executor.enable_ffi = false; let compiled = COMPILED.find("CheatCodes").expect("could not find contract"); let (addr, _, _, _) = @@ -1312,14 +1276,7 @@ mod tests { #[test] fn tracing_call() { use std::collections::BTreeMap; - - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); + let mut evm = vm_tracing(); let compiled = COMPILED.find("Trace").expect("could not find contract"); let (addr, _, _, _) = evm @@ -1377,13 +1334,7 @@ mod tests { fn tracing_create() { use std::collections::BTreeMap; - let config = Config::istanbul(); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let gas_limit = 10_000_000; - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = - Executor::new_with_cheatcodes(backend, gas_limit, &config, &precompiles, false, true); + let mut evm = vm_tracing(); let compiled = COMPILED.find("Trace").expect("could not find contract"); let (addr, _, _, _) = evm diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 46c3c6477bd0d..9483a73f9ced1 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -186,6 +186,50 @@ pub mod helpers { use ethers::types::H160; use sputnik::backend::{MemoryBackend, MemoryVicinity}; + use crate::{ + fuzz::FuzzedExecutor, + sputnik::{ + cheatcodes::cheatcode_handler::{CheatcodeStackExecutor, CheatcodeStackState}, + PrecompileFn, PRECOMPILES_MAP, + }, + }; + use once_cell::sync::Lazy; + + type TestSputnikVM<'a, B> = Executor< + // state + CheatcodeStackState<'a, B>, + // actual stack executor + CheatcodeStackExecutor<'a, 'a, B, BTreeMap>, + >; + + static CFG: Lazy = Lazy::new(Config::london); + static VICINITY: Lazy = Lazy::new(new_vicinity); + const GAS_LIMIT: u64 = 30_000_000; + + /// Instantiates a Sputnik EVM with enabled cheatcodes + FFI and a simple non-forking in memory + /// backend and tracing disabled + pub fn vm<'a>() -> TestSputnikVM<'a, MemoryBackend<'a>> { + let backend = new_backend(&*VICINITY, Default::default()); + Executor::new_with_cheatcodes(backend, GAS_LIMIT, &*CFG, &*PRECOMPILES_MAP, true, false) + } + + /// Instantiates a Sputnik EVM with enabled cheatcodes + FFI and a simple non-forking in memory + /// backend and tracing enabled + pub fn vm_tracing<'a>() -> TestSputnikVM<'a, MemoryBackend<'a>> { + let backend = new_backend(&*VICINITY, Default::default()); + Executor::new_with_cheatcodes(backend, GAS_LIMIT, &*CFG, &*PRECOMPILES_MAP, true, true) + } + + /// Instantiates a FuzzedExecutor over provided Sputnik EVM + pub fn fuzzvm<'a, B: Backend>( + evm: &'a mut TestSputnikVM<'a, B>, + ) -> FuzzedExecutor<'a, TestSputnikVM<'a, B>, CheatcodeStackState<'a, B>> { + let cfg = proptest::test_runner::Config { failure_persistence: None, ..Default::default() }; + + let runner = proptest::test_runner::TestRunner::new(cfg); + FuzzedExecutor::new(evm, runner, Address::zero()) + } + pub fn new_backend(vicinity: &MemoryVicinity, state: MemoryState) -> MemoryBackend<'_> { MemoryBackend::new(vicinity, state) } @@ -208,52 +252,33 @@ pub mod helpers { #[cfg(test)] mod tests { - use super::{ - helpers::{new_backend, new_vicinity}, - *, + use super::*; + use crate::{ + sputnik::helpers::vm, + test_helpers::{can_call_vm_directly, solidity_unit_test, COMPILED}, }; - use crate::test_helpers::{can_call_vm_directly, solidity_unit_test, COMPILED}; - - use crate::sputnik::PRECOMPILES_MAP; use ethers::utils::id; use sputnik::{ExitReason, ExitRevert, ExitSucceed}; #[test] fn sputnik_can_call_vm_directly() { - let cfg = Config::istanbul(); + let evm = vm(); let compiled = COMPILED.find("Greeter").expect("could not find contract"); - - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); can_call_vm_directly(evm, compiled); } #[test] fn sputnik_solidity_unit_test() { - let cfg = Config::istanbul(); - + let evm = vm(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); solidity_unit_test(evm, compiled); } #[test] fn failing_with_no_reason_if_no_setup() { - let cfg = Config::istanbul(); - + let mut evm = vm(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); - let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()).unwrap(); @@ -271,15 +296,9 @@ mod tests { #[test] fn failing_solidity_unit_test() { - let cfg = Config::istanbul(); - + let mut evm = vm(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); - let (addr, _, _, _) = evm.deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()).unwrap(); @@ -295,20 +314,14 @@ mod tests { _ => panic!("unexpected error variant"), }; assert_eq!(reason, "not equal to `hi`".to_string()); - assert_eq!(gas_used, 31233); + assert_eq!(gas_used, 26633); } #[test] fn test_can_call_large_contract() { - let cfg = Config::istanbul(); - + let mut evm = vm(); let compiled = COMPILED.find("LargeContract").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(13_000_000, &cfg, &backend, &precompiles); - let from = Address::random(); let (addr, _, _, _) = evm.deploy(from, compiled.bytecode().unwrap().clone(), 0.into()).unwrap(); diff --git a/evm-adapters/src/sputnik/forked_backend/rpc.rs b/evm-adapters/src/sputnik/forked_backend/rpc.rs index acbfb83f4dfc8..a3d13882b7a7a 100644 --- a/evm-adapters/src/sputnik/forked_backend/rpc.rs +++ b/evm-adapters/src/sputnik/forked_backend/rpc.rs @@ -215,7 +215,7 @@ mod tests { #[test] fn forked_backend() { - let cfg = Config::istanbul(); + let cfg = Config::london(); let compiled = COMPILED.find("Greeter").expect("could not find contract"); let provider = Provider::::try_from( diff --git a/forge/src/multi_runner.rs b/forge/src/multi_runner.rs index f63a6a2dc06e5..a223a363af0a4 100644 --- a/forge/src/multi_runner.rs +++ b/forge/src/multi_runner.rs @@ -230,29 +230,12 @@ mod tests { mod sputnik { use super::*; - use evm::Config; - use evm_adapters::sputnik::{ - helpers::{new_backend, new_vicinity}, - Executor, PRECOMPILES_MAP, - }; + use evm_adapters::sputnik::helpers::vm; use std::collections::HashMap; #[test] fn test_sputnik_debug_logs() { - let config = Config::istanbul(); - let gas_limit = 12_500_000; - let env = new_vicinity(); - let backend = new_backend(&env, Default::default()); - // important to instantiate the VM with cheatcodes - let precompiles = PRECOMPILES_MAP.clone(); - let evm = Executor::new_with_cheatcodes( - backend, - gas_limit, - &config, - &precompiles, - false, - false, - ); + let evm = vm(); let mut runner = runner(evm); let results = runner.test(Regex::new(".*").unwrap()).unwrap(); @@ -273,13 +256,7 @@ mod tests { #[test] fn test_sputnik_multi_runner() { - let config = Config::istanbul(); - let gas_limit = 12_500_000; - let env = new_vicinity(); - let backend = new_backend(&env, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let evm = Executor::new(gas_limit, &config, &backend, &precompiles); - test_multi_runner(evm); + test_multi_runner(vm()); } } diff --git a/forge/src/runner.rs b/forge/src/runner.rs index fd0ffeebefc2d..1c02bfe86f408 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -241,6 +241,10 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { self.evm.reset_traces(); // call the setup function in each test to reset the test's state. + let mut traces: Option> = None; + let mut identified_contracts: Option> = None; + let mut ident = BTreeMap::new(); + if setup { tracing::trace!("setting up"); let setup_logs = self @@ -249,21 +253,18 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { .wrap_err(format!("could not setup during {} test", func.signature()))? .1; logs.extend_from_slice(&setup_logs); - } - let mut traces: Option> = None; - let mut identified_contracts: Option> = None; - let mut ident = BTreeMap::new(); - if let Some(evm_traces) = self.evm.traces() { - if !evm_traces.is_empty() { - let trace = evm_traces.into_iter().next().expect("no trace"); - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - traces = Some(vec![trace]); + if let Some(evm_traces) = self.evm.traces() { + if !evm_traces.is_empty() && self.evm.tracing_enabled() { + let trace = evm_traces.into_iter().next().expect("no trace"); + trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + traces = Some(vec![trace]); + } } } @@ -293,7 +294,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { }; if let Some(evm_traces) = self.evm.traces() { - if !evm_traces.is_empty() { + if !evm_traces.is_empty() && self.evm.tracing_enabled() { let trace = evm_traces.into_iter().next().expect("no trace"); trace.update_identified( 0, @@ -304,6 +305,9 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { identified_contracts = Some(ident); if let Some(ref mut traces) = traces { traces.push(trace); + } else { + // we didnt have a setup + traces = Some(vec![trace]); } } } @@ -388,16 +392,11 @@ mod tests { use super::*; use crate::test_helpers::COMPILED; use ethers::solc::artifacts::CompactContractRef; - use evm::Config; - use evm_adapters::sputnik::PRECOMPILES_MAP; + use evm_adapters::sputnik::helpers::vm; mod sputnik { use std::str::FromStr; - use evm_adapters::sputnik::{ - helpers::{new_backend, new_vicinity}, - Executor, - }; use foundry_utils::get_func; use proptest::test_runner::Config as FuzzConfig; @@ -405,24 +404,15 @@ mod tests { #[test] fn test_runner() { - let cfg = Config::istanbul(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - let precompiles = PRECOMPILES_MAP.clone(); - let evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); + let evm = vm(); super::test_runner(evm, compiled); } #[test] fn test_function_overriding() { - let cfg = Config::istanbul(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); + let mut evm = vm(); let (addr, _, _, _) = evm .deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()) .unwrap(); @@ -450,13 +440,8 @@ mod tests { #[test] fn test_fuzzing_counterexamples() { - let cfg = Config::istanbul(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); + let mut evm = vm(); let (addr, _, _, _) = evm .deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()) .unwrap(); @@ -485,13 +470,8 @@ mod tests { #[test] fn test_fuzzing_ok() { - let cfg = Config::istanbul(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(u64::MAX, &cfg, &backend, &precompiles); + let mut evm = vm(); let (addr, _, _, _) = evm .deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()) .unwrap(); @@ -510,13 +490,8 @@ mod tests { #[test] fn test_fuzz_shrinking() { - let cfg = Config::istanbul(); let compiled = COMPILED.find("GreeterTest").expect("could not find contract"); - let vicinity = new_vicinity(); - let backend = new_backend(&vicinity, Default::default()); - - let precompiles = PRECOMPILES_MAP.clone(); - let mut evm = Executor::new(12_000_000, &cfg, &backend, &precompiles); + let mut evm = vm(); let (addr, _, _, _) = evm .deploy(Address::zero(), compiled.bytecode().unwrap().clone(), 0.into()) .unwrap(); From a918468ae3d672494fa9fc65e3fe384ef0c09574 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 10:59:32 -0700 Subject: [PATCH 31/34] fix unnecessary trace_index set --- evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 5e6bca115831a..2536e923a3649 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -818,7 +818,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let reason = self.execute(&mut runtime); // log::debug!(target: "evm", "Create execution using address {}: {:?}", address, reason); - self.state_mut().trace_index = pre_index; + match reason { ExitReason::Succeed(s) => { let out = runtime.machine().return_value(); From b2e9922e84a9746ae9f76122957c2f9feca8f69f Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 11:21:27 -0700 Subject: [PATCH 32/34] refactor runner tracing + tracing_enabled --- evm-adapters/src/evmodin.rs | 4 ++ evm-adapters/src/lib.rs | 11 ++-- .../sputnik/cheatcodes/cheatcode_handler.rs | 6 ++- evm-adapters/src/sputnik/evm.rs | 4 ++ evm-adapters/src/sputnik/mod.rs | 5 ++ forge/src/runner.rs | 52 +++++++++---------- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/evm-adapters/src/evmodin.rs b/evm-adapters/src/evmodin.rs index 54f51f4178536..e2c6b2a9b606b 100644 --- a/evm-adapters/src/evmodin.rs +++ b/evm-adapters/src/evmodin.rs @@ -67,6 +67,10 @@ impl Evm for EvmOdin { false } + fn tracing_enabled(&self) -> bool { + false + } + fn initialize_contracts>(&mut self, contracts: I) { contracts.into_iter().for_each(|(address, bytecode)| { self.host.set_code(address, bytecode.0); diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index 810ca3523cfda..ea267648b770a 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -76,16 +76,11 @@ pub trait Evm { /// Resets the EVM's state to the provided value fn reset(&mut self, state: State); - /// Turns on/off tracing + /// Turns on/off tracing, returning the previously set value fn set_tracing_enabled(&mut self, enabled: bool) -> bool; - /// returns whether tracing is enabled - fn tracing_enabled(&mut self) -> bool { - // kind of hack that uses an existing method to reduce code - let curr = self.set_tracing_enabled(false); - self.set_tracing_enabled(curr); - curr - } + /// Returns whether tracing is enabled + fn tracing_enabled(&self) -> bool; /// Performs a [`call_unchecked`](Self::call_unchecked), checks if execution reverted, and /// proceeds to return the decoded response to the user. diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 2536e923a3649..b136116cf5684 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -98,6 +98,10 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor bool { + self.state().trace_enabled + } + fn gas_left(&self) -> U256 { // NB: We do this to avoid `function cannot return without recursing` U256::from(self.state().metadata().gasometer().gas()) @@ -818,7 +822,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let reason = self.execute(&mut runtime); // log::debug!(target: "evm", "Create execution using address {}: {:?}", address, reason); - + match reason { ExitReason::Succeed(s) => { let out = runtime.machine().return_value(); diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 9483a73f9ced1..9a37c1307a6cc 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -87,6 +87,10 @@ where self.executor.set_tracing_enabled(enabled) } + fn tracing_enabled(&self) -> bool { + self.executor.tracing_enabled() + } + /// given an iterator of contract address to contract bytecode, initializes /// the state with the contract deployed at the specified address fn initialize_contracts>(&mut self, contracts: T) { diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index b91ce4c8f58e9..ef9511a213ab2 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -64,6 +64,7 @@ pub trait SputnikExecutor { fn state_mut(&mut self) -> &mut S; fn expected_revert(&self) -> Option<&[u8]>; fn set_tracing_enabled(&mut self, enabled: bool) -> bool; + fn tracing_enabled(&self) -> bool; fn gas_left(&self) -> U256; fn transact_call( &mut self, @@ -130,6 +131,10 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor false } + fn tracing_enabled(&self) -> bool { + false + } + fn gas_left(&self) -> U256 { // NB: We do this to avoid `function cannot return without recursing` U256::from(self.state().metadata().gasometer().gas()) diff --git a/forge/src/runner.rs b/forge/src/runner.rs index 9fe30803ab530..e14466033abbe 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -241,10 +241,6 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { self.evm.reset_traces(); // call the setup function in each test to reset the test's state. - let mut traces: Option> = None; - let mut identified_contracts: Option> = None; - let mut ident = BTreeMap::new(); - if setup { tracing::trace!("setting up"); let setup_logs = self @@ -253,23 +249,8 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { .wrap_err(format!("could not setup during {} test", func.signature()))? .1; logs.extend_from_slice(&setup_logs); - - if let Some(evm_traces) = self.evm.traces() { - if !evm_traces.is_empty() && self.evm.tracing_enabled() { - let trace = evm_traces.into_iter().next().expect("no trace"); - trace.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - traces = Some(vec![trace]); - } - } } - self.evm.reset_traces(); - let (status, reason, gas_used, logs) = match self.evm.call::<(), _, _>( self.sender, self.address, @@ -293,22 +274,39 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { }, }; + let mut traces: Option> = None; + let mut identified_contracts: Option> = None; + if let Some(evm_traces) = self.evm.traces() { if !evm_traces.is_empty() && self.evm.tracing_enabled() { - let trace = evm_traces.into_iter().next().expect("no trace"); - trace.update_identified( + let mut ident = BTreeMap::new(); + // create an iter over the traces + let mut trace_iter = evm_traces.into_iter(); + let mut temp_traces = Vec::new(); + if setup { + // grab the setup trace if it exists + let setup = trace_iter.next().expect("no setup trace"); + setup.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + temp_traces.push(setup); + } + // grab the test trace + let test_trace = trace_iter.next().expect("no test trace"); + test_trace.update_identified( 0, known_contracts.expect("traces enabled but no identified_contracts"), &mut ident, self.evm, ); + temp_traces.push(test_trace); + + // pass back the identified contracts and traces identified_contracts = Some(ident); - if let Some(ref mut traces) = traces { - traces.push(trace); - } else { - // we didnt have a setup - traces = Some(vec![trace]); - } + traces = Some(temp_traces); } } From bb9a70753c88bce6ab26fe55d295ce406638fb0e Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 13:10:17 -0700 Subject: [PATCH 33/34] nits + value printing --- evm-adapters/src/call_tracing.rs | 244 +++++++----------- evm-adapters/src/lib.rs | 4 +- .../sputnik/cheatcodes/cheatcode_handler.rs | 29 ++- evm-adapters/src/sputnik/evm.rs | 2 +- evm-adapters/src/sputnik/mod.rs | 6 +- evm-adapters/testdata/Trace.sol | 1 - forge/src/runner.rs | 51 ++-- 7 files changed, 151 insertions(+), 186 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index cd27aa11b2777..7593198628fa1 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -1,6 +1,6 @@ use ethers::{ abi::{Abi, FunctionExt, RawLog}, - types::H160, + types::{H160, U256}, }; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -129,40 +129,14 @@ impl CallTraceArena { #[cfg(feature = "sputnik")] identified_contracts.insert(*CHEATCODE_ADDRESS, ("VM".to_string(), HEVM_ABI.clone())); - let mut abi = None; - let mut name = None; - { - if let Some((name_, abi_)) = identified_contracts.get(&trace.addr) { - abi = Some(abi_.clone()); - name = Some(name_.clone()); - } - } - if trace.created { - match (abi, name) { - (Some(_abi), Some(_name)) => {} - _ => { - // if its a creation call, check the output instead of asking the evm for the - // runtime code - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - } - } - } - } else { - match (abi, name) { - (Some(_abi), Some(_name)) => {} - _ => { - // check the code at the address and try to find the corresponding contract - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - } - } + let res = identified_contracts.get(&trace.addr); + if res.is_none() { + let code = if trace.created { trace.output.clone() } else { evm.code(trace.addr) }; + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, known_code))| diff_score(known_code, &code) < 0.10) + { + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); } } @@ -227,24 +201,27 @@ impl CallTraceArena { #[cfg(not(feature = "sputnik"))] let color = if trace.success { Colour::Green } else { Colour::Red }; - let mut abi = None; - let mut name = None; - { - if let Some((name_, abi_)) = identified_contracts.get(&trace.addr) { - abi = Some(abi_.clone()); - name = Some(name_.clone()); - } - } - // was this a contract creation? - if trace.created { - match (abi, name) { - (Some(abi), Some(name)) => { - // if we have identified the address already, decode and print with the provided - // name and abi + // we have to clone the name and abi because identified_contracts is later borrowed + // immutably + let res = if let Some((name, abi)) = identified_contracts.get(&trace.addr) { + Some((name.clone(), abi.clone())) + } else { + None + }; + if res.is_none() { + // get the code to compare + let code = if trace.created { trace.output.clone() } else { evm.code(trace.addr) }; + if let Some((name, (abi, _code))) = contracts + .iter() + .find(|(_key, (_abi, known_code))| diff_score(known_code, &code) < 0.10) + { + // found matching contract, insert and print + identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); + if trace.created { println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); self.print_children_and_logs( idx, - Some(&abi), + Some(abi), contracts, identified_contracts, evm, @@ -256,98 +233,53 @@ impl CallTraceArena { color.paint("←"), trace.output.len() ); + } else { + // re-enter this function at the current node + self.pretty_print(idx, contracts, identified_contracts, evm, left); } - _ => { - // otherwise, try to identify it and print - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &trace.output) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - println!( - "{}{} {}@{}", - left, - Colour::Yellow.paint("→ new"), - name, - trace.addr - ); - self.print_children_and_logs( - idx, - Some(abi), - contracts, - identified_contracts, - evm, - left, - ); - println!( - "{} └─ {} {} bytes of code", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - trace.output.len() - ); - } else { - // we couldn't identify, print the children and logs without the abi - println!( - "{}{} @{}", - left, - Colour::Yellow.paint("→ new"), - trace.addr - ); - self.print_children_and_logs( - idx, - None, - contracts, - identified_contracts, - evm, - left, - ); - println!( - "{} └─ {} {} bytes of code", - left.replace("├─", "│").replace("└─", " "), - color.paint("←"), - trace.output.len() - ); - } - } + } else if trace.created { + // we couldn't identify, print the children and logs without the abi + println!("{}{} @{}", left, Colour::Yellow.paint("→ new"), trace.addr); + self.print_children_and_logs(idx, None, contracts, identified_contracts, evm, left); + println!( + "{} └─ {} {} bytes of code", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() + ); + } else { + let output = trace.print_func_call(None, None, color, left); + self.print_children_and_logs(idx, None, contracts, identified_contracts, evm, left); + output.print(color, left); } - } else { - match (abi, name) { - (Some(abi), Some(name)) => { - // print the function call, grab the output, print the children and logs, and - // finally output - let output = trace.print_func_call(Some(&abi), Some(&name), color, left); - self.print_children_and_logs( - idx, - Some(&abi), - contracts, - identified_contracts, - evm, - left, - ); - output.print(color, left); - } - _ => { - if let Some((name, (abi, _code))) = contracts - .iter() - .find(|(_key, (_abi, code))| diff_score(code, &evm.code(trace.addr)) < 0.10) - { - identified_contracts.insert(trace.addr, (name.to_string(), abi.clone())); - // re-enter this function at this node level if we found the contract - self.pretty_print(idx, contracts, identified_contracts, evm, left); - } else { - // we couldn't identify it, print without abi and name - let output = trace.print_func_call(None, None, color, left); - self.print_children_and_logs( - idx, - None, - contracts, - identified_contracts, - evm, - left, - ); - output.print(color, left); - } - } + } else if let Some((name, abi)) = res { + if trace.created { + println!("{}{} {}@{}", left, Colour::Yellow.paint("→ new"), name, trace.addr); + self.print_children_and_logs( + idx, + Some(&abi), + contracts, + identified_contracts, + evm, + left, + ); + println!( + "{} └─ {} {} bytes of code", + left.replace("├─", "│").replace("└─", " "), + color.paint("←"), + trace.output.len() + ); + } else { + let output = trace.print_func_call(Some(&abi), Some(&name), color, left); + self.print_children_and_logs( + idx, + Some(&abi), + contracts, + identified_contracts, + evm, + left, + ); + output.print(color, left); } } } @@ -475,6 +407,8 @@ pub struct CallTrace { pub addr: H160, /// Creation pub created: bool, + /// Ether value transfer + pub value: U256, /// Call data, including function selector (if applicable) pub data: Vec, /// Gas cost @@ -521,12 +455,17 @@ impl CallTrace { } println!( - "{}[{}] {}::{}({})", + "{}[{}] {}::{}{}({})", left, self.cost, color.paint(name), color.paint(func_name), - strings + if self.value > 0.into() { + format!("{{value: {}}}", self.value) + } else { + "".to_string() + }, + strings, ); if !self.output.is_empty() { @@ -542,7 +481,17 @@ impl CallTrace { } } else { // fallback function - println!("{}[{}] {}::fallback()", left, self.cost, color.paint(name),); + println!( + "{}[{}] {}::fallback{}()", + left, + self.cost, + color.paint(name), + if self.value > 0.into() { + format!("{{value: {}}}", self.value) + } else { + "".to_string() + } + ); return Output::Raw(self.output[..].to_vec()) } @@ -550,7 +499,7 @@ impl CallTrace { // We couldn't decode the function call, so print it as an abstract call println!( - "{}[{}] {}::{}({})", + "{}[{}] {}::{}{}({})", left, self.cost, color.paint(format!("{}", self.addr)), @@ -559,11 +508,16 @@ impl CallTrace { } else { hex::encode(&self.data[..]) }, + if self.value > 0.into() { + format!("{{value: {}}}", self.value) + } else { + "".to_string() + }, if self.data.len() >= 4 { hex::encode(&self.data[4..]) } else { hex::encode(&vec![][..]) - } + }, ); Output::Raw(self.output[..].to_vec()) diff --git a/evm-adapters/src/lib.rs b/evm-adapters/src/lib.rs index ea267648b770a..3ecbd94bbf6e7 100644 --- a/evm-adapters/src/lib.rs +++ b/evm-adapters/src/lib.rs @@ -103,8 +103,8 @@ pub trait Evm { } } - fn traces(&self) -> Option> { - None + fn traces(&self) -> Vec { + vec![] } fn reset_traces(&mut self) {} diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index b136116cf5684..fd0d66c2bb918 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -203,8 +203,8 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor Option> { - Some(self.state().traces.clone()) + fn traces(&self) -> Vec { + self.state().traces.clone() } fn reset_traces(&mut self) { @@ -335,7 +335,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ) -> Capture<(ExitReason, Vec), Infallible> { let mut res = vec![]; let pre_index = self.state().trace_index; - let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), false); + let trace = self.start_trace(*CHEATCODE_ADDRESS, input.clone(), 0.into(), false); // Get a mutable ref to the state so we can apply the cheats let state = self.state_mut(); let decoded = match HEVMCalls::decode(&input) { @@ -514,7 +514,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> } } - fn start_trace(&mut self, address: H160, input: Vec, creation: bool) -> Option { + fn start_trace( + &mut self, + address: H160, + input: Vec, + transfer: U256, + creation: bool, + ) -> Option { if self.enable_trace { let mut trace: CallTrace = CallTrace { // depth only starts tracking at first child substate and is 0. so add 1 when depth @@ -527,6 +533,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> addr: address, created: creation, data: input, + value: transfer, ..Default::default() }; @@ -571,7 +578,12 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> context: Context, ) -> Capture<(ExitReason, Vec), Infallible> { let pre_index = self.state().trace_index; - let trace = self.start_trace(code_address, input.clone(), false); + let trace = self.start_trace( + code_address, + input.clone(), + transfer.as_ref().map(|x| x.value).unwrap_or_default(), + false, + ); macro_rules! try_or_fail { ( $e:expr ) => { @@ -722,7 +734,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let address = self.create_address(scheme); - let trace = self.start_trace(address, init_code.clone(), true); + let trace = self.start_trace(address, init_code.clone(), value, true); macro_rules! try_or_fail { ( $e:expr ) => { @@ -1114,7 +1126,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a target_gas: Option, ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { self.create_inner(caller, scheme, value, init_code, target_gas, true) - // self.handler.create(caller, scheme, value, init_code, target_gas) } fn pre_validate( @@ -1331,7 +1342,7 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, ""); + evm.traces()[1].pretty_print(0, &mapping, &mut identified, &evm, ""); } #[test] @@ -1389,6 +1400,6 @@ mod tests { ), ); let mut identified = Default::default(); - evm.traces().expect("no traces")[1].pretty_print(0, &mapping, &mut identified, &evm, ""); + evm.traces()[1].pretty_print(0, &mapping, &mut identified, &evm, ""); } } diff --git a/evm-adapters/src/sputnik/evm.rs b/evm-adapters/src/sputnik/evm.rs index 9a37c1307a6cc..4f4480c30c85b 100644 --- a/evm-adapters/src/sputnik/evm.rs +++ b/evm-adapters/src/sputnik/evm.rs @@ -115,7 +115,7 @@ where self.executor.state().code(address) } - fn traces(&self) -> Option> { + fn traces(&self) -> Vec { self.executor.traces() } diff --git a/evm-adapters/src/sputnik/mod.rs b/evm-adapters/src/sputnik/mod.rs index ef9511a213ab2..c75f08ddb5e3c 100644 --- a/evm-adapters/src/sputnik/mod.rs +++ b/evm-adapters/src/sputnik/mod.rs @@ -87,13 +87,13 @@ pub trait SputnikExecutor { fn create_address(&self, caller: CreateScheme) -> Address; - /// Returns a vector of sraw logs that occurred during the previous VM + /// Returns a vector of raw logs that occurred during the previous VM /// execution fn raw_logs(&self) -> Vec; /// Gets a trace - fn traces(&self) -> Option> { - None + fn traces(&self) -> Vec { + vec![] } fn reset_traces(&mut self) {} diff --git a/evm-adapters/testdata/Trace.sol b/evm-adapters/testdata/Trace.sol index 5775f9b471345..3d9b98d13f00c 100644 --- a/evm-adapters/testdata/Trace.sol +++ b/evm-adapters/testdata/Trace.sol @@ -59,7 +59,6 @@ contract Trace { return new RecursiveCall(address(this)); } - function recurseCall(uint256 neededDepth, uint256 depth) public returns (uint256) { if (address(first) == address(0)) { first = new RecursiveCall(address(this)); diff --git a/forge/src/runner.rs b/forge/src/runner.rs index e14466033abbe..37bf6e915ed45 100644 --- a/forge/src/runner.rs +++ b/forge/src/runner.rs @@ -277,37 +277,36 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let mut traces: Option> = None; let mut identified_contracts: Option> = None; - if let Some(evm_traces) = self.evm.traces() { - if !evm_traces.is_empty() && self.evm.tracing_enabled() { - let mut ident = BTreeMap::new(); - // create an iter over the traces - let mut trace_iter = evm_traces.into_iter(); - let mut temp_traces = Vec::new(); - if setup { - // grab the setup trace if it exists - let setup = trace_iter.next().expect("no setup trace"); - setup.update_identified( - 0, - known_contracts.expect("traces enabled but no identified_contracts"), - &mut ident, - self.evm, - ); - temp_traces.push(setup); - } - // grab the test trace - let test_trace = trace_iter.next().expect("no test trace"); - test_trace.update_identified( + let evm_traces = self.evm.traces(); + if !evm_traces.is_empty() && self.evm.tracing_enabled() { + let mut ident = BTreeMap::new(); + // create an iter over the traces + let mut trace_iter = evm_traces.into_iter(); + let mut temp_traces = Vec::new(); + if setup { + // grab the setup trace if it exists + let setup = trace_iter.next().expect("no setup trace"); + setup.update_identified( 0, known_contracts.expect("traces enabled but no identified_contracts"), &mut ident, self.evm, ); - temp_traces.push(test_trace); - - // pass back the identified contracts and traces - identified_contracts = Some(ident); - traces = Some(temp_traces); + temp_traces.push(setup); } + // grab the test trace + let test_trace = trace_iter.next().expect("no test trace"); + test_trace.update_identified( + 0, + known_contracts.expect("traces enabled but no identified_contracts"), + &mut ident, + self.evm, + ); + temp_traces.push(test_trace); + + // pass back the identified contracts and traces + identified_contracts = Some(ident); + traces = Some(temp_traces); } self.evm.reset_traces(); @@ -335,6 +334,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { setup: bool, runner: TestRunner, ) -> Result { + // do not trace in fuzztests, as it's a big performance hit let prev = self.evm.set_tracing_enabled(false); let start = Instant::now(); let should_fail = func.name.starts_with("testFail"); @@ -369,6 +369,7 @@ impl<'a, S: Clone, E: Evm> ContractRunner<'a, S, E> { let duration = Instant::now().duration_since(start); tracing::debug!(?duration, %success); + // reset tracing to previous value in case next test *isn't* a fuzz test self.evm.set_tracing_enabled(prev); // from that call? Ok(TestResult { From 90d81d2136e3c64e7a0e8f4fc667bae098419f0a Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Dec 2021 13:17:08 -0700 Subject: [PATCH 34/34] last nits --- evm-adapters/src/call_tracing.rs | 4 +--- evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/evm-adapters/src/call_tracing.rs b/evm-adapters/src/call_tracing.rs index 7593198628fa1..eb91a6878fae7 100644 --- a/evm-adapters/src/call_tracing.rs +++ b/evm-adapters/src/call_tracing.rs @@ -66,12 +66,11 @@ impl Output { impl CallTraceArena { /// Pushes a new trace into the arena, returning the trace that was passed in with updated /// values - pub fn push_trace(&mut self, entry: usize, mut new_trace: CallTrace) -> CallTrace { + pub fn push_trace(&mut self, entry: usize, mut new_trace: &mut CallTrace) { match new_trace.depth { // The entry node, just update it 0 => { self.update(new_trace.clone()); - new_trace } // we found the parent node, add the new trace as a child _ if self.arena[entry].trace.depth == new_trace.depth - 1 => { @@ -86,7 +85,6 @@ impl CallTraceArena { }; self.arena.push(node); self.arena[entry].children.push(new_trace.idx); - new_trace } // we haven't found the parent node, go deeper _ => self.push_trace( diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index 04e7c164f9293..fac44a1982ea7 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -544,9 +544,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> ..Default::default() }; - // we should probably delay having the input and other stuff so - // we minimize the size of the clone - trace = self.state_mut().trace_mut().push_trace(0, trace.clone()); + self.state_mut().trace_mut().push_trace(0, &mut trace); self.state_mut().trace_index = trace.idx; Some(trace) } else {