#![deny(rustdoc::all)]
#![allow(rustdoc::missing_doc_code_examples)]
use anchor_lang::prelude::*;
use num_traits::cast::ToPrimitive;
use smart_wallet::SmartWallet;
use vipers::prelude::*;
mod account_structs;
mod account_validators;
mod events;
mod macros;
pub mod proposal;
mod state;
use account_structs::*;
pub use events::*;
pub use proposal::*;
pub use state::*;
declare_id!("Govz1VyoyLD5BL6CSCxUJLVLsQHRwjfFj1prNsdNg5Jw");
#[program]
pub mod govern {
use super::*;
#[access_control(ctx.accounts.validate())]
pub fn create_governor(
ctx: Context<CreateGovernor>,
_bump: u8,
electorate: Pubkey,
params: GovernanceParameters,
) -> Result<()> {
invariant!(
params.timelock_delay_seconds >= 0,
"timelock delay must be at least 0 seconds"
);
let governor = &mut ctx.accounts.governor;
governor.base = ctx.accounts.base.key();
governor.bump = unwrap_bump!(ctx, "governor");
governor.proposal_count = 0;
governor.electorate = electorate;
governor.smart_wallet = ctx.accounts.smart_wallet.key();
governor.params = params;
emit!(GovernorCreateEvent {
governor: governor.key(),
electorate,
smart_wallet: ctx.accounts.smart_wallet.key(),
parameters: params,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn create_proposal(
ctx: Context<CreateProposal>,
_bump: u8,
instructions: Vec<ProposalInstruction>,
) -> Result<()> {
let governor = &mut ctx.accounts.governor;
let proposal = &mut ctx.accounts.proposal;
proposal.governor = governor.key();
proposal.index = governor.proposal_count;
proposal.bump = unwrap_bump!(ctx, "proposal");
proposal.proposer = ctx.accounts.proposer.key();
proposal.quorum_votes = governor.params.quorum_votes;
proposal.created_at = Clock::get()?.unix_timestamp;
proposal.canceled_at = 0;
proposal.activated_at = 0;
proposal.voting_ends_at = 0;
proposal.queued_at = 0;
proposal.queued_transaction = Pubkey::default();
proposal.instructions = instructions.clone();
governor.proposal_count += 1;
emit!(ProposalCreateEvent {
governor: governor.key(),
proposal: proposal.key(),
index: proposal.index,
instructions,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn activate_proposal(ctx: Context<ActivateProposal>) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
let now = Clock::get()?.unix_timestamp;
proposal.activated_at = now;
proposal.voting_ends_at = unwrap_int!(ctx
.accounts
.governor
.params
.voting_period
.to_i64()
.and_then(|v: i64| now.checked_add(v)));
emit!(ProposalActivateEvent {
governor: proposal.governor,
proposal: proposal.key(),
voting_ends_at: proposal.voting_ends_at,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn cancel_proposal(ctx: Context<CancelProposal>) -> Result<()> {
let proposal = &mut ctx.accounts.proposal;
proposal.canceled_at = Clock::get()?.unix_timestamp;
emit!(ProposalCancelEvent {
governor: proposal.governor,
proposal: proposal.key(),
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn queue_proposal(ctx: Context<QueueProposal>, tx_bump: u8) -> Result<()> {
ctx.accounts.queue_transaction(tx_bump)?;
emit!(ProposalQueueEvent {
governor: ctx.accounts.proposal.governor,
proposal: ctx.accounts.proposal.key(),
transaction: ctx.accounts.transaction.key(),
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn new_vote(ctx: Context<NewVote>, _bump: u8, voter: Pubkey) -> Result<()> {
let vote = &mut ctx.accounts.vote;
vote.proposal = ctx.accounts.proposal.key();
vote.voter = voter;
vote.bump = unwrap_bump!(ctx, "vote");
vote.side = VoteSide::Pending.into();
vote.weight = 0;
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn set_vote(ctx: Context<SetVote>, side: u8, weight: u64) -> Result<()> {
let vote = &ctx.accounts.vote;
let proposal = &mut ctx.accounts.proposal;
proposal.subtract_vote_weight(vote.side.try_into()?, vote.weight)?;
proposal.add_vote_weight(side.try_into()?, weight)?;
let vote = &mut ctx.accounts.vote;
vote.side = side;
vote.weight = weight;
emit!(VoteSetEvent {
governor: proposal.governor,
proposal: proposal.key(),
voter: vote.voter,
vote: vote.key(),
side,
weight,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn set_governance_params(
ctx: Context<SetGovernanceParams>,
params: GovernanceParameters,
) -> Result<()> {
let prev_params = ctx.accounts.governor.params;
ctx.accounts.governor.params = params;
emit!(GovernorSetParamsEvent {
governor: ctx.accounts.governor.key(),
prev_params,
params,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn set_electorate(ctx: Context<SetGovernanceParams>, new_electorate: Pubkey) -> Result<()> {
let prev_electorate = ctx.accounts.governor.electorate;
ctx.accounts.governor.electorate = new_electorate;
emit!(GovernorSetElectorateEvent {
governor: ctx.accounts.governor.key(),
prev_electorate,
new_electorate,
});
Ok(())
}
#[access_control(ctx.accounts.validate())]
pub fn create_proposal_meta(
ctx: Context<CreateProposalMeta>,
_bump: u8,
title: String,
description_link: String,
) -> Result<()> {
let proposal_meta = &mut ctx.accounts.proposal_meta;
proposal_meta.proposal = ctx.accounts.proposal.key();
proposal_meta.title = title.clone();
proposal_meta.description_link = description_link.clone();
emit!(ProposalMetaCreateEvent {
governor: ctx.accounts.proposal.governor,
proposal: ctx.accounts.proposal.key(),
title,
description_link,
});
Ok(())
}
}
#[error_code]
pub enum ErrorCode {
#[msg("Invalid vote side.")]
InvalidVoteSide,
#[msg("The owner of the smart wallet doesn't match with current.")]
GovernorNotFound,
#[msg("The proposal cannot be activated since it has not yet passed the voting delay.")]
VotingDelayNotMet,
#[msg("Only drafts can be canceled.")]
ProposalNotDraft,
#[msg("The proposal must be active.")]
ProposalNotActive,
}