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

feat(forge vb): --ignore-predeploy-immutables #8850

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
72 changes: 27 additions & 45 deletions crates/forge/tests/cli/verify_bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ fn test_verify_bytecode_with_ignore(
verifier_url: &str,
expected_matches: (&str, &str),
ignore: &str,
ignore_immutables: bool,
chain: &str,
) {
let etherscan_key = next_mainnet_etherscan_api_key();
Expand All @@ -96,26 +97,27 @@ fn test_verify_bytecode_with_ignore(
prj.add_source(contract_name, &source_code).unwrap();
prj.write_config(config);

let output = cmd
.forge_fuse()
.args([
"verify-bytecode",
addr,
contract_name,
"--etherscan-api-key",
&etherscan_key,
"--verifier",
verifier,
"--verifier-url",
verifier_url,
"--rpc-url",
&rpc_url,
"--ignore",
ignore,
])
.assert_success()
.get_output()
.stdout_lossy();
let mut args = vec![
"verify-bytecode",
addr,
contract_name,
"--etherscan-api-key",
&etherscan_key,
"--verifier",
verifier,
"--verifier-url",
verifier_url,
"--rpc-url",
&rpc_url,
"--ignore",
ignore,
];

if ignore_immutables {
args.push("--ignore-predeploy-immutables");
}

let output = cmd.forge_fuse().args(args).assert_success().get_output().stdout_lossy();

if ignore == "creation" {
assert!(!output.contains(
Expand All @@ -131,6 +133,9 @@ fn test_verify_bytecode_with_ignore(
assert!(!output
.contains(format!("Runtime code matched with status {}", expected_matches.1).as_str()));
} else {
if ignore_immutables {
assert!(output.contains("Ignoring immutable references"));
}
assert!(output
.contains(format!("Runtime code matched with status {}", expected_matches.1).as_str()));
}
Expand Down Expand Up @@ -261,6 +266,7 @@ forgetest_async!(can_ignore_creation, |prj, cmd| {
"https://api.etherscan.io/api",
("ignored", "partial"),
"creation",
false,
"1",
);
});
Expand All @@ -283,31 +289,7 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| {
"https://api.etherscan.io/api",
("partial", "ignored"),
"runtime",
false,
"1",
);
});

// Test predeploy contracts
// TODO: Add test utils for base such as basescan keys and alchemy keys.
// WETH9 Predeploy
// forgetest_async!(can_verify_predeploys, |prj, cmd| {
// test_verify_bytecode_with_ignore(
// prj,
// cmd,
// "0x4200000000000000000000000000000000000006",
// "WETH9",
// Config {
// evm_version: EvmVersion::default(),
// optimizer: true,
// optimizer_runs: 10000,
// cbor_metadata: true,
// bytecode_hash: BytecodeHash::Bzzr1,
// ..Default::default()
// },
// "etherscan",
// "https://api.basescan.org/api",
// ("ignored", "partial"),
// "creation",
// "base",
// );
// });
67 changes: 53 additions & 14 deletions crates/verify/src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
/// Ignore verification for creation or runtime bytecode.
#[clap(long, value_name = "BYTECODE_TYPE")]
pub ignore: Option<BytecodeType>,

/// Ignore immutable references while verifying runtime bytecode for predeployed contracts.
/// Use this to avoid passing `--constructor-args`.
#[clap(long, default_value = "false")]
pub ignore_predeploy_immutables: bool,
}

impl figment::Provider for VerifyBytecodeArgs {
Expand Down Expand Up @@ -138,6 +143,14 @@
&config,
)?;

// ignore flag setup
let ignore_predeploy_immutables = self.ignore_predeploy_immutables;
let mut ignore = self.ignore;
if ignore_predeploy_immutables && self.ignore.is_none() {
ignore = Some(BytecodeType::Creation);
}

trace!(?ignore_predeploy_immutables);
// Get the bytecode at the address, bailing if it doesn't exist.
let code = provider.get_code_at(self.address).await?;
if code.is_empty() {
Expand Down Expand Up @@ -209,6 +222,8 @@
check_explorer_args(source_code.clone())?
};

trace!(provided_constructor_args = ?constructor_args);

// This fails only when the contract expects constructor args but NONE were provided OR
// retrieved from explorer (in case of predeploys).
crate::utils::check_args_len(&artifact, &constructor_args)?;
Expand All @@ -220,7 +235,10 @@
format!("Attempting to verify predeployed contract at {:?}. Ignoring creation code verification.", self.address)
.yellow()
.bold()
)
);
if ignore_predeploy_immutables {
println!("{}", "Ignoring immutable references for predeploys.".yellow().bold());
}
}

// Append constructor args to the local_bytecode.
Expand Down Expand Up @@ -272,17 +290,38 @@
crate::utils::deploy_contract(&mut executor, &env, config.evm_spec_id(), &gen_tx)?;

// Compare runtime bytecode
let (deployed_bytecode, onchain_runtime_code) = crate::utils::get_runtime_codes(
&mut executor,
&provider,
self.address,
fork_address,
None,
)
.await?;
let (mut deployed_bytecode, mut onchain_runtime_code) =
crate::utils::get_runtime_codes(
&mut executor,
&provider,
self.address,
fork_address,
None,
)
.await?;

