Skip to content

Commit ba2de65

Browse files
iFrostizzDaniPopes
authored andcommitted
Debugger Refactor #2: DebuggerArgs (foundry-rs#5753)
* 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]>
1 parent 4bbc3be commit ba2de65

File tree

21 files changed

+727
-603
lines changed

21 files changed

+727
-603
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/cli/src/utils/cmd.rs

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ use ethers::{
22
abi::Abi,
33
core::types::Chain,
44
solc::{
5-
artifacts::{CompactBytecode, CompactDeployedBytecode, ContractBytecodeSome},
5+
artifacts::{CompactBytecode, CompactDeployedBytecode},
66
cache::{CacheEntry, SolFilesCache},
77
info::ContractInfo,
88
utils::read_json_file,
9-
Artifact, ArtifactId, ProjectCompileOutput,
9+
Artifact, ProjectCompileOutput,
1010
},
1111
};
1212
use eyre::{Result, WrapErr};
@@ -20,9 +20,9 @@ use foundry_evm::{
2020
CallTraceDecoder, CallTraceDecoderBuilder, TraceKind, Traces,
2121
},
2222
};
23-
use std::{collections::BTreeMap, fmt::Write, path::PathBuf, str::FromStr};
23+
use std::{fmt::Write, path::PathBuf, str::FromStr};
2424
use tracing::trace;
25-
use ui::{TUIExitReason, Tui, Ui};
25+
use ui::DebuggerArgs;
2626
use yansi::Paint;
2727

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

393393
if debug {
394-
let (sources, bytecode) = etherscan_identifier.get_compiled_contracts().await?;
395-
run_debugger(result, decoder, bytecode, sources)?;
394+
let sources = etherscan_identifier.get_compiled_contracts().await?;
395+
let debugger = DebuggerArgs {
396+
debug: vec![result.debug],
397+
decoder: &decoder,
398+
sources,
399+
breakpoints: Default::default(),
400+
};
401+
debugger.run()?;
396402
} else {
397403
print_traces(&mut result, &decoder, verbose).await?;
398404
}
@@ -429,31 +435,3 @@ pub async fn print_traces(
429435
println!("Gas used: {}", result.gas_used);
430436
Ok(())
431437
}
432-
433-
pub fn run_debugger(
434-
result: TraceResult,
435-
decoder: CallTraceDecoder,
436-
known_contracts: BTreeMap<ArtifactId, ContractBytecodeSome>,
437-
sources: BTreeMap<ArtifactId, String>,
438-
) -> Result<()> {
439-
let calls: Vec<DebugArena> = vec![result.debug];
440-
let flattened = calls.last().expect("we should have collected debug info").flatten(0);
441-
let tui = Tui::new(
442-
flattened,
443-
0,
444-
decoder.contracts,
445-
known_contracts.into_iter().map(|(id, artifact)| (id.name, artifact)).collect(),
446-
sources
447-
.into_iter()
448-
.map(|(id, source)| {
449-
let mut sources = BTreeMap::new();
450-
sources.insert(0, source);
451-
(id.name, sources)
452-
})
453-
.collect(),
454-
Default::default(),
455-
)?;
456-
match tui.start().expect("Failed to start tui") {
457-
TUIExitReason::CharExit => Ok(()),
458-
}
459-
}

crates/common/src/compile.rs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//! Support for compiling [ethers::solc::Project]
2-
use crate::{glob::GlobMatcher, term, TestFunctionExt};
2+
use crate::{compact_to_contract, glob::GlobMatcher, term, TestFunctionExt};
33
use comfy_table::{presets::ASCII_MARKDOWN, *};
44
use ethers_etherscan::contract::Metadata;
55
use ethers_solc::{
@@ -11,7 +11,7 @@ use ethers_solc::{
1111
};
1212
use eyre::Result;
1313
use std::{
14-
collections::BTreeMap,
14+
collections::{BTreeMap, HashMap},
1515
convert::Infallible,
1616
fmt::Display,
1717
path::{Path, PathBuf},
@@ -171,6 +171,10 @@ impl ProjectCompiler {
171171
}
172172
}
173173

174+
/// Map over artifacts contract sources name -> file_id -> (source, contract)
175+
#[derive(Default, Debug, Clone)]
176+
pub struct ContractSources(pub HashMap<String, HashMap<u32, (String, ContractBytecodeSome)>>);
177+
174178
// https://eips.ethereum.org/EIPS/eip-170
175179
const CONTRACT_SIZE_LIMIT: usize = 24576;
176180

@@ -398,10 +402,11 @@ pub fn compile_target_with_filter(
398402
}
399403
}
400404

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

