| # | Solver | huber | Submission |
|---|---|---|---|
| 1 | steventhornton | 4,412,972,275 | Download |
Implement the most accurate standard normal cumulative distribution function
you can, in Solidity, over the domain with
, using 18-decimal fixed-point
(PRB-Math SD59x18).
A valid submission compiles under a pinned toolchain, stays inside a strict opcode allowlist and a 512-byte bytecode budget, hits both endpoints exactly, and never reverts on a domain input. Accuracy decides the ranking; gas only breaks an exact tie.
What you submit
A single Solidity source file (not bytecode) defining exactly:
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.19;
import { SD59x18 } from "@prb/math/src/SD59x18.sol";
library CDFImpl {
function _cdf(SD59x18 x) internal pure returns (SD59x18) {
// your implementation, for x in [0, b]
}
}
The verifier compiles it against a fixed, canonical
NormalCDF
contract that you cannot modify. That contract exposes the public entry point
cdf(SD59x18) and calls your _cdf for x in . Outside it supplies
the exact tail values and the reflection identity ; those
regions are fixed and are not scored.
You MUST import PRB-Math's SD59x18 exactly as
shown, and no other external import is allowed. Your own internal helpers and constants
belong in the same file. Inside _cdf you may do anything the
Constraints allow: PRB-Math's math functions are optional, and
dropping to raw int256 arithmetic is fine.
Domain and encoding
A ULP (unit in the last place) is the smallest representable step at 18 decimals:
, the integer 1.
- 18-decimal fixed point: a real is the integer .
- Domain: with (integer
8835109788175395786). This is one ULP below , the smallest input whose rounds to at 18 decimals. -
500000000000000000. -
999999999999999999(that is ).
Compilation (fixed)
The verifier compiles with, exactly:
- solc 0.8.33 (solc-js build
0.8.33+commit.64118f21.Emscripten.clang), with--optimize --optimize-runs 200,--evm-version osaka,--via-ir, and--no-cbor-metadata. solc-js is a pure-JS build of that exact compiler, so the bytecode is reproducible from the pinned npm version with no external solc binary. - PRB-Math v4.1.2, installed from npm (
@prb/math@4.1.2).
Constraints
Throughout, b = 8.835109788175395786 is the right endpoint of the domain, defined
under What you submit.
Validity rules (any failure means invalid)
- Compiles under the pinned toolchain and produces a deployable
NormalCDFwithcdf(int256) pure. - Opcode allowlist: the whole deployed runtime uses only the opcodes listed
below. It must be a pure computation: no storage, block or transaction context,
external code, gas introspection, logs, calls, contract creation, or
KECCAK256. Checked statically over the whole runtime, and again dynamically under an opcode-recording tracer on a small diverse subset of calls, so a banned opcode hidden from the static sweep is still caught when it executes. - Bytecode size at most
512bytes, on the whole deployed runtime. - Endpoints (exact):
cdf(0) == 500000000000000000andcdf(b) == 999999999999999999. - Monotonic: the sampled outputs, sorted by
x, are non-decreasing. The check is on theNsample points only; monotonicity over the whole domain is infeasible to test. - In range: every sampled output lies in
[500000000000000000, 999999999999999999]. - Total:
cdfreverts on no sampled input and runs out of gas on none. Like rules 5, 6 and 8 this is checked over theNsample points, not proved over the whole domain. - Gas: max execution gas per call over the sample at most
1024(gas_used - 21000 - calldata_cost). - No Yul
verbatim: the source must not use the Yulverbatimbuiltin, which injects raw bytecode that would bypass the opcode allowlist. Ordinary inlineassembly { }is allowed. - Source size at most
65536bytes.
Opcode allowlist
The canonical list is verifier/opcodes.json, and it is
closed-world: anything not listed, including opcodes added by future hardforks, is
rejected. CALLVALUE is allowed only because solc emits it, with ISZERO and revert,
as the non-payable guard on every non-payable function.
| Group | Opcodes |
|---|---|
| Halt / return | STOP RETURN REVERT INVALID |
| Arithmetic | ADD MUL SUB DIV SDIV MOD SMOD ADDMOD MULMOD EXP SIGNEXTEND |
| Comparison / bitwise | LT GT SLT SGT EQ ISZERO AND OR XOR NOT BYTE SHL SHR SAR |
| Read calldata | CALLDATALOAD CALLDATASIZE CALLDATACOPY |
| Non-payable guard | CALLVALUE |
| Stack / memory / local flow | POP MLOAD MSTORE MSTORE8 MSIZE MCOPY JUMP JUMPI JUMPDEST PC PUSH0 |
| Push | PUSH1 .. PUSH32 |
| Dup | DUP1 .. DUP16 |
| Swap | SWAP1 .. SWAP16 |
Score
Two metrics, ranked lexicographically. Accuracy always decides; gas only breaks an exact accuracy tie.
- huber (minimize) - the Huber loss of the output-ULP error against the banker's-rounded (round-half-even) reference, over a seeded sample of the domain.
- gas (minimize) - worst-case execution gas over that same sample
(
gas_used - 21000 - calldata_cost).
huber
Per point, the error and its Huber term are:
with knee ULP. The reported score is over points, rounded to the nearest integer. Scores run to , so a sub-1-ULP difference is an exact tie and gas decides.
Huber rather than plain RMSE keeps the score robust: errors up to count quadratically, but the rare deep-tail spikes count only linearly, so a handful of large errors cannot dominate.
The sample
The output range is split into 10 equal-width bands, and each band
gets the same number of points, drawn uniformly in within the band (the band edges
are grid(probit) of the boundaries, ends pinned to 0 and b). Equal weight per band
means the deep tail, out to , is exercised as much as the head rather than
starved.
The draw uses a fixed public seed, the number 42, the same for every submission, so scoring is deterministic and reproducible. That is also what lets gas break an exact accuracy tie: two submissions draw the identical sample.
Ranking
A new record must be strictly better on the first metric where it differs from the current best. Any accuracy gain, however small, outranks any gas gain; gas only separates submissions whose accuracy is exactly equal, for example the same approximation with and without an accuracy-neutral dead branch. A submission that ties on both metrics does not displace the incumbent, so exact ties go to the earliest submission. There is no minimum-improvement margin.
Every parameter above (, the seed, the band edges, the Huber knee, the domain, and
the size, gas and source-size caps) is frozen in
verifier/config.ts.
Verifier
Compiles your CDFImpl against the fixed NormalCDF wrapper under the pinned
toolchain, enforces every rule in Constraints, then scores the
result. It enforces those rules and nothing more: the verifier is canonical, it IS the
spec.
Determinism and duplicates
- Sample seed. The sample is drawn by a
SHA-256counter-mode PRNG from a fixed public seed, the number 42 as a 32-byte value (SAMPLE_SEED_HEXinverifier/config.ts), the same for every submission. Scoring is therefore deterministic and reproducible. - Fingerprint (the dedup key).
SHA-256of your deployed runtime bytecode with the trailing CBOR metadata stripped (the pin includes--no-cbor-metadata, and any trailer is stripped as a fallback). A submission whose runtime bytecode is byte-identical to an earlier one is rejected; it would score identically and lose the earliest-wins tie anyway. Only real logic changes move the runtime bytecode, so comments, whitespace and formatting cannot dodge dedup.
The verifier is the canonical spec. View verifier source →
steventhornton