BL127 is a number format and a math library for Solidity. A value is stored as value × 2^128 in an int256, so it keeps 38 correct digits at 1 where WAD keeps 18, and the same Yul source ships two ways: a library you inline, and a deployed contract you call. The two are fuzzed equal on every int256, reverts included.
Install
The import path is the same however you install it:
# Foundry, from git
forge install securedataresearch/blockulator
echo "@blockulator/contracts/=lib/blockulator/contracts/src/" >> remappings.txt
# npm, yarn or pnpm (Hardhat needs no remapping; Foundry reading node_modules needs this one)
npm install @blockulator/contracts
echo "@blockulator/contracts/=node_modules/@blockulator/contracts/" >> remappings.txt
# Soldeer
forge soldeer install blockulator~<version>
echo "@blockulator/contracts/=dependencies/blockulator-<version>/" >> remappings.txt
import {BL127Lib} from "@blockulator/contracts/bl127/BL127Lib.sol";
contract CompoundExample {
function accrue(int256 principalWad, int256 rateWad, uint256 steps)
external pure returns (int256 balanceWad)
{
int256 b = BL127Lib.fromFixed18(principalWad);
int256 growth = BL127Lib.add(BL127Lib.ONE, BL127Lib.fromFixed18(rateWad));
for (uint256 i = 0; i < steps; i++) {
b = BL127Lib.mul(b, growth);
}
return BL127Lib.toFixed18(b);
}
}
This example is compiled and tested in test/examples/ against the ladder's compound scenario. Compile with via_ir = true in foundry.toml: through the IR the inline ops come within a few percent of the deployed contract's gas, and on the legacy pipeline the heavy ops cost 5–14% more (profile.md).
Typed API
BL127Type.sol is opt-in: type BL127 is int256, with + - * /, unary - and < > <= >= == != bound to the library, and every other op attached (x.exp(), a.mulUp(b)). bl127FromWad and toWadDown / toWadUp convert unsigned WADs, bl127FromInt integers, and ONE and PI are typed constants. Each function only re-types its arguments, so the words and reverts are BL127Lib's, checked under fuzzing in test/typed/. Through the IR every op costs the same gas as the raw call; under the legacy pipeline the wrapper is one more internal call, 37 to 114 gas.
import {BL127, ONE, PI, bl127FromWad} from "bl127/BL127Type.sol";
BL127 b = bl127FromWad(1.5e18) * ONE.exp(); // 1.5e; * is BL127Lib.mul
BL127 r = (b - ONE).ln() / PI + -ONE; // operators and attached ops
bool inside = r >= -ONE && r <= ONE;
uint256 paid = b.toWadDown(); // 4077422742688567853, floored
The format
The range is ±1.7 × 1038. With 128 bits after the point, BL127 keeps 38 digits at 1, 32 at 10−6 and 8 at 10−30. ONE is 2^128, every op takes and returns this one type, and fromFixed18 and toFixed18 convert to and from WAD.
Errors are Overflow(), DivisionByZero() and OutOfRange(). Nothing wraps.
The deployed contracts
Calling the deployed contract costs one staticcall, about 600 gas more than inlining on simple ops like add and mul. On the heavy ops (exp, exp2, exp10, sinh, cosh and both Lambert W branches) it is cheaper than inlining, by 50 to 620 gas (gas.md). It is also the way to use BL127 from an eth_call, where gas is free.
- network
- sepolia (chain id 11155111)
- address
- 0x0DF738d125677550a80A46BaF254386C66f96E0d
- verified
- Sourcify, Blockscout. Etherscan has no Yul path.
Deployment record
- deployer
- 0x4e59b44847b379578588920cA78FbF26c0B4956C (the CREATE2 proxy)
- salt
- "FP127 v9" = 0x4650313237207639000000000000000000000000000000000000000000000000
- init code hash
- 0x3c663aca27b46991b0b81c2e1056ec9b270da4b37b5ae1944b5f40a79dbb5316
- runtime
- 3728 bytes, keccak 0x2841130456eac80bb73cf869aded1898e3acaaaa2c36d385f076145a7695f6ee
- compiler
- Yul, solc 0.8.38+commit.1835d247, optimizer 200 runs, osaka
- deployed
- block 12017693, source commit 80d0656
- record
- BL127.md, bl127.json
BL127 v9 (Blockulator 3.0) is five contracts from one source, one idea each. The record above is the BL127 contract's; BL127.md has all five.
| contract | what it holds | address | ops |
|---|---|---|---|
| BL127 | the number | 0x0DF738d125677550a80A46BaF254386C66f96E0d | add sub mul div inv sqrt cbrt hypot gavg; neg abs sign; min max clamp; floor ceil round frac; avg dist zeroFloorSub lerp; factorial; mulDown mulUp divDown divUp; fromFixed18 toFixed18 toFixed18Down toFixed18Up |
| BL127E | the elementary functions, on one exp/log kernel | 0xc16F4C45B78D90A4D4C030cABF4f2A34c1Fb0808 | exp exp2 exp10 expm1 ln log2 log10 log2Up log1p pow powu compound |
| BL127H | the hyperbolic functions | 0xf2F99947aD1cC370bD5388a2997A5E4963a0B27B | sinh cosh tanh atanh |
| BL127N | the normal distribution | 0xAA7Cd9635aE8630379636F533f23DBf2C193d296 | erf erfc normCdf |
| BL127S | Lambert W | 0xB9C2583a7Ad2251444DC7752658B6C1107C25036 | lambertW0 lambertWm1 |
Each object has its own interface (IBL127.sol, IBL127E.sol, IBL127H.sol, IBL127N.sol, IBL127S.sol) and ABI; ops.json names each op's object. The inline library holds all 53 ops.
They have no storage, no owner and no upgrade path: a new version gets a new salt and a new address, and v1 to v8 (one contract each) stay deployed at the addresses in the record. It is built for evmVersion osaka and needs the CLZ opcode (EIP-7939), so it runs on Ethereum, Sepolia and the other chains with CLZ, but not on zkSync Era, Avalanche C-Chain or Polygon zkEVM (the list). What changed in each version is in the changelog.
sqrt(2) from the command line:
cast call 0x0DF738d125677550a80A46BaF254386C66f96E0d "sqrt(int256)(int256)" \
680564733841876926926749214863536422912 \
--rpc-url https://ethereum-sepolia-rpc.publicnode.com
# 481231938336009023090067544955250113854
python3 -c "print(481231938336009023090067544955250113854 / 2**128)"
# 1.4142135623730951
From Solidity:
import {IBL127} from "bl127/IBL127.sol";
contract StaticcallExample {
IBL127 public constant BL127 = IBL127(0x0DF738d125677550a80A46BaF254386C66f96E0d);
int256 internal constant ONE = int256(1) << 128;
function accrue(int256 principalWad, int256 rateWad, uint256 steps)
external view returns (int256 balanceWad)
{
int256 b = BL127.fromFixed18(principalWad);
int256 growth = BL127.add(ONE, BL127.fromFixed18(rateWad));
for (uint256 i = 0; i < steps; i++) {
b = BL127.mul(b, growth);
}
return BL127.toFixed18(b);
}
}
Integrating
Two rules, each from the replay of a real exploit. The integration page has the replays, the code in both formats and the edge-op tables.
- Store the state in the format. Keep a running balance, index or accumulator as an BL127 word, and round to token units only on the way out. In Bunni v2's replay this alone turns a balance drained from 28 wei to 4 into 35.6 wei, within 10−37 of exact.
- Round toward the protocol at the token edge. Floor what you pay out and ceil what you take in:
toFixed18DownandtoFixed18Up,mulDownandmulUp,divDownanddivUp. Under this rule Resupply's borrow against 1 wei of collateral reverts, and Balancer V2's charge matches the exact one on every call.
The ops
Each op's worst-case precision against its gas. Hover a dot for the op.
| op | what | domain | reverts | bits (min/mean) | gas, object | gas, inline | bytes inline |
|---|---|---|---|---|---|---|---|
| fromFixed18(int256)proven 0x9aea0a13 |
Convert an 18-decimal fixed-point value to 127.128. | |x| < 2^127 | OutOfRangeOutOfRange() if |x| >= 2^127 |
131/191 | 858 | 140 | 86 |
| toFixed18(int256)proven 0xa2745c74 |
Convert a 127.128 value to 18-decimal fixed-point. | any x | never | 181/199 | 877 | 121 | 54 |
| toFixed18Down(int256)proven 0x32154abe |
Convert a 127.128 value to 18-decimal fixed-point, rounding toward negative infinityConvert a 127.128 value to 18-decimal fixed-point, rounding toward negative infinity: toFixed18 under the name that says its direction. For an amount a protocol pays out. |
any x | never | 181/199 | 765 | 160 | 77 |
| toFixed18Up(int256)proven 0x27066ea4 |
Convert a 127.128 value to 18-decimal fixed-point, rounding toward positive infinityConvert a 127.128 value to 18-decimal fixed-point, rounding toward positive infinity: ceil(x * 1e18 / 2^128). For an amount a protocol charges. |
any x | never | 182/201 | 874 | 185 | 92 |
| add(int256,int256)proven 0xa5f3c23b |
a + b. Reverts with Overflow on signed overflow. | any a, b | OverflowOverflow() on signed overflow |
exact | 682 | 128 | 75 |
| sub(int256,int256)proven 0xadefc37b |
a - b. Reverts with Overflow on signed overflow. | any a, b | OverflowOverflow() on signed overflow |
exact | 741 | 140 | 75 |
| mul(int256,int256)proven 0xbbe93d91 |
floor(a * b / 2^128), the 127.128 product. | any a, b | OverflowOverflow() when a·b / 2^128 does not fit in int256 |
132/232 | 862 | 267 | 119 |
| div(int256,int256)proven 0x43509138 |
a / b in 127.128, truncated toward zero. | b != 0 | DivisionByZero, OverflowDivisionByZero() when b == 0; Overflow() when the quotient does not fit |
129/193 | 1,047 | 413 | 480 |
| mulDown(int256,int256)proven 0x4b8d10e0 |
floor(a * b / 2^128), toward negative infinity: mul under the name that says its direction. | any a, b | OverflowOverflow() when a·b / 2^128 does not fit |
131/229 | 803 | 315 | 128 |
| mulUp(int256,int256)proven 0x1aabb2f7 |
ceil(a * b / 2^128), toward positive infinity. | any a, b | OverflowOverflow() when the rounded product does not fit |
129/226 | 913 | 337 | 243 |
| divDown(int256,int256)proven 0xddb29e92 |
a / b in 127.128, rounded toward negative infinitya / b in 127.128, rounded toward negative infinity. div truncates toward zero, which rounds a negative quotient up; this does not. |
b != 0 | DivisionByZero, OverflowDivisionByZero() when b == 0; Overflow() when the quotient does not fit |
128/188 | 1,030 | 476 | 498 |
| divUp(int256,int256)proven 0xe2bec1fe |
a / b in 127.128, rounded toward positive infinity. | b != 0 | DivisionByZero, OverflowDivisionByZero() when b == 0; Overflow() when the quotient does not fit |
129/190 | 1,115 | 479 | 499 |
| exp2(int256) 0xdbbb06d2 |
2^x, rounded to nearest. | x < 127; returns 0 below -129 | OverflowOverflow() for x >= 127 |
130/217 | 2,906 | 2,712 | 5,637 |
| exp(int256) 0xe46751e3 |
e^x, rounded to nearest. | x <= 88; returns 0 below -90 | OverflowOverflow() for x > 88 |
130/207 | 3,058 | 2,835 | 5,794 |
| exp10(int256) 0x4c0dafa2 |
10^x, rounded to nearest. | 10^x < 2^127 (x below about 38.23) | OverflowOverflow() above that |
131/217 | 3,171 | 3,081 | 5,825 |
| log2(int256) 0xe0d68737 |
log2(x) for x > 0, rounded to nearest. | x > 0 | OutOfRangeOutOfRange() for x <= 0 |
130/233 | 2,178 | 1,723 | 8,337 |
| ln(int256) 0x8e6f2353 |
ln(x) = log2(x) * ln(2) for x > 0, rounded to nearest. Reverts with OutOfRange otherwise. | x > 0 | OutOfRangeOutOfRange() for x <= 0 |
130/135 | 2,321 | 1,944 | 8,605 |
| log10(int256) 0xb47ca3c7 |
log10(x) = log2(x) / log2(10) for x > 0, rounded to nearest. Reverts with OutOfRange otherwise. | x > 0 | OutOfRangeOutOfRange() for x <= 0 |
130/135 | 2,360 | 1,934 | 8,605 |
| log2Up(int256) 0xa1985bb6 |
ceil(log2(x)) for x > 0: msb(x) - 128, plus one unless x is a power of two. Exact. | x > 0 | OutOfRangeOutOfRange() for x <= 0 |
exact | 815 | 209 | 106 |
| sqrt(int256)proven 0x665df460 |
sqrt(x) for x >= 0. | x >= 0 | OutOfRangeOutOfRange() for x < 0 |
129/198 | 1,221 | 469 | 358 |
| pow(int256,int256) 0x92b0c5b2 |
x^y = 2^(y * log2(x)) for x >= 0, rounded to nearest. | x >= 0 | OutOfRange, OverflowOutOfRange() for x < 0; Overflow() when the result does not fit |
130/181 | 4,185 | 3,317 | 15,227 |
| powu(int256,int256) 0xb8846d69 |
x^floor(n) for an integer exponent, rounded to nearestx^floor(n) for an integer exponent, rounded to nearest: binary exponentiation on a 256-bit mantissa with a separate binary exponent, rounded once; when that mantissa's error bound (k + 5) 2^-255, k = |n|, straddles a rounding midpoint, the same product on a 512-bit mantissa decides (within (3k + 2) 2^-510 relative, under 2^-237 ULP below k = 2^16; exact at every tie). From k = 2^16, pow(|x|, n) with the sign restored. Correctly rounded, the magnitude's ties away from zero. Exact whenever the true value is representable. Negative n inverts the mantissa, so x^-n is as accurate as x^n. |
any x, any integer n = floor(n); the result must fit | Overflow, DivisionByZeroOverflow() when x^n does not fit; DivisionByZero() for 0^n with n < 0 |
130/204 | 6,125 | 5,252 | 16,406 |
| compound(int256,int256) 0xa31e7db8 |
(1 + rate)^floor(n)(1 + rate)^floor(n): n periods of compounding at rate, rounded to nearest, so a per-second rate over years keeps every bit the format holds. 1 + rate is exact in 127.128 (the sum of two grid values), so nothing is lost before the exponentiation; on an 18-decimal grid the rate itself would have been the first loss. |
rate > -1 and 1 + rate fits; any integer n = floor(n) | Overflow, DivisionByZeroOverflow() when 1 + rate or the result does not fit; DivisionByZero() for rate == -1 with n < 0 |
130/208 | 4,006 | 3,078 | 16,932 |
| cbrt(int256) 0xd4b6188c |
Cube root, defined for all x: cbrt(-x) = -cbrt(x)Cube root, defined for all x: cbrt(-x) = -cbrt(x). Rounded to nearest (decided exactly); exact for perfect cubes. |
any x | OverflowOverflow() only at -2^255 |
130/164 | 2,452 | 1,936 | 600 |
| gavg(int256,int256) 0xa13ed853 |
The geometric mean sqrt(a b), rounded once to nearest from the exact 512-bit pro…The geometric mean sqrt(a b), rounded once to nearest from the exact 512-bit product; two negatives give the positive root. |
a·b >= 0 | OutOfRange, OverflowOutOfRange() if a and b have opposite signs (both nonzero); Overflow() only for a = b = -2^255 |
131/210 | 1,625 | 1,003 | 566 |
| hypot(int256,int256) 0xc479210c |
sqrt(a^2 + b^2), rounded once to nearest from the exact sum of squares (two words); no intermediate overflows. | any a, b; the result must fit | OverflowOverflow() when sqrt(a^2 + b^2) rounds past the largest word |
131/183 | 1,689 | 1,164 | 549 |
| inv(int256)proven 0x3a02c983 |
1 / x. Reverts with DivisionByZero for x == 0. | x != 0 | DivisionByZero, OverflowDivisionByZero() for x == 0; Overflow() when 1/x does not fit |
exact | 954 | 242 | 135 |
| abs(int256) 0x1b5ac4b5 |
|x|. Reverts with Overflow for x == -2^255, which has no positive. | any x | OverflowOverflow() at -2^255 |
exact | 681 | 140 | 74 |
| neg(int256) 0x54a53dc1 |
-x. Reverts with Overflow for x == -2^255. | any x | OverflowOverflow() at -2^255 |
exact | 712 | 116 | 70 |
| sign(int256) 0xae610b48 |
-1, 0 or 1 in 127.128. | any x | never | exact | 737 | 75 | 8 |
| min(int256,int256) 0x29aa9cbe |
Signed minimum. | any a, b | never | exact | 935 | 126 | 51 |
| max(int256,int256) 0x81fe5786 |
Signed maximum. | any a, b | never | exact | 1,037 | 125 | 51 |
| clamp(int256,int256,int256) 0x7b8d0f0c |
min(hi, max(lo, x)). If lo > hi the result is hi, as in the Huff. | any x, lo, hi | never | exact | 760 | 241 | 76 |
| avg(int256,int256) 0xebf274be |
floor((a + b) / 2) without intermediate overflow. | any a, b | never | 129/234 | 857 | 114 | 46 |
| zeroFloorSub(int256,int256) 0xff78cf2d |
max(0, a - b), with the subtraction checked. | any a, b | OverflowOverflow() if a - b does not fit |
exact | 1,032 | 212 | 101 |
| dist(int256,int256) 0x2110b689 |
|a - b|, checked. | any a, b | OverflowOverflow() if a - b does not fit |
exact | 992 | 289 | 122 |
| lerp(int256,int256,int256) 0x84e13b60 |
a + t * (b - a), checked. t is not clamped to [0, 1]. | any a, b, t | OverflowOverflow() on any intermediate that does not fit |
exact | 986 | 477 | 219 |
| floor(int256)proven 0xda3ff648 |
Largest integer <= x. Clearing the fraction bits of a two's complement word is a floor for both signs. | any x | never | exact | 744 | 72 | 17 |
| ceil(int256)proven 0x42fba7ae |
Smallest integer >= x, as -floor(-x). Reverts with Overflow when that integer is 2^127, which does not fit. | any x | OverflowOverflow() when the result is 2^127 |
exact | 834 | 147 | 90 |
| frac(int256) 0xae2785ac |
x - floor(x), always in [0, 1). | any x | never | exact | 767 | 69 | 16 |
| round(int256)proven 0x58b4370b |
Nearest integer, halves rounded up: floor(x + 1/2). Checked. | any x | OverflowOverflow() when the result is 2^127 |
exact | 884 | 212 | 103 |
| factorial(int256) 0x5ad78e8a |
floor(n)! for 0 <= n < 34. 33! is the largest that fits. | 0 <= n < 34 | OutOfRangeOutOfRange() outside that |
exact | 880 | 400 | 864 |
| lambertW0(int256) 0x6de23d34 |
Principal branch of the Lambert W function, W(x) e^W(x) = x, for x >= -1/e, rounded to nearest. | x >= -1/e | OutOfRangeOutOfRange() below -1/e |
130/134 | 7,947 | 8,304 | 12,374 |
| lambertWm1(int256) 0xae94abc8 |
Secondary real branch of the Lambert W function, W(x) e^W(x) = x with W(x) <= -1…Secondary real branch of the Lambert W function, W(x) e^W(x) = x with W(x) <= -1, for -1/e <= x < 0, rounded to nearest. |
-1/e <= x < 0 | OutOfRangeOutOfRange() outside that |
130/131 | 7,882 | 8,156 | 12,050 |
| log1p(int256) 0x8552a5b0 |
ln(1 + x), rounded to nearestln(1 + x), rounded to nearest. The sum 1 + x is exact in 127.128, so this is ln of an exact argument, rounded once; it exists because on an 18-decimal grid the sum is where a small x loses its digits. |
x > -1 | OutOfRange, OverflowOutOfRange() at or below -1; Overflow() if 1 + x does not fit |
130/132 | 2,526 | 2,064 | 8,692 |
| expm1(int256) 0x973fe005 |
e^x - 1, rounded to neareste^x - 1, rounded to nearest: on the 2^-128 grid the one is exact, so this is exp(x) - 1 word for word, a small x by its own series for gas: 27 terms at 2^-192 for |x| <= 1/16. |
x <= 88; returns -1 below -90 | OverflowOverflow() for x > 88 |
130/153 | 3,252 | 2,996 | 6,207 |
| sinh(int256) 0x17cdc976 |
sinh(x), correctly rounded (to nearest, ties away from zero)sinh(x), correctly rounded (to nearest, ties away from zero): (u + u/(1+u)) / 2 with u = e^|x| - 1 from one double-word exp at 2^-192, u/(1+u) = 1 - e^-|x| by one division, the difference taken before the one rounding; when that value's bound reaches the rounding boundary, e^|x| and e^-|x| in two words. |
|x| <= 88 | OverflowOverflow() beyond that |
130/149 | 4,196 | 4,109 | 6,392 |
| cosh(int256) 0xaa13e96a |
cosh(x), correctly rounded (to nearest, ties up)cosh(x), correctly rounded (to nearest, ties up): 1 + u (u/(1+u)) / 2 with u = e^|x| - 1 from one double-word exp at 2^-192 and u/(1+u) = 1 - e^-|x| by one division, which is (e^|x| + e^-|x|) / 2 summed before the one rounding; when that value's bound reaches the rounding boundary, e^|x| and e^-|x| in two words. cosh(0) is exactly one. |
|x| <= 88 | OverflowOverflow() beyond that |
129/175 | 4,218 | 4,073 | 6,382 |
| tanh(int256) 0x5e1f05c7 |
tanh(x), correctly rounded (to nearest, ties away from zero)tanh(x), correctly rounded (to nearest, ties away from zero). For |x| < 1/4, u / (u + 2) with u = e^(2|x|) - 1 at 2^-192; up to |x| < 46, (1 - w) / (1 + w) with w = e^(-2|x|) at 2^-192; one division and one rounding, or, when the value's bound reaches the rounding boundary, (1 - w) / (1 + w) in two words. Beyond, exactly +-1, the nearest word. |
any x | never | 130/216 | 4,205 | 3,975 | 6,715 |
| atanh(int256) 0xc6285a68 |
atanh(x) = log1p(2|x| / (1 - |x|)) / 2, correctly rounded (to nearest, ties away…atanh(x) = log1p(2|x| / (1 - |x|)) / 2, correctly rounded (to nearest, ties away from zero): the quotient at 2^-192 by one division, one unrounded logarithm of 1 + it, scaled by ln2/2; when that value's bound reaches the rounding boundary, the double-word l2p of 1 + |x| and 1 - |x|, and when that one's does, their logarithms in two words. |
|x| < 1 | OutOfRangeOutOfRange() at or beyond +-1 |
130/131 | 3,268 | 2,944 | 11,158 |
| erf(int256) 0x7a959e36 |
The error functionThe error function. |x| <= 4 by the series, |x| > 4 as 1 - erfc(x) by the tail polynomial, both at 2^-192; one rounding when the value's bound (2^45.6 and 2^20 units of 2^-192, erf_model.py) clears the rounding boundary, else BL19's wide second stage (erfw127): correctly rounded. +-1 exactly from |x| >= 9.375, where 1 - erf is below 2^-130.8 and the subtraction rounds to one. |
any x | never | 132/186 | 7,676 | 7,559 | 8,849 |
| erfc(int256) 0xa79456da |
The complementary error function 1 - erf(x), computed directly for x > 4 so the …The complementary error function 1 - erf(x), computed directly for x > 4 so the tail keeps its digits down to the format's floor (erfc(9.3) is one ULP; zero from 9.375, where it is below 2^-130.8); 2 - erfc(-x) for x < 0. One rounding, or erf's second stage when the bound reaches the boundary: correctly rounded. |
any x | never | 130/132 | 7,330 | 7,186 | 8,880 |
| normCdf(int256) 0x7b9745a0 |
The standard normal distribution function Phi(x) = erfc(-x / sqrt2) / 2, the arg…The standard normal distribution function Phi(x) = erfc(-x / sqrt2) / 2, the argument scaled at 2^-192. The tail keeps its digits to the format's floor: Phi(-13) is about two ULPs, Phi(-14) rounds to zero, and Phi(x) is exactly one from x = 14. One rounding, or erf's second stage at y = |x| / sqrt 2 (exact, as BL19's) when the bound reaches the boundary: correctly rounded. This is the N(d) of Black-Scholes; on an 18-decimal grid it is zero from about d = -8.3. |
any x | never | 147/228 | 6,155 | 6,037 | 8,926 |
Bits are relative correct bits against mpmath at 200 decimal places over the committed vectors; "exact" is bit-identical. proven: every input against a reference spec, on the proofs page. Open an op for its method; hover a reverts cell for when. Gas is the median over random inputs that take no shortcut for the 22 ops that have them (the transcendentals, roots and powers), else over the op's input ladder. "Object" includes the staticcall to the deployed object that holds the op (BL127, BL127E, BL127H, BL127N or BL127S); "inline" is BL127Lib. "Bytes inline" is the runtime code one op adds to a contract that calls it through BL127Lib, helpers included (default profile, legacy pipeline; hover for via_ir). Ops that share a kernel add less together than their rows (size.json's bundles). Inlining saves about 554 gas a call; staticcall the deployed object instead when inlining would take a contract near the 24,576-byte limit, or for a large op called rarely. Sources: ops.json (generated from the Yul source), precision.json, gas.json, size.json.
For agents
/.well-known/agent.json, /llms.txt, ABI at /data/abi.json. Reads are eth_call: no gas, no wallet.