simple_voter/
lib.rs

1//! A simple Tribeca voter program where 1 token = 1 vote.
2//!
3//! WARNING: this program is incomplete. Do not use this in production directly.
4//! Do not use this as a reference. Do not collect $200.
5
6use anchor_lang::prelude::*;
7use anchor_spl::token::*;
8use govern::{proposal::ProposalState, Governor, Proposal, Vote};
9use vipers::*;
10
11mod account_validators;
12pub mod macros;
13mod processor;
14mod state;
15mod token_cpi;
16
17pub use state::*;
18
19declare_id!("Tok6iuA69RLN1QrpXgQKnDgE1YYbLzQsZGSoz75fQdz");
20
21#[program]
22pub mod simple_voter {
23    use super::*;
24
25    #[access_control(ctx.accounts.validate())]
26    pub fn initialize_electorate(
27        ctx: Context<InitializeElectorate>,
28        _bump: u8,
29        proposal_threshold: u64,
30    ) -> Result<()> {
31        let electorate = &mut ctx.accounts.electorate;
32        electorate.bump = unwrap_bump!(ctx, "electorate");
33        electorate.proposal_threshold = proposal_threshold;
34        electorate.base = ctx.accounts.base.key();
35        electorate.governor = ctx.accounts.governor.key();
36        electorate.gov_token_mint = ctx.accounts.gov_token_mint.key();
37
38        Ok(())
39    }
40
41    #[access_control(ctx.accounts.validate())]
42    pub fn initialize_token_record(ctx: Context<InitializeTokenRecord>, _bump: u8) -> Result<()> {
43        let token_record = &mut ctx.accounts.token_record;
44        token_record.bump = unwrap_bump!(ctx, "token_record");
45        token_record.balance = ctx.accounts.gov_token_vault.amount;
46        token_record.authority = ctx.accounts.authority.key();
47        token_record.electorate = ctx.accounts.electorate.key();
48        token_record.token_vault_key = ctx.accounts.gov_token_vault.key();
49
50        Ok(())
51    }
52
53    #[access_control(ctx.accounts.validate())]
54    pub fn activate_proposal(ctx: Context<ActivateProposal>) -> Result<()> {
55        processor::proposer::activate_proposal(ctx)
56    }
57
58    #[access_control(ctx.accounts.validate())]
59    pub fn deposit_tokens(ctx: Context<TokenContext>, amount: u64) -> Result<()> {
60        ctx.accounts.transfer_to_vault(amount)?;
61
62        let token_record = &mut ctx.accounts.token_record;
63        let vault = &mut ctx.accounts.gov_token_vault;
64        vault.reload()?;
65        token_record.balance = vault.amount;
66
67        Ok(())
68    }
69
70    #[access_control(ctx.accounts.validate())]
71    pub fn withdraw_tokens(ctx: Context<TokenContext>, amount: u64) -> Result<()> {
72        ctx.accounts.transfer_from_vault(amount)?;
73
74        let token_record = &mut ctx.accounts.token_record;
75        invariant!(
76            token_record.unfinalized_votes == 0,
77            "some votes not finalized"
78        );
79        let vault = &mut ctx.accounts.gov_token_vault;
80        vault.reload()?;
81        token_record.balance = vault.amount;
82
83        Ok(())
84    }
85
86    #[access_control(ctx.accounts.validate())]
87    pub fn cast_votes(ctx: Context<VoterContext>, vote_side: u8) -> Result<()> {
88        processor::voter::process_cast_votes(ctx, vote_side)
89    }
90
91    #[access_control(ctx.accounts.validate())]
92    pub fn withdraw_votes(ctx: Context<VoterContext>) -> Result<()> {
93        processor::voter::process_withdraw_votes(ctx)
94    }
95
96    pub fn finalize_votes(ctx: Context<FinalizeVote>) -> Result<()> {
97        invariant!(ctx.accounts.proposal.get_state()? != ProposalState::Active);
98        let token_record = &mut ctx.accounts.token_record;
99        token_record.unfinalized_votes = unwrap_int!(token_record.unfinalized_votes.checked_sub(1));
100
101        Ok(())
102    }
103}
104
105#[derive(Accounts)]
106pub struct InitializeElectorate<'info> {
107    /// Base used to create the voter.
108    pub base: Signer<'info>,
109    /// The electorate.
110    #[account(
111        init,
112        seeds = [
113            b"SimpleElectorate".as_ref(),
114            base.key().to_bytes().as_ref()
115        ],
116        bump,
117        payer = payer,
118        space = 8 + Electorate::LEN
119    )]
120    pub electorate: Account<'info, Electorate>,
121    /// TODO(michael): Docs
122    pub governor: Account<'info, Governor>,
123    /// TODO(michael): Docs
124    pub gov_token_mint: Account<'info, Mint>,
125    /// TODO(michael): Docs
126    #[account(mut)]
127    pub payer: Signer<'info>,
128    /// TODO(michael): Docs
129    pub system_program: Program<'info, System>,
130}
131
132#[derive(Accounts)]
133pub struct InitializeTokenRecord<'info> {
134    pub authority: Signer<'info>,
135    /// TODO(michael): Docs
136    #[account(
137        init,
138        seeds = [
139            b"SimpleTokenRecord".as_ref(),
140            authority.key().to_bytes().as_ref(),
141            electorate.key().to_bytes().as_ref()
142        ],
143        bump,
144        payer = payer,
145        space = 8 + TokenRecord::LEN
146    )]
147    pub token_record: Account<'info, state::TokenRecord>,
148    #[account(mut)]
149    pub electorate: Account<'info, state::Electorate>,
150    /// TODO(michael): Docs
151    pub gov_token_vault: Account<'info, TokenAccount>,
152    /// TODO(michael): Docs
153    #[account(mut)]
154    pub payer: Signer<'info>,
155    /// TODO(michael): Docs
156    pub system_program: Program<'info, System>,
157}
158
159#[derive(Accounts)]
160pub struct FinalizeVote<'info> {
161    /// TODO(michael): Docs
162    pub authority: Signer<'info>,
163    /// TODO(michael): Docs
164    #[account(mut)]
165    pub governor: Account<'info, Governor>,
166    /// TODO(michael): Docs
167    #[account(mut)]
168    pub proposal: Account<'info, Proposal>,
169    /// TODO(michael): Docs
170    #[account(mut)]
171    pub token_record: Account<'info, state::TokenRecord>,
172}
173
174#[derive(Accounts)]
175pub struct TribecaContext<'info> {
176    /// TODO(michael): Docs
177    #[account(mut)]
178    pub governor: Account<'info, Governor>,
179    /// TODO(michael): Docs
180    pub program: Program<'info, govern::program::Govern>,
181}
182
183#[derive(Accounts)]
184pub struct ActivateProposal<'info> {
185    #[account(has_one = governor)]
186    pub electorate: Account<'info, state::Electorate>,
187    pub governor: Account<'info, Governor>,
188    #[account(mut, has_one = governor)]
189    pub proposal: Account<'info, Proposal>,
190    /// The [govern] program.
191    pub govern_program: Program<'info, govern::program::Govern>,
192}
193
194#[derive(Accounts)]
195pub struct TokenContext<'info> {
196    /// TODO(michael): Docs
197    pub authority: Signer<'info>,
198    /// TODO(michael): Docs
199    #[account(mut)]
200    pub token_record: Account<'info, state::TokenRecord>,
201    /// TODO(michael): Docs
202    #[account(mut)]
203    pub gov_token_account: Account<'info, TokenAccount>,
204    /// TODO(michael): Docs
205    #[account(mut)]
206    pub gov_token_vault: Account<'info, TokenAccount>,
207    /// TODO(michael): Docs
208    pub token_program: Program<'info, Token>,
209}
210
211#[derive(Accounts)]
212pub struct VoterContext<'info> {
213    /// The [Electorate].
214    pub electorate: Account<'info, Electorate>,
215    /// TODO(michael): Docs
216    pub authority: Signer<'info>,
217    /// TODO(michael): Docs
218    #[account(mut)]
219    pub proposal: Account<'info, Proposal>,
220    /// TODO(michael): Docs
221    #[account(mut)]
222    pub token_record: Account<'info, state::TokenRecord>,
223    /// TODO(michael): Docs
224    #[account(mut)]
225    pub vote: Account<'info, Vote>,
226    /// TODO(michael): Docs
227    pub tribeca: TribecaContext<'info>,
228}
229
230#[error_code]
231pub enum ErrorCode {
232    #[msg("Below proposing threshold.")]
233    BelowProposingThreshold,
234}