Skip to content

Commit

Permalink
fix: exclude empty artifacts from ContractsByArtifact (#7713)
Browse files Browse the repository at this point in the history
* fix: exclude empty artifacts from ContractsByArtifact

* additional check

* fmt

* doc

* use Option for bytecodes
  • Loading branch information
klkvr committed Apr 18, 2024
1 parent 3f5c615 commit 79dd88c
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 29 deletions.
11 changes: 7 additions & 4 deletions crates/cheatcodes/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,11 +355,14 @@ fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result<B
}
}?;

if deployed {
return Ok(artifact.1.deployed_bytecode.clone())
let maybe_bytecode = if deployed {
artifact.1.deployed_bytecode.clone()
} else {
return Ok(artifact.1.bytecode.clone())
}
artifact.1.bytecode.clone()
};

return maybe_bytecode
.ok_or_else(|| fmt_err!("No bytecode for contract. Is it abstract or unlinked?"));
} else {
match (file.map(|f| f.to_string_lossy().to_string()), contract_name) {
(Some(file), Some(contract_name)) => {
Expand Down
25 changes: 20 additions & 5 deletions crates/common/src/contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pub struct ContractData {
/// Contract ABI.
pub abi: JsonAbi,
/// Contract creation code.
pub bytecode: Bytes,
pub bytecode: Option<Bytes>,
/// Contract runtime code.
pub deployed_bytecode: Bytes,
pub deployed_bytecode: Option<Bytes>,
}

type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData);
Expand All @@ -45,6 +45,12 @@ impl ContractsByArtifact {
let bytecode = artifact.bytecode.and_then(|b| b.into_bytes())?;
let deployed_bytecode =
artifact.deployed_bytecode.and_then(|b| b.into_bytes())?;

// Exclude artifacts with present but empty bytecode. Such artifacts are usually
// interfaces and abstract contracts.
let bytecode = (bytecode.len() > 0).then_some(bytecode);
let deployed_bytecode =
(deployed_bytecode.len() > 0).then_some(deployed_bytecode);
let abi = artifact.abi?;

Some((id, ContractData { name, abi, bytecode, deployed_bytecode }))
Expand All @@ -55,14 +61,23 @@ impl ContractsByArtifact {

/// Finds a contract which has a similar bytecode as `code`.
pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef> {
self.iter()
.find(|(_, contract)| bytecode_diff_score(contract.bytecode.as_ref(), code) <= 0.1)
self.iter().find(|(_, contract)| {
if let Some(bytecode) = &contract.bytecode {
bytecode_diff_score(bytecode.as_ref(), code) <= 0.1
} else {
false
}
})
}

/// Finds a contract which has a similar deployed bytecode as `code`.
pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef> {
self.iter().find(|(_, contract)| {
bytecode_diff_score(contract.deployed_bytecode.as_ref(), code) <= 0.1
if let Some(deployed_bytecode) = &contract.deployed_bytecode {
bytecode_diff_score(deployed_bytecode.as_ref(), code) <= 0.1
} else {
false
}
})
}

Expand Down
21 changes: 12 additions & 9 deletions crates/evm/traces/src/identifier/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ impl<'a> LocalTraceIdentifier<'a> {
pub fn new(known_contracts: &'a ContractsByArtifact) -> Self {
let mut ordered_ids = known_contracts
.iter()
.map(|(id, contract)| (id, contract.deployed_bytecode.len()))
.filter_map(|(id, contract)| Some((id, contract.deployed_bytecode.as_ref()?)))
.map(|(id, bytecode)| (id, bytecode.len()))
.collect::<Vec<_>>();
ordered_ids.sort_by_key(|(_, len)| *len);
Self { known_contracts, ordered_ids }
Expand All @@ -40,14 +41,16 @@ impl<'a> LocalTraceIdentifier<'a> {

let mut check = |id| {
let contract = self.known_contracts.get(id)?;
let score = bytecode_diff_score(&contract.deployed_bytecode, code);
if score == 0.0 {
trace!(target: "evm::traces", "found exact match");
return Some((id, &contract.abi));
}
if score < min_score {
min_score = score;
min_score_id = Some((id, &contract.abi));
if let Some(deployed_bytecode) = &contract.deployed_bytecode {
let score = bytecode_diff_score(deployed_bytecode, code);
if score == 0.0 {
trace!(target: "evm::traces", "found exact match");
return Some((id, &contract.abi));
}
if score < min_score {
min_score = score;
min_score_id = Some((id, &contract.abi));
}
}
None
};
Expand Down
4 changes: 3 additions & 1 deletion crates/script/src/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use alloy_primitives::{Address, Bytes};
use alloy_provider::Provider;
use alloy_rpc_types::request::TransactionRequest;
use async_recursion::async_recursion;
use eyre::Result;
use eyre::{OptionExt, Result};
use foundry_cheatcodes::ScriptWallets;
use foundry_cli::utils::{ensure_clean_constructor, needs_setup};
use foundry_common::{
Expand Down Expand Up @@ -63,6 +63,8 @@ impl LinkedState {

let ContractData { abi, bytecode, .. } = build_data.get_target_contract()?;

let bytecode = bytecode.ok_or_eyre("target contract has no bytecode")?;

let (func, calldata) = args.get_method_and_calldata(&abi)?;

ensure_clean_constructor(&abi)?;
Expand Down
8 changes: 3 additions & 5 deletions crates/script/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,9 @@ impl ScriptArgs {

// From artifacts
for (artifact, contract) in known_contracts.iter() {
bytecodes.push((
artifact.name.clone(),
&contract.bytecode,
&contract.deployed_bytecode,
));
let Some(bytecode) = &contract.bytecode else { continue };
let Some(deployed_bytecode) = &contract.deployed_bytecode else { continue };
bytecodes.push((artifact.name.clone(), bytecode, deployed_bytecode));
}

// From traces
Expand Down
5 changes: 3 additions & 2 deletions crates/script/src/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ impl TransactionWithMetadata {

let Some(data) = self.transaction.input.input() else { return Ok(()) };
let Some(info) = info else { return Ok(()) };
let Some(bytecode) = info.bytecode.as_ref() else { return Ok(()) };

// `create2` transactions are prefixed by a 32 byte salt.
let creation_code = if is_create2 {
Expand All @@ -143,11 +144,11 @@ impl TransactionWithMetadata {
};

// The constructor args start after bytecode.
let contains_constructor_args = creation_code.len() > info.bytecode.len();
let contains_constructor_args = creation_code.len() > bytecode.len();
if !contains_constructor_args {
return Ok(());
}
let constructor_args = &creation_code[info.bytecode.len()..];
let constructor_args = &creation_code[bytecode.len()..];

let Some(constructor) = info.abi.constructor() else { return Ok(()) };
let values = constructor.abi_decode_input(constructor_args, false).map_err(|e| {
Expand Down
6 changes: 3 additions & 3 deletions crates/script/src/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ impl VerifyBundle {
libraries: &[String],
) -> Option<VerifyArgs> {
for (artifact, contract) in self.known_contracts.iter() {
let Some(bytecode) = contract.bytecode.as_ref() else { continue };
// If it's a CREATE2, the tx.data comes with a 32-byte salt in the beginning
// of the transaction
if data.split_at(create2_offset).1.starts_with(&contract.bytecode) {
let constructor_args =
data.split_at(create2_offset + contract.bytecode.len()).1.to_vec();
if data.split_at(create2_offset).1.starts_with(bytecode) {
let constructor_args = data.split_at(create2_offset + bytecode.len()).1.to_vec();

let contract = ContractInfo {
path: Some(
Expand Down

0 comments on commit 79dd88c

Please sign in to comment.