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

Debugger Refactor #2: DebuggerArgs #5753

Merged
merged 19 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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