diff --git a/crates/common/src/compile.rs b/crates/common/src/compile.rs index ed98038fa146e..082fc0628d572 100644 --- a/crates/common/src/compile.rs +++ b/crates/common/src/compile.rs @@ -12,7 +12,7 @@ use eyre::Result; use foundry_block_explorers::contract::Metadata; use foundry_compilers::{ Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig, - artifacts::{BytecodeObject, Contract, Source, remappings::Remapping}, + artifacts::{BytecodeObject, Contract, EvmVersion, Source, remappings::Remapping}, compilers::{ Compiler, solc::{Solc, SolcCompiler}, @@ -56,8 +56,11 @@ pub struct ProjectCompiler { /// Whether to bail on compiler errors. bail: Option, - /// Whether to ignore the contract initcode size limit introduced by EIP-3860. - ignore_eip_3860: bool, + /// Whether to ignore the contract initcode size limit. + ignore_initcode_size: bool, + + /// The EVM version to check for contract size limits (EIP-7907). + evm_version: EvmVersion, /// Extra files to include, that are not necessarily in the project's source dir. files: Vec, @@ -84,7 +87,8 @@ impl ProjectCompiler { print_sizes: None, quiet: Some(crate::shell::is_quiet()), bail: None, - ignore_eip_3860: false, + ignore_initcode_size: false, + evm_version: EvmVersion::default(), files: Vec::new(), dynamic_test_linking: false, } @@ -126,10 +130,17 @@ impl ProjectCompiler { self } - /// Sets whether to ignore EIP-3860 initcode size limits. + /// Sets whether to ignore initcode size limits. + #[inline] + pub fn ignore_initcode_size(mut self, yes: bool) -> Self { + self.ignore_initcode_size = yes; + self + } + + /// Sets the EVM version to check for EIP-7907 size limits. #[inline] - pub fn ignore_eip_3860(mut self, yes: bool) -> Self { - self.ignore_eip_3860 = yes; + pub fn evm_version(mut self, version: EvmVersion) -> Self { + self.evm_version = version; self } @@ -276,8 +287,11 @@ impl ProjectCompiler { sh_println!()?; } - let mut size_report = - SizeReport { report_kind: report_kind(), contracts: BTreeMap::new() }; + let mut size_report = SizeReport { + report_kind: report_kind(), + evm_version: self.evm_version, + contracts: BTreeMap::new(), + }; let mut artifacts: BTreeMap> = BTreeMap::new(); for (id, artifact) in output.artifact_ids().filter(|(id, _)| { @@ -322,16 +336,22 @@ impl ProjectCompiler { sh_println!("{size_report}")?; + let runtime_limit = size_report.runtime_size_limit(); + let eip_name = + if is_eip_7907_enabled(self.evm_version) { "EIP-7907" } else { "EIP-170" }; eyre::ensure!( !size_report.exceeds_runtime_size_limit(), "some contracts exceed the runtime size limit \ - (EIP-170: {CONTRACT_RUNTIME_SIZE_LIMIT} bytes)" + ({eip_name}: {runtime_limit} bytes)" ); - // Check size limits only if not ignoring EIP-3860 + // Check init code size limit only if not ignoring it + let initcode_limit = size_report.initcode_size_limit(); + let init_eip_name = + if is_eip_7907_enabled(self.evm_version) { "EIP-7907" } else { "EIP-3860" }; eyre::ensure!( - self.ignore_eip_3860 || !size_report.exceeds_initcode_size_limit(), + self.ignore_initcode_size || !size_report.exceeds_initcode_size_limit(), "some contracts exceed the initcode size limit \ - (EIP-3860: {CONTRACT_INITCODE_SIZE_LIMIT} bytes)" + ({init_eip_name}: {initcode_limit} bytes)" ); } @@ -345,10 +365,18 @@ const CONTRACT_RUNTIME_SIZE_LIMIT: usize = 24576; // https://eips.ethereum.org/EIPS/eip-3860 const CONTRACT_INITCODE_SIZE_LIMIT: usize = 49152; +// https://eips.ethereum.org/EIPS/eip-7907 +const CONTRACT_RUNTIME_SIZE_LIMIT_EIP_7907: usize = 49152; + +// https://eips.ethereum.org/EIPS/eip-7907 +const CONTRACT_INITCODE_SIZE_LIMIT_EIP_7907: usize = 98304; + /// Contracts with info about their size pub struct SizeReport { /// What kind of report to generate. report_kind: ReportKind, + /// The EVM version to check for EIP-7907 size limits. + evm_version: EvmVersion, /// `contract name -> info` pub contracts: BTreeMap, } @@ -376,12 +404,30 @@ impl SizeReport { /// Returns true if any contract exceeds the runtime size limit, excluding dev contracts. pub fn exceeds_runtime_size_limit(&self) -> bool { - self.max_runtime_size() > CONTRACT_RUNTIME_SIZE_LIMIT + self.max_runtime_size() > self.runtime_size_limit() } /// Returns true if any contract exceeds the initcode size limit, excluding dev contracts. pub fn exceeds_initcode_size_limit(&self) -> bool { - self.max_init_size() > CONTRACT_INITCODE_SIZE_LIMIT + self.max_init_size() > self.initcode_size_limit() + } + + /// Returns the runtime size limit based on EIP configuration. + pub fn runtime_size_limit(&self) -> usize { + if is_eip_7907_enabled(self.evm_version) { + CONTRACT_RUNTIME_SIZE_LIMIT_EIP_7907 + } else { + CONTRACT_RUNTIME_SIZE_LIMIT + } + } + + /// Returns the initcode size limit based on EIP configuration. + pub fn initcode_size_limit(&self) -> usize { + if is_eip_7907_enabled(self.evm_version) { + CONTRACT_INITCODE_SIZE_LIMIT_EIP_7907 + } else { + CONTRACT_INITCODE_SIZE_LIMIT + } } } @@ -402,6 +448,9 @@ impl Display for SizeReport { impl SizeReport { fn format_json_output(&self) -> String { + let runtime_limit = self.runtime_size_limit(); + let initcode_limit = self.initcode_size_limit(); + let contracts = self .contracts .iter() @@ -412,8 +461,8 @@ impl SizeReport { serde_json::json!({ "runtime_size": contract.runtime_size, "init_size": contract.init_size, - "runtime_margin": CONTRACT_RUNTIME_SIZE_LIMIT as isize - contract.runtime_size as isize, - "init_margin": CONTRACT_INITCODE_SIZE_LIMIT as isize - contract.init_size as isize, + "runtime_margin": runtime_limit as isize - contract.runtime_size as isize, + "init_margin": initcode_limit as isize - contract.init_size as isize, }), ) }) @@ -426,6 +475,9 @@ impl SizeReport { let mut table = Table::new(); table.apply_modifier(UTF8_ROUND_CORNERS); + let runtime_limit = self.runtime_size_limit(); + let initcode_limit = self.initcode_size_limit(); + table.set_header(vec![ Cell::new("Contract"), Cell::new("Runtime Size (B)"), @@ -440,19 +492,22 @@ impl SizeReport { .iter() .filter(|(_, c)| !c.is_dev_contract && (c.runtime_size > 0 || c.init_size > 0)); for (name, contract) in contracts { - let runtime_margin = - CONTRACT_RUNTIME_SIZE_LIMIT as isize - contract.runtime_size as isize; - let init_margin = CONTRACT_INITCODE_SIZE_LIMIT as isize - contract.init_size as isize; + let runtime_margin = runtime_limit as isize - contract.runtime_size as isize; + let init_margin = initcode_limit as isize - contract.init_size as isize; + + // Dynamic color thresholds based on the limit of the active EIP + let runtime_yellow_threshold = runtime_limit * 3 / 4; + let init_yellow_threshold = initcode_limit * 3 / 4; let runtime_color = match contract.runtime_size { - ..18_000 => Color::Reset, - 18_000..=CONTRACT_RUNTIME_SIZE_LIMIT => Color::Yellow, + size if size < runtime_yellow_threshold => Color::Reset, + size if size <= runtime_limit => Color::Yellow, _ => Color::Red, }; let init_color = match contract.init_size { - ..36_000 => Color::Reset, - 36_000..=CONTRACT_INITCODE_SIZE_LIMIT => Color::Yellow, + size if size < init_yellow_threshold => Color::Reset, + size if size <= initcode_limit => Color::Yellow, _ => Color::Red, }; @@ -506,6 +561,13 @@ pub struct ContractInfo { pub is_dev_contract: bool, } +/// Returns true if EIP-7907 size limits should be used based on the EVM version. +/// EIP-7907 is enabled in Osaka. +#[inline] +pub fn is_eip_7907_enabled(evm_version: EvmVersion) -> bool { + evm_version >= EvmVersion::Osaka +} + /// Compiles target file path. /// /// If `quiet` no solc related output will be emitted to stdout. @@ -678,4 +740,27 @@ mod tests { }) ); } + + #[test] + fn test_size_report_limits_based_on_evm_version() { + let size_report_osaka = SizeReport { + report_kind: ReportKind::Text, + evm_version: EvmVersion::Osaka, + contracts: BTreeMap::new(), + }; + + let size_report_prague = SizeReport { + report_kind: ReportKind::Text, + evm_version: EvmVersion::Prague, + contracts: BTreeMap::new(), + }; + + // Osaka should use EIP-7907 limits + assert_eq!(size_report_osaka.runtime_size_limit(), CONTRACT_RUNTIME_SIZE_LIMIT_EIP_7907); + assert_eq!(size_report_osaka.initcode_size_limit(), CONTRACT_INITCODE_SIZE_LIMIT_EIP_7907); + + // Prague should use EIP-170/EIP-3860 limits + assert_eq!(size_report_prague.runtime_size_limit(), CONTRACT_RUNTIME_SIZE_LIMIT); + assert_eq!(size_report_prague.initcode_size_limit(), CONTRACT_INITCODE_SIZE_LIMIT); + } } diff --git a/crates/common/src/constants.rs b/crates/common/src/constants.rs index d127cc0548b06..63e4450178054 100644 --- a/crates/common/src/constants.rs +++ b/crates/common/src/constants.rs @@ -9,9 +9,12 @@ pub const DEV_CHAIN_ID: u64 = 31337; /// The first four bytes of the call data for a function call specifies the function to be called. pub const SELECTOR_LEN: usize = 4; -/// Maximum size in bytes (0x6000) that a contract can have. +/// Maximum size in bytes (0x6000) that a contract can have (EIP-170). pub const CONTRACT_MAX_SIZE: usize = 24576; +/// Maximum size in bytes (0xC000) that a contract can have under EIP-7907. +pub const CONTRACT_MAX_SIZE_EIP_7907: usize = 49152; + /// Default request timeout for http requests /// /// Note: this is only used so that connections, that are discarded on the server side won't stay diff --git a/crates/forge/src/cmd/build.rs b/crates/forge/src/cmd/build.rs index 272cae93badaa..0c1625c1bf7f9 100644 --- a/crates/forge/src/cmd/build.rs +++ b/crates/forge/src/cmd/build.rs @@ -56,10 +56,10 @@ pub struct BuildArgs { #[serde(skip)] pub sizes: bool, - /// Ignore initcode contract bytecode size limit introduced by EIP-3860. - #[arg(long, alias = "ignore-initcode-size")] + /// Ignore initcode contract bytecode size limit. + #[arg(long, alias = "ignore-eip-3860")] #[serde(skip)] - pub ignore_eip_3860: bool, + pub ignore_initcode_size: bool, #[command(flatten)] #[serde(flatten)] @@ -100,7 +100,8 @@ impl BuildArgs { .dynamic_test_linking(config.dynamic_test_linking) .print_names(self.names) .print_sizes(self.sizes) - .ignore_eip_3860(self.ignore_eip_3860) + .ignore_initcode_size(self.ignore_initcode_size) + .evm_version(config.evm_version) .bail(!format_json); // Runs the SolidityLinter before compilation. @@ -207,8 +208,8 @@ impl Provider for BuildArgs { dict.insert("sizes".to_string(), true.into()); } - if self.ignore_eip_3860 { - dict.insert("ignore_eip_3860".to_string(), true.into()); + if self.ignore_initcode_size { + dict.insert("ignore_initcode_size".to_string(), true.into()); } Ok(Map::from([(Config::selected_profile(), dict)])) diff --git a/crates/forge/tests/cli/build.rs b/crates/forge/tests/cli/build.rs index 26db462134e1d..4fb8f033fab8d 100644 --- a/crates/forge/tests/cli/build.rs +++ b/crates/forge/tests/cli/build.rs @@ -100,7 +100,39 @@ Compiler run successful! .is_json(), ); - // Ignore EIP-3860 + // EIP-7907 increased code size limits + + cmd.forge_fuse().args(["build", "--sizes", "--evm-version", "osaka"]).assert_success().stdout_eq( + str![[r#" + No files changed, compilation skipped + + ╭---------------+------------------+-------------------+--------------------+---------------------╮ + | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | + +=================================================================================================+ + | LargeContract | 62 | 50,125 | 49,090 | 48,179 | + ╰---------------+------------------+-------------------+--------------------+---------------------╯ + + + "#]], + ); + + // Ignore initcode size + + cmd.forge_fuse().args(["build", "--sizes", "--ignore-initcode-size"]).assert_success().stdout_eq( + str![[r#" + No files changed, compilation skipped + + ╭---------------+------------------+-------------------+--------------------+---------------------╮ + | Contract | Runtime Size (B) | Initcode Size (B) | Runtime Margin (B) | Initcode Margin (B) | + +=================================================================================================+ + | LargeContract | 62 | 50,125 | 24,514 | -973 | + ╰---------------+------------------+-------------------+--------------------+---------------------╯ + + + "#]], + ); + + // Ignore initcode size using EIP-3860 alias (legacy) cmd.forge_fuse().args(["build", "--sizes", "--ignore-eip-3860"]).assert_success().stdout_eq( str![[r#"