locked_voter/
lib.rs

1//! Voter which locks up governance tokens for a user-provided duration in exchange for increased voting power.
2#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4#![deny(clippy::unwrap_used)]
5
6pub mod macros;
7
8use anchor_lang::prelude::*;
9use anchor_spl::token::{Mint, Token, TokenAccount};
10use govern::{Governor, Proposal, Vote};
11use vipers::prelude::*;
12
13mod instructions;
14pub mod locker;
15mod state;
16
17pub use instructions::*;
18pub use state::*;
19
20declare_id!("LocktDzaV1W2Bm9DeZeiyz4J9zs4fRqNiYqQyracRXw");
21
22/// Locked voter program.
23#[deny(missing_docs)]
24#[program]
25pub mod locked_voter {
26    use super::*;
27
28    /// Creates a new [Locker].
29    #[access_control(ctx.accounts.validate())]
30    pub fn new_locker(ctx: Context<NewLocker>, _bump: u8, params: LockerParams) -> Result<()> {
31        ctx.accounts.new_locker(unwrap_bump!(ctx, "locker"), params)
32    }
33
34    /// Creates a new [Escrow] for an account.
35    ///
36    /// A Vote Escrow, or [Escrow] for short, is an agreement between an account (known as the `authority`) and the DAO to
37    /// lock up tokens for a specific period of time, in exchange for voting rights
38    /// linearly proportional to the amount of votes given.
39    #[access_control(ctx.accounts.validate())]
40    pub fn new_escrow(ctx: Context<NewEscrow>, _bump: u8) -> Result<()> {
41        ctx.accounts.new_escrow(unwrap_bump!(ctx, "escrow"))
42    }
43
44    /// Stakes `amount` tokens into the [Escrow].
45    /// WARNING: if the program has a whitelist, one should use [crate::locked_voter::lock_with_whitelist] instead.
46    /// This version of the instruction is deprecated.
47    #[access_control(ctx.accounts.validate())]
48    pub fn lock<'info>(
49        ctx: Context<'_, '_, '_, 'info, Lock<'info>>,
50        amount: u64,
51        duration: i64,
52    ) -> Result<()> {
53        msg!("Warning: this instruction is deprecated in favor of `lock_with_whitelist`, `lock_with_whitelist_entry`, or `lock_permissionless`. Please update your Tribeca SDK.");
54        if ctx.accounts.locker.params.whitelist_enabled {
55            ctx.accounts.check_whitelisted(ctx.remaining_accounts)?;
56        }
57        ctx.accounts.lock(amount, duration)
58    }
59
60    /// Stakes `amount` tokens into the [Escrow], if there is a whitelist.
61    #[access_control(ctx.accounts.validate())]
62    pub fn lock_with_whitelist<'info>(
63        ctx: Context<'_, '_, '_, 'info, LockWithWhitelist<'info>>,
64        amount: u64,
65        duration: i64,
66    ) -> Result<()> {
67        instructions::lock_with_whitelist::handler(ctx, amount, duration)
68    }
69
70    /// Stakes `amount` tokens into the [Escrow] via CPI by using the provided [LockerWhitelistEntry].
71    #[access_control(ctx.accounts.validate())]
72    pub fn lock_with_whitelist_entry<'info>(
73        ctx: Context<'_, '_, '_, 'info, LockWithWhitelistEntry<'info>>,
74        amount: u64,
75        duration: i64,
76    ) -> Result<()> {
77        instructions::lock_with_whitelist_entry::handler(ctx, amount, duration)
78    }
79
80    /// Stakes `amount` tokens into the [Escrow] if there is no whitelist.
81    #[access_control(ctx.accounts.validate())]
82    pub fn lock_permissionless<'info>(
83        ctx: Context<'_, '_, '_, 'info, Lock<'info>>,
84        amount: u64,
85        duration: i64,
86    ) -> Result<()> {
87        instructions::lock_permissionless::handler(ctx, amount, duration)
88    }
89
90    /// Exits the DAO; i.e., withdraws all staked tokens in an [Escrow] if the [Escrow] is unlocked.
91    #[access_control(ctx.accounts.validate())]
92    pub fn exit(ctx: Context<Exit>) -> Result<()> {
93        ctx.accounts.exit()
94    }
95
96    /// Activates a proposal.
97    #[access_control(ctx.accounts.validate())]
98    pub fn activate_proposal(ctx: Context<ActivateProposal>) -> Result<()> {
99        ctx.accounts.activate_proposal()
100    }
101
102    /// Casts a vote.
103    #[access_control(ctx.accounts.validate())]
104    pub fn cast_vote(ctx: Context<CastVote>, side: u8) -> Result<()> {
105        ctx.accounts.cast_vote(side)
106    }
107
108    /// Delegate escrow vote.
109    #[access_control(ctx.accounts.validate())]
110    pub fn set_vote_delegate(ctx: Context<SetVoteDelegate>, new_delegate: Pubkey) -> Result<()> {
111        ctx.accounts.set_vote_delegate(new_delegate)
112    }
113
114    /// Set locker params.
115    #[access_control(ctx.accounts.validate())]
116    pub fn set_locker_params(ctx: Context<SetLockerParams>, params: LockerParams) -> Result<()> {
117        ctx.accounts.set_locker_params(params)
118    }
119
120    /// Creates a new [LockerWhitelistEntry] to whitelist program from CPI.
121    #[access_control(ctx.accounts.validate())]
122    pub fn approve_program_lock_privilege(
123        ctx: Context<ApproveProgramLockPrivilege>,
124        _bump: u8,
125    ) -> Result<()> {
126        ctx.accounts
127            .approve_program_lock_privilege(unwrap_bump!(ctx, "whitelist_entry"))
128    }
129
130    /// Close a [LockerWhitelistEntry] revoking program's CPI privilege.
131    #[access_control(ctx.accounts.validate())]
132    pub fn revoke_program_lock_privilege(ctx: Context<RevokeProgramLockPrivilege>) -> Result<()> {
133        ctx.accounts.revoke_program_lock_privilege()
134    }
135}
136
137/// [locked_voter] errors.
138#[error_code]
139pub enum ErrorCode {
140    #[msg("CPI caller not whitelisted to invoke lock instruction.")]
141    ProgramNotWhitelisted,
142    #[msg("Lockup duration must at least be the min stake duration.")]
143    LockupDurationTooShort,
144    #[msg("Lockup duration must at most be the max stake duration.")]
145    LockupDurationTooLong,
146    #[msg("A voting escrow refresh cannot shorten the escrow time remaining.")]
147    RefreshCannotShorten,
148    #[msg("Escrow has not ended.")]
149    EscrowNotEnded,
150    #[msg("Program whitelist enabled; please provide whitelist entry and instructions sysvar or use the 'lock_with_whitelist' instruction.")]
151    MustProvideWhitelist,
152    #[msg("CPI caller not whitelisted for escrow owner to invoke lock instruction.")]
153    EscrowOwnerNotWhitelisted,
154    #[msg("Must call `lock_with_whitelist_entry` to lock via CPI.")]
155    MustCallLockWithWhitelistEntry,
156    #[msg("Must call `lock_permissionless` since this DAO does not have a CPI whitelist.")]
157    MustCallLockPermissionless,
158}