-
Notifications
You must be signed in to change notification settings - Fork 85
Mini-instructions (MVP) #477
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
ba8e57f
3e81a70
0ccd17b
ba791c3
0d6ab0b
d3dcea3
759a8a1
b26d280
fc4bb82
ef2fbf7
9bf7aa5
cd1afde
9515959
59e57c6
d8a8b0c
e9a8d78
13db286
ee5bb42
94166c0
7f42131
21a61a2
44bfa45
8d25d31
686f9d5
a8110ca
a298864
2608fb8
4a23ef9
69df3dc
feebe8a
796ffe8
6c5f5f0
79544df
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,17 +62,28 @@ pub struct AdminEnqueueMultisigProposalApproval<'info> { | |
|
|
||
| impl AdminEnqueueMultisigProposalApproval<'_> { | ||
| pub fn validate(&self, _args: &AdminEnqueueMultisigProposalApprovalArgs) -> Result<()> { | ||
| #[cfg(feature = "production")] | ||
| require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin); | ||
| // On a liquidated DAO the liquidator replaces the admin id as the | ||
| // required signer. Enqueueing is the only capability the liquidator | ||
| // gains: the approve leg stays permissionless and execution is | ||
| // ordinary top-level Squads execution. | ||
|
Comment on lines
+66
to
+68
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How does this work?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We reuse our existing system of enqueueing and executing an admin-controller Squads proposal approval, but now whoever is the liquidator can do the enqueueing of the approval. Running the approval itself is always permissionless. |
||
| match self.dao.liquidator { | ||
| Some(liquidator) => { | ||
| require_keys_eq!( | ||
| self.admin.key(), | ||
| liquidator, | ||
| FutarchyError::InvalidLiquidator | ||
| ); | ||
| } | ||
| None => { | ||
| #[cfg(feature = "production")] | ||
| require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin); | ||
| } | ||
| } | ||
|
|
||
| if !matches!(self.dao.amm.state, PoolState::Spot { .. }) { | ||
| return Err(FutarchyError::PoolNotInSpotState.into()); | ||
| } | ||
|
|
||
| if self.dao.optimistic_proposal.is_some() { | ||
| return Err(FutarchyError::ActiveOptimisticProposalAlreadyEnqueued.into()); | ||
| } | ||
|
|
||
| validate_squads_proposal( | ||
| &self.squads_multisig_proposal, | ||
| &self.squads_multisig, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| use super::*; | ||
|
|
||
| mod admin { | ||
| use anchor_lang::prelude::declare_id; | ||
|
|
||
| // MetaDAO ops multisig — the same signer as the approval enqueue | ||
| declare_id!("6awyHMshBGVjJ3ozdSJdyyDE1CTAXUwrpNMaRGMsb4sf"); | ||
| } | ||
|
|
||
| #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] | ||
| pub struct AdminUpdateProposalParamsArgs { | ||
| pub duration_in_seconds: Option<u32>, | ||
| pub pass_threshold_bps: Option<i16>, | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| #[event_cpi] | ||
| pub struct AdminUpdateProposalParams<'info> { | ||
| #[account(mut)] | ||
| pub dao: Box<Account<'info, Dao>>, | ||
|
|
||
| #[account(mut, has_one = dao)] | ||
| pub proposal: Box<Account<'info, Proposal>>, | ||
|
|
||
| pub admin: Signer<'info>, | ||
| } | ||
|
|
||
| impl AdminUpdateProposalParams<'_> { | ||
| pub fn validate(&self, args: &AdminUpdateProposalParamsArgs) -> Result<()> { | ||
| #[cfg(feature = "production")] | ||
| require_keys_eq!(self.admin.key(), admin::ID, FutarchyError::InvalidAdmin); | ||
|
|
||
| require!(self.dao.liquidator.is_none(), FutarchyError::DaoLiquidated); | ||
|
|
||
| // Only arbitrary proposals can be modified | ||
| require!( | ||
| matches!(self.proposal.action, ProposalAction::ExecuteArbitrary), | ||
| FutarchyError::InvalidProposalKind | ||
| ); | ||
|
|
||
| // A live proposal's terms never move: the snapshot exists so later | ||
| // changes can't shift the goalposts under it | ||
| require!( | ||
| matches!(self.proposal.state, ProposalState::Draft { .. }), | ||
| FutarchyError::ProposalNotInDraftState | ||
| ); | ||
|
|
||
| // An admin transaction that does nothing should fail | ||
| require!( | ||
| args.duration_in_seconds.is_some() || args.pass_threshold_bps.is_some(), | ||
| FutarchyError::EmptyProposalParamsUpdate | ||
| ); | ||
|
|
||
| if let Some(duration_in_seconds) = args.duration_in_seconds { | ||
| // The same comparison `launch_proposal` makes | ||
| require_gt!( | ||
| duration_in_seconds, | ||
| self.proposal.action.params().twap_start_delay_seconds, | ||
| FutarchyError::ProposalDurationTooShort | ||
| ); | ||
| } | ||
|
|
||
| if let Some(pass_threshold_bps) = args.pass_threshold_bps { | ||
| require_gte!( | ||
| MAX_PROPOSAL_PASS_THRESHOLD_BPS, | ||
| pass_threshold_bps, | ||
| FutarchyError::InvalidProposalPassThreshold | ||
| ); | ||
| require_gte!( | ||
| pass_threshold_bps, | ||
| MIN_PROPOSAL_PASS_THRESHOLD_BPS, | ||
| FutarchyError::InvalidProposalPassThreshold | ||
| ); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn handle(ctx: Context<Self>, args: AdminUpdateProposalParamsArgs) -> Result<()> { | ||
| let proposal = &mut ctx.accounts.proposal; | ||
| let dao = &mut ctx.accounts.dao; | ||
|
|
||
| let old_duration_in_seconds = proposal.duration_in_seconds; | ||
| let old_pass_threshold_bps = proposal.pass_threshold_bps; | ||
|
|
||
| if let Some(duration_in_seconds) = args.duration_in_seconds { | ||
| proposal.duration_in_seconds = duration_in_seconds; | ||
| } | ||
|
|
||
| if let Some(pass_threshold_bps) = args.pass_threshold_bps { | ||
| proposal.pass_threshold_bps = pass_threshold_bps; | ||
| } | ||
|
|
||
| dao.seq_num += 1; | ||
| let clock = Clock::get()?; | ||
|
|
||
| emit_cpi!(AdminUpdateProposalParamsEvent { | ||
| common: CommonFields::new(&clock, dao.seq_num), | ||
| dao: dao.key(), | ||
| proposal: proposal.key(), | ||
| admin: ctx.accounts.admin.key(), | ||
| old_duration_in_seconds, | ||
| new_duration_in_seconds: proposal.duration_in_seconds, | ||
| old_pass_threshold_bps, | ||
| new_pass_threshold_bps: proposal.pass_threshold_bps, | ||
| }); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like admin should be more for security stuff and we can have a different one for council blocking proposals, maybe? Not a strong opinion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. Mainly holding this here until we make the decision on whether we're merging in #469 so that we know whether that will be the council (one of the parties in the multisig), or we will have a separate council that can cancel a proposal at any time.