Repository: foundry-rs/foundry
Pull request #10081: chore: cleanup eof support
Author: login: grandizzy
Labels: name: L-ignore
description: Log: ignore PR in changelog
Created: 2025-03-14T15:36:04Z | Merged: 2025-03-20T08:07:09Z

<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/foundry-rs/foundry/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

<!-- ** Please select "Allow edits from maintainers" in the PR Options ** -->

## Motivation
- remove run eof test in docker
- rename test mod to eof
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Breaking changes


Diff:
diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs
index b9de478c3f237..5bc232e4f018c 100644
--- a/crates/cli/src/opts/build/core.rs
+++ b/crates/cli/src/opts/build/core.rs
@@ -120,11 +120,7 @@ pub struct BuildOpts {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub build_info_path: Option<PathBuf>,
 
-    /// Use EOF-enabled solc binary. Enables via-ir and sets EVM version to Prague. Requires Docker
-    /// to be installed.
-    ///
-    /// Note that this is a temporary solution until the EOF support is merged into the main solc
-    /// release.
+    /// Whether to compile contracts to EOF bytecode.
     #[arg(long)]
     #[serde(skip)]
     pub eof: bool,
diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs
index 62df691ac2ef6..4461459234955 100644
--- a/crates/config/src/lib.rs
+++ b/crates/config/src/lib.rs
@@ -48,8 +48,6 @@ use std::{
     fs,
     path::{Path, PathBuf},
     str::FromStr,
-    sync::mpsc::{self, RecvTimeoutError},
-    time::Duration,
 };
 
 mod macros;
@@ -587,9 +585,6 @@ impl Config {
     pub const DEFAULT_CREATE2_DEPLOYER: Address =
         address!("4e59b44847b379578588920ca78fbf26c0b4956c");
 
-    /// Docker image with eof-enabled solc binary
-    pub const EOF_SOLC_IMAGE: &'static str = "ghcr.io/paradigmxyz/forge-eof@sha256:46f868ce5264e1190881a3a335d41d7f42d6f26ed20b0c823609c715e38d603f";
-
     /// Loads the `Config` from the current directory.
     ///
     /// See [`figment`](Self::figment) for more details.
@@ -901,16 +896,20 @@ impl Config {
 
     /// Adjusts settings if EOF compilation is enabled.
     ///
-    /// This includes enabling via_ir, eof_version and ensuring that evm_version is not lower than
-    /// Prague.
+    /// This includes enabling optimizer, via_ir, eof_version and ensuring that evm_version is not
+    /// lower than Osaka.
     pub fn sanitize_eof_settings(&mut self) {
         if self.eof {
-            self.via_ir = true;
+            self.optimizer = Some(true);
+            self.normalize_optimizer_settings();
+
             if self.eof_version.is_none() {
                 self.eof_version = Some(EofVersion::V1);
             }
-            if self.evm_version < EvmVersion::Prague {
-                self.evm_version = EvmVersion::Prague;
+
+            self.via_ir = true;
+            if self.evm_version < EvmVersion::Osaka {
+                self.evm_version = EvmVersion::Osaka;
             }
         }
     }
@@ -1086,45 +1085,7 @@ impl Config {
     /// it's missing, unless the `offline` flag is enabled, in which case an error is thrown.
     ///
     /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists.
-    #[allow(clippy::disallowed_macros)]
     fn ensure_solc(&self) -> Result<Option<Solc>, SolcError> {
-        if self.eof {
-            let (tx, rx) = mpsc::channel();
-            let root = self.root.clone();
-            std::thread::spawn(move || {
-                tx.send(
-                    Solc::new_with_args(
-                        "docker",
-                        [
-                            "run",
-                            "--rm",
-                            "-i",
-                            "-v",
-                            &format!("{}:/app/root", root.display()),
-                            Self::EOF_SOLC_IMAGE,
-                        ],
-                    )
-                    .map(Some),
-                )
-            });
-            // If it takes more than 1 second, this likely means we are pulling the image.
-            return match rx.recv_timeout(Duration::from_secs(1)) {
-                Ok(res) => res,
-                Err(RecvTimeoutError::Timeout) => {
-                    // `sh_warn!` is a circular dependency, preventing us from using it here.
-                    eprintln!(
-                        "{}",
-                        yansi::Paint::yellow(
-                            "Pulling Docker image for eof-solc, this might take some time..."
-                        )
-                    );
-
-                    rx.recv().expect("sender dropped")
-                }
-                Err(RecvTimeoutError::Disconnected) => panic!("sender dropped"),
-            };
-        }
-
         if let Some(solc) = &self.solc {
             let solc = match solc {
                 SolcReq::Version(version) => {
diff --git a/crates/forge/tests/cli/eof.rs b/crates/forge/tests/cli/eof.rs
new file mode 100644
index 0000000000000..445980304547d
--- /dev/null
+++ b/crates/forge/tests/cli/eof.rs
@@ -0,0 +1,66 @@
+use foundry_compilers::artifacts::ConfigurableContractArtifact;
+
+// Ensure we can build and decode EOF bytecode.
+forgetest_init!(test_build_with_eof, |prj, cmd| {
+    cmd.forge_fuse()
+        .args(["build", "src/Counter.sol", "--eof", "--use", "0.8.29"])
+        .assert_success()
+        .stdout_eq(str![[r#"
+[COMPILING_FILES] with [SOLC_VERSION]
+[SOLC_VERSION] [ELAPSED]
+Compiler run successful!
+
+"#]]);
+
+    // get artifact bytecode
+    let artifact_path = prj.paths().artifacts.join("Counter.sol/Counter.json");
+    let artifact: ConfigurableContractArtifact =
+        foundry_compilers::utils::read_json_file(&artifact_path).unwrap();
+    assert!(artifact.metadata.is_some());
+    let bytecode = format!("{}", artifact.bytecode.unwrap().object.into_bytes().unwrap());
+
+    cmd.cast_fuse()
+        .args(["decode-eof", bytecode.as_str()])
+        .assert_success().stdout_eq(str![[r#"
+Header:
+╭------------------------+-------╮
+| type_size              | 4     |
+|------------------------+-------|
+| num_code_sections      | 1     |
+|------------------------+-------|
+| code_sizes             | [17]  |
+|------------------------+-------|
+| num_container_sections | 1     |
+|------------------------+-------|
+| container_sizes        | [257] |
+|------------------------+-------|
+| data_size              | 0     |
+╰------------------------+-------╯
+
+Code sections:
+╭---+--------+---------+------------------+--------------------------------------╮
+|   | Inputs | Outputs | Max stack height | Code                                 |
++================================================================================+
+| 0 | 0      | 128     | 2                | 0x608060405234e100055f6080ee005f80fd |
+╰---+--------+---------+------------------+--------------------------------------╯
+
+Container sections:
+╭---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------╮
+| 0 | 0xef000101000402000100ab04004300008000056080806040526004361015e100035f80fd5f3560e01c9081633fb5c1cb14e1006d81638381f58a14e100475063d09de08a14e100045fe0ffd534e100325f600319360112e100255f545f198114e10009600190015f555f80f3634e487b7160e01b5f52601160045260245ffd5f80fd5f80fd34e100155f600319360112e100086020905f548152f35f80fd5f80fd34e100166020600319360112e100086004355f555f80f35f80fd5f80fda364697066735822122030195051c5939201983e86f52c88296e7ba03945a054b4e413a7b16cafb76bd96c6578706572696d656e74616cf564736f6c634300081d0041 |
+╰---+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------╯
+
+"#]]);
+});
+
+// Ensure compiler fails if doesn't support EOFs but eof flag used.
+forgetest_init!(test_unsupported_compiler, |prj, cmd| {
+    cmd.forge_fuse()
+        .args(["build", "src/Counter.sol", "--eof", "--use", "0.8.27"])
+        .assert_failure()
+        .stderr_eq(str![[r#"
+...
+Error: Compiler run failed:
+...
+
+"#]]);
+});
diff --git a/crates/forge/tests/cli/main.rs b/crates/forge/tests/cli/main.rs
index cd12f45ab7143..87b4729daae9d 100644
--- a/crates/forge/tests/cli/main.rs
+++ b/crates/forge/tests/cli/main.rs
@@ -16,11 +16,11 @@ mod create;
 mod debug;
 mod doc;
 mod eip712;
+mod eof;
 mod failure_assertions;
 mod geiger;
 mod inline_config;
 mod multi_script;
-mod odyssey;
 mod script;
 mod soldeer;
 mod svm;
diff --git a/crates/forge/tests/cli/odyssey.rs b/crates/forge/tests/cli/odyssey.rs
deleted file mode 100644
index f465c032bd134..0000000000000
--- a/crates/forge/tests/cli/odyssey.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use foundry_compilers::artifacts::EvmVersion;
-
-// Ensure we can run basic counter tests with EOF support.
-forgetest_init!(test_eof_flag, |prj, cmd| {
-    if !has_docker() {
-        println!("skipping because no docker is available");
-        return;
-    }
-
-    prj.update_config(|config| {
-        config.evm_version = EvmVersion::Osaka;
-    });
-
-    cmd.forge_fuse().args(["test", "--eof"]).assert_success().stdout_eq(str![[r#"
-[COMPILING_FILES] with [SOLC_VERSION]
-[SOLC_VERSION] [ELAPSED]
-Compiler run successful with warnings:
-Warning (3805): This is a pre-release compiler version, please do not use it in production.
-
-Ran 2 tests for test/Counter.t.sol:CounterTest
-[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS])
-[PASS] test_Increment() ([GAS])
-Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]
-
-Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)
-
-"#]]);
-});
-
-fn has_docker() -> bool {
-    if !cfg!(target_os = "linux") {
-        return false;
-    }
-
-    // `images` will also check for the daemon.
-    std::process::Command::new("docker").arg("images").output().is_ok_and(|o| o.status.success())
-}


Reviews:
user: login: mattsse
body: perhaps we also need to set the version to =0.8.29?
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-14T15:51:48Z
user: login: DaniPopes
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-17T10:56:50Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T11:06:32Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T12:13:07Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T12:13:19Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T12:13:24Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T12:13:36Z
user: login: grandizzy
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-17T12:13:58Z
user: login: zerosnacks
body: lgtm!
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-17T13:51:33Z
user: login: DaniPopes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T07:10:23Z
user: login: DaniPopes
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T07:47:08Z

Review comments:
diff_hunk: @@ -1088,43 +1083,6 @@ impl Config {
     /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists.
     #[allow(clippy::disallowed_macros)]
path: crates/config/src/lib.rs
user: login: DaniPopes
body: rm
created_at: 2025-03-17T10:53:53Z
author_association: MEMBER
diff_hunk: @@ -120,11 +120,7 @@ pub struct BuildOpts {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub build_info_path: Option<PathBuf>,
 
-    /// Use EOF-enabled solc binary. Enables via-ir and sets EVM version to Prague. Requires Docker
-    /// to be installed.
-    ///
-    /// Note that this is a temporary solution until the EOF support is merged into the main solc
-    /// release.
+    /// Build with EOF.
path: crates/cli/src/opts/build/core.rs
user: login: DaniPopes
body: Should mention that this compiles contracts to EOF bytecode
created_at: 2025-03-17T10:54:24Z
author_association: MEMBER
diff_hunk: @@ -901,14 +896,14 @@ impl Config {
 
     /// Adjusts settings if EOF compilation is enabled.
     ///
-    /// This includes enabling via_ir, eof_version and ensuring that evm_version is not lower than
+    /// This includes enabling optimizer, via_ir and ensuring that evm_version is not lower than
     /// Prague.
     pub fn sanitize_eof_settings(&mut self) {
         if self.eof {
+            self.optimizer = Some(true);
+            self.normalize_optimizer_settings();
+
             self.via_ir = true;
-            if self.eof_version.is_none() {
-                self.eof_version = Some(EofVersion::V1);
-            }
path: crates/config/src/lib.rs
user: login: DaniPopes
body: we don't pass the required flags:

https://soliditylang.org/blog/2025/03/12/solidity-0.8.29-release-announcement/

> To try it out with your contract, use --experimental-eof-version 1 on the command line or settings.eofVersion: 1 in Standard JSON and remember to select an EVM version that supports it (--evm-version osaka/settings.evmVersion: "osaka").
created_at: 2025-03-17T10:55:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
path: crates/forge/tests/cli/eof.rs
user: login: DaniPopes
body: 0.8.29; this should at least emit a warning that the current compiler does not support EOF
created_at: 2025-03-17T10:55:48Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
+        r#"
+[COMPILING_FILES] with [SOLC_VERSION]
+[SOLC_VERSION] [ELAPSED]
+Compiler run successful!
+
+Ran 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS])
+[PASS] test_Increment() ([GAS])
+Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]
+
+Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)
+
+"#
path: crates/forge/tests/cli/eof.rs
user: login: DaniPopes
body: this test should check that the bytecode we run is EOF somehow
created_at: 2025-03-17T10:56:43Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
path: crates/forge/tests/cli/eof.rs
user: login: grandizzy
body: doh :man_facepalming: thanks for catching this
created_at: 2025-03-17T11:06:32Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
path: crates/forge/tests/cli/eof.rs
user: login: grandizzy
body: fixed in https://github.com/foundry-rs/foundry/pull/10081/commits/165f64e6e7ca563d40964a016bade30f46ede790
not sure if warn needed as compiler will fail with `Internal compiler error` (added `test_unsupported_compiler` test)if mismatch, wdyt?
created_at: 2025-03-17T12:13:07Z
author_association: COLLABORATOR
diff_hunk: @@ -901,14 +896,14 @@ impl Config {
 
     /// Adjusts settings if EOF compilation is enabled.
     ///
-    /// This includes enabling via_ir, eof_version and ensuring that evm_version is not lower than
+    /// This includes enabling optimizer, via_ir and ensuring that evm_version is not lower than
     /// Prague.
     pub fn sanitize_eof_settings(&mut self) {
         if self.eof {
+            self.optimizer = Some(true);
+            self.normalize_optimizer_settings();
+
             self.via_ir = true;
-            if self.eof_version.is_none() {
-                self.eof_version = Some(EofVersion::V1);
-            }
path: crates/config/src/lib.rs
user: login: grandizzy
body: fixed in https://github.com/foundry-rs/foundry/pull/10081/commits/165f64e6e7ca563d40964a016bade30f46ede790
created_at: 2025-03-17T12:13:18Z
author_association: COLLABORATOR
diff_hunk: @@ -120,11 +120,7 @@ pub struct BuildOpts {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub build_info_path: Option<PathBuf>,
 
-    /// Use EOF-enabled solc binary. Enables via-ir and sets EVM version to Prague. Requires Docker
-    /// to be installed.
-    ///
-    /// Note that this is a temporary solution until the EOF support is merged into the main solc
-    /// release.
+    /// Build with EOF.
path: crates/cli/src/opts/build/core.rs
user: login: grandizzy
body: added in https://github.com/foundry-rs/foundry/pull/10081/commits/165f64e6e7ca563d40964a016bade30f46ede790
created_at: 2025-03-17T12:13:24Z
author_association: COLLABORATOR
diff_hunk: @@ -1088,43 +1083,6 @@ impl Config {
     /// If `solc` is [`SolcReq::Local`] then this will ensure that the path exists.
     #[allow(clippy::disallowed_macros)]
path: crates/config/src/lib.rs
user: login: grandizzy
body: removed in https://github.com/foundry-rs/foundry/pull/10081/commits/165f64e6e7ca563d40964a016bade30f46ede790
created_at: 2025-03-17T12:13:36Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
+        r#"
+[COMPILING_FILES] with [SOLC_VERSION]
+[SOLC_VERSION] [ELAPSED]
+Compiler run successful!
+
+Ran 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testFuzz_SetNumber(uint256) (runs: 256, [AVG_GAS])
+[PASS] test_Increment() ([GAS])
+Suite result: ok. 2 passed; 0 failed; 0 skipped; [ELAPSED]
+
+Ran 1 test suite [ELAPSED]: 2 tests passed, 0 failed, 0 skipped (2 total tests)
+
+"#
path: crates/forge/tests/cli/eof.rs
user: login: grandizzy
body: I updated test to use cast eof decode bytecode in https://github.com/foundry-rs/foundry/pull/10081/commits/165f64e6e7ca563d40964a016bade30f46ede790
created_at: 2025-03-17T12:13:57Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,18 @@
+// Ensure we can run basic counter tests with EOF support.
+forgetest_init!(test_eof_flag, |prj, cmd| {
+    cmd.forge_fuse().args(["test", "--eof", "--use", "0.8.19"]).assert_success().stdout_eq(str![[
path: crates/forge/tests/cli/eof.rs
user: login: DaniPopes
body: this is fine
created_at: 2025-03-20T07:10:23Z
author_association: MEMBER

PR comments:
user: login: grandizzy
created_at: 2025-03-17T09:58:25Z
body: > perhaps we also need to set the version to =0.8.29?

yeah, was actually something I overlooked initially but fixed in https://github.com/foundry-rs/foundry/pull/10081/commits/0118360a92568152737e7c45f8d4cfa5bbefec1b (removing running in docker container)
I also had to remove eof version, if setting it as before test fails with `Error: Unknown key "eofVersion"`  but I think that's OK, maybe there's no such setting in solc? @klkvr wdyt?
author_association: COLLABORATOR
user: login: klkvr
created_at: 2025-03-17T10:09:27Z
body: This setting was in the solc version we were using but it might've changed. From the release announcement it seems like it should still be there

<img width="1277" alt="image" src="https://github.com/user-attachments/assets/0e50086e-55f9-4305-91aa-2d2fbf7b939c" />

We need to make sure that running with `--eof` always produces EOF bytecode

author_association: MEMBER
user: login: zerosnacks
created_at: 2025-03-17T10:34:56Z
body: > > perhaps we also need to set the version to =0.8.29?
> 
> yeah, was actually something I overlooked initially but fixed in [0118360](https://github.com/foundry-rs/foundry/commit/0118360a92568152737e7c45f8d4cfa5bbefec1b) (removing running in docker container) I also had to remove eof version, if setting it as before test fails with `Error: Unknown key "eofVersion"` but I think that's OK, maybe there's no such setting in solc? @klkvr wdyt?

I think it is available under the `--experimental-eof-version` flag

https://github.com/ethereum/solidity/blob/a65229279d769a7b2936aef2aa802d77abbdc343/solc/CommandLineParser.cpp#L50C45-L50C69
author_association: MEMBER
user: login: grandizzy
created_at: 2025-03-20T06:42:33Z
body: @DaniPopes pls rereview
author_association: COLLABORATOR