reth EIP-7928 report: see how a Hackerbane report is structured. Open the report → (opens in a new tab)

· Oscar Chambers

The oksolc compiler assessment: a compiler bug is not a contract bug

The published oksolc assessment compared commit a593391 with solc 0.8.36 and read remediation on 11 October 2026 at f8749e6. Twenty-three findings were solved on a branch the report says is not merged to oksolc main, and HACK-05 and HACK-15 remain risk accepted.

oksolc 0.1.4 is a Zig rewrite of the Solidity compiler. It aims to match solc 0.8.36 on the compile that Foundry and Hardhat already request: via-IR on, the optimizer on, 200 runs. Legacy codegen is refused.

The published write-up is the oksolc compiler security audit. It reads okcontract/oksolc at a59339132a1aecc8594a34cbba6b5a1bffbeeca8, with solc 0.8.36+commit.8a079791 as the reference. The report date is 30 September 2026. Remediation was read later, on 11 October 2026, at f8749e66983f9a0e70837e7d3823e372db5c6901 on okcontract/oksolc-hackerbane. The report says that branch is not merged to okcontract/oksolc main. The Zig unit suite was not rerun for that reading, and the solc crash in HACK-27 was not rerun.

The cover counts are 1 Critical, 15 High, 3 Medium, 6 Low, and 4 Informational, numbered HACK-01 through HACK-29. Twenty-three findings are solved. HACK-05 and HACK-15 are risk accepted and still differ from solc 0.8.36. HACK-17, HACK-19, HACK-27, and HACK-29 are acknowledged.

The bug is in the translator

A contract bug is a property of one program: this source, this call, this broken invariant. A compiler bug is a property of the translator. The same source goes in, and oksolc and solc disagree about whether it compiles, which diagnostic they print, or which bytes come out.

The report scores that directly. Deposit and Yield stay None, because this tree does not custody funds. Wrong deployed bytes are an integrity finding. A program one compiler accepts and the other rejects is an integrity or availability finding. A different diagnostic, with no bytecode on either side, is Low. Notes about solc itself are Informational.

On the path the project documents, the bytes matched. Creation and runtime bytecode matched solc on a 50-source via-IR campaign, optimizer runs 200, Cancun, and on a frozen corpus, 5 of 5. Seaport 1.6 matched, including the one stack-too-deep error both compilers share. On 11 October 2026 the OpenZeppelin 5.6.1 closure in audit/vectors/H-10/consecutive.json (38 files, 1099241 bytes) compiled on oksolc at f8749e6 with 0 errors. All 19 nonempty creation-bytecode objects matched solc 0.8.36, including ERC721ConsecutiveTarget.

On those inputs the deployed bytes were solc's bytes. The findings are the places the translator diverged.

Different bytes for one input

HACK-01 is the Critical finding, score 10, solved. Assembly JSON can hold two .data entries for one 32-byte hash, spelled in different cases. At the review commit, oksolc applied the strings in parse order and solc applied them in sorted key order. The vector ends in bytecode 6003fe22 on oksolc and 6003fe11 on solc. The final data byte is what would be deployed. Commit cb426c8 sorts the keys. oksolc does not ship an import-asm-json command, and its own JSON printer already sorts keys, so a round trip of an oksolc export does not hit HACK-01.

HACK-28 is the other byte-level story, and it started as a shared behavior. require with a named custom error encoded the arguments in source order in both compilers. The same names on revert encoded declaration order. A caller that decodes by parameter name reads the fields swapped, and replacing oksolc with solc 0.8.36 did not change the require case. The finding is Informational, score 0.0. Commit 29da586 encodes the named require arguments in declaration order. The remediation record says the named fixture then differs from solc 0.8.36 via-IR, and marks HACK-28 solved.

One compiler accepts what the other rejects

The High band is mostly an accept/reject split, or oksolc aborting on a source solc compiles. Except HACK-05 and HACK-15, these are solved in the tree read at f8749e6.

HACK-02, solved. oksolc compile followed a symlink that pointed outside the project and emitted bytecode for the outside file, under the in-project name. An import of the same symlink was rejected.

HACK-03, solved. A PUSH value that already started with 0x assembled on oksolc. solc rejected it.

HACK-04, solved. Assembly tag integers were parsed as plain decimal. solc treats a leading zero as octal and a leading 0x as hex, and it rejects an octal token that contains 8 or 9. The same tag text can name a different jump target.

HACK-05, risk accepted, High 7.5. A subassembly key of bare 0x is still SubassemblyIndexNotInteger on oksolc. solc 0.8.36 takes index 0 and emits fe. The remediation record says the bare 0x form stays rejected.

HACK-06, solved. super.f() aborted when the next base declared f without a body and a later base implemented it. solc 0.8.36 compiled the call. The command-line tool printed InternalFailure.

