feat(treasury): 24h InstantWithdrawCw20 pull limit per spender+CW20 (#7) #28

Merged
PlasticDigits merged 1 commit from feat/cw20-pull-limit-24h-7 into master 2026-07-31 02:58:59 +00:00
PlasticDigits commented 2026-07-31 02:57:02 +00:00 (Migrated from gitlab.com)

Summary

Implements #7: governance-configurable tumbling 24h pull limits for InstantWithdrawCw20, keyed by (token, spender).

  • Fail-closed when limit unset/removed (Cw20PullLimitNotSet)
  • Tumbling 86400s window (wrap-mapper style); exceed → Cw20PullLimitExceeded with no Transfer
  • SetCw20Spender { limit_24h? }, SetCw20SpenderLimit / RemoveCw20SpenderLimit, query Cw20SpenderLimit
  • Quota accounting runs after solvency so failed balance checks do not burn quota
  • Docs + skill invariants updated (retired “no on-chain pull cap v1”)
  • Workspace version 0.2.0 → 0.2.1

Closes audit follow-ups H-2 / M-1 from audits/INTERNAL_COMPOSER_1785465508.md.

Acceptance criteria (#7)

  • Gov can set / update / remove a 24h pull limit for a specific (spender, token) pair; non-gov cannot
  • InstantWithdrawCw20 enforces the limit; exceed fails with clear error and no Transfer
  • Limit isolation across spender/token pairs
  • After 24h window resets, quota replenishes
  • Multiple pulls accumulate; exact remaining succeeds; remaining+1 fails
  • Pause / wrapping pause / whitelist / native InstantWithdraw / ProposeWithdraw unchanged (except docs)
  • Zero amount still rejected; insufficient balance fails independently (no quota burn)
  • Unregistered spender fails auth without consuming quota
  • Migrate preserves existing state; new limit storage available; pre-existing spenders fail-closed until limit set
  • Queries expose limit, used, remaining, window timing
  • Docs + skill updated; schema-ready; unit tests green

Not in this MR

  • LocalTerra / cw-multi-test balance-proof integration (unit tests cover WasmMsg + quota accounting)
  • Mainnet migrate + production limit_24h ops execution
  • ust1-window consumer UX for limit errors (companion ust1-window#20)
  • Formal A1–A14 security sign-off beyond unit coverage
  • Timelock on SetCw20Spender (still product choice from #6)

Test plan

  • cd contracts && cargo test --package treasury --lib → 150 passed
  • cd contracts && cargo test --package wrap-mapper --lib → 100 passed (native InstantWithdraw regression)
  • After merge: store + migrate treasury; SetCw20Spender with limit_24h for vFDUSD+window; query Cw20SpenderLimit
  • Confirm exhausted limit cannot drain full treasury balance
  • Confirm wrapping_paused / ProposeWithdraw unaffected
## Summary Implements [#7](https://gitlab.com/PlasticDigits2/ustr-cmm/-/issues/7): governance-configurable tumbling **24h pull limits** for `InstantWithdrawCw20`, keyed by `(token, spender)`. - Fail-closed when limit unset/removed (`Cw20PullLimitNotSet`) - Tumbling 86400s window (wrap-mapper style); exceed → `Cw20PullLimitExceeded` with no Transfer - `SetCw20Spender { limit_24h? }`, `SetCw20SpenderLimit` / `RemoveCw20SpenderLimit`, query `Cw20SpenderLimit` - Quota accounting runs after solvency so failed balance checks do not burn quota - Docs + skill invariants updated (retired “no on-chain pull cap v1”) - Workspace version `0.2.0` → `0.2.1` Closes audit follow-ups H-2 / M-1 from `audits/INTERNAL_COMPOSER_1785465508.md`. ## Acceptance criteria (#7) - [x] Gov can set / update / remove a 24h pull limit for a specific `(spender, token)` pair; non-gov cannot - [x] `InstantWithdrawCw20` enforces the limit; exceed fails with clear error and **no** Transfer - [x] Limit isolation across spender/token pairs - [x] After 24h window resets, quota replenishes - [x] Multiple pulls accumulate; exact remaining succeeds; remaining+1 fails - [x] Pause / wrapping pause / whitelist / native InstantWithdraw / ProposeWithdraw unchanged (except docs) - [x] Zero amount still rejected; insufficient balance fails independently (no quota burn) - [x] Unregistered spender fails auth without consuming quota - [x] Migrate preserves existing state; new limit storage available; pre-existing spenders fail-closed until limit set - [x] Queries expose limit, used, remaining, window timing - [x] Docs + skill updated; schema-ready; unit tests green ## Not in this MR - LocalTerra / cw-multi-test balance-proof integration (unit tests cover WasmMsg + quota accounting) - Mainnet migrate + production `limit_24h` ops execution - ust1-window consumer UX for limit errors (companion [ust1-window#20](https://gitlab.com/PlasticDigits/ust1-window/-/work_items/20)) - Formal A1–A14 security sign-off beyond unit coverage - Timelock on `SetCw20Spender` (still product choice from #6) ## Test plan - [x] `cd contracts && cargo test --package treasury --lib` → **150 passed** - [x] `cd contracts && cargo test --package wrap-mapper --lib` → **100 passed** (native InstantWithdraw regression) - [ ] After merge: store + migrate treasury; `SetCw20Spender` with `limit_24h` for vFDUSD+window; query `Cw20SpenderLimit` - [ ] Confirm exhausted limit cannot drain full treasury balance - [ ] Confirm `wrapping_paused` / ProposeWithdraw unaffected
PlasticDigits commented 2026-07-31 02:58:59 +00:00 (Migrated from gitlab.com)

mentioned in commit 98fc4de2a0

mentioned in commit 98fc4de2a0d8020a10ba07c9cf1417ff9826242c
PlasticDigits (Migrated from gitlab.com) merged commit 98fc4de2a0 into master 2026-07-31 02:58:59 +00:00
PlasticDigits commented 2026-07-31 03:13:52 +00:00 (Migrated from gitlab.com)

mentioned in commit b164ea7523

mentioned in commit b164ea7523a2fdd70604b3fb5f2f1dc5d24a3729
PlasticDigits commented 2026-07-31 03:21:16 +00:00 (Migrated from gitlab.com)

mentioned in issue #5

mentioned in issue #5
Sign in to join this conversation.
No reviewers
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/ustr-cmm!28
No description provided.