Skip to content

feat(tokens): cap the built-in token table at 500 entries — frees 23,104 B flash - #39

Merged
BitHighlander merged 1 commit into
masterfrom
feat/715-token-flash-budget
Aug 21, 2026
Merged

feat(tokens): cap the built-in token table at 500 entries — frees 23,104 B flash#39
BitHighlander merged 1 commit into
masterfrom
feat/715-token-flash-budget

Conversation

@BitHighlander

Copy link
Copy Markdown
Owner

tokens is the single largest read-only symbol in the ARM image: 31,104 bytes for 1,945 entries — bigger than MessagesMap (27,264) or the BIP-39 wordlist (8,196).

tokens   31,104 -> 8,000 B   (500 entries x 16)
saved    23,104 B of flash

Why a budget, not a bigger table

The vetted source is a stale snapshot and cannot be made current by shipping more of it. Measured against ethereum-lists as pinned:

  • 1,924 of 1,945 entries are Ethereum mainnet. Optimism has 2, Polygon 3, BSC 3. Base and Arbitrum have no directory at all.
  • Absent entirely: UNI, AAVE, stETH, wstETH, rETH, cbETH, PEPE, and every modern stablecoin — FRAX, LUSD, PYUSD, crvUSD, USDe.
  • ARB resolves to 0xafbec4d6…, a 2017 token called "ARBITRAGE". Arbitrum's real ARB (0xB50721BC…) is not in the table.

So the long tail is not coverage — it is 2017-era ICO tokens occupying flash while the assets users hold are missing. USDC, USDT, DAI, WETH, WBTC, LINK are present and correct, and those are what the budget protects.

Policy

  1. Budget: 350 from ethereum-lists + 150 from the uniswap list.
  2. Priority symbols first — stablecoins, then majors.
  3. A priority symbol is taken only when the source gives exactly one address. Two entries sharing a symbol is how a scam token inherits a real one's label. Ambiguous symbols are dropped and reported at build time.
  4. Remainder filled in the existing deterministic order, so output stays reproducible and diffable.

No address is written in the policy — symbols are matched against the vetted source. A hand-typed address in a token table is a mislabelling defect waiting to happen.

Two groups pinned for structural reasons, both named

  • REQUIRED_BY_COINS (26) — tickers declares with a contract address. correctly failed when the first cut dropped them: the device would advertise a coin it cannot name. They are 2017 tokens and should go next, but that cut belongs in — itself a 23,808-byte symbol.
  • REQUIRED_BY_TESTS (1) — ADT, which a test uses as its canonical "known ERC-20" while asserting a hardcoded signature. A fixture should not pin firmware flash; migrating that test to USDC retires it.

439/439 · full pyk 632 passed / 25 skipped / 0 failed · ARM SRAM unchanged at 18,172 B

…104 B flash

`tokens` is the single largest read-only symbol in the ARM image: 31,104 bytes
for 1,945 entries, bigger than MessagesMap (27,264) or the BIP-39 wordlist
(8,196). After this it is 8,000 bytes.

    tokens   31,104 -> 8,000 B   (500 entries x 16)
    saved    23,104 B of flash

WHY A BUDGET RATHER THAN A BIGGER TABLE. The vetted source is a stale snapshot
and cannot be made current by shipping more of it. Measured against
ethereum-lists as pinned here:

  - 1,924 of 1,945 entries are Ethereum mainnet. Optimism has 2, Polygon 3,
    BSC 3. The "top EVM chains" are effectively absent, and Base and Arbitrum
    have no directory at all.
  - Absent entirely: UNI, AAVE, stETH, wstETH, rETH, cbETH, PEPE, and every
    modern stablecoin -- FRAX, LUSD, PYUSD, crvUSD, USDe.
  - `ARB` resolves to 0xafbec4d6..., a 2017 token named "ARBITRAGE". Arbitrum's
    real ARB (0xB50721BC...) is not in the table.

So the long tail is not coverage, it is 2017-era ICO tokens occupying flash
while the assets users actually hold are missing. USDC, USDT, DAI, WETH, WBTC
and LINK are present and correct, and those are what the budget protects.

POLICY (keepkeylib/eth/token_policy.py, and it is the whole design):
  1. Budget: 350 from ethereum-lists + 150 from the uniswap list.
  2. Priority symbols first -- stablecoins, then majors.
  3. A priority symbol is taken ONLY when the source gives it exactly one
     address. Two entries sharing a symbol is how a scam token inherits a real
     one's label, and the device would render the attacker's name. Ambiguous
     symbols are dropped from the priority pass and reported at build time.
  4. Remaining budget filled in the existing deterministic order (by address),
     so output is reproducible and diffable.

NO ADDRESS IS WRITTEN IN THE POLICY. Symbols are matched against the vetted
source. A hand-typed address in a token table is a mislabelling defect waiting
to happen, and the file says so, so it does not become the place one appears.

TWO GROUPS PINNED FOR STRUCTURAL REASONS, both named rather than hidden:
  - REQUIRED_BY_COINS (26): tickers coins[] declares with a contract address.
    Coins.TableSanity asserts each resolves uniquely, and correctly FAILED when
    the first cut dropped them -- the device would advertise a coin it cannot
    name. They are 2017 tokens and are exactly what should go next, but the cut
    has to happen in coins[] first, itself a 23,808-byte symbol.
  - REQUIRED_BY_TESTS (1): ADT, which
    test_ethereum_signtx_knownerc20_eip_1559 uses as its canonical "known
    ERC-20" while asserting a hardcoded signature. A fixture should not get to
    pin firmware flash; migrating that test to USDC retires the entry, and is
    tracked as fixture debt rather than smuggled into this commit.

Verified: firmware-unit 439/439 (including Coins.TableSanity), full pyk suite
632 passed / 25 skipped / 0 failed, ARM SRAM reserve unchanged at 18,172 B.
@BitHighlander
BitHighlander merged commit ef34f35 into master Aug 21, 2026
1 of 3 checks passed
@BitHighlander
BitHighlander deleted the feat/715-token-flash-budget branch August 21, 2026 19:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant