Skip to content

feat(hardfork): support EIP-7907: increase initcode and contract code size limit #10932

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 109 additions & 24 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -56,8 +56,11 @@ pub struct ProjectCompiler {
/// Whether to bail on compiler errors.
bail: Option<bool>,

/// 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<PathBuf>,
Expand All @@ -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,
}
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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<String, Vec<_>> = BTreeMap::new();
for (id, artifact) in output.artifact_ids().filter(|(id, _)| {
Expand Down Expand Up @@ -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)"
);
}

Expand All @@ -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<String, ContractInfo>,
}
Expand Down Expand Up @@ -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
}
}
}

Expand All @@ -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()
Expand All @@ -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,
}),
)
})
Expand All @@ -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)"),
Expand All @@ -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,
};

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}
}
5 changes: 4 additions & 1 deletion crates/common/src/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions crates/forge/src/cmd/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Author

Choose a reason for hiding this comment

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

@zerosnacks @DaniPopes are you okay with changing the default here? Shouldn't be any breaking changes from cli usage due to this


#[command(flatten)]
#[serde(flatten)]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)]))
Expand Down
34 changes: 33 additions & 1 deletion crates/forge/tests/cli/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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#"
Expand Down
Loading