Skip to content

Commit

Permalink
Debugger Refactor #2: DebuggerArgs (#5753)
Browse files Browse the repository at this point in the history
* fuzz single refactor

* add struct docs

* Update crates/evm/src/fuzz/mod.rs

Co-authored-by: DaniPopes <[email protected]>

* add docs and move types to types.rs

* fmt

* add new debugger args type

* add minimal debugger-refactor changes

* finish him!

* fmt

* remove TODO

* minimal diff

* apply review suggestions

* add TODO

* looks better

* make ContractSources wrapper

* add more docki docs

* write file_id docs!

---------

Co-authored-by: DaniPopes <[email protected]>
  • Loading branch information
iFrostizz and DaniPopes authored Sep 11, 2023
1 parent 7e89653 commit 523354e
Show file tree
Hide file tree
Showing 21 changed files with 727 additions and 603 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

46 changes: 12 additions & 34 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ use ethers::{
abi::Abi,
core::types::Chain,
solc::{
artifacts::{CompactBytecode, CompactDeployedBytecode, ContractBytecodeSome},
artifacts::{CompactBytecode, CompactDeployedBytecode},
cache::{CacheEntry, SolFilesCache},
info::ContractInfo,
utils::read_json_file,
Artifact, ArtifactId, ProjectCompileOutput,
Artifact, ProjectCompileOutput,
},
};
use eyre::{Result, WrapErr};
Expand All @@ -20,9 +20,9 @@ use foundry_evm::{
CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
},
};
use std::{collections::BTreeMap, fmt::Write, path::PathBuf, str::FromStr};
use std::{fmt::Write, path::PathBuf, str::FromStr};
use tracing::trace;
use ui::{TUIExitReason, Tui, Ui};
use ui::DebuggerArgs;
use yansi::Paint;

/// Given a `Project`'s output, removes the matching ABI, Bytecode and
Expand Down Expand Up @@ -391,8 +391,14 @@ pub async fn handle_traces(
}

if debug {
let (sources, bytecode) = etherscan_identifier.get_compiled_contracts().await?;
run_debugger(result, decoder, bytecode, sources)?;
let sources = etherscan_identifier.get_compiled_contracts().await?;
let debugger = DebuggerArgs {
debug: vec![result.debug],
decoder: &decoder,
sources,
breakpoints: Default::default(),
};
debugger.run()?;
} else {
print_traces(&mut result, &decoder, verbose).await?;
}
Expand Down Expand Up @@ -429,31 +435,3 @@ pub async fn print_traces(
println!("Gas used: {}", result.gas_used);
Ok(())
}

pub fn run_debugger(
result: TraceResult,
decoder: CallTraceDecoder,
known_contracts: BTreeMap<ArtifactId, ContractBytecodeSome>,
sources: BTreeMap<ArtifactId, String>,
) -> Result<()> {
let calls: Vec<DebugArena> = vec![result.debug];
let flattened = calls.last().expect("we should have collected debug info").flatten(0);
let tui = Tui::new(
flattened,
0,
decoder.contracts,
known_contracts.into_iter().map(|(id, artifact)| (id.name, artifact)).collect(),
sources
.into_iter()
.map(|(id, source)| {
let mut sources = BTreeMap::new();
sources.insert(0, source);
(id.name, sources)
})
.collect(),
Default::default(),
)?;
match tui.start().expect("Failed to start tui") {
TUIExitReason::CharExit => Ok(()),
}
}
28 changes: 16 additions & 12 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Support for compiling [ethers::solc::Project]
use crate::{glob::GlobMatcher, term, TestFunctionExt};
use crate::{compact_to_contract, glob::GlobMatcher, term, TestFunctionExt};
use comfy_table::{presets::ASCII_MARKDOWN, *};
use ethers_etherscan::contract::Metadata;
use ethers_solc::{
Expand All @@ -11,7 +11,7 @@ use ethers_solc::{
};
use eyre::Result;
use std::{
collections::BTreeMap,
collections::{BTreeMap, HashMap},
convert::Infallible,
fmt::Display,
path::{Path, PathBuf},
Expand Down Expand Up @@ -171,6 +171,10 @@ impl ProjectCompiler {
}
}

/// Map over artifacts contract sources name -> file_id -> (source, contract)
#[derive(Default, Debug, Clone)]
pub struct ContractSources(pub HashMap<String, HashMap<u32, (String, ContractBytecodeSome)>>);

// https://eips.ethereum.org/EIPS/eip-170
const CONTRACT_SIZE_LIMIT: usize = 24576;

Expand Down Expand Up @@ -398,10 +402,11 @@ pub fn compile_target_with_filter(
}
}

