Repository navigation
Performance package withdrawal limits - #494
metapileks merged 27 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.
…ce-based-performance-package-vesting-and-distribution
… note live-proposal sell usage
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.

Confidence Score: 5/5
The changes since the previous review appear safe to merge.Summary
The PR separates unlocking from withdrawals, adds withdrawal limits and sell withdrawals, and updates migration, retirement, and SDK support.
Since the previous review:
Reviews (4) · Last reviewed commit: "fix(pbpp): build the v0.6 burn script wi..." · Reviewed by Greptile