diff --git a/crates/forge/tests/cli/verify_bytecode.rs b/crates/forge/tests/cli/verify_bytecode.rs index 4f0366ada55f4..f7ade549a9854 100644 --- a/crates/forge/tests/cli/verify_bytecode.rs +++ b/crates/forge/tests/cli/verify_bytecode.rs @@ -144,6 +144,65 @@ fn test_verify_bytecode_with_ignore( ); } } + +#[inline] +fn test_verify_bytecode_mismatch( + prj: TestProject, + mut cmd: TestCommand, + addr: &str, + contract_name: &str, + config: Config, + verifier: &str, + verifier_url: &str, +) { + let etherscan_key = next_etherscan_api_key(); + let rpc_url = next_http_archive_rpc_url(); + + // Fetch real source code + let real_source = cmd + .cast_fuse() + .args(["source", addr, "--flatten", "--etherscan-api-key", ðerscan_key]) + .assert_success() + .get_output() + .stdout_lossy(); + + prj.add_source(contract_name, &real_source); + prj.write_config(config); + // Build once with correct source (creates cache) + cmd.forge_fuse().arg("build").assert_success(); + + let source_code = r#" + contract SystemConfig { + uint256 public constant MODIFIED_VALUE = 999; + + function someFunction() public pure returns (uint256) { + return MODIFIED_VALUE; + } + } + "#; + + // Now replace with different incorrect source code + prj.add_source(contract_name, source_code); + let args = vec![ + "verify-bytecode", + addr, + contract_name, + "--etherscan-api-key", + ðerscan_key, + "--verifier", + verifier, + "--verifier-url", + verifier_url, + "--rpc-url", + &rpc_url, + ]; + let output = cmd.forge_fuse().args(args).assert_success().get_output().stderr_lossy(); + + // Verify that bytecode does NOT match (recompiled with incorrect source) + assert!(output.contains("Error: Creation code did not match".to_string().as_str())); + assert!(output.contains("Error: Runtime code did not match".to_string().as_str())); +} + forgetest_async!(can_verify_bytecode_no_metadata, |prj, cmd| { test_verify_bytecode( prj, @@ -296,6 +355,26 @@ forgetest_async!(can_ignore_runtime, |prj, cmd| { ); }); +// Test that verification fails when source code doesn't match deployed bytecode +forgetest_async!(can_verify_bytecode_fails_on_source_mismatch, |prj, cmd| { + test_verify_bytecode_mismatch( + prj, + cmd, + "0xba2492e52F45651B60B8B38d4Ea5E2390C64Ffb1", + "SystemConfig", + Config { + evm_version: EvmVersion::London, + optimizer_runs: Some(999999), + optimizer: Some(true), + cbor_metadata: false, + bytecode_hash: BytecodeHash::None, + ..Default::default() + }, + "etherscan", + "https://api.etherscan.io/v2/api?chainid=1", + ); +}); + // Test predeploy contracts // TODO: Add test utils for base such as basescan keys and alchemy keys. // WETH9 Predeploy diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 8047f32fe7efe..67301a6c83dcf 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -187,14 +187,7 @@ impl VerifyBytecodeArgs { let etherscan_metadata = source_code.items.first().unwrap(); // Obtain local artifact - let artifact = if let Ok(local_bytecode) = - crate::utils::build_using_cache(&self, etherscan_metadata, &config) - { - trace!("using cache"); - local_bytecode - } else { - crate::utils::build_project(&self, &config)? - }; + let artifact = crate::utils::build_project(&self, &config)?; // Get local bytecode (creation code) let local_bytecode = artifact diff --git a/crates/verify/src/utils.rs b/crates/verify/src/utils.rs index 8f24e67ebf3f1..603fd8d99b775 100644 --- a/crates/verify/src/utils.rs +++ b/crates/verify/src/utils.rs @@ -97,51 +97,6 @@ pub fn build_project( Ok(artifact.into_contract_bytecode()) } -pub fn build_using_cache( - args: &VerifyBytecodeArgs, - etherscan_settings: &Metadata, - config: &Config, -) -> Result { - let project = config.project()?; - let cache = project.read_cache_file()?; - let cached_artifacts = cache.read_artifacts::()?; - - for (key, value) in cached_artifacts { - let name = args.contract.name.to_owned() + ".sol"; - let version = etherscan_settings.compiler_version.to_owned(); - // Ignores vyper - if version.starts_with("vyper:") { - eyre::bail!("Vyper contracts are not supported") - } - // Parse etherscan version string - let version = version.split('+').next().unwrap_or("").trim_start_matches('v').to_string(); - - // Check if `out/directory` name matches the contract name - if key.ends_with(name.as_str()) { - let name = name.replace(".sol", ".json"); - for artifact in value.into_values().flatten() { - // Check if ABI file matches the name - if !artifact.file.ends_with(&name) { - continue; - } - - // Check if Solidity version matches - if let Ok(version) = Version::parse(&version) - && !(artifact.version.major == version.major - && artifact.version.minor == version.minor - && artifact.version.patch == version.patch) - { - continue; - } - - return Ok(artifact.artifact); - } - } - } - - eyre::bail!("couldn't find cached artifact for contract {}", args.contract.name) -} - pub fn print_result( res: Option, bytecode_type: BytecodeType,