sec: generalized CW20 code-id audit harness (decomp + exploit suite; gates #581) #589

Closed
opened 2026-08-22 02:59:07 +00:00 by PlasticDigits · 31 comments
PlasticDigits commented 2026-08-22 02:59:07 +00:00 (Migrated from gitlab.com)

Gate

This issue blocks #581. Do not close #581 with a go to factory AddWhitelistedCodeId 8266, and do not CreatePair / seed SpaceUSD/UST1, until this harness exists, the standard procedure is documented, and code ID 8266 has a per-code-id audit report plus a green automated suite under that procedure.

Parent ops: #558. Policy: docs/runbooks/cw20-whitelist-policy.md / #377 H-01 / P2. Pin + write-path re-check: #582 F6 (already live; this issue does not replace F6).

Policy change (replaces #581 hash-equal rebuild): a byte-identical optimizer rebuild (data_hash match) is not required and is not a go/no-go input. Old popular CW20 templates (Terraport 8266, TerraSwap 6036, and later IDs) routinely lack a public Cargo.lock and/or use private crates, so hash-equal rebuilds fail even when the LCD wasm is a standard token. The replacement gate is:

  1. Canonical LCD wasm downloaded and checksum-pinned to CodeInfo.data_hash (prove we have the on-chain binary — not that we compiled it).
  2. Decompilation of that binary into a per-code-id folder.
  3. Human audit of the decompiled code against a written catalogue.
  4. Automated test suite on that wasm covering all DEX invariants that interact with CW20 assets, all common CW20 behaviors, all known CW20 exploits, ERC-20 analogues, and DEX exploits that malicious / weird tokens enable.

Optional source rebuild remains an appendix only.


Current codebase

Whitelist and listing are already fail-closed on code ID, not on token address:

Piece What it does today Gap
Factory WHITELISTED_CODE_IDS CreatePair refuses unknown templates No reusable way to decide a new ID is safe
Pair F6 pin + IsCodeIdWhitelisted Live code_id must match listing pin and stay listed (#582) Does not say whether the template is 1:1 / honeypot-free
docs/runbooks/cw20-whitelist-policy.md Forbids FoT / rebase; asks for source review + staging 1:1 probe; still talks about checksum / rebuild evidence Procedure is narrative; no folder, no decomp, no shared suite; #581 notes still treat hash-equal rebuild as the remaining gate
scripts/verify-cw20-code-ids.sh LCD CodeInfo only Does not download wasm, decompile, or test behavior
audits/CW20-8266-581.md + hash-repro / terraswap notes One-off 8266 investigation (LCD fingerprint, LocalTerra 1:1 probe, failed rust-optimizer 0.12.11 rebuild) Not generalized; hash-repro is a dead end; decomp and exploit suite are not in-repo
smartcontracts/tests/src/adversarial_token.rs In-process mock CW20: Honest vs FeeOnTransfer; documents P2 desync; router dust (R4); hook spoof (H2) Does not execute a downloaded LCD wasm. Only one weird-token mode. No rebase / honeypot / blacklist / hidden mint / callback-on-Transfer / pause / flash-mint fixtures
docs/contracts-security-audit.md Invariant matrix P1–P10, F1–F6, R1–R4, C4, H1–H2, I2, W1, L1–L22 with cw-multi-test evidence Those tests use our mintable / mock tokens, not candidate listing wasm
docs/exploit-replay-matrix.md SEC-D06 Names malicious CW20 (FoT, callback griefing, balance desync) Replay is documented, not a per-code-id product test
Factory today GetWhitelistedCodeIds = [6036, 10184] Future IDs (8266, others) have no standard intake

There is no cw20-codeid-audits/ (or equivalent) tree, no standard per-code-id report template, no cosmwasm-vm / LocalTerra harness that loads arbitrary LCD wasm, and no catalogue that maps ERC-20 “weird token” classes onto CW20 + this DEX.


Why this is needed

  1. Hash-equal rebuild does not scale. #581 spent a full investigation reconstructing classic_terraport and pinning rustc 1.66.0 / cosmwasm-std 1.3.3 / rust-optimizer:0.12.11. The rebuild was smaller than LCD wasm, exported an extra __getrandom_custom, and never matched data_hash 953AD60C…. That is the expected outcome for Columbus-5-era tokens without a published lockfile. The same will happen for other popular IDs.
  2. LCD query surface is not an audit. ALPHA 8654 looked listable until tax_map appeared. 8266 has no tax_map and still needs transfer-path proof. Query enums cannot show hidden skim, sell-side honeypot, or admin-only TransferFrom.
  3. Whitelist is template-global. Approving 8266 admits all current and future instantiations (1686+ on columbus-5), not just SpaceUSD. The decision must be about the wasm, repeated the same way for every future ID.
  4. DEX safety is an invariant problem, not a vibe check. Pair / router / limits credit declared CW20 amounts (P2, L1). A token that is “almost” cw20-base can still desync reserves, brick withdraw, trap makers, or drain via donation / first-depositor / callback tricks. Those classes are well documented on EVM and only partially mirrored here.
  5. One-off /tmp probes do not persist. #581 LocalTerra 1:1 probes passed and must be turned into a checked-in, parameterized suite so the next code ID is not a research project.

Constraints / guardrails

  • Do not add pair / router balance-delta / FoT swap math to “support” taxed or rebasing tokens (H-01; Balancer STA 2020 class). A failing suite means do not whitelist, not “make the AMM tolerate it”.
  • Do not treat a source-tree review of a non-matching rebuild as the binary audit. Audit the LCD wasm (decomp + tests). Public git is supporting evidence only.
  • Do not require optimizer SHA / byte identity as a gate. Optional appendix only.
  • Do not whitelist on decomp “looks like cw20-base” alone. Tests must pass. Decomp can miss obfuscated branches; tests can miss un-exercised admin setters — both are required.
  • Canonical wasm is LCD /cosmwasm/wasm/v1/code/{id} (or equivalent) whose SHA-256 equals CodeInfo.data_hash. No third-party mirror without that check.
  • F6 stays. This harness decides whether a template may be added to the whitelist. It does not replace listing-time pin or write-path re-check.
  • Factory-global implication must be written on every report: approving the ID admits every instantiate of that wasm.
  • Do not bind-mount indexer/ into root Docker to run cargo (AGENTS.md / make test-indexer-target-ownership). Host compile; LocalTerra via make setup-cloud-localterra / make has-localterra.
  • Do not store secrets, mnemonics, or production admin keys in the audit tree.
  • Wasm in git: prefer a download script + pinned checksum over committing multi-MB binaries. If binaries are committed, use Git LFS and a size cap; CI must still re-hash against LCD.
  • Decomp is not redistributable “source” for legal claims. Reports must say the inspected artifact is a decompilation of LCD wasm.
  • Standard CW20 control: every new check must also run against 10184 (protocol mintable) and, where LCD allows, 6036 (already listed TerraSwap token). A check that fails on the control tokens is a harness bug, not a listing veto.
  • Known-bad control: ALPHA-style 8654 / in-process FoT mock must fail the 1:1 and DEX-invariant slices (document expected FAIL). The suite is wrong if 8654 goes green.
  • Related work stays bundled in this issue (folder + procedure + decomp pipeline + catalogue + harness + 8266 first report). Do not split into parallel issues.

Relevant files

Path Role
docs/runbooks/cw20-whitelist-policy.md Ops policy to rewrite: decomp + suite replace hash-equal rebuild
docs/runbooks/cw20-code-id-ops.md F6 freeze / refresh; cross-link the new procedure
docs/contracts-security-audit.md Invariant IDs the DEX slice must re-run on candidate wasm
docs/security-model.md Code ID whitelist section
docs/exploit-replay-matrix.md SEC-D06 + historical rows to encode as tests
docs/terraport.md Terraport code-id table
scripts/verify-cw20-code-ids.sh Extend or wrap as fetch step
smartcontracts/tests/src/adversarial_token.rs FoT / dust / hook fixtures to keep as known-bad oracles
smartcontracts/tests/src/asset_code_id_pin_tests.rs F6 migrate-off-whitelist
smartcontracts/tests/src/audit_invariant_tests.rs, security_tests.rs, reentrancy_tests.rs, limit_order_tests.rs, sweep_tests.rs Existing invariant evidence to reuse or port onto LCD wasm
audits/CW20-8266-581*.md Move / supersede under cw20-codeid-audits/codeids/8266/
skills/AGENTS_CW20_CODE_ID_PIN.md Agent playbook; add harness pointer
Makefile verify-issue-* pattern Add make verify-issue-<this>

Layout

cw20-codeid-audits/
  README.md                 # how to add a code ID
  PROCEDURE.md              # standard audit steps (below)
  CATALOG.md                # exploit / weird-token catalogue + citations (living)
  report-template.md        # required sections for every codeids/<id>/REPORT.md
  scripts/
    fetch-lcd-wasm.sh       # LCD CodeInfo + wasm + sha256 pin
    decompile-wasm.sh       # wabt: wasm2wat, wasm-decompile, wasm-objdump, strings
  harness/                  # Rust: cosmwasm-vm token-only + DEX/LocalTerra runners
  fixtures/                 # known-good / known-bad control notes (not live keys)
  codeids/
    10184/                  # protocol mintable control
    6036/                   # listed TerraSwap token control
    8266/                   # first candidate (#581)
      meta.json             # code_id, data_hash, creator, instantiate permission, LCD
      wasm.sha256           # pin
      decomp/               # generated; wat / decompile / objdump / strings
      REPORT.md             # filled template
    8654/                   # known-bad ALPHA taxed control (expected FAIL)

Do not dump decomp into smartcontracts/ or frontend-dapp/. Keep the tree self-contained.

Standard procedure (every code ID)

  1. Identity — LCD CodeInfo: code_id, data_hash, creator, instantiate permission, contract count. Write meta.json.
  2. Fetch — download wasm; SHA-256 must equal data_hash. Fail closed on mismatch.
  3. Fingerprint — exports, producers/rustc, crate strings (cw20_base, terraport_token, tax_map, requires_terra), custom query/execute enums from error strings.
  4. Decompile — wasm2wat, wasm-decompile (C-like), wasm-objdump -x / -d for transfer/send/transfer_from/send_from/burn/mint and any tax/hook/admin symbols. Store under codeids/<id>/decomp/.
  5. Static audit — walk decomp against CATALOG.md. Record hits, misses, and unreadable regions. Extra queries (balance_at, total_supply_at, tax_map) must be classified (snapshot vs live mutation vs tax).
  6. Automated suite — token-only VM tests, then DEX invariant tests (store wasm on LocalTerra or load via cosmwasm-vm in a pair harness). Persist logs/JUnit under the code-id folder or CI artifacts.
  7. Report — fill REPORT.md: fingerprint, decomp findings, suite results vs catalogue, factory-global impact, instance-admin / migrate residual (F6), go / no-go.
  8. Optional appendix — source URL / CertiK zip / rebuild hash. Never blocks go/no-go.

Harness (two layers)

Layer A — token-only (cosmwasm-vm): instantiate the LCD wasm (or skip instantiate and execute against a stored instance snapshot if init is exotic), then drive CW20 execute/query. No pair. Fast, CI-friendly, catches FoT/rebase/honeypot/allowance bugs.

Layer B — DEX + limits: store the same wasm on LocalTerra (or a cw-multi-test custom wasm loader if feasible), whitelist only in the test factory, CreatePair with a 10184 counter-asset, then re-run the invariant matrix that touches CW20 balances (P1, P2, P3, P4, P10, R1–R4, C4, L1–L3, L6, L10, L11, sweep). Compare to the same scenario with 10184/10184 as control.

Prefer parameterized tests (CODE_ID=8266 make verify-issue-NNN) over copy-paste per ID.

Catalogue (research baseline — implement every row)

Sources used for this issue (must be cited in CATALOG.md and kept current):

  • d-xo/weird-erc20 (imBTC / Lendf.me ERC-777 reentrancy; STA / PAXG FoT; Balancer STA drain; USDT approve race; blocklist / pause; flash mint; proxied multi-address; high/low decimals; missing returns; permit no-ops)
  • Trail of Bits token integration checklist and non-standard tokens
  • Consensys Diligence token interaction checklist (fee, interest, ERC-777 hooks, allowance race)
  • cw20-base execute surface (Transfer, Send, Burn, Mint, Increase/DecreaseAllowance, TransferFrom, SendFrom, BurnFrom, marketing, UpdateMinter)
  • fragwuerdig/cw20-taxed + Columbus-5 ALPHA 8654 tax_map (tax on transfer/send/transfer_from/send_from; contract vs wallet discrimination)
  • CosmWasm CWA-2024-002 wrapping Uint256::pow; ASA-2024-007 / Terra–Astroport IBC-hooks infinite mint (token used as pool asset after unbacked mint)
  • SEC-D02–D06 in docs/exploit-replay-matrix.md; in-repo P2 / P3 / R4 / H1
  • EVM honeypot / tax-abuse writeups (sell-only tax, pair-address block, hidden mint, allowance backdoor, trading toggle, max-wallet shrink): TokenToolHub honeypot guide; Hacken honeypot techniques; evm-token-guard pattern list
  • Flash-loan / donation / first-depositor inflation (Compound-fork / ERC-4626 class; Osmosis LP-share — SEC-D04); sandwich + max_spread (existing security_tests)

Map every ERC-20 class to a CW20 test (or an explicit N/A with reason, e.g. “no bool return — CosmWasm errors instead”).


Acceptance criteria

  • cw20-codeid-audits/ exists with PROCEDURE.md, CATALOG.md (every catalogue row + citation), report-template.md, fetch + decompile scripts, and codeids/<id>/ subfolders
  • docs/runbooks/cw20-whitelist-policy.md (and #581) no longer require compile-hash / byte-identical rebuild; they require this procedure
  • Harness Layer A runs against arbitrary pinned LCD wasm (parameterized by code ID)
  • Harness Layer B re-runs all DEX invariants that interact with asset CW20s on that wasm (see test plan)
  • Suite covers all common CW20 behaviors (see test plan)
  • Suite covers all catalogue exploit rows (pass = “this wasm does not exhibit the malice”; known-bad 8654 / FoT mock fails the 1:1 / P2 rows as expected)
  • Control: 10184 green on honest rows; 6036 green or documented exceptions; 8654 (or FoT fixture) red on FoT/P2
  • codeids/8266/REPORT.md filled (decomp + suite). #581 may then record go/no-go from that report, not from a rebuild hash
  • make verify-issue-<this> (and agent skill) documented in AGENTS.md / playbook
  • Existing audits/CW20-8266-581*.md are linked or moved so there is one intake path

Test plan — all paths

Framework / intake paths

Path Expected
fetch-lcd-wasm.sh happy path wasm + data_hash match → meta.json + wasm.sha256
Fetch hash mismatch / truncated download FAIL closed; no decomp, no suite
Unknown / missing code ID on LCD FAIL with LCD error; no fake pass
Decompile tools missing FAIL with install hint (wabt); do not skip decomp
Re-run on already-fetched ID Idempotent; does not overwrite REPORT.md conclusions
CODE_ID=10184 (control) Full suite green on honest / invariant rows
CODE_ID=8654 or FoT fixture 1:1 + P2 rows FAIL (expected); report template still generates
Instantiate permission Everybody vs Nobody Recorded; suite still instantiates via test key or uses a live instance snapshot
Init requires exotic marketing / snapshot fields Harness supplies minimal valid init or clones a live instantiate; document which

Common CW20 behaviors (Layer A — must implement all)

For each of Transfer, Send, TransferFrom, SendFrom, Burn, BurnFrom, Mint, IncreaseAllowance, DecreaseAllowance, plus queries Balance, TokenInfo, Allowance, Minter, AllAllowances / AllAccounts if present:

Behavior Expected for a listable template
Exact debit / credit Recipient delta == declared amount; sender debit == amount (or burn/mint identity). No silent fee.
Send / SendFrom hook Receiver gets Cw20ReceiveMsg with same amount; 1:1 credit before hook; receiver revert rolls back balances
Zero amount Deterministic: reject or no-op; same as 10184; no reserve desync if later paired
Self transfer Balance unchanged net; no fee event
Oversize transfer / insufficient allowance Reject; no partial debit
Unauthorized mint / burn-from Reject
Mint cap (if any) Enforce; overflow rejected
Allowance expire TransferFrom / SendFrom reject after expiry
UpdateMinter / marketing / logo Cannot move balances; minter clear is one-way if spec says so
Idle balance After N empty blocks, all holder balance and total_supply unchanged (anti-rebase)
Snapshot queries (balance_at / total_supply_at) Historical only; do not mutate current balances
Decimals / symbol / name Present; decimals ≤ 18 for CreatePair bootstrap (P3); no script injection in name/symbol for dApp (record; frontend escape is separate)
Supply conservation Σ balances == total_supply after every honest op (spot-check + random walk)

DEX invariant paths (Layer B — must re-run on candidate wasm)

Reuse IDs from docs/contracts-security-audit.md. In scope (asset is the candidate CW20; other side 10184 unless noted):

ID What to assert with candidate wasm
P1 After swap, k' \ge k within documented rounding
P2 After provide / swap / withdraw, pair RESERVES == CW20 balance of pair (no FoT skim, no silent donate credit)
P3 First deposit burns MINIMUM_LIQUIDITY; decimals > 18 rejected
P4 / P10 Fees in range; failed swap leaves treasury unchanged
P5–P9 Discount / factory-admin paths still hold (token is just the asset; run once per harness, not every ID, unless the token spoofs query)
F1–F6 CreatePair accepts only after test whitelist; F6 pin equals stored code ID; migrate-off-template still freezes (already tested in asset_code_id_pin_tests — run once + once with this wasm as the “honest then migrate” subject if admin exists)
R1–R4, C4 Router single- and multi-hop; minimum_receive / per-hop min_return; dust on output is not swept (R4)
H1–H2, I2 Allowlisted hook revert is atomic; spoofed pair rejected; tax-hook (protocol hook, not token tax) still settles from swap flow
L1–L3, L6, L10, L11 Limit place via Send 1:1 escrow; cancel / claim refunds exact remaining; pause blocks CW20 refunds; batch refunds do not over-pay
Sweep Direct donate to pair is not LP shares (P3 / existing donation tests); sweep recovers excess without rewriting honest reserves

Out of scope for per-code-id Layer B (keep existing cw-multi-test only): pure book-structure IDs that do not depend on token semantics (L5, L12–L22 walk/hint/dust-flush/price-band), unless the token can change balances mid-match (then they become in scope).

Docs / CI paths

Path Expected
make verify-issue-<this> without LocalTerra Layer A + static fetch/decomp on cached wasm; Layer B skip or fail with make has-localterra hint (do not silent-pass)
Cloud Agent make setup-cloud-localterra then Layer B; no SKIP (no LocalTerra) without provisioning
Policy docs rg for “hash-equal” / “byte-identical” / “optimizer rebuild required” is empty except history notes

Test plan — attack, hack, and abuse vectors

Each row is a required automated case (or explicit N/A). “Fail” means the token is not listable (or the DEX correctly rejects / stays solvent). Citations are the research baseline; implementers must add any newer public class found while building CATALOG.md.

A. Malicious / weird CW20 (and ERC-20 analogues)

ID Vector ERC-20 analogue / incident CW20 / DEX encoding Listable?
A1 Fee-on-transfer / deflationary transfer STA, PAXG, USDT-optional fee; Balancer 2020 STA drain; AuditBase M006 Transfer/Send/TransferFrom/SendFrom recipient delta < declared; pair P2 desync No
A2 Directional / sell-only / buy-only tax BSC tax tokens; Token-Tax-Abuse-Science Fee when recipient or sender is pair/router; wallet↔wallet 1:1 No
A3 Reflection / rebase / elastic supply AMPL; “balance modifications outside transfers” (weird-erc20) Idle balance or total_supply changes; balance_at that rewrites current No
A4 Hidden / admin-settable tax (tax_map, UpdateTaxMap) cw20-taxed; ALPHA 8654 Query or decomp shows tax config; setter can turn on FoT after listing No (F6 does not see same-code-id setter)
A5 Transfer hook / callback on plain Transfer ERC-777 tokensToSend / tokensReceived; imBTC Uniswap; Lendf.me; Cream Transfer (not only Send) dispatches WasmMsg to sender/recipient No (reentrancy / grief)
A6 Send hook grief / reenter pair ERC-677 transferAndCall; CosmWasm intended Receive Receiver re-enters pair Swap/Withdraw/PlaceLimit; pair must fail closed (existing reentrancy tests + this wasm as offer token) Must not extract value
A7 Allowance race / IncreaseAllowance surprise ERC-20 approve race; USDT “must set 0 first” CW20 uses increase/decrease — assert no Approve {amount} overwrite race; if custom Approve exists, test double-spend window Document; No if spender can exceed intended cap
A8 Allowance backdoor evm-token-guard transferFrom without allowance / _isBot Admin or hardcoded addr moves tokens without allowance No
A9 Blocklist / allowlist honeypot USDC/USDT blocklist; pair-address sell block Transfer to pair reverts for non-admin after buy; DEX withdraw/swap bricks No
A10 Pause / trading toggle BNB/ZIL pause; “enable trading” honeypot Admin pause stops pair Transfer No unless we control admin (record residual)
A11 Max-tx / max-wallet shrink Soft honeypot Admin lowers cap below pair balance No
A12 Hidden mint / stealth balance write Honeypot mint-to-drain Mint without minter; direct balance map write; mint ignores cap No
A13 Flash mint DAI flash mint Intra-tx supply uint128::MAX then burn; can break first-deposit / donation No unless proven unused and P3 still holds
A14 Upgrade / Migrate surface USDC proxy; weird-erc20 Upgradable Instance admin + MsgMigrateContract — F6 tested; report must list admin Residual; F6 freeze required
A15 Proxied / multi-address token Rescue-drain via second entry point Two contracts share balances; sweep/rescue confusion No
A16 Missing / lying query Missing bool return; Tether Gold false success CosmWasm: query Balance disagrees with transfer events; TokenInfo.total_supply ≠ sum No
A17 High / low decimals USDC 6; YAM 24; Gemini 2 decimals > 18 → CreatePair reject (P3); 0 decimals → overflow/precision tests Follow P3; extra No if math breaks P1
A18 Revert on zero / to-zero / huge amount LEND zero transfer; OZ to-zero; UNI uint96 Pair/router/limits must not brick on honest dust; if token reverts on amounts the pair uses, No
A19 TransferFrom self-semantics DSToken vs OZ Pair never relies on self-TransferFrom; still test
A20 Permit / phantom function DAI permit; Multichain no-op permit If permit exists and is a no-op, DEX must not assume allowance changed N/A if absent
A21 Name/symbol XSS Etherdelta JS in token name Record; dApp must escape (existing frontend) Not a whitelist veto alone
A22 Non-string metadata MKR bytes32 name CW20 is string; if binary, queries break dApp No if TokenInfo unreadable
A23 Interest / airdrop to holders cToken / rebase-like drip Pair balance grows without Provide → P2 desync or trapped yield No
A24 Transfer less than amount when amount == max cUSDCv3 max-uint special case Uint128::MAX transfer credits only balance No if pair can pass max
A25 Native-denom dual representation CELO / POL / Uniswap V4 CW20 wrap vs uluna/uusd — factory already rejects natives; test candidate is not secretly bank-denom No if dual-spend
A26 IBC / bridged unbacked mint ASA-2024-007; Terra 2024 Astroport If wasm has IBC hooks / ibc_receive, treat as infinite-mint class No unless proven unused
A27 CWA-2024-002 wrapping pow cosmwasm-std < 1.4.4 Uint256::pow If token uses wrapping pow for balances, No
A28 requires_terra / classic taxer integration Terra Classic burn tax on natives, not CW20 Strings alone ≠ FoT; still prove 1:1 on CW20 path Decide from tests
A29 Snapshot used as rebase 8266 balance_at Idle current balance stable; snapshot ≠ live mutate No if live mutate
A30 Marketing / logo store blowup Unbounded logo upload DoS / gas grief only; record Usually residual

B. DEX exploits that use a malicious or weird token

ID Vector What the suite must show
B1 Reserve desync → insolvent withdraw FoT / rebase token: after swap, LP withdraw fails or steals the honest side (P2). Candidate must not produce this. Known-bad must.
B2 First-depositor / donation inflation Donate candidate wasm to pair then provide; LP shares must not inflate (P3, SEC-D04, existing test_direct_token_donation_does_not_inflate_lp_shares)
B3 Flash provide → swap → withdraw No risk-free profit (test_flash_provide_swap_withdraw_no_profit) with this token
B4 Router dust attribution Pre-seed router with candidate token; hop must use delta only (R4)
B5 Hook + token callback sandwich Token Send receiver + pair hook both re-enter; no double-pay
B6 Limit escrow skim Place limit with FoT token: PENDING_ESCROW vs actual balance; cancel/claim honesty (L1). Candidate 1:1 only.
B7 Honeypot: buy on pool, cannot sell Swap A→candidate succeeds, candidate→A reverts for non-admin
B8 Pair blacklisted by token after TVL Token admin blocks pair address; withdraw/swap die
B9 Migrate-after-list to FoT Honest wasm → MsgMigrateContract FoT → writes fail closed (F6). Already in asset_code_id_pin_tests; include this wasm as the honest side once.
B10 Sandwich / oracle tilt Large swap vs max_spread / min_return still holds (existing security tests)
B11 Commission on failed swap Token that reverts in Transfer mid-settlement: treasury unchanged (P10)
B12 Sweep confusion Token with two addresses or lying Balance makes sweep steal pool
B13 CreatePair spam / Everybody instantiate Report-only: listing 8266 admits all instances; not an automated pass
B14 Mint inflation vs “USD” ticker SpaceUSD-style minter cap; report residual (not P2)
B15 IBC unbacked mint dumped on pool If token is IBC-mintable, pool can be drained; No unless out of scope + written

C. Harness / process abuse

ID Abuse Expected
C1 Swap in a rebuilt wasm that is not LCD data_hash Fetch/pin step FAIL; suite refuses to run
C2 Skip decomp, run tests only Procedure / CI FAIL (both required)
C3 Skip tests, attach only decomp prose Procedure / CI FAIL
C4 Mark 8654 / FoT as go Reviewer veto; known-bad job must be red
C5 Silent Layer B skip without LocalTerra FAIL or explicit skip reason; no green verify target
C6 Commit mnemonic / admin key in codeids/ Hook or CI secret scan; reject
C7 Treat CertiK marketing / file-level Skynet hashes as data_hash Procedure forbids; report template has “unverified third-party claim” checkbox

Verification criteria

  1. make verify-issue-<this> is green on CI or Cloud Agent for 10184 (and FoT known-bad red).
  2. Fresh clone: scripts/fetch-lcd-wasm.sh 8266 + decompile-wasm.sh produce codeids/8266/decomp/ and wasm.sha256 matching LCD 953AD60C… without any optimizer rebuild.
  3. codeids/8266/REPORT.md exists with every procedure section, every catalogue row checked (pass / fail / N/A+reason), Layer A+B logs referenced, and an explicit go or no-go.
  4. rg on docs/runbooks/cw20-whitelist-policy.md shows decomp+suite as the whitelist evidence; hash-equal rebuild is optional appendix only.
  5. #581 description/checklist no longer requires compile-hash match; #581 remains open until the 8266 report is accepted.
  6. Reviewer can re-run Layer A on the pinned wasm without network (cached) and get the same verdict.

Labels / owner

contracts + security + docs + testing + ops (whitelist procedure). Implementation can proceed without a 2-of-3 tx; listing 8266 still needs #581 go after this ships.

Priority

P1 for #558 / #581 SpaceUSD/UST1 intake. Also the standing process for every future CW20 code ID.

## Gate **This issue blocks [#581](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/581).** Do **not** close #581 with a **go** to factory `AddWhitelistedCodeId` **8266**, and do **not** `CreatePair` / seed **SpaceUSD/UST1**, until this harness exists, the standard procedure is documented, and **code ID 8266** has a per-code-id audit report plus a **green automated suite** under that procedure. Parent ops: [#558](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/558). Policy: [`docs/runbooks/cw20-whitelist-policy.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/cw20-whitelist-policy.md) / [#377](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/377) **H-01** / **P2**. Pin + write-path re-check: [#582](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/582) **F6** (already live; this issue does **not** replace F6). **Policy change (replaces #581 hash-equal rebuild):** a byte-identical optimizer rebuild (`data_hash` match) is **not** required and is **not** a go/no-go input. Old popular CW20 templates (Terraport 8266, TerraSwap 6036, and later IDs) routinely lack a public `Cargo.lock` and/or use private crates, so hash-equal rebuilds fail even when the LCD wasm is a standard token. The replacement gate is: 1. Canonical LCD wasm downloaded and checksum-pinned to `CodeInfo.data_hash` (prove we have **the** on-chain binary — not that we compiled it). 2. Decompilation of that binary into a per-code-id folder. 3. Human audit of the decompiled code against a written catalogue. 4. Automated test suite on **that wasm** covering **all** DEX invariants that interact with CW20 assets, **all** common CW20 behaviors, **all** known CW20 exploits, **ERC-20 analogues**, and **DEX exploits** that malicious / weird tokens enable. Optional source rebuild remains an appendix only. --- ## Current codebase Whitelist and listing are already fail-closed on **code ID**, not on token address: | Piece | What it does today | Gap | |-------|--------------------|-----| | Factory `WHITELISTED_CODE_IDS` | `CreatePair` refuses unknown templates | No reusable way to **decide** a new ID is safe | | Pair **F6** pin + `IsCodeIdWhitelisted` | Live `code_id` must match listing pin and stay listed ([#582](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/582)) | Does not say whether the **template** is 1:1 / honeypot-free | | [`docs/runbooks/cw20-whitelist-policy.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/cw20-whitelist-policy.md) | Forbids FoT / rebase; asks for source review + staging 1:1 probe; still talks about checksum / rebuild evidence | Procedure is narrative; no folder, no decomp, no shared suite; #581 notes still treat hash-equal rebuild as the remaining gate | | [`scripts/verify-cw20-code-ids.sh`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/scripts/verify-cw20-code-ids.sh) | LCD `CodeInfo` only | Does not download wasm, decompile, or test behavior | | [`audits/CW20-8266-581.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/audits/CW20-8266-581.md) + hash-repro / terraswap notes | One-off 8266 investigation (LCD fingerprint, LocalTerra 1:1 probe, failed rust-optimizer 0.12.11 rebuild) | Not generalized; hash-repro is a dead end; decomp and exploit suite are not in-repo | | [`smartcontracts/tests/src/adversarial_token.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/tests/src/adversarial_token.rs) | In-process **mock** CW20: `Honest` vs `FeeOnTransfer`; documents **P2** desync; router dust (**R4**); hook spoof (**H2**) | Does **not** execute a downloaded LCD wasm. Only one weird-token mode. No rebase / honeypot / blacklist / hidden mint / callback-on-`Transfer` / pause / flash-mint fixtures | | [`docs/contracts-security-audit.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/contracts-security-audit.md) | Invariant matrix **P1–P10**, **F1–F6**, **R1–R4**, **C4**, **H1–H2**, **I2**, **W1**, **L1–L22** with `cw-multi-test` evidence | Those tests use **our** mintable / mock tokens, not candidate listing wasm | | [`docs/exploit-replay-matrix.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/exploit-replay-matrix.md) **SEC-D06** | Names malicious CW20 (FoT, callback griefing, balance desync) | Replay is documented, not a per-code-id product test | | Factory today | `GetWhitelistedCodeIds` = **`[6036, 10184]`** | Future IDs (8266, others) have no standard intake | There is **no** `cw20-codeid-audits/` (or equivalent) tree, **no** standard per-code-id report template, **no** `cosmwasm-vm` / LocalTerra harness that loads **arbitrary** LCD wasm, and **no** catalogue that maps ERC-20 “weird token” classes onto CW20 + this DEX. --- ## Why this is needed 1. **Hash-equal rebuild does not scale.** #581 spent a full investigation reconstructing `classic_terraport` and pinning rustc **1.66.0** / `cosmwasm-std` **1.3.3** / `rust-optimizer:0.12.11`. The rebuild was smaller than LCD wasm, exported an extra `__getrandom_custom`, and never matched `data_hash` `953AD60C…`. That is the expected outcome for Columbus-5-era tokens without a published lockfile. The same will happen for other popular IDs. 2. **LCD query surface is not an audit.** ALPHA **8654** looked listable until `tax_map` appeared. 8266 has **no** `tax_map` and still needs transfer-path proof. Query enums cannot show hidden skim, sell-side honeypot, or admin-only `TransferFrom`. 3. **Whitelist is template-global.** Approving 8266 admits **all** current and future instantiations (1686+ on columbus-5), not just SpaceUSD. The decision must be about the **wasm**, repeated the same way for every future ID. 4. **DEX safety is an invariant problem, not a vibe check.** Pair / router / limits credit **declared** CW20 amounts (**P2**, **L1**). A token that is “almost” cw20-base can still desync reserves, brick withdraw, trap makers, or drain via donation / first-depositor / callback tricks. Those classes are well documented on EVM and only partially mirrored here. 5. **One-off `/tmp` probes do not persist.** #581 LocalTerra 1:1 probes passed and must be turned into a **checked-in, parameterized** suite so the next code ID is not a research project. --- ## Constraints / guardrails - **Do not** add pair / router **balance-delta / FoT swap math** to “support” taxed or rebasing tokens (**H-01**; Balancer STA 2020 class). A failing suite means **do not whitelist**, not “make the AMM tolerate it”. - **Do not** treat a source-tree review of a **non-matching** rebuild as the binary audit. Audit the **LCD wasm** (decomp + tests). Public git is supporting evidence only. - **Do not** require optimizer SHA / byte identity as a gate. Optional appendix only. - **Do not** whitelist on decomp “looks like cw20-base” alone. Tests must pass. Decomp can miss obfuscated branches; tests can miss un-exercised admin setters — both are required. - **Canonical wasm** is LCD `/cosmwasm/wasm/v1/code/{id}` (or equivalent) whose SHA-256 equals `CodeInfo.data_hash`. No third-party mirror without that check. - **F6 stays.** This harness decides whether a template may be **added** to the whitelist. It does not replace listing-time pin or write-path re-check. - **Factory-global implication** must be written on every report: approving the ID admits every instantiate of that wasm. - **Do not** bind-mount `indexer/` into root Docker to run `cargo` (`AGENTS.md` / `make test-indexer-target-ownership`). Host compile; LocalTerra via `make setup-cloud-localterra` / `make has-localterra`. - **Do not** store secrets, mnemonics, or production admin keys in the audit tree. - **Wasm in git:** prefer a download script + pinned checksum over committing multi-MB binaries. If binaries are committed, use Git LFS and a size cap; CI must still re-hash against LCD. - **Decomp is not redistributable “source”** for legal claims. Reports must say the inspected artifact is a decompilation of LCD wasm. - **Standard CW20 control:** every new check must also run against **10184** (protocol mintable) and, where LCD allows, **6036** (already listed TerraSwap token). A check that fails on the control tokens is a harness bug, not a listing veto. - **Known-bad control:** ALPHA-style **8654** / in-process FoT mock must **fail** the 1:1 and DEX-invariant slices (document expected FAIL). The suite is wrong if 8654 goes green. - Related work stays bundled **in this issue** (folder + procedure + decomp pipeline + catalogue + harness + 8266 first report). Do not split into parallel issues. --- ## Relevant files | Path | Role | |------|------| | [`docs/runbooks/cw20-whitelist-policy.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/cw20-whitelist-policy.md) | Ops policy to rewrite: decomp + suite replace hash-equal rebuild | | [`docs/runbooks/cw20-code-id-ops.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/runbooks/cw20-code-id-ops.md) | F6 freeze / refresh; cross-link the new procedure | | [`docs/contracts-security-audit.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/contracts-security-audit.md) | Invariant IDs the DEX slice must re-run on candidate wasm | | [`docs/security-model.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/security-model.md) | Code ID whitelist section | | [`docs/exploit-replay-matrix.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/exploit-replay-matrix.md) | SEC-D06 + historical rows to encode as tests | | [`docs/terraport.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/terraport.md) | Terraport code-id table | | [`scripts/verify-cw20-code-ids.sh`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/scripts/verify-cw20-code-ids.sh) | Extend or wrap as `fetch` step | | [`smartcontracts/tests/src/adversarial_token.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/tests/src/adversarial_token.rs) | FoT / dust / hook fixtures to keep as **known-bad** oracles | | [`smartcontracts/tests/src/asset_code_id_pin_tests.rs`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/smartcontracts/tests/src/asset_code_id_pin_tests.rs) | F6 migrate-off-whitelist | | `smartcontracts/tests/src/audit_invariant_tests.rs`, `security_tests.rs`, `reentrancy_tests.rs`, `limit_order_tests.rs`, `sweep_tests.rs` | Existing invariant evidence to **reuse or port** onto LCD wasm | | [`audits/CW20-8266-581*.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/tree/main/audits) | Move / supersede under `cw20-codeid-audits/codeids/8266/` | | [`skills/AGENTS_CW20_CODE_ID_PIN.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_CW20_CODE_ID_PIN.md) | Agent playbook; add harness pointer | | `Makefile` `verify-issue-*` pattern | Add `make verify-issue-<this>` | --- ## Recommended direction ### Layout ```text cw20-codeid-audits/ README.md # how to add a code ID PROCEDURE.md # standard audit steps (below) CATALOG.md # exploit / weird-token catalogue + citations (living) report-template.md # required sections for every codeids/<id>/REPORT.md scripts/ fetch-lcd-wasm.sh # LCD CodeInfo + wasm + sha256 pin decompile-wasm.sh # wabt: wasm2wat, wasm-decompile, wasm-objdump, strings harness/ # Rust: cosmwasm-vm token-only + DEX/LocalTerra runners fixtures/ # known-good / known-bad control notes (not live keys) codeids/ 10184/ # protocol mintable control 6036/ # listed TerraSwap token control 8266/ # first candidate (#581) meta.json # code_id, data_hash, creator, instantiate permission, LCD wasm.sha256 # pin decomp/ # generated; wat / decompile / objdump / strings REPORT.md # filled template 8654/ # known-bad ALPHA taxed control (expected FAIL) ``` Do **not** dump decomp into `smartcontracts/` or `frontend-dapp/`. Keep the tree self-contained. ### Standard procedure (every code ID) 1. **Identity** — LCD `CodeInfo`: `code_id`, `data_hash`, creator, instantiate permission, contract count. Write `meta.json`. 2. **Fetch** — download wasm; SHA-256 **must** equal `data_hash`. Fail closed on mismatch. 3. **Fingerprint** — exports, `producers`/`rustc`, crate strings (`cw20_base`, `terraport_token`, `tax_map`, `requires_terra`), custom query/execute enums from error strings. 4. **Decompile** — `wasm2wat`, `wasm-decompile` (C-like), `wasm-objdump -x` / `-d` for `transfer`/`send`/`transfer_from`/`send_from`/`burn`/`mint` and any tax/hook/admin symbols. Store under `codeids/<id>/decomp/`. 5. **Static audit** — walk decomp against `CATALOG.md`. Record hits, misses, and unreadable regions. Extra queries (`balance_at`, `total_supply_at`, `tax_map`) must be classified (snapshot vs live mutation vs tax). 6. **Automated suite** — token-only VM tests, then DEX invariant tests (store wasm on LocalTerra **or** load via `cosmwasm-vm` in a pair harness). Persist logs/JUnit under the code-id folder or CI artifacts. 7. **Report** — fill `REPORT.md`: fingerprint, decomp findings, suite results vs catalogue, factory-global impact, instance-admin / migrate residual (F6), **go / no-go**. 8. **Optional appendix** — source URL / CertiK zip / rebuild hash. Never blocks go/no-go. ### Harness (two layers) **Layer A — token-only (`cosmwasm-vm`):** instantiate the LCD wasm (or skip instantiate and execute against a stored instance snapshot if init is exotic), then drive CW20 execute/query. No pair. Fast, CI-friendly, catches FoT/rebase/honeypot/allowance bugs. **Layer B — DEX + limits:** store the same wasm on LocalTerra (or a `cw-multi-test` custom wasm loader if feasible), whitelist **only in the test factory**, `CreatePair` with a **10184** counter-asset, then re-run the invariant matrix that touches CW20 balances (P1, P2, P3, P4, P10, R1–R4, C4, L1–L3, L6, L10, L11, sweep). Compare to the same scenario with 10184/10184 as control. Prefer **parameterized** tests (`CODE_ID=8266 make verify-issue-NNN`) over copy-paste per ID. ### Catalogue (research baseline — implement **every** row) Sources used for this issue (must be cited in `CATALOG.md` and kept current): - [d-xo/weird-erc20](https://github.com/d-xo/weird-erc20) (imBTC / Lendf.me ERC-777 reentrancy; STA / PAXG FoT; Balancer STA drain; USDT approve race; blocklist / pause; flash mint; proxied multi-address; high/low decimals; missing returns; permit no-ops) - Trail of Bits [token integration checklist](https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/token_integration.md) and [non-standard tokens](https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/non-standard-tokens.md) - Consensys Diligence token interaction checklist (fee, interest, ERC-777 hooks, allowance race) - [cw20-base execute surface](https://github.com/CosmWasm/cw-plus/blob/main/packages/cw20/src/msg.rs) (`Transfer`, `Send`, `Burn`, `Mint`, `Increase/DecreaseAllowance`, `TransferFrom`, `SendFrom`, `BurnFrom`, marketing, `UpdateMinter`) - [fragwuerdig/cw20-taxed](https://github.com/fragwuerdig/cw20-taxed) + Columbus-5 ALPHA **8654** `tax_map` (tax on `transfer`/`send`/`transfer_from`/`send_from`; contract vs wallet discrimination) - CosmWasm [CWA-2024-002](https://github.com/CosmWasm/advisories/blob/main/CWAs/CWA-2024-002.md) wrapping `Uint256::pow`; [ASA-2024-007](https://github.com/cosmos/ibc-go/security/advisories/GHSA-j496-crgh-34mx) / Terra–Astroport IBC-hooks infinite mint (token **used as** pool asset after unbacked mint) - SEC-D02–D06 in `docs/exploit-replay-matrix.md`; in-repo **P2** / **P3** / **R4** / **H1** - EVM honeypot / tax-abuse writeups (sell-only tax, pair-address block, hidden mint, allowance backdoor, trading toggle, max-wallet shrink): TokenToolHub honeypot guide; Hacken honeypot techniques; evm-token-guard pattern list - Flash-loan / donation / first-depositor inflation (Compound-fork / ERC-4626 class; Osmosis LP-share — SEC-D04); sandwich + `max_spread` (existing `security_tests`) Map **every** ERC-20 class to a CW20 test (or an explicit **N/A** with reason, e.g. “no `bool` return — CosmWasm errors instead”). --- ## Acceptance criteria - [ ] `cw20-codeid-audits/` exists with `PROCEDURE.md`, `CATALOG.md` (every catalogue row + citation), `report-template.md`, fetch + decompile scripts, and `codeids/<id>/` subfolders - [ ] `docs/runbooks/cw20-whitelist-policy.md` (and #581) no longer require compile-hash / byte-identical rebuild; they require this procedure - [ ] Harness Layer A runs against **arbitrary** pinned LCD wasm (parameterized by code ID) - [ ] Harness Layer B re-runs **all** DEX invariants that interact with asset CW20s on that wasm (see test plan) - [ ] Suite covers **all** common CW20 behaviors (see test plan) - [ ] Suite covers **all** catalogue exploit rows (pass = “this wasm does not exhibit the malice”; known-bad 8654 / FoT mock **fails** the 1:1 / P2 rows as expected) - [ ] Control: **10184** green on honest rows; **6036** green or documented exceptions; **8654** (or FoT fixture) red on FoT/P2 - [ ] `codeids/8266/REPORT.md` filled (decomp + suite). #581 may then record go/no-go from **that** report, not from a rebuild hash - [ ] `make verify-issue-<this>` (and agent skill) documented in `AGENTS.md` / playbook - [ ] Existing `audits/CW20-8266-581*.md` are linked or moved so there is one intake path --- ## Test plan — all paths ### Framework / intake paths | Path | Expected | |------|----------| | `fetch-lcd-wasm.sh` happy path | wasm + `data_hash` match → `meta.json` + `wasm.sha256` | | Fetch hash mismatch / truncated download | **FAIL closed**; no decomp, no suite | | Unknown / missing code ID on LCD | **FAIL** with LCD error; no fake pass | | Decompile tools missing | **FAIL** with install hint (`wabt`); do not skip decomp | | Re-run on already-fetched ID | Idempotent; does not overwrite `REPORT.md` conclusions | | `CODE_ID=10184` (control) | Full suite green on honest / invariant rows | | `CODE_ID=8654` or FoT fixture | 1:1 + P2 rows **FAIL** (expected); report template still generates | | Instantiate permission Everybody vs Nobody | Recorded; suite still instantiates via test key or uses a live instance snapshot | | Init requires exotic `marketing` / snapshot fields | Harness supplies minimal valid init **or** clones a live instantiate; document which | ### Common CW20 behaviors (Layer A — must implement all) For each of `Transfer`, `Send`, `TransferFrom`, `SendFrom`, `Burn`, `BurnFrom`, `Mint`, `IncreaseAllowance`, `DecreaseAllowance`, plus queries `Balance`, `TokenInfo`, `Allowance`, `Minter`, `AllAllowances` / `AllAccounts` if present: | Behavior | Expected for a listable template | |----------|----------------------------------| | Exact debit / credit | Recipient delta == declared `amount`; sender debit == `amount` (or burn/mint identity). **No silent fee.** | | `Send` / `SendFrom` hook | Receiver gets `Cw20ReceiveMsg` with **same** `amount`; 1:1 credit **before** hook; receiver revert rolls back balances | | Zero amount | Deterministic: reject **or** no-op; same as 10184; no reserve desync if later paired | | Self transfer | Balance unchanged net; no fee event | | Oversize transfer / insufficient allowance | Reject; no partial debit | | Unauthorized mint / burn-from | Reject | | Mint cap (if any) | Enforce; overflow rejected | | Allowance expire | `TransferFrom` / `SendFrom` reject after expiry | | `UpdateMinter` / marketing / logo | Cannot move balances; minter clear is one-way if spec says so | | Idle balance | After N empty blocks, all holder `balance` and `total_supply` unchanged (anti-rebase) | | Snapshot queries (`balance_at` / `total_supply_at`) | Historical only; **do not** mutate current balances | | Decimals / symbol / name | Present; decimals ≤ **18** for `CreatePair` bootstrap (**P3**); no script injection in name/symbol for dApp (record; frontend escape is separate) | | Supply conservation | Σ balances == `total_supply` after every honest op (spot-check + random walk) | ### DEX invariant paths (Layer B — must re-run on candidate wasm) Reuse IDs from `docs/contracts-security-audit.md`. **In scope** (asset is the candidate CW20; other side 10184 unless noted): | ID | What to assert with candidate wasm | |----|-------------------------------------| | **P1** | After swap, \(k' \ge k\) within documented rounding | | **P2** | After provide / swap / withdraw, pair `RESERVES` == CW20 `balance` of pair (no FoT skim, no silent donate credit) | | **P3** | First deposit burns `MINIMUM_LIQUIDITY`; decimals > 18 rejected | | **P4** / **P10** | Fees in range; failed swap leaves treasury unchanged | | **P5–P9** | Discount / factory-admin paths still hold (token is just the asset; run once per harness, not every ID, unless the token spoofs query) | | **F1–F6** | `CreatePair` accepts only after test whitelist; **F6** pin equals stored code ID; migrate-off-template still freezes (**already tested** in `asset_code_id_pin_tests` — run once + once with this wasm as the “honest then migrate” subject if admin exists) | | **R1–R4**, **C4** | Router single- and multi-hop; `minimum_receive` / per-hop `min_return`; **dust on output is not swept** (**R4**) | | **H1–H2**, **I2** | Allowlisted hook revert is atomic; spoofed pair rejected; tax-hook (protocol hook, not token tax) still settles from swap flow | | **L1–L3**, **L6**, **L10**, **L11** | Limit place via `Send` 1:1 escrow; cancel / claim refunds exact remaining; pause blocks CW20 refunds; batch refunds do not over-pay | | Sweep | Direct donate to pair is **not** LP shares (**P3** / existing donation tests); sweep recovers excess without rewriting honest reserves | **Out of scope for per-code-id Layer B** (keep existing `cw-multi-test` only): pure book-structure IDs that do not depend on token semantics (**L5**, **L12–L22** walk/hint/dust-flush/price-band), unless the token can change balances mid-match (then they become in scope). ### Docs / CI paths | Path | Expected | |------|----------| | `make verify-issue-<this>` without LocalTerra | Layer A + static fetch/decomp on cached wasm; Layer B skip **or** fail with `make has-localterra` hint (do not silent-pass) | | Cloud Agent | `make setup-cloud-localterra` then Layer B; no `SKIP (no LocalTerra)` without provisioning | | Policy docs | `rg` for “hash-equal” / “byte-identical” / “optimizer rebuild required” is empty except history notes | --- ## Test plan — attack, hack, and abuse vectors Each row is a **required** automated case (or explicit N/A). “Fail” means the **token is not listable** (or the DEX correctly rejects / stays solvent). Citations are the research baseline; implementers must add any newer public class found while building `CATALOG.md`. ### A. Malicious / weird CW20 (and ERC-20 analogues) | ID | Vector | ERC-20 analogue / incident | CW20 / DEX encoding | Listable? | |----|--------|----------------------------|---------------------|-----------| | A1 | Fee-on-transfer / deflationary transfer | STA, PAXG, USDT-optional fee; Balancer 2020 STA drain; AuditBase M006 | `Transfer`/`Send`/`TransferFrom`/`SendFrom` recipient delta &lt; declared; pair **P2** desync | **No** | | A2 | Directional / sell-only / buy-only tax | BSC tax tokens; Token-Tax-Abuse-Science | Fee when `recipient` or `sender` is pair/router; wallet↔wallet 1:1 | **No** | | A3 | Reflection / rebase / elastic supply | AMPL; “balance modifications outside transfers” (weird-erc20) | Idle `balance` or `total_supply` changes; `balance_at` that rewrites current | **No** | | A4 | Hidden / admin-settable tax (`tax_map`, `UpdateTaxMap`) | cw20-taxed; ALPHA **8654** | Query or decomp shows tax config; setter can turn on FoT after listing | **No** (F6 does not see same-code-id setter) | | A5 | Transfer hook / callback on plain `Transfer` | ERC-777 `tokensToSend` / `tokensReceived`; imBTC Uniswap; Lendf.me; Cream | `Transfer` (not only `Send`) dispatches `WasmMsg` to sender/recipient | **No** (reentrancy / grief) | | A6 | `Send` hook grief / reenter pair | ERC-677 `transferAndCall`; CosmWasm intended `Receive` | Receiver re-enters pair `Swap`/`Withdraw`/`PlaceLimit`; pair must fail closed (existing reentrancy tests + this wasm as offer token) | Must not extract value | | A7 | Allowance race / `IncreaseAllowance` surprise | ERC-20 approve race; USDT “must set 0 first” | CW20 uses increase/decrease — assert no `Approve {amount}` overwrite race; if custom `Approve` exists, test double-spend window | Document; **No** if spender can exceed intended cap | | A8 | Allowance backdoor | evm-token-guard `transferFrom` without allowance / `_isBot` | Admin or hardcoded addr moves tokens without allowance | **No** | | A9 | Blocklist / allowlist honeypot | USDC/USDT blocklist; pair-address sell block | `Transfer` to pair reverts for non-admin after buy; DEX withdraw/swap bricks | **No** | | A10 | Pause / trading toggle | BNB/ZIL pause; “enable trading” honeypot | Admin pause stops pair `Transfer` | **No** unless we control admin (record residual) | | A11 | Max-tx / max-wallet shrink | Soft honeypot | Admin lowers cap below pair balance | **No** | | A12 | Hidden mint / stealth balance write | Honeypot mint-to-drain | Mint without minter; direct balance map write; mint ignores cap | **No** | | A13 | Flash mint | DAI flash mint | Intra-tx supply `uint128::MAX` then burn; can break first-deposit / donation | **No** unless proven unused and P3 still holds | | A14 | Upgrade / `Migrate` surface | USDC proxy; weird-erc20 Upgradable | Instance admin + `MsgMigrateContract` — **F6** tested; report must list admin | Residual; F6 freeze required | | A15 | Proxied / multi-address token | Rescue-drain via second entry point | Two contracts share balances; sweep/rescue confusion | **No** | | A16 | Missing / lying query | Missing `bool` return; Tether Gold `false` success | CosmWasm: query `Balance` disagrees with transfer events; `TokenInfo.total_supply` ≠ sum | **No** | | A17 | High / low decimals | USDC 6; YAM 24; Gemini 2 | `decimals > 18` → `CreatePair` reject (**P3**); `0` decimals → overflow/precision tests | Follow P3; extra **No** if math breaks P1 | | A18 | Revert on zero / to-zero / huge amount | LEND zero transfer; OZ to-zero; UNI uint96 | Pair/router/limits must not brick on honest dust; if token reverts on amounts the pair uses, **No** | | A19 | `TransferFrom` self-semantics | DSToken vs OZ | Pair never relies on self-`TransferFrom`; still test | | A20 | Permit / phantom function | DAI permit; Multichain no-op permit | If `permit` exists and is a no-op, DEX must not assume allowance changed | N/A if absent | | A21 | Name/symbol XSS | Etherdelta JS in token name | Record; dApp must escape (existing frontend) | Not a whitelist veto alone | | A22 | Non-string metadata | MKR `bytes32` name | CW20 is string; if binary, queries break dApp | **No** if `TokenInfo` unreadable | | A23 | Interest / airdrop to holders | cToken / rebase-like drip | Pair balance grows without `Provide` → P2 desync or trapped yield | **No** | | A24 | Transfer less than `amount` when `amount == max` | cUSDCv3 max-uint special case | `Uint128::MAX` transfer credits only balance | **No** if pair can pass max | | A25 | Native-denom dual representation | CELO / POL / Uniswap V4 | CW20 wrap vs `uluna`/`uusd` — factory already rejects natives; test candidate is not secretly bank-denom | **No** if dual-spend | | A26 | IBC / bridged unbacked mint | ASA-2024-007; Terra 2024 Astroport | If wasm has IBC hooks / `ibc_receive`, treat as infinite-mint class | **No** unless proven unused | | A27 | CWA-2024-002 wrapping pow | cosmwasm-std &lt; 1.4.4 `Uint256::pow` | If token uses wrapping pow for balances, **No** | | A28 | `requires_terra` / classic taxer integration | Terra Classic burn tax on **natives**, not CW20 | Strings alone ≠ FoT; still prove 1:1 on CW20 path | Decide from tests | | A29 | Snapshot used as rebase | 8266 `balance_at` | Idle current balance stable; snapshot ≠ live mutate | **No** if live mutate | | A30 | Marketing / logo store blowup | Unbounded logo upload | DoS / gas grief only; record | Usually residual | ### B. DEX exploits that **use** a malicious or weird token | ID | Vector | What the suite must show | |----|--------|---------------------------| | B1 | Reserve desync → insolvent withdraw | FoT / rebase token: after swap, LP withdraw fails or steals the honest side (**P2**). Candidate must **not** produce this. Known-bad **must**. | | B2 | First-depositor / donation inflation | Donate candidate wasm to pair then provide; LP shares must not inflate (**P3**, SEC-D04, existing `test_direct_token_donation_does_not_inflate_lp_shares`) | | B3 | Flash provide → swap → withdraw | No risk-free profit (`test_flash_provide_swap_withdraw_no_profit`) with this token | | B4 | Router dust attribution | Pre-seed router with candidate token; hop must use delta only (**R4**) | | B5 | Hook + token callback sandwich | Token `Send` receiver + pair hook both re-enter; no double-pay | | B6 | Limit escrow skim | Place limit with FoT token: `PENDING_ESCROW` vs actual balance; cancel/claim honesty (**L1**). Candidate 1:1 only. | | B7 | Honeypot: buy on pool, cannot sell | Swap A→candidate succeeds, candidate→A reverts for non-admin | **No** | | B8 | Pair blacklisted by token after TVL | Token admin blocks pair address; withdraw/swap die | **No** unless we own admin | | B9 | Migrate-after-list to FoT | Honest wasm → `MsgMigrateContract` FoT → writes fail closed (**F6**). Already in `asset_code_id_pin_tests`; include this wasm as the honest side once. | | B10 | Sandwich / oracle tilt | Large swap vs `max_spread` / `min_return` still holds (existing security tests) | | B11 | Commission on failed swap | Token that reverts in `Transfer` mid-settlement: treasury unchanged (**P10**) | | B12 | Sweep confusion | Token with two addresses or lying `Balance` makes sweep steal pool | **No** | | B13 | CreatePair spam / Everybody instantiate | Report-only: listing 8266 admits all instances; not an automated pass | | B14 | Mint inflation vs “USD” ticker | SpaceUSD-style minter cap; report residual (not P2) | | B15 | IBC unbacked mint dumped on pool | If token is IBC-mintable, pool can be drained; **No** unless out of scope + written | ### C. Harness / process abuse | ID | Abuse | Expected | |----|-------|----------| | C1 | Swap in a rebuilt wasm that is **not** LCD `data_hash` | Fetch/pin step **FAIL**; suite refuses to run | | C2 | Skip decomp, run tests only | Procedure / CI **FAIL** (both required) | | C3 | Skip tests, attach only decomp prose | Procedure / CI **FAIL** | | C4 | Mark 8654 / FoT as go | Reviewer veto; known-bad job must be red | | C5 | Silent Layer B skip without LocalTerra | **FAIL** or explicit skip reason; no green verify target | | C6 | Commit mnemonic / admin key in `codeids/` | Hook or CI secret scan; reject | | C7 | Treat CertiK marketing / file-level Skynet hashes as `data_hash` | Procedure forbids; report template has “unverified third-party claim” checkbox | --- ## Verification criteria 1. `make verify-issue-<this>` is green on CI or Cloud Agent for **10184** (and FoT known-bad red). 2. Fresh clone: `scripts/fetch-lcd-wasm.sh 8266` + `decompile-wasm.sh` produce `codeids/8266/decomp/` and `wasm.sha256` matching LCD `953AD60C…` without any optimizer rebuild. 3. `codeids/8266/REPORT.md` exists with every procedure section, every catalogue row checked (pass / fail / N/A+reason), Layer A+B logs referenced, and an explicit **go** or **no-go**. 4. `rg` on `docs/runbooks/cw20-whitelist-policy.md` shows decomp+suite as the whitelist evidence; hash-equal rebuild is optional appendix only. 5. #581 description/checklist no longer requires compile-hash match; #581 remains **open** until the 8266 report is accepted. 6. Reviewer can re-run Layer A on the pinned wasm without network (cached) and get the same verdict. --- ## Labels / owner contracts + security + docs + testing + ops (whitelist procedure). Implementation can proceed without a 2-of-3 tx; **listing 8266** still needs #581 go after this ships. ## Priority P1 for #558 / #581 SpaceUSD/UST1 intake. Also the standing process for every future CW20 code ID.
PlasticDigits commented 2026-08-22 02:59:28 +00:00 (Migrated from gitlab.com)

marked as related to #581

marked as related to #581
PlasticDigits commented 2026-08-22 02:59:30 +00:00 (Migrated from gitlab.com)

Opened as the standing CW20 code-id intake: per-ID folder, LCD wasm pin (identity hash only), decompile, catalogue audit, and automated suite.

This gates #581. A rust-optimizer byte-identical rebuild is not required. #581 go/no-go must come from cw20-codeid-audits/codeids/8266/REPORT.md after this ships.

GitLab blocks issue links are unavailable on this license, so the gate is written in both descriptions and this related link.

Opened as the standing CW20 code-id intake: per-ID folder, LCD wasm pin (identity hash only), decompile, catalogue audit, and automated suite. This **gates #581**. A rust-optimizer byte-identical rebuild is **not** required. #581 go/no-go must come from `cw20-codeid-audits/codeids/8266/REPORT.md` after this ships. GitLab `blocks` issue links are unavailable on this license, so the gate is written in both descriptions and this related link.
PlasticDigits commented 2026-08-22 02:59:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #581

mentioned in issue #581
PlasticDigits commented 2026-08-22 03:10:03 +00:00 (Migrated from gitlab.com)

Supplement — additional automated-test areas for the suite (append-only)

Everything below is new surface relative to the existing plan (A1–A30 token classes, B1–B15 DEX vectors, C1–C7 process rows). Each item links to specific research or gives a coherent justification, and states what the suite must show. The recurring theme: the A-rows are mostly static, point-in-time, honest-probe properties; the largest detection gap is behavior that is honest at audit time / for the probe path and malicious later / for the DEX path, plus attacks on the off-chain trust chain (indexer/oracle/frontend) where pair solvency is never touched.

Additional research base (beyond the sources already in the issue):


D. Stateful / conditional / environment-dependent token behavior (detection-gap classes)

ID Vector Justification / citation Suite must show
D1 Time/height-activated behavior — honest before height/timestamp X, taxed or sell-blocked after PinkSale anti-bot schedules behavior by block number; Honeypot.is and Cube Exchange both warn a token sellable at scan time can become a honeypot later. A one-shot 1:1 probe cannot see this. Advance block_time/block_height (days, months, years) and re-run the full 1:1 + Layer B slice at each step. Any behavior delta = No.
D2 Magnitude-dependent behavior — dust transfers 1:1, whale transfers taxed/reverted; or minimum-transfer-amount revert GoPlus is_anti_whale, SafeMoon setMaxTxPercent, Honeypot.is maxBuy/maxSell fields exist precisely because amount-gated behavior is common. Small-amount probes pass by design. Amount fuzz from 1 unit → whole supply across all entrypoints; record any threshold where behavior changes. Min-transfer revert is a chain risk (see CH4), not auto-veto.
D3 History/op-count-dependent activation — first N transfers honest, then tax; holder-count or supply milestones flip behavior Stateful honeypots (Cube Exchange "stateful or dynamic control"); token-generator templates ship milestone features. Loop N (e.g. 1k) honest ops, re-probe 1:1; vary holder count and total supply across thresholds found in decomp.
D4 Caller-class discrimination — different behavior for EOA vs contract vs pair vs router vs treasury CW20 analogue of extcodesize honeypot checks: a CosmWasm contract can query ContractInfo on info.sender and branch. Trapdoor paper catalogs owner/sell-blocking modifiers; A2 covers pair-direction tax but not arbitrary caller classes. Transfer matrix over caller classes (EOA, bare contract, pair, router, treasury, limit book); any class-dependent delta = No.
D5 Entrypoint-selective taxation — Transfer 1:1 but Send/TransferFrom/SendFrom taxed (or vice versa) cw20-taxed (already cited) configures tax per entrypoint. Users probe wallet→wallet Transfer; the DEX path is Send (limits) and TransferFrom/Send (pair settlement). This is the CW20-specific honeypot shape. Independent 1:1 assertion per entrypoint, not a single "transfer" probe. Any entrypoint asymmetry = No.
D6 External dependency on the transfer path — transfer queries/calls a second contract (oracle-priced tax, external controller, "is trading enabled" flag) GoPlus external_call risk class. Also the CosmWasm analogue of Curve read-only reentrancy: token reads pair/router state mid-transfer to compute behavior (circular dependency: pair calls token, token queries pair's half-updated state). Decomp must enumerate every submessage/query on transfer/send/burn paths; harness mutates the dependency's state and re-tests; any external call on the transfer path = documented, default No.
D7 Token-contract-as-trader (auto-LP / swapAndLiquify) — the token's own transfer triggers the token contract to swap/add-liquidity on the pair mid-settlement SafeMoon swapAndLiquify + lockTheSwap; thousands of forks. In CosmWasm this is a submessage from token → pair while the pair is mid-execution — a reentrancy path B5 doesn't cover (B5 is receiver-hook reentry; this is token-initiated trading). Fixture token with auto-LP behavior; pair swap/provide/withdraw must fail closed or settle correctly when the token itself trades mid-transfer.
D8 Permissionless economic setters — any caller can register an AMM pair address, enable tax, or trigger balance-affecting functions SafeMoon's setAutomatedMarketMakerPair pattern; GoPlus owner_change_balance. A permissionless variant means tax/honeypot can activate after listing with no admin action — invisible to admin-key review. Enumerate every non-admin-gated execute; assert none alter balances, allowances, tax, or transferability. Any hit = No.
D9 Hidden owner / fake renounce / admin-of-admin — admin "renounced" to a contract with a public trigger, or ownership reclaimable GoPlus hidden_owner + can_take_back_ownership classes; Terraport on this chain was a centralization/insider loss. If admin is a contract, decomp/trace that contract's public surface; test that no public path re-seizes admin or moves balances. Record admin chain-of-custody in REPORT.md.
D10 Share-based / dual-balance semantics — Balance derived from shares × rate; full-balance transfer leaves ghost dust; total_supply ≠ Σ balances by design Lido stETH guide ("track shares, not balanceOf"); ChainSecurity documents the 1-wei ghost share that prevents state clearing. CW20 analogue breaks P2 (reserves vs balance) and sweep (dust that can never be swept). Full-balance transfer test (must empty exactly); rate-drift test (change underlying rate, assert pair P2 impact); ghost-dust × sweep interaction. Any derived-balance behavior = No for listing, or explicit P2 waiver analysis.
D11 Rate-limited transfers — per-address cooldown, per-block tx count, per-block volume cap GoPlus trading_cooldown; PinkSale "Time Limit Per Trade". Breaks batch and multi-op flows even when single transfers are honest. Two transfers from same address in one tx and in adjacent blocks; batch refund (L11) and ladder batch place (#494) with a cooldown fixture — must not silently strand funds (see CH3).
D12 Payable CW20 — transfer/send requires attached native funds Terra Classic taxes natives (Tax2Gas: contracts taxed on receive); a CW20 can additionally require funds on execute. Pair/router never attach natives → systematic settlement DoS; if any path does attach, value leaks. Execute every entrypoint with and without native funds attached; any funds requirement = No.
D13 Dynamic decimals / mutable TokenInfo — decimals (or supply) changes after pair bootstrap Nothing in wasm prevents a query returning different values over time. Pair bootstraps on decimals (P3); limit price band is human-scale from decimals (L20 / #529). Post-bootstrap change misprices the book (see CH13). Query TokenInfo repeatedly across intervening executes/migrates; any mutation = No.
D14 Init-parameter fuzz matrix — same wasm, adversarial instantiate config The whitelist is template-global (admits all 1686+ instances of 8266). Token-generator templates take tax %, max-tx, owner, and pair addresses as init params; an honest decomp of the wasm does not clear malicious instances. This is the highest-value gap between "code ID audit" and "listing safety". Parameterized instantiate matrix over the init schema (boundary + adversarial values); detect configs that enable FoT/honeypot/cooldown; report must state which init params are economically live and whether Everybody instantiate admits hostile configs.
D15 Stealth genesis allocation + state-enumeration honesty — init balances to undisclosed addresses; AllAccounts/AllAllowances pagination skips entries Hidden premine → soft rug on own pool (B14 is report-only; this is the detection test). Pagination that hides accounts conceals backdoor balances from reviewers. Reconstruct expected state from init msg + full op replay; diff against complete pagination of AllAccounts/AllAllowances; any state not reachable via queries = No.
D16 Event honesty — transfer without event, event with wrong amount, or forged sender Etherscan spoof tokens / MyCrypto PoC / Phantom Events paper. Distinct from SEC-D07 (which scopes events by emitting contract): here the token is the legitimate emitter of its own events but lies in them. The indexer ingests these events for tape/P&L. Every balance delta must have a matching event with exact amount/sender/receiver; indexer must reconcile token events against Balance queries and flag divergence (see CH16).
D17 Hidden / dormant execute & query variants — trojan branch behind a non-standard msg ({"rug":{}}), unknown-variant handling "Hidden state update" class (Art of the Scam). Decomp enumerates handlers; tests must confirm the negative space: unknown variants must error, never silently succeed or mutate. Fuzz unknown/malformed execute+query variants; decomp coverage report: every exported handler mapped to CW20 surface or flagged as extra (extras = audit focus).
D18 Receive msg parser robustness — oversized msg, duplicate JSON keys, deep nesting, unknown fields, nested Send loops CWA-2024-001: stack overflow in serde-json-wasm — a crafted msg can panic the pair's Receive handler. The token (or any sender) controls this msg. Msg fuzz matrix against pair/router Receive: size bounds, nesting depth, duplicate keys, unknown fields, self-referential Send. Must reject cleanly, never panic, never recurse.
D19 Gas-scaling benchmark — transfer cost grows with holder count (reflection distribution loops) or storage SWC-128 (DoS with block gas limit); SafeMoon/RFI reflection mechanics. Token swaps fine at 100 holders, exceeds block gas at 10k → slow brick after listing (see CH6). Measure transfer/swap gas vs holder-count curve (100/1k/10k/100k fixtures); fail if superlinear beyond block gas margin at realistic counts.
D20 Full CWA fingerprint sweep — map producers/crate strings against all CosmWasm advisories, not just CWA-2024-002 CWA index lists 2023-001…2025-007: stack overflows, gas mispricing, incorrect metering, non-deterministic module_query_safe, unbounded reply recursion, sub-context gas. A token compiled against an affected cosmwasm-std/wasmvm range inherits the class. Fingerprint → advisory matrix row per code ID in REPORT.md; any affected-range hit = documented decision.
D21 Query-side DoS — panicking or gas-heavy Balance/Allowance/TokenInfo queries; unbounded AllAccounts The indexer, route/solve (#485), and frontend poll token queries. A token that bricks or balloons queries poisons the off-chain stack even when on-chain settlement is fine. Query gas benchmarks + panic fuzz; indexer soak test with the candidate's queries in the hot path.
D22 Simulation-vs-execution divergence Hybrid quoting (#418) assumes quote == execute; Honeypot.is documents simulation as point-in-time. CosmWasm contracts can't detect simulation directly, but state-dependent gas/behavior still creates quote-time→exec-time divergence (TOCTOU). Quote→execute atomicity tests under adversarial interleaved state changes; document the residual explicitly in REPORT.md.

E. Additional DEX-interaction vectors (Layer B extensions)

ID Vector Justification / citation Suite must show
E1 Same-token pair (token0 == token1) MonoX $31M: swapping a token for itself double-wrote the price update and drained the protocol. CreatePair rejects identical assets; defense-in-depth: pair swap asserts offer ≠ ask even if factory bypassed; run with candidate wasm on both sides.
E2 Donation front-run / TOCTOU on Provide — attacker donates between the LP's quote and execution, diluting shares Euler donateToReserves class ($197M) + OZ ERC-4626 inflation analysis. B2 tests donation-then-provide; the missing case is donation interleaved between simulation and execution of an honest provide. Provide with min-shares bound under an interleaved donation in the same block; LP must get ≥ bound or tx reverts.
E3 Public burn of the pair's balance — unauthorized burn (distinct from A8 transfer backdoor) forces P2 desync, then sync/sweep exploitation Burn-side allowance backdoors appear in trapdoor datasets; cheaper to test than to reason about. Any public/under-authorized burn path = No; with fixture, show post-burn sweep/sync cannot extract honest LP value.
E4 Rebase × sweep, both directions — rebase-down then sweep extracts "excess"; rebase-up excess skimmed by third-party sweep caller Sweep recovers excess (existing), but elastic supply makes "excess" attacker-manipulable; value leaks from LPs to whoever calls sweep first. Elastic fixture: sweep under rebase-down must not touch honest reserves; rebase-up excess distribution must be documented and not LP-extractive.
E5 Flash-mint × first-deposit × burn composite Chains A13 + B2: mint u128::MAX → donate → provide dust → burn → victim provides at manipulated rate. Composite scenario test, not three isolated ones.
E6 Token-hook reentrancy on book operations — place/cancel/claim reentered via token callback B5 covers swap/withdraw; limit place is a Send (escrow) and claim/cancel are transfers out — same callback surface on the book side (L1–L3). Reentrancy fixtures on place/cancel/claim/match settlement; fail closed.
E7 Hook side-effects mid-batch — token hook mutates token state (e.g. blacklists the pair) between items of a batch refund / ladder place Batch ops (L11, #494) amortize gas across many transfers; a stateful hook can make item i+1 behave differently than item i within one tx. Batch atomicity under a hook that flips state mid-batch: full revert or exact per-item accounting, never partial over/under-pay.
E8 Token-level pause/blacklist during parked-expired claim window L22/#504 parks expired limits; L6/#120 blocks claim under pair pause. A token-level pause during the park window bricks the claim until unpause — if the admin never unpauses, maker funds are permanently trapped. Claim path with a pausable fixture: parked funds must remain claimable-or-safely-parked under token pause; residual risk written in REPORT.md.
E9 Cyclic routes with the candidate token twice (A→B→A) R4 dust attribution is tested per-hop; a cycle re-enters the same token's transfer logic twice in one tx with intermediate router balances — the exact shape rate-limited (D11) and hook (A5) tokens break. Multi-hop cycle with candidate at both ends; per-hop min_return and dust attribution hold.
E10 Solver/graph robustness under template spam — Everybody instantiate → CreatePair spam from the candidate template; token queries that fail B13 is report-only. The automatable part: route/solve latency (#485) under N junk pairs, and graceful exclusion (not 500) when a pair's token queries fail. Solver benchmark with 1k/10k junk pairs from candidate wasm; fault-injection: pair-info query failure → hop excluded, solve succeeds.
E11 Zap floors with candidate token — one-sided add/withdraw (Z533/Z559) Zap execution must follow floors, not optimistic quotes; FoT/rebase breaks zap accounting exactly where users can't see it. Zap-in/out with candidate wasm: execution ≥ floor or revert; no silent shortfall credit.
E12 Wrap-mapper / UST1-window adjacency If the candidate template is ever used for cUSTC/vFDUSD-like assets: unwrap dual-read (#523), wrap link identity (#570), oracle window mint/redeem 1:1 (#506). Conditional, but must be a checklist row so it isn't forgotten when such a template arrives. Conditional suite section: wrap/unwrap/window 1:1 + impostor-identity rejection.
E13 Fee-treasury accounting in the candidate token F4 rotates fees to treasury; fees denominated in a FoT/rebase token drift between accrual and sweep. Treasury accrual vs actual balance reconciliation with weird-token fixture.
E14 Math-boundary fuzz at u128/u256 edges — decimals = 18, supply near u128::MAX, price ratios at extremes A17 covers decimals > 18 rejection; the boundary within limits (18 decimals, max supply) stresses pair k-math, limit price band (L20), and any cumulative price/TWAP state for overflow/wrap. Boundary matrix: no panic, no wrap, k-monotonicity (P1) within documented rounding.
E15 Rounding-direction arbitrage loop P1 is asserted per-swap; the adversarial version loops micro provide/swap/withdraw cycles harvesting rounding dust — with an attacker-issued (zero-cost) token this is free money if rounding leaks. N-iteration loop: k must not decrease beyond documented rounding; no risk-free profit (extends test_flash_provide_swap_withdraw_no_profit).

F. Off-chain trust chain — the token attacks the displayed truth, not pair solvency

Pair solvency can be perfect while users are robbed via the indexer/oracle/frontend layer. These rows make that explicit and automatable.

ID Vector Justification / citation Suite must show
F1 Event-vs-state consistency at the indexer See D16. Indexer tape/P&L built from token events can be poisoned by a lying-but-legitimate emitter even with SEC-D07 contract scoping correct. Indexer test: ingest lying-event fixture; tape amounts reconciled against balance deltas; divergence flagged, not silently indexed.
F2 Metadata injection — NUL bytes / invalid strings in name/symbol; homoglyph/RTL tickers impersonating UST1/cUSTC; SVG logo payload Postgres text rejects the NUL character (docs) → one malicious instantiation can stall ingest; homoglyph tickers are the fake_token class (GoPlus); SVG XSS is a known payload carrier. Metadata fuzz at ingest (NUL, oversized, control chars) must not crash or stall the indexer; frontend ticker dedupe + escaping test; logo allowlist covers SVG (record-only).
F3 Hub-price tilt via attacker-seeded pool #556 derives cUSTC/UST1/USTR USD from the largest-liquidity pool. An attacker with a mintable token can make their pool the largest and tilt every USD figure on the dApp. Hub derivation test: attacker pool with fake depth must not move hub USD beyond deviation caps; anchor/allowlist behavior documented.
F4 External-oracle ticker collision #515/#580 key external USD feeds by ticker (ustc/lunc/vfdusd). A candidate token named "USTC" or "FDUSD" invites mis-attribution. Oracle lookup keyed by contract address, never ticker; test with colliding-ticker fixture.
F5 Candle-close sniping #568 marks candle USD at time-stamped marks with idle mark-to-market. A dust swap at an extreme price just before a boundary skews the mark; with a zero-cost attacker token this is cheap. Candle mark tests: dust-at-extreme-price fixture must not move the USD mark (TWAP/median, not last-trade).
F6 Wash-volume stats poisoning Trader leaderboard (#553) and trailing windows (#576/#577) count volume; self-trades with an attacker-issued token are zero-cost and inflate ranks/24h stats. Volume-attribution test with self-trade fixture; documented heuristic (same-beneficial-owner discounting) or explicit accepted-risk note.
F7 Query-DoS → indexer lag → stale oracle chain D21 chained off-chain: slow token queries lag the indexer, which silently degrades every downstream USD figure. Indexer lag alarm/behavior test under slow-query fixture; stale-data surfacing (existing stale-overview row #577) must trigger.
F8 P&L from events vs balance deltas #551/#560 compute realized P&L; FoT/rebase tokens make event amounts ≠ balance deltas. P&L reconciliation test with FoT fixture: indexer must use balance deltas or flag the pair as unmetered.

G. Harness / methodology additions

ID Addition Justification / citation
G1 Differential op-sequence testing vs cw20-base — run identical randomized op sequences (proptest-style random walks with shrinking) against the candidate wasm and a reference cw20-base; diff all state transitions Differential testing is the standard compiler/token correctness methodology (Csmith lineage); catches unknown-unknowns that per-row checklists miss.
G2 Mutant library as regression oracles — beyond the single 8654/FoT known-bad control: a checked-in set of generated cw20-base mutants (FoT, rebase, sell-block, cooldown, entrypoint-selective tax, event-liar, hidden-mint, ghost-dust…) that the suite must catch Mutation testing (cargo-mutants methodology). One known-bad control proves the suite catches one class; a mutant per D/A row proves each row's test actually fires.
G3 Differential decompilation — bindiff/Diaphora-style comparison of candidate wasm against a reference cw20-base build to surface injected basic blocks; publish a decomp coverage metric (% functions mapped; unmapped = audit gap) Diaphora / BinDiff are the standard binary-diffing tools; turns "decomp looks like cw20-base" into a measurable claim.
G4 Multi-endpoint consensus fetch — CodeInfo.data_hash from ≥2 independent LCD/RPC endpoints before pinning The pin proves we have the on-chain binary only if the endpoint is honest; a single compromised LCD can serve a trojaned wasm with a matching claimed hash.
G5 Live-instance sampling — enumerate existing instances of the code ID (1686+ for 8266) and sample-query their configs (tax_map, minter, admin, pause) Template honest ≠ instances honest (D14); sampling quantifies the real-world blast radius of a whitelist decision and feeds REPORT.md's factory-global section.
G6 Time-travel / state-fuzz harness primitives — first-class support for advancing height/time, op-count histories, and holder-count scaling Required by D1/D3/D19; without harness support these rows degrade to prose.
G7 Post-listing runtime monitoring + F6-freeze drill — watchtower alerts on instance migrate events, admin changes, tax-setter calls, and gov param changes (e.g. burn_tax) Listing is point-in-time (Honeypot.is caveat; D1/D8/D9 classes activate later). Tax2Gas history shows chain params move by governance. The drill measures time-to-freeze, which is the real control after a late activation.
G8 Host-import + IBC-entrypoint enumeration in the fingerprint Extends A26: enumerate all IBC entrypoints and unexpected host imports, not just ibc_receive strings.
G9 Seeded-wrong harness self-tests — truncated wasm, non-wasm bytes, hash-mismatch, wrong-architecture binary must all fail closed Extends C1 from "rebuilt wasm" to arbitrary corrupted input; a harness that green-lights garbage is worse than no harness.

CH. Exploit-chaining scenarios (explicit multi-step tests)

The point of this supplement: single-row tests pass while compositions drain value. Each chain is one automated scenario.

ID Chain Suite must show
CH1 D1 time-activated tax → post-listing B7 honeypot: honest at audit, sells revert after height H Time-travel re-probe catches activation before listing; G7 monitoring catches it after.
CH2 D8 permissionless pair-registration → D5 entrypoint-selective tax: after CreatePair, anyone registers the pair address; only Send/TransferFrom (the DEX paths) get taxed Post-create re-run of per-entrypoint 1:1; registration must not change DEX-path behavior.
CH3 D11 cooldown × L11 batch refunds / #494 ladder batch: second transfer in a batch reverts → whole batch fails or strands funds Batch ops with cooldown fixture: atomic revert with full recovery, never partial stranding.
CH4 D2 minimum-transfer × L22 parked dust refunds: park refund below token minimum → claim bricked forever Dust-park claim with min-transfer fixture: park must round up, merge, or refuse — never brick.
CH5 A8 allowance backdoor × L1 escrow: admin TransferFrom pulls the pair's escrowed maker balances while the book looks intact Escrow drain attempt must fail; alarm test proves the suite notices escrow/balance divergence.
CH6 D19 reflection O(n) × holder growth × swap gas: listable at 100 holders, swaps exceed block gas at 10k Gas-curve projection row fails the listing before the slow brick.
CH7 A3 rebase-down → sweep extracts "excess" → rebase-up → LPs short E4 scenario; sweep must be rebase-aware or token vetoed.
CH8 A13 flash mint → B2 donation → provide dust → burn → victim deposit captured E5 composite.
CH9 A26/B15 unbacked mint → router multihop dump → UST1 oracle pairs → depeg cascade into the #506 window Conditional on IBC/bridge entrypoints (G8); mint-rights inventory in REPORT.md.
CH10 D16 event spoof → indexer tape → frontend price/volume → users trade on phantom liquidity; pair solvency untouched F1 reconciliation; spoofed tape never reaches the dApp unflagged.
CH11 F2 NUL-byte metadata → indexer ingest stall → stale hub USD everywhere → mispriced display sitewide F2 fuzz + F7 stale-surfacing.
CH12 F3 hub tilt → #569 protocol stats + #553 leaderboard poisoned from one attacker pool F3 deviation caps.
CH13 D13 dynamic decimals → post-bootstrap price-band shift (L20) → mispriced resting limits → fill arbitrage drains makers Decimals-mutation fixture through place/fill; book reprices or freezes, never fills at stale scale.
CH14 D7 swapAndLiquify → token contract reenters pair mid-sell → mid-settlement price shift E6/D7 fixture; pair fails closed.
CH15 D9 fake renounce → admin re-seize → B9 migrate to a different but whitelisted code ID → F6 must still freeze (pin is the listing ID, not "any whitelisted ID") Migrate-to-other-whitelisted-ID scenario explicitly in asset_code_id_pin_tests style; freeze confirmed.
CH16 D16 event-liar × F8: token emits full-amount events, delivers less (FoT) → indexer P&L/tape wrong even though pair P2 catches the desync on-chain Indexer reconciliation test; the two detection layers must agree or alarm.
CH17 E1 same-token pair × hidden mint: MonoX-style self-swap price overwrite, then drain Factory reject + pair-level assert; historical MonoX replay encoded as a test (DeFiHackLabs methodology).
CH18 D12 payable transfer × router: every routed swap fails (DoS), or worse, a path that attaches funds leaks natives to the token Router with payable fixture: clean rejection, no funds attachment anywhere.

Documented N/A mappings (record in CATALOG.md; do not spend test budget)

  • tx.origin authentication tricks — no tx.origin in CosmWasm; info.sender only. (Trapdoor paper catalogs tx.origin sell-blocks; class is EVM-only.)
  • extcodesize == 0 caller checks — CosmWasm analogue exists via ContractInfo query; covered as D4.
  • selfdestruct / CREATE2 metamorphic redeploy — no selfdestruct; code IDs are immutable; the metamorphosis surface is MsgMigrateContract, covered by A14/B9/F6 (+CH15).
  • Missing bool return / return-data bombs — CosmWasm errors instead of returning false; wasm memory model differs (already noted in the issue).
  • Read-only reentrancy (Curve get_virtual_price) — CosmWasm queries cannot re-enter a mid-execution contract, and the indexer reads only committed state (Tendermint finality). The residual analogues are covered: token reading pair state mid-transfer (D6) and display-layer staleness (F5/F7).
  • Compiler-level honeypots (Art of the Scam "Solidity compiler" class) — no direct wasm analogue, but the July 2023 Vyper lock miscompilation shows toolchain provenance matters; covered by D20 fingerprint sweep + G3 differential decompilation.

Suggested landing: append D/E/F/G/CH to CATALOG.md as new sections and reference this note from the 8266 REPORT.md checklist so every row gets pass/fail/N/A+reason like the existing A/B/C rows.

## Supplement — additional automated-test areas for the suite (append-only) Everything below is **new surface** relative to the existing plan (A1–A30 token classes, B1–B15 DEX vectors, C1–C7 process rows). Each item links to specific research or gives a coherent justification, and states what the suite must show. The recurring theme: the A-rows are mostly **static, point-in-time, honest-probe** properties; the largest detection gap is behavior that is honest at audit time / for the probe path and malicious later / for the DEX path, plus attacks on the **off-chain trust chain** (indexer/oracle/frontend) where pair solvency is never touched. Additional research base (beyond the sources already in the issue): - [GoPlus Token Risk Classification](https://whitepaper.gopluslabs.io/goplus-network/the-goplus-security-layer/security-data-layer/token-risk-classification) + [response field list](https://docs.gopluslabs.io/reference/response-details-1) (`hidden_owner`, `can_take_back_ownership`, `owner_change_balance`, `trading_cooldown`, `is_anti_whale`, `external_call`, `fake_token`) - [Trapdoor tokens on Uniswap (BCRA 2025)](https://doi.org/10.1016/j.bcra.2025.100370), [Trade or Trick? (Xia et al., PODC/POMACS 2021)](https://arxiv.org/abs/2109.00229), [Do not rug on me (Mazorra et al. 2022)](https://arxiv.org/abs/2201.07220), [The Art of the Scam (Torres & Steichen, USENIX Sec '19)](https://www.usenix.org/system/files/sec19-torres.pdf) - [Honeypot.is IsHoneypot methodology](https://docs.honeypot.is/ishoneypot) (buy/sell simulation; `maxBuy`/`maxSell` detection; explicit point-in-time caveat) and [Cube Exchange on stateful/dynamic honeypot control](https://www.cube.exchange/what-is/honeypot-token) - [PinkSale Pink Anti-Bot docs](https://docs.pinksale.finance/pink-anti-bot/pink-anti-bot-guide) (per-tx amount limits that grow per block, per-wallet time cooldowns, block-scheduled disable — evidence that time/amount/state-gated transfer behavior is a standard token feature) - [SafeMoon.sol](https://github.com/safemoonprotocol/Safemoon.sol/blob/main/Safemoon.sol) (`swapAndLiquify`, `setAutomatedMarketMakerPair`, `setTaxFeePercent`, `setMaxTxPercent`, `_isExcludedFromFee`) — the token contract as an autonomous trader on its own pair - [Lido stETH integration guide](https://github.com/lidofinance/docs/blob/main/docs/guides/lido-tokens-integration-guide.md) + [ChainSecurity: Hitchhiker's Guide to Rebasing Tokens](https://www.chainsecurity.com/blog/the-hitchhikers-guide-to-rebasing-tokens) (share-based balances; 1-wei ghost-share dust; "track shares, not balanceOf") - [ChainSecurity: Curve LP oracle read-only reentrancy post-mortem](https://www.chainsecurity.com/blog/curve-lp-oracle-manipulation-post-mortem) and the July 2023 Vyper 0.2.15/0.2.16/0.3.0 reentrancy-lock miscompilation (~$52M; [summary](https://www.kayssel.com/newsletter/issue-57/)) — compiler/toolchain fingerprint matters, not just source - [MonoX post-mortem](https://medium.com/monoswap/exploit-post-mortem-33921a779b43) + [SlowMist analysis](https://slowmist.medium.com/detailed-analysis-of-the-31-million-monox-protocol-hack-574d8c44a9c8) ($31M; tokenIn == tokenOut) - [Euler $197M donation/self-liquidation](https://www.zellic.io/blog/euler-finance-exploit-analysis) + [OpenZeppelin: ERC-4626 inflation defense](https://www.openzeppelin.com/news/a-novel-defense-against-erc4626-inflation-attacks) - Event forgery: [Etherscan on spoof tokens](https://medium.com/etherscan-blog/spoof-tokens-on-ethereum-c2ad882d9cf6), [MyCrypto PoC](https://blog.mycrypto.com/bad-actors-abusing-etherscan-to-trick-you/), [Phantom Events (arXiv 2502.13513)](https://arxiv.org/html/2502.13513) (zero-transfer scam: $27.36M, 28k victims) - Unbacked mint beyond IBC: [Wormhole $326M (CertiK)](https://www.certik.com/blog/wormhole-bridge-exploit-incident-analysis), [EIP-7281 xERC20](https://eips.ethereum.org/EIPS/eip-7281) (bridge minter-rights standard) - [CosmWasm CWA index](https://github.com/CosmWasm/advisories/blob/main/CWAs/README.md) — full sweep 2023-001…2025-007, incl. [CWA-2024-001 serde-json-wasm stack overflow](https://github.com/CosmWasm/advisories/blob/main/CWAs/CWA-2024-001.md) (directly relevant to `Receive` msg parsing) - Terra Classic specifics: [v3.3.0 Tax2Gas notes](https://github.com/classic-terra/documents/blob/main/chain-updates/v3_3_0.md) (`burn_tax` is a live gov parameter; contracts taxed on receive), [Terraport $4M post-mortem (CertiK)](https://www.certik.com/blog/post-mortem-terraport-finance) (centralization/insider class on this exact chain), [DeFiHackLabs](https://github.com/SunWeb3Sec/DeFiHackLabs) (850+ incident PoCs — the replay-methodology model this harness should mirror) --- ### D. Stateful / conditional / environment-dependent token behavior (detection-gap classes) | ID | Vector | Justification / citation | Suite must show | |----|--------|--------------------------|-----------------| | D1 | **Time/height-activated behavior** — honest before height/timestamp X, taxed or sell-blocked after | PinkSale anti-bot schedules behavior by block number; Honeypot.is and Cube Exchange both warn a token sellable at scan time can become a honeypot later. A one-shot 1:1 probe cannot see this. | Advance `block_time`/`block_height` (days, months, years) and re-run the full 1:1 + Layer B slice at each step. Any behavior delta = **No**. | | D2 | **Magnitude-dependent behavior** — dust transfers 1:1, whale transfers taxed/reverted; or minimum-transfer-amount revert | GoPlus `is_anti_whale`, SafeMoon `setMaxTxPercent`, Honeypot.is `maxBuy`/`maxSell` fields exist precisely because amount-gated behavior is common. Small-amount probes pass by design. | Amount fuzz from 1 unit → whole supply across all entrypoints; record any threshold where behavior changes. Min-transfer revert is a **chain risk** (see CH4), not auto-veto. | | D3 | **History/op-count-dependent activation** — first N transfers honest, then tax; holder-count or supply milestones flip behavior | Stateful honeypots (Cube Exchange "stateful or dynamic control"); token-generator templates ship milestone features. | Loop N (e.g. 1k) honest ops, re-probe 1:1; vary holder count and total supply across thresholds found in decomp. | | D4 | **Caller-class discrimination** — different behavior for EOA vs contract vs pair vs router vs treasury | CW20 analogue of `extcodesize` honeypot checks: a CosmWasm contract *can* query `ContractInfo` on `info.sender` and branch. Trapdoor paper catalogs owner/sell-blocking modifiers; A2 covers pair-direction tax but not arbitrary caller classes. | Transfer matrix over caller classes (EOA, bare contract, pair, router, treasury, limit book); any class-dependent delta = **No**. | | D5 | **Entrypoint-selective taxation** — `Transfer` 1:1 but `Send`/`TransferFrom`/`SendFrom` taxed (or vice versa) | cw20-taxed (already cited) configures tax **per entrypoint**. Users probe wallet→wallet `Transfer`; the DEX path is `Send` (limits) and `TransferFrom`/`Send` (pair settlement). This is the CW20-specific honeypot shape. | Independent 1:1 assertion per entrypoint, not a single "transfer" probe. Any entrypoint asymmetry = **No**. | | D6 | **External dependency on the transfer path** — transfer queries/calls a second contract (oracle-priced tax, external controller, "is trading enabled" flag) | GoPlus `external_call` risk class. Also the CosmWasm analogue of Curve read-only reentrancy: token reads pair/router state mid-transfer to compute behavior (circular dependency: pair calls token, token queries pair's half-updated state). | Decomp must enumerate every submessage/query on transfer/send/burn paths; harness mutates the dependency's state and re-tests; any external call on the transfer path = documented, default **No**. | | D7 | **Token-contract-as-trader (auto-LP / swapAndLiquify)** — the token's own transfer triggers the token contract to swap/add-liquidity on the pair mid-settlement | SafeMoon `swapAndLiquify` + `lockTheSwap`; thousands of forks. In CosmWasm this is a submessage from token → pair *while the pair is mid-execution* — a reentrancy path B5 doesn't cover (B5 is receiver-hook reentry; this is token-initiated trading). | Fixture token with auto-LP behavior; pair swap/provide/withdraw must fail closed or settle correctly when the token itself trades mid-transfer. | | D8 | **Permissionless economic setters** — any caller can register an AMM pair address, enable tax, or trigger balance-affecting functions | SafeMoon's `setAutomatedMarketMakerPair` pattern; GoPlus `owner_change_balance`. A *permissionless* variant means tax/honeypot can activate **after** listing with no admin action — invisible to admin-key review. | Enumerate every non-admin-gated execute; assert none alter balances, allowances, tax, or transferability. Any hit = **No**. | | D9 | **Hidden owner / fake renounce / admin-of-admin** — admin "renounced" to a contract with a public trigger, or ownership reclaimable | GoPlus `hidden_owner` + `can_take_back_ownership` classes; Terraport on this chain was a centralization/insider loss. | If admin is a contract, decomp/trace that contract's public surface; test that no public path re-seizes admin or moves balances. Record admin chain-of-custody in REPORT.md. | | D10 | **Share-based / dual-balance semantics** — `Balance` derived from shares × rate; full-balance transfer leaves ghost dust; `total_supply` ≠ Σ balances by design | Lido stETH guide ("track shares, not balanceOf"); ChainSecurity documents the 1-wei ghost share that prevents state clearing. CW20 analogue breaks P2 (reserves vs balance) and sweep (dust that can never be swept). | Full-balance transfer test (must empty exactly); rate-drift test (change underlying rate, assert pair P2 impact); ghost-dust × sweep interaction. Any derived-balance behavior = **No** for listing, or explicit P2 waiver analysis. | | D11 | **Rate-limited transfers** — per-address cooldown, per-block tx count, per-block volume cap | GoPlus `trading_cooldown`; PinkSale "Time Limit Per Trade". Breaks *batch* and *multi-op* flows even when single transfers are honest. | Two transfers from same address in one tx and in adjacent blocks; batch refund (L11) and ladder batch place (#494) with a cooldown fixture — must not silently strand funds (see CH3). | | D12 | **Payable CW20** — transfer/send requires attached native funds | Terra Classic taxes natives (Tax2Gas: contracts taxed on *receive*); a CW20 can additionally require `funds` on execute. Pair/router never attach natives → systematic settlement DoS; if any path does attach, value leaks. | Execute every entrypoint with and without native funds attached; any funds requirement = **No**. | | D13 | **Dynamic decimals / mutable TokenInfo** — `decimals` (or supply) changes after pair bootstrap | Nothing in wasm prevents a query returning different values over time. Pair bootstraps on decimals (P3); limit price band is human-scale from decimals (L20 / #529). Post-bootstrap change misprices the book (see CH13). | Query `TokenInfo` repeatedly across intervening executes/migrates; any mutation = **No**. | | D14 | **Init-parameter fuzz matrix** — same wasm, adversarial instantiate config | The whitelist is template-global (admits all 1686+ instances of 8266). Token-generator templates take tax %, max-tx, owner, and pair addresses as **init params**; an honest decomp of the wasm does not clear malicious *instances*. This is the highest-value gap between "code ID audit" and "listing safety". | Parameterized instantiate matrix over the init schema (boundary + adversarial values); detect configs that enable FoT/honeypot/cooldown; report must state which init params are economically live and whether `Everybody` instantiate admits hostile configs. | | D15 | **Stealth genesis allocation + state-enumeration honesty** — init balances to undisclosed addresses; `AllAccounts`/`AllAllowances` pagination skips entries | Hidden premine → soft rug on own pool (B14 is report-only; this is the detection test). Pagination that hides accounts conceals backdoor balances from reviewers. | Reconstruct expected state from init msg + full op replay; diff against complete pagination of `AllAccounts`/`AllAllowances`; any state not reachable via queries = **No**. | | D16 | **Event honesty** — transfer without event, event with wrong amount, or forged sender | Etherscan spoof tokens / MyCrypto PoC / Phantom Events paper. Distinct from SEC-D07 (which scopes events by emitting contract): here the token **is** the legitimate emitter of its own events but lies in them. The indexer ingests these events for tape/P&L. | Every balance delta must have a matching event with exact amount/sender/receiver; indexer must reconcile token events against `Balance` queries and flag divergence (see CH16). | | D17 | **Hidden / dormant execute & query variants** — trojan branch behind a non-standard msg (`{"rug":{}}`), unknown-variant handling | "Hidden state update" class (Art of the Scam). Decomp enumerates handlers; tests must confirm the *negative space*: unknown variants must error, never silently succeed or mutate. | Fuzz unknown/malformed execute+query variants; decomp coverage report: every exported handler mapped to CW20 surface or flagged as extra (extras = audit focus). | | D18 | **`Receive` msg parser robustness** — oversized msg, duplicate JSON keys, deep nesting, unknown fields, nested `Send` loops | CWA-2024-001: stack overflow in serde-json-wasm — a crafted msg can panic the *pair's* Receive handler. The token (or any sender) controls this msg. | Msg fuzz matrix against pair/router Receive: size bounds, nesting depth, duplicate keys, unknown fields, self-referential Send. Must reject cleanly, never panic, never recurse. | | D19 | **Gas-scaling benchmark** — transfer cost grows with holder count (reflection distribution loops) or storage | SWC-128 (DoS with block gas limit); SafeMoon/RFI reflection mechanics. Token swaps fine at 100 holders, exceeds block gas at 10k → slow brick after listing (see CH6). | Measure transfer/swap gas vs holder-count curve (100/1k/10k/100k fixtures); fail if superlinear beyond block gas margin at realistic counts. | | D20 | **Full CWA fingerprint sweep** — map `producers`/crate strings against *all* CosmWasm advisories, not just CWA-2024-002 | CWA index lists 2023-001…2025-007: stack overflows, gas mispricing, incorrect metering, non-deterministic `module_query_safe`, unbounded reply recursion, sub-context gas. A token compiled against an affected cosmwasm-std/wasmvm range inherits the class. | Fingerprint → advisory matrix row per code ID in REPORT.md; any affected-range hit = documented decision. | | D21 | **Query-side DoS** — panicking or gas-heavy `Balance`/`Allowance`/`TokenInfo` queries; unbounded `AllAccounts` | The indexer, route/solve (#485), and frontend poll token queries. A token that bricks or balloons queries poisons the off-chain stack even when on-chain settlement is fine. | Query gas benchmarks + panic fuzz; indexer soak test with the candidate's queries in the hot path. | | D22 | **Simulation-vs-execution divergence** | Hybrid quoting (#418) assumes quote == execute; Honeypot.is documents simulation as point-in-time. CosmWasm contracts can't detect simulation directly, but state-dependent gas/behavior still creates quote-time→exec-time divergence (TOCTOU). | Quote→execute atomicity tests under adversarial interleaved state changes; document the residual explicitly in REPORT.md. | --- ### E. Additional DEX-interaction vectors (Layer B extensions) | ID | Vector | Justification / citation | Suite must show | |----|--------|--------------------------|-----------------| | E1 | **Same-token pair** (`token0 == token1`) | MonoX $31M: swapping a token for itself double-wrote the price update and drained the protocol. | `CreatePair` rejects identical assets; defense-in-depth: pair swap asserts offer ≠ ask even if factory bypassed; run with candidate wasm on both sides. | | E2 | **Donation front-run / TOCTOU on Provide** — attacker donates between the LP's quote and execution, diluting shares | Euler `donateToReserves` class ($197M) + OZ ERC-4626 inflation analysis. B2 tests donation-then-provide; the missing case is donation *interleaved between simulation and execution* of an honest provide. | Provide with min-shares bound under an interleaved donation in the same block; LP must get ≥ bound or tx reverts. | | E3 | **Public burn of the pair's balance** — unauthorized burn (distinct from A8 transfer backdoor) forces P2 desync, then sync/sweep exploitation | Burn-side allowance backdoors appear in trapdoor datasets; cheaper to test than to reason about. | Any public/under-authorized burn path = **No**; with fixture, show post-burn sweep/sync cannot extract honest LP value. | | E4 | **Rebase × sweep, both directions** — rebase-down then sweep extracts "excess"; rebase-up excess skimmed by third-party sweep caller | Sweep recovers excess (existing), but elastic supply makes "excess" attacker-manipulable; value leaks from LPs to whoever calls sweep first. | Elastic fixture: sweep under rebase-down must not touch honest reserves; rebase-up excess distribution must be documented and not LP-extractive. | | E5 | **Flash-mint × first-deposit × burn composite** | Chains A13 + B2: mint u128::MAX → donate → provide dust → burn → victim provides at manipulated rate. | Composite scenario test, not three isolated ones. | | E6 | **Token-hook reentrancy on book operations** — place/cancel/claim reentered via token callback | B5 covers swap/withdraw; limit place is a `Send` (escrow) and claim/cancel are transfers out — same callback surface on the book side (L1–L3). | Reentrancy fixtures on place/cancel/claim/match settlement; fail closed. | | E7 | **Hook side-effects mid-batch** — token hook mutates token state (e.g. blacklists the pair) between items of a batch refund / ladder place | Batch ops (L11, #494) amortize gas across many transfers; a stateful hook can make item *i+1* behave differently than item *i* within one tx. | Batch atomicity under a hook that flips state mid-batch: full revert or exact per-item accounting, never partial over/under-pay. | | E8 | **Token-level pause/blacklist during parked-expired claim window** | L22/#504 parks expired limits; L6/#120 blocks claim under *pair* pause. A *token-level* pause during the park window bricks the claim until unpause — if the admin never unpauses, maker funds are permanently trapped. | Claim path with a pausable fixture: parked funds must remain claimable-or-safely-parked under token pause; residual risk written in REPORT.md. | | E9 | **Cyclic routes with the candidate token twice** (A→B→A) | R4 dust attribution is tested per-hop; a cycle re-enters the same token's transfer logic twice in one tx with intermediate router balances — the exact shape rate-limited (D11) and hook (A5) tokens break. | Multi-hop cycle with candidate at both ends; per-hop `min_return` and dust attribution hold. | | E10 | **Solver/graph robustness under template spam** — `Everybody` instantiate → CreatePair spam from the candidate template; token queries that fail | B13 is report-only. The automatable part: route/solve latency (#485) under N junk pairs, and graceful exclusion (not 500) when a pair's token queries fail. | Solver benchmark with 1k/10k junk pairs from candidate wasm; fault-injection: pair-info query failure → hop excluded, solve succeeds. | | E11 | **Zap floors with candidate token** — one-sided add/withdraw (Z533/Z559) | Zap execution must follow floors, not optimistic quotes; FoT/rebase breaks zap accounting exactly where users can't see it. | Zap-in/out with candidate wasm: execution ≥ floor or revert; no silent shortfall credit. | | E12 | **Wrap-mapper / UST1-window adjacency** | If the candidate template is ever used for cUSTC/vFDUSD-like assets: unwrap dual-read (#523), wrap link identity (#570), oracle window mint/redeem 1:1 (#506). Conditional, but must be a checklist row so it isn't forgotten when such a template arrives. | Conditional suite section: wrap/unwrap/window 1:1 + impostor-identity rejection. | | E13 | **Fee-treasury accounting in the candidate token** | F4 rotates fees to treasury; fees *denominated in* a FoT/rebase token drift between accrual and sweep. | Treasury accrual vs actual balance reconciliation with weird-token fixture. | | E14 | **Math-boundary fuzz at u128/u256 edges** — decimals = 18, supply near u128::MAX, price ratios at extremes | A17 covers decimals > 18 rejection; the boundary *within* limits (18 decimals, max supply) stresses pair k-math, limit price band (L20), and any cumulative price/TWAP state for overflow/wrap. | Boundary matrix: no panic, no wrap, k-monotonicity (P1) within documented rounding. | | E15 | **Rounding-direction arbitrage loop** | P1 is asserted per-swap; the adversarial version loops micro provide/swap/withdraw cycles harvesting rounding dust — with an attacker-issued (zero-cost) token this is free money if rounding leaks. | N-iteration loop: k must not decrease beyond documented rounding; no risk-free profit (extends `test_flash_provide_swap_withdraw_no_profit`). | --- ### F. Off-chain trust chain — the token attacks the *displayed* truth, not pair solvency Pair solvency can be perfect while users are robbed via the indexer/oracle/frontend layer. These rows make that explicit and automatable. | ID | Vector | Justification / citation | Suite must show | |----|--------|--------------------------|-----------------| | F1 | **Event-vs-state consistency at the indexer** | See D16. Indexer tape/P&L built from token events can be poisoned by a lying-but-legitimate emitter even with SEC-D07 contract scoping correct. | Indexer test: ingest lying-event fixture; tape amounts reconciled against balance deltas; divergence flagged, not silently indexed. | | F2 | **Metadata injection** — NUL bytes / invalid strings in name/symbol; homoglyph/RTL tickers impersonating UST1/cUSTC; SVG logo payload | Postgres `text` rejects the NUL character ([docs](https://www.postgresql.org/docs/current/datatype-character.html)) → one malicious instantiation can stall ingest; homoglyph tickers are the `fake_token` class (GoPlus); SVG XSS is a known payload carrier. | Metadata fuzz at ingest (NUL, oversized, control chars) must not crash or stall the indexer; frontend ticker dedupe + escaping test; logo allowlist covers SVG (record-only). | | F3 | **Hub-price tilt via attacker-seeded pool** | #556 derives cUSTC/UST1/USTR USD from the *largest-liquidity* pool. An attacker with a mintable token can make their pool the largest and tilt every USD figure on the dApp. | Hub derivation test: attacker pool with fake depth must not move hub USD beyond deviation caps; anchor/allowlist behavior documented. | | F4 | **External-oracle ticker collision** | #515/#580 key external USD feeds by ticker (ustc/lunc/vfdusd). A candidate token named "USTC" or "FDUSD" invites mis-attribution. | Oracle lookup keyed by contract address, never ticker; test with colliding-ticker fixture. | | F5 | **Candle-close sniping** | #568 marks candle USD at time-stamped marks with idle mark-to-market. A dust swap at an extreme price just before a boundary skews the mark; with a zero-cost attacker token this is cheap. | Candle mark tests: dust-at-extreme-price fixture must not move the USD mark (TWAP/median, not last-trade). | | F6 | **Wash-volume stats poisoning** | Trader leaderboard (#553) and trailing windows (#576/#577) count volume; self-trades with an attacker-issued token are zero-cost and inflate ranks/24h stats. | Volume-attribution test with self-trade fixture; documented heuristic (same-beneficial-owner discounting) or explicit accepted-risk note. | | F7 | **Query-DoS → indexer lag → stale oracle chain** | D21 chained off-chain: slow token queries lag the indexer, which silently degrades every downstream USD figure. | Indexer lag alarm/behavior test under slow-query fixture; stale-data surfacing (existing stale-overview row #577) must trigger. | | F8 | **P&L from events vs balance deltas** | #551/#560 compute realized P&L; FoT/rebase tokens make event amounts ≠ balance deltas. | P&L reconciliation test with FoT fixture: indexer must use balance deltas or flag the pair as unmetered. | --- ### G. Harness / methodology additions | ID | Addition | Justification / citation | |----|----------|--------------------------| | G1 | **Differential op-sequence testing vs cw20-base** — run identical randomized op sequences (proptest-style random walks with shrinking) against the candidate wasm and a reference cw20-base; diff all state transitions | Differential testing is the standard compiler/token correctness methodology (Csmith lineage); catches unknown-unknowns that per-row checklists miss. | | G2 | **Mutant library as regression oracles** — beyond the single 8654/FoT known-bad control: a checked-in set of generated cw20-base mutants (FoT, rebase, sell-block, cooldown, entrypoint-selective tax, event-liar, hidden-mint, ghost-dust…) that the suite must catch | Mutation testing (cargo-mutants methodology). One known-bad control proves the suite catches *one* class; a mutant per D/A row proves each row's test actually fires. | | G3 | **Differential decompilation** — bindiff/Diaphora-style comparison of candidate wasm against a reference cw20-base build to surface injected basic blocks; publish a decomp coverage metric (% functions mapped; unmapped = audit gap) | [Diaphora](https://github.com/joxeankoret/diaphora) / [BinDiff](https://github.com/google/bindiff) are the standard binary-diffing tools; turns "decomp looks like cw20-base" into a measurable claim. | | G4 | **Multi-endpoint consensus fetch** — `CodeInfo.data_hash` from ≥2 independent LCD/RPC endpoints before pinning | The pin proves we have *the* on-chain binary only if the endpoint is honest; a single compromised LCD can serve a trojaned wasm with a matching claimed hash. | | G5 | **Live-instance sampling** — enumerate existing instances of the code ID (1686+ for 8266) and sample-query their configs (`tax_map`, minter, admin, pause) | Template honest ≠ instances honest (D14); sampling quantifies the real-world blast radius of a whitelist decision and feeds REPORT.md's factory-global section. | | G6 | **Time-travel / state-fuzz harness primitives** — first-class support for advancing height/time, op-count histories, and holder-count scaling | Required by D1/D3/D19; without harness support these rows degrade to prose. | | G7 | **Post-listing runtime monitoring + F6-freeze drill** — watchtower alerts on instance migrate events, admin changes, tax-setter calls, and gov param changes (e.g. `burn_tax`) | Listing is point-in-time (Honeypot.is caveat; D1/D8/D9 classes activate later). Tax2Gas history shows chain params move by governance. The drill measures time-to-freeze, which is the real control after a late activation. | | G8 | **Host-import + IBC-entrypoint enumeration in the fingerprint** | Extends A26: enumerate *all* IBC entrypoints and unexpected host imports, not just `ibc_receive` strings. | | G9 | **Seeded-wrong harness self-tests** — truncated wasm, non-wasm bytes, hash-mismatch, wrong-architecture binary must all fail closed | Extends C1 from "rebuilt wasm" to arbitrary corrupted input; a harness that green-lights garbage is worse than no harness. | --- ### CH. Exploit-chaining scenarios (explicit multi-step tests) The point of this supplement: single-row tests pass while **compositions** drain value. Each chain is one automated scenario. | ID | Chain | Suite must show | |----|-------|-----------------| | CH1 | D1 time-activated tax → post-listing B7 honeypot: honest at audit, sells revert after height H | Time-travel re-probe catches activation before listing; G7 monitoring catches it after. | | CH2 | D8 permissionless pair-registration → D5 entrypoint-selective tax: after `CreatePair`, anyone registers the pair address; only `Send`/`TransferFrom` (the DEX paths) get taxed | Post-create re-run of per-entrypoint 1:1; registration must not change DEX-path behavior. | | CH3 | D11 cooldown × L11 batch refunds / #494 ladder batch: second transfer in a batch reverts → whole batch fails or strands funds | Batch ops with cooldown fixture: atomic revert with full recovery, never partial stranding. | | CH4 | D2 minimum-transfer × L22 parked dust refunds: park refund below token minimum → claim bricked forever | Dust-park claim with min-transfer fixture: park must round up, merge, or refuse — never brick. | | CH5 | A8 allowance backdoor × L1 escrow: admin `TransferFrom` pulls the pair's escrowed maker balances while the book looks intact | Escrow drain attempt must fail; alarm test proves the suite notices escrow/balance divergence. | | CH6 | D19 reflection O(n) × holder growth × swap gas: listable at 100 holders, swaps exceed block gas at 10k | Gas-curve projection row fails the listing before the slow brick. | | CH7 | A3 rebase-down → sweep extracts "excess" → rebase-up → LPs short | E4 scenario; sweep must be rebase-aware or token vetoed. | | CH8 | A13 flash mint → B2 donation → provide dust → burn → victim deposit captured | E5 composite. | | CH9 | A26/B15 unbacked mint → router multihop dump → UST1 oracle pairs → depeg cascade into the #506 window | Conditional on IBC/bridge entrypoints (G8); mint-rights inventory in REPORT.md. | | CH10 | D16 event spoof → indexer tape → frontend price/volume → users trade on phantom liquidity; pair solvency untouched | F1 reconciliation; spoofed tape never reaches the dApp unflagged. | | CH11 | F2 NUL-byte metadata → indexer ingest stall → stale hub USD everywhere → mispriced display sitewide | F2 fuzz + F7 stale-surfacing. | | CH12 | F3 hub tilt → #569 protocol stats + #553 leaderboard poisoned from one attacker pool | F3 deviation caps. | | CH13 | D13 dynamic decimals → post-bootstrap price-band shift (L20) → mispriced resting limits → fill arbitrage drains makers | Decimals-mutation fixture through place/fill; book reprices or freezes, never fills at stale scale. | | CH14 | D7 swapAndLiquify → token contract reenters pair mid-sell → mid-settlement price shift | E6/D7 fixture; pair fails closed. | | CH15 | D9 fake renounce → admin re-seize → B9 migrate to a *different but whitelisted* code ID → F6 must still freeze (pin is the listing ID, not "any whitelisted ID") | Migrate-to-other-whitelisted-ID scenario explicitly in `asset_code_id_pin_tests` style; freeze confirmed. | | CH16 | D16 event-liar × F8: token emits full-amount events, delivers less (FoT) → indexer P&L/tape wrong even though pair P2 catches the desync on-chain | Indexer reconciliation test; the two detection layers must agree or alarm. | | CH17 | E1 same-token pair × hidden mint: MonoX-style self-swap price overwrite, then drain | Factory reject + pair-level assert; historical MonoX replay encoded as a test (DeFiHackLabs methodology). | | CH18 | D12 payable transfer × router: every routed swap fails (DoS), or worse, a path that attaches funds leaks natives to the token | Router with payable fixture: clean rejection, no funds attachment anywhere. | --- ### Documented N/A mappings (record in CATALOG.md; do not spend test budget) - **`tx.origin` authentication tricks** — no `tx.origin` in CosmWasm; `info.sender` only. (Trapdoor paper catalogs `tx.origin` sell-blocks; class is EVM-only.) - **`extcodesize == 0` caller checks** — CosmWasm analogue exists via `ContractInfo` query; covered as D4. - **`selfdestruct` / CREATE2 metamorphic redeploy** — no selfdestruct; code IDs are immutable; the metamorphosis surface is `MsgMigrateContract`, covered by A14/B9/F6 (+CH15). - **Missing `bool` return / return-data bombs** — CosmWasm errors instead of returning false; wasm memory model differs (already noted in the issue). - **Read-only reentrancy (Curve `get_virtual_price`)** — CosmWasm queries cannot re-enter a mid-execution contract, and the indexer reads only committed state (Tendermint finality). The residual analogues are covered: token reading pair state mid-transfer (D6) and display-layer staleness (F5/F7). - **Compiler-level honeypots (Art of the Scam "Solidity compiler" class)** — no direct wasm analogue, but the July 2023 Vyper lock miscompilation shows toolchain provenance matters; covered by D20 fingerprint sweep + G3 differential decompilation. Suggested landing: append D/E/F/G/CH to `CATALOG.md` as new sections and reference this note from the 8266 REPORT.md checklist so every row gets pass/fail/N/A+reason like the existing A/B/C rows.
PlasticDigits commented 2026-08-22 03:43:47 +00:00 (Migrated from gitlab.com)

mentioned in commit 7b01c62ff7

mentioned in commit 7b01c62ff72df9a3a36ebe6cf59e2b03bfc58fdc
PlasticDigits commented 2026-08-22 03:44:11 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1114

mentioned in merge request !1114
PlasticDigits commented 2026-08-22 03:44:21 +00:00 (Migrated from gitlab.com)

Harness MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/396

make verify-issue-589 is green (16/16 including retest). 8266 REPORT.md is NO-GO until Layer A-lcd + Layer B-lt execute the pinned LCD wasm (CODE_ID=8266 LAYER_B_LT=1 after wabt). Do not AddWhitelistedCodeId 8266. #581 stays open.

Skill: skills/AGENTS_CW20_CODE_ID_AUDIT.md.

Harness MR: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/396 `make verify-issue-589` is green (16/16 including retest). 8266 `REPORT.md` is **NO-GO** until Layer A-lcd + Layer B-lt execute the pinned LCD wasm (`CODE_ID=8266 LAYER_B_LT=1` after wabt). Do not `AddWhitelistedCodeId 8266`. #581 stays open. Skill: `skills/AGENTS_CW20_CODE_ID_AUDIT.md`.
PlasticDigits commented 2026-08-22 06:10:00 +00:00 (Migrated from gitlab.com)

mentioned in commit fa8550f356

mentioned in commit fa8550f35614fdee0b75463b0f9a9ffd2f53166e
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-22 06:10:01 +00:00
PlasticDigits commented 2026-08-22 06:28:42 +00:00 (Migrated from gitlab.com)

Post-merge note (!1114 on main)

Merged: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/396

Sanity: make verify-issue-589 16/16. Layer A+B multi-test 34 passed; known-bad FoT 1:1 / P2 stay red. Decompile fail-closed without wabt (C2). H-01 / F6 production contracts unchanged.

8266 remains NO-GO. cw20-codeid-audits/codeids/8266/REPORT.md — do not AddWhitelistedCodeId 8266. #581 was reopened (GitLab auto-closed it from merge text that contained close #581).

Gaps vs full AC (harness landed; binary suite on LCD wasm did not):

  1. Layer A-lcd / B-lt do not execute pinned token.wasm in Rust. CI is A-mt / B-mt mutants + mintable analogue.
  2. LAYER_B_LT=1 currently skips with PASS when LocalTerra is up — it does not StoreCode the LCD wasm. Do not read a green verify as candidate clearance.
  3. 6036 has README only (no REPORT.md). 8654 control is in-process FoT mutant, not LCD 8654.
  4. Operator path still needed: apt install wabt, CODE_ID=8266 fetch+decomp, then real B-lt once implemented.

Byte-identical optimizer rebuild stays appendix-only.

## Post-merge note (!1114 on `main`) Merged: https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/merge_requests/396 **Sanity:** `make verify-issue-589` **16/16**. Layer A+B multi-test 34 passed; known-bad FoT 1:1 / P2 stay red. Decompile fail-closed without `wabt` (C2). H-01 / F6 production contracts unchanged. **8266 remains NO-GO.** `cw20-codeid-audits/codeids/8266/REPORT.md` — do **not** `AddWhitelistedCodeId 8266`. #581 was reopened (GitLab auto-closed it from merge text that contained `close #581`). **Gaps vs full AC (harness landed; binary suite on LCD wasm did not):** 1. Layer A-lcd / B-lt do **not** execute pinned `token.wasm` in Rust. CI is A-mt / B-mt mutants + mintable analogue. 2. `LAYER_B_LT=1` currently **skips** with PASS when LocalTerra is up — it does not `StoreCode` the LCD wasm. Do not read a green verify as candidate clearance. 3. 6036 has README only (no `REPORT.md`). 8654 control is in-process FoT mutant, not LCD 8654. 4. Operator path still needed: `apt install wabt`, `CODE_ID=8266` fetch+decomp, then real B-lt once implemented. Byte-identical optimizer rebuild stays appendix-only.
PlasticDigits commented 2026-08-22 06:29:12 +00:00 (Migrated from gitlab.com)

mentioned in issue #590

mentioned in issue #590
PlasticDigits commented 2026-08-22 06:29:13 +00:00 (Migrated from gitlab.com)

marked as related to #590

marked as related to #590
PlasticDigits commented 2026-08-22 06:29:30 +00:00 (Migrated from gitlab.com)

Follow-up tracker: #590 (A-lcd/B-lt + 8266 still NO-GO).

Follow-up tracker: #590 (A-lcd/B-lt + 8266 still NO-GO).
PlasticDigits commented 2026-08-22 09:37:55 +00:00 (Migrated from gitlab.com)

mentioned in issue #591

mentioned in issue #591
PlasticDigits commented 2026-08-22 10:59:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #592

mentioned in issue #592
PlasticDigits commented 2026-08-23 03:50:08 +00:00 (Migrated from gitlab.com)

mentioned in commit 093b78e43b

mentioned in commit 093b78e43ba1b43bcc448b81eea49621bd0aee6f
PlasticDigits commented 2026-08-23 03:53:49 +00:00 (Migrated from gitlab.com)

mentioned in issue #601

mentioned in issue #601
PlasticDigits commented 2026-08-23 06:48:17 +00:00 (Migrated from gitlab.com)

mentioned in issue #604

mentioned in issue #604
PlasticDigits commented 2026-08-23 06:48:28 +00:00 (Migrated from gitlab.com)

mentioned in issue #605

mentioned in issue #605
PlasticDigits commented 2026-08-23 11:52:16 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1122

mentioned in merge request !1122
PlasticDigits commented 2026-08-23 12:41:00 +00:00 (Migrated from gitlab.com)

mentioned in issue #611

mentioned in issue #611
PlasticDigits commented 2026-08-23 12:41:03 +00:00 (Migrated from gitlab.com)

marked as related to #611

marked as related to #611
PlasticDigits commented 2026-08-23 13:05:31 +00:00 (Migrated from gitlab.com)

mentioned in issue #612

mentioned in issue #612
PlasticDigits commented 2026-08-24 03:01:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #616

mentioned in issue #616
PlasticDigits commented 2026-08-24 03:53:51 +00:00 (Migrated from gitlab.com)

mentioned in commit d3dba8bfef

mentioned in commit d3dba8bfefefa8ee208df8b23a68fc5213a11a6c
PlasticDigits commented 2026-08-24 05:54:21 +00:00 (Migrated from gitlab.com)

mentioned in issue #623

mentioned in issue #623
PlasticDigits commented 2026-08-24 05:54:22 +00:00 (Migrated from gitlab.com)

marked as related to #623

marked as related to #623
PlasticDigits commented 2026-08-24 13:31:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #627

mentioned in issue #627
PlasticDigits commented 2026-08-24 13:31:41 +00:00 (Migrated from gitlab.com)

marked as related to #627

marked as related to #627
PlasticDigits commented 2026-08-24 13:48:35 +00:00 (Migrated from gitlab.com)

mentioned in commit 021945811b

mentioned in commit 021945811bb6b1024a8b300befa4b725ad828d03
PlasticDigits commented 2026-08-24 13:48:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #628

mentioned in issue #628
PlasticDigits commented 2026-08-25 05:24:40 +00:00 (Migrated from gitlab.com)

mentioned in commit 4379639ceb

mentioned in commit 4379639ceb45ef414076d0760acdf93f7a97ac91
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#589
No description provided.