415-
let (artifact_id, contract) = project_output
416-
.into_contract_bytecodes()
420+
let (artifact_id, file_id, contract) = project_output
421+
.into_artifacts()
417422
.find(|(artifact_id, _)| artifact_id.name == metadata.contract_name)
423+
.map(|(aid, art)| {
424+
(aid, art.source_file().expect("no source file").id, art.into_contract_bytecode())
425+
})
418426
.expect("there should be a contract with bytecode");
419-
let bytecode = ContractBytecodeSome {
420-
abi: contract.abi.unwrap(),
421-
bytecode: contract.bytecode.unwrap().into(),
422-
deployed_bytecode: contract.deployed_bytecode.unwrap().into(),
423-
};
427+
let bytecode = compact_to_contract(contract)?;
424428

425429
root.close()?;
426430

427-
Ok((artifact_id, bytecode))
431+
Ok((artifact_id, file_id, bytecode))
428432
}
429433

430434
/// Creates a [Project] from an Etherscan source.

crates/common/src/contracts.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use ethers_core::{
55
types::{Address, H256},
66
utils::hex,
77
};
8-
use ethers_solc::{artifacts::ContractBytecodeSome, ArtifactId, ProjectPathsConfig};
8+
use ethers_solc::{
9+
artifacts::{CompactContractBytecode, ContractBytecodeSome},
10+
ArtifactId, ProjectPathsConfig,
11+
};
912
use once_cell::sync::Lazy;
1013
use regex::Regex;
1114
use std::{
@@ -265,3 +268,17 @@ mod tests {
265268
let _decoded = abi::decode(&params, args).unwrap();
266269
}
267270
}
271+
272+
/// Helper function to convert CompactContractBytecode ~> ContractBytecodeSome
273+
pub fn compact_to_contract(
274+
contract: CompactContractBytecode,
275+
) -> eyre::Result<ContractBytecodeSome> {
276+
Ok(ContractBytecodeSome {
277+
abi: contract.abi.ok_or(eyre::eyre!("No contract abi"))?,
278+
bytecode: contract.bytecode.ok_or(eyre::eyre!("No contract bytecode"))?.into(),
279+
deployed_bytecode: contract
280+
.deployed_bytecode
281+
.ok_or(eyre::eyre!("No contract deployed bytecode"))?
282+
.into(),
283+
})
284+
}

crates/evm/src/debug.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
55
use std::fmt::Display;
66

77
/// An arena of [DebugNode]s
8-
#[derive(Default, Debug, Clone)]
8+
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
99
pub struct DebugArena {
1010
/// The arena of nodes
1111
pub arena: Vec<DebugNode>,
@@ -78,7 +78,7 @@ impl DebugArena {
7878
}
7979

8080
/// A node in the arena
81-
#[derive(Default, Debug, Clone)]
81+
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
8282
pub struct DebugNode {
8383
/// Parent node index in the arena
8484
pub parent: Option<usize>,
@@ -109,7 +109,7 @@ impl DebugNode {
109109
/// It holds the current program counter (where in the program you are),
110110
/// the stack and memory (prior to the opcodes execution), any bytes to be
111111
/// pushed onto the stack, and the instruction counter for use with sourcemap.
112-
#[derive(Debug, Clone)]
112+
#[derive(Debug, Clone, Serialize, Deserialize)]
113113
pub struct DebugStep {
114114
/// Stack *prior* to running the associated opcode
115115
pub stack: Vec<U256>,

crates/evm/src/fuzz/mod.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,7 @@ impl<'a> FuzzedExecutor<'a> {
8181
// Stores coverage information for all fuzz cases
8282
let coverage: RefCell<Option<HitMaps>> = RefCell::default();
8383

84-
// Stores fuzz state for use with [fuzz_calldata_from_state]
85-
let state: EvmFuzzState = if let Some(fork_db) = self.executor.backend.active_fork_db() {
86-
build_initial_state(fork_db, &self.config.dictionary)
87-
} else {
88-
build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary)
89-
};
84+
let state = self.build_fuzz_state();
9085

9186
let mut weights = vec![];
9287
let dictionary_weight = self.config.dictionary.dictionary_weight.min(100);
@@ -248,6 +243,15 @@ impl<'a> FuzzedExecutor<'a> {
248243
}))
249244
}
250245
}
246+
247+
/// Stores fuzz state for use with [fuzz_calldata_from_state]
248+
pub fn build_fuzz_state(&self) -> EvmFuzzState {
249+
if let Some(fork_db) = self.executor.backend.active_fork_db() {
250+
build_initial_state(fork_db, &self.config.dictionary)
251+
} else {
252+
build_initial_state(self.executor.backend.mem_db(), &self.config.dictionary)
253+
}
254+
}
251255
}
252256

