diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index d0f9afbb1e4d2..b0f25be1dff56 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -23,6 +23,7 @@ use foundry_compilers::{ solc::SolcSettings, }; use num_format::{Locale, ToFormattedString}; +use revm::primitives::{eip170, eip3860, eip7954, hardfork::SpecId}; use solar::{ ast::{Arena, ContractKind, ItemKind}, interface::{Session, source_map::FileName}, @@ -357,10 +358,10 @@ impl ProjectCompiler { sh_println!("{size_report}")?; - let runtime_eip = if size_report.limits.runtime == CONTRACT_RUNTIME_SIZE_LIMIT { - "EIP-170: " - } else { - "" + let runtime_eip = match size_report.limits.runtime { + CONTRACT_RUNTIME_SIZE_LIMIT => "EIP-170: ", + eip7954::MAX_CODE_SIZE => "EIP-7954: ", + _ => "", }; eyre::ensure!( !size_report.exceeds_runtime_size_limit(), @@ -368,10 +369,10 @@ impl ProjectCompiler { size_report.limits.runtime ); // Check size limits only if not ignoring EIP-3860 - let initcode_eip = if size_report.limits.initcode == CONTRACT_INITCODE_SIZE_LIMIT { - "EIP-3860: " - } else { - "" + let initcode_eip = match size_report.limits.initcode { + CONTRACT_INITCODE_SIZE_LIMIT => "EIP-3860: ", + eip7954::MAX_INITCODE_SIZE => "EIP-7954: ", + _ => "", }; eyre::ensure!( self.ignore_eip_3860 || !size_report.exceeds_initcode_size_limit(), @@ -385,10 +386,10 @@ impl ProjectCompiler { } // https://eips.ethereum.org/EIPS/eip-170 -const CONTRACT_RUNTIME_SIZE_LIMIT: usize = 24576; +const CONTRACT_RUNTIME_SIZE_LIMIT: usize = eip170::MAX_CODE_SIZE; // https://eips.ethereum.org/EIPS/eip-3860 -const CONTRACT_INITCODE_SIZE_LIMIT: usize = 49152; +const CONTRACT_INITCODE_SIZE_LIMIT: usize = eip3860::MAX_INITCODE_SIZE; const CONTRACT_RUNTIME_SIZE_WARN_THRESHOLD: usize = 18_000; const CONTRACT_INITCODE_SIZE_WARN_THRESHOLD: usize = 36_000; @@ -413,6 +414,15 @@ impl ContractSizeLimits { Self { runtime, initcode: runtime.saturating_mul(2) } } + /// Returns the protocol limits active for an EVM specification. + pub const fn for_spec_id(spec_id: SpecId) -> Self { + if spec_id.is_enabled_in(SpecId::AMSTERDAM) { + Self::new(eip7954::MAX_CODE_SIZE, eip7954::MAX_INITCODE_SIZE) + } else { + Self::new(CONTRACT_RUNTIME_SIZE_LIMIT, CONTRACT_INITCODE_SIZE_LIMIT) + } + } + const fn runtime_warning_threshold(self) -> usize { scaled_threshold( self.runtime, @@ -898,4 +908,13 @@ mod tests { ContractSizeLimits::new(50_000, 100_000) ); } + + #[test] + fn contract_size_limits_follow_evm_spec() { + assert_eq!(ContractSizeLimits::for_spec_id(SpecId::OSAKA), ContractSizeLimits::default()); + assert_eq!( + ContractSizeLimits::for_spec_id(SpecId::AMSTERDAM), + ContractSizeLimits::new(eip7954::MAX_CODE_SIZE, eip7954::MAX_INITCODE_SIZE) + ); + } } diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 6fd83203ca7a1..7a3ce45f6eb8b 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -128,7 +128,7 @@ impl BuildArgs { config .code_size_limit .map(ContractSizeLimits::with_runtime_limit) - .unwrap_or_default(), + .unwrap_or_else(|| ContractSizeLimits::for_spec_id(config.evm_spec_id())), ) .bail(!format_json) .compile(&project)?; diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index ba733a5e2a0bf..cceaa34fad8c3 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -1,4 +1,5 @@ use crate::utils::generate_large_init_contract; +use foundry_compilers::artifacts::EvmVersion; use foundry_test_utils::{forgetest, forgetest_init, snapbox::IntoData, str}; use globset::Glob; use std::fs; @@ -189,6 +190,27 @@ forgetest!(build_sizes_respects_configured_code_size_limit, |prj, cmd| { ); }); +forgetest!(build_sizes_respects_amsterdam_code_size_limits, |prj, cmd| { + prj.add_source("LargeContract.sol", generate_large_init_contract(50_000).as_str()); + prj.update_config(|config| { + config.evm_version = EvmVersion::Amsterdam; + }); + + cmd.args(["build", "--sizes", "--json"]).assert_success().stdout_eq( + str![[r#" +{ + "LargeContract": { + "runtime_size": 62, + "init_size": 50125, + "runtime_margin": 32706, + "init_margin": 15411 + } +} +"#]] + .is_json(), + ); +}); + // tests build output is as expected forgetest_init!(exact_build_output, |prj, cmd| { prj.initialize_default_contracts(); diff --git a/crates/forge/tests/cli/script.rs b/crates/forge/tests/cli/script.rs index ac2f45c52dfd1..bc2a4e0e375fa 100644 --- a/crates/forge/tests/cli/script.rs +++ b/crates/forge/tests/cli/script.rs @@ -9,6 +9,7 @@ use alloy_network::Ethereum; use alloy_primitives::{Address, Bytes, U256, address, hex}; use anvil::{NodeConfig, spawn}; use forge_script_sequence::ScriptSequence; +use foundry_compilers::artifacts::EvmVersion; use foundry_test_utils::{ ScriptOutcome, ScriptTester, rpc::{self, next_http_archive_rpc_url}, @@ -4054,6 +4055,32 @@ Error: `LargeRuntime` is above the contract size limit ([..] > 24576). "#]]); }); +forgetest_async!(script_check_contract_sizes_uses_amsterdam_code_size_limit, |prj, cmd| { + foundry_test_utils::util::initialize(prj.root()); + write_large_runtime_deploy_script(&prj, 25_000); + prj.update_config(|config| { + config.evm_version = EvmVersion::Amsterdam; + }); + + let (_api, handle) = spawn(NodeConfig::test().with_gas_limit(Some(1_000_000_000))).await; + cmd.set_current_dir(prj.root()); + for var in ["FOUNDRY_CODE_SIZE_LIMIT", "DAPP_CODE_SIZE_LIMIT", "DAPP_TEST_CODE_SIZE_LIMIT"] { + cmd.unset_env(var); + } + let assert = cmd + .args([ + "script", + "DeployLarge", + "--rpc-url", + &handle.http_endpoint(), + "--gas-limit", + "1000000000", + ]) + .assert_success(); + let stderr = String::from_utf8_lossy(&assert.get_output().stderr); + assert!(!stderr.contains("above the contract size limit"), "{stderr}"); +}); + // Tests that `forge script` honors `code_size_limit` configured via foundry.toml // (the bug fix: previously only the CLI flag was honored). forgetest_async!(script_check_contract_sizes_honors_config_limit, |prj, cmd| { diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 1d0c57ead7a3a..c68cafad7a4e5 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -454,7 +454,11 @@ impl ScriptArgs { .code_size_limit .or(pre_simulation.script_config.config.code_size_limit) .map(ContractSizeLimits::with_runtime_limit) - .unwrap_or_default(); + .unwrap_or_else(|| { + ContractSizeLimits::for_spec_id( + pre_simulation.script_config.config.evm_spec_id(), + ) + }); pre_simulation.args.check_contract_sizes( size_limits, &pre_simulation.execution_result, @@ -565,8 +569,8 @@ impl ScriptArgs { Ok((func.clone(), data.into())) } - /// Checks if the transaction is a deployment with either a size above the default contract size - /// limit or specified `code_size_limit`. + /// Checks if the transaction is a deployment with a size above the active EVM specification's + /// contract size limit or the specified `code_size_limit`. /// /// If `self.broadcast` is enabled, it asks confirmation of the user. Otherwise, it just warns /// the user.