-
Notifications
You must be signed in to change notification settings - Fork 208
Contract to bytecode compilation + E2E testing harness #1161
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a complete end-to-end compilation pipeline that takes Fe source code through to executable EVM bytecode. The main additions are a Solc runner that compiles Yul to bytecode and a contract testing harness built on revm for executing and testing contracts.
Key Changes:
- Added
solc-runnerandcontract-harnesscrates to enable bytecode compilation and EVM execution - Introduced new intrinsics (
calldataload,return_data) andDispatchertrait for contract runtime support - Refactored Yul emitter into modular components (function, statements, control flow, expressions)
- Added
ReturnDataterminator to MIR for raw memory returns
Reviewed Changes
Copilot reviewed 64 out of 65 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| library/core/src/lib.fe | Exports new Dispatcher trait and additional intrinsics for contract runtime |
| library/core/src/intrinsic.fe | Adds calldataload and return_data intrinsics for contract ABI handling |
| library/core/src/dispatcher.fe | Defines minimal Dispatcher trait interface for contract entry points |
| crates/solc-runner/src/lib.rs | New crate that invokes solc to compile Yul into bytecode |
| crates/solc-runner/Cargo.toml | Dependencies for solc runner (contains invalid edition) |
| crates/contract-harness/src/lib.rs | Complete testing harness using revm for contract execution |
| crates/contract-harness/Cargo.toml | Dependencies for contract testing harness |
| crates/codegen/src/yul/emitter/*.rs | Refactored emitter split into focused modules (function, statements, control_flow, expr, util, module) |
| crates/codegen/src/yul/state.rs | Updated to use Rc<Cell<usize>> for shared local counter across cloned states |
| crates/mir/src/lower.rs | Adds ensure_const_expr_values and handles ReturnData terminator |
| crates/mir/src/ir.rs | Adds Calldataload and ReturnData intrinsic opcodes and new terminator variant |
| crates/mir/src/hash.rs | Hash implementation for new ReturnData terminator |
| crates/mir/src/monomorphize.rs | Adds method name prefixing for impl blocks |
| crates/hir-analysis/src/ty/ty_check/*.rs | Caches callable information to avoid redundant trait resolution |
| crates/codegen/tests/fixtures/*.fe | New test fixtures for contract dispatch and intrinsics |
| .github/workflows/main.yml | CI setup to download and cache solc for tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if matches!(value_data.origin, ValueOrigin::Call(..)) { | ||
| docs.push(YulDoc::line(format!("pop({lowered})"))); | ||
| } else { | ||
| docs.push(YulDoc::line(lowered)); |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The variable v0 is not being used in the returned value for this intrinsic. After evaluating the call_expr in line 209, the code checks if it's a ValueOrigin::Call and wraps it with pop(), but for all other value origins it just returns the lowered expression as a line. This means that for non-call expression statements (line 212), we're emitting a bare expression as a statement, which in Yul would just be a value that gets discarded. This seems intentional but could lead to confusing Yul output.
| docs.push(YulDoc::line(lowered)); | |
| // Do not emit a bare expression as a statement; value is discarded. | |
| // Optionally, emit a comment for clarity: | |
| // docs.push(YulDoc::line(format("// discarded value: {lowered}"))); |
| let line = match intr.op { | ||
| IntrinsicOp::Mstore => format!("mstore({}, {})", args[0], args[1]), | ||
| IntrinsicOp::Mstore8 => format!("mstore8({}, {})", args[0], args[1]), | ||
| IntrinsicOp::Sstore => format!("sstore({}, {})", args[0], args[1]), | ||
| IntrinsicOp::ReturnData => format!("return({}, {})", args[0], args[1]), | ||
| _ => unreachable!(), |
Copilot
AI
Nov 20, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing handling for IntrinsicOp::ReturnData case. The function only handles Mstore, Mstore8, and Sstore in the match statement (lines 333-335), but based on the IR changes, ReturnData is also a non-value-returning intrinsic that should be handled here. The unreachable!() at line 337 would be hit if ReturnData intrinsic is processed as a statement.
6b3f87d to
962953d
Compare
962953d to
d95e932
Compare
This builds on top of #1159. It connects our current codegen pipeline that could previously only produce YUL to the solc-runner that takes the yul and compiles it into bytecode.
This also introduces a testing harness that takes Fe source, compiles it, deploys it on an EVM instance and lets us do transactions to interact with the deployed contract to make assertions.
Obviously this side steps quite a few bigger issues (e.g. ad hoc hardcoded dispatcher) but it is really valuable to be able to generate binary code and execute it. This already exposed quite a few issues with the generated YUL and hence helps me to drive the codegen work forward.
It also does a bit of a cleanup for the YUL emitter code.