Skip to content

feat(arcade): ArcadeEscrow — per-wallet NODL balance for Arcade builds - #135

Merged
aliXsed merged 5 commits into
mainfrom
claude/arcadeescrow-pr-auto-fix-588d10
Nov 2, 2026
Merged

aliXsed merged 5 commits into
mainfrom
claude/arcadeescrow-pr-auto-fix-588d10

Conversation

@aliXsed

@aliXsed aliXsed commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Adds src/arcade/ArcadeEscrow.sol: P1 of NodleCode/agent-stack#50, built to design section 9.3. Each wallet holds a reusable NODL balance. arcade-forge, as oracle, charges it once per charged build. Users leave via requestWithdraw → withdraw after a delay.
  • Adds ArcadeEscrow to ops/verify_zksync_contracts.py.
  • Adds the arcade tests to the CI coverage job with a 95% line gate, like swarms and collections.
  • Unit, fuzz and stateful invariant tests (test/arcade/), plus script/DeployArcadeEscrow.s.sol for ZkSync.
Property How
Admin can't drain users withdrawFees is capped at feesAccrued. Invariant: token.balanceOf(this) == Σ balanceOf + feesAccrued
Admin can't widen charging by surprise Timelock: granting ORACLE_ROLE, raising maxCharge or the quota, and shortening the period are scheduled (scheduleChange → executeChange, cancelable) and wait ADMIN_DELAY = MAX_WITHDRAW_DELAY + 1 day. Anyone can exit first. Revokes, decreases and pause apply immediately
Leaked oracle key is bounded maxCharge per call + QuotaControl per period, and a charge only moves NODL into fees
Users can always exit Pause blocks deposit/charge only. withdrawDelay is capped at MAX_WITHDRAW_DELAY (7 days), and a delay change never moves a pending request
Idempotent settlement chargeOf[ref] = keccak256(account, amount). An exact retry reverts AlreadyCharged (the worker reads it as "settled"). The same ref with a different payload reverts ChargeConflict. ref is a random nonce, never an artifact id
Mid-build withdrawal race Two-step withdrawal. withdraw pays min(requested, balance), so a charge during the delay is deducted
Dust minDeposit applies only to an empty balance
Upgrades Not upgradeable (holds user funds)

Choices beyond the design, to review:

  • MAX_WITHDRAW_DELAY (7 days) is new. Without it the admin could set a delay that locks balances.
  • The admin timelock is new (ADMIN_DELAY = 8 days). Its cost: replacing a leaked oracle key takes 8 days, during which NODL settlement is off (revoke is still instant). Lowering MAX_WITHDRAW_DELAY to ~24 h would cut that to ~2 days. QuotaControl.setQuota/setPeriod become virtual to allow the gate; Payment and Rewards are unchanged.
  • A duplicate ref reverts instead of silently no-oping. A ref reused with a different payload reverts with a separate ChargeConflict error.
  • ReentrancyGuard sits on the transfer paths as defence in depth. They already follow checks-effects-interactions.

Still open on the issue (not decided by this PR): P1 is marked deferred, pending escrow vs. a one-way NODL → credits purchase, store-policy and counsel review, an external audit, multisig and oracle-key custody. WhitelistPaymaster whitelisting is a separate admin transaction after deploy.

Test plan

  • forge test: 1410 passed, 0 failed (including 39 escrow unit/fuzz tests and 5 invariants at 256 runs × 500 depth)
  • CI coverage command locally: arcade 97.64% lines, swarms 95.83%, collections 96.95% (all ≥ 95%)
  • forge build --zksync src/arcade/ArcadeEscrow.sol script/DeployArcadeEscrow.s.sol compiles
  • yarn lint (warnings only, same as Payment.sol), cspell clean on new files
  • Testnet deploy and a deposit → charge → withdraw round trip
  • External audit before mainnet

🤖 Generated with Claude Code

Users hold a reusable balance that the arcade-forge oracle charges once per
charged build; the admin can only withdraw accrued fees, and pause never
blocks a withdrawal (agent-stack design 9.3, issue NodleCode/agent-stack#50 P1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 29, 2026 03:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The admin can drain user balances by granting itself charging authority and raising limits, contradicting the stated no-drain guarantee.

3 open issues
What changed in this PR

Adds a non-upgradeable NODL escrow for Arcade builds, giving wallets reusable balances that the settlement oracle charges per build.

Changes:

  • Implements deposits, delayed withdrawals, reference-based charge deduplication, and fee accounting.
  • Adds charge limits, pause controls, and unit, fuzz, and stateful invariant tests.
  • Provides an environment-configured deployment script for ZkSync Era.
File Description
test/​arcade/​ArcadeEscrowInvariants.t.sol Checks accounting, solvency, quota limits, and withdrawals while paused.
test/​arcade/​ArcadeEscrow.t.sol Tests escrow operations, permissions, limits, and withdrawal behavior.
src/​arcade/​ArcadeEscrow.sol Implements wallet balances, oracle charges, delayed withdrawals, and admin controls.
script/​DeployArcadeEscrow.s.sol Deploys the escrow using environment-provided addresses and limits.

Review effort: Balanced

Comment thread src/arcade/ArcadeEscrow.sol Resolved
Comment thread script/DeployArcadeEscrow.s.sol Resolved
Comment thread src/arcade/ArcadeEscrow.sol Resolved
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

LCOV of commit caf2dec during checks #806

Summary coverage rate:
  lines......: 29.5% (1251 of 4243 lines)
  functions..: 32.6% (221 of 677 functions)
  branches...: 30.7% (237 of 773 branches)

Files changed coverage rate:
                                                    |Lines       |Functions  |Branches    
  Filename                                          |Rate     Num|Rate    Num|Rate     Num
  ========================================================================================
  script/DeployArcadeEscrow.s.sol                   | 0.0%     27| 0.0%     1| 0.0%      6
  src/QuotaControl.sol                              |75.0%     28|66.7%     6| 100%      4
  src/arcade/ArcadeEscrow.sol                       |97.7%    131| 100%    26| 100%     37
  test/arcade/ArcadeEscrowInvariants.t.sol          |96.7%     61|91.7%    12|81.8%     11

aliXsed and others added 4 commits October 29, 2026 16:30
…cation

A reused ref with a different account or amount now reverts ChargeConflict
instead of AlreadyCharged, so the settlement worker cannot mistake it for a
settled retry. Also documents that the admin multisig is trusted, and adds
DeployArcadeEscrow to the zkSync verification maps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds test/arcade to the coverage run and a src/arcade threshold step matching
swarms and collections, plus a test for the uint128 withdrawal bound.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hdrawal

Granting ORACLE_ROLE, raising maxCharge or the quota, and shortening the quota
period now wait ADMIN_DELAY (MAX_WITHDRAW_DELAY + 1 day), so users can exit
before a change that widens what can be charged. Revokes, decreases and pause
stay immediate. QuotaControl's setters become virtual so the escrow can gate them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r-auto-fix-588d10

# Conflicts:
#	.github/workflows/checks.yml
@aliXsed
aliXsed merged commit cb808e1 into main Nov 2, 2026
3 checks passed
@aliXsed
aliXsed deleted the claude/arcadeescrow-pr-auto-fix-588d10 branch November 2, 2026 04:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants