Repository navigation
feat(arcade): ArcadeEscrow — per-wallet NODL balance for Arcade builds - #135
Merged
Merged
Conversation
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>
There was a problem hiding this comment.
🟡 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
LCOV of commit
|
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
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 viarequestWithdraw→withdrawafter a delay.ArcadeEscrowtoops/verify_zksync_contracts.py.test/arcade/), plusscript/DeployArcadeEscrow.s.solfor ZkSync.withdrawFeesis capped atfeesAccrued. Invariant:token.balanceOf(this) == Σ balanceOf + feesAccruedORACLE_ROLE, raisingmaxChargeor the quota, and shortening the period are scheduled (scheduleChange→executeChange, cancelable) and waitADMIN_DELAY=MAX_WITHDRAW_DELAY+ 1 day. Anyone can exit first. Revokes, decreases and pause apply immediatelymaxChargeper call +QuotaControlper period, and a charge only moves NODL into feesdeposit/chargeonly.withdrawDelayis capped atMAX_WITHDRAW_DELAY(7 days), and a delay change never moves a pending requestchargeOf[ref] = keccak256(account, amount). An exact retry revertsAlreadyCharged(the worker reads it as "settled"). The samerefwith a different payload revertsChargeConflict.refis a random nonce, never an artifact idwithdrawpaysmin(requested, balance), so a charge during the delay is deductedminDepositapplies only to an empty balanceChoices beyond the design, to review:
MAX_WITHDRAW_DELAY(7 days) is new. Without it the admin could set a delay that locks balances.ADMIN_DELAY= 8 days). Its cost: replacing a leaked oracle key takes 8 days, during which NODL settlement is off (revoke is still instant). LoweringMAX_WITHDRAW_DELAYto ~24 h would cut that to ~2 days.QuotaControl.setQuota/setPeriodbecomevirtualto allow the gate;PaymentandRewardsare unchanged.refreverts instead of silently no-oping. A ref reused with a different payload reverts with a separateChargeConflicterror.ReentrancyGuardsits 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.
WhitelistPaymasterwhitelisting 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)forge build --zksync src/arcade/ArcadeEscrow.sol script/DeployArcadeEscrow.s.solcompilesyarn lint(warnings only, same asPayment.sol),cspellclean on new files🤖 Generated with Claude Code