2021-10-20 12:38:47 -07:00
|
|
|
use access_control::*;
|
2021-10-19 15:51:13 -07:00
|
|
|
use account::*;
|
2021-10-05 13:40:01 -07:00
|
|
|
use anchor_lang::prelude::*;
|
2021-10-23 16:05:21 -07:00
|
|
|
use anchor_spl::token::{self, Mint};
|
2021-10-19 15:51:13 -07:00
|
|
|
use context::*;
|
|
|
|
use error::*;
|
2021-10-21 19:16:32 -07:00
|
|
|
use spl_governance::addins::voter_weight::VoterWeightAccountType;
|
2021-11-27 00:10:42 -08:00
|
|
|
use spl_governance::state::token_owner_record;
|
2021-11-29 07:27:51 -08:00
|
|
|
use std::{convert::TryFrom, str::FromStr};
|
2021-10-18 12:17:39 -07:00
|
|
|
|
2021-10-20 12:38:47 -07:00
|
|
|
mod access_control;
|
2021-11-27 23:34:10 -08:00
|
|
|
pub mod account;
|
2021-10-19 15:51:13 -07:00
|
|
|
mod context;
|
|
|
|
mod error;
|
2021-10-05 13:40:01 -07:00
|
|
|
|
2021-10-20 12:38:47 -07:00
|
|
|
// The program address.
|
2021-10-05 13:40:01 -07:00
|
|
|
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
|
|
|
|
|
2021-10-16 09:47:57 -07:00
|
|
|
/// # Introduction
|
|
|
|
///
|
|
|
|
/// The governance registry is an "addin" to the SPL governance program that
|
2021-10-20 12:38:47 -07:00
|
|
|
/// allows one to both vote with many different ypes of tokens for voting and to
|
|
|
|
/// scale voting power as a linear function of time locked--subject to some
|
|
|
|
/// maximum upper bound.
|
2021-10-16 09:47:57 -07:00
|
|
|
///
|
2021-10-20 12:38:47 -07:00
|
|
|
/// The flow for voting with this program is as follows:
|
2021-10-16 09:47:57 -07:00
|
|
|
///
|
|
|
|
/// - Create a SPL governance realm.
|
|
|
|
/// - Create a governance registry account.
|
|
|
|
/// - Add exchange rates for any tokens one wants to deposit. For example,
|
|
|
|
/// if one wants to vote with tokens A and B, where token B has twice the
|
|
|
|
/// voting power of token A, then the exchange rate of B would be 2 and the
|
|
|
|
/// exchange rate of A would be 1.
|
|
|
|
/// - Create a voter account.
|
|
|
|
/// - Deposit tokens into this program, with an optional lockup period.
|
|
|
|
/// - Vote.
|
|
|
|
///
|
|
|
|
/// Upon voting with SPL governance, a client is expected to call
|
|
|
|
/// `decay_voting_power` to get an up to date measurement of a given `Voter`'s
|
|
|
|
/// voting power for the given slot. If this is not done, then the transaction
|
|
|
|
/// will fail (since the SPL governance program will require the measurement
|
|
|
|
/// to be active for the current slot).
|
|
|
|
///
|
|
|
|
/// # Interacting with SPL Governance
|
|
|
|
///
|
|
|
|
/// This program does not directly interact with SPL governance via CPI.
|
|
|
|
/// Instead, it simply writes a `VoterWeightRecord` account with a well defined
|
|
|
|
/// format, which is then used by SPL governance as the voting power measurement
|
|
|
|
/// for a given user.
|
2021-10-20 12:38:47 -07:00
|
|
|
///
|
|
|
|
/// # Max Vote Weight
|
|
|
|
///
|
|
|
|
/// Given that one can use multiple tokens to vote, the max vote weight needs
|
|
|
|
/// to be a function of the total supply of all tokens, converted into a common
|
|
|
|
/// currency. For example, if you have Token A and Token B, where 1 Token B =
|
|
|
|
/// 10 Token A, then the `max_vote_weight` should be `supply(A) + supply(B)*10`
|
|
|
|
/// where both are converted into common decimals. Then, when calculating the
|
|
|
|
/// weight of an individual voter, one can convert B into A via the given
|
|
|
|
/// exchange rate, which must be fixed.
|
|
|
|
///
|
|
|
|
/// Note that the above also implies that the `max_vote_weight` must fit into
|
|
|
|
/// a u64.
|
2021-10-05 13:40:01 -07:00
|
|
|
#[program]
|
|
|
|
pub mod governance_registry {
|
|
|
|
use super::*;
|
|
|
|
|
2021-11-28 21:51:06 -08:00
|
|
|
/// Creates a new voting registrar. There can only be a single registrar
|
2021-10-12 12:34:05 -07:00
|
|
|
/// per governance realm.
|
2021-10-16 09:47:57 -07:00
|
|
|
pub fn create_registrar(
|
|
|
|
ctx: Context<CreateRegistrar>,
|
2021-11-29 07:11:17 -08:00
|
|
|
vote_weight_decimals: u8,
|
2021-10-05 13:40:01 -07:00
|
|
|
registrar_bump: u8,
|
|
|
|
) -> Result<()> {
|
2021-10-12 12:34:05 -07:00
|
|
|
let registrar = &mut ctx.accounts.registrar.load_init()?;
|
2021-10-15 11:22:05 -07:00
|
|
|
registrar.bump = registrar_bump;
|
2021-11-27 00:00:12 -08:00
|
|
|
registrar.governance_program_id = ctx.accounts.governance_program_id.key();
|
2021-10-05 13:40:01 -07:00
|
|
|
registrar.realm = ctx.accounts.realm.key();
|
2021-10-21 19:16:32 -07:00
|
|
|
registrar.realm_community_mint = ctx.accounts.realm_community_mint.key();
|
2021-11-30 04:10:48 -08:00
|
|
|
registrar.registrar_authority = ctx.accounts.registrar_authority.key();
|
2021-11-29 07:11:17 -08:00
|
|
|
registrar.vote_weight_decimals = vote_weight_decimals;
|
2021-11-29 06:56:45 -08:00
|
|
|
registrar.time_offset = 0;
|
2021-10-12 12:34:05 -07:00
|
|
|
|
2021-10-05 13:40:01 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-12 12:34:05 -07:00
|
|
|
/// Creates a new exchange rate for a given mint. This allows a voter to
|
|
|
|
/// deposit the mint in exchange for vTokens. There can only be a single
|
|
|
|
/// exchange rate per mint.
|
2021-10-20 12:38:47 -07:00
|
|
|
///
|
|
|
|
/// WARNING: This can be freely called when any of the rates are empty.
|
|
|
|
/// This should be called immediately upon creation of a Registrar.
|
|
|
|
#[access_control(rate_is_empty(&ctx, idx))]
|
2021-10-16 09:47:57 -07:00
|
|
|
pub fn create_exchange_rate(
|
|
|
|
ctx: Context<CreateExchangeRate>,
|
2021-10-20 12:38:47 -07:00
|
|
|
idx: u16,
|
2021-11-28 23:04:33 -08:00
|
|
|
mint: Pubkey,
|
|
|
|
rate: u64,
|
|
|
|
decimals: u8,
|
2021-10-16 09:47:57 -07:00
|
|
|
) -> Result<()> {
|
2021-11-28 23:04:33 -08:00
|
|
|
require!(rate > 0, InvalidRate);
|
2021-10-12 12:34:05 -07:00
|
|
|
let registrar = &mut ctx.accounts.registrar.load_mut()?;
|
2021-11-29 07:11:17 -08:00
|
|
|
registrar.rates[idx as usize] = registrar.new_rate(mint, decimals, rate)?;
|
2021-10-20 12:38:47 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a new voter account. There can only be a single voter per
|
|
|
|
/// user wallet.
|
2021-10-21 19:16:32 -07:00
|
|
|
pub fn create_voter(
|
|
|
|
ctx: Context<CreateVoter>,
|
|
|
|
voter_bump: u8,
|
|
|
|
voter_weight_record_bump: u8,
|
|
|
|
) -> Result<()> {
|
2021-11-27 00:21:58 -08:00
|
|
|
// Forbid creating voter accounts from CPI. The goal is to make automation
|
|
|
|
// impossible that weakens some of the limitations intentionally imposed on
|
|
|
|
// locked tokens.
|
|
|
|
{
|
|
|
|
use anchor_lang::solana_program::sysvar::instructions as tx_instructions;
|
|
|
|
let ixns = ctx.accounts.instructions.to_account_info();
|
|
|
|
let current_index = tx_instructions::load_current_index_checked(&ixns)? as usize;
|
|
|
|
let current_ixn = tx_instructions::load_instruction_at_checked(current_index, &ixns)?;
|
|
|
|
require!(
|
|
|
|
current_ixn.program_id == *ctx.program_id,
|
|
|
|
ErrorCode::ForbiddenCpi
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-10-21 19:16:32 -07:00
|
|
|
// Load accounts.
|
|
|
|
let registrar = &ctx.accounts.registrar.load()?;
|
2021-10-20 12:38:47 -07:00
|
|
|
let voter = &mut ctx.accounts.voter.load_init()?;
|
2021-10-21 19:16:32 -07:00
|
|
|
let voter_weight_record = &mut ctx.accounts.voter_weight_record;
|
|
|
|
|
|
|
|
// Init the voter.
|
2021-10-20 12:38:47 -07:00
|
|
|
voter.voter_bump = voter_bump;
|
2021-10-21 19:16:32 -07:00
|
|
|
voter.voter_weight_record_bump = voter_weight_record_bump;
|
2021-11-30 04:10:48 -08:00
|
|
|
voter.voter_authority = ctx.accounts.voter_authority.key();
|
2021-10-20 12:38:47 -07:00
|
|
|
voter.registrar = ctx.accounts.registrar.key();
|
|
|
|
|
2021-10-21 19:16:32 -07:00
|
|
|
// Init the voter weight record.
|
|
|
|
voter_weight_record.account_type = VoterWeightAccountType::VoterWeightRecord;
|
|
|
|
voter_weight_record.realm = registrar.realm;
|
|
|
|
voter_weight_record.governing_token_mint = registrar.realm_community_mint;
|
2021-11-30 04:10:48 -08:00
|
|
|
voter_weight_record.governing_token_owner = ctx.accounts.voter_authority.key();
|
2021-10-21 19:16:32 -07:00
|
|
|
|
2021-10-05 13:40:01 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-16 09:47:57 -07:00
|
|
|
/// Creates a new deposit entry and updates it by transferring in tokens.
|
2021-10-19 15:08:05 -07:00
|
|
|
pub fn create_deposit(
|
|
|
|
ctx: Context<CreateDeposit>,
|
|
|
|
kind: LockupKind,
|
|
|
|
amount: u64,
|
2021-11-30 00:23:07 -08:00
|
|
|
periods: i32,
|
2021-10-19 15:08:05 -07:00
|
|
|
) -> Result<()> {
|
2021-10-16 09:47:57 -07:00
|
|
|
// Creates the new deposit.
|
|
|
|
let deposit_id = {
|
2021-10-19 15:08:05 -07:00
|
|
|
// Load accounts.
|
2021-10-16 09:47:57 -07:00
|
|
|
let registrar = &ctx.accounts.deposit.registrar.load()?;
|
|
|
|
let voter = &mut ctx.accounts.deposit.voter.load_mut()?;
|
|
|
|
|
2021-10-24 18:14:06 -07:00
|
|
|
// Set the lockup start timestamp.
|
2021-11-29 06:56:45 -08:00
|
|
|
let start_ts = registrar.clock_unix_timestamp();
|
2021-10-19 15:08:05 -07:00
|
|
|
|
2021-10-16 09:47:57 -07:00
|
|
|
// Get the exchange rate entry associated with this deposit.
|
|
|
|
let er_idx = registrar
|
|
|
|
.rates
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.mint == ctx.accounts.deposit.deposit_mint.key())
|
|
|
|
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
|
|
|
|
|
|
|
|
// Get and set up the first free deposit entry.
|
|
|
|
let free_entry_idx = voter
|
|
|
|
.deposits
|
|
|
|
.iter()
|
|
|
|
.position(|d_entry| !d_entry.is_used)
|
|
|
|
.ok_or(ErrorCode::DepositEntryFull)?;
|
|
|
|
let d_entry = &mut voter.deposits[free_entry_idx];
|
|
|
|
d_entry.is_used = true;
|
|
|
|
d_entry.rate_idx = free_entry_idx as u8;
|
|
|
|
d_entry.rate_idx = er_idx as u8;
|
2021-11-28 23:04:33 -08:00
|
|
|
d_entry.amount_deposited_native = 0;
|
|
|
|
d_entry.amount_initially_locked_native = 0;
|
2021-10-19 15:08:05 -07:00
|
|
|
d_entry.lockup = Lockup {
|
|
|
|
kind,
|
|
|
|
start_ts,
|
|
|
|
end_ts: start_ts
|
2021-11-30 00:23:07 -08:00
|
|
|
.checked_add(i64::from(periods).checked_mul(kind.period_secs()).unwrap())
|
2021-10-19 15:08:05 -07:00
|
|
|
.unwrap(),
|
|
|
|
padding: [0u8; 16],
|
|
|
|
};
|
2021-10-16 09:47:57 -07:00
|
|
|
|
|
|
|
free_entry_idx as u8
|
|
|
|
};
|
|
|
|
|
|
|
|
// Updates the entry by transferring in tokens.
|
|
|
|
let update_ctx = Context::new(ctx.program_id, &mut ctx.accounts.deposit, &[]);
|
|
|
|
update_deposit(update_ctx, deposit_id, amount)?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Updates a deposit entry by depositing tokens into the registrar in
|
|
|
|
/// exchange for *frozen* voting tokens. These tokens are not used for
|
|
|
|
/// anything other than displaying the amount in wallets.
|
|
|
|
pub fn update_deposit(ctx: Context<UpdateDeposit>, id: u8, amount: u64) -> Result<()> {
|
2021-10-14 11:31:52 -07:00
|
|
|
let registrar = &ctx.accounts.registrar.load()?;
|
2021-10-12 12:34:05 -07:00
|
|
|
let voter = &mut ctx.accounts.voter.load_mut()?;
|
|
|
|
|
2021-11-27 00:14:41 -08:00
|
|
|
voter.last_deposit_slot = Clock::get()?.slot;
|
|
|
|
|
2021-11-28 23:04:33 -08:00
|
|
|
// Get the exchange rate entry associated with this deposit.
|
|
|
|
let er_idx = registrar
|
|
|
|
.rates
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.mint == ctx.accounts.deposit_mint.key())
|
|
|
|
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
|
|
|
|
let _er_entry = registrar.rates[er_idx];
|
2021-10-12 12:34:05 -07:00
|
|
|
|
2021-10-18 17:59:09 -07:00
|
|
|
require!(voter.deposits.len() > id as usize, InvalidDepositId);
|
|
|
|
let d_entry = &mut voter.deposits[id as usize];
|
2021-11-30 02:49:59 -08:00
|
|
|
require!(d_entry.is_used, InvalidDepositId);
|
2021-10-18 17:59:09 -07:00
|
|
|
|
2021-10-12 12:34:05 -07:00
|
|
|
// Deposit tokens into the registrar.
|
2021-10-15 11:22:05 -07:00
|
|
|
token::transfer(ctx.accounts.transfer_ctx(), amount)?;
|
2021-11-28 23:04:33 -08:00
|
|
|
d_entry.amount_deposited_native += amount;
|
|
|
|
|
|
|
|
// Adding funds to a lockup that is already in progress can be complicated
|
|
|
|
// for linear vesting schedules because all added funds should be paid out
|
|
|
|
// gradually over the remaining lockup duration.
|
|
|
|
// The logic used is to wrap up the current lockup, and create a new one
|
|
|
|
// for the expected remainder duration.
|
2021-11-29 06:56:45 -08:00
|
|
|
let curr_ts = registrar.clock_unix_timestamp();
|
|
|
|
d_entry.amount_initially_locked_native -= d_entry.vested(curr_ts)?;
|
2021-11-28 23:04:33 -08:00
|
|
|
d_entry.amount_initially_locked_native += amount;
|
|
|
|
d_entry.lockup.start_ts = d_entry
|
|
|
|
.lockup
|
|
|
|
.start_ts
|
|
|
|
.checked_add(
|
2021-11-30 00:23:07 -08:00
|
|
|
i64::try_from(d_entry.lockup.period_current(curr_ts)?)
|
2021-11-28 23:04:33 -08:00
|
|
|
.unwrap()
|
2021-11-30 00:23:07 -08:00
|
|
|
.checked_mul(d_entry.lockup.kind.period_secs())
|
2021-11-28 23:04:33 -08:00
|
|
|
.unwrap(),
|
|
|
|
)
|
|
|
|
.unwrap();
|
2021-10-05 13:40:01 -07:00
|
|
|
|
2021-10-19 15:08:05 -07:00
|
|
|
// Thaw the account if it's frozen, so that we can mint.
|
|
|
|
if ctx.accounts.voting_token.is_frozen() {
|
|
|
|
token::thaw_account(
|
|
|
|
ctx.accounts
|
|
|
|
.thaw_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2021-10-05 13:40:01 -07:00
|
|
|
// Mint vote tokens to the depositor.
|
2021-10-15 11:22:05 -07:00
|
|
|
token::mint_to(
|
|
|
|
ctx.accounts
|
|
|
|
.mint_to_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
2021-10-23 16:05:21 -07:00
|
|
|
amount,
|
2021-10-15 11:22:05 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// Freeze the vote tokens; they are just used for UIs + accounting.
|
|
|
|
token::freeze_account(
|
|
|
|
ctx.accounts
|
|
|
|
.freeze_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
|
|
|
)?;
|
2021-10-05 13:40:01 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-12 12:34:05 -07:00
|
|
|
/// Withdraws tokens from a deposit entry, if they are unlocked according
|
|
|
|
/// to a vesting schedule.
|
2021-10-16 09:47:57 -07:00
|
|
|
///
|
|
|
|
/// `amount` is in units of the native currency being withdrawn.
|
|
|
|
pub fn withdraw(ctx: Context<Withdraw>, deposit_id: u8, amount: u64) -> Result<()> {
|
2021-10-21 13:03:59 -07:00
|
|
|
// Load the accounts.
|
2021-10-16 09:47:57 -07:00
|
|
|
let registrar = &ctx.accounts.registrar.load()?;
|
|
|
|
let voter = &mut ctx.accounts.voter.load_mut()?;
|
|
|
|
require!(voter.deposits.len() > deposit_id.into(), InvalidDepositId);
|
|
|
|
|
2021-11-27 00:10:42 -08:00
|
|
|
// Governance may forbid withdraws, for example when engaged in a vote.
|
2021-11-30 04:10:48 -08:00
|
|
|
let token_owner = ctx.accounts.voter_authority.key();
|
2021-11-27 00:10:42 -08:00
|
|
|
let token_owner_record_address_seeds =
|
|
|
|
token_owner_record::get_token_owner_record_address_seeds(
|
|
|
|
®istrar.realm,
|
|
|
|
®istrar.realm_community_mint,
|
|
|
|
&token_owner,
|
|
|
|
);
|
|
|
|
let token_owner_record_data = token_owner_record::get_token_owner_record_data_for_seeds(
|
|
|
|
®istrar.governance_program_id,
|
|
|
|
&ctx.accounts.token_owner_record.to_account_info(),
|
|
|
|
&token_owner_record_address_seeds,
|
|
|
|
)?;
|
|
|
|
token_owner_record_data.assert_can_withdraw_governing_tokens()?;
|
|
|
|
|
2021-11-27 00:14:41 -08:00
|
|
|
// Must not withdraw in the same slot as depositing, to prevent people
|
|
|
|
// depositing, having the vote weight updated, withdrawing and then
|
|
|
|
// voting.
|
|
|
|
require!(
|
|
|
|
voter.last_deposit_slot < Clock::get()?.slot,
|
|
|
|
ErrorCode::InvalidToDepositAndWithdrawInOneSlot
|
|
|
|
);
|
|
|
|
|
2021-10-21 13:03:59 -07:00
|
|
|
// Get the deposit being withdrawn from.
|
2021-11-29 06:56:45 -08:00
|
|
|
let curr_ts = registrar.clock_unix_timestamp();
|
2021-10-16 09:47:57 -07:00
|
|
|
let deposit_entry = &mut voter.deposits[deposit_id as usize];
|
|
|
|
require!(deposit_entry.is_used, InvalidDepositId);
|
2021-11-29 06:56:45 -08:00
|
|
|
msg!("deposit_entry.vested() {:?}", deposit_entry.vested(curr_ts));
|
2021-10-19 15:08:05 -07:00
|
|
|
require!(
|
2021-11-29 06:56:45 -08:00
|
|
|
deposit_entry.amount_withdrawable(curr_ts) >= amount,
|
2021-11-28 23:04:33 -08:00
|
|
|
InsufficientVestedTokens
|
|
|
|
);
|
|
|
|
// technically unnecessary
|
|
|
|
require!(
|
|
|
|
deposit_entry.amount_deposited_native >= amount,
|
2021-10-19 15:08:05 -07:00
|
|
|
InsufficientVestedTokens
|
|
|
|
);
|
2021-10-16 09:47:57 -07:00
|
|
|
|
2021-11-27 00:26:33 -08:00
|
|
|
// Get the exchange rate for the token being withdrawn.
|
|
|
|
let er_idx = registrar
|
|
|
|
.rates
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.mint == ctx.accounts.withdraw_mint.key())
|
|
|
|
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
|
2021-11-28 23:04:33 -08:00
|
|
|
let _er_entry = registrar.rates[er_idx];
|
2021-11-27 23:34:10 -08:00
|
|
|
require!(
|
|
|
|
er_idx == deposit_entry.rate_idx as usize,
|
|
|
|
ErrorCode::InvalidMint
|
|
|
|
);
|
2021-11-27 00:26:33 -08:00
|
|
|
|
2021-10-21 13:03:59 -07:00
|
|
|
// Update deposit book keeping.
|
2021-11-28 23:04:33 -08:00
|
|
|
deposit_entry.amount_deposited_native -= amount;
|
2021-10-16 09:47:57 -07:00
|
|
|
|
|
|
|
// Transfer the tokens to withdraw.
|
|
|
|
token::transfer(
|
|
|
|
ctx.accounts
|
|
|
|
.transfer_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
|
|
|
amount,
|
|
|
|
)?;
|
|
|
|
|
2021-10-21 13:03:59 -07:00
|
|
|
// Unfreeze the voting token.
|
2021-10-16 09:47:57 -07:00
|
|
|
token::thaw_account(
|
|
|
|
ctx.accounts
|
|
|
|
.thaw_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
|
|
|
)?;
|
|
|
|
|
|
|
|
// Burn the voting tokens.
|
2021-10-21 13:03:59 -07:00
|
|
|
token::burn(ctx.accounts.burn_ctx(), amount)?;
|
|
|
|
|
|
|
|
// Re-freeze the vote token.
|
|
|
|
token::freeze_account(
|
|
|
|
ctx.accounts
|
|
|
|
.freeze_ctx()
|
|
|
|
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
|
|
|
|
)?;
|
2021-10-16 09:47:57 -07:00
|
|
|
|
2021-10-12 12:34:05 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-11-30 02:49:59 -08:00
|
|
|
/// Close an empty deposit, allowing it to be reused in the future
|
|
|
|
pub fn close_deposit(ctx: Context<CloseDeposit>, deposit_id: u8) -> Result<()> {
|
|
|
|
let voter = &mut ctx.accounts.voter.load_mut()?;
|
|
|
|
|
|
|
|
require!(voter.deposits.len() > deposit_id as usize, InvalidDepositId);
|
|
|
|
let d = &mut voter.deposits[deposit_id as usize];
|
|
|
|
require!(d.is_used, InvalidDepositId);
|
|
|
|
require!(d.amount_deposited_native == 0, VotingTokenNonZero);
|
|
|
|
|
|
|
|
// We do not need to check d.amount_initially_locked_native or d.lockup
|
|
|
|
// here - the fact that the deposit contains no tokens is sufficient.
|
|
|
|
|
|
|
|
d.is_used = false;
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-19 15:08:05 -07:00
|
|
|
/// Resets a lockup to start at the current slot timestamp and to last for
|
2021-11-30 00:23:07 -08:00
|
|
|
/// `periods`, which must be >= the number of periods left on the lockup.
|
|
|
|
/// This will re-lock any non-withdrawn vested funds.
|
|
|
|
pub fn reset_lockup(ctx: Context<UpdateSchedule>, deposit_id: u8, periods: i64) -> Result<()> {
|
2021-11-29 06:56:45 -08:00
|
|
|
let registrar = ctx.accounts.registrar.load()?;
|
2021-10-18 17:55:15 -07:00
|
|
|
let voter = &mut ctx.accounts.voter.load_mut()?;
|
|
|
|
require!(voter.deposits.len() > deposit_id as usize, InvalidDepositId);
|
|
|
|
|
|
|
|
let d = &mut voter.deposits[deposit_id as usize];
|
|
|
|
require!(d.is_used, InvalidDepositId);
|
|
|
|
|
2021-10-19 15:08:05 -07:00
|
|
|
// The lockup period can only be increased.
|
2021-11-29 06:56:45 -08:00
|
|
|
let curr_ts = registrar.clock_unix_timestamp();
|
2021-11-30 02:56:14 -08:00
|
|
|
require!(
|
|
|
|
periods as u64 >= d.lockup.periods_left(curr_ts)?,
|
|
|
|
InvalidDays
|
|
|
|
);
|
2021-10-18 17:55:15 -07:00
|
|
|
|
2021-11-30 00:23:07 -08:00
|
|
|
// TODO: Check for correctness
|
|
|
|
d.amount_initially_locked_native = d.amount_deposited_native;
|
2021-10-19 15:08:05 -07:00
|
|
|
|
2021-11-29 06:56:45 -08:00
|
|
|
d.lockup.start_ts = curr_ts;
|
2021-11-30 00:23:07 -08:00
|
|
|
d.lockup.end_ts = curr_ts
|
|
|
|
.checked_add(periods.checked_mul(d.lockup.kind.period_secs()).unwrap())
|
|
|
|
.unwrap();
|
2021-10-18 17:55:15 -07:00
|
|
|
|
2021-10-05 13:40:01 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-10-14 11:31:52 -07:00
|
|
|
|
|
|
|
/// Calculates the lockup-scaled, time-decayed voting power for the given
|
|
|
|
/// voter and writes it into a `VoteWeightRecord` account to be used by
|
|
|
|
/// the SPL governance program.
|
|
|
|
///
|
2021-10-18 17:55:15 -07:00
|
|
|
/// This "revise" instruction should be called in the same transaction,
|
|
|
|
/// immediately before voting.
|
2021-10-21 19:16:32 -07:00
|
|
|
pub fn update_voter_weight_record(ctx: Context<UpdateVoterWeightRecord>) -> Result<()> {
|
2021-11-28 23:04:33 -08:00
|
|
|
let registrar = ctx.accounts.registrar.load()?;
|
2021-10-18 17:55:15 -07:00
|
|
|
let voter = ctx.accounts.voter.load()?;
|
2021-10-21 19:16:32 -07:00
|
|
|
let record = &mut ctx.accounts.voter_weight_record;
|
2021-11-28 23:04:33 -08:00
|
|
|
record.voter_weight = voter.weight(®istrar)?;
|
2021-10-18 17:55:15 -07:00
|
|
|
record.voter_weight_expiry = Some(Clock::get()?.slot);
|
2021-10-21 19:16:32 -07:00
|
|
|
|
2021-10-14 11:31:52 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-23 16:05:21 -07:00
|
|
|
/// Calculates the max vote weight for the registry. This is a function
|
|
|
|
/// of the total supply of all exchange rate mints, converted into a
|
|
|
|
/// common currency with a common number of decimals.
|
|
|
|
///
|
|
|
|
/// Note that this method is only safe to use if the cumulative supply for
|
|
|
|
/// all tokens fits into a u64 *after* converting into common decimals, as
|
|
|
|
/// defined by the registrar's `rate_decimal` field.
|
|
|
|
pub fn update_max_vote_weight<'info>(
|
|
|
|
ctx: Context<'_, '_, '_, 'info, UpdateMaxVoteWeight<'info>>,
|
|
|
|
) -> Result<()> {
|
|
|
|
let registrar = ctx.accounts.registrar.load()?;
|
|
|
|
let _max_vote_weight = {
|
|
|
|
let total: Result<u64> = ctx
|
|
|
|
.remaining_accounts
|
|
|
|
.iter()
|
|
|
|
.map(|acc| Account::<Mint>::try_from(acc))
|
|
|
|
.collect::<std::result::Result<Vec<Account<Mint>>, ProgramError>>()?
|
|
|
|
.iter()
|
|
|
|
.try_fold(0u64, |sum, m| {
|
|
|
|
let er_idx = registrar
|
|
|
|
.rates
|
|
|
|
.iter()
|
|
|
|
.position(|r| r.mint == m.key())
|
|
|
|
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
|
|
|
|
let er_entry = registrar.rates[er_idx];
|
2021-11-28 23:04:33 -08:00
|
|
|
let amount = er_entry.convert(m.supply);
|
2021-10-23 16:05:21 -07:00
|
|
|
let total = sum.checked_add(amount).unwrap();
|
|
|
|
Ok(total)
|
|
|
|
});
|
|
|
|
total?
|
2021-11-28 23:04:33 -08:00
|
|
|
.checked_mul(FIXED_VOTE_WEIGHT_FACTOR + LOCKING_VOTE_WEIGHT_FACTOR)
|
|
|
|
.unwrap()
|
2021-10-23 16:05:21 -07:00
|
|
|
};
|
|
|
|
// TODO: SPL governance has not yet implemented this feature.
|
|
|
|
// When it has, probably need to write the result into an account,
|
|
|
|
// similar to VoterWeightRecord.
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-10-14 11:31:52 -07:00
|
|
|
/// Closes the voter account, allowing one to retrieve rent exemption SOL.
|
2021-10-21 19:30:52 -07:00
|
|
|
/// Only accounts with no remaining deposits can be closed.
|
2021-10-14 11:31:52 -07:00
|
|
|
pub fn close_voter(ctx: Context<CloseVoter>) -> Result<()> {
|
2021-10-21 19:30:52 -07:00
|
|
|
let voter = &ctx.accounts.voter.load()?;
|
|
|
|
let amount = voter.deposits.iter().fold(0u64, |sum, d| {
|
2021-11-28 23:04:33 -08:00
|
|
|
sum.checked_add(d.amount_deposited_native).unwrap()
|
2021-10-21 19:30:52 -07:00
|
|
|
});
|
|
|
|
require!(amount == 0, VotingTokenNonZero);
|
2021-10-14 11:31:52 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-11-29 06:56:45 -08:00
|
|
|
|
|
|
|
pub fn set_time_offset(ctx: Context<SetTimeOffset>, time_offset: i64) -> Result<()> {
|
2021-11-29 07:27:51 -08:00
|
|
|
let allowed_program =
|
|
|
|
Pubkey::from_str("GovernanceProgram11111111111111111111111111").unwrap();
|
2021-11-29 06:56:45 -08:00
|
|
|
let registrar = &mut ctx.accounts.registrar.load_mut()?;
|
2021-11-29 07:27:51 -08:00
|
|
|
require!(
|
|
|
|
registrar.governance_program_id == allowed_program,
|
|
|
|
ErrorCode::DebugInstruction
|
|
|
|
);
|
2021-11-29 06:56:45 -08:00
|
|
|
registrar.time_offset = time_offset;
|
|
|
|
Ok(())
|
|
|
|
}
|
2021-10-05 13:40:01 -07:00
|
|
|
}
|