# Audit report

> Review Submission19/ only (read Submission19/AUDIT_REQUEST.md first): World source 7939c37, NOT deployed. Main subject: the GOLD ledger for paid Genesis NFT mints (server/genesis-gold.ts, genesis-gold-reader.ts, genesis-gold-service.ts, worker/index.ts GenesisGold service binding, worker/app.ts cron), and 21df62b: each payment token has its own GOLD price (imdUSD 1,200, USDC 1,600; AUDIT_REQUEST.md section 1b). The production registry is empty. The Genesis contract is public at github.com/tungweb3/ember-evo-genesis-review (Submission5). Also included: 7021b5b, the live fix of audit f6bd50ad.
>
> 1. Can GOLD be spent without a finalized matching PaidOrderConsumed + GenesisMinted, released while a consumption exists or can still finalize, or counted twice? (genesis-gold-reader.ts read, world-gold.ts reconcileHeldMintGold)
> 2. orderWindow (bounded eth_getLogs search, 10 blocks per request, 12 s slots, binary search after missed slots): any off-by-one or case where a real consumption is missed? It must only ever keep the hold, never release.
> 3. reserveGenesisGold: can a caller holding the service binding hold GOLD without the wallet's EIP-712 consent, with costs or recipient other than the registry's, a deadline beyond 900 s, or hold twice on retry? Is the encodeMintAuth genesis-r5-paid-v1 branch sufficient?
> 4. Worker wiring: package.json imports (#worker-entrypoint), the extra WorkerEntrypoint export, cron isolation and limits (50 ms CPU, subrequests, rate limiter), secrets reaching errors or logs.
> 5. 21df62b / 7939c37: can an order be held at one price and settle at another, can a registry entry let a caller hold an amount no token charges, or can the price check be bypassed on retry (stored-order path of reserveGenesisGold)?
> 6. 7021b5b: lower-nonce payment must have status 0x1; a failed note is dropped only by order id and hash; ESBUILD_BINARY_PATH / NAPI_RS_NATIVE_LIBRARY_PATH refused.
>
> Report severity, reproduction and fix. Not in scope: 3D/rendering, media, Celestao, AIMarket.

| | |
|---|---|
| Repository | https://github.com/tungweb3/imd-ember-world-review.git |
| Commit | `bfea56472b0be701131c42614de3ee2d70980ee5` |
| Job | `8febfeed-bda1-48f9-bdd2-002de3c83788` |
| Judged | 2026-10-28 23:43 UTC |
| Findings | 1 medium · 4 low · 1 info |

Four agents audited the code as it is at `bfea564`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Medium: 21df62b keeps one embeoCost per deployment while the contract charges a per-token amount: a mint paid in USDC (6 decimals) can never settle, so its 1,600 GOLD stays reserved forever, or can never be h

`Submission19/source/server/genesis-gold.ts:70`

```
 if(recipient!==economyAddress(d.recipient)||o.embeoCost!==d.embeoCost||(o.goldCost!==d.goldCost&&!d.addedTokenGoldCosts.includes(o.goldCost as string)))fail('MINT_PRICE_MISMATCH');
```

AUDIT_REQUEST.md §1b: the mainnet Genesis contract takes imdUSD v1, USDC and imdUSD v2, "every token pays the same $20", and each token has its own GOLD price. 21df62b made the GOLD price per token (goldCost / addedTokenGoldCosts) but left the payment-token amount a single registry value: GenesisDeployment has one embeoCost (genesis-gold.ts:30), parseOrder refuses any order whose embeoCost differs from it (line 70, MINT_PRICE_MISMATCH), and the reader settles a used order only when GenesisMinted.cost equals the stored order's embeoCost (genesis-gold-reader.ts:136, else MINT_PROOF_CONFLICT). In the public Genesis contract (Submission5 EmberEvoGenesisCandidate.sol, mintPaid) GenesisMinted.cost is the payment-token amount actually transferred (_mintOne(lookKey, choice, true, cost) after safeTransferFrom(msg.sender, recipient, cost)); the Submission5 README pins imdUSD as an 18-decimal token (0x61aA…2a25), so its $20 is 20000000000000000000, while mainnet USDC has 6 decimals, so its $20 is 20000000. No single embeoCost equals both. Whatever the registry stores, one of two outcomes follows for every USDC-paid order: (a) the signer sends USDC's amount and the hold is refused at line 70, so the 1,600-GOLD price in addedTokenGoldCosts can never be used; or (b) the signer sends the registry's imdUSD amount so the hold is accepted at 1,600 GOLD, the wallet pays USDC on chain, PaidOrderConsumed.goldCost = 1600 matches, but GenesisMinted.cost = 20000000 fails line 136 on every cron run and the 1,600 GOLD stays reserved indefinitely (§2: no operator or release path exists, and the retry path answers MINT_ORDER_CONFLICT). This is not the §2 "registry cost that differs from the contract" misconfiguration: with two tokens of different decimals there is no correct value. Merged from audit_math (the other specialists did not report it). Fix: make the registry list one (embeoCost, goldCost) pair per payment token (deployment token plus at most two added tokens), let parseOrder accept an order whose pair is one of them, and keep the reader's two equality checks against the order's own pair; the wallet's consent already covers both embeoCost and goldCost, so a hold still settles only at the amounts consented to.

**Reproduction**

Reproduced with the submission's own genesis-gold-reader.ts (byte-identical copy, sha256 44bac795…) against a JSON-RPC stand-in shaped like tests/genesis-gold.test.mjs (test/scratch/reader/embeo-conflict.mjs). Registry entry: embeoCost "20000000000000000000" (imdUSD $20), goldCost "1200", addedTokenGoldCosts ["1600"]. Stored hold: order {embeoCost "20000000000000000000", goldCost "1600"}, amount 1600. Chain at a finalized block: paidOrderTokenId(orderId)=7, receipt status 0x1 with PaidOrderConsumed(orderId, wallet, 7, goldCost=1600) and GenesisMinted(wallet, 7, lookKey, choice=1, skin=1, paid=1, cost=20000000). Expected: proof tokenId 7, reconcileHeldMintGold settles consumed and debits 1,600 GOLD. Actual: read() throws MINT_PROOF_CONFLICT at genesis-gold-reader.ts:136 (wordAt(gData,3)=20000000 !== BigInt("20000000000000000000")); the hold stays held on every run. Control: the same chain with GenesisMinted.cost=20000000000000000000 returns tokenId 7. Case (a): reserve with order.embeoCost "20000000" → line 70 (o.embeoCost!==d.embeoCost) throws MINT_PRICE_MISMATCH before anything is held. The existing test "owner 2026-10-29" varies only the GOLD price, never the payment amount.

### 2. Low: orderWindow: the branch taken when the first guess lands at or below the deploy block skips the binary search, so a hold reconciled long after its order is searched from the deploy block (hundreds of

`Submission19/source/server/genesis-gold-reader.ts:64`

```
 if(lo<=deploy){const t=await timeOf(deploy);return {from:deploy,to:t>deadlineS?deploy:min(fin.number,deploy+(deadlineS-t)/slot)};}
```

The first guess lo = fin − floor((finTime − start)/slot) − 1 steps back one block per slot since start, but the chain has one block per filled slot, so every missed slot since the order pushes lo one block too far back. The non-deploy branch repairs that with the timestamp binary search (lines 67–72); the lo<=deploy branch does not: it returns [deploy, deploy + floor((deadline − timeOf(deploy))/slot)], every block from deployment to the order's deadline. lo<=deploy holds as soon as slots between deployment and the order's window <= missed slots since deployment + 1 (about 1 % of elapsed slots on mainnet), so an order placed D days after deployment enters this branch once it is reconciled about 100·D days later, and the condition only becomes more true as fin advances. Which holds are read that late: a used order whose consumption lies outside its window (the MINT_PROOF_CONFLICT case AUDIT_REQUEST.md §2 says the cron retries forever) and any backlog after a long RPC outage. The window width/10 eth_getLogs requests are issued in one scheduled invocation (the financial cron is every minute, worker/app.ts:262), each drawing on ECONOMY_CHAIN_LIMITER key genesis:rpc (600 per 60 s in Submission13's wrangler.jsonc) and on the invocation's subrequest cap; when either refuses, genesisChainRpc throws MINT_CHAIN_UNAVAILABLE, so the order and every later order of that page fail on that run, and the same search repeats on every cursor wrap. Nothing is ever released or spent wrongly (the window still contains the consumption; the port checked that no in-window block is excluded), so this is liveness and cost only. The comment above orderWindow claims the window stays about TTL/slot blocks "however late the reconciliation runs"; this branch does not. Merged from audit_math and audit_economics (same mechanism, same fix). Fix: in the lo<=deploy branch, when timeOf(deploy) < start, set lo = deploy, loTime = that time, hi = min(fin.number, deploy + ceil((start − loTime)/slot)) and run the same binary search before computing to; keep the current return only when timeOf(deploy) >= start.

**Reproduction**

test/scratch/orderwindow-port.mjs is a verbatim port of orderWindow over a chain where block n is in slot n + floor(n/99) (one missed slot per hundred), deployBlock 1000, slot 12 s. Order with deadline = timeOf(1000) + 86400 + 900 (placed one day after deployment), finalized block = last block at or before deadline + L. L = 1 day: branch 'search', window [8117, 8204], 88 blocks, 9 eth_getLogs requests, 9 block reads. L = 100 days: lo <= 1000, branch 'deploy', window [1000, 8275], 7,276 blocks, 728 eth_getLogs requests. Order placed 2 days after deployment, L = 200 days: window [1000, 15475], 14,476 blocks, 1,448 eth_getLogs requests in one invocation, above both the 600/min limiter and the 1,000-subrequest cap. Expected in every case: a window of about 90 blocks found by the binary search from deploy. In all runs every block at or after deployment with timestamp in [deadline − 1020, deadline] lies inside the window (0 missed), so only the hold is kept, never released.

### 3. Low: The Genesis cron page of 8 is cut from every held row of world_gold_mints, not the deployment's own: holds of another registry entry fill a deployment's page and delay its settlement

`Submission19/source/server/genesis-gold.ts:156`

```
   const r=await reconcileHeldMintGold(db,reader(d,{}),nowMs,{after:at?.after??'',limit});
```

reconcileGenesisDeployments keeps one cursor per registry entry and asks reconcileHeldMintGold for a page of GENESIS_CRON_PAGE (8) held orders with only {after, limit}. The page query in the locked world-gold.ts:204 is SELECT id FROM world_gold_mints WHERE state='held' AND id>? ORDER BY id LIMIT ? with no chain_instance, chain_id or contract filter; the scope is checked only afterwards per row (world-gold.ts:206, MINT_SCOPE_MISMATCH), and line 159 filters those outcomes out of the report while the cursor (line 157–158) still advances past them. So with more than one entry in an environment (genesis-deployments.ts allows it; the test only requires unique ids), each deployment's page is filled with the next 8 held mints of any deployment, and every deployment's cursor cycles through every other deployment's rows. A consumed or expired hold is reached only after ceil(totalHeld/8) cron minutes instead of ceil(ownHeld/8), on every wrap of the cursor; each run also spends D1 reads on rows it cannot act on, and the "per deployment" page comment at lines 144–147 does not hold. Only the Genesis path writes world_gold_mints in this snapshot (reserveMintGold has no other caller in Submission19), so with the single production entry planned the defect is dormant; it appears as soon as a second entry (a redeploy, a second collection) is listed. No GOLD is spent or released wrongly. Merged from audit_math, audit_economics, audit_flow and audit_permissions (same mechanism). Fix without touching the locked file: in reconcileGenesisDeployments page the ids with a query restricted to chain_instance = d.id (and chain_id, contract) and call reconcileHeldMintGold with options.orderId per id; or, when the lock is next refreshed, add an optional chainInstance filter to the page query.

**Reproduction**

State: registry LIST = [B, A] of one environment (A = DEP of tests/genesis-gold.test.mjs, B = {...DEP, id:'sepolia-r5-test-2', contract:'0x'+'d'.repeat(40)}). Hold 8 orders of A with orderIds 0x…0500 … 0x…0507 and one order of B with orderId 0x…0600; chain stand-in reports finalTime = DEADLINE + 1 for both. Call reconcileGenesisDeployments(db, LIST, 'sepolia', reader, NOW) once. Expected: B's one order is released in this run (its page has room). Actual, from the code path: world-gold.ts:204 returns ids 0x…0500–0x…0507 for B's page (no scope filter), each is MINT_SCOPE_MISMATCH at world-gold.ts:206, filtered at genesis-gold.ts:159, B's cursor is written as 0x…0507 (lines 157–158) and B's order is read only on the next cron minute; with 400 held orders of A, a B hold is visited once every 50 minutes. The single-order path reconcileGenesisGold (line 137) does filter by row.chainInstance; the cron path does not.

### 4. Low: An identical reserve retry is re-validated against the current registry: after a registry price-list edit, a lost-response retry answers MINT_PRICE_MISMATCH although the GOLD is still held

`Submission19/source/server/genesis-gold.ts:103`

```
  const o=parseOrder(d,r.order,Math.min(nowMs,existing.deadline-1));
```

The comment at lines 101–102 says validation of a stored order happened when it was first held and that an identical retry returns the stored state. The stored-order path, however, calls parseOrder with the current registry entry d before the stored document is compared (line 106), and parseOrder's line 70 refuses the order with MINT_PRICE_MISMATCH when goldCost is no longer d.goldCost or one of d.addedTokenGoldCosts (or recipient / embeoCost changed). So item 5's question (can the price check be bypassed on retry) is answered no, but the asymmetry runs the other way: the first path validated the price once, the retry validates it again against whatever the registry says now, and the signer cannot distinguish that error from "nothing was held" while the wallet's GOLD stays reserved until the order settles or expires. A signer that reads MINT_PRICE_MISMATCH as "not held" (its natural meaning) issues a fresh order id and holds again, double-reserving the wallet for up to 900 s. genesis-deployments.ts:11–12 says entries are append-only while any order is held, but correcting an added-token price (the edit 21df62b makes most likely) is exactly this case and nothing enforces the rule. Merged from audit_flow and audit_permissions. Fix: on the existing-row path, compare the caller's order with the stored document first and skip the registry price/recipient/cost check when they match (consent still verified), so a retry always returns the stored state; or add a test that refuses a registry edit of pinned costs while holds exist.

**Reproduction**

Reproduced with the submission's genesis-gold.ts (byte-identical copy) over an in-memory stand-in of the book (test/scratch/gold/retry.mjs). Registry DEP: goldCost '8', addedTokenGoldCosts ['9']. 1) reserveGenesisGold with order {goldCost:'9', …} and the wallet's consent → {state:'held', reserved:'9'}. 2) The byte-identical request again, unchanged registry → same state (the documented idempotency). 3) The byte-identical request with the registry's addedTokenGoldCosts edited to [] or ['10'] (same id, contract, chain). Expected per lines 101–102: {state:'held', reserved:'9'}. Actual: throws MINT_PRICE_MISMATCH from parseOrder (line 70) called at line 103, while the stored row is still state 'held', amount 9.