/// Creates and compiles a project from an Etherscan source.
/// Compiles an Etherscan source from metadata by creating a project.
/// Returns the artifact_id, the file_id, and the bytecode
pub async fn compile_from_source(
metadata: &Metadata,
) -> Result<(ArtifactId, ContractBytecodeSome)> {
) -> Result<(ArtifactId, u32, ContractBytecodeSome)> {
let root = tempfile::tempdir()?;
let root_path = root.path();
let project = etherscan_project(metadata, root_path)?;
Expand All @@ -412,19 +417,18 @@ pub async fn compile_from_source(
eyre::bail!(project_output.to_string())
}

let (artifact_id, contract) = project_output
.into_contract_bytecodes()
let (artifact_id, file_id, contract) = project_output
.into_artifacts()
.find(|(artifact_id, _)| artifact_id.name == metadata.contract_name)
.map(|(aid, art)| {
(aid, art.source_file().expect("no source file").id, art.into_contract_bytecode())
})
.expect("there should be a contract with bytecode");
let bytecode = ContractBytecodeSome {
abi: contract.abi.unwrap(),
bytecode: contract.bytecode.unwrap().into(),
deployed_bytecode: contract.deployed_bytecode.unwrap().into(),
};
let bytecode = compact_to_contract(contract)?;

root.close()?;

Ok((artifact_id, bytecode))
Ok((artifact_id, file_id, bytecode))
}

/// Creates a [Project] from an Etherscan source.
Expand Down
19 changes: 18 additions & 1 deletion crates/common/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use ethers_core::{
types::{Address, H256},
utils::hex,
};
use ethers_solc::{artifacts::ContractBytecodeSome, ArtifactId, ProjectPathsConfig};
use ethers_solc::{
artifacts::{CompactContractBytecode, ContractBytecodeSome},
ArtifactId, ProjectPathsConfig,
};
use once_cell::sync::Lazy;
use regex::Regex;
use std::{
Expand Down Expand Up @@ -265,3 +268,17 @@ mod tests {
let _decoded = abi::decode(&params, args).unwrap();
}
}

/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
pub fn compact_to_contract(
contract: CompactContractBytecode,
) -> eyre::Result<ContractBytecodeSome> {
Ok(ContractBytecodeSome {
abi: contract.abi.ok_or(eyre::eyre!("No contract abi"))?,
bytecode: contract.bytecode.ok_or(eyre::eyre!("No contract bytecode"))?.into(),
deployed_bytecode: contract
.deployed_bytecode
.ok_or(eyre::eyre!("No contract deployed bytecode"))?
.into(),
})
}
6 changes: 3 additions & 3 deletions crates/evm/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use std::fmt::Display;

/// An arena of [DebugNode]s
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct DebugArena {
/// The arena of nodes
pub arena: Vec<DebugNode>,
Expand Down Expand Up @@ -78,7 +78,7 @@ impl DebugArena {
}

/// A node in the arena
#[derive(Default, Debug, Clone)]
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct DebugNode {
/// Parent node index in the arena
pub parent: Option<usize>,
Expand Down Expand Up @@ -109,7 +109,7 @@ impl DebugNode {
/// It holds the current program counter (where in the program you are),
/// the stack and memory (prior to the opcodes execution), any bytes to be
/// pushed onto the stack, and the instruction counter for use with sourcemap.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebugStep {
/// Stack *prior* to running the associated opcode
pub stack: Vec<U256>,
Expand Down
16 changes: 10 additions & 6 deletions crates/evm/src/fuzz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,7 @@ impl<'a> FuzzedExecutor<'a> {
// Stores coverage information for all fuzz cases
let coverage: RefCell<Option<HitMaps>> = RefCell::default();

// Stores fuzz state for use with [fuzz_calldata_from_state]
let state: EvmFuzzState = if let Some(fork_db) = self.executor.backend.active_fork_db() {
build_initial_state(fork_db, &self.config.dictionary)
} else {
build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary)
};
let state = self.build_fuzz_state();

let mut weights = vec![];
let dictionary_weight = self.config.dictionary.dictionary_weight.min(100);
Expand Down Expand Up @@ -248,6 +243,15 @@ impl<'a> FuzzedExecutor<'a> {
}))
}
}

