Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(forge): Add call tracing support #192

Merged
merged 40 commits into from
Dec 23, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
b470925
first pass
brockelmore Dec 2, 2021
bfa42a8
fixes
brockelmore Dec 2, 2021
7106cd7
fmt
brockelmore Dec 2, 2021
f83b226
better fmting
brockelmore Dec 2, 2021
bf17ebb
updates
brockelmore Dec 2, 2021
23687c4
fmt
brockelmore Dec 2, 2021
f4671a9
updates
brockelmore Dec 11, 2021
e61cbda
Merge branch 'master' into brock/tracing
brockelmore Dec 11, 2021
f397646
fmt
brockelmore Dec 11, 2021
95cb702
fix after master merge
brockelmore Dec 11, 2021
0594faa
fix tests post master merge
brockelmore Dec 11, 2021
34f75e2
warning fixes
brockelmore Dec 11, 2021
62406e1
fmt
brockelmore Dec 11, 2021
79d210f
lots of fixes
brockelmore Dec 12, 2021
4b91be7
fmt
brockelmore Dec 12, 2021
3f8c2b7
fix
brockelmore Dec 12, 2021
cc7e2ed
cyan color
brockelmore Dec 12, 2021
f558e82
fixes
brockelmore Dec 12, 2021
ba514cb
prettier raw logs + parse setup contracts
brockelmore Dec 12, 2021
133685f
Merge branch 'master' into brock/tracing
brockelmore Dec 12, 2021
36b4cf6
update diff_score threshold
brockelmore Dec 12, 2021
c827d33
better printing
brockelmore Dec 16, 2021
58c6c1b
remove integration tests
brockelmore Dec 22, 2021
38ca049
Merge branch 'master' into brock/tracing
brockelmore Dec 22, 2021
1f759c0
improvements
brockelmore Dec 23, 2021
637c50a
improvements + fmt + clippy
brockelmore Dec 23, 2021
08ee0da
fixes
brockelmore Dec 23, 2021
6271684
more cleanup
brockelmore Dec 23, 2021
ee88892
cleanup and verbosity > 3 setup print
brockelmore Dec 23, 2021
dc10a70
refactor printing
brockelmore Dec 23, 2021
c93da0a
documentation + cleanup
brockelmore Dec 23, 2021
6c825a0
fix negative number printing
brockelmore Dec 23, 2021
54a975f
fix tests to match master and fix tracing_enabled
brockelmore Dec 23, 2021
06eafba
Merge branch 'master' into brock/tracing
brockelmore Dec 23, 2021
a918468
fix unnecessary trace_index set
brockelmore Dec 23, 2021
57affc9
Merge branch 'brock/tracing' of https://github.com/gakonst/foundry in…
brockelmore Dec 23, 2021
b2e9922
refactor runner tracing + tracing_enabled
brockelmore Dec 23, 2021
bb9a707
nits + value printing
brockelmore Dec 23, 2021
4660e3c
Merge branch 'master' into brock/tracing
brockelmore Dec 23, 2021
90d81d2
last nits
brockelmore Dec 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
first pass
  • Loading branch information
brockelmore committed Dec 2, 2021
commit b4709253670c246381beef61b79875d3ec32ba1d
90 changes: 90 additions & 0 deletions evm-adapters/src/call_tracing.rs
Original file line number Diff line number Diff line change
@@ -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<u8>,
/// Gas cost
pub cost: u64,
/// Output
pub output: Vec<u8>,
/// Logs
pub logs: Vec<RawLog>,
/// inner calls
pub inner: Vec<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);
}
}

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<String, (Abi, Address, Vec<String>)>) {
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));
}

}
}
2 changes: 2 additions & 0 deletions evm-adapters/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
79 changes: 78 additions & 1 deletion evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use super::{
use crate::{
sputnik::{Executor, SputnikExecutor},
Evm,
call_tracing::CallTrace,
};

use sputnik::{
Expand Down Expand Up @@ -163,6 +164,13 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> SputnikExecutor<CheatcodeStackState<'
self.state_mut().substate.logs_mut().clear()
}

fn raw_logs(&self) -> Vec<RawLog> {
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<String> {
let logs = self.state().substate.logs().to_vec();
logs.into_iter()
Expand Down Expand Up @@ -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<u8>, 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<Vec<u8>>,
) {
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(
Expand All @@ -441,11 +475,19 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P>
take_stipend: bool,
context: Context,
) -> Capture<(ExitReason, Vec<u8>), 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()))
},
}
};
}
Expand Down Expand Up @@ -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()))
}
Expand All @@ -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()))
}
Expand All @@ -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))
}
Expand All @@ -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()))
}
Expand All @@ -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()))
Expand Down Expand Up @@ -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);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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> {
Expand All @@ -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() }
}
}

Expand Down
10 changes: 10 additions & 0 deletions evm-adapters/src/sputnik/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod cheatcodes;
pub mod state;

use ethers::{
abi::RawLog,
providers::Middleware,
types::{Address, H160, H256, U256},
};
Expand Down Expand Up @@ -81,6 +82,10 @@ pub trait SputnikExecutor<S> {

fn create_address(&self, caller: CreateScheme) -> Address;

/// Returns a vector of sraw logs that occurred during the previous VM
brockelmore marked this conversation as resolved.
Show resolved Hide resolved
/// execution
fn raw_logs(&self) -> Vec<RawLog>;

/// Returns a vector of string parsed logs that occurred during the previous VM
/// execution
fn logs(&self) -> Vec<String>;
Expand Down Expand Up @@ -142,6 +147,11 @@ impl<'a, 'b, S: StackState<'a>, P: PrecompileSet> SputnikExecutor<S>
fn logs(&self) -> Vec<String> {
vec![]
}

fn raw_logs(&self) -> Vec<RawLog> {
vec![]
}

fn clear_logs(&mut self) {}
}

Expand Down
23 changes: 23 additions & 0 deletions evm-adapters/testdata/Trace.sol
Original file line number Diff line number Diff line change
@@ -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 {
}
}