From 79dd88c29794b9c7eb47bca792dc7ea8ab4f114a Mon Sep 17 00:00:00 2001 From: Arsenii Kulikov Date: Fri, 19 Apr 2024 02:11:34 +0400 Subject: [PATCH] fix: exclude empty artifacts from `ContractsByArtifact` (#7713) * fix: exclude empty artifacts from ContractsByArtifact * additional check * fmt * doc * use Option for bytecodes --- crates/cheatcodes/src/fs.rs | 11 ++++++---- crates/common/src/contracts.rs | 25 ++++++++++++++++++----- crates/evm/traces/src/identifier/local.rs | 21 +++++++++++-------- crates/script/src/execute.rs | 4 +++- crates/script/src/lib.rs | 8 +++----- crates/script/src/transaction.rs | 5 +++-- crates/script/src/verify.rs | 6 +++--- 7 files changed, 51 insertions(+), 29 deletions(-) diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index a729d53bb843..728358bd6301 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -355,11 +355,14 @@ fn get_artifact_code(state: &Cheatcodes, path: &str, deployed: bool) -> Result { diff --git a/crates/common/src/contracts.rs b/crates/common/src/contracts.rs index 561d7229b528..1627e79587c9 100644 --- a/crates/common/src/contracts.rs +++ b/crates/common/src/contracts.rs @@ -20,9 +20,9 @@ pub struct ContractData { /// Contract ABI. pub abi: JsonAbi, /// Contract creation code. - pub bytecode: Bytes, + pub bytecode: Option, /// Contract runtime code. - pub deployed_bytecode: Bytes, + pub deployed_bytecode: Option, } type ArtifactWithContractRef<'a> = (&'a ArtifactId, &'a ContractData); @@ -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 })) @@ -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 { - 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 { 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 + } }) } diff --git a/crates/evm/traces/src/identifier/local.rs b/crates/evm/traces/src/identifier/local.rs index 175414a7caee..129656b95921 100644 --- a/crates/evm/traces/src/identifier/local.rs +++ b/crates/evm/traces/src/identifier/local.rs @@ -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::>(); ordered_ids.sort_by_key(|(_, len)| *len); Self { known_contracts, ordered_ids } @@ -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 }; diff --git a/crates/script/src/execute.rs b/crates/script/src/execute.rs index 1239cda1f3bc..6dc6a9617c09 100644 --- a/crates/script/src/execute.rs +++ b/crates/script/src/execute.rs @@ -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::{ @@ -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)?; diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index dc4e05e1a0bf..cc43929a6e06 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -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 diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index cf392129055e..3f5405db6f68 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -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 { @@ -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| { diff --git a/crates/script/src/verify.rs b/crates/script/src/verify.rs index c9e10200437d..f6545c30194c 100644 --- a/crates/script/src/verify.rs +++ b/crates/script/src/verify.rs @@ -106,11 +106,11 @@ impl VerifyBundle { libraries: &[String], ) -> Option { 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(