/// Stores fuzz state for use with [fuzz_calldata_from_state]
pub fn build_fuzz_state(&self) -> EvmFuzzState {
if let Some(fork_db) = self.executor.backend.active_fork_db() {
build_initial_state(fork_db, &self.config.dictionary)
} else {
build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary)
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
Expand Down
25 changes: 12 additions & 13 deletions crates/evm/src/trace/identifier/etherscan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use ethers::{
abi::Address,
etherscan,
etherscan::contract::{ContractMetadata, Metadata},
prelude::{artifacts::ContractBytecodeSome, errors::EtherscanError, ArtifactId},
prelude::errors::EtherscanError,
types::H160,
};
use foundry_common::compile;
use foundry_common::compile::{self, ContractSources};
use foundry_config::{Chain, Config};
use futures::{
future::{join_all, Future},
Expand Down Expand Up @@ -58,13 +58,7 @@ impl EtherscanIdentifier {

/// Goes over the list of contracts we have pulled from the traces, clones their source from
/// Etherscan and compiles them locally, for usage in the debugger.
pub async fn get_compiled_contracts(
&self,
) -> eyre::Result<(BTreeMap<ArtifactId, String>, BTreeMap<ArtifactId, ContractBytecodeSome>)>
{
let mut compiled_contracts = BTreeMap::new();
let mut sources = BTreeMap::new();

pub async fn get_compiled_contracts(&self) -> eyre::Result<ContractSources> {
// TODO: Add caching so we dont double-fetch contracts.
let contracts_iter = self
.contracts
Expand All @@ -87,15 +81,20 @@ impl EtherscanIdentifier {
// poll all the futures concurrently
let artifacts = join_all(outputs_fut).await;

let mut sources: ContractSources = Default::default();

// construct the map
for (results, (_, metadata)) in artifacts.into_iter().zip(contracts_iter) {
// get the inner type
let (artifact_id, bytecode) = results?;
compiled_contracts.insert(artifact_id.clone(), bytecode);
sources.insert(artifact_id, metadata.source_code());
let (artifact_id, file_id, bytecode) = results?;
sources
.0
.entry(artifact_id.clone().name)
.or_default()
.insert(file_id, (metadata.source_code(), bytecode));
}

Ok((sources, compiled_contracts))
Ok(sources)
}
}

Expand Down
7 changes: 3 additions & 4 deletions crates/forge/bin/cmd/debug.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs};
use clap::{Parser, ValueHint};
use eyre::Result;
use foundry_cli::opts::CoreBuildArgs;
use foundry_common::evm::{Breakpoints, EvmArgs};
use foundry_common::evm::EvmArgs;
use std::path::PathBuf;

// Loads project's figment and merges the build cli arguments into it
Expand Down Expand Up @@ -41,7 +40,7 @@ pub struct DebugArgs {
}

impl DebugArgs {
pub async fn debug(self, breakpoints: Breakpoints) -> Result<()> {
pub async fn run(self) -> eyre::Result<()> {
let script = ScriptArgs {
path: self.path.to_str().expect("Invalid path string.").to_string(),
args: self.args,
Expand All @@ -54,6 +53,6 @@ impl DebugArgs {
retry: RETRY_VERIFY_ON_CREATE,
..Default::default()
};
script.run_script(breakpoints).await
script.run_script().await
}
}
Loading

0 comments on commit 523354e

Please sign in to comment.