HACK-07, solved. An external call that returns string memory, compiled for Homestead, was accepted. solc rejected it. The oksolc creation bytecode contained STATICCALL. Homestead has no STATICCALL.

HACK-08, solved. string.length was accepted. solc reports a missing member. bytes.length matches on both compilers.

HACK-09, solved. A bare storage push with no call was accepted. solc rejects it because push is not unique.

HACK-10, solved. .selector resolved on an internal function and on an anonymous event. solc rejects both.

HACK-11, solved. i.f.selector was a compile-time constant when i was a state variable. solc rejects that, because a state-variable read is not a constant.

HACK-12, solved. uint256.min and uint256.max compiled on the type name. solc reports a missing member. type(uint256).min matches on both.

HACK-13, solved. Importing a file as a module dropped an overloaded free function, so a call solc compiled was a missing member on oksolc.

HACK-14, solved in f7d50b0. A using-for attachment of every type aborted when the library also had a zero-argument function of the same name. solc compiled the call. Solady 0.1.26 hit the same abort. solc compiled that Solady input, and the library sources without the triggering test compiled on oksolc.

HACK-15, risk accepted, High 7.5. Yul memoryguard(2147483648) still compiles on oksolc, to bytecode 63800000005f5500. solc 0.8.36 returns an internal compiler error and no bytecode. The report says Solidity via-IR sources do not reach this builtin from user assembly. The remediation record says the bound stays below 2^32 - 1.

HACK-16, solved. The OpenZeppelin 5.6.1 Standard JSON from oksolc issue 8 failed in the unused pruner with MissingReference. solc 0.8.36 compiled it. The reduced consecutive-target vector is the one that was compiled again on 11 October, with the bytecode match above.

Same bytecode, different compiler text

HACK-17, Medium, acknowledged. A contract with no external interface functions gets unoptimized IR that omits shift_right_224_unsigned. solc still emits that helper. The helper is dead. Optimized IR and bytecode match. The remediation record says the unused helper stays omitted.

HACK-18, Medium, solved in 65d0a9e. Unoptimized IR named a storage push parameter value_0. solc 0.8.36 names it value0. Runtime bytecode matched on the fixtures.

HACK-19, Medium, acknowledged. A Yul number is spelled differently from solc 0.8.36, including a hexadecimal 2^64 array bound. Creation and runtime bytecode match. A tool that hashes the IR text will disagree with solc. A tool that executes the bytecode will not. The remediation record cites 910b649 and 1935ab2 and says the hexadecimal bound stays.

Diagnostics and JSON shape

HACK-20 through HACK-25 are Low, score 2.5, and solved. Both compilers reject the program, or the bytes and storage slots a deployment would use still match, and the diagnostic or the JSON shape differs.

HACK-20 also reports the conversion error after the named-argument error. solc stops at the first. HACK-21's non-payable constructor message omits the contract name that solc includes. HACK-22 reports .gas() as a deprecation error. solc reports a missing member. HACK-23's output-selection star includes ir and irOptimized. solc's star does not. Bytecode on the fixtures still matched. HACK-24 emits an empty object for types when a storage area has no variables. solc emits null. Slot lists are empty on both sides. HACK-25 reports a parser error for a Yul literal of 2^256. solc reports a type error. For that literal, neither compiler emits bytecode.

HACK-26 is Informational and solved by keeping the rejection. Standard JSON with viaIR: false is UnimplementedFeatureError on oksolc and a successful legacy compile on solc. oksolc emits no legacy bytecode. A Foundry project that points at oksolc and leaves via-IR unset fails the build.

When the reference compiler is the one that breaks

HACK-27, Informational, acknowledged. Tag value 4294967295 makes solc 0.8.36 exit with SIGSEGV. oksolc returns TagTooLarge and emits nothing. There is no solc bytecode to compare. The report records no oksolc change for this row.

HACK-29, Informational, acknowledged with HACK-15. solc aborts Yul optimization when memoryguard's literal is at or above 2^31. A later assert in the same function caps the slot count at 2^32 - 1. The two bounds are not the same expression. The remediation record says oksolc has no 2^31 ceiling. That is the open difference already filed as HACK-15: oksolc compiles the value, and solc returns an internal compiler error.

Seaport 1.6 matched solc, including the shared stack-too-deep error. Solady 0.1.26 aborted in the using-for lookup and compiled on solc. OpenZeppelin 5.6.1 died in the unused pruner at the review commit, and the consecutive-target closure matched solc's creation bytecode after f8749e6. The object of the assessment is the compiler that is supposed to reproduce those bytes.

The finding list, the differential runs, and the 11 October remediation read are in the oksolc report. Compiler reviews of this kind are the work described on blockchain infrastructure audit.