Performance package withdrawal limits - #494
metapileks wants to merge 15 commits into
Conversation
|
Repository Guard
Repository GuardCargo dependency pinning
Cross-program Anchor/Solana version consistency
solana-program crate pin
Anchor.toml solana_version
Crate minimum age
Yarn package.json pinning
npm minimum age
Workflow toolchain consistency
GitHub Action SHA pinning
Sensitive program / config changes
Overall status: pass Lockfile freshness (Cargo.lock + yarn.lock) is checked by the workflow directly and cannot be bypassed. The sensitive-diff section is a review hint - CODEOWNERS handles the actual merge gate. |
| /// The recipient's ATA that receives the unlocked balance - created if needed | ||
| #[account( | ||
| init_if_needed, | ||
| payer = admin, |
There was a problem hiding this comment.
Will admin always be an EOA?
There was a problem hiding this comment.
Any specific reason for this question?
The admin already isn't an EOA (it's a squads multisig) and this works just fine with it being a squads multisig. The vault can be a payer, it just has to have an adequate SOL balance, and there is no reentrancy.
LMK if something particular is bothering you here.
| let locked = self | ||
| .total_token_amount | ||
| .checked_sub(self.already_unlocked_amount) | ||
| .ok_or(PriceBasedPerformancePackageError::InvariantViolated)?; |
There was a problem hiding this comment.
I like this way of doing math since we can catch errors
There was a problem hiding this comment.
Or rather when they occur we can see where they are
| source_code: "https://github.com/metaDAOproject/programs", | ||
| source_release: "v0.6.0", | ||
| source_release: "v0.6.1", | ||
| policy: "The market will decide whether we pay a bug bounty.", |
There was a problem hiding this comment.
Side note, but we should probably have a proper bug bounty program at this point
| #[access_control(ctx.accounts.validate_with_limits(¶ms))] | ||
| pub fn initialize_performance_package_with_limits( | ||
| ctx: Context<InitializePerformancePackage>, | ||
| params: InitializePerformancePackageWithLimitsParams, | ||
| ) -> Result<()> { | ||
| InitializePerformancePackage::handle_with_limits(ctx, params) | ||
| } |
There was a problem hiding this comment.
Why not just put this in regular initialize performance package? Seems somewhat silly to add an extra function
There was a problem hiding this comment.
Mainly to keep compatibility with our launchpad programs without redeploying them. If you disagree with my reasoning I'd be okay with making that change as well.
Unlocking and withdrawing are now separate steps.
complete_unlockonly marks tranches as unlocked; the recipient pulls what is unlocked either as tokens (withdraw_tokens) or by selling into the DAO's spot pool and receiving the USDC (withdraw_via_sell).A package may carry a withdrawal policy: a cap on tokens and a cap on quote value per fixed window, a mode saying which of the two routes are open, and an end date after which the caps fall away. Token withdrawals are valued at the higher of the pool's price observation and its reserve price; sells count what the pool actually paid. Limits are set at creation or through the existing two-party change flow, which gains an
UnlockTermschange that moves the cliff and replaces the limits in one step. Without an active policy both routes are open and uncapped.The performance package account grows from 520 to 582 bytes. A permissionless resize migrates live packages, and every instruction refuses an unmigrated package.
Instruction changes
resize_performance_packagewithdrawal_policy = None; the payer covers the extra rent.AccountNotMigrateduntil the package is resized.initialize_performance_package_with_limits{ base, limits }and stores a policy anchored at the creation clock. Caps must be non-zero, the end in the future and the window at least one second (InvalidWithdrawalLimits).complete_unlockwithdraw_tokensamountfrom the vault, bounded by vault minus still-locked (InsufficientWithdrawableBalance). Under an active policy the mode must allow tokens and the window's token and quote caps must fit, valued from theDaoat the oracle account.withdraw_via_sellamountfrom its vault into the DAO's spot pool through futarchy'sspot_swap, guarded bymin_quote_out, and forwards exactly the proceeds. Under an active policy the mode must allow sells, the token cap applies and the quote cap counts the proceeds.propose_change/execute_changeChangeType::UnlockTerms { min_unlock_timestamp, limits: Option<LimitsParams> }, allowed in any state. Propose validates the limits; execute sets the cliff and replaces the policy: same window length keeps the usage, a different one re-anchors the windows at now,Noneremoves it.burn_performance_packageInvalidQuoteMint); the ATA and destination come together (QuoteSweepAccountsIncomplete). New accounts: recipient, its ATA, the three optional quote accounts, system and associated token programs.State, events and errors
PerformancePackage.withdrawal_policy: Option<WithdrawalPolicy>appended, withlimits: WithdrawalLimits(start, end, window seconds, both caps, modeTokens | Sell | Both) andusage: WindowUsage(window index, tokens used, quote used).OldPerformancePackagedecodes the 520-byte layout for the resize.futarchywith thecpifeature.TokensWithdrawn(withcapped: Option<CappedWithdrawal>, the valuation price, quote value and usage when limits were active) andTokensSold(amount, proceeds,min_quote_out,capped: Option<WindowUsage>).InvalidWithdrawalLimits,InsufficientWithdrawableBalance,TokenWindowLimitExceeded,QuoteWindowLimitExceeded,InvalidPriceObservation,WithdrawTokensDisabled,WithdrawViaSellDisabled,AccountNotMigrated,InvalidQuoteMint,QuoteSweepAccountsIncomplete.SDK and scripts
price_based_performance_package/v0.6regenerated in place.completeUnlockIxtakes only the package and oracle; newinitializePerformancePackageWithLimitsIx,withdrawTokensIx,withdrawViaSellIx,resizePerformancePackageIx;burnPerformancePackageIxtakes the recipient and, optionally,quoteMintandquoteDestinationfor the sweep;proposeChangeIxis typed withProposeChangeParams.price_based_performance_package/v0.6/withdrawalLimits: active policy, current window and effective usage, withdrawable balance, valuation price, maximum token withdrawal and a sell proceeds estimate, all computed from fetched accounts.resizePerformancePackages.ts(modelled onresizeDaos.ts), aproposePerformancePackageUnlockTermsDAO action with the Rip Cars admin enqueuerip-cars/proposeUnlockTerms.ts,executeChange.tsfor the recipient, and the burn script passing the new accounts.Greptile Summary
This PR separates tranche unlocking from withdrawal, adds capped token and sell-based withdrawal routes, migrates existing package accounts to the expanded state layout, and updates retirement, change-management, SDK, scripts, and tests accordingly.
Confidence Score: 5/5
The PR appears safe to merge; the previously reported SDK argument issue is fixed and no actionable new defect remains.
The earlier migration finding was correctly conceded and its thread is resolved. The quote-sweep builder now accepts a single typed pair, fully addressing the other previous finding. The changes since the prior review introduce no new blocking or non-blocking findings.
Important Files Changed
Reviews (2): Last reviewed commit: "burn pp - move quote sweep into a single..." | Re-trigger Greptile
Context used: