feat(treasury): 24h InstantWithdrawCw20 pull limit per spender+CW20 #7
Labels
No labels
bug
docs
documentation
duplicate
enhancement
frontend
good first issue
help wanted
invalid
missing-implementation
priority
medium
product
QA
question
testing
UX
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
code/ustr-cmm#7
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Add a governance-configurable 24-hour pull limit for treasury
InstantWithdrawCw20, keyed by (spender, CW20 token) so each registered spender has an independent rolling (or fixed-window) quota per token it can pull.This closes the documented v1 gap from #6 / MR !27: “No on-chain CW20 pull cap (v1); window-side limits are the only product control.” Also tracks audit follow-up in
audits/INTERNAL_COMPOSER_1785465508.md(H-2 / M-1 / P3).Companion consumer remains ust1-window#20 (window may keep its own inventory policy; treasury limit is a hard ceiling).
Current codebase
contracts/contracts/treasury/src/state.rs→CW20_SPENDERSMap<&str, Addr>: one spender per token; overwrite onSetCw20Spender. Doc invariant: registered spender may drain full treasury balance of that token.contracts/contracts/treasury/src/contract.rs→execute_instant_withdraw_cw20cw20_iw_paused, zero amount,sender == CW20_SPENDERS[token], treasury CW20 balance ≥ amount; emitsCw20ExecuteMsg::Transfer. No cumulative / time-window accounting.contracts/contracts/treasury/src/msg.rsSetCw20Spender/RemoveCw20Spender/SetCw20InstantWithdrawPaused/InstantWithdrawCw20; queryCw20Spenders {}.CW20_INSTANT_WITHDRAW_PAUSED(cw20_iw_paused)wrapping_paused.contracts/contracts/wrap-mapper/src/{state,contract}.rsRateLimitConfig+RateLimitState(max_amount, tumbling window,amount_used/window_start);SetRateLimit/RemoveRateLimit/check_rate_limit.docs/CONTRACTS.mddecision #10,skills/treasury-cw20-instant-withdraw/SKILL.mdinvariant #4Implication of “per spender + CW20”: Today auth is token→single spender. Limits must still be keyed by (spender, token) so that (a) the same spender registered on multiple tokens has separate quotas, (b) rotating the spender resets or isolates usage under the new identity, and (c) a future multi-spender-per-token model (if pursued) does not require another storage redesign. Prefer composite keys even if v1 keeps “at most one spender per token.”
Why this is needed
Without this, mainnet
SetCw20Spenderremains an all-or-nothing trust grant for the full token balance.Constraints / guardrails
wrapping_paused≠cw20_iw_paused), native wrap InstantWithdraw, or ProposeWithdraw / ExecuteWithdraw.(spender, token)— not global, not token-only, not spender-only.window_start, matching wrap-mapper) or calendar UTC; prefer tumbling for consistency with wrap-mapper unless product requires calendar days.limit == null/ absent ⇒ deny pulls until gov sets a limit (fail-closed for new registrations), orUint128::MAX/ “unlimited” opt-in for parity with v1 during migration.Prefer fail-closed for new
SetCw20Spenderafter this feature, with a migrate path that sets an explicit high limit for any pre-existing mapping if needed.SetCw20Spender/ wrap-mapperSetRateLimit), but document ops risk.cw20_spenders,cw20_whitelist,denom_wrappers,cw20_iw_paused.Relevant files
contracts/contracts/treasury/src/msg.rs— new execute/query variants; possibly extendSetCw20Spenderor addSetCw20SpenderLimitcontracts/contracts/treasury/src/state.rs— limit config + usage maps; extend spender value type if neededcontracts/contracts/treasury/src/contract.rs— enforce inexecute_instant_withdraw_cw20; gov setters; migratecontracts/contracts/treasury/src/error.rs— e.g.Cw20PullLimitExceeded { spender, token, requested, remaining, reset_at }contracts/contracts/wrap-mapper/src/state.rs+contract.rs— reference rate-limit pattern (RateLimitConfig/check_rate_limit)docs/CONTRACTS.md,docs/ARCHITECTURE.md,docs/DEPLOYMENT.md— replace “no on-chain pull cap” with limit semantics + opsskills/treasury-cw20-instant-withdraw/SKILL.md— update invariants (retire “no pull cap v1”)plans/NATIVE_TOKEN_WRAPPING.md— cross-link if it still describes CW20 IW as uncappedRecommended direction
Mirror wrap-mapper’s rate-limit shape, keyed by composite
(spender, token):Storage sketch:
cw20_pull_limitsMap<(spender, token), Uint128>or structcw20_pull_usageMap<(spender, token), { amount_used, window_start }>On
InstantWithdrawCw20:(info.sender, token); if unset → error (fail-closed) or unlimited (if product chooses).now >= window_start + 86400, resetamount_used = 0,window_start = now.amount_used + amount > limit→Cw20PullLimitExceeded.amount_used += amount, then emit Transfer.Spender rotation: When
SetCw20Spenderoverwrites spender A→B for a token, usage under A no longer applies to B (B starts fresh). Optionally clear A’s usage for that token on remove/overwrite to avoid storage bloat (document).Do not gate native InstantWithdraw or ProposeWithdraw with this limit.
Acceptance criteria
(spender, token)pair; non-gov cannot.InstantWithdrawCw20enforces the limit for the caller+token; pulls that would exceed remaining quota fail with a clear error and no Transfer.limit_24h.#[cw_serde]); unit tests green.Test plan — functional paths
SetCw20SpenderLimit(or SetCw20Spender with limit)amount_usedincreasesCw20PullLimitExceeded; no Transfer; used unchangedCw20InstantWithdrawPausedInsufficientBalance(not limit error)ZeroAmount; used unchangedTest plan — attack / abuse / hack vectors
amount_used + amountcw20_spenders/ whitelistVerification criteria
cargo test --package treasury --libgreen including new limit cases above.limit_24hbefore or with enabling redeem; confirm exhausted limit cannot drain full treasury balance.Dependencies
SetCw20Spenderfor vFDUSD (strongly preferred for blast-radius control).mentioned in commit
b9cd7cc3f7mentioned in merge request !28
mentioned in commit
98fc4de2a0mentioned in commit
b164ea7523mentioned in issue #5
mentioned in issue #8
mentioned in merge request PlasticDigits/ust1-window!23
mentioned in issue #10