253257
#[derive(Clone, Debug, Serialize, Deserialize)]

crates/evm/src/trace/identifier/etherscan.rs

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ use ethers::{
44
abi::Address,
55
etherscan,
66
etherscan::contract::{ContractMetadata, Metadata},
7-
prelude::{artifacts::ContractBytecodeSome, errors::EtherscanError, ArtifactId},
7+
prelude::errors::EtherscanError,
88
types::H160,
99
};
10-
use foundry_common::compile;
10+
use foundry_common::compile::{self, ContractSources};
1111
use foundry_config::{Chain, Config};
1212
use futures::{
1313
future::{join_all, Future},
@@ -58,13 +58,7 @@ impl EtherscanIdentifier {
5858

5959
/// Goes over the list of contracts we have pulled from the traces, clones their source from
6060
/// Etherscan and compiles them locally, for usage in the debugger.
61-
pub async fn get_compiled_contracts(
62-
&self,
63-
) -> eyre::Result<(BTreeMap<ArtifactId, String>, BTreeMap<ArtifactId, ContractBytecodeSome>)>
64-
{
65-
let mut compiled_contracts = BTreeMap::new();
66-
let mut sources = BTreeMap::new();
67-
61+
pub async fn get_compiled_contracts(&self) -> eyre::Result<ContractSources> {
6862
// TODO: Add caching so we dont double-fetch contracts.
6963
let contracts_iter = self
7064
.contracts
@@ -87,15 +81,20 @@ impl EtherscanIdentifier {
8781
// poll all the futures concurrently
8882
let artifacts = join_all(outputs_fut).await;
8983

84+
let mut sources: ContractSources = Default::default();
85+
9086
// construct the map
9187
for (results, (_, metadata)) in artifacts.into_iter().zip(contracts_iter) {
9288
// get the inner type
93-
let (artifact_id, bytecode) = results?;
94-
compiled_contracts.insert(artifact_id.clone(), bytecode);
95-
sources.insert(artifact_id, metadata.source_code());
89+
let (artifact_id, file_id, bytecode) = results?;
90+
sources
91+
.0
92+
.entry(artifact_id.clone().name)
93+
.or_default()
94+
.insert(file_id, (metadata.source_code(), bytecode));
9695
}
9796

98-
Ok((sources, compiled_contracts))
97+
Ok(sources)
9998
}
10099
}
101100

crates/forge/bin/cmd/debug.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
use super::{build::BuildArgs, retry::RETRY_VERIFY_ON_CREATE, script::ScriptArgs};
22
use clap::{Parser, ValueHint};
3-
use eyre::Result;
43
use foundry_cli::opts::CoreBuildArgs;
5-
use foundry_common::evm::{Breakpoints, EvmArgs};
4+
use foundry_common::evm::EvmArgs;
65
use std::path::PathBuf;
76

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

4342
impl DebugArgs {
44-
pub async fn debug(self, breakpoints: Breakpoints) -> Result<()> {
43+
pub async fn run(self) -> eyre::Result<()> {
4544
let script = ScriptArgs {
4645
path: self.path.to_str().expect("Invalid path string.").to_string(),
4746
args: self.args,
@@ -54,6 +53,6 @@ impl DebugArgs {
5453
retry: RETRY_VERIFY_ON_CREATE,
5554
..Default::default()
5655
};
57-
script.run_script(breakpoints).await
56+
script.run_script().await
5857
}
5958
}

0 commit comments

Comments
 (0)