### 5. Low: The signer's reconcile RPC and the cron spend one rate-limit key 'genesis:rpc' with no reserved budget: a reconcile loop from the service binding starves the cron's Genesis step on that location

`Submission19/source/server/genesis-gold-service.ts:23`

```
    if(!env.ECONOMY_CHAIN_LIMITER||!(await env.ECONOMY_CHAIN_LIMITER.limit({key:'genesis:rpc'})).success)throw Error('MINT_CHAIN_UNAVAILABLE');
```

genesisChainRpc charges one ECONOMY_CHAIN_LIMITER token per JSON-RPC call under the constant key 'genesis:rpc' whether the caller is the cron (maintainGenesisGold) or the GenesisGold.reconcile service method (worker/index.ts:15), and the service method has no limit of its own. A reconcile of a used order without a transaction hint costs about 20–40 calls (eth_chainId, finalized block, eth_getCode once, two eth_call, up to ~18 eth_getBlockByNumber in the binary search, up to 11 eth_getLogs, receipt, block, final block check). The limiter namespace is 600 per 60 s (Submission13 wrangler.jsonc ECONOMY_CHAIN_LIMITER), so about 20 unhinted reconcile calls in a minute (a signer retrying orders whose receipt is not yet at or below the finalized block, which return MINT_PROOF_CONFLICT and invite a retry) exhaust the key; every cron page on that location then gets MINT_CHAIN_UNAVAILABLE from its first call and settles nothing until the window resets. The caller is the trusted same-account signer, so no theft or wrong settlement; but the cron isolation item 4 asks about does not hold at the limiter: the try/catch in worker/app.ts:195 isolates the step from the World's GOLD work, not from the service binding's traffic. Kept from audit_permissions. Fix: a separate key for the service path (e.g. 'genesis:rpc:service') or a per-minute cap on reconcile calls that answers GOLD_UNAVAILABLE, keeping 'genesis:rpc' for the cron.

**Reproduction**

State: ECONOMY_CHAIN_LIMITER 600/60 s; one held order X whose consumption is in a block above the finalized block. Input: the signer calls GenesisGold.reconcile({deploymentId, orderId:X}) 30 times within a minute (each ~20+ RPC calls, each returning {state:'held', code:'MINT_PROOF_CONFLICT'}), then the cron's maintainGenesisGold runs on the same location. Expected: the cron page reads its 8 orders. Actual: line 23 rejects the first eth_chainId of every order with MINT_CHAIN_UNAVAILABLE; the run logs 8 × MINT_CHAIN_UNAVAILABLE and settles nothing. Code path: genesis-gold-service.ts:19–25 (one key for both callers), worker/index.ts:15 (reconcile with no limiter), genesis-gold-reader.ts:90–140 (the per-order call count).

### 6. Info: GenesisGold reserve/order responses return the wallet's whole GOLD balance to the service caller, contrary to genesisGoldOrder's own comment

`Submission19/source/server/genesis-gold.ts:88`

```
 return {orderId:row.id,state:row.state,bindingDigest:await economyDigest(JSON.stringify(auth)),...balance};
```

view() spreads goldBalanceView(...) of the order's wallet (book, reserved, available across all of the wallet's holds and purchases) into every reserve and order response. The comment at line 123 says "no consent signature or other wallet data is returned", but the balance is. The caller is the same-account signer Worker, so this is scope and documentation, not a bypass: the signer needs the hold's state and amount, not the wallet's balance across unrelated holds. Kept from audit_permissions. Fix: return {orderId, state, bindingDigest, gold: row.gold} from view() and keep the comment, or state in the comment that the balance is intentionally shared with the signer.

**Reproduction**

State: wallet W with book 10 and one held Genesis order O of 8. Input: GenesisGold.order({deploymentId:DEP.id, orderId:O}). Expected per the line-123 comment: {orderId, state, bindingDigest}. Actual: {orderId:O, state:'held', bindingDigest, book:'10', reserved:'8', available:'2'}; tests/genesis-gold.test.mjs line 36 asserts exactly ['held','10','8','2'] on the reserve response, and the in-memory run test/scratch/gold/retry.mjs prints reserved '9' from the same view().

---

Judge's submission `9f3ce0dd28c472f502770af5b62aa4323d3044f4de532c76a0bb8fbfd27c49f8`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