if ignore_predeploy_immutables {
// Locate immutable refs using the offsets in the artifact
let immutable_refs = crate::utils::get_immutable_refs(&artifact);

trace!(immutable_refs_found = immutable_refs.is_some());

if let Some(refs) = immutable_refs {
// TODO: Extract those sections from both `deployed_bytecode` and
// `onchain_runtime_code`.

trace!("extracting refs from deployed bytecode");
deployed_bytecode =
crate::utils::extract_immutables_refs(refs.clone(), deployed_bytecode);

trace!("extracting refs from onchain runtime code");
onchain_runtime_code =
crate::utils::extract_immutables_refs(refs, onchain_runtime_code);
}
}

let match_type = crate::utils::match_bytecodes(
&deployed_bytecode.original_bytes(),
&deployed_bytecode,
&onchain_runtime_code,
&constructor_args,
true,
Expand Down Expand Up @@ -365,7 +404,7 @@

trace!(ignore = ?self.ignore);
// Check if `--ignore` is set to `creation`.
if !self.ignore.is_some_and(|b| b.is_creation()) {
if !ignore.is_some_and(|b| b.is_creation()) {
// Compare creation code with locally built bytecode and `maybe_creation_code`.
let match_type = crate::utils::match_bytecodes(
local_bytecode_vec.as_slice(),
Expand Down Expand Up @@ -401,7 +440,7 @@
}
}

if !self.ignore.is_some_and(|b| b.is_runtime()) {
if !ignore.is_some_and(|b| b.is_runtime()) {
// Get contract creation block.
let simulation_block = match self.block {
Some(BlockId::Number(BlockNumberOrTag::Number(block))) => block,
Expand Down Expand Up @@ -468,7 +507,7 @@
&transaction,
)?;

// State committed using deploy_with_env, now get the runtime bytecode from the db.
// State commited using deploy_with_env, now get the runtime bytecode from the db.

Check failure on line 510 in crates/verify/src/bytecode.rs

View workflow job for this annotation

GitHub Actions / codespell

commited ==> committed
let (fork_runtime_code, onchain_runtime_code) = crate::utils::get_runtime_codes(
&mut executor,
&provider,
Expand All @@ -480,7 +519,7 @@

// Compare the onchain runtime bytecode with the runtime code from the fork.
let match_type = crate::utils::match_bytecodes(
&fork_runtime_code.original_bytes(),
&fork_runtime_code,
&onchain_runtime_code,
&constructor_args,
true,
Expand Down
59 changes: 55 additions & 4 deletions crates/verify/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ use foundry_block_explorers::{
errors::EtherscanError,
};
use foundry_common::{abi::encode_args, compile::ProjectCompiler, provider::RetryProvider};
use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion};
use foundry_compilers::artifacts::{BytecodeHash, CompactContractBytecode, EvmVersion, Offsets};
use foundry_config::Config;
use foundry_evm::{constants::DEFAULT_CREATE2_DEPLOYER, executors::TracingExecutor, opts::EvmOpts};
use reqwest::Url;
use revm_primitives::{
db::Database,
env::{EnvWithHandlerCfg, HandlerCfg},
Bytecode, Env, SpecId,
Env, SpecId,
};
use semver::Version;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use yansi::Paint;

/// Enum to represent the type of bytecode being verified
Expand Down Expand Up @@ -136,6 +137,56 @@ pub fn build_using_cache(
eyre::bail!("couldn't find cached artifact for contract {}", args.contract.name)
}

pub fn get_immutable_refs(
artifact: &CompactContractBytecode,
) -> Option<BTreeMap<String, Vec<Offsets>>> {
if artifact.deployed_bytecode.as_ref().is_some_and(|b| !b.immutable_references.is_empty()) {
let immutable_refs =
artifact.deployed_bytecode.as_ref().unwrap().immutable_references.clone();

return Some(immutable_refs);
}

None
}
Comment on lines +140 to +151
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we make this just return a Vec of (start, end) tuples so that we can just iterate over offsets in extract_immutable_refs


pub fn extract_immutables_refs(
immutable_refs: BTreeMap<String, Vec<Offsets>>,
mut bytecode: Bytes,
) -> Bytes {
let mut total_len_extracted_expected = 0;
let init_length = bytecode.len();
for (_key, offsets) in immutable_refs {
for offset in offsets {
let start = offset.start as usize;
let end = (offset.start + offset.length) as usize;

total_len_extracted_expected += offset.length;

// Remove this sections of bytes from the bytecode
let start_section = bytecode.slice(0..start);
let end_section = bytecode.slice(end..bytecode.len());

// Combine the start and end sections
let mut start_section_vec = start_section.to_vec();

start_section_vec.extend_from_slice(&end_section);

bytecode = Bytes::from(start_section_vec);
Comment on lines +160 to +175
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should account for bytecode being shifted on every iteration, thus requiring next offsets to be shifted too

}
}
let end_length = bytecode.len();

tracing::info!(
"Total length extracted (Expected): {}, Initial length: {}, Final length: {}",
total_len_extracted_expected,
init_length,
end_length
);

bytecode
}

pub fn print_result(
args: &VerifyBytecodeArgs,
res: Option<VerificationType>,
Expand Down Expand Up @@ -390,7 +441,7 @@ pub async fn get_runtime_codes(
address: Address,
fork_address: Address,
block: Option<u64>,
) -> Result<(Bytecode, Bytes)> {
) -> Result<(Bytes, Bytes)> {
let fork_runtime_code = executor
.backend_mut()
.basic(fork_address)?
Expand All @@ -414,7 +465,7 @@ pub async fn get_runtime_codes(
provider.get_code_at(address).await?
};

Ok((fork_runtime_code, onchain_runtime_code))
Ok((fork_runtime_code.original_bytes(), onchain_runtime_code))
}

/// Returns `true` if the URL only consists of host.
Expand Down
Loading