v26.4.0
Changelog
26.4.0 (2026-09-30)
Features
- Added experimental host-native executable builds for standalone Fe files on
x86-64 Linux and AArch64 macOS. Build Fe with thecraneliftfeature and pass
--backend nativeto produce an executable frompub fn main() -> i32. (#1549) - Track ownership and borrows through tuples, structs, arrays, pointer aliases, and helper calls with a structural borrow checker. Borrow information is preserved when references are returned inside aggregates, so overlapping live borrows are rejected after tuple destructuring as well as after a direct return. (#1557)
- Validate generic calls' ownership and borrow requirements when their concrete trait implementations are selected. Checks that depend on an unresolved implementation remain explicitly pending; executable calls without a concrete implementation or a compiler-provided effect contract are rejected. (#1557)
- Extended the experimental native backend to whole workspaces, including dependencies.
fe test --backend nativeruns tests as native executables, andstd::ioprovides character input and output through the host. (#1565) - Native programs can receive process arguments by declaring
pub fn main(argc: i32, argv: **u8) -> i32and reading them through the bounds-checkedstd::native::Args.std::native::cpu_clock_ticksreturns the process CPU time. (#1566) - Added
std::native::ByteBuffer, an explicitly owned heap byte buffer for native programs with fallible growth that keeps existing data, zero initialization, overlap-safe copying and an explicitrelease. (#1580) - Const functions can take immutable trait providers through
usesand supply them withwith, including forwarding through generic const functions, calls to const trait methods, andrefborrows of a provider such asref selfmethod calls. Mutable effects, type-keyed (storage) effects and extern functions with effects are rejected. (#1582) - Const functions can borrow their own locals and parameters with
mutandref, so helpers that update a value in place, or take amutargument, now evaluate at compile time. Returning a borrow of a local from the function that owns it is rejected during evaluation. (#1582) - Add
core::text::concat_slicefor runtime-length string slices andTextBuilderfor incremental byte-preserving text construction, also available throughstd::text. - Add
std::evm::checksum_addressfor ERC-55 checksummed address formatting. Expand regression coverage of the existingcore::num::isqrtacross power-of-two and maximum-square boundaries. - Add
std::evm::cloneshelpers for ERC-1167 initcode, CREATE/CREATE2 deployment and deterministic address prediction. - Add overflow-safe EIP-150 gas-budget admission and Call::call_with_min_gas, with a caller reserve, prepaid input memory and no automatic returndata copy.
- Added
Call::try_call_intoandCall::try_static_into, which copy at most the capacity of a caller-provided buffer so oversized returndata cannot force a large copy, plusCall::send_valuefor plain value transfers andCall::try_call_rawfor forwarding raw calldata.Call::static,Call::try_staticandAddress::staticnow only need a read-onlyCalleffect. Addedstd::evm::erc165with OpenZeppelinERC165Checkersemantics andAddress::from_word_truncate. - Added
core::num::leading_zerosandcore::num::trailing_zerosforu256. Both return 256 for zero.leading_zeroscompiles to the EVMCLZinstruction (EIP-7939) and to a branch-free bit search for native targets; both functions are also available in constant evaluation. - Added
core::panic_code(code), which reverts with the SolidityPanic(uint256)payload, and thecore::panics::PANIC_*constants (re-exported fromstd::evm::panic). Out-of-boundscore::ptraccesses now revert withPanic(0x32)instead ofINVALID. On native targets the call traps. - Added
core::text(re-exported asstd::text) withconcat,decimal,hex,hex_upperandbase64for buildingDynStringvalues, plusDynString::slice,DynString::from_word_prefix,DynString::zeroedandEqforBytesandDynString. - Added
std::evm::SolSlotfor Solidity storage layouts at runtime slots:read/writeaccess a packed state variable at a byte offset without touching its neighbours (forbool,Address, all integer widths includingsol::Int24etc., andFixedBytes<N>), andread_bytes/read_string/write_bytes/write_stringuse Solidity'sbytes/stringstorage layout. Also addedstd::evm::SolMapping, which derivesmappingslots like Solidity for a runtime root, including nested mappings. Custom-width Solidity ints now implementStorageKey. - Added
std::evm::keccak_words([..])for hashing a fixed list of words andstd::evm::create2_address(deployer, salt, init_code_hash).keccak_packedandencode_packednow write each value directly at its packed width instead of going through thePackedbuilder, which makes them about four times cheaper, and custom-width Solidity ints (sol::Int24etc.) now implementEncodePacked. - Added
std::evm::merklefor keccak Merkle proofs:verify,process_proofandhash_pair_sortedmatch OpenZeppelin'sMerkleProoffor sorted-pair trees, andverify_indexed/process_indexed_proofcheck positional proofs where bitiof the leaf index picks the side at leveli(as in Seaport bulk order signatures). Proofs are read in place from aMemSlice<u256>or a decodedbytes32[]/uint256[](DynArray<Bytes32>/DynArray<u256>). - Added full-precision
mul_div,mul_div_ceil,checked_mul_div,checked_mul_div_ceilandfull_multocore::num. They computea * b / dwith a 512-bit intermediate product, so results are exact whenever the quotient fits in au256.mul_divandmul_div_ceilfail like checked arithmetic (division by zero, or overflow when the quotient does not fit), and thechecked_variants returnNoneinstead.addmodandmulmodare now available ascore::num::addmodandcore::num::mulmodtoo;std::evm::addmodandstd::evm::mulmodremain available. - Added non-reverting token helpers that return a classified
std::evm::TokenCalloutcome (Ok,Reverted,BadReturn,NoCode) so callers can raise their own errors:erc20::try_transfer,try_transfer_fromandtry_approve(Solady/OpenZeppelin semantics, bounded 32-byte returndata copy), plus the newstd::evm::erc721::try_transfer_fromandstd::evm::erc1155::try_safe_transfer_from/try_safe_batch_transfer_from.erc721::check_on_receivedclassifies anonERC721Receivedhook as aReceiverCheck(Accepted,WrongMagic,BadReturn,Reverted,NoCode). - Fields and array elements reached through a temporary pointer, such as
f().valueor(*f()).items[i], can now be assigned, compound-assigned and borrowed withreformut, just like those reached through a pointer variable.
Bugfixes
-
Fix resolution and validation of default type and const arguments, including dependent defaults in generic function and method calls.
Fix compiler panics and incorrect trait dispatch when using functions and enum variant constructors as values. (#1535)
-
Fix copying arrays returned from functions so that modifying a copy no longer changes the original array. This also fixes copies of nested arrays while preserving explicit reference semantics. (#1555)
-
Allow generic constants to contain array repeats with symbolic lengths and indices and use them in branches, enum matches, casts, and type-level expressions. Evaluation now preserves bounds checks, operand failures, and assertion diagnostics through specialization. (#1556)
-
Preserve native
refandmutreferences' addresses, layouts, and aliasing when storing them in pointer slots or aggregate fields and returning them through helpers. Mutations through a stored reference continue to affect the original referent, including when helpers allocate memory while the reference is live. (#1557) -
Fix internal compiler errors in native-reference slot assignments, valid compound assignments, and Copy reads from reference-returning calls. Invalid mutations through shared references and mutable field borrows through immutable owned receivers now produce source diagnostics. (#1557)
-
Reserve
StorageMap's complete hashing buffer before invoking custom key encoders, preventing allocations during encoding from overwriting the key. CustomStorageKeyimplementations must now provideencoded_len(self) -> u256and makewrite_keyreturn()instead of the encoded length. (#1557) -
Reject uses of moved non-Copy values through pointers, including pointers returned by calls and projections into their fields or array elements. Also reject native-reference loads from uninitialized or byte-overwritten slots: typed stores can initialize a reference slot, while zeroing or copying raw bytes cannot establish a valid reference. (#1557)
-
Account for persistent and transient state access during external calls, including callbacks. Live state borrows now conflict with
CALL,DELEGATECALL,CREATE, andCREATE2;STATICCALLconflicts with mutable state borrows while allowing shared state borrows. (#1557) -
Event entries in the generated JSON ABI now include
"anonymous": false, so strict ABI consumers such as Foundry's Alloy parser can deserialize them. (#1562) -
Fixed string literal escapes being kept verbatim. The escapes
\",\\,\n,\rand\tare now decoded, so escaped strings have the correct byte length and contents in constants and generated code. Invalid escapes are reported at their source location. (#1563) -
Fixed repeated effect calls through a temporary trait-effect provider in a
withblock, which could falsely move the provider or update separate copies of it. Every call in the block now uses the same provider. (#1564) -
Fix verification of payable value-returning handlers while requiring ABI wrappers to call their planned return helper and return-type specialization. (#1567)
-
Fix ABI decoding of malformed arrays so overflowing offsets and lengths revert with empty data instead of an arithmetic panic. (#1570)
-
Fix EVM compilation of unchecked signed negation, including wrapping negation of the minimum signed value. (#1571)
-
Fix JSON ABI state mutability so call-local memory effects alone do not prevent a function from being marked pure. (#1572)
-
Include reachable custom errors in generated JSON ABIs, including errors raised through panic_with_value, Result::unwrap(), and overloaded operators. (#1573)
-
Fix a compiler panic during JSON ABI generation for contracts that call generic helpers such as encode_msg_calldata. (#1574)
-
Fixed calls to shared methods on non-Copy fields, such as
frame.memory.capacity(), falsely consuming the field and rejecting later uses of the containing value. (#1581) -
Accept ABI dynamic byte and string payloads without trailing padding while preserving bounded reads and copies, zeroing owned padding, and keeping decoded arrays word-aligned for re-encoding. (#1587)
-
Native tests on macOS no longer fail intermittently with "Operation not permitted" when the runner stops a finished test's process group. (#1590)
-
Fixed compilation of recursive raw pointer types such as
struct Node { value: u256, next: *Node }, which passed type checking but then failed during lowering. Borrow checking of programs that use such types now also terminates. (#1609) -
Allow bounded raw static calls through a read-only Call effect, preserving view mutability in the generated ABI.
-
Calling a function that declares a return value but always diverges (for example one that ends in
core::panic()) in tail position no longer fails Sonatina IR verification with IR0601. -
Comparison operators such as
==and<no longer move a non-Copy right-hand operand;a == bnow borrowsbasa.eq(b)does. -
Diagnose invalid constant declaration types, including oversized inline strings, before constant evaluation instead of reaching a compiler panic.
-
Fix
continueinforloops so the loop moves on to the next element. Previouslycontinuejumped back without advancing, so the loop revisited the same element. -
Fix
fe buildfailing in MIR lowering when emitting an#[event]struct with no fields. Such events now emit a LOG1 withkeccak256("Name()")as the only topic and empty data, matching Solidity. -
Fix a bare call such as
digest(..)inside animplortraitresolving to the enclosing impl's or trait's own function of the same name instead of the module-level function. Like in Rust, functions of an impl or trait are no longer in scope as bare names inside it; call them asSelf::name(..)orself.name(..), and the "undefined variable" error now points this out. Associated consts remain usable unqualified. -
Fix a compiler panic ("optional CTFE fold invariant failed: constant integer exceeds its declared type") when constant evaluation folded bitwise-not or other integer operations whose result has a view type, such as
x == !(0 as u256). Integer views now keep the width and signedness of the viewed integer during constant evaluation. -
Fix a compiler panic when implicitly borrowing a record literal, such as an Address literal used in an equality comparison.
-
Fix an internal borrow-check error when reading a field through a pointer returned by a call, such as
pick(a, b).value. Also fix silently lost writes when amut selfmethod call or awithbinding reaches a value through a temporary pointer, such as(*f()).set(1)orwith ((*f()).counter) { ... }: the call or effect now updates the pointee instead of a copy. -
Fix compilation of errors and events with long names by splitting their generated signature strings into supported chunks.
-
Fix layout checking rejecting types that repeat a generic type through ordinary fields, such as a struct holding
Option<Argument>whereArgumentholdsOption<u256>. Such layouts are finite. Types whose arguments grow, such asGrowing<T>containingGrowing<(T, T)>, are still reported as expanding. -
Fix parsing of generic arguments that start with a qualified path, such as
Wrapped<<T as Model>::Point>. The<<is no longer mistaken for a left shift, so the argument now belongs toWrappedin type position, in expression position, and in a method call such aswrapped.pick<<T as Model>::Point>(value). -
Fix the Solidity ABI encoding of
msgvariants that return a tuple with dynamic elements, such as-> (DynString, Bytes32, Address). The return value is now encoded as a parameter list, like the values of a Solidity function with multiple return values (returns (string, bytes32, address)), instead of as a single tuple behind an extra leading offset word. Typed calls andstd::abi::sol::decode_outputdecode tuple returns the same way, and the generated JSON ABI lists one output per tuple element. This changes the returndata of dynamic tuple returns; static tuples encode the same bytes as before. A single dynamic return such as-> DynStringor an#[abi]struct is unchanged. -
Fixed a compiler panic when resolving associated types on generic functions with implicit effect parameters by preserving each explicit parameter's original source index during lowering.
-
Reject integer expression literals larger than 256 bits during type checking instead of silently truncating them during EVM lowering.
-
Report
U::NameandSelf::Nameas not found when only the bound on a different type parameter declaresName. For example, infn f<T: Arrow, U>(_ x: U::Dom)and intrait Eval<T: Arrow> { fn eval(_ x: Self::Dom) }, the name used to resolve silently toT::Dom. -
Report malformed
sol("...")selector signatures on msg variants (unbalanced parentheses, invalid or empty function names, empty argument types, whitespace) asfe checkerrors instead of silently using the keccak of the malformed string as the selector and failing only later during JSON ABI emission. -
Report overflowing dynamic ABI offsets as decode errors instead of arithmetic panics.
-
Resolve
Self::Nameinside a trait whenNameis an associated type of one of its supertraits, such asSelf::Domintrait Eval: Arrow. Bounds in scope at the reference, such as a method's ownwhere Self: Extra, are considered alongside the inherited ones, and the reference is reported as ambiguous when they name different associated types. Reaching a single declaration through several supertraits is not ambiguous, even when only some of them bind it with an equality. -
Reverts (including
assert!with a message) now compile for native targets, where they trap. Previously they failed with an unsupported-terminator error. -
Run
fe testand the contract test harness under Osaka rules, the EVM revision Fe compiles for. Previously tests executed with Prague rules, so Osaka instructions such asCLZhalted withNotActivated. The Osaka per-transaction gas cap (EIP-7825) is lifted for tests. -
String literals longer than 31 bytes can now be used as tuple parts of
core::keccak,std::abi::solandstd::io::write/writelnarguments, e.g.core::keccak(("OrderComponents(...)", "OfferItem(...)")), which hashes the concatenated bytes. Oversized string literals elsewhere now report the inline string capacity error instead of a compiler invariant failure during constant evaluation. -
Teach the tree-sitter grammar the generic arguments that start with a qualified path, such as
Wrapped<<M as Model>::Point>::new(point). The compiler already accepted these in expression position while the grammar reported an error, so editors flagged valid code. A<<that is not followed by a qualified path is still a left shift. -
The init code a contract deploys with
create<B>/create2<B>(and reads withContract::init_code_offset/len) is now exactly theB.binartifact thatfe buildwrites forB. Previously the embedded copy ofBwas compiled together with the embedding contract, so shared helpers, symbol names and inlining budgets could change it, and CREATE2 addresses derived off-chain fromB.bindid not match the deployed address. Each contract is now compiled on its own and embedded as its final bytes, which also makes a contract's bytecode independent of other contracts in the same file. -
FixedBytes<N>(Bytes1throughBytes32) now implementsCopy, so abytesNvalue such as a conduit key can be read from a view parameter or used more than once without a move conflict. -
decode_bytes_view_at(anddecode_bytes_view) now revert with empty returndata, like Solidity's ABI decoder, instead of panicking withPanic(0x11)when an offset or length is so large that its sum with the payload position overflows. -
fe fmtno longer joins a bare qualified first generic argument onto the opening<, which turnedWrapped< <T as Model>>intoWrapped<<T as Model>>and changed how it parsed. It now formats asWrapped< <T as Model> >.
Performance improvements
- Reduced the cost of
DynArray::getfor single-word static elements by relying on the frame validation already done at construction. Bounds checks and canonical-value validation are unchanged. In a Merkle proof benchmark this cuts gas for a 16-sibling proof by about 46% and runtime bytecode by about 28%. (#1559) - Constant string values, such as ERC-20
name()andsymbol()getters, are now folded during ABI encoding instead of computing their length at runtime. In an ERC-20 benchmark this shrinks runtime bytecode by about 21% and cuts the gas ofname()from 3,861 to 857. (#1560) - Helper functions reached from several recv arms or modules are now emitted once instead of once per caller, which substantially shrinks contracts with many entry points. (#1618)
- Reduced compile times by caching and sharing work in semantic analysis, trait solving and borrow checking, and by lowering only the function parameters a caller needs.
- Reuse the outcome of a speculative parse at a position instead of repeating it. Disambiguating generic arguments from shifts and comparisons re-parsed the same nested syntax once per enclosing level, so deeply nested arguments such as
Wrap<<Wrap<<T as Model>::Point> as Model>::Point>cost roughly five times more per level of depth. A 12-level type took about 96 seconds to parse and now takes milliseconds. Incomplete and malformed nesting, which is what an editor sees while the code is still being typed, is reused the same way.
Internal Changes - for Fe Contributors
- Rebuild the embedded
coreandstdlibraries when a file is added toingots/coreoringots/std, including in builds that use a compiler cache. (#1582)