From 7e8bd1608cdec215521c7946b06c75acf42d63b6 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 24 May 2024 18:26:45 +0300 Subject: [PATCH 01/59] add CPI functions && add extend deposit function --- program-states/src/error.rs | 3 + program-states/src/state/lockup.rs | 2 +- .../src/cpi_instructions.rs | 121 ++++++++++++++++ .../src/instructions/create_deposit_entry.rs | 77 ++++++++++ .../src/instructions/deposit.rs | 8 -- .../src/instructions/extend_deposit.rs | 135 ++++++++++++++++++ .../src/instructions/mod.rs | 2 + .../src/instructions/withdraw.rs | 35 +++++ programs/voter-stake-registry/src/lib.rs | 9 ++ 9 files changed, 383 insertions(+), 9 deletions(-) create mode 100644 programs/voter-stake-registry/src/cpi_instructions.rs create mode 100644 programs/voter-stake-registry/src/instructions/extend_deposit.rs diff --git a/program-states/src/error.rs b/program-states/src/error.rs index 84a5cc14..e6cfea97 100644 --- a/program-states/src/error.rs +++ b/program-states/src/error.rs @@ -122,4 +122,7 @@ pub enum VsrError { // 6039 / 0x1797 #[msg("")] UnlockAlreadyRequested, + // 6040 / 0x1798 + #[msg("")] + RestakeDepositIsNotAllowed, } diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index 5a6b5c9b..82d48ade 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -147,7 +147,7 @@ impl Lockup { } #[repr(u8)] -#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq)] +#[derive(AnchorSerialize, AnchorDeserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum LockupPeriod { None, ThreeMonths, diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs new file mode 100644 index 00000000..3c81b0b0 --- /dev/null +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -0,0 +1,121 @@ +use anchor_lang::prelude::borsh; +use anchor_lang::{prelude::Pubkey, AnchorDeserialize, AnchorSerialize}; +use mplx_staking_states::state::LockupPeriod; + +// TODO: move the const +pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("11111111111111111111111111111111"); + +#[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] +pub enum RewardsInstruction { + /// Creates and initializes a reward pool account + /// + /// Accounts: + /// [R] Root account (ex-Config program account) + /// [W] Reward pool account + /// [R] Deposit authority + /// [WS] Payer + /// [R] System program + InitializePool, + + /// Creates a new vault account and adds it to the reward pool + /// + /// Accounts: + /// [R] Root account (ex-Config program account) + /// [W] Reward pool account + /// [R] Reward mint account + /// [W] Vault account + /// [WS] Payer + /// [R] Token program + /// [R] System program + /// [R] Rent sysvar + AddVault, + + /// Fills the reward pool with rewards + /// + /// Accounts: + /// [W] Reward pool account + /// [R] Mint of rewards account + /// [W] Vault for rewards account + /// [RS] Transfer account + /// [W] From account + /// [R] Token program + FillVault { + /// Amount to fill + amount: u64, + }, + + /// Initializes mining account for the specified user + /// + /// Accounts: + /// [W] Reward pool account + /// [W] Mining + /// [R] User + /// [WS] Payer + /// [R] System program + /// [R] Rent sysvar + InitializeMining, + + /// Deposits amount of supply to the mining account + /// + /// Accounts: + /// [W] Reward pool account + /// [W] Mining + /// [R] Mint of rewards account + /// [R] User + /// [RS] Deposit authority + DepositMining { + /// Amount to deposit + amount: u64, + /// Lockup Period + lockup_period: LockupPeriod, + }, + + /// Withdraws amount of supply to the mining account + /// + /// Accounts: + /// [W] Reward pool account + /// [W] Mining + /// [R] User + /// [RS] Deposit authority + WithdrawMining { + /// Amount to withdraw + amount: u64, + }, + + /// Claims amount of rewards + /// + /// Accounts: + /// [R] Reward pool account + /// [R] Mint of rewards account + /// [W] Vault for rewards account + /// [W] Mining + /// [RS] User + /// [W] User reward token account + /// [R] Token program + Claim, + + /// Creates and initializes a reward root + /// + /// Accounts: + /// [WS] Root account (ex-Config program account) + /// [WS] Authority + /// [R] System program + InitializeRoot, + + /// Restakes deposit + /// + /// Accounts: + /// [W] Reward pool account + /// [W] Mining + /// [R] Mint of rewards account + /// [R] User + /// [RS] Deposit authority + RestakeDeposit { + /// Requested lockup period for restaking + lockup_period: LockupPeriod, + /// Amount of tokens to be restaked + amount: u64, + /// Deposit start_ts + deposit_start_ts: u64, + }, +} diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index 1cc81471..6aeeee29 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -3,6 +3,13 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; +use solana_program::{ + instruction::Instruction, + program::{invoke, invoke_signed}, + system_program, sysvar, +}; + +use crate::cpi_instructions::RewardsInstruction; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { @@ -87,3 +94,73 @@ pub fn create_deposit_entry( Ok(()) } + +/// Rewards initialize mining +#[allow(clippy::too_many_arguments)] +pub fn initialize_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + payer: AccountInfo<'a>, + system_program: AccountInfo<'a>, + rent: AccountInfo<'a>, +) -> Result<()> { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new(payer.key(), true), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + let ix = + Instruction::new_with_borsh(*program_id, &RewardsInstruction::InitializeMining, accounts); + + invoke( + &ix, + &[reward_pool, mining, user, payer, system_program, rent], + )?; + + Ok(()) +} + +/// Rewards deposit mining +#[allow(clippy::too_many_arguments)] +pub fn deposit_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + lockup_period: LockupPeriod, + signers_seeds: &[&[&[u8]]], + reward_mint: &Pubkey, +) -> Result<()> { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(reward_mint.key(), false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::DepositMining { + amount, + lockup_period, + }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index c9dd9059..d3bef7fe 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -53,14 +53,6 @@ impl<'info> Deposit<'info> { /// /// `deposit_entry_index`: Index of the deposit entry. /// `amount`: Number of native tokens to transfer. -/// -/// Note that adding tokens to a deposit entry with vesting, where some vesting -/// periods are already in the past is supported. What happens is that the tokens -/// get distributed over vesting points in the future. -/// -/// Example: 20 tokens are deposited to a three-day vesting deposit entry -/// that started 36 hours ago. That means 10 extra tokens will vest in 12 hours -/// and another 10 in 36 hours. pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { if amount == 0 { return Ok(()); diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs new file mode 100644 index 00000000..c36b2556 --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -0,0 +1,135 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use mplx_staking_states::error::*; +use mplx_staking_states::state::*; +use solana_program::{instruction::Instruction, program::invoke_signed}; + +use crate::cpi_instructions::RewardsInstruction; +use crate::cpi_instructions::REWARD_CONTRACT_ID; + +#[derive(Accounts)] +pub struct RestakeDeposit<'info> { + pub registrar: AccountLoader<'info, Registrar>, + + // checking the PDA address it just an extra precaution, + // the other constraints must be exhaustive + #[account( + mut, + seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter.load()?.voter_authority.key().as_ref()], + bump = voter.load()?.voter_bump, + has_one = registrar)] + pub voter: AccountLoader<'info, Voter>, + + #[account( + mut, + associated_token::authority = voter, + associated_token::mint = deposit_token.mint, + )] + pub vault: Box>, + + #[account( + mut, + constraint = deposit_token.owner == deposit_authority.key(), + )] + pub deposit_token: Box>, + pub deposit_authority: Signer<'info>, + + pub token_program: Program<'info, Token>, +} + +/// Prolongs the deposit +/// +/// The deposit will be restaked with the same lockup period as it was previously. +/// +/// The deposit entry must have been initialized with create_deposit_entry. +/// +/// `deposit_entry_index`: Index of the deposit entry. +pub fn restake_deposit( + ctx: Context, + deposit_entry_index: u8, + lockup_period: LockupPeriod, +) -> Result<()> { + let registrar = &ctx.accounts.registrar.load()?; + let voter = &mut ctx.accounts.voter.load_mut()?; + + let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + + // Get the exchange rate entry associated with this deposit. + let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; + require_eq!( + mint_idx, + d_entry.voting_mint_config_idx as usize, + VsrError::InvalidMint + ); + + let start_ts = d_entry.lockup.start_ts; + let curr_ts = registrar.clock_unix_timestamp(); + let d_enty_lockup_period = d_entry.lockup.period; + let amount = d_entry.amount_deposited_native; + + if lockup_period != LockupPeriod::Flex { + require!( + lockup_period == d_entry.lockup.period, + VsrError::RestakeDepositIsNotAllowed + ); + } + // TODO: call restake cpi + + d_entry.lockup.start_ts = curr_ts; + d_entry.lockup.end_ts = curr_ts + .checked_add(lockup_period.to_secs()) + .ok_or(VsrError::InvalidTimestampArguments)?; + + msg!( + "Restaked deposit with amount {} at deposit index {} with lockup kind {:?} with lockup period {:?} and {} seconds left. It's used now: {:?}", + amount, + deposit_entry_index, + d_entry.lockup.kind, + d_entry.lockup.period, + d_entry.lockup.seconds_left(curr_ts), + d_entry.is_used, + ); + + Ok(()) +} + +/// Restake deposit +#[allow(clippy::too_many_arguments)] +fn restake_deposit_cpi<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + reward_mint: &Pubkey, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + lockup_period: LockupPeriod, + deposit_start_ts: u64, + signers_seeds: &[&[&[u8]]], +) -> Result<()> { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::RestakeDeposit { + lockup_period, + amount, + deposit_start_ts, + }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index b17af1fe..918778be 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -5,6 +5,7 @@ pub use create_deposit_entry::*; pub use create_registrar::*; pub use create_voter::*; pub use deposit::*; +pub use extend_deposit::*; pub use internal_transfer_unlocked::*; pub use log_voter_info::*; pub use unlock_tokens::*; @@ -18,6 +19,7 @@ mod create_deposit_entry; mod create_registrar; mod create_voter; mod deposit; +mod extend_deposit; mod internal_transfer_unlocked; mod log_voter_info; mod unlock_tokens; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index ef54b783..05e125dd 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,7 +1,9 @@ +use crate::cpi_instructions::RewardsInstruction; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; +use solana_program::{instruction::Instruction, program::invoke_signed}; use crate::voter::load_token_owner_record; use crate::voter::VoterWeightRecord; @@ -158,3 +160,36 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> Ok(()) } + +/// Rewards withdraw mining +#[allow(clippy::too_many_arguments)] +pub fn withdraw_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + signers_seeds: &[&[&[u8]]], +) -> Result<()> { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::WithdrawMining { amount }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 27d390e5..c528014a 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -2,6 +2,7 @@ use anchor_lang::prelude::*; use instructions::*; use mplx_staking_states::state::*; +mod cpi_instructions; pub mod events; mod governance; mod instructions; @@ -152,4 +153,12 @@ pub mod voter_stake_registry { amount, ) } + + pub fn restake_deposit( + ctx: Context, + deposit_entry_index: u8, + lockup_period: LockupPeriod, + ) -> Result<()> { + instructions::restake_deposit(ctx, deposit_entry_index, lockup_period) + } } From f2215c213fb7ba6d0468a3482f42a8162b0c5fbf Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 27 May 2024 18:27:50 +0300 Subject: [PATCH 02/59] add CPI invocations (except for withdraw) --- .../src/cpi_instructions.rs | 187 +++++++++++++----- .../src/instructions/create_voter.rs | 23 +++ .../src/instructions/deposit.rs | 38 ++++ .../src/instructions/extend_deposit.rs | 82 ++++---- 4 files changed, 239 insertions(+), 91 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 3c81b0b0..526469d8 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,49 +1,20 @@ use anchor_lang::prelude::borsh; +use anchor_lang::Key; use anchor_lang::{prelude::Pubkey, AnchorDeserialize, AnchorSerialize}; use mplx_staking_states::state::LockupPeriod; +use solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + program::{invoke, invoke_signed}, + system_program, sysvar, +}; // TODO: move the const pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("11111111111111111111111111111111"); #[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] pub enum RewardsInstruction { - /// Creates and initializes a reward pool account - /// - /// Accounts: - /// [R] Root account (ex-Config program account) - /// [W] Reward pool account - /// [R] Deposit authority - /// [WS] Payer - /// [R] System program - InitializePool, - - /// Creates a new vault account and adds it to the reward pool - /// - /// Accounts: - /// [R] Root account (ex-Config program account) - /// [W] Reward pool account - /// [R] Reward mint account - /// [W] Vault account - /// [WS] Payer - /// [R] Token program - /// [R] System program - /// [R] Rent sysvar - AddVault, - - /// Fills the reward pool with rewards - /// - /// Accounts: - /// [W] Reward pool account - /// [R] Mint of rewards account - /// [W] Vault for rewards account - /// [RS] Transfer account - /// [W] From account - /// [R] Token program - FillVault { - /// Amount to fill - amount: u64, - }, - /// Initializes mining account for the specified user /// /// Accounts: @@ -52,7 +23,6 @@ pub enum RewardsInstruction { /// [R] User /// [WS] Payer /// [R] System program - /// [R] Rent sysvar InitializeMining, /// Deposits amount of supply to the mining account @@ -94,14 +64,6 @@ pub enum RewardsInstruction { /// [R] Token program Claim, - /// Creates and initializes a reward root - /// - /// Accounts: - /// [WS] Root account (ex-Config program account) - /// [WS] Authority - /// [R] System program - InitializeRoot, - /// Restakes deposit /// /// Accounts: @@ -119,3 +81,136 @@ pub enum RewardsInstruction { deposit_start_ts: u64, }, } + +/// Rewards initialize mining +#[allow(clippy::too_many_arguments)] +pub fn initialize_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + payer: AccountInfo<'a>, + system_program: AccountInfo<'a>, +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new(payer.key(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = + Instruction::new_with_borsh(*program_id, &RewardsInstruction::InitializeMining, accounts); + + invoke(&ix, &[reward_pool, mining, user, payer, system_program]) +} + +/// Rewards deposit mining +#[allow(clippy::too_many_arguments)] +pub fn deposit_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + lockup_period: LockupPeriod, + signers_seeds: &[&[&[u8]]], + reward_mint: &Pubkey, +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::DepositMining { + amount, + lockup_period, + }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + ) +} + +/// Restake deposit +#[allow(clippy::too_many_arguments)] +pub fn extend_deposit<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + reward_mint: &Pubkey, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + lockup_period: LockupPeriod, + deposit_start_ts: u64, + signers_seeds: &[&[&[u8]]], +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::RestakeDeposit { + lockup_period, + amount, + deposit_start_ts, + }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + )?; + + Ok(()) +} + +/// Rewards withdraw mining +#[allow(clippy::too_many_arguments)] +pub fn withdraw_mining<'a>( + program_id: &Pubkey, + reward_pool: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, + amount: u64, + signers_seeds: &[&[&[u8]]], +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new_readonly(user.key(), false), + AccountMeta::new_readonly(deposit_authority.key(), true), + ]; + + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::WithdrawMining { amount }, + accounts, + ); + + invoke_signed( + &ix, + &[reward_pool, mining, user, deposit_authority], + signers_seeds, + ) +} diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 329624bd..355452a5 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -4,6 +4,8 @@ use mplx_staking_states::error::*; use mplx_staking_states::state::*; use std::mem::size_of; +use crate::cpi_instructions; +use crate::cpi_instructions::REWARD_CONTRACT_ID; use crate::voter::VoterWeightRecord; #[derive(Accounts)] @@ -45,6 +47,12 @@ pub struct CreateVoter<'info> { /// CHECK: Address constraint is set #[account(address = tx_instructions::ID)] pub instructions: UncheckedAccount<'info>, + + /// CHECK: mining PDA will be checked in the rewards contract + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + pub reward_pool: UncheckedAccount<'info>, } /// Creates a new voter account. There can only be a single voter per @@ -77,6 +85,21 @@ pub fn create_voter( *ctx.bumps.get("voter_weight_record").unwrap() ); + let mining = &ctx.accounts.deposit_mining; + let user = &ctx.accounts.payer; + let voter = &ctx.accounts.voter; + let system_program = &ctx.accounts.system_program; + let reward_pool = &ctx.accounts.reward_pool; + + cpi_instructions::initialize_mining( + &REWARD_CONTRACT_ID, + reward_pool.to_account_info(), + mining.to_account_info(), + voter.to_account_info(), + user.to_account_info(), + system_program.to_account_info(), + )?; + // Load accounts. let registrar = &ctx.accounts.registrar.load()?; let voter_authority = ctx.accounts.voter_authority.key(); diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index d3bef7fe..17587cc0 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -3,6 +3,9 @@ use anchor_spl::token::{self, Token, TokenAccount}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; +use crate::cpi_instructions; +use crate::cpi_instructions::REWARD_CONTRACT_ID; + #[derive(Accounts)] pub struct Deposit<'info> { pub registrar: AccountLoader<'info, Registrar>, @@ -31,6 +34,12 @@ pub struct Deposit<'info> { pub deposit_authority: Signer<'info>, pub token_program: Program<'info, Token>, + + /// CHECK: mining PDA will be checked in the rewards contract + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + pub reward_pool: UncheckedAccount<'info>, } impl<'info> Deposit<'info> { @@ -76,6 +85,35 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R token::transfer(ctx.accounts.transfer_ctx(), amount)?; d_entry.amount_deposited_native = d_entry.amount_deposited_native.checked_add(amount).unwrap(); + let reward_pool = &ctx.accounts.reward_pool; + let mining = &ctx.accounts.deposit_mining; + let deposit_authority = &ctx.accounts.deposit_authority; + let reward_mint = &ctx.accounts.deposit_token.mint; + let voter = &ctx.accounts.voter; + + let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( + &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], + &REWARD_CONTRACT_ID, + ); + + let signers_seeds = &[ + &reward_pool.key().to_bytes()[..32], + &reward_mint.key().to_bytes()[..32], + &[pool_bump_seed], + ]; + + cpi_instructions::deposit_mining( + &REWARD_CONTRACT_ID, + reward_pool.to_account_info(), + mining.to_account_info(), + voter.to_account_info(), + deposit_authority.to_account_info(), + amount, + d_entry.lockup.period, + &[signers_seeds], + reward_mint, + )?; + msg!( "Deposited amount {} at deposit index {} with lockup kind {:?} with lockup period {:?} and {} seconds left. It's used now: {:?}", amount, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index c36b2556..719b38ce 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -2,9 +2,8 @@ use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; -use solana_program::{instruction::Instruction, program::invoke_signed}; -use crate::cpi_instructions::RewardsInstruction; +use crate::cpi_instructions::extend_deposit; use crate::cpi_instructions::REWARD_CONTRACT_ID; #[derive(Accounts)] @@ -35,6 +34,12 @@ pub struct RestakeDeposit<'info> { pub deposit_authority: Signer<'info>, pub token_program: Program<'info, Token>, + + /// CHECK: mining PDA will be checked in the rewards contract + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + pub reward_pool: UncheckedAccount<'info>, } /// Prolongs the deposit @@ -64,7 +69,6 @@ pub fn restake_deposit( let start_ts = d_entry.lockup.start_ts; let curr_ts = registrar.clock_unix_timestamp(); - let d_enty_lockup_period = d_entry.lockup.period; let amount = d_entry.amount_deposited_native; if lockup_period != LockupPeriod::Flex { @@ -73,7 +77,36 @@ pub fn restake_deposit( VsrError::RestakeDepositIsNotAllowed ); } - // TODO: call restake cpi + + let reward_pool = &ctx.accounts.reward_pool; + let mining = &ctx.accounts.deposit_mining; + let deposit_authority = &ctx.accounts.deposit_authority; + let reward_mint = &ctx.accounts.deposit_token.mint; + let voter = &ctx.accounts.voter; + + let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( + &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], + &REWARD_CONTRACT_ID, + ); + + let signers_seeds = &[ + &reward_pool.key().to_bytes()[..32], + &reward_mint.key().to_bytes()[..32], + &[pool_bump_seed], + ]; + + extend_deposit( + &REWARD_CONTRACT_ID, + reward_pool.to_account_info(), + mining.to_account_info(), + reward_mint, + voter.to_account_info(), + deposit_authority.to_account_info(), + amount, + lockup_period, + start_ts, + &[signers_seeds], + )?; d_entry.lockup.start_ts = curr_ts; d_entry.lockup.end_ts = curr_ts @@ -92,44 +125,3 @@ pub fn restake_deposit( Ok(()) } - -/// Restake deposit -#[allow(clippy::too_many_arguments)] -fn restake_deposit_cpi<'a>( - program_id: &Pubkey, - reward_pool: AccountInfo<'a>, - mining: AccountInfo<'a>, - reward_mint: &Pubkey, - user: AccountInfo<'a>, - deposit_authority: AccountInfo<'a>, - amount: u64, - lockup_period: LockupPeriod, - deposit_start_ts: u64, - signers_seeds: &[&[&[u8]]], -) -> Result<()> { - let accounts = vec![ - AccountMeta::new(reward_pool.key(), false), - AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new_readonly(user.key(), false), - AccountMeta::new_readonly(deposit_authority.key(), true), - ]; - - let ix = Instruction::new_with_borsh( - *program_id, - &RewardsInstruction::RestakeDeposit { - lockup_period, - amount, - deposit_start_ts, - }, - accounts, - ); - - invoke_signed( - &ix, - &[reward_pool, mining, user, deposit_authority], - signers_seeds, - )?; - - Ok(()) -} From 9ca230bac6c0a44172de28620b12e20f78c44251 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 27 May 2024 18:37:53 +0300 Subject: [PATCH 03/59] add CPI invocation for the withdraw function --- .../src/instructions/withdraw.rs | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 05e125dd..3b5de9fe 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,12 +1,8 @@ -use crate::cpi_instructions::RewardsInstruction; +use crate::cpi_instructions::{withdraw_mining, REWARD_CONTRACT_ID}; +use crate::voter::{load_token_owner_record, VoterWeightRecord}; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; -use solana_program::{instruction::Instruction, program::invoke_signed}; - -use crate::voter::load_token_owner_record; -use crate::voter::VoterWeightRecord; +use mplx_staking_states::{error::*, state::*}; #[derive(Accounts)] pub struct Withdraw<'info> { @@ -58,6 +54,12 @@ pub struct Withdraw<'info> { pub destination: Box>, pub token_program: Program<'info, Token>, + + /// CHECK: mining PDA will be checked in the rewards contract + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + pub reward_pool: UncheckedAccount<'info>, } impl<'info> Withdraw<'info> { @@ -158,37 +160,31 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> record.voter_weight = voter.weight()?; record.voter_weight_expiry = Some(Clock::get()?.slot); - Ok(()) -} - -/// Rewards withdraw mining -#[allow(clippy::too_many_arguments)] -pub fn withdraw_mining<'a>( - program_id: &Pubkey, - reward_pool: AccountInfo<'a>, - mining: AccountInfo<'a>, - user: AccountInfo<'a>, - deposit_authority: AccountInfo<'a>, - amount: u64, - signers_seeds: &[&[&[u8]]], -) -> Result<()> { - let accounts = vec![ - AccountMeta::new(reward_pool.key(), false), - AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(user.key(), false), - AccountMeta::new_readonly(deposit_authority.key(), true), - ]; + let reward_pool = &ctx.accounts.reward_pool; + let mining = &ctx.accounts.deposit_mining; + let deposit_authority = &ctx.accounts.voter_authority; + let reward_mint = &ctx.accounts.destination.mint; + let voter = &ctx.accounts.voter; - let ix = Instruction::new_with_borsh( - *program_id, - &RewardsInstruction::WithdrawMining { amount }, - accounts, + let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( + &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], + &REWARD_CONTRACT_ID, ); - invoke_signed( - &ix, - &[reward_pool, mining, user, deposit_authority], - signers_seeds, + let signers_seeds = &[ + &reward_pool.key().to_bytes()[..32], + &reward_mint.key().to_bytes()[..32], + &[pool_bump_seed], + ]; + + withdraw_mining( + &REWARD_CONTRACT_ID, + reward_pool.to_account_info(), + mining.to_account_info(), + voter.to_account_info(), + deposit_authority.to_account_info(), + amount, + &[signers_seeds], )?; Ok(()) From fba14a8809a6de112fec564197794422f6563045 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 28 May 2024 12:33:05 +0300 Subject: [PATCH 04/59] cleanup --- CHANGELOG.md | 36 --- LICENSE | 675 ------------------------------------------------- run-format.sh | 6 - run-release.sh | 30 --- run-test.sh | 5 - 5 files changed, 752 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 LICENSE delete mode 100755 run-format.sh delete mode 100755 run-release.sh delete mode 100755 run-test.sh diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 202ba8cb..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,36 +0,0 @@ -# Voter Stake Registry Changelog - -## v0.2.4 - 2022-5-4 - not on mainnet - -### Program -- Upgrade Anchor to v0.24.2 - -## v0.2.3 - 2022-4-29 - not on mainnet - -### Program -- Use spl-governance 2.2.1 as dependency, instead of a specific commit. - -### Typescript Client -- Upgrade the Anchor dependency to v0.24.2 - -## v0.2.2 - skipped - -## v0.2.1 - 2022-4-3 - mainnet deploy slot 129520307 - -### Program -- Increase the maximum number of lockup periods to 200 * 365 to allow for 200-year cliff and - constant lockups. -- Add a function to compute the guaranteed locked vote power bonus. This is unused by the - program itself, but helpful for programs that want to provide benefits based on a user's - lockup amount and time. - -### Other -- Add cli tool to decode voter accounts. -- Update dependencies. - - -## v0.2.0 - 2022-2-14 - mainnet deploy slot 121129331 - -- First release. -- Available on devnet at 4Q6WW2ouZ6V3iaNm56MTd5n2tnTm4C5fiH8miFHnAFHo -- In use by the Mango DAO on mainnet at the same address. diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 53d1f3d0..00000000 --- a/LICENSE +++ /dev/null @@ -1,675 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - diff --git a/run-format.sh b/run-format.sh deleted file mode 100755 index 612025aa..00000000 --- a/run-format.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -yarn prettier --write tests -cargo fmt diff --git a/run-release.sh b/run-release.sh deleted file mode 100755 index e511c492..00000000 --- a/run-release.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [[ -z "${PROVIDER_WALLET}" ]]; then - echo "Please provide path to a provider wallet keypair." - exit -1 -fi - -if [[ -z "${VERSION_MANUALLY_BUMPED}" ]]; then - echo "Please bump versions in package.json and in cargo.toml." - exit -1 -fi - -# build program -anchor build - -# update on chain program and IDL, atm used for testing/developing -anchor deploy --provider.cluster devnet --provider.wallet ${PROVIDER_WALLET} -anchor idl upgrade --provider.cluster devnet --provider.wallet ${PROVIDER_WALLET}\ - --filepath target/idl/voter_stake_registry.json 4Q6WW2ouZ6V3iaNm56MTd5n2tnTm4C5fiH8miFHnAFHo - -# update types in npm package and publish the npm package -cp ./target/types/voter_stake_registry.ts src/voter_stake_registry.ts -yarn clean && yarn build && cp package.json ./dist/ && yarn publish dist - -echo -echo Remember to commit and push the version update as well as the changes -echo to src/voter_stake_registry.ts . -echo diff --git a/run-test.sh b/run-test.sh deleted file mode 100755 index a921b32a..00000000 --- a/run-test.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -cargo test-bpf \ No newline at end of file From 0afc71bb012b7fe2d8a6457727df184bff119856 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 28 May 2024 17:39:39 +0300 Subject: [PATCH 05/59] extend CPI enum && provide source with program ids --- .../src/cpi_instructions.rs | 51 +++++++++++++++++-- programs/voter-stake-registry/src/lib.rs | 6 +-- 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 526469d8..f4c99275 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -7,14 +7,51 @@ use solana_program::{ entrypoint::ProgramResult, instruction::{AccountMeta, Instruction}, program::{invoke, invoke_signed}, - system_program, sysvar, + system_program, }; -// TODO: move the const -pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("11111111111111111111111111111111"); +pub const REWARD_CONTRACT_ID: Pubkey = + solana_program::pubkey!("J8oa8UUJBydrTKtCdkvwmQQ27ZFDq54zAxWJY5Ey72Ji"); #[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] pub enum RewardsInstruction { + /// Creates and initializes a reward pool account + /// + /// Accounts: + /// [R] Root account (ex-Config program account) + /// [W] Reward pool account + /// [R] Deposit authority + /// [WS] Payer + /// [R] System program + InitializePool, + + /// Creates a new vault account and adds it to the reward pool + /// + /// Accounts: + /// [R] Root account (ex-Config program account) + /// [W] Reward pool account + /// [R] Reward mint account + /// [W] Vault account + /// [WS] Payer + /// [R] Token program + /// [R] System program + /// [R] Rent sysvar + AddVault, + + /// Fills the reward pool with rewards + /// + /// Accounts: + /// [W] Reward pool account + /// [R] Mint of rewards account + /// [W] Vault for rewards account + /// [RS] Transfer account + /// [W] From account + /// [R] Token program + FillVault { + /// Amount to fill + amount: u64, + }, + /// Initializes mining account for the specified user /// /// Accounts: @@ -64,6 +101,14 @@ pub enum RewardsInstruction { /// [R] Token program Claim, + /// Creates and initializes a reward root + /// + /// Accounts: + /// [WS] Root account (ex-Config program account) + /// [WS] Authority + /// [R] System program + InitializeRoot, + /// Restakes deposit /// /// Accounts: diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index c528014a..5f4838be 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -2,7 +2,7 @@ use anchor_lang::prelude::*; use instructions::*; use mplx_staking_states::state::*; -mod cpi_instructions; +pub mod cpi_instructions; pub mod events; mod governance; mod instructions; @@ -126,9 +126,7 @@ pub mod voter_stake_registry { instructions::unlock_tokens(ctx, deposit_entry_index) } - pub fn close_voter<'key, 'accounts, 'remaining, 'info>( - ctx: Context<'key, 'accounts, 'remaining, 'info, CloseVoter<'info>>, - ) -> Result<()> { + pub fn close_voter<'info>(ctx: Context<'_, '_, '_, 'info, CloseVoter<'info>>) -> Result<()> { instructions::close_voter(ctx) } From b5d3b1b5e059e422515c541ce1a666f1a0c8aafb Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 28 May 2024 17:42:19 +0300 Subject: [PATCH 06/59] add initial invocations of rewards contract && add basic methods for interaction with rewards --- .../tests/fixtures/mplx_rewards.so | Bin 0 -> 270528 bytes .../tests/rewards_invocations.rs | 268 ++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100755 programs/voter-stake-registry/tests/fixtures/mplx_rewards.so create mode 100644 programs/voter-stake-registry/tests/rewards_invocations.rs diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so new file mode 100755 index 0000000000000000000000000000000000000000..91c91e196eaf46333d93502842cdb7802ca348d6 GIT binary patch literal 270528 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

O5Oup+M80+u!zGIN@-;c0><6SA$e`?;BLOrap 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

4uKnEcs?d?B&D{Okl)XwTmaXn@cQbD zW>xv}RLD=`4uDJIgH7B1*1)-0Jl~qbZT4^{!14J!p9WVJ@VsAqKx*rcU)~>C?O$jS z&j-W@r?&D*a047z!pq0R2duXG7PxO2&-X3o4z1wMi4SIN?IpwqvNor|vDG}E1m|Ns zUjkRaae1F-b-Z%xc>T2az}MEF%pJRbqaz>T|jc`e872baKAaPn_>{S3JH4W6%n z6M3GmgJXwyz6q|}%k$;?xTE)TH^Jp^^1S~+?qY$v_7HdD5$^h7ZtYR-05}d#fivJ7 zxCjnE#fQrfcSD7+w(sG&VWiWxo`@rca-(Cf*{T0tgz$tJJ zTmiShi8FZn8E_6<0@uJTaNtbdegvEVXTUjd30woWz=5;)_G7EKE8se~2~Mo$^>g4@ zjOUZ`??dW(D1c+@czFw40yn_^^}K!roCNoQi{QdW-k!FFI{^0g@_Z7U0aw8du$JWQ z#lb0XO#A`f_WVzR)8O1rUcLw}gInOB9K)o}XAGPH=fSOAeEW4dW=XZz0{i8dCzVft z)8GcUPmYaI_4D8=IP?kLzbH5bE`lrI2H5{e-hL9C0hhp4aBwegF9I$c1W&Vq~J3b+B*zQDI11V_LLa2lKi7r+&89jx8R`x5|1z;SRI+y^dz%iub= z1rGG_{)NFga2@QwiPyKX+&QrI0OY|1a0}e{2VOt?kK8SJLv{814VHL51`a;S^Kozn z+$NmmFF|y z0=VxrUOo@5g9ESg@-eUlE`sY|f1S4%1*gGza22e*!P^Uili)142(E$~VDnABy%0DC zPJ#Qt1#ku20BgVC+Yf@H;3U`r=fP!g9jv_t+XqL%Nw5WOfWvR|_OsxSoExj&j}u@E zT>mw1FaGb`DR2gy`YkV?1E=5P`69Ruj{J_7?*rGtfYac>pLqK7WS#W+lFCU%2ojQ#>56;f!`6f6qhv#GRd&LI&4{pupzh)d=A_Mhhn_E1#Yh8`T9C;^BnFdI1SE& zTj0JpZ!Zt7g0=H_`2x5B*3ReU1Ko13}g7js+S(j`3K z00%GQ`8c?-h3D(N-1#JT4V>D_^Hp%}a-J`3=Z^2-PJ)9wdA3D%zG{SSb{;21aw&VaMv0=NvWftz6SpLl_5W$8v;kc32+*m0cXJla0y%mH^AC6yuW^M5F7?a!EtaBoCaIqEI1D?g3I74 zxDIZDwKCuT05}Yefs^1gxDT8Mm%vqU1FU@q`U?(%Bj7kV0ZxN6;4C-?E`m$oD!2x2 zf?Ht!cVYj*VQ>T-2PeR3a344aE`lrII=BV)e~<4^2pk0`z-e$FI1es?tKbG$llK%* zKko&=VQ>r_2dBUpa2A{gm%vr9x(9;luXdClUlSYzhrnTQ1RMp&z;SQ_oCK%Aec&v( z04{ z=Tocx^vU_r$~ACS&VN?<1~{~lw_la>omKgWoWHEx2ad@3%PJqZjJMYZ4sGH2=H=Y+ zUEImt+y!v%6Fgr8NA~i399-MS^G$I4Q#_vnH$TnuW`;X`4R;?{yO!roaOy^$_kW2y z04~eJeP}tKP~|hLd4KD2exS++=PxNYoYzfs^1YxD3v^9sN%j)n2rN zy8y2FdA>D@I}zY6faCLd-oKE$9^uX|<<2eVj;`RYg7YhR-dxRHIhVT*4##;unBXpM z=8jy<9p1v-x1GDTliPmfS4yNWv{$Fr;RZ-L``dHLM6+=0(<$FAd! zgEJP-=l65h4s!eDcx%|6<@` zmbYJifV&QEg7c5?@`1zL^~bm)f5)9Hac7_84$1Mxs{cuF1zbDI>!*)#7vy+Q)m~MO zhg2@T!plcq5Ny4*Wh^0p}e&pEI}%a=eUcFDb{XC|AJ#1-yPq zj$cvbQ;T>$8sQEs=1#8SF2}e7Yq`y{!Rxux;F28Aq59VV2jut-m5<2r7RrT9y#3&2 z?%c)P+NIp7%eY&;+}ah~VQ}k~!Jmi4t?^oixq}t%@XOrhJKQCCKNYcmqCIoM;Lkt8 zr5^6kLhh)%Ux%u%E#~>~67Jkm?));ay#I!3FDmc9p9|lk>-w zt8%`#a%uA5^GWQlHHAAdle@BjJHLoKzJ$BJj61M~+b`!As{RyhuUDc!$&U?Q4=!@; z+k^GRM@>Q7>%E7Zequ0hlKYMfT71;xFArL5Gm;u?U);+xw`U^_13F)ZPR-8rl>T18zGh1BdUm&{$GnY>*AmM zcufnoFN1+~?zVQn)keg=+m|RoR2e0M;yZxEqSk9>%7k3uy*SJgTqkB6u)9;*5QdMp-k zZHR5F`s)6yYJF4`vMsW?fr$86P+UK%zPf*`TC4lVifdFXYWu4H3n`}dy}y9`Uu257 N%IRLh(rSg<|8KJ(uM+?O literal 0 HcmV?d00001 diff --git a/programs/voter-stake-registry/tests/rewards_invocations.rs b/programs/voter-stake-registry/tests/rewards_invocations.rs new file mode 100644 index 00000000..f5572be3 --- /dev/null +++ b/programs/voter-stake-registry/tests/rewards_invocations.rs @@ -0,0 +1,268 @@ +use anchor_lang::prelude::*; +use anchor_lang::AnchorDeserialize; +use anchor_spl::token::TokenAccount; +use program_test::*; +use solana_program_test::*; +use solana_sdk::program_pack::IsInitialized; +use solana_sdk::sysvar; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_program, + transport::TransportError, +}; +use voter_stake_registry::cpi_instructions::RewardsInstruction; + +mod program_test; + +#[tokio::test] +pub async fn initialize_root() -> std::result::Result<(), TransportError> { + let context = TestContext::new().await; + let payer = &context.users[0].key; + + let rewards_root_kp = context.rewards.initialize_root(payer).await?; + + let rewards_root_account = context + .solana + .get_account::(rewards_root_kp.pubkey()) + .await; + + assert_eq!(rewards_root_account.authority, payer.pubkey()); + assert_eq!(rewards_root_account.account_type, AccountType::RewardsRoot); + + Ok(()) +} + +// just run transaction to make sure they work +#[tokio::test] +pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError> { + let context = TestContext::new().await; + let payer = &context.users[0].key; + + // create token mint + let reward_mint = Keypair::new(); + let manager = &payer.pubkey(); + create_mint( + &mut context.solana.context.borrow_mut(), + &reward_mint, + manager, + ) + .await + .unwrap(); + + let rewards_root_kp = context.rewards.initialize_root(payer).await?; + + let rewards_pool = context + .rewards + .initialize_pool(&rewards_root_kp, payer, payer) + .await?; + + let vault = context + .rewards + .add_vault( + &rewards_root_kp.pubkey(), + &rewards_pool, + &reward_mint.pubkey(), + payer, + ) + .await?; + + // TODO: will not work because no deposits yet + // let rewarder = context + // .solana + // .create_token_account(&payer.pubkey(), reward_mint.pubkey()) + // .await; + // let amount = 100; + // context + // .rewards + // .fill_vault( + // &rewards_pool, + // &reward_mint.pubkey(), + // &vault, + // payer, + // &rewarder, + // amount, + // ) + // .await?; + + Ok(()) +} + +impl RewardsCookie { + pub async fn initialize_root( + &self, + payer: &Keypair, + ) -> std::result::Result { + let rewards_root = Keypair::new(); + + let accounts = vec![ + AccountMeta::new(rewards_root.pubkey(), true), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::InitializeRoot, + accounts, + ); + + let signers = vec![payer, &rewards_root]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(rewards_root) + } + + pub async fn initialize_pool( + &self, + rewards_root: &Keypair, + deposit_authority: &Keypair, + payer: &Keypair, + ) -> std::result::Result { + let (reward_pool, _bump) = Pubkey::find_program_address( + &["reward_pool".as_bytes(), &rewards_root.pubkey().to_bytes()], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new_readonly(rewards_root.pubkey(), false), + AccountMeta::new(reward_pool, false), + AccountMeta::new_readonly(deposit_authority.pubkey(), false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::InitializePool, + accounts, + ); + + let signers = vec![payer]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(reward_pool) + } + + pub async fn add_vault( + &self, + rewards_root: &Pubkey, + reward_pool: &Pubkey, + reward_mint: &Pubkey, + payer: &Keypair, + ) -> std::result::Result { + let (vault, _bump) = Pubkey::find_program_address( + &[ + "vault".as_bytes(), + &reward_pool.to_bytes(), + &reward_mint.to_bytes(), + ], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new_readonly(*rewards_root, false), + AccountMeta::new(*reward_pool, false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new(vault, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + let ix = + Instruction::new_with_borsh(self.program_id, &RewardsInstruction::AddVault, accounts); + + let signers = vec![payer]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(vault) + } + + pub async fn fill_vault( + &self, + reward_pool: &Pubkey, + reward_mint: &Pubkey, + vault: &Pubkey, + authority: &Keypair, + source_token_account: &Pubkey, + amount: u64, + ) -> std::result::Result<(), BanksClientError> { + let accounts = vec![ + AccountMeta::new(*reward_pool, false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new(*vault, false), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new(*source_token_account, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::FillVault { amount }, + accounts, + ); + + let signers = vec![authority]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] +pub struct RewardsRoot { + /// Account type - RewardsRoot + pub account_type: AccountType, + /// Authority address + pub authority: Pubkey, +} + +#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] +pub enum AccountType { + /// If the account has not been initialized, the enum will be 0 + #[default] + Uninitialized, + /// Rewards root + RewardsRoot, + /// Reward pool + RewardPool, +} + +impl IsInitialized for RewardsRoot { + fn is_initialized(&self) -> bool { + self.account_type != AccountType::Uninitialized + } +} + +impl AccountDeserialize for RewardsRoot { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let rewards_root: RewardsRoot = + AnchorDeserialize::deserialize(buf).map_err(|_| ErrorCode::AccountDidNotDeserialize)?; + if !IsInitialized::is_initialized(&rewards_root) { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + Ok(rewards_root) + } + + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let rewards_root: RewardsRoot = AnchorDeserialize::deserialize(buf) + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + Ok(rewards_root) + } +} From d36557de7b721b0a9e0207413f47001ad5f4c191 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 28 May 2024 17:43:21 +0300 Subject: [PATCH 07/59] add create_mint function for test utils --- .../tests/program_test/utils.rs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index 47348af6..1f8ab0a8 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,7 +1,13 @@ use bytemuck::{bytes_of, Contiguous}; use solana_program::program_error::ProgramError; +use solana_program_test::{BanksClientError, ProgramTestContext}; +use solana_sdk::account::Account; +use solana_sdk::program_pack::Pack; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::system_instruction; +use solana_sdk::transaction::Transaction; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { @@ -32,3 +38,36 @@ pub fn create_signer_key_and_nonce(program_id: &Pubkey, acc_pk: &Pubkey) -> (Pub pub fn clone_keypair(keypair: &Keypair) -> Keypair { Keypair::from_base58_string(&keypair.to_base58_string()) } + +pub async fn create_mint( + context: &mut ProgramTestContext, + mint: &Keypair, + manager: &Pubkey, +) -> Result<(), BanksClientError> { + let rent = context.banks_client.get_rent().await.unwrap(); + + let tx = Transaction::new_signed_with_payer( + &[ + system_instruction::create_account( + &context.payer.pubkey(), + &mint.pubkey(), + rent.minimum_balance(spl_token::state::Mint::LEN), + spl_token::state::Mint::LEN as u64, + &spl_token::id(), + ), + spl_token::instruction::initialize_mint( + &spl_token::id(), + &mint.pubkey(), + manager, + None, + 0, + ) + .unwrap(), + ], + Some(&context.payer.pubkey()), + &[&context.payer, mint], + context.last_blockhash, + ); + + context.banks_client.process_transaction(tx).await +} From d4c9ce6ee209d5f3634d91b6a9e1a07e2fde59fd Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 28 May 2024 17:44:48 +0300 Subject: [PATCH 08/59] add RewardsCookies and extend context of Addin Context --- .../voter-stake-registry/tests/program_test/cookies.rs | 9 ++++++++- programs/voter-stake-registry/tests/program_test/mod.rs | 8 ++++++++ .../voter-stake-registry/tests/program_test/solana.rs | 1 - 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/programs/voter-stake-registry/tests/program_test/cookies.rs b/programs/voter-stake-registry/tests/program_test/cookies.rs index 42e5e797..0bd8cf48 100644 --- a/programs/voter-stake-registry/tests/program_test/cookies.rs +++ b/programs/voter-stake-registry/tests/program_test/cookies.rs @@ -1,4 +1,6 @@ -use crate::utils::*; +use std::sync::Arc; + +use crate::{solana, utils::*}; use solana_program::pubkey::*; use solana_sdk::signature::Keypair; @@ -30,3 +32,8 @@ pub struct UserCookie { pub key: Keypair, pub token_accounts: Vec, } + +pub struct RewardsCookie { + pub solana: Arc, + pub program_id: Pubkey, +} diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index cd849633..145f1f29 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -82,6 +82,7 @@ impl Log for LoggerWrapper { pub struct TestContext { pub solana: Arc, pub governance: GovernanceCookie, + pub rewards: RewardsCookie, pub addin: AddinCookie, pub mints: Vec, pub users: Vec, @@ -122,6 +123,9 @@ impl TestContext { governance_program_id, processor!(spl_governance::processor::process_instruction), ); + let rewards_program_id = + Pubkey::from_str("J8oa8UUJBydrTKtCdkvwmQQ27ZFDq54zAxWJY5Ey72Ji").unwrap(); + test.add_program("mplx_rewards", rewards_program_id, None); // Setup the environment @@ -230,6 +234,10 @@ impl TestContext { program_id: addin_program_id, time_offset: RefCell::new(0), }, + rewards: RewardsCookie { + solana: solana.clone(), + program_id: rewards_program_id, + }, mints, users, quote_index, diff --git a/programs/voter-stake-registry/tests/program_test/solana.rs b/programs/voter-stake-registry/tests/program_test/solana.rs index 8aa58999..3bc9441f 100644 --- a/programs/voter-stake-registry/tests/program_test/solana.rs +++ b/programs/voter-stake-registry/tests/program_test/solana.rs @@ -113,7 +113,6 @@ impl SolanaCookie { .to_vec() } - #[allow(dead_code)] pub async fn get_account(&self, address: Pubkey) -> T { let data = self.get_account_data(address).await; let mut data_slice: &[u8] = &data; From 52f823c81b90c6726b1a2a80c789e25f8e6bed34 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 29 May 2024 20:56:05 +0300 Subject: [PATCH 09/59] refactor tests && enable CPI calls in test for all functions && change mutability of input parameters for functions that are related to CPI invocatioins --- program-states/src/state/voter.rs | 12 + .../src/cpi_instructions.rs | 10 +- .../src/instructions/create_deposit_entry.rs | 77 ----- .../src/instructions/create_voter.rs | 78 +++-- .../src/instructions/deposit.rs | 50 +-- .../src/instructions/extend_deposit.rs | 9 +- .../src/instructions/withdraw.rs | 9 +- .../tests/fixtures/mplx_rewards.so | Bin 270528 -> 273600 bytes .../tests/program_test/addin.rs | 46 +-- .../tests/program_test/cookies.rs | 5 - .../tests/program_test/mod.rs | 9 +- .../tests/program_test/rewards.rs | 284 ++++++++++++++++++ .../tests/program_test/utils.rs | 54 +++- .../tests/rewards_invocations.rs | 223 ++------------ .../tests/test_all_deposits.rs | 27 +- .../voter-stake-registry/tests/test_basic.rs | 39 ++- .../tests/test_deposit_constant.rs | 72 ++++- .../tests/test_deposit_no_locking.rs | 70 +++-- .../tests/test_internal_transfers.rs | 38 ++- .../voter-stake-registry/tests/test_lockup.rs | 99 ++++-- .../tests/test_log_voter_info.rs | 24 +- .../voter-stake-registry/tests/test_voting.rs | 80 ++++- 22 files changed, 878 insertions(+), 437 deletions(-) create mode 100644 programs/voter-stake-registry/tests/program_test/rewards.rs diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index c863c7d5..51545512 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -62,6 +62,18 @@ impl Voter { require!(d.is_used, VsrError::UnusedDepositEntryIndex); Ok(d) } + + pub fn active_deposit(&self, index: u8) -> Result<&DepositEntry> { + let index = index as usize; + require_gt!( + self.deposits.len(), + index, + VsrError::OutOfBoundsDepositEntryIndex + ); + let d = &self.deposits[index]; + require!(d.is_used, VsrError::UnusedDepositEntryIndex); + Ok(d) + } } #[macro_export] diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index f4c99275..e3cd0d58 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -75,6 +75,8 @@ pub enum RewardsInstruction { amount: u64, /// Lockup Period lockup_period: LockupPeriod, + /// Specifies mint addr + reward_mint_addr: Pubkey, }, /// Withdraws amount of supply to the mining account @@ -154,7 +156,7 @@ pub fn initialize_mining<'a>( /// Rewards deposit mining #[allow(clippy::too_many_arguments)] pub fn deposit_mining<'a>( - program_id: &Pubkey, + program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, user: AccountInfo<'a>, @@ -167,23 +169,23 @@ pub fn deposit_mining<'a>( let accounts = vec![ AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(*reward_mint, false), AccountMeta::new_readonly(user.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), ]; let ix = Instruction::new_with_borsh( - *program_id, + program_id.key(), &RewardsInstruction::DepositMining { amount, lockup_period, + reward_mint_addr: *reward_mint, }, accounts, ); invoke_signed( &ix, - &[reward_pool, mining, user, deposit_authority], + &[reward_pool, mining, user, deposit_authority, program_id], signers_seeds, ) } diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index 6aeeee29..1cc81471 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -3,13 +3,6 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; -use solana_program::{ - instruction::Instruction, - program::{invoke, invoke_signed}, - system_program, sysvar, -}; - -use crate::cpi_instructions::RewardsInstruction; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { @@ -94,73 +87,3 @@ pub fn create_deposit_entry( Ok(()) } - -/// Rewards initialize mining -#[allow(clippy::too_many_arguments)] -pub fn initialize_mining<'a>( - program_id: &Pubkey, - reward_pool: AccountInfo<'a>, - mining: AccountInfo<'a>, - user: AccountInfo<'a>, - payer: AccountInfo<'a>, - system_program: AccountInfo<'a>, - rent: AccountInfo<'a>, -) -> Result<()> { - let accounts = vec![ - AccountMeta::new(reward_pool.key(), false), - AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(user.key(), false), - AccountMeta::new(payer.key(), true), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ]; - - let ix = - Instruction::new_with_borsh(*program_id, &RewardsInstruction::InitializeMining, accounts); - - invoke( - &ix, - &[reward_pool, mining, user, payer, system_program, rent], - )?; - - Ok(()) -} - -/// Rewards deposit mining -#[allow(clippy::too_many_arguments)] -pub fn deposit_mining<'a>( - program_id: &Pubkey, - reward_pool: AccountInfo<'a>, - mining: AccountInfo<'a>, - user: AccountInfo<'a>, - deposit_authority: AccountInfo<'a>, - amount: u64, - lockup_period: LockupPeriod, - signers_seeds: &[&[&[u8]]], - reward_mint: &Pubkey, -) -> Result<()> { - let accounts = vec![ - AccountMeta::new(reward_pool.key(), false), - AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(reward_mint.key(), false), - AccountMeta::new_readonly(user.key(), false), - AccountMeta::new_readonly(deposit_authority.key(), true), - ]; - - let ix = Instruction::new_with_borsh( - *program_id, - &RewardsInstruction::DepositMining { - amount, - lockup_period, - }, - accounts, - ); - - invoke_signed( - &ix, - &[reward_pool, mining, user, deposit_authority], - signers_seeds, - )?; - - Ok(()) -} diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 355452a5..6cb9b712 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -48,11 +48,16 @@ pub struct CreateVoter<'info> { #[account(address = tx_instructions::ID)] pub instructions: UncheckedAccount<'info>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + /// CHECK: mining PDA will be checked in the rewards contract + #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, - /// CHECK: Reward Pool PDA will be checked in the rewards contract - pub reward_pool: UncheckedAccount<'info>, + /// CHECK: Rewards program ID + pub rewards_program: UncheckedAccount<'info>, } /// Creates a new voter account. There can only be a single voter per @@ -85,37 +90,44 @@ pub fn create_voter( *ctx.bumps.get("voter_weight_record").unwrap() ); - let mining = &ctx.accounts.deposit_mining; - let user = &ctx.accounts.payer; - let voter = &ctx.accounts.voter; - let system_program = &ctx.accounts.system_program; - let reward_pool = &ctx.accounts.reward_pool; - - cpi_instructions::initialize_mining( - &REWARD_CONTRACT_ID, - reward_pool.to_account_info(), - mining.to_account_info(), - voter.to_account_info(), - user.to_account_info(), - system_program.to_account_info(), - )?; - - // Load accounts. - let registrar = &ctx.accounts.registrar.load()?; - let voter_authority = ctx.accounts.voter_authority.key(); - - let voter = &mut ctx.accounts.voter.load_init()?; - voter.voter_bump = voter_bump; - voter.voter_weight_record_bump = voter_weight_record_bump; - voter.voter_authority = voter_authority; - voter.registrar = ctx.accounts.registrar.key(); - - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - voter_weight_record.account_discriminator = - spl_governance_addin_api::voter_weight::VoterWeightRecord::ACCOUNT_DISCRIMINATOR; - voter_weight_record.realm = registrar.realm; - voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; - voter_weight_record.governing_token_owner = voter_authority; + { + // Load accounts. + let registrar = &ctx.accounts.registrar.load()?; + let voter_authority = ctx.accounts.voter_authority.key(); + + let voter = &mut ctx.accounts.voter.load_init()?; + voter.voter_bump = voter_bump; + voter.voter_weight_record_bump = voter_weight_record_bump; + voter.voter_authority = voter_authority; + voter.registrar = ctx.accounts.registrar.key(); + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + voter_weight_record.account_discriminator = + spl_governance_addin_api::voter_weight::VoterWeightRecord::ACCOUNT_DISCRIMINATOR; + voter_weight_record.realm = registrar.realm; + voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; + voter_weight_record.governing_token_owner = voter_authority; + } + + { + // initialize Mining account for Voter + let mining = ctx.accounts.deposit_mining.to_account_info(); + let payer = ctx.accounts.payer.to_account_info(); + // let voter = ctx.accounts.voter.to_account_info(); + let user = ctx.accounts.voter_authority.to_account_info(); + let system_program = ctx.accounts.system_program.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + + // TODO: should it be voter or voter authority? + cpi_instructions::initialize_mining( + &REWARD_CONTRACT_ID, + reward_pool, + mining, + user, + payer, + system_program, + )?; + } Ok(()) } diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 17587cc0..b0786671 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -35,11 +35,16 @@ pub struct Deposit<'info> { pub token_program: Program<'info, Token>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + /// CHECK: mining PDA will be checked in the rewards contract + #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, - /// CHECK: Reward Pool PDA will be checked in the rewards contract - pub reward_pool: UncheckedAccount<'info>, + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, } impl<'info> Deposit<'info> { @@ -66,30 +71,33 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R if amount == 0 { return Ok(()); } - let registrar = &ctx.accounts.registrar.load()?; - let voter = &mut ctx.accounts.voter.load_mut()?; - - let d_entry = voter.active_deposit_mut(deposit_entry_index)?; - - // Get the exchange rate entry associated with this deposit. - let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; - require_eq!( - mint_idx, - d_entry.voting_mint_config_idx as usize, - VsrError::InvalidMint - ); - - // Deposit tokens into the vault and increase the lockup amount too. let curr_ts = registrar.clock_unix_timestamp(); - token::transfer(ctx.accounts.transfer_ctx(), amount)?; - d_entry.amount_deposited_native = d_entry.amount_deposited_native.checked_add(amount).unwrap(); + + { + let voter = &mut ctx.accounts.voter.load_mut()?; + let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + + // Get the exchange rate entry associated with this deposit. + let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; + require_eq!( + mint_idx, + d_entry.voting_mint_config_idx as usize, + VsrError::InvalidMint + ); + // Deposit tokens into the vault and increase the lockup amount too. + + token::transfer(ctx.accounts.transfer_ctx(), amount)?; + d_entry.amount_deposited_native = + d_entry.amount_deposited_native.checked_add(amount).unwrap(); + } let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let deposit_authority = &ctx.accounts.deposit_authority; let reward_mint = &ctx.accounts.deposit_token.mint; - let voter = &ctx.accounts.voter; + let voter = &ctx.accounts.voter.load()?; + let d_entry = voter.active_deposit(deposit_entry_index)?; let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], @@ -103,10 +111,10 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R ]; cpi_instructions::deposit_mining( - &REWARD_CONTRACT_ID, + ctx.accounts.rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), - voter.to_account_info(), + ctx.accounts.voter.to_account_info(), deposit_authority.to_account_info(), amount, d_entry.lockup.period, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 719b38ce..c2d91077 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -35,11 +35,16 @@ pub struct RestakeDeposit<'info> { pub token_program: Program<'info, Token>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + /// CHECK: mining PDA will be checked in the rewards contract + #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, - /// CHECK: Reward Pool PDA will be checked in the rewards contract - pub reward_pool: UncheckedAccount<'info>, + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, } /// Prolongs the deposit diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 3b5de9fe..30595ce0 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -55,11 +55,16 @@ pub struct Withdraw<'info> { pub token_program: Program<'info, Token>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + /// CHECK: mining PDA will be checked in the rewards contract + #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, - /// CHECK: Reward Pool PDA will be checked in the rewards contract - pub reward_pool: UncheckedAccount<'info>, + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, } impl<'info> Withdraw<'info> { diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index 91c91e196eaf46333d93502842cdb7802ca348d6..c2ffc9bccd86c1fdbafefa6a339c92701e9a9c54 100755 GIT binary patch delta 35845 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 bcO39{9XM#-l(6*31Xgi49iii Result<(), BanksClientError> { + ) -> std::result::Result<(), BanksClientError> { let data = anchor_lang::InstructionData::data( &voter_stake_registry::instruction::InternalTransferUnlocked { source_deposit_entry_index, @@ -134,6 +133,7 @@ impl AddinCookie { .await } + #[allow(clippy::too_many_arguments)] pub async fn configure_voting_mint( &self, registrar: &RegistrarCookie, @@ -199,12 +199,16 @@ impl AddinCookie { VotingMintConfigCookie { mint: mint.clone() } } + #[allow(clippy::too_many_arguments)] pub async fn create_voter( &self, registrar: &RegistrarCookie, token_owner_record: &TokenOwnerRecordCookie, authority: &Keypair, payer: &Keypair, + reward_pool: &Pubkey, + deposit_mining: &Pubkey, + rewards_program: &Pubkey, ) -> VoterCookie { let (voter, voter_bump) = Pubkey::find_program_address( &[ @@ -239,6 +243,9 @@ impl AddinCookie { system_program: solana_sdk::system_program::id(), rent: solana_program::sysvar::rent::id(), instructions: solana_program::sysvar::instructions::id(), + reward_pool: *reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, }, None, ); @@ -266,6 +273,7 @@ impl AddinCookie { } } + #[allow(clippy::too_many_arguments)] pub async fn create_deposit_entry( &self, registrar: &RegistrarCookie, @@ -277,7 +285,7 @@ impl AddinCookie { start_ts: Option, period: LockupPeriod, ) -> std::result::Result<(), BanksClientError> { - let vault = voter.vault_address(&voting_mint); + let vault = voter.vault_address(voting_mint); let data = anchor_lang::InstructionData::data( &voter_stake_registry::instruction::CreateDepositEntry { @@ -318,7 +326,7 @@ impl AddinCookie { .await } - #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] pub async fn deposit( &self, registrar: &RegistrarCookie, @@ -328,8 +336,11 @@ impl AddinCookie { token_address: Pubkey, deposit_entry_index: u8, amount: u64, + reward_pool: &Pubkey, + deposit_mining: &Pubkey, + rewards_program: &Pubkey, ) -> std::result::Result<(), BanksClientError> { - let vault = voter.vault_address(&voting_mint); + let vault = voter.vault_address(voting_mint); let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Deposit { @@ -345,6 +356,9 @@ impl AddinCookie { deposit_token: token_address, deposit_authority: authority.pubkey(), token_program: spl_token::id(), + reward_pool: *reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, }, None, ); @@ -363,7 +377,6 @@ impl AddinCookie { .await } - #[allow(dead_code)] pub async fn unlock_tokens( &self, registrar: &RegistrarCookie, @@ -399,7 +412,7 @@ impl AddinCookie { .await } - #[allow(dead_code)] + #[allow(clippy::too_many_arguments)] pub async fn withdraw( &self, registrar: &RegistrarCookie, @@ -409,8 +422,11 @@ impl AddinCookie { token_address: Pubkey, deposit_entry_index: u8, amount: u64, + reward_pool: &Pubkey, + deposit_mining: &Pubkey, + rewards_program: &Pubkey, ) -> std::result::Result<(), BanksClientError> { - let vault = voter.vault_address(&voting_mint); + let vault = voter.vault_address(voting_mint); let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Withdraw { @@ -428,6 +444,9 @@ impl AddinCookie { destination: token_address, voter_authority: authority.pubkey(), token_program: spl_token::id(), + reward_pool: *reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, }, None, ); @@ -446,7 +465,6 @@ impl AddinCookie { .await } - #[allow(dead_code)] pub async fn close_voter( &self, registrar: &RegistrarCookie, @@ -454,7 +472,7 @@ impl AddinCookie { voting_mint: &VotingMintConfigCookie, voter_authority: &Keypair, ) -> std::result::Result<(), BanksClientError> { - let vault = voter.vault_address(&voting_mint); + let vault = voter.vault_address(voting_mint); let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::CloseVoter {}); @@ -511,7 +529,6 @@ impl AddinCookie { } } - #[allow(dead_code)] pub async fn update_voter_weight_record( &self, registrar: &RegistrarCookie, @@ -529,13 +546,12 @@ impl AddinCookie { .await) } - #[allow(dead_code)] pub async fn close_deposit_entry( &self, voter: &VoterCookie, authority: &Keypair, deposit_entry_index: u8, - ) -> Result<(), BanksClientError> { + ) -> std::result::Result<(), BanksClientError> { let data = anchor_lang::InstructionData::data( &voter_stake_registry::instruction::CloseDepositEntry { deposit_entry_index, @@ -564,7 +580,6 @@ impl AddinCookie { .await } - #[allow(dead_code)] pub async fn log_voter_info( &self, registrar: &RegistrarCookie, @@ -597,7 +612,6 @@ impl AddinCookie { .unwrap(); } - #[allow(dead_code)] pub async fn set_time_offset( &self, _registrar: &RegistrarCookie, @@ -623,7 +637,6 @@ impl AddinCookie { } impl VotingMintConfigCookie { - #[allow(dead_code)] pub async fn vault_balance(&self, solana: &SolanaCookie, voter: &VoterCookie) -> u64 { let vault = voter.vault_address(&self); solana.get_account::(vault).await.amount @@ -631,7 +644,6 @@ impl VotingMintConfigCookie { } impl VoterCookie { - #[allow(dead_code)] pub async fn deposit_amount(&self, solana: &SolanaCookie, deposit_id: u8) -> u64 { solana .get_account::(self.address) diff --git a/programs/voter-stake-registry/tests/program_test/cookies.rs b/programs/voter-stake-registry/tests/program_test/cookies.rs index 0bd8cf48..9af26135 100644 --- a/programs/voter-stake-registry/tests/program_test/cookies.rs +++ b/programs/voter-stake-registry/tests/program_test/cookies.rs @@ -32,8 +32,3 @@ pub struct UserCookie { pub key: Keypair, pub token_accounts: Vec, } - -pub struct RewardsCookie { - pub solana: Arc, - pub program_id: Pubkey, -} diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index 145f1f29..da802bfc 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -16,9 +16,12 @@ pub use governance::*; pub use solana::*; pub use utils::*; +use self::rewards::RewardsCookie; + pub mod addin; pub mod cookies; pub mod governance; +pub mod rewards; pub mod solana; pub mod utils; @@ -117,7 +120,7 @@ impl TestContext { test.set_compute_max_units(120000); let governance_program_id = - Pubkey::from_str(&"GovernanceProgramTest1111111111111111111111").unwrap(); + Pubkey::from_str("GovernanceProgramTest1111111111111111111111").unwrap(); test.add_program( "spl_governance", governance_program_id, @@ -135,8 +138,8 @@ impl TestContext { index: 0, decimals: 6, unit: 10u64.pow(6) as f64, - base_lot: 100 as f64, - quote_lot: 10 as f64, + base_lot: 100_f64, + quote_lot: 10_f64, pubkey: None, //Some(mngo_token::ID), authority: Keypair::new(), }, // symbol: "MNGO".to_string() diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs new file mode 100644 index 00000000..1fd5d78c --- /dev/null +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -0,0 +1,284 @@ +use std::sync::Arc; + +use anchor_lang::prelude::*; +use anchor_lang::AnchorDeserialize; +use mplx_staking_states::state::LockupPeriod; +use solana_program_test::*; +use solana_sdk::program_pack::IsInitialized; +use solana_sdk::sysvar; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_program, +}; +use voter_stake_registry::cpi_instructions::RewardsInstruction; + +use crate::SolanaCookie; + +pub struct RewardsCookie { + pub solana: Arc, + pub program_id: Pubkey, +} + +impl RewardsCookie { + pub async fn initialize_root( + &self, + payer: &Keypair, + ) -> std::result::Result { + let rewards_root = Keypair::new(); + + let accounts = vec![ + AccountMeta::new(rewards_root.pubkey(), true), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::InitializeRoot, + accounts, + ); + + let signers = vec![payer, &rewards_root]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(rewards_root) + } + + pub async fn initialize_pool( + &self, + rewards_root: &Keypair, + deposit_authority: &Keypair, + payer: &Keypair, + ) -> std::result::Result { + let (reward_pool, _bump) = Pubkey::find_program_address( + &["reward_pool".as_bytes(), &rewards_root.pubkey().to_bytes()], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new_readonly(rewards_root.pubkey(), false), + AccountMeta::new(reward_pool, false), + AccountMeta::new_readonly(deposit_authority.pubkey(), false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::InitializePool, + accounts, + ); + + let signers = vec![payer]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(reward_pool) + } + + pub async fn add_vault( + &self, + rewards_root: &Pubkey, + reward_pool: &Pubkey, + reward_mint: &Pubkey, + payer: &Keypair, + ) -> std::result::Result { + let (vault, _bump) = Pubkey::find_program_address( + &[ + "vault".as_bytes(), + &reward_pool.to_bytes(), + &reward_mint.to_bytes(), + ], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new_readonly(*rewards_root, false), + AccountMeta::new(*reward_pool, false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new(vault, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new_readonly(system_program::id(), false), + AccountMeta::new_readonly(sysvar::rent::id(), false), + ]; + + let ix = + Instruction::new_with_borsh(self.program_id, &RewardsInstruction::AddVault, accounts); + + let signers = vec![payer]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(vault) + } + + pub async fn fill_vault( + &self, + reward_pool: &Pubkey, + reward_mint: &Pubkey, + vault: &Pubkey, + authority: &Keypair, + source_token_account: &Pubkey, + amount: u64, + ) -> std::result::Result<(), BanksClientError> { + let accounts = vec![ + AccountMeta::new(*reward_pool, false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new(*vault, false), + AccountMeta::new_readonly(authority.pubkey(), true), + AccountMeta::new(*source_token_account, false), + AccountMeta::new_readonly(spl_token::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::FillVault { amount }, + accounts, + ); + + let signers = vec![authority]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + pub async fn initialize_mining<'a>( + &self, + reward_pool: &Pubkey, + user: &Pubkey, + payer: &Keypair, + ) -> std::result::Result { + let (mining, _bump) = Pubkey::find_program_address( + &[ + "mining".as_bytes(), + &user.key().to_bytes(), + &reward_pool.key().to_bytes(), + ], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new(*reward_pool, false), + AccountMeta::new(mining, false), + AccountMeta::new_readonly(*user, false), + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::InitializeMining, + accounts, + ); + + let signers = vec![payer]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(mining) + } + + #[allow(clippy::too_many_arguments)] + pub async fn deposit_mining<'a>( + &self, + reward_pool: &Pubkey, + user: &Pubkey, + deposit_authority: &Keypair, + amount: u64, + lockup_period: LockupPeriod, + mint_account: &Pubkey, + ) -> std::result::Result<(), BanksClientError> { + let (mining, _bump) = Pubkey::find_program_address( + &[ + "mining".as_bytes(), + &user.key().to_bytes(), + &reward_pool.key().to_bytes(), + ], + &self.program_id, + ); + + let accounts = vec![ + AccountMeta::new(*reward_pool, false), + AccountMeta::new(mining, false), + AccountMeta::new_readonly(*user, false), + AccountMeta::new_readonly(deposit_authority.pubkey(), true), + ]; + + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::DepositMining { + amount, + lockup_period, + reward_mint_addr: *mint_account, + }, + accounts, + ); + + let signers = vec![deposit_authority]; + + self.solana + .process_transaction(&[ix], Some(&signers)) + .await?; + + Ok(()) + } +} + +#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] +pub struct RewardsRoot { + /// Account type - RewardsRoot + pub account_type: AccountType, + /// Authority address + pub authority: Pubkey, +} + +#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] +pub enum AccountType { + /// If the account has not been initialized, the enum will be 0 + #[default] + Uninitialized, + /// Rewards root + RewardsRoot, + /// Reward pool + RewardPool, +} + +impl IsInitialized for RewardsRoot { + fn is_initialized(&self) -> bool { + self.account_type != AccountType::Uninitialized + } +} + +impl AccountDeserialize for RewardsRoot { + fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { + let rewards_root: RewardsRoot = + AnchorDeserialize::deserialize(buf).map_err(|_| ErrorCode::AccountDidNotDeserialize)?; + if !IsInitialized::is_initialized(&rewards_root) { + return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); + } + Ok(rewards_root) + } + + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + let rewards_root: RewardsRoot = AnchorDeserialize::deserialize(buf) + .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; + Ok(rewards_root) + } +} diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index 1f8ab0a8..2f4d6304 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,13 +1,15 @@ use bytemuck::{bytes_of, Contiguous}; use solana_program::program_error::ProgramError; use solana_program_test::{BanksClientError, ProgramTestContext}; -use solana_sdk::account::Account; use solana_sdk::program_pack::Pack; use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; use solana_sdk::system_instruction; use solana_sdk::transaction::Transaction; +use solana_sdk::transport::TransportError; + +use crate::{AddinCookie, RegistrarCookie, TestContext}; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { @@ -71,3 +73,53 @@ pub async fn create_mint( context.banks_client.process_transaction(tx).await } + +pub async fn initialize_rewards_contract( + payer: &Keypair, + context: &TestContext, +) -> Result { + // create token mint + let reward_mint = Keypair::new(); + let manager = &payer.pubkey(); + create_mint( + &mut context.solana.context.borrow_mut(), + &reward_mint, + manager, + ) + .await + .unwrap(); + + let rewards_root = context.rewards.initialize_root(payer).await?; + let deposit_authority = Keypair::new(); + let rewards_pool = context + .rewards + .initialize_pool(&rewards_root, &deposit_authority, payer) + .await?; + let _vault = context + .rewards + .add_vault( + &rewards_root.pubkey(), + &rewards_pool, + &reward_mint.pubkey(), + payer, + ) + .await?; + + Ok(rewards_pool) +} + +pub fn find_deposit_mining_addr( + user: &Pubkey, + rewards_pool: &Pubkey, + rewards_program_addr: &Pubkey, +) -> Pubkey { + let (deposit_mining, _bump) = Pubkey::find_program_address( + &[ + "mining".as_bytes(), + &user.to_bytes(), + &rewards_pool.to_bytes(), + ], + rewards_program_addr, + ); + deposit_mining +} diff --git a/programs/voter-stake-registry/tests/rewards_invocations.rs b/programs/voter-stake-registry/tests/rewards_invocations.rs index f5572be3..ffa62ff9 100644 --- a/programs/voter-stake-registry/tests/rewards_invocations.rs +++ b/programs/voter-stake-registry/tests/rewards_invocations.rs @@ -1,21 +1,13 @@ -use anchor_lang::prelude::*; -use anchor_lang::AnchorDeserialize; use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::LockupPeriod; use program_test::*; use solana_program_test::*; -use solana_sdk::program_pack::IsInitialized; -use solana_sdk::sysvar; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_program, - transport::TransportError, -}; -use voter_stake_registry::cpi_instructions::RewardsInstruction; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::cpi_instructions::deposit_mining; -mod program_test; +use crate::rewards::{AccountType, RewardsRoot}; + +pub mod program_test; #[tokio::test] pub async fn initialize_root() -> std::result::Result<(), TransportError> { @@ -54,12 +46,13 @@ pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError let rewards_root_kp = context.rewards.initialize_root(payer).await?; + let deposit_authority = Keypair::new(); let rewards_pool = context .rewards - .initialize_pool(&rewards_root_kp, payer, payer) + .initialize_pool(&rewards_root_kp, &deposit_authority, payer) .await?; - let vault = context + let _vault = context .rewards .add_vault( &rewards_root_kp.pubkey(), @@ -69,6 +62,27 @@ pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError ) .await?; + let user = Keypair::new(); + + let _mining = context + .rewards + .initialize_mining(&rewards_pool, &user.pubkey(), payer) + .await?; + + let amount = 1; + let lockup_period = LockupPeriod::ThreeMonths; + // context + // .rewards + // .deposit_mining( + // &rewards_pool, + // &user.pubkey(), + // &deposit_authority, + // amount, + // lockup_period, + // &reward_mint.pubkey(), + // ) + // .await?; + // TODO: will not work because no deposits yet // let rewarder = context // .solana @@ -89,180 +103,3 @@ pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError Ok(()) } - -impl RewardsCookie { - pub async fn initialize_root( - &self, - payer: &Keypair, - ) -> std::result::Result { - let rewards_root = Keypair::new(); - - let accounts = vec![ - AccountMeta::new(rewards_root.pubkey(), true), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new_readonly(system_program::id(), false), - ]; - - let ix = Instruction::new_with_borsh( - self.program_id, - &RewardsInstruction::InitializeRoot, - accounts, - ); - - let signers = vec![payer, &rewards_root]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok(rewards_root) - } - - pub async fn initialize_pool( - &self, - rewards_root: &Keypair, - deposit_authority: &Keypair, - payer: &Keypair, - ) -> std::result::Result { - let (reward_pool, _bump) = Pubkey::find_program_address( - &["reward_pool".as_bytes(), &rewards_root.pubkey().to_bytes()], - &self.program_id, - ); - - let accounts = vec![ - AccountMeta::new_readonly(rewards_root.pubkey(), false), - AccountMeta::new(reward_pool, false), - AccountMeta::new_readonly(deposit_authority.pubkey(), false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new_readonly(system_program::id(), false), - ]; - - let ix = Instruction::new_with_borsh( - self.program_id, - &RewardsInstruction::InitializePool, - accounts, - ); - - let signers = vec![payer]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok(reward_pool) - } - - pub async fn add_vault( - &self, - rewards_root: &Pubkey, - reward_pool: &Pubkey, - reward_mint: &Pubkey, - payer: &Keypair, - ) -> std::result::Result { - let (vault, _bump) = Pubkey::find_program_address( - &[ - "vault".as_bytes(), - &reward_pool.to_bytes(), - &reward_mint.to_bytes(), - ], - &self.program_id, - ); - - let accounts = vec![ - AccountMeta::new_readonly(*rewards_root, false), - AccountMeta::new(*reward_pool, false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(vault, false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(sysvar::rent::id(), false), - ]; - - let ix = - Instruction::new_with_borsh(self.program_id, &RewardsInstruction::AddVault, accounts); - - let signers = vec![payer]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok(vault) - } - - pub async fn fill_vault( - &self, - reward_pool: &Pubkey, - reward_mint: &Pubkey, - vault: &Pubkey, - authority: &Keypair, - source_token_account: &Pubkey, - amount: u64, - ) -> std::result::Result<(), BanksClientError> { - let accounts = vec![ - AccountMeta::new(*reward_pool, false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(*vault, false), - AccountMeta::new_readonly(authority.pubkey(), true), - AccountMeta::new(*source_token_account, false), - AccountMeta::new_readonly(spl_token::id(), false), - ]; - - let ix = Instruction::new_with_borsh( - self.program_id, - &RewardsInstruction::FillVault { amount }, - accounts, - ); - - let signers = vec![authority]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok(()) - } -} - -#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] -pub struct RewardsRoot { - /// Account type - RewardsRoot - pub account_type: AccountType, - /// Authority address - pub authority: Pubkey, -} - -#[derive(Clone, Debug, PartialEq, AnchorDeserialize, AnchorSerialize, Default)] -pub enum AccountType { - /// If the account has not been initialized, the enum will be 0 - #[default] - Uninitialized, - /// Rewards root - RewardsRoot, - /// Reward pool - RewardPool, -} - -impl IsInitialized for RewardsRoot { - fn is_initialized(&self) -> bool { - self.account_type != AccountType::Uninitialized - } -} - -impl AccountDeserialize for RewardsRoot { - fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result { - let rewards_root: RewardsRoot = - AnchorDeserialize::deserialize(buf).map_err(|_| ErrorCode::AccountDidNotDeserialize)?; - if !IsInitialized::is_initialized(&rewards_root) { - return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into()); - } - Ok(rewards_root) - } - - fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { - let rewards_root: RewardsRoot = AnchorDeserialize::deserialize(buf) - .map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize)?; - Ok(rewards_root) - } -} diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 0e6cafac..49963503 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -18,7 +18,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; @@ -26,7 +26,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { let voter_authority = &context.users[1].key; let voter_mngo = context.users[1].token_accounts[0]; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = addin @@ -48,8 +48,23 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; for i in 0..32 { @@ -75,6 +90,9 @@ async fn test_all_deposits() -> Result<(), TransportError> { voter_mngo, i, 12000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .unwrap(); @@ -105,6 +123,9 @@ async fn test_all_deposits() -> Result<(), TransportError> { voter_mngo, 0, 1000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index c647ef10..1d0040d5 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -19,14 +19,14 @@ async fn test_basic() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = context @@ -66,9 +66,24 @@ async fn test_basic() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = context .addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; // test deposit and withdraw @@ -93,16 +108,20 @@ async fn test_basic() -> Result<(), TransportError> { LockupPeriod::None, ) .await?; + context .addin .deposit( ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await?; @@ -124,10 +143,13 @@ async fn test_basic() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &&context.users[2].key, + &context.users[2].key, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .expect_err("fails because voter_authority is invalid"); @@ -138,10 +160,13 @@ async fn test_basic() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await?; @@ -166,7 +191,7 @@ async fn test_basic() -> Result<(), TransportError> { .await?; context .addin - .close_voter(®istrar, &voter, &mngo_voting_mint, &voter_authority) + .close_voter(®istrar, &voter, &mngo_voting_mint, voter_authority) .await?; let lamports_after = context .solana diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index ec01c99a..3d26b4ba 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -26,11 +26,11 @@ async fn balances( context.solana.advance_clock_by_slots(2).await; let token = context.solana.token_account_balance(address).await; - let vault = voting_mint.vault_balance(&context.solana, &voter).await; + let vault = voting_mint.vault_balance(&context.solana, voter).await; let deposit = voter.deposit_amount(&context.solana, deposit_id).await; let vwr = context .addin - .update_voter_weight_record(®istrar, &voter) + .update_voter_weight_record(registrar, voter) .await .unwrap(); Balances { @@ -54,14 +54,14 @@ async fn test_deposit_constant() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = addin @@ -83,8 +83,23 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; let reference_account = context.users[1].token_accounts[0]; @@ -103,10 +118,13 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; let deposit = |amount: u64| { @@ -114,10 +132,13 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; @@ -131,7 +152,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - &voter_authority, + voter_authority, &mngo_voting_mint, 0, LockupKind::Constant, @@ -158,7 +179,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { // request unlock addin - .unlock_tokens(®istrar, &voter, &voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 0) .await .unwrap(); withdraw(10_000) @@ -190,14 +211,14 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = addin @@ -219,8 +240,23 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; let reference_account = context.users[1].token_accounts[0]; @@ -229,10 +265,13 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; let deposit = |amount: u64| { @@ -240,10 +279,13 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; @@ -251,7 +293,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - &voter_authority, + voter_authority, &mngo_voting_mint, 0, LockupKind::Constant, diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 41e021fd..875ea878 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -27,11 +27,11 @@ async fn balances( context.solana.advance_clock_by_slots(2).await; let token = context.solana.token_account_balance(address).await; - let vault = voting_mint.vault_balance(&context.solana, &voter).await; + let vault = voting_mint.vault_balance(&context.solana, voter).await; let deposit = voter.deposit_amount(&context.solana, deposit_id).await; let vwr = context .addin - .update_voter_weight_record(®istrar, &voter) + .update_voter_weight_record(registrar, voter) .await .unwrap(); Balances { @@ -55,7 +55,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; @@ -63,10 +63,10 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { let voter_authority = &context.users[1].key; let voter2_authority = &context.users[2].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let token_owner_record2 = realm - .create_token_owner_record(voter2_authority.pubkey(), &payer) + .create_token_owner_record(voter2_authority.pubkey(), payer) .await; let registrar = addin @@ -88,12 +88,39 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining_voter = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, + ) .await; + let deposit_mining_voter2 = find_deposit_mining_addr( + &voter2_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter2 = addin - .create_voter(®istrar, &token_owner_record2, &voter2_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record2, + voter2_authority, + payer, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) .await; let reference_account = context.users[1].token_accounts[0]; @@ -112,10 +139,13 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, amount, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) }; let deposit = |deposit_id: u8, amount: u64| { @@ -123,10 +153,13 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, deposit_id, amount, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) }; // test deposit and withdraw @@ -139,7 +172,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - &voter_authority, + voter_authority, &mngo_voting_mint, 0, LockupKind::None, @@ -170,7 +203,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - &voter_authority, + voter_authority, &mngo_voting_mint, 1, LockupKind::None, @@ -207,15 +240,15 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { // Close the empty deposit (closing deposits 1 and 2 fails) addin - .close_deposit_entry(&voter, &voter_authority, 2) + .close_deposit_entry(&voter, voter_authority, 2) .await .expect_err("deposit not in use"); addin - .close_deposit_entry(&voter, &voter_authority, 1) + .close_deposit_entry(&voter, voter_authority, 1) .await .expect_err("deposit not empty"); addin - .close_deposit_entry(&voter, &voter_authority, 0) + .close_deposit_entry(&voter, voter_authority, 0) .await .unwrap(); @@ -242,7 +275,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter2, - &voter2_authority, + voter2_authority, &mngo_voting_mint, 5, LockupKind::None, @@ -256,10 +289,13 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ®istrar, &voter2, &mngo_voting_mint, - &voter2_authority, + voter2_authority, context.users[2].token_accounts[0], 5, 1000, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) .await .unwrap(); @@ -282,7 +318,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .create_deposit_entry( ®istrar, &voter, - &voter_authority, + voter_authority, &mngo_voting_mint, 0, LockupKind::None, diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index 78ada785..b9f1ebee 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -82,8 +82,22 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter = addin - .create_voter(®istrar, &token_owner_record, voter_authority, payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; let reference_account = context.users[1].token_accounts[0]; @@ -96,6 +110,9 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { reference_account, index, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; @@ -205,8 +222,22 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter = addin - .create_voter(®istrar, &token_owner_record, voter_authority, payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; let reference_account = context.users[1].token_accounts[0]; @@ -219,6 +250,9 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> reference_account, index, amount, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) }; diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 38d851c1..d664b45e 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -19,14 +19,14 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = context @@ -66,9 +66,24 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = context .addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; // test deposit and withdraw @@ -92,16 +107,19 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await?; context .addin - .unlock_tokens(®istrar, &voter, &voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 0) .await .expect_err("fails because it's too early to unlock is invalid"); context @@ -110,10 +128,13 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ®istrar, &voter, &mngo_voting_mint, - &&context.users[1].key, + &context.users[1].key, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .expect_err("fails because it's impossible to withdraw without unlock"); @@ -133,14 +154,14 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = context @@ -180,9 +201,24 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = context .addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; // test deposit and withdraw @@ -206,10 +242,13 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await?; @@ -223,7 +262,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { // unlock is possible context .addin - .unlock_tokens(®istrar, &voter, &voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 0) .await .unwrap(); @@ -234,10 +273,13 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &&context.users[1].key, + &context.users[1].key, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .expect_err("fails because cooldown is ongoing"); @@ -257,14 +299,14 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; let voter_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = context @@ -304,9 +346,24 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = context .addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; // test deposit and withdraw @@ -330,10 +387,13 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await?; let secs_per_day = 24 * 60 * 60; @@ -345,7 +405,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran context .addin - .unlock_tokens(®istrar, &voter, &voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 0) .await .unwrap(); @@ -362,10 +422,13 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ®istrar, &voter, &mngo_voting_mint, - &&context.users[1].key, + &context.users[1].key, reference_account, 0, 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 75326766..0ce33632 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -39,7 +39,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; @@ -47,7 +47,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { let voter_authority = &context.users[1].key; let voter_mngo = context.users[1].token_accounts[0]; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let registrar = addin @@ -69,8 +69,23 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) .await; addin @@ -95,6 +110,9 @@ async fn test_log_voter_info() -> Result<(), TransportError> { voter_mngo, 0, 12000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index f5021aa6..eaad766b 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -18,7 +18,7 @@ async fn test_voting() -> Result<(), TransportError> { "testrealm", realm_authority.pubkey(), &context.mints[0], - &payer, + payer, &context.addin.program_id, ) .await; @@ -28,10 +28,10 @@ async fn test_voting() -> Result<(), TransportError> { let voter_mngo = context.users[1].token_accounts[0]; let voter_usdc = context.users[1].token_accounts[1]; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), &payer) + .create_token_owner_record(voter_authority.pubkey(), payer) .await; let token_owner_record2 = realm - .create_token_owner_record(voter2_authority.pubkey(), &payer) + .create_token_owner_record(voter2_authority.pubkey(), payer) .await; let registrar = addin @@ -68,11 +68,39 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; + let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_mining_voter = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter = addin - .create_voter(®istrar, &token_owner_record, &voter_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, + ) .await; + + let deposit_mining_voter2 = find_deposit_mining_addr( + &voter2_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); let voter2 = addin - .create_voter(®istrar, &token_owner_record2, &voter2_authority, &payer) + .create_voter( + ®istrar, + &token_owner_record2, + voter2_authority, + payer, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) .await; let mint_governance = realm @@ -80,7 +108,7 @@ async fn test_voting() -> Result<(), TransportError> { context.mints[0].pubkey.unwrap(), &context.mints[0].authority, &voter, - &voter_authority, + voter_authority, payer, addin.update_voter_weight_record_instruction(®istrar, &voter), ) @@ -108,6 +136,9 @@ async fn test_voting() -> Result<(), TransportError> { voter_mngo, 0, 499, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) .await .unwrap(); @@ -116,7 +147,7 @@ async fn test_voting() -> Result<(), TransportError> { realm .create_proposal( mint_governance.address, - &voter_authority, + voter_authority, &voter, payer, addin.update_voter_weight_record_instruction(®istrar, &voter), @@ -133,6 +164,9 @@ async fn test_voting() -> Result<(), TransportError> { voter_mngo, 0, 501, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) .await .unwrap(); @@ -141,7 +175,7 @@ async fn test_voting() -> Result<(), TransportError> { let proposal = realm .create_proposal( mint_governance.address, - &voter_authority, + voter_authority, &voter, payer, addin.update_voter_weight_record_instruction(®istrar, &voter), @@ -156,10 +190,13 @@ async fn test_voting() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - &voter_authority, + voter_authority, voter_mngo, 0, 1, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, ) .await .expect_err("could not withdraw"); @@ -186,6 +223,9 @@ async fn test_voting() -> Result<(), TransportError> { voter_mngo, 0, 750, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, ) .await .unwrap(); @@ -212,6 +252,9 @@ async fn test_voting() -> Result<(), TransportError> { voter_usdc, 1, 1000, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, ) .await .unwrap(); @@ -221,7 +264,7 @@ async fn test_voting() -> Result<(), TransportError> { mint_governance.address, &proposal, &voter2, - &voter2_authority, + voter2_authority, payer, addin.update_voter_weight_record_instruction(®istrar, &voter2), ) @@ -242,10 +285,13 @@ async fn test_voting() -> Result<(), TransportError> { ®istrar, &voter2, &mngo_voting_mint, - &voter2_authority, + voter2_authority, voter_mngo, 0, 1, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, ) .await .expect_err("could not withdraw"); @@ -256,10 +302,13 @@ async fn test_voting() -> Result<(), TransportError> { ®istrar, &voter2, &usdc_voting_mint, - &voter2_authority, + voter2_authority, voter_usdc, 1, 1, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, ) .await .unwrap(); @@ -269,7 +318,7 @@ async fn test_voting() -> Result<(), TransportError> { mint_governance.address, &proposal, voter2.token_owner_record, - &voter2_authority, + voter2_authority, payer.pubkey(), ) .await @@ -281,10 +330,13 @@ async fn test_voting() -> Result<(), TransportError> { ®istrar, &voter2, &mngo_voting_mint, - &voter2_authority, + voter2_authority, voter_mngo, 0, 750, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, ) .await .unwrap(); From 0da37d153ef42bce7cfdf7b01494bcbd397799c1 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Thu, 30 May 2024 22:02:11 +0300 Subject: [PATCH 10/59] CPI invocations for instructions --- .../src/cpi_instructions.rs | 38 ++--- .../src/instructions/create_voter.rs | 1 - .../src/instructions/deposit.rs | 16 +- .../src/instructions/withdraw.rs | 145 ++++++++---------- 4 files changed, 86 insertions(+), 114 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index e3cd0d58..2233100a 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -77,6 +77,8 @@ pub enum RewardsInstruction { lockup_period: LockupPeriod, /// Specifies mint addr reward_mint_addr: Pubkey, + /// Specifies the owner of the Mining Account + owner: Pubkey, }, /// Withdraws amount of supply to the mining account @@ -89,6 +91,8 @@ pub enum RewardsInstruction { WithdrawMining { /// Amount to withdraw amount: u64, + /// Owner of Mining Account + owner: Pubkey, }, /// Claims amount of rewards @@ -159,17 +163,15 @@ pub fn deposit_mining<'a>( program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, - user: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, amount: u64, lockup_period: LockupPeriod, - signers_seeds: &[&[&[u8]]], - reward_mint: &Pubkey, + reward_mint_addr: &Pubkey, + owner: &Pubkey, ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(user.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), ]; @@ -178,16 +180,13 @@ pub fn deposit_mining<'a>( &RewardsInstruction::DepositMining { amount, lockup_period, - reward_mint_addr: *reward_mint, + reward_mint_addr: *reward_mint_addr, + owner: *owner, }, accounts, ); - invoke_signed( - &ix, - &[reward_pool, mining, user, deposit_authority, program_id], - signers_seeds, - ) + invoke(&ix, &[reward_pool, mining, deposit_authority, program_id]) } /// Restake deposit @@ -234,30 +233,27 @@ pub fn extend_deposit<'a>( /// Rewards withdraw mining #[allow(clippy::too_many_arguments)] pub fn withdraw_mining<'a>( - program_id: &Pubkey, + program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, - user: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, amount: u64, - signers_seeds: &[&[&[u8]]], + owner: &Pubkey, ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(user.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), ]; let ix = Instruction::new_with_borsh( - *program_id, - &RewardsInstruction::WithdrawMining { amount }, + program_id.key(), + &RewardsInstruction::WithdrawMining { + amount, + owner: *owner, + }, accounts, ); - invoke_signed( - &ix, - &[reward_pool, mining, user, deposit_authority], - signers_seeds, - ) + invoke(&ix, &[reward_pool, mining, deposit_authority, program_id]) } diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 6cb9b712..c59ddf0b 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -113,7 +113,6 @@ pub fn create_voter( // initialize Mining account for Voter let mining = ctx.accounts.deposit_mining.to_account_info(); let payer = ctx.accounts.payer.to_account_info(); - // let voter = ctx.accounts.voter.to_account_info(); let user = ctx.accounts.voter_authority.to_account_info(); let system_program = ctx.accounts.system_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index b0786671..e5f47cdd 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -4,7 +4,6 @@ use mplx_staking_states::error::*; use mplx_staking_states::state::*; use crate::cpi_instructions; -use crate::cpi_instructions::REWARD_CONTRACT_ID; #[derive(Accounts)] pub struct Deposit<'info> { @@ -97,29 +96,18 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R let deposit_authority = &ctx.accounts.deposit_authority; let reward_mint = &ctx.accounts.deposit_token.mint; let voter = &ctx.accounts.voter.load()?; + let owner = &voter.voter_authority; let d_entry = voter.active_deposit(deposit_entry_index)?; - let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( - &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], - &REWARD_CONTRACT_ID, - ); - - let signers_seeds = &[ - &reward_pool.key().to_bytes()[..32], - &reward_mint.key().to_bytes()[..32], - &[pool_bump_seed], - ]; - cpi_instructions::deposit_mining( ctx.accounts.rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), - ctx.accounts.voter.to_account_info(), deposit_authority.to_account_info(), amount, d_entry.lockup.period, - &[signers_seeds], reward_mint, + owner, )?; msg!( diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 30595ce0..f3689db5 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,4 +1,4 @@ -use crate::cpi_instructions::{withdraw_mining, REWARD_CONTRACT_ID}; +use crate::cpi_instructions::withdraw_mining; use crate::voter::{load_token_owner_record, VoterWeightRecord}; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; @@ -84,7 +84,6 @@ impl<'info> Withdraw<'info> { /// `deposit_entry_index`: The deposit entry to withdraw from. /// `amount` is in units of the native currency being withdrawn. pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { - // NOTE: that block is mandatory because otherwise runtime borrow checking will fail! { // Transfer the tokens to withdraw. let voter = &mut ctx.accounts.voter.load()?; @@ -94,102 +93,92 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> amount, )?; } - - // Load the accounts. - let registrar = &ctx.accounts.registrar.load()?; - let voter = &mut ctx.accounts.voter.load_mut()?; - - // Get the exchange rate for the token being withdrawn. - let mint_idx = registrar.voting_mint_config_index(ctx.accounts.destination.mint)?; - - // Governance may forbid withdraws, for example when engaged in a vote. - // Not applicable for tokens that don't contribute to voting power. - if registrar.voting_mints[mint_idx].grants_vote_weight() { - let token_owner_record = load_token_owner_record( - &voter.voter_authority, - &ctx.accounts.token_owner_record.to_account_info(), - registrar, - )?; - token_owner_record.assert_can_withdraw_governing_tokens()?; - } - - // Get the deposit being withdrawn from. - let curr_ts = registrar.clock_unix_timestamp(); - let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?; - - // check whether funds are cooled down - if deposit_entry.lockup.kind == LockupKind::Constant { - require!( - deposit_entry.lockup.cooldown_requested, - VsrError::UnlockMustBeCalledFirst + { + // Load the accounts. + let registrar = &ctx.accounts.registrar.load()?; + let voter = &mut ctx.accounts.voter.load_mut()?; + + // Get the exchange rate for the token being withdrawn. + let mint_idx = registrar.voting_mint_config_index(ctx.accounts.destination.mint)?; + + // Governance may forbid withdraws, for example when engaged in a vote. + // Not applicable for tokens that don't contribute to voting power. + if registrar.voting_mints[mint_idx].grants_vote_weight() { + let token_owner_record = load_token_owner_record( + &voter.voter_authority, + &ctx.accounts.token_owner_record.to_account_info(), + registrar, + )?; + token_owner_record.assert_can_withdraw_governing_tokens()?; + } + + // Get the deposit being withdrawn from. + let curr_ts = registrar.clock_unix_timestamp(); + let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?; + + // check whether funds are cooled down + if deposit_entry.lockup.kind == LockupKind::Constant { + require!( + deposit_entry.lockup.cooldown_requested, + VsrError::UnlockMustBeCalledFirst + ); + require!( + curr_ts >= deposit_entry.lockup.cooldown_ends_at, + VsrError::InvalidTimestampArguments + ); + } + + require_gte!( + deposit_entry.amount_unlocked(curr_ts), + amount, + VsrError::InsufficientUnlockedTokens ); - require!( - curr_ts >= deposit_entry.lockup.cooldown_ends_at, - VsrError::InvalidTimestampArguments + require_eq!( + mint_idx, + deposit_entry.voting_mint_config_idx as usize, + VsrError::InvalidMint ); - } - require_gte!( - deposit_entry.amount_unlocked(curr_ts), - amount, - VsrError::InsufficientUnlockedTokens - ); - require_eq!( - mint_idx, - deposit_entry.voting_mint_config_idx as usize, - VsrError::InvalidMint - ); - - // Bookkeeping for withdrawn funds. - require_gte!( - deposit_entry.amount_deposited_native, - amount, - VsrError::InternalProgramError - ); + // Bookkeeping for withdrawn funds. + require_gte!( + deposit_entry.amount_deposited_native, + amount, + VsrError::InternalProgramError + ); - deposit_entry.amount_deposited_native = deposit_entry - .amount_deposited_native - .checked_sub(amount) - .unwrap(); + deposit_entry.amount_deposited_native = deposit_entry + .amount_deposited_native + .checked_sub(amount) + .unwrap(); - msg!( - "Withdrew amount {} at deposit index {} with lockup kind {:?} and {} seconds left", - amount, - deposit_entry_index, - deposit_entry.lockup.kind, - deposit_entry.lockup.seconds_left(curr_ts), - ); + msg!( + "Withdrew amount {} at deposit index {} with lockup kind {:?} and {} seconds left", + amount, + deposit_entry_index, + deposit_entry.lockup.kind, + deposit_entry.lockup.seconds_left(curr_ts), + ); + } // Update the voter weight record + let voter = &ctx.accounts.voter.load()?; let record = &mut ctx.accounts.voter_weight_record; record.voter_weight = voter.weight()?; record.voter_weight_expiry = Some(Clock::get()?.slot); + let rewards_program = &ctx.accounts.rewards_program; let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let deposit_authority = &ctx.accounts.voter_authority; - let reward_mint = &ctx.accounts.destination.mint; - let voter = &ctx.accounts.voter; - - let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( - &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], - &REWARD_CONTRACT_ID, - ); - - let signers_seeds = &[ - &reward_pool.key().to_bytes()[..32], - &reward_mint.key().to_bytes()[..32], - &[pool_bump_seed], - ]; + let owner = &ctx.accounts.voter_authority; withdraw_mining( - &REWARD_CONTRACT_ID, + rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), - voter.to_account_info(), deposit_authority.to_account_info(), amount, - &[signers_seeds], + owner.key, )?; Ok(()) From 7818d9e6bc2c0fa9cdfb47a9d3abcfc4edfbfc55 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Thu, 30 May 2024 22:03:03 +0300 Subject: [PATCH 11/59] restring votingMintConfig with the only one value --- program-states/src/state/registrar.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/program-states/src/state/registrar.rs b/program-states/src/state/registrar.rs index aa6cee82..cbcdc4c3 100644 --- a/program-states/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -15,13 +15,13 @@ pub struct Registrar { /// Storage for voting mints and their configuration. /// The length should be adjusted for one's use case. - pub voting_mints: [VotingMintConfig; 4], + pub voting_mints: [VotingMintConfig; 1], /// Debug only: time offset, to allow tests to move forward in time. pub time_offset: i64, pub bump: u8, } -const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 4 * 96 + 8 + 1); +const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 96 + 8 + 1); const_assert!(std::mem::size_of::() % 8 == 0); pub const REGISTRAR_DISCRIMINATOR: [u8; 8] = [193, 202, 205, 51, 78, 168, 150, 128]; From 754e0b602a649797dda248c3e17dd2c7c6f2568c Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Thu, 30 May 2024 22:03:37 +0300 Subject: [PATCH 12/59] update integration tests accordingly to the newest changes in processor --- .../tests/fixtures/mplx_rewards.so | Bin 273600 -> 279680 bytes .../tests/program_test/addin.rs | 6 +- .../tests/program_test/rewards.rs | 16 ++-- .../tests/program_test/utils.rs | 23 +----- .../tests/rewards_invocations.rs | 76 +++++++++--------- .../tests/test_all_deposits.rs | 9 ++- .../voter-stake-registry/tests/test_basic.rs | 52 +++++++----- .../tests/test_deposit_constant.rs | 24 +++++- .../tests/test_deposit_no_locking.rs | 20 +++-- .../tests/test_internal_transfers.rs | 24 +++++- .../voter-stake-registry/tests/test_lockup.rs | 33 ++++++-- .../tests/test_log_voter_info.rs | 15 +++- .../voter-stake-registry/tests/test_voting.rs | 9 ++- 13 files changed, 194 insertions(+), 113 deletions(-) diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index c2ffc9bccd86c1fdbafefa6a339c92701e9a9c54..f59195d4706eed90dd389d81b0c76fcbc2153784 100755 GIT binary patch delta 51853 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 delta 40937 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 diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index f198d130..dde95311 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -332,7 +332,7 @@ impl AddinCookie { registrar: &RegistrarCookie, voter: &VoterCookie, voting_mint: &VotingMintConfigCookie, - authority: &Keypair, + deposit_authority: &Keypair, token_address: Pubkey, deposit_entry_index: u8, amount: u64, @@ -354,7 +354,7 @@ impl AddinCookie { voter: voter.address, vault, deposit_token: token_address, - deposit_authority: authority.pubkey(), + deposit_authority: deposit_authority.pubkey(), token_program: spl_token::id(), reward_pool: *reward_pool, deposit_mining: *deposit_mining, @@ -370,7 +370,7 @@ impl AddinCookie { }]; // clone the secrets - let signer = Keypair::from_base58_string(&authority.to_base58_string()); + let signer = Keypair::from_base58_string(&deposit_authority.to_base58_string()); self.solana .process_transaction(&instructions, Some(&[&signer])) diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 1fd5d78c..7d66eb62 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -52,19 +52,19 @@ impl RewardsCookie { pub async fn initialize_pool( &self, - rewards_root: &Keypair, - deposit_authority: &Keypair, + rewards_root: &Pubkey, + deposit_authority: &Pubkey, payer: &Keypair, ) -> std::result::Result { let (reward_pool, _bump) = Pubkey::find_program_address( - &["reward_pool".as_bytes(), &rewards_root.pubkey().to_bytes()], + &["reward_pool".as_bytes(), &rewards_root.key().to_bytes()], &self.program_id, ); let accounts = vec![ - AccountMeta::new_readonly(rewards_root.pubkey(), false), + AccountMeta::new_readonly(rewards_root.key(), false), AccountMeta::new(reward_pool, false), - AccountMeta::new_readonly(deposit_authority.pubkey(), false), + AccountMeta::new_readonly(deposit_authority.key(), false), AccountMeta::new(payer.pubkey(), true), AccountMeta::new_readonly(system_program::id(), false), ]; @@ -199,16 +199,16 @@ impl RewardsCookie { pub async fn deposit_mining<'a>( &self, reward_pool: &Pubkey, - user: &Pubkey, deposit_authority: &Keypair, amount: u64, lockup_period: LockupPeriod, mint_account: &Pubkey, + owner: &Pubkey, ) -> std::result::Result<(), BanksClientError> { let (mining, _bump) = Pubkey::find_program_address( &[ "mining".as_bytes(), - &user.key().to_bytes(), + &owner.key().to_bytes(), &reward_pool.key().to_bytes(), ], &self.program_id, @@ -217,7 +217,6 @@ impl RewardsCookie { let accounts = vec![ AccountMeta::new(*reward_pool, false), AccountMeta::new(mining, false), - AccountMeta::new_readonly(*user, false), AccountMeta::new_readonly(deposit_authority.pubkey(), true), ]; @@ -227,6 +226,7 @@ impl RewardsCookie { amount, lockup_period, reward_mint_addr: *mint_account, + owner: *owner, }, accounts, ); diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index 2f4d6304..ec4df82f 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -77,32 +77,17 @@ pub async fn create_mint( pub async fn initialize_rewards_contract( payer: &Keypair, context: &TestContext, + reward_mint: &Pubkey, + deposit_authority: &Pubkey, ) -> Result { - // create token mint - let reward_mint = Keypair::new(); - let manager = &payer.pubkey(); - create_mint( - &mut context.solana.context.borrow_mut(), - &reward_mint, - manager, - ) - .await - .unwrap(); - let rewards_root = context.rewards.initialize_root(payer).await?; - let deposit_authority = Keypair::new(); let rewards_pool = context .rewards - .initialize_pool(&rewards_root, &deposit_authority, payer) + .initialize_pool(&rewards_root.pubkey(), deposit_authority, payer) .await?; let _vault = context .rewards - .add_vault( - &rewards_root.pubkey(), - &rewards_pool, - &reward_mint.pubkey(), - payer, - ) + .add_vault(&rewards_root.pubkey(), &rewards_pool, reward_mint, payer) .await?; Ok(rewards_pool) diff --git a/programs/voter-stake-registry/tests/rewards_invocations.rs b/programs/voter-stake-registry/tests/rewards_invocations.rs index ffa62ff9..52687c2a 100644 --- a/programs/voter-stake-registry/tests/rewards_invocations.rs +++ b/programs/voter-stake-registry/tests/rewards_invocations.rs @@ -30,47 +30,47 @@ pub async fn initialize_root() -> std::result::Result<(), TransportError> { // just run transaction to make sure they work #[tokio::test] pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError> { - let context = TestContext::new().await; - let payer = &context.users[0].key; + // let context = TestContext::new().await; + // let payer = &context.users[0].key; + + // // create token mint + // let reward_mint = Keypair::new(); + // let manager = &payer.pubkey(); + // create_mint( + // &mut context.solana.context.borrow_mut(), + // &reward_mint, + // manager, + // ) + // .await + // .unwrap(); + + // let rewards_root_kp = context.rewards.initialize_root(payer).await?; + + // let deposit_authority = Keypair::new(); + // let rewards_pool = context + // .rewards + // .initialize_pool(&rewards_root_kp, &deposit_authority, payer) + // .await?; + + // let _vault = context + // .rewards + // .add_vault( + // &rewards_root_kp.pubkey(), + // &rewards_pool, + // &reward_mint.pubkey(), + // payer, + // ) + // .await?; - // create token mint - let reward_mint = Keypair::new(); - let manager = &payer.pubkey(); - create_mint( - &mut context.solana.context.borrow_mut(), - &reward_mint, - manager, - ) - .await - .unwrap(); + // let user = Keypair::new(); - let rewards_root_kp = context.rewards.initialize_root(payer).await?; + // let _mining = context + // .rewards + // .initialize_mining(&rewards_pool, &user.pubkey(), payer) + // .await?; - let deposit_authority = Keypair::new(); - let rewards_pool = context - .rewards - .initialize_pool(&rewards_root_kp, &deposit_authority, payer) - .await?; - - let _vault = context - .rewards - .add_vault( - &rewards_root_kp.pubkey(), - &rewards_pool, - &reward_mint.pubkey(), - payer, - ) - .await?; - - let user = Keypair::new(); - - let _mining = context - .rewards - .initialize_mining(&rewards_pool, &user.pubkey(), payer) - .await?; - - let amount = 1; - let lockup_period = LockupPeriod::ThreeMonths; + // let amount = 1; + // let lockup_period = LockupPeriod::ThreeMonths; // context // .rewards // .deposit_mining( diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 49963503..59a7a0ef 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -48,7 +48,14 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = voter_authority; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 1d0040d5..b5d65085 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -2,7 +2,7 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use mplx_staking_states::state::{LockupKind, LockupPeriod, SECS_PER_DAY}; use program_test::*; mod program_test; @@ -24,9 +24,9 @@ async fn test_basic() -> Result<(), TransportError> { ) .await; - let voter_authority = &context.users[1].key; + let deposit_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let registrar = context @@ -66,7 +66,16 @@ async fn test_basic() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -103,9 +112,9 @@ async fn test_basic() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, - LockupKind::None, + LockupKind::Constant, None, - LockupPeriod::None, + LockupPeriod::ThreeMonths, ) .await?; @@ -115,7 +124,7 @@ async fn test_basic() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - voter_authority, + deposit_authority, reference_account, 0, 10000, @@ -139,20 +148,19 @@ async fn test_basic() -> Result<(), TransportError> { context .addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - &context.users[2].key, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) + .set_time_offset(®istrar, &realm_authority, 100 * 86400) + .await; + + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 0) .await - .expect_err("fails because voter_authority is invalid"); + .unwrap(); + + context + .addin + .set_time_offset(®istrar, &realm_authority, 106 * 86400) + .await; context .addin @@ -160,7 +168,7 @@ async fn test_basic() -> Result<(), TransportError> { ®istrar, &voter, &mngo_voting_mint, - voter_authority, + deposit_authority, reference_account, 0, 10000, @@ -191,7 +199,7 @@ async fn test_basic() -> Result<(), TransportError> { .await?; context .addin - .close_voter(®istrar, &voter, &mngo_voting_mint, voter_authority) + .close_voter(®istrar, &voter, &mngo_voting_mint, &voter_authority) .await?; let lamports_after = context .solana diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 3d26b4ba..bda0639c 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -59,9 +59,9 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ) .await; - let voter_authority = &context.users[1].key; + let deposit_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let registrar = addin @@ -83,7 +83,16 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -240,7 +249,14 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = &context.users[1].key; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 875ea878..ec2d4e07 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -60,13 +60,13 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; - let voter_authority = &context.users[1].key; - let voter2_authority = &context.users[2].key; + let deposit_authority = &context.users[1].key; + let deposit2_authority = &context.users[2].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let token_owner_record2 = realm - .create_token_owner_record(voter2_authority.pubkey(), payer) + .create_token_owner_record(deposit2_authority.pubkey(), payer) .await; let registrar = addin @@ -88,7 +88,16 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining_voter = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -106,6 +115,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; + let voter2_authority = deposit2_authority; let deposit_mining_voter2 = find_deposit_mining_addr( &voter2_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index b9f1ebee..9acb3fe6 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -58,9 +58,9 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { ) .await; - let voter_authority = &context.users[1].key; + let deposit_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let registrar = addin @@ -82,7 +82,16 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -222,7 +231,14 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = &mngo_voting_mint.mint.authority; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index d664b45e..2d4e650b 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -24,9 +24,9 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ) .await; - let voter_authority = &context.users[1].key; + let deposit_authority = &context.users[1].key; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let registrar = context @@ -66,7 +66,16 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -201,7 +210,14 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = &mngo_voting_mint.mint.authority; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -346,7 +362,14 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = &mngo_voting_mint.mint.authority; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 0ce33632..569b7663 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -44,10 +44,10 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; - let voter_authority = &context.users[1].key; + let deposit_authority = &context.users[1].key; let voter_mngo = context.users[1].token_accounts[0]; let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) + .create_token_owner_record(deposit_authority.pubkey(), payer) .await; let registrar = addin @@ -69,7 +69,16 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index eaad766b..6166ff04 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -68,7 +68,14 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract(payer, &context).await?; + let deposit_authority = &mngo_voting_mint.mint.authority; + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; let deposit_mining_voter = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, From 9483a9f394f4fae902c4c22208ac87aa31847c2c Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 14:25:22 +0300 Subject: [PATCH 13/59] add new time warp util && make test_all_deposits green --- .../tests/program_test/utils.rs | 19 +++++++++++++++++ .../tests/test_all_deposits.rs | 21 ++++++++++++------- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index ec4df82f..13c33672 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,3 +1,5 @@ +use std::borrow::BorrowMut; + use bytemuck::{bytes_of, Contiguous}; use solana_program::program_error::ProgramError; use solana_program_test::{BanksClientError, ProgramTestContext}; @@ -108,3 +110,20 @@ pub fn find_deposit_mining_addr( ); deposit_mining } + +pub async fn advance_clock_by_ts(context: &mut ProgramTestContext, ts: i64) { + let old_clock = context + .banks_client + .get_sysvar::() + .await + .unwrap(); + + let initial_slot = context.banks_client.get_root_slot().await.unwrap(); + context + .warp_to_slot(initial_slot + (ts / 2) as u64) + .unwrap(); + + let mut new_clock = old_clock.clone(); + new_clock.unix_timestamp += ts; + context.borrow_mut().set_sysvar(&new_clock); +} diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 59a7a0ef..7ff31e6a 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -82,9 +82,9 @@ async fn test_all_deposits() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, i, - LockupKind::None, + LockupKind::Constant, None, - LockupPeriod::None, + LockupPeriod::ThreeMonths, ) .await .unwrap(); @@ -106,12 +106,9 @@ async fn test_all_deposits() -> Result<(), TransportError> { } // advance time, to be in the middle of all deposit lockups - addin - .set_time_offset(®istrar, &realm_authority, 32 * 24 * 60 * 60) - .await; - context.solana.advance_clock_by_slots(2).await; + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 45 * 86400).await; - // the two most expensive calls which scale with number of deposts + // the two most expensive calls which scale with number of deposits // are update_voter_weight_record and withdraw - both compute the vote weight let vwr = addin @@ -120,6 +117,16 @@ async fn test_all_deposits() -> Result<(), TransportError> { .unwrap(); assert_eq!(vwr.voter_weight, 12000 * 32); + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 50 * 86400).await; + + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 0) + .await + .unwrap(); + + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; + // make sure withdrawing works with all deposits filled addin .withdraw( From 47f86fa0a4726e23e2671c09b3a83c91463580b1 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 19:18:20 +0300 Subject: [PATCH 14/59] add more money for the current deposit is forbidden now --- .../src/instructions/deposit.rs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index e5f47cdd..2cff8291 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -70,12 +70,17 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R if amount == 0 { return Ok(()); } + let registrar = &ctx.accounts.registrar.load()?; let curr_ts = registrar.clock_unix_timestamp(); { let voter = &mut ctx.accounts.voter.load_mut()?; let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + require!( + d_entry.amount_deposited_native == 0, + VsrError::DepositingIsForbidded + ); // Get the exchange rate entry associated with this deposit. let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; @@ -84,20 +89,26 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R d_entry.voting_mint_config_idx as usize, VsrError::InvalidMint ); - // Deposit tokens into the vault and increase the lockup amount too. + // Deposit tokens into the vault and increase the lockup amount too. token::transfer(ctx.accounts.transfer_ctx(), amount)?; d_entry.amount_deposited_native = d_entry.amount_deposited_native.checked_add(amount).unwrap(); } + let voter = &ctx.accounts.voter.load()?; + let owner = &voter.voter_authority; + let d_entry = voter.active_deposit(deposit_entry_index)?; + + // no sense to call cpi if lockup had not been staked + if d_entry.lockup.kind == LockupKind::None || d_entry.lockup.period == LockupPeriod::None { + return Ok(()); + } + let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let deposit_authority = &ctx.accounts.deposit_authority; let reward_mint = &ctx.accounts.deposit_token.mint; - let voter = &ctx.accounts.voter.load()?; - let owner = &voter.voter_authority; - let d_entry = voter.active_deposit(deposit_entry_index)?; cpi_instructions::deposit_mining( ctx.accounts.rewards_program.to_account_info(), From 503652d20c4903538b955a0faa1e56a920ef9554 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 19:19:08 +0300 Subject: [PATCH 15/59] no CPI is needed when LockupKind and LockupType are None --- programs/voter-stake-registry/src/instructions/withdraw.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index f3689db5..17b9bf2d 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -158,6 +158,12 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> deposit_entry.lockup.kind, deposit_entry.lockup.seconds_left(curr_ts), ); + + if deposit_entry.lockup.kind == LockupKind::None + && deposit_entry.lockup.period == LockupPeriod::None + { + return Ok(()); + } } // Update the voter weight record From c89d32a9bb123bedb26eeb8ad99edcad12534dc5 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 19:20:02 +0300 Subject: [PATCH 16/59] add new error --- program-states/src/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/program-states/src/error.rs b/program-states/src/error.rs index e6cfea97..84668a2c 100644 --- a/program-states/src/error.rs +++ b/program-states/src/error.rs @@ -125,4 +125,7 @@ pub enum VsrError { // 6040 / 0x1798 #[msg("")] RestakeDepositIsNotAllowed, + // 6041 / 0x1799 + #[msg("To deposit additional tokens, extend the deposit")] + DepositingIsForbidded, } From dbf2c444fdf3c67c7f6663f980fedcd2145a2469 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 19:20:14 +0300 Subject: [PATCH 17/59] fix integration tests --- program-states/src/state/registrar.rs | 4 +-- .../tests/test_deposit_no_locking.rs | 25 +++++--------- .../tests/test_internal_transfers.rs | 14 ++++---- .../voter-stake-registry/tests/test_lockup.rs | 4 +-- .../voter-stake-registry/tests/test_voting.rs | 33 ++----------------- 5 files changed, 21 insertions(+), 59 deletions(-) diff --git a/program-states/src/state/registrar.rs b/program-states/src/state/registrar.rs index cbcdc4c3..8a780b58 100644 --- a/program-states/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -15,13 +15,13 @@ pub struct Registrar { /// Storage for voting mints and their configuration. /// The length should be adjusted for one's use case. - pub voting_mints: [VotingMintConfig; 1], + pub voting_mints: [VotingMintConfig; 2], /// Debug only: time offset, to allow tests to move forward in time. pub time_offset: i64, pub bump: u8, } -const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 96 + 8 + 1); +const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 2 * 96 + 8 + 1); const_assert!(std::mem::size_of::() % 8 == 0); pub const REGISTRAR_DISCRIMINATOR: [u8; 8] = [193, 202, 205, 51, 78, 168, 150, 128]; diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index ec2d4e07..72eb63b5 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -191,22 +191,13 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await .unwrap(); - deposit(0, 10000).await.unwrap(); + deposit(0, 15000).await.unwrap(); let after_deposit = get_balances(0).await; assert_eq!(token, after_deposit.token + after_deposit.vault); assert_eq!(after_deposit.voter_weight, after_deposit.vault); - assert_eq!(after_deposit.vault, 10000); - assert_eq!(after_deposit.deposit, 10000); - - // add to the existing deposit 0 - deposit(0, 5000).await.unwrap(); - - let after_deposit2 = get_balances(0).await; - assert_eq!(token, after_deposit2.token + after_deposit2.vault); - assert_eq!(after_deposit2.voter_weight, after_deposit2.vault); - assert_eq!(after_deposit2.vault, 15000); - assert_eq!(after_deposit2.deposit, 15000); + assert_eq!(after_deposit.vault, 15000); + assert_eq!(after_deposit.deposit, 15000); // create a separate deposit (index 1) addin @@ -224,11 +215,11 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .unwrap(); deposit(1, 7000).await.unwrap(); - let after_deposit3 = get_balances(1).await; - assert_eq!(token, after_deposit3.token + after_deposit3.vault); - assert_eq!(after_deposit3.voter_weight, after_deposit3.vault); - assert_eq!(after_deposit3.vault, 22000); - assert_eq!(after_deposit3.deposit, 7000); + let after_deposit2 = get_balances(1).await; + assert_eq!(token, after_deposit2.token + after_deposit2.vault); + assert_eq!(after_deposit2.voter_weight, after_deposit2.vault); + assert_eq!(after_deposit2.vault, 22000); + assert_eq!(after_deposit2.deposit, 7000); withdraw(10000).await.unwrap(); diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index 9acb3fe6..b8373e89 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -222,16 +222,16 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> payer, 0, &context.mints[0], - 0, // dump values, they doen't matter - 1.0, // dump values, they doen't matter - 1.0, // dump values, they doen't matter - 1, // dump values, they doen't matter - None, // dump values, they doen't matter - None, // dump values, they doen't matter + 0, // dump values, they don't matter + 1.0, // dump values, they don't matter + 1.0, // dump values, they don't matter + 1, // dump values, they don't matter + None, // dump values, they don't matter + None, // dump values, they don't matter ) .await; - let deposit_authority = &mngo_voting_mint.mint.authority; + let deposit_authority = &voter_authority; let rewards_pool = initialize_rewards_contract( payer, &context, diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 2d4e650b..2727a5ae 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -210,7 +210,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; - let deposit_authority = &mngo_voting_mint.mint.authority; + let deposit_authority = &voter_authority; let rewards_pool = initialize_rewards_contract( payer, &context, @@ -362,7 +362,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; - let deposit_authority = &mngo_voting_mint.mint.authority; + let deposit_authority = &voter_authority; let rewards_pool = initialize_rewards_contract( payer, &context, diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 6166ff04..cf4d01d0 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -68,7 +68,7 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let deposit_authority = &mngo_voting_mint.mint.authority; + let deposit_authority = &voter_authority; let rewards_pool = initialize_rewards_contract( payer, &context, @@ -142,42 +142,13 @@ async fn test_voting() -> Result<(), TransportError> { voter_authority, voter_mngo, 0, - 499, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, - ) - .await - .unwrap(); - - // need vote weight of 1000, but only have 499 - realm - .create_proposal( - mint_governance.address, - voter_authority, - &voter, - payer, - addin.update_voter_weight_record_instruction(®istrar, &voter), - ) - .await - .expect_err("not enough tokens to create proposal"); - - addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 501, + 1000, &rewards_pool, &deposit_mining_voter, &context.rewards.program_id, ) .await .unwrap(); - context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again let proposal = realm .create_proposal( From 14f39911501a483b0b5b14b3dbdab8eac6db8d44 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 31 May 2024 20:44:39 +0300 Subject: [PATCH 18/59] add claim fucntion --- .../src/cpi_instructions.rs | 39 ++++++++++++ .../src/instructions/claim.rs | 61 +++++++++++++++++++ .../src/instructions/mod.rs | 1 + 3 files changed, 101 insertions(+) create mode 100644 programs/voter-stake-registry/src/instructions/claim.rs diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 2233100a..f3ce2844 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -257,3 +257,42 @@ pub fn withdraw_mining<'a>( invoke(&ix, &[reward_pool, mining, deposit_authority, program_id]) } + +/// Rewards withdraw mining +#[allow(clippy::too_many_arguments)] +pub fn claim<'a>( + program_id: AccountInfo<'a>, + reward_pool: AccountInfo<'a>, + reward_mint: AccountInfo<'a>, + vault: AccountInfo<'a>, + mining: AccountInfo<'a>, + user: AccountInfo<'a>, + user_reward_token_account: AccountInfo<'a>, + token_program: AccountInfo<'a>, +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new_readonly(reward_pool.key(), false), + AccountMeta::new_readonly(reward_mint.key(), false), + AccountMeta::new(vault.key(), false), + AccountMeta::new(mining.key(), false), + AccountMeta::new(user.key(), true), + AccountMeta::new(user_reward_token_account.key(), false), + AccountMeta::new_readonly(token_program.key(), false), + ]; + + let ix = Instruction::new_with_borsh(program_id.key(), &RewardsInstruction::Claim, accounts); + + invoke( + &ix, + &[ + reward_pool, + reward_mint, + vault, + mining, + user, + user_reward_token_account, + token_program, + program_id, + ], + ) +} diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs new file mode 100644 index 00000000..b6c73d5d --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -0,0 +1,61 @@ +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use anchor_spl::token::{Token, TokenAccount}; + +use crate::cpi_instructions; + +#[derive(Accounts)] +pub struct Claim<'info> { + /// CHECK: Reward Pool PDA will be checked in the rewards contract + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + + pub reward_mint: Account<'info, Mint>, + + #[account(mut)] + pub vault: UncheckedAccount<'info>, + + /// CHECK: mining PDA will be checked in the rewards contract + #[account(mut)] + pub deposit_mining: UncheckedAccount<'info>, + + // pub voter_authority: Signer<'info>, + pub owner: Signer<'info>, + + #[account(mut)] + pub user_reward_token_account: Account<'info, TokenAccount>, + + pub token_program: Program<'info, Token>, + + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, +} + +/// Claims token from the Rewards Contract. +/// +/// Tokens will be transfered from Vault in Rewards account to User's user_reward_token_account. +/// This call actually doesn't mutating Staking's accounts, only Reward's accounts will be mutated. +pub fn claim(ctx: Context) -> Result<()> { + let rewards_program = ctx.accounts.rewards_program.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let rewards_mint = ctx.accounts.reward_mint.to_account_info(); + let vault = ctx.accounts.vault.to_account_info(); + let deposit_mining = ctx.accounts.deposit_mining.to_account_info(); + let user = ctx.accounts.owner.to_account_info(); + let user_reward_token_account = ctx.accounts.user_reward_token_account.to_account_info(); + let token_program = ctx.accounts.token_program.to_account_info(); + + cpi_instructions::claim( + rewards_program, + reward_pool, + rewards_mint, + vault, + deposit_mining, + user, + user_reward_token_account, + token_program, + )?; + + // TODO: add msg about claimed amount, getting use of return_data function + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 918778be..cccbc5f3 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -12,6 +12,7 @@ pub use unlock_tokens::*; pub use update_voter_weight_record::*; pub use withdraw::*; +mod claim; mod close_deposit_entry; mod close_voter; mod configure_voting_mint; From 890b4c9904f791bbc57011e97dc39133180fb7e9 Mon Sep 17 00:00:00 2001 From: Kyryo Stepanov Date: Mon, 3 Jun 2024 20:56:40 +0300 Subject: [PATCH 19/59] update claim function && small refactor --- programs/voter-stake-registry/src/instructions/claim.rs | 6 +++--- .../voter-stake-registry/src/instructions/log_voter_info.rs | 4 ++-- programs/voter-stake-registry/src/instructions/mod.rs | 1 + programs/voter-stake-registry/src/lib.rs | 4 ++++ 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index b6c73d5d..38e52639 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -1,5 +1,4 @@ use anchor_lang::prelude::*; -use anchor_spl::token::Mint; use anchor_spl::token::{Token, TokenAccount}; use crate::cpi_instructions; @@ -7,10 +6,10 @@ use crate::cpi_instructions; #[derive(Accounts)] pub struct Claim<'info> { /// CHECK: Reward Pool PDA will be checked in the rewards contract - #[account(mut)] pub reward_pool: UncheckedAccount<'info>, - pub reward_mint: Account<'info, Mint>, + /// CHECK: Rewards mint addr will be checked in the rewards contract + pub reward_mint: UncheckedAccount<'info>, #[account(mut)] pub vault: UncheckedAccount<'info>, @@ -20,6 +19,7 @@ pub struct Claim<'info> { pub deposit_mining: UncheckedAccount<'info>, // pub voter_authority: Signer<'info>, + #[account(mut)] pub owner: Signer<'info>, #[account(mut)] diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index d7199916..dca58eb0 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -44,11 +44,11 @@ pub fn log_voter_info( } let lockup = &deposit.lockup; let seconds_left = lockup.seconds_left(curr_ts); - let end_ts = curr_ts as u64 + seconds_left; + let end_ts = curr_ts + seconds_left; let voting_mint_config = ®istrar.voting_mints[deposit.voting_mint_config_idx as usize]; let locking_info = (seconds_left > 0).then(|| LockingInfo { amount: deposit.amount_locked(curr_ts), - end_timestamp: (lockup.kind != LockupKind::Constant).then(|| end_ts), + end_timestamp: (lockup.kind != LockupKind::Constant).then_some(end_ts), vesting: None, }); diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index cccbc5f3..5527c83f 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,3 +1,4 @@ +pub use claim::*; pub use close_deposit_entry::*; pub use close_voter::*; pub use configure_voting_mint::*; diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 5f4838be..d6604f2f 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -159,4 +159,8 @@ pub mod voter_stake_registry { ) -> Result<()> { instructions::restake_deposit(ctx, deposit_entry_index, lockup_period) } + + pub fn claim(ctx: Context) -> Result<()> { + instructions::claim(ctx) + } } From 143e062810a5948aa74eda00cb6b1248730c5be3 Mon Sep 17 00:00:00 2001 From: Kyryo Stepanov Date: Mon, 3 Jun 2024 20:57:27 +0300 Subject: [PATCH 20/59] update tests to verify whether claim CPI is working --- .../tests/program_test/addin.rs | 49 ++++- .../tests/program_test/cookies.rs | 4 +- .../tests/program_test/mod.rs | 24 +-- .../tests/program_test/rewards.rs | 12 +- .../tests/program_test/solana.rs | 34 +++- .../tests/program_test/utils.rs | 2 +- .../tests/rewards_invocations.rs | 105 ----------- .../voter-stake-registry/tests/test_basic.rs | 4 +- .../voter-stake-registry/tests/test_claim.rs | 170 ++++++++++++++++++ 9 files changed, 276 insertions(+), 128 deletions(-) delete mode 100644 programs/voter-stake-registry/tests/rewards_invocations.rs create mode 100644 programs/voter-stake-registry/tests/test_claim.rs diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index dde95311..d5ac29fc 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -2,6 +2,7 @@ use std::cell::RefCell; use std::sync::Arc; use mplx_staking_states::state::LockupPeriod; + use solana_sdk::pubkey::Pubkey; use solana_sdk::{ instruction::Instruction, @@ -634,11 +635,57 @@ impl AddinCookie { new_clock.unix_timestamp += time_offset - old_offset; self.solana.context.borrow_mut().set_sysvar(&new_clock); } + + #[allow(clippy::too_many_arguments)] + pub async fn claim( + &self, + reward_pool: &Pubkey, + reward_mint: &Pubkey, + reward_mining: &Pubkey, + owner: &Keypair, + owner_reward_ata: &Pubkey, + rewards_program: &Pubkey, + ) -> std::result::Result<(), BanksClientError> { + let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Claim); + + let (vault_pubkey, _) = Pubkey::find_program_address( + &[ + b"vault".as_ref(), + reward_pool.as_ref(), + reward_mint.as_ref(), + ], + rewards_program, + ); + + let accounts = anchor_lang::ToAccountMetas::to_account_metas( + &voter_stake_registry::accounts::Claim { + reward_pool: *reward_pool, + reward_mint: *reward_mint, + vault: vault_pubkey, + deposit_mining: *reward_mining, + owner: owner.pubkey(), + user_reward_token_account: *owner_reward_ata, + token_program: spl_token::id(), + rewards_program: *rewards_program, + }, + None, + ); + + let instructions = vec![Instruction { + program_id: self.program_id, + accounts, + data, + }]; + + self.solana + .process_transaction(&instructions, Some(&[owner])) + .await + } } impl VotingMintConfigCookie { pub async fn vault_balance(&self, solana: &SolanaCookie, voter: &VoterCookie) -> u64 { - let vault = voter.vault_address(&self); + let vault = voter.vault_address(self); solana.get_account::(vault).await.amount } } diff --git a/programs/voter-stake-registry/tests/program_test/cookies.rs b/programs/voter-stake-registry/tests/program_test/cookies.rs index 9af26135..42e5e797 100644 --- a/programs/voter-stake-registry/tests/program_test/cookies.rs +++ b/programs/voter-stake-registry/tests/program_test/cookies.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use crate::{solana, utils::*}; +use crate::utils::*; use solana_program::pubkey::*; use solana_sdk::signature::Keypair; diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index da802bfc..a3a75158 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -154,26 +154,25 @@ impl TestContext { }, // symbol: "USDC".to_string() ]; // Add mints in loop - for mint_index in 0..mints.len() { - let mint_pk: Pubkey; - if mints[mint_index].pubkey.is_none() { - mint_pk = Pubkey::new_unique(); + for mint in mints.iter_mut() { + let mint_pk = if mint.pubkey.is_none() { + Pubkey::new_unique() } else { - mint_pk = mints[mint_index].pubkey.unwrap(); - } + mint.pubkey.unwrap() + }; test.add_packable_account( mint_pk, u32::MAX as u64, &Mint { is_initialized: true, - mint_authority: COption::Some(mints[mint_index].authority.pubkey()), - decimals: mints[mint_index].decimals, + mint_authority: COption::Some(mint.authority.pubkey()), + decimals: mint.decimals, ..Mint::default() }, &spl_token::id(), ); - mints[mint_index].pubkey = Some(mint_pk); + mint.pubkey = Some(mint_pk); } let quote_index = mints.len() - 1; @@ -194,13 +193,14 @@ impl TestContext { // give every user 10^18 (< 2^60) of every token // ~~ 1 trillion in case of 6 decimals let mut token_accounts = Vec::new(); - for mint_index in 0..mints.len() { + for mint in mints.iter() { + // for mint_index in 0..mints.len() { let token_key = Pubkey::new_unique(); test.add_packable_account( token_key, u32::MAX as u64, &spl_token::state::Account { - mint: mints[mint_index].pubkey.unwrap(), + mint: mint.pubkey.unwrap(), owner: user_key.pubkey(), amount: 1_000_000_000_000_000_000, state: spl_token::state::AccountState::Initialized, @@ -238,7 +238,7 @@ impl TestContext { time_offset: RefCell::new(0), }, rewards: RewardsCookie { - solana: solana.clone(), + solana, program_id: rewards_program_id, }, mints, diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 7d66eb62..406fc5e0 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -127,15 +127,23 @@ impl RewardsCookie { &self, reward_pool: &Pubkey, reward_mint: &Pubkey, - vault: &Pubkey, authority: &Keypair, source_token_account: &Pubkey, amount: u64, ) -> std::result::Result<(), BanksClientError> { + let (vault, _bump) = Pubkey::find_program_address( + &[ + "vault".as_bytes(), + &reward_pool.to_bytes(), + &reward_mint.to_bytes(), + ], + &self.program_id, + ); + let accounts = vec![ AccountMeta::new(*reward_pool, false), AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(*vault, false), + AccountMeta::new(vault, false), AccountMeta::new_readonly(authority.pubkey(), true), AccountMeta::new(*source_token_account, false), AccountMeta::new_readonly(spl_token::id(), false), diff --git a/programs/voter-stake-registry/tests/program_test/solana.rs b/programs/voter-stake-registry/tests/program_test/solana.rs index 3bc9441f..1144810f 100644 --- a/programs/voter-stake-registry/tests/program_test/solana.rs +++ b/programs/voter-stake-registry/tests/program_test/solana.rs @@ -32,7 +32,7 @@ impl SolanaCookie { let mut context = self.context.borrow_mut(); let mut transaction = - Transaction::new_with_payer(&instructions, Some(&context.payer.pubkey())); + Transaction::new_with_payer(instructions, Some(&context.payer.pubkey())); let mut all_signers = vec![&context.payer]; @@ -97,7 +97,7 @@ impl SolanaCookie { self.process_transaction(&instructions, Some(&[&keypair])) .await .unwrap(); - return keypair.pubkey(); + keypair.pubkey() } #[allow(dead_code)] @@ -128,4 +128,34 @@ impl SolanaCookie { pub fn program_output(&self) -> super::ProgramOutput { self.program_output.read().unwrap().clone() } + + #[allow(dead_code)] + pub async fn create_spl_ata(&self, owner: &Pubkey, mint: &Pubkey, payer: &Keypair) -> Pubkey { + // let rent = self.rent.minimum_balance(spl_token::state::Account::LEN); + + let (ata_addr, _ata_bump) = Pubkey::find_program_address( + &[ + &owner.to_bytes(), + &spl_token::ID.to_bytes(), + &mint.to_bytes(), + ], + &spl_associated_token_account::ID, + ); + + let create_ata_ix = + spl_associated_token_account::instruction::create_associated_token_account( + &payer.pubkey(), + owner, + mint, + &spl_token::ID, + ); + + let instructions = &[create_ata_ix]; + + self.process_transaction(instructions, Some(&[payer])) + .await + .unwrap(); + + ata_addr + } } diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index 13c33672..ed240ab8 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -11,7 +11,7 @@ use solana_sdk::system_instruction; use solana_sdk::transaction::Transaction; use solana_sdk::transport::TransportError; -use crate::{AddinCookie, RegistrarCookie, TestContext}; +use crate::TestContext; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { diff --git a/programs/voter-stake-registry/tests/rewards_invocations.rs b/programs/voter-stake-registry/tests/rewards_invocations.rs deleted file mode 100644 index 52687c2a..00000000 --- a/programs/voter-stake-registry/tests/rewards_invocations.rs +++ /dev/null @@ -1,105 +0,0 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::LockupPeriod; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::cpi_instructions::deposit_mining; - -use crate::rewards::{AccountType, RewardsRoot}; - -pub mod program_test; - -#[tokio::test] -pub async fn initialize_root() -> std::result::Result<(), TransportError> { - let context = TestContext::new().await; - let payer = &context.users[0].key; - - let rewards_root_kp = context.rewards.initialize_root(payer).await?; - - let rewards_root_account = context - .solana - .get_account::(rewards_root_kp.pubkey()) - .await; - - assert_eq!(rewards_root_account.authority, payer.pubkey()); - assert_eq!(rewards_root_account.account_type, AccountType::RewardsRoot); - - Ok(()) -} - -// just run transaction to make sure they work -#[tokio::test] -pub async fn initialize_rewards_flow() -> std::result::Result<(), TransportError> { - // let context = TestContext::new().await; - // let payer = &context.users[0].key; - - // // create token mint - // let reward_mint = Keypair::new(); - // let manager = &payer.pubkey(); - // create_mint( - // &mut context.solana.context.borrow_mut(), - // &reward_mint, - // manager, - // ) - // .await - // .unwrap(); - - // let rewards_root_kp = context.rewards.initialize_root(payer).await?; - - // let deposit_authority = Keypair::new(); - // let rewards_pool = context - // .rewards - // .initialize_pool(&rewards_root_kp, &deposit_authority, payer) - // .await?; - - // let _vault = context - // .rewards - // .add_vault( - // &rewards_root_kp.pubkey(), - // &rewards_pool, - // &reward_mint.pubkey(), - // payer, - // ) - // .await?; - - // let user = Keypair::new(); - - // let _mining = context - // .rewards - // .initialize_mining(&rewards_pool, &user.pubkey(), payer) - // .await?; - - // let amount = 1; - // let lockup_period = LockupPeriod::ThreeMonths; - // context - // .rewards - // .deposit_mining( - // &rewards_pool, - // &user.pubkey(), - // &deposit_authority, - // amount, - // lockup_period, - // &reward_mint.pubkey(), - // ) - // .await?; - - // TODO: will not work because no deposits yet - // let rewarder = context - // .solana - // .create_token_account(&payer.pubkey(), reward_mint.pubkey()) - // .await; - // let amount = 100; - // context - // .rewards - // .fill_vault( - // &rewards_pool, - // &reward_mint.pubkey(), - // &vault, - // payer, - // &rewarder, - // amount, - // ) - // .await?; - - Ok(()) -} diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index b5d65085..43c42d6c 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -2,7 +2,7 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod, SECS_PER_DAY}; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; mod program_test; @@ -199,7 +199,7 @@ async fn test_basic() -> Result<(), TransportError> { .await?; context .addin - .close_voter(®istrar, &voter, &mngo_voting_mint, &voter_authority) + .close_voter(®istrar, &voter, &mngo_voting_mint, voter_authority) .await?; let lamports_after = context .solana diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs new file mode 100644 index 00000000..70f9ab67 --- /dev/null +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -0,0 +1,170 @@ +use anchor_spl::token::TokenAccount; +use solana_program_test::*; +use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; + +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; + +mod program_test; + +#[tokio::test] +async fn successeful_claim() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let deposit_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(deposit_authority.pubkey(), payer) + .await; + + let registrar = context + .addin + .create_registrar(&realm, &realm_authority, payer) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let rewards_pool = initialize_rewards_contract( + payer, + &context, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_authority.pubkey(), + ) + .await?; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter_authority_ata = context + .rewards + .solana + .create_spl_ata( + &voter_authority.pubkey(), + &mngo_voting_mint.mint.pubkey.unwrap(), + payer, + ) + .await; + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + let depositer_token_account = context.users[1].token_accounts[0]; + + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::ThreeMonths, + ) + .await?; + + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + deposit_authority, + depositer_token_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + + let rewards_source_ata = context.users[0].token_accounts[0]; + context + .rewards + .fill_vault( + &rewards_pool, + &mngo_voting_mint.mint.pubkey.unwrap(), + payer, + &rewards_source_ata, + 100, + ) + .await + .unwrap(); + + context + .addin + .claim( + &rewards_pool, + &mngo_voting_mint.mint.pubkey.unwrap(), + &deposit_mining, + voter_authority, + &voter_authority_ata, + &context.rewards.program_id, + ) + .await?; + + let claimed_amount = context + .solana + .token_account_balance(voter_authority_ata) + .await; + assert_eq!(100, claimed_amount); + + Ok(()) +} From 0e4c3e7114b5ebf3d12e43f1116d2c57ff1c41d9 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 7 Jun 2024 17:38:28 +0300 Subject: [PATCH 21/59] Update CPI invocations accordingly to the latest rewards API --- .../src/cpi_instructions.rs | 107 ++++++++++-------- .../src/instructions/claim.rs | 20 +++- .../src/instructions/create_voter.rs | 13 ++- .../src/instructions/deposit.rs | 18 ++- .../src/instructions/extend_deposit.rs | 17 ++- .../src/instructions/withdraw.rs | 16 ++- programs/voter-stake-registry/src/lib.rs | 74 ++++++++++-- .../tests/fixtures/mplx_rewards.so | Bin 279680 -> 288792 bytes 8 files changed, 193 insertions(+), 72 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index f3ce2844..4f62bd35 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -6,7 +6,7 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, instruction::{AccountMeta, Instruction}, - program::{invoke, invoke_signed}, + program::invoke_signed, system_program, }; @@ -14,29 +14,24 @@ pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("J8oa8UUJBydrTKtCdkvwmQQ27ZFDq54zAxWJY5Ey72Ji"); #[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] + pub enum RewardsInstruction { /// Creates and initializes a reward pool account /// /// Accounts: - /// [R] Root account (ex-Config program account) - /// [W] Reward pool account - /// [R] Deposit authority - /// [WS] Payer - /// [R] System program - InitializePool, - - /// Creates a new vault account and adds it to the reward pool - /// - /// Accounts: - /// [R] Root account (ex-Config program account) /// [W] Reward pool account /// [R] Reward mint account /// [W] Vault account /// [WS] Payer + /// [R] Rent sysvar /// [R] Token program /// [R] System program - /// [R] Rent sysvar - AddVault, + InitializePool { + /// Account responsible for charging mining owners + deposit_authority: Pubkey, + /// Account can fill the reward vault + fill_authority: Pubkey, + }, /// Fills the reward pool with rewards /// @@ -50,17 +45,21 @@ pub enum RewardsInstruction { FillVault { /// Amount to fill amount: u64, + /// Rewards distribution ends at given date + distribution_ends_at: u64, }, - /// Initializes mining account for the specified user + /// Initializes mining account for the specified mining owner /// /// Accounts: /// [W] Reward pool account /// [W] Mining - /// [R] User /// [WS] Payer /// [R] System program - InitializeMining, + InitializeMining { + /// Represent the end-user, owner of the mining + mining_owner: Pubkey, + }, /// Deposits amount of supply to the mining account /// @@ -68,15 +67,12 @@ pub enum RewardsInstruction { /// [W] Reward pool account /// [W] Mining /// [R] Mint of rewards account - /// [R] User /// [RS] Deposit authority DepositMining { /// Amount to deposit amount: u64, /// Lockup Period lockup_period: LockupPeriod, - /// Specifies mint addr - reward_mint_addr: Pubkey, /// Specifies the owner of the Mining Account owner: Pubkey, }, @@ -86,12 +82,12 @@ pub enum RewardsInstruction { /// Accounts: /// [W] Reward pool account /// [W] Mining - /// [R] User + /// [R] Mining owner /// [RS] Deposit authority WithdrawMining { /// Amount to withdraw amount: u64, - /// Owner of Mining Account + /// Specifies the owner of the Mining Account owner: Pubkey, }, @@ -102,26 +98,19 @@ pub enum RewardsInstruction { /// [R] Mint of rewards account /// [W] Vault for rewards account /// [W] Mining - /// [RS] User - /// [W] User reward token account + /// [RS] Mining owner + /// [RS] Deposit authority + /// [W] Mining owner reward token account /// [R] Token program Claim, - /// Creates and initializes a reward root - /// - /// Accounts: - /// [WS] Root account (ex-Config program account) - /// [WS] Authority - /// [R] System program - InitializeRoot, - /// Restakes deposit /// /// Accounts: /// [W] Reward pool account /// [W] Mining /// [R] Mint of rewards account - /// [R] User + /// [R] Mining owner /// [RS] Deposit authority RestakeDeposit { /// Requested lockup period for restaking @@ -131,6 +120,15 @@ pub enum RewardsInstruction { /// Deposit start_ts deposit_start_ts: u64, }, + + /// Distributes tokens among mining owners + /// + /// Accounts: + /// [W] Reward pool account + /// [R] Mint of rewards account + /// [W] Vault for rewards account + /// [RS] Distribute rewards authority + DistributeRewards, } /// Rewards initialize mining @@ -139,22 +137,31 @@ pub fn initialize_mining<'a>( program_id: &Pubkey, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, - user: AccountInfo<'a>, + mining_owner: &Pubkey, payer: AccountInfo<'a>, system_program: AccountInfo<'a>, + signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(user.key(), false), AccountMeta::new(payer.key(), true), AccountMeta::new_readonly(system_program::id(), false), ]; - let ix = - Instruction::new_with_borsh(*program_id, &RewardsInstruction::InitializeMining, accounts); + let ix = Instruction::new_with_borsh( + *program_id, + &RewardsInstruction::InitializeMining { + mining_owner: *mining_owner, + }, + accounts, + ); - invoke(&ix, &[reward_pool, mining, user, payer, system_program]) + invoke_signed( + &ix, + &[reward_pool, mining, payer, system_program], + &[signers_seeds], + ) } /// Rewards deposit mining @@ -166,8 +173,8 @@ pub fn deposit_mining<'a>( deposit_authority: AccountInfo<'a>, amount: u64, lockup_period: LockupPeriod, - reward_mint_addr: &Pubkey, owner: &Pubkey, + signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), @@ -180,13 +187,16 @@ pub fn deposit_mining<'a>( &RewardsInstruction::DepositMining { amount, lockup_period, - reward_mint_addr: *reward_mint_addr, owner: *owner, }, accounts, ); - invoke(&ix, &[reward_pool, mining, deposit_authority, program_id]) + invoke_signed( + &ix, + &[reward_pool, mining, deposit_authority, program_id], + &[signers_seeds], + ) } /// Restake deposit @@ -201,7 +211,7 @@ pub fn extend_deposit<'a>( amount: u64, lockup_period: LockupPeriod, deposit_start_ts: u64, - signers_seeds: &[&[&[u8]]], + signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), @@ -224,7 +234,7 @@ pub fn extend_deposit<'a>( invoke_signed( &ix, &[reward_pool, mining, user, deposit_authority], - signers_seeds, + &[signers_seeds], )?; Ok(()) @@ -239,6 +249,7 @@ pub fn withdraw_mining<'a>( deposit_authority: AccountInfo<'a>, amount: u64, owner: &Pubkey, + signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), @@ -255,7 +266,11 @@ pub fn withdraw_mining<'a>( accounts, ); - invoke(&ix, &[reward_pool, mining, deposit_authority, program_id]) + invoke_signed( + &ix, + &[reward_pool, mining, deposit_authority, program_id], + &[signers_seeds], + ) } /// Rewards withdraw mining @@ -269,6 +284,7 @@ pub fn claim<'a>( user: AccountInfo<'a>, user_reward_token_account: AccountInfo<'a>, token_program: AccountInfo<'a>, + signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new_readonly(reward_pool.key(), false), @@ -282,7 +298,7 @@ pub fn claim<'a>( let ix = Instruction::new_with_borsh(program_id.key(), &RewardsInstruction::Claim, accounts); - invoke( + invoke_signed( &ix, &[ reward_pool, @@ -294,5 +310,6 @@ pub fn claim<'a>( token_program, program_id, ], + &[signers_seeds], ) } diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index 38e52639..f2d47197 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -20,7 +20,7 @@ pub struct Claim<'info> { // pub voter_authority: Signer<'info>, #[account(mut)] - pub owner: Signer<'info>, + pub mining_owner: Signer<'info>, #[account(mut)] pub user_reward_token_account: Account<'info, TokenAccount>, @@ -35,15 +35,26 @@ pub struct Claim<'info> { /// /// Tokens will be transfered from Vault in Rewards account to User's user_reward_token_account. /// This call actually doesn't mutating Staking's accounts, only Reward's accounts will be mutated. -pub fn claim(ctx: Context) -> Result<()> { +pub fn claim( + ctx: Context, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, +) -> Result<()> { let rewards_program = ctx.accounts.rewards_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); let rewards_mint = ctx.accounts.reward_mint.to_account_info(); let vault = ctx.accounts.vault.to_account_info(); let deposit_mining = ctx.accounts.deposit_mining.to_account_info(); - let user = ctx.accounts.owner.to_account_info(); + let mining_owner = ctx.accounts.mining_owner.to_account_info(); let user_reward_token_account = ctx.accounts.user_reward_token_account.to_account_info(); let token_program = ctx.accounts.token_program.to_account_info(); + let signers_seeds = &[ + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar_bump][..], + ]; cpi_instructions::claim( rewards_program, @@ -51,9 +62,10 @@ pub fn claim(ctx: Context) -> Result<()> { rewards_mint, vault, deposit_mining, - user, + mining_owner, user_reward_token_account, token_program, + signers_seeds, )?; // TODO: add msg about claimed amount, getting use of return_data function diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index c59ddf0b..1a15d4e9 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -69,6 +69,9 @@ pub fn create_voter( ctx: Context, voter_bump: u8, voter_weight_record_bump: u8, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> Result<()> { // Forbid creating voter accounts from CPI. The goal is to make automation // impossible that weakens some of the limitations intentionally imposed on @@ -113,11 +116,16 @@ pub fn create_voter( // initialize Mining account for Voter let mining = ctx.accounts.deposit_mining.to_account_info(); let payer = ctx.accounts.payer.to_account_info(); - let user = ctx.accounts.voter_authority.to_account_info(); + let user = ctx.accounts.voter_authority.key; let system_program = ctx.accounts.system_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let signers_seeds = &[ + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar_bump][..], + ]; - // TODO: should it be voter or voter authority? cpi_instructions::initialize_mining( &REWARD_CONTRACT_ID, reward_pool, @@ -125,6 +133,7 @@ pub fn create_voter( user, payer, system_program, + signers_seeds, )?; } diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 2cff8291..f352f2bd 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -66,7 +66,14 @@ impl<'info> Deposit<'info> { /// /// `deposit_entry_index`: Index of the deposit entry. /// `amount`: Number of native tokens to transfer. -pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { +pub fn deposit( + ctx: Context, + deposit_entry_index: u8, + amount: u64, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, +) -> Result<()> { if amount == 0 { return Ok(()); } @@ -108,7 +115,12 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let deposit_authority = &ctx.accounts.deposit_authority; - let reward_mint = &ctx.accounts.deposit_token.mint; + let signers_seeds = &[ + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar_bump][..], + ]; cpi_instructions::deposit_mining( ctx.accounts.rewards_program.to_account_info(), @@ -117,8 +129,8 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R deposit_authority.to_account_info(), amount, d_entry.lockup.period, - reward_mint, owner, + signers_seeds, )?; msg!( diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index c2d91077..cf8fddc4 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -58,6 +58,9 @@ pub fn restake_deposit( ctx: Context, deposit_entry_index: u8, lockup_period: LockupPeriod, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; @@ -89,15 +92,11 @@ pub fn restake_deposit( let reward_mint = &ctx.accounts.deposit_token.mint; let voter = &ctx.accounts.voter; - let (_reward_pool_pubkey, pool_bump_seed) = Pubkey::find_program_address( - &[&reward_pool.key().to_bytes(), &reward_mint.key().to_bytes()], - &REWARD_CONTRACT_ID, - ); - let signers_seeds = &[ - &reward_pool.key().to_bytes()[..32], - &reward_mint.key().to_bytes()[..32], - &[pool_bump_seed], + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar_bump][..], ]; extend_deposit( @@ -110,7 +109,7 @@ pub fn restake_deposit( amount, lockup_period, start_ts, - &[signers_seeds], + signers_seeds, )?; d_entry.lockup.start_ts = curr_ts; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 17b9bf2d..cac8c5db 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -83,7 +83,14 @@ impl<'info> Withdraw<'info> { /// /// `deposit_entry_index`: The deposit entry to withdraw from. /// `amount` is in units of the native currency being withdrawn. -pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { +pub fn withdraw( + ctx: Context, + deposit_entry_index: u8, + amount: u64, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, +) -> Result<()> { { // Transfer the tokens to withdraw. let voter = &mut ctx.accounts.voter.load()?; @@ -177,6 +184,12 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> let mining = &ctx.accounts.deposit_mining; let deposit_authority = &ctx.accounts.voter_authority; let owner = &ctx.accounts.voter_authority; + let signers_seeds = &[ + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar_bump][..], + ]; withdraw_mining( rewards_program.to_account_info(), @@ -185,6 +198,7 @@ pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> deposit_authority.to_account_info(), amount, owner.key, + signers_seeds, )?; Ok(()) diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index d6604f2f..02860ea4 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -89,8 +89,18 @@ pub mod voter_stake_registry { ctx: Context, voter_bump: u8, voter_weight_record_bump: u8, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> Result<()> { - instructions::create_voter(ctx, voter_bump, voter_weight_record_bump) + instructions::create_voter( + ctx, + voter_bump, + voter_weight_record_bump, + registrar_bump, + realm_governing_mint_pubkey, + realm_pubkey, + ) } pub fn create_deposit_entry( @@ -103,12 +113,40 @@ pub mod voter_stake_registry { instructions::create_deposit_entry(ctx, deposit_entry_index, kind, start_ts, period) } - pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { - instructions::deposit(ctx, deposit_entry_index, amount) + pub fn deposit( + ctx: Context, + deposit_entry_index: u8, + amount: u64, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, + ) -> Result<()> { + instructions::deposit( + ctx, + deposit_entry_index, + amount, + registrar_bump, + realm_governing_mint_pubkey, + realm_pubkey, + ) } - pub fn withdraw(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { - instructions::withdraw(ctx, deposit_entry_index, amount) + pub fn withdraw( + ctx: Context, + deposit_entry_index: u8, + amount: u64, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, + ) -> Result<()> { + instructions::withdraw( + ctx, + deposit_entry_index, + amount, + registrar_bump, + realm_governing_mint_pubkey, + realm_pubkey, + ) } pub fn close_deposit_entry( @@ -156,11 +194,31 @@ pub mod voter_stake_registry { ctx: Context, deposit_entry_index: u8, lockup_period: LockupPeriod, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> Result<()> { - instructions::restake_deposit(ctx, deposit_entry_index, lockup_period) + instructions::restake_deposit( + ctx, + deposit_entry_index, + lockup_period, + registrar_bump, + realm_governing_mint_pubkey, + realm_pubkey, + ) } - pub fn claim(ctx: Context) -> Result<()> { - instructions::claim(ctx) + pub fn claim( + ctx: Context, + registrar_bump: u8, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, + ) -> Result<()> { + instructions::claim( + ctx, + registrar_bump, + realm_governing_mint_pubkey, + realm_pubkey, + ) } } diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index f59195d4706eed90dd389d81b0c76fcbc2153784..f754808c640f79ea27110cdc08321f0349b3978c 100755 GIT binary patch literal 288792 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 literal 279680 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

v06-4B`2O`8kJ8jK%eRQGX}5Qf-ssBX`M`qoX>O M7%3 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 From 1203844f918896996eeb2fc85fe6c336911dcf80 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 7 Jun 2024 21:05:44 +0300 Subject: [PATCH 22/59] Change CPI call interfaces && binary fixture && integration tests accordingly to the devnet version requirements --- .../src/cpi_instructions.rs | 21 +-- .../src/instructions/claim.rs | 7 +- .../src/instructions/create_voter.rs | 12 +- .../src/instructions/deposit.rs | 4 +- .../src/instructions/extend_deposit.rs | 4 +- .../src/instructions/withdraw.rs | 4 +- programs/voter-stake-registry/src/lib.rs | 12 +- .../tests/fixtures/mplx_rewards.so | Bin 288792 -> 296136 bytes .../tests/program_test/addin.rs | 79 +++++------- .../tests/program_test/rewards.rs | 122 +++++++++--------- .../tests/program_test/utils.rs | 22 ---- .../tests/test_all_deposits.rs | 23 ++-- .../voter-stake-registry/tests/test_basic.rs | 21 ++- .../voter-stake-registry/tests/test_claim.rs | 56 ++++++-- .../tests/test_deposit_constant.rs | 43 +++--- .../tests/test_deposit_no_locking.rs | 21 ++- .../tests/test_internal_transfers.rs | 43 +++--- .../voter-stake-registry/tests/test_lockup.rs | 65 ++++++---- .../tests/test_log_voter_info.rs | 21 ++- .../voter-stake-registry/tests/test_voting.rs | 22 ++-- 20 files changed, 329 insertions(+), 273 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 4f62bd35..05b4d776 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -6,7 +6,7 @@ use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, instruction::{AccountMeta, Instruction}, - program::invoke_signed, + program::{invoke, invoke_signed}, system_program, }; @@ -31,6 +31,9 @@ pub enum RewardsInstruction { deposit_authority: Pubkey, /// Account can fill the reward vault fill_authority: Pubkey, + /// Account that can distribute money among users after + /// if RewardVault had been filled with rewards + distribution_authority: Pubkey, }, /// Fills the reward pool with rewards @@ -140,7 +143,6 @@ pub fn initialize_mining<'a>( mining_owner: &Pubkey, payer: AccountInfo<'a>, system_program: AccountInfo<'a>, - signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), @@ -157,11 +159,7 @@ pub fn initialize_mining<'a>( accounts, ); - invoke_signed( - &ix, - &[reward_pool, mining, payer, system_program], - &[signers_seeds], - ) + invoke(&ix, &[reward_pool, mining, payer, system_program]) } /// Rewards deposit mining @@ -281,7 +279,8 @@ pub fn claim<'a>( reward_mint: AccountInfo<'a>, vault: AccountInfo<'a>, mining: AccountInfo<'a>, - user: AccountInfo<'a>, + mining_owner: AccountInfo<'a>, + deposit_authority: AccountInfo<'a>, user_reward_token_account: AccountInfo<'a>, token_program: AccountInfo<'a>, signers_seeds: &[&[u8]], @@ -291,7 +290,8 @@ pub fn claim<'a>( AccountMeta::new_readonly(reward_mint.key(), false), AccountMeta::new(vault.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new(user.key(), true), + AccountMeta::new_readonly(mining_owner.key(), true), + AccountMeta::new_readonly(deposit_authority.key(), true), AccountMeta::new(user_reward_token_account.key(), false), AccountMeta::new_readonly(token_program.key(), false), ]; @@ -305,7 +305,8 @@ pub fn claim<'a>( reward_mint, vault, mining, - user, + mining_owner, + deposit_authority, user_reward_token_account, token_program, program_id, diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index f2d47197..a6e30d61 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -18,10 +18,13 @@ pub struct Claim<'info> { #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, - // pub voter_authority: Signer<'info>, #[account(mut)] pub mining_owner: Signer<'info>, + /// CHECK: Registrar plays the role of deposit_authority on the Rewards Contract, + /// therefore their PDA that should sign the CPI call + pub registrar: UncheckedAccount<'info>, + #[account(mut)] pub user_reward_token_account: Account<'info, TokenAccount>, @@ -46,6 +49,7 @@ pub fn claim( let rewards_mint = ctx.accounts.reward_mint.to_account_info(); let vault = ctx.accounts.vault.to_account_info(); let deposit_mining = ctx.accounts.deposit_mining.to_account_info(); + let deposit_authority = ctx.accounts.registrar.to_account_info(); let mining_owner = ctx.accounts.mining_owner.to_account_info(); let user_reward_token_account = ctx.accounts.user_reward_token_account.to_account_info(); let token_program = ctx.accounts.token_program.to_account_info(); @@ -63,6 +67,7 @@ pub fn claim( vault, deposit_mining, mining_owner, + deposit_authority, user_reward_token_account, token_program, signers_seeds, diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 1a15d4e9..98474fb5 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -10,6 +10,8 @@ use crate::voter::VoterWeightRecord; #[derive(Accounts)] pub struct CreateVoter<'info> { + /// Also, Registrar plays the role of deposit_authority on the Rewards Contract, + /// therefore their PDA that should sign the CPI call pub registrar: AccountLoader<'info, Registrar>, #[account( @@ -69,9 +71,6 @@ pub fn create_voter( ctx: Context, voter_bump: u8, voter_weight_record_bump: u8, - registrar_bump: u8, - realm_governing_mint_pubkey: Pubkey, - realm_pubkey: Pubkey, ) -> Result<()> { // Forbid creating voter accounts from CPI. The goal is to make automation // impossible that weakens some of the limitations intentionally imposed on @@ -119,12 +118,6 @@ pub fn create_voter( let user = ctx.accounts.voter_authority.key; let system_program = ctx.accounts.system_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); - let signers_seeds = &[ - &realm_pubkey.key().to_bytes(), - b"registrar".as_ref(), - &realm_governing_mint_pubkey.key().to_bytes(), - &[registrar_bump][..], - ]; cpi_instructions::initialize_mining( &REWARD_CONTRACT_ID, @@ -133,7 +126,6 @@ pub fn create_voter( user, payer, system_program, - signers_seeds, )?; } diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index f352f2bd..31bee5f9 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -114,7 +114,7 @@ pub fn deposit( let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; - let deposit_authority = &ctx.accounts.deposit_authority; + let pool_deposit_authority = &ctx.accounts.registrar.to_account_info(); let signers_seeds = &[ &realm_pubkey.key().to_bytes(), b"registrar".as_ref(), @@ -126,7 +126,7 @@ pub fn deposit( ctx.accounts.rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), - deposit_authority.to_account_info(), + pool_deposit_authority.to_account_info(), amount, d_entry.lockup.period, owner, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index cf8fddc4..aa58a6a8 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -88,7 +88,7 @@ pub fn restake_deposit( let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; - let deposit_authority = &ctx.accounts.deposit_authority; + let pool_deposit_authority = &ctx.accounts.registrar; let reward_mint = &ctx.accounts.deposit_token.mint; let voter = &ctx.accounts.voter; @@ -105,7 +105,7 @@ pub fn restake_deposit( mining.to_account_info(), reward_mint, voter.to_account_info(), - deposit_authority.to_account_info(), + pool_deposit_authority.to_account_info(), amount, lockup_period, start_ts, diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index cac8c5db..b9ded8dc 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -182,7 +182,7 @@ pub fn withdraw( let rewards_program = &ctx.accounts.rewards_program; let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; - let deposit_authority = &ctx.accounts.voter_authority; + let pool_deposit_authority = &ctx.accounts.registrar; let owner = &ctx.accounts.voter_authority; let signers_seeds = &[ &realm_pubkey.key().to_bytes(), @@ -195,7 +195,7 @@ pub fn withdraw( rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), - deposit_authority.to_account_info(), + pool_deposit_authority.to_account_info(), amount, owner.key, signers_seeds, diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 02860ea4..f81da445 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -89,18 +89,8 @@ pub mod voter_stake_registry { ctx: Context, voter_bump: u8, voter_weight_record_bump: u8, - registrar_bump: u8, - realm_governing_mint_pubkey: Pubkey, - realm_pubkey: Pubkey, ) -> Result<()> { - instructions::create_voter( - ctx, - voter_bump, - voter_weight_record_bump, - registrar_bump, - realm_governing_mint_pubkey, - realm_pubkey, - ) + instructions::create_voter(ctx, voter_bump, voter_weight_record_bump) } pub fn create_deposit_entry( diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index f754808c640f79ea27110cdc08321f0349b3978c..37ca50427b4be87aed1f53032ce478c59a100a5f 100755 GIT binary patch delta 84040 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 diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index d5ac29fc..14eb143f 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -22,6 +22,9 @@ pub struct RegistrarCookie { pub address: Pubkey, pub authority: Pubkey, pub mint: MintCookie, + pub registrar_bump: u8, + pub realm_pubkey: Pubkey, + pub realm_governing_token_mint_pubkey: Pubkey, } #[derive(Clone)] @@ -78,12 +81,8 @@ impl AddinCookie { data, }]; - // clone the user secret - let signer1 = Keypair::from_base58_string(&payer.to_base58_string()); - let signer2 = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer1, &signer2])) + .process_transaction(&instructions, Some(&[payer, authority])) .await .unwrap(); @@ -91,6 +90,9 @@ impl AddinCookie { address: registrar, authority: realm.authority, mint: realm.community_token_mint.clone(), + registrar_bump, + realm_pubkey: realm.realm, + realm_governing_token_mint_pubkey: community_token_mint, } } @@ -126,11 +128,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[authority])) .await } @@ -188,12 +187,8 @@ impl AddinCookie { data, }]; - // clone the user secret - //let signer1 = Keypair::from_base58_string(&payer.to_base58_string()); - let signer2 = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer2])) + .process_transaction(&instructions, Some(&[authority])) .await .unwrap(); @@ -257,12 +252,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer1 = Keypair::from_base58_string(&payer.to_base58_string()); - let signer2 = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer1, &signer2])) + .process_transaction(&instructions, Some(&[payer, authority])) .await .unwrap(); @@ -319,11 +310,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer1 = Keypair::from_base58_string(&voter_authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer1])) + .process_transaction(&instructions, Some(&[voter_authority])) .await } @@ -347,6 +335,9 @@ impl AddinCookie { anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Deposit { deposit_entry_index, amount, + realm_governing_mint_pubkey: registrar.realm_governing_token_mint_pubkey, + registrar_bump: registrar.registrar_bump, + realm_pubkey: registrar.realm_pubkey, }); let accounts = anchor_lang::ToAccountMetas::to_account_metas( @@ -370,11 +361,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&deposit_authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[deposit_authority])) .await } @@ -405,11 +393,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[authority])) .await } @@ -433,6 +418,9 @@ impl AddinCookie { anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Withdraw { deposit_entry_index, amount, + realm_governing_mint_pubkey: registrar.realm_governing_token_mint_pubkey, + registrar_bump: registrar.registrar_bump, + realm_pubkey: registrar.realm_pubkey, }); let accounts = anchor_lang::ToAccountMetas::to_account_metas( @@ -458,11 +446,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[authority])) .await } @@ -496,11 +481,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&voter_authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[voter_authority])) .await } @@ -573,11 +555,8 @@ impl AddinCookie { data, }]; - // clone the secrets - let signer = Keypair::from_base58_string(&authority.to_base58_string()); - self.solana - .process_transaction(&instructions, Some(&[&signer])) + .process_transaction(&instructions, Some(&[authority])) .await } @@ -642,11 +621,16 @@ impl AddinCookie { reward_pool: &Pubkey, reward_mint: &Pubkey, reward_mining: &Pubkey, - owner: &Keypair, + mining_owner: &Keypair, owner_reward_ata: &Pubkey, rewards_program: &Pubkey, + registrar: &RegistrarCookie, ) -> std::result::Result<(), BanksClientError> { - let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Claim); + let data = anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Claim { + realm_governing_mint_pubkey: registrar.realm_governing_token_mint_pubkey, + registrar_bump: registrar.registrar_bump, + realm_pubkey: registrar.realm_pubkey, + }); let (vault_pubkey, _) = Pubkey::find_program_address( &[ @@ -663,7 +647,8 @@ impl AddinCookie { reward_mint: *reward_mint, vault: vault_pubkey, deposit_mining: *reward_mining, - owner: owner.pubkey(), + mining_owner: mining_owner.pubkey(), + registrar: registrar.address, user_reward_token_account: *owner_reward_ata, token_program: spl_token::id(), rewards_program: *rewards_program, @@ -678,7 +663,7 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[owner])) + .process_transaction(&instructions, Some(&[mining_owner])) .await } } diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 406fc5e0..923a7092 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -23,55 +23,49 @@ pub struct RewardsCookie { } impl RewardsCookie { - pub async fn initialize_root( - &self, - payer: &Keypair, - ) -> std::result::Result { - let rewards_root = Keypair::new(); - - let accounts = vec![ - AccountMeta::new(rewards_root.pubkey(), true), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new_readonly(system_program::id(), false), - ]; - - let ix = Instruction::new_with_borsh( - self.program_id, - &RewardsInstruction::InitializeRoot, - accounts, - ); - - let signers = vec![payer, &rewards_root]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok(rewards_root) - } - pub async fn initialize_pool( &self, - rewards_root: &Pubkey, deposit_authority: &Pubkey, + fill_authority: &Pubkey, + distribution_authority: &Pubkey, payer: &Keypair, - ) -> std::result::Result { - let (reward_pool, _bump) = Pubkey::find_program_address( - &["reward_pool".as_bytes(), &rewards_root.key().to_bytes()], + reward_mint: &Pubkey, + ) -> std::result::Result<(Pubkey, Pubkey), BanksClientError> { + let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( + &[ + "reward_pool".as_bytes(), + &deposit_authority.key().to_bytes(), + &fill_authority.key().to_bytes(), + ], + &self.program_id, + ); + + let (reward_vault, _reward_vault_bump) = Pubkey::find_program_address( + &[ + "vault".as_bytes(), + &reward_pool.key().to_bytes(), + &reward_mint.key().to_bytes(), + ], &self.program_id, ); let accounts = vec![ - AccountMeta::new_readonly(rewards_root.key(), false), AccountMeta::new(reward_pool, false), - AccountMeta::new_readonly(deposit_authority.key(), false), + AccountMeta::new_readonly(*reward_mint, false), + AccountMeta::new(reward_vault, false), AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(sysvar::rent::id(), false), + AccountMeta::new_readonly(spl_token::id(), false), AccountMeta::new_readonly(system_program::id(), false), ]; let ix = Instruction::new_with_borsh( self.program_id, - &RewardsInstruction::InitializePool, + &RewardsInstruction::InitializePool { + deposit_authority: *deposit_authority, + fill_authority: *fill_authority, + distribution_authority: *distribution_authority, + }, accounts, ); @@ -81,16 +75,18 @@ impl RewardsCookie { .process_transaction(&[ix], Some(&signers)) .await?; - Ok(reward_pool) + Ok((reward_pool, reward_vault)) } - pub async fn add_vault( + pub async fn fill_vault( &self, - rewards_root: &Pubkey, reward_pool: &Pubkey, reward_mint: &Pubkey, - payer: &Keypair, - ) -> std::result::Result { + fill_authority: &Keypair, + source_token_account: &Pubkey, + amount: u64, + distribution_ends_at: u64, + ) -> std::result::Result<(), BanksClientError> { let (vault, _bump) = Pubkey::find_program_address( &[ "vault".as_bytes(), @@ -101,35 +97,38 @@ impl RewardsCookie { ); let accounts = vec![ - AccountMeta::new_readonly(*rewards_root, false), AccountMeta::new(*reward_pool, false), AccountMeta::new_readonly(*reward_mint, false), AccountMeta::new(vault, false), - AccountMeta::new(payer.pubkey(), true), + AccountMeta::new_readonly(fill_authority.pubkey(), true), + AccountMeta::new(*source_token_account, false), AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(system_program::id(), false), - AccountMeta::new_readonly(sysvar::rent::id(), false), ]; - let ix = - Instruction::new_with_borsh(self.program_id, &RewardsInstruction::AddVault, accounts); + let ix = Instruction::new_with_borsh( + self.program_id, + &RewardsInstruction::FillVault { + amount, + distribution_ends_at, + }, + accounts, + ); - let signers = vec![payer]; + let signers = vec![fill_authority]; self.solana .process_transaction(&[ix], Some(&signers)) .await?; - Ok(vault) + Ok(()) } - pub async fn fill_vault( + pub async fn distribute_rewards( &self, reward_pool: &Pubkey, reward_mint: &Pubkey, - authority: &Keypair, - source_token_account: &Pubkey, - amount: u64, + reward_vault: &Pubkey, + distribute_authority: &Keypair, ) -> std::result::Result<(), BanksClientError> { let (vault, _bump) = Pubkey::find_program_address( &[ @@ -143,19 +142,17 @@ impl RewardsCookie { let accounts = vec![ AccountMeta::new(*reward_pool, false), AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(vault, false), - AccountMeta::new_readonly(authority.pubkey(), true), - AccountMeta::new(*source_token_account, false), - AccountMeta::new_readonly(spl_token::id(), false), + AccountMeta::new(*reward_vault, false), + AccountMeta::new_readonly(distribute_authority.pubkey(), true), ]; let ix = Instruction::new_with_borsh( self.program_id, - &RewardsInstruction::FillVault { amount }, + &RewardsInstruction::DistributeRewards, accounts, ); - let signers = vec![authority]; + let signers = vec![distribute_authority]; self.solana .process_transaction(&[ix], Some(&signers)) @@ -168,13 +165,13 @@ impl RewardsCookie { pub async fn initialize_mining<'a>( &self, reward_pool: &Pubkey, - user: &Pubkey, + mining_owner: &Pubkey, payer: &Keypair, ) -> std::result::Result { let (mining, _bump) = Pubkey::find_program_address( &[ "mining".as_bytes(), - &user.key().to_bytes(), + &mining_owner.key().to_bytes(), &reward_pool.key().to_bytes(), ], &self.program_id, @@ -183,14 +180,15 @@ impl RewardsCookie { let accounts = vec![ AccountMeta::new(*reward_pool, false), AccountMeta::new(mining, false), - AccountMeta::new_readonly(*user, false), AccountMeta::new(payer.pubkey(), true), AccountMeta::new_readonly(system_program::id(), false), ]; let ix = Instruction::new_with_borsh( self.program_id, - &RewardsInstruction::InitializeMining, + &RewardsInstruction::InitializeMining { + mining_owner: *mining_owner, + }, accounts, ); @@ -210,7 +208,6 @@ impl RewardsCookie { deposit_authority: &Keypair, amount: u64, lockup_period: LockupPeriod, - mint_account: &Pubkey, owner: &Pubkey, ) -> std::result::Result<(), BanksClientError> { let (mining, _bump) = Pubkey::find_program_address( @@ -233,7 +230,6 @@ impl RewardsCookie { &RewardsInstruction::DepositMining { amount, lockup_period, - reward_mint_addr: *mint_account, owner: *owner, }, accounts, diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index ed240ab8..19cd16d6 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -9,9 +9,6 @@ use solana_sdk::signature::Keypair; use solana_sdk::signer::Signer; use solana_sdk::system_instruction; use solana_sdk::transaction::Transaction; -use solana_sdk::transport::TransportError; - -use crate::TestContext; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { @@ -76,25 +73,6 @@ pub async fn create_mint( context.banks_client.process_transaction(tx).await } -pub async fn initialize_rewards_contract( - payer: &Keypair, - context: &TestContext, - reward_mint: &Pubkey, - deposit_authority: &Pubkey, -) -> Result { - let rewards_root = context.rewards.initialize_root(payer).await?; - let rewards_pool = context - .rewards - .initialize_pool(&rewards_root.pubkey(), deposit_authority, payer) - .await?; - let _vault = context - .rewards - .add_vault(&rewards_root.pubkey(), &rewards_pool, reward_mint, payer) - .await?; - - Ok(rewards_pool) -} - pub fn find_deposit_mining_addr( user: &Pubkey, rewards_pool: &Pubkey, diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 7ff31e6a..cd168043 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -48,14 +48,21 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; - let deposit_authority = voter_authority; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; + let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 43c42d6c..3b85d0bf 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -66,13 +66,20 @@ async fn test_basic() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 70f9ab67..b8532871 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -1,6 +1,6 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; @@ -66,13 +66,20 @@ async fn successeful_claim() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; @@ -135,28 +142,51 @@ async fn successeful_claim() -> Result<(), TransportError> { ) .await?; - let rewards_source_ata = context.users[0].token_accounts[0]; + let rewards_source_ata = context.users[3].token_accounts[0]; + let amount = 100; + let distribution_ends_at = context + .solana + .context + .borrow_mut() + .banks_client + .get_sysvar::() + .await + .unwrap() + .unix_timestamp as u64 + + 86400; context .rewards .fill_vault( &rewards_pool, - &mngo_voting_mint.mint.pubkey.unwrap(), - payer, + reward_mint, + &fill_authority, &rewards_source_ata, - 100, + amount, + distribution_ends_at, ) .await .unwrap(); + context + .rewards + .distribute_rewards( + &rewards_pool, + reward_mint, + &rewards_vault, + &distribution_authority, + ) + .await?; + context .addin .claim( &rewards_pool, - &mngo_voting_mint.mint.pubkey.unwrap(), + reward_mint, &deposit_mining, voter_authority, &voter_authority_ata, &context.rewards.program_id, + ®istrar, ) .await?; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index bda0639c..ff63c3fa 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -83,13 +83,20 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; @@ -249,14 +256,20 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) .await; - let deposit_authority = &context.users[1].key; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 72eb63b5..bb41b71b 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -88,13 +88,20 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index b8373e89..a37acb7e 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -82,13 +82,20 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; @@ -231,14 +238,20 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> ) .await; - let deposit_authority = &voter_authority; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 2727a5ae..35c73565 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -66,13 +66,20 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; @@ -210,14 +217,20 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; - let deposit_authority = &voter_authority; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -362,14 +375,20 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; - let deposit_authority = &voter_authority; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 569b7663..8ea4df5c 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -69,13 +69,20 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index cf4d01d0..2000af48 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -68,14 +68,20 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let deposit_authority = &voter_authority; - let rewards_pool = initialize_rewards_contract( - payer, - &context, - &mngo_voting_mint.mint.pubkey.unwrap(), - &deposit_authority.pubkey(), - ) - .await?; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let reward_mint = &context.mints[0].pubkey.unwrap(); + let pool_deposit_authority = ®istrar.address; + let (rewards_pool, _rewards_vault) = context + .rewards + .initialize_pool( + pool_deposit_authority, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + payer, + reward_mint, + ) + .await?; let deposit_mining_voter = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, From 956e5db46d7fbbf9bedfc5eac4507de50642f6b3 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 10 Jun 2024 12:32:35 +0300 Subject: [PATCH 23/59] update program id --- programs/voter-stake-registry/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index f81da445..aa1cccfc 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -9,7 +9,7 @@ mod instructions; pub mod voter; // The program address. -declare_id!("3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s"); +declare_id!("36WUaavkjamJNEhjw4drmdo11NBFbgfxCFVcwLoEDmiq"); /// # Introduction /// From f451c6f1b750c6c8b4e6f13927d3462f0734045a Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 10 Jun 2024 12:35:31 +0300 Subject: [PATCH 24/59] add check for vault field --- programs/voter-stake-registry/src/instructions/claim.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index a6e30d61..014e137c 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -12,6 +12,8 @@ pub struct Claim<'info> { pub reward_mint: UncheckedAccount<'info>, #[account(mut)] + /// CHECK: Rewards vault is used as a source of rewards and + /// is checked on the rewards contract pub vault: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract From b294280268393dc6515786ed8de60d0bfe106f6c Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 10 Jun 2024 16:51:36 +0300 Subject: [PATCH 25/59] read returned data from cpi --- program-states/src/error.rs | 3 +++ .../src/instructions/claim.rs | 19 +++++++++++++----- programs/voter-stake-registry/src/lib.rs | 4 ++-- .../tests/fixtures/mplx_rewards.so | Bin 296136 -> 296472 bytes 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/program-states/src/error.rs b/program-states/src/error.rs index 84668a2c..d7ab5ad4 100644 --- a/program-states/src/error.rs +++ b/program-states/src/error.rs @@ -128,4 +128,7 @@ pub enum VsrError { // 6041 / 0x1799 #[msg("To deposit additional tokens, extend the deposit")] DepositingIsForbidded, + // 6042 / 0x179a + #[msg("Cpi call must return data, but data is absent")] + CpiReturnDataIsAbsent, } diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index 014e137c..c64aa496 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -1,7 +1,11 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; +use std::borrow::Borrow; +use crate::borsh::BorshDeserialize; use crate::cpi_instructions; +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use solana_program::program::get_return_data; #[derive(Accounts)] pub struct Claim<'info> { @@ -45,7 +49,7 @@ pub fn claim( registrar_bump: u8, realm_governing_mint_pubkey: Pubkey, realm_pubkey: Pubkey, -) -> Result<()> { +) -> Result { let rewards_program = ctx.accounts.rewards_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); let rewards_mint = ctx.accounts.reward_mint.to_account_info(); @@ -75,6 +79,11 @@ pub fn claim( signers_seeds, )?; - // TODO: add msg about claimed amount, getting use of return_data function - Ok(()) + if let Some((_rewards_program_id, claimed_rewards_raw)) = get_return_data() { + let claimed_rewards = u64::deserialize(&mut claimed_rewards_raw.borrow())?; + msg!("Rewards are clamed {:?}", claimed_rewards); + Ok(claimed_rewards) + } else { + Err(VsrError::CpiReturnDataIsAbsent.into()) + } } diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index aa1cccfc..d9c053a8 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -9,7 +9,7 @@ mod instructions; pub mod voter; // The program address. -declare_id!("36WUaavkjamJNEhjw4drmdo11NBFbgfxCFVcwLoEDmiq"); +declare_id!("3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s"); /// # Introduction /// @@ -203,7 +203,7 @@ pub mod voter_stake_registry { registrar_bump: u8, realm_governing_mint_pubkey: Pubkey, realm_pubkey: Pubkey, - ) -> Result<()> { + ) -> Result { instructions::claim( ctx, registrar_bump, diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index 37ca50427b4be87aed1f53032ce478c59a100a5f..cdb32e955133f5201056088719128e0a6cb292ee 100755 GIT binary patch delta 29795 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 iP773QG^K^PQrY2R`BLG Date: Mon, 10 Jun 2024 18:29:22 +0300 Subject: [PATCH 26/59] update contract's id --- Anchor.toml | 6 +++--- program-states/src/lib.rs | 2 +- programs/voter-stake-registry/src/lib.rs | 2 +- .../voter-stake-registry/tests/program_test/addin.rs | 4 ++-- .../tests/program_test/governance.rs | 4 ++-- .../voter-stake-registry/tests/program_test/mod.rs | 11 +++++------ .../tests/program_test/rewards.rs | 4 ++-- 7 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Anchor.toml b/Anchor.toml index 89219698..15d211cd 100644 --- a/Anchor.toml +++ b/Anchor.toml @@ -2,13 +2,13 @@ anchor_version = "0.26.0" solana_version = "1.14.10" [programs.localnet] -voter_stake_registry = "3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s" +voter_stake_registry = "9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8" [programs.devnet] -voter_stake_registry = "3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s" +voter_stake_registry = "9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8" [programs.mainnet] -voter_stake_registry = "3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s" +voter_stake_registry = "9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8" [provider] cluster = "devnet" diff --git a/program-states/src/lib.rs b/program-states/src/lib.rs index c8ba2c9b..a5afd1e4 100644 --- a/program-states/src/lib.rs +++ b/program-states/src/lib.rs @@ -7,4 +7,4 @@ pub mod error; pub mod state; // Requires by Anchor to declare accounts in crate -declare_id!("3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s"); +declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index d9c053a8..6f5ac827 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -9,7 +9,7 @@ mod instructions; pub mod voter; // The program address. -declare_id!("3GepGwMp6WgPqgNa5NuSpnw3rQjYnqHCcVWhVmpGnw6s"); +declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); /// # Introduction /// diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 14eb143f..c3ff7661 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -1,5 +1,5 @@ use std::cell::RefCell; -use std::sync::Arc; +use std::rc::Rc; use mplx_staking_states::state::LockupPeriod; @@ -13,7 +13,7 @@ use crate::*; #[derive(Clone)] pub struct AddinCookie { - pub solana: Arc, + pub solana: Rc, pub program_id: Pubkey, pub time_offset: RefCell, } diff --git a/programs/voter-stake-registry/tests/program_test/governance.rs b/programs/voter-stake-registry/tests/program_test/governance.rs index 2bc2c1b2..7986044b 100644 --- a/programs/voter-stake-registry/tests/program_test/governance.rs +++ b/programs/voter-stake-registry/tests/program_test/governance.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::rc::Rc; use solana_program::instruction::Instruction; use solana_sdk::pubkey::Pubkey; @@ -9,7 +9,7 @@ use crate::*; #[derive(Clone)] pub struct GovernanceCookie { - pub solana: Arc, + pub solana: Rc, pub program_id: Pubkey, } diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index a3a75158..c180f8f6 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -1,5 +1,6 @@ use log::*; use std::cell::RefCell; +use std::rc::Rc; use std::{str::FromStr, sync::Arc, sync::RwLock}; use solana_program::{program_option::COption, program_pack::Pack}; @@ -83,7 +84,7 @@ impl Log for LoggerWrapper { } pub struct TestContext { - pub solana: Arc, + pub solana: Rc, pub governance: GovernanceCookie, pub rewards: RewardsCookie, pub addin: AddinCookie, @@ -109,11 +110,9 @@ impl TestContext { output: program_output.clone(), })); - let addin_program_id = voter_stake_registry::id(); - let mut test = ProgramTest::new( "voter_stake_registry", - addin_program_id, + voter_stake_registry::id(), processor!(voter_stake_registry::entry), ); // intentionally set to half the limit, to catch potential problems early @@ -220,7 +219,7 @@ impl TestContext { let mut context = test.start_with_context().await; let rent = context.banks_client.get_rent().await.unwrap(); - let solana = Arc::new(SolanaCookie { + let solana = Rc::new(SolanaCookie { context: RefCell::new(context), rent, program_output: program_output.clone(), @@ -234,7 +233,7 @@ impl TestContext { }, addin: AddinCookie { solana: solana.clone(), - program_id: addin_program_id, + program_id: voter_stake_registry::id(), time_offset: RefCell::new(0), }, rewards: RewardsCookie { diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 923a7092..dae64fbc 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::rc::Rc; use anchor_lang::prelude::*; use anchor_lang::AnchorDeserialize; @@ -18,7 +18,7 @@ use voter_stake_registry::cpi_instructions::RewardsInstruction; use crate::SolanaCookie; pub struct RewardsCookie { - pub solana: Arc, + pub solana: Rc, pub program_id: Pubkey, } From ae20b636a77c7f32209f45de0f13a451786713c0 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 11:51:35 +0300 Subject: [PATCH 27/59] update program's idl --- idl/voter_stake_registry.ts | 1718 ++++++++++++++--------------------- 1 file changed, 700 insertions(+), 1018 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 6bb58144..4be00c0f 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -173,7 +173,11 @@ export type VoterStakeRegistry = { { "name": "registrar", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "Also, Registrar plays the role of deposit_authority on the Rewards Contract,", + "therefore their PDA that should sign the CPI call" + ] }, { "name": "voter", @@ -221,6 +225,21 @@ export type VoterStakeRegistry = { "docs": [ "NOTE: this account is currently unused" ] + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -345,6 +364,21 @@ export type VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -355,6 +389,18 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -414,6 +460,21 @@ export type VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -424,6 +485,18 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -589,108 +662,151 @@ export type VoterStakeRegistry = { "type": "u64" } ] - } - ], - "accounts": [ + }, { - "name": "registrar", - "docs": [ - "Instance of a voting rights distributor." + "name": "restakeDeposit", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "depositToken", + "isMut": true, + "isSigner": false + }, + { + "name": "depositAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "governanceProgramId", - "type": "publicKey" - }, - { - "name": "realm", - "type": "publicKey" - }, - { - "name": "realmGoverningTokenMint", - "type": "publicKey" - }, - { - "name": "realmAuthority", - "type": "publicKey" - }, - { - "name": "votingMints", - "docs": [ - "Storage for voting mints and their configuration.", - "The length should be adjusted for one's use case." - ], - "type": { - "array": [ - { - "defined": "VotingMintConfig" - }, - 4 - ] - } - }, - { - "name": "timeOffset", - "docs": [ - "Debug only: time offset, to allow tests to move forward in time." - ], - "type": "i64" - }, - { - "name": "bump", - "type": "u8" + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + }, + { + "name": "lockupPeriod", + "type": { + "defined": "LockupPeriod" } - ] - } + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ] }, { - "name": "voter", - "docs": [ - "User account for minting voting rights." + "name": "claim", + "accounts": [ + { + "name": "rewardPool", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardMint", + "isMut": false, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "is checked on the rewards contract" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "miningOwner", + "isMut": true, + "isSigner": true + }, + { + "name": "registrar", + "isMut": false, + "isSigner": false, + "docs": [ + "therefore their PDA that should sign the CPI call" + ] + }, + { + "name": "userRewardTokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "deposits", - "type": { - "array": [ - { - "defined": "DepositEntry" - }, - 32 - ] - } - }, - { - "name": "voterAuthority", - "type": "publicKey" - }, - { - "name": "registrar", - "type": "publicKey" - }, - { - "name": "voterBump", - "type": "u8" - }, - { - "name": "voterWeightRecordBump", - "type": "u8" - }, - { - "name": "reserved1", - "type": { - "array": [ - "u8", - 14 - ] - } - } - ] - } + "args": [ + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ], + "returns": "u64" } ], "types": [ @@ -752,227 +868,154 @@ export type VoterStakeRegistry = { } }, { - "name": "DepositEntry", - "docs": [ - "Bookkeeping for a single deposit for a given mint and lockup schedule." - ], + "name": "RewardsInstruction", "type": { - "kind": "struct", - "fields": [ - { - "name": "lockup", - "type": { - "defined": "Lockup" - } - }, - { - "name": "amountDepositedNative", - "docs": [ - "Amount in deposited, in native currency. Withdraws of vested tokens", - "directly reduce this amount.", - "", - "This directly tracks the total amount added by the user. They may", - "never withdraw more than this amount." - ], - "type": "u64" - }, + "kind": "enum", + "variants": [ { - "name": "votingMintConfigIdx", - "type": "u8" + "name": "InitializePool", + "fields": [ + { + "name": "deposit_authority", + "docs": [ + "Account responsible for charging mining owners" + ], + "type": "publicKey" + }, + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account that can distribute money among users after", + "if RewardVault had been filled with rewards" + ], + "type": "publicKey" + } + ] + }, + { + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } + ] }, { - "name": "isUsed", - "type": "bool" + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } + ] + }, + { + "name": "DepositMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to deposit" + ], + "type": "u64" + }, + { + "name": "lockup_period", + "docs": [ + "Lockup Period" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "Claim" + }, + { + "name": "RestakeDeposit", + "fields": [ + { + "name": "lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "amount", + "docs": [ + "Amount of tokens to be restaked" + ], + "type": "u64" + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + } + ] }, { - "name": "reserved1", - "type": { - "array": [ - "u8", - 6 - ] - } + "name": "DistributeRewards" } ] } - }, - { - "name": "Lockup", - "type": { - "kind": "struct", - "fields": [ - { - "name": "startTs", - "docs": [ - "Note, that if start_ts is in the future, the funds are nevertheless", - "locked up!", - "Start of the lockup." - ], - "type": "u64" - }, - { - "name": "endTs", - "docs": [ - "End of the lockup." - ], - "type": "u64" - }, - { - "name": "cooldownEndsAt", - "docs": [ - "End of the cooldown." - ], - "type": "u64" - }, - { - "name": "cooldownRequested", - "type": "bool" - }, - { - "name": "kind", - "docs": [ - "Type of lockup." - ], - "type": { - "defined": "LockupKind" - } - }, - { - "name": "period", - "docs": [ - "Type of lockup" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "reserved1", - "docs": [ - "Padding after period to align the struct size to 8 bytes" - ], - "type": { - "array": [ - "u8", - 5 - ] - } - } - ] - } - }, - { - "name": "VotingMintConfig", - "docs": [ - "Exchange rate for an asset that can be used to mint voting rights.", - "", - "See documentation of configure_voting_mint for details on how", - "native token amounts convert to vote weight." - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "mint", - "docs": [ - "Mint for this entry." - ], - "type": "publicKey" - }, - { - "name": "grantAuthority", - "docs": [ - "The authority that is allowed to push grants into voters" - ], - "type": "publicKey" - }, - { - "name": "baselineVoteWeightScaledFactor", - "docs": [ - "Vote weight factor for all funds in the account, no matter if locked or not.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "docs": [ - "Maximum extra vote weight factor for lockups.", - "", - "This is the extra votes gained for lockups lasting lockup_saturation_secs or", - "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", - "based on lockup_time divided by lockup_saturation_secs.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "docs": [ - "Number of seconds of lockup needed to reach the maximum lockup bonus." - ], - "type": "u64" - }, - { - "name": "digitShift", - "docs": [ - "Number of digits to shift native amounts, applying a 10^digit_shift factor." - ], - "type": "i8" - }, - { - "name": "padding", - "type": { - "array": [ - "u8", - 7 - ] - } - } - ] - } - }, - { - "name": "LockupPeriod", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "ThreeMonths" - }, - { - "name": "SixMonths" - }, - { - "name": "OneYear" - }, - { - "name": "Flex" - } - ] - } - }, - { - "name": "LockupKind", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "Constant" - } - ] - } - } - ], - "events": [ + } + ], + "events": [ { "name": "VoterInfo", "fields": [ @@ -1027,208 +1070,6 @@ export type VoterStakeRegistry = { } ] } - ], - "errors": [ - { - "code": 6000, - "name": "InvalidRate", - "msg": "Exchange rate must be greater than zero" - }, - { - "code": 6001, - "name": "RatesFull", - "msg": "" - }, - { - "code": 6002, - "name": "VotingMintNotFound", - "msg": "" - }, - { - "code": 6003, - "name": "DepositEntryNotFound", - "msg": "" - }, - { - "code": 6004, - "name": "DepositEntryFull", - "msg": "" - }, - { - "code": 6005, - "name": "VotingTokenNonZero", - "msg": "" - }, - { - "code": 6006, - "name": "OutOfBoundsDepositEntryIndex", - "msg": "" - }, - { - "code": 6007, - "name": "UnusedDepositEntryIndex", - "msg": "" - }, - { - "code": 6008, - "name": "InsufficientUnlockedTokens", - "msg": "" - }, - { - "code": 6009, - "name": "UnableToConvert", - "msg": "" - }, - { - "code": 6010, - "name": "InvalidLockupPeriod", - "msg": "" - }, - { - "code": 6011, - "name": "InvalidEndTs", - "msg": "" - }, - { - "code": 6012, - "name": "InvalidDays", - "msg": "" - }, - { - "code": 6013, - "name": "VotingMintConfigIndexAlreadyInUse", - "msg": "" - }, - { - "code": 6014, - "name": "OutOfBoundsVotingMintConfigIndex", - "msg": "" - }, - { - "code": 6015, - "name": "InvalidDecimals", - "msg": "Exchange rate decimals cannot be larger than registrar decimals" - }, - { - "code": 6016, - "name": "InvalidToDepositAndWithdrawInOneSlot", - "msg": "" - }, - { - "code": 6017, - "name": "ShouldBeTheFirstIxInATx", - "msg": "" - }, - { - "code": 6018, - "name": "ForbiddenCpi", - "msg": "" - }, - { - "code": 6019, - "name": "InvalidMint", - "msg": "" - }, - { - "code": 6020, - "name": "DebugInstruction", - "msg": "" - }, - { - "code": 6021, - "name": "ClawbackNotAllowedOnDeposit", - "msg": "" - }, - { - "code": 6022, - "name": "DepositStillLocked", - "msg": "" - }, - { - "code": 6023, - "name": "InvalidAuthority", - "msg": "" - }, - { - "code": 6024, - "name": "InvalidTokenOwnerRecord", - "msg": "" - }, - { - "code": 6025, - "name": "InvalidRealmAuthority", - "msg": "" - }, - { - "code": 6026, - "name": "VoterWeightOverflow", - "msg": "" - }, - { - "code": 6027, - "name": "LockupSaturationMustBePositive", - "msg": "" - }, - { - "code": 6028, - "name": "VotingMintConfiguredWithDifferentIndex", - "msg": "" - }, - { - "code": 6029, - "name": "InternalProgramError", - "msg": "" - }, - { - "code": 6030, - "name": "InsufficientLockedTokens", - "msg": "" - }, - { - "code": 6031, - "name": "MustKeepTokensLocked", - "msg": "" - }, - { - "code": 6032, - "name": "InvalidLockupKind", - "msg": "" - }, - { - "code": 6033, - "name": "InvalidChangeToClawbackDepositEntry", - "msg": "" - }, - { - "code": 6034, - "name": "InternalErrorBadLockupVoteWeight", - "msg": "" - }, - { - "code": 6035, - "name": "DepositStartTooFarInFuture", - "msg": "" - }, - { - "code": 6036, - "name": "VaultTokenNonZero", - "msg": "" - }, - { - "code": 6037, - "name": "InvalidTimestampArguments", - "msg": "" - }, - { - "code": 6038, - "name": "UnlockMustBeCalledFirst", - "msg": "" - }, - { - "code": 6039, - "name": "UnlockAlreadyRequested", - "msg": "" - } ] }; @@ -1407,7 +1248,11 @@ export const IDL: VoterStakeRegistry = { { "name": "registrar", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "Also, Registrar plays the role of deposit_authority on the Rewards Contract,", + "therefore their PDA that should sign the CPI call" + ] }, { "name": "voter", @@ -1455,6 +1300,21 @@ export const IDL: VoterStakeRegistry = { "docs": [ "NOTE: this account is currently unused" ] + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -1579,6 +1439,21 @@ export const IDL: VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -1589,6 +1464,18 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -1648,6 +1535,21 @@ export const IDL: VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -1658,6 +1560,18 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -1823,108 +1737,151 @@ export const IDL: VoterStakeRegistry = { "type": "u64" } ] - } - ], - "accounts": [ + }, { - "name": "registrar", - "docs": [ - "Instance of a voting rights distributor." - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "governanceProgramId", - "type": "publicKey" - }, - { - "name": "realm", - "type": "publicKey" - }, - { - "name": "realmGoverningTokenMint", - "type": "publicKey" - }, - { - "name": "realmAuthority", - "type": "publicKey" - }, - { - "name": "votingMints", - "docs": [ - "Storage for voting mints and their configuration.", - "The length should be adjusted for one's use case." - ], - "type": { - "array": [ - { - "defined": "VotingMintConfig" - }, - 4 - ] - } - }, - { - "name": "timeOffset", - "docs": [ - "Debug only: time offset, to allow tests to move forward in time." - ], - "type": "i64" - }, - { - "name": "bump", - "type": "u8" + "name": "restakeDeposit", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "depositToken", + "isMut": true, + "isSigner": false + }, + { + "name": "depositAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + }, + { + "name": "lockupPeriod", + "type": { + "defined": "LockupPeriod" } - ] - } + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ] }, { - "name": "voter", - "docs": [ - "User account for minting voting rights." + "name": "claim", + "accounts": [ + { + "name": "rewardPool", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardMint", + "isMut": false, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "is checked on the rewards contract" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false + }, + { + "name": "miningOwner", + "isMut": true, + "isSigner": true + }, + { + "name": "registrar", + "isMut": false, + "isSigner": false, + "docs": [ + "therefore their PDA that should sign the CPI call" + ] + }, + { + "name": "userRewardTokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "deposits", - "type": { - "array": [ - { - "defined": "DepositEntry" - }, - 32 - ] - } - }, - { - "name": "voterAuthority", - "type": "publicKey" - }, - { - "name": "registrar", - "type": "publicKey" - }, - { - "name": "voterBump", - "type": "u8" - }, - { - "name": "voterWeightRecordBump", - "type": "u8" - }, - { - "name": "reserved1", - "type": { - "array": [ - "u8", - 14 - ] - } - } - ] - } + "args": [ + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ], + "returns": "u64" } ], "types": [ @@ -1986,221 +1943,148 @@ export const IDL: VoterStakeRegistry = { } }, { - "name": "DepositEntry", - "docs": [ - "Bookkeeping for a single deposit for a given mint and lockup schedule." - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "lockup", - "type": { - "defined": "Lockup" - } - }, - { - "name": "amountDepositedNative", - "docs": [ - "Amount in deposited, in native currency. Withdraws of vested tokens", - "directly reduce this amount.", - "", - "This directly tracks the total amount added by the user. They may", - "never withdraw more than this amount." - ], - "type": "u64" - }, - { - "name": "votingMintConfigIdx", - "type": "u8" - }, - { - "name": "isUsed", - "type": "bool" - }, - { - "name": "reserved1", - "type": { - "array": [ - "u8", - 6 - ] - } - } - ] - } - }, - { - "name": "Lockup", - "type": { - "kind": "struct", - "fields": [ - { - "name": "startTs", - "docs": [ - "Note, that if start_ts is in the future, the funds are nevertheless", - "locked up!", - "Start of the lockup." - ], - "type": "u64" - }, - { - "name": "endTs", - "docs": [ - "End of the lockup." - ], - "type": "u64" - }, - { - "name": "cooldownEndsAt", - "docs": [ - "End of the cooldown." - ], - "type": "u64" - }, - { - "name": "cooldownRequested", - "type": "bool" - }, - { - "name": "kind", - "docs": [ - "Type of lockup." - ], - "type": { - "defined": "LockupKind" - } - }, - { - "name": "period", - "docs": [ - "Type of lockup" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "reserved1", - "docs": [ - "Padding after period to align the struct size to 8 bytes" - ], - "type": { - "array": [ - "u8", - 5 - ] - } - } - ] - } - }, - { - "name": "VotingMintConfig", - "docs": [ - "Exchange rate for an asset that can be used to mint voting rights.", - "", - "See documentation of configure_voting_mint for details on how", - "native token amounts convert to vote weight." - ], - "type": { - "kind": "struct", - "fields": [ - { - "name": "mint", - "docs": [ - "Mint for this entry." - ], - "type": "publicKey" - }, - { - "name": "grantAuthority", - "docs": [ - "The authority that is allowed to push grants into voters" - ], - "type": "publicKey" - }, - { - "name": "baselineVoteWeightScaledFactor", - "docs": [ - "Vote weight factor for all funds in the account, no matter if locked or not.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "docs": [ - "Maximum extra vote weight factor for lockups.", - "", - "This is the extra votes gained for lockups lasting lockup_saturation_secs or", - "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", - "based on lockup_time divided by lockup_saturation_secs.", - "", - "In 1/SCALED_FACTOR_BASE units." - ], - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "docs": [ - "Number of seconds of lockup needed to reach the maximum lockup bonus." - ], - "type": "u64" - }, - { - "name": "digitShift", - "docs": [ - "Number of digits to shift native amounts, applying a 10^digit_shift factor." - ], - "type": "i8" - }, - { - "name": "padding", - "type": { - "array": [ - "u8", - 7 - ] - } - } - ] - } - }, - { - "name": "LockupPeriod", + "name": "RewardsInstruction", "type": { "kind": "enum", "variants": [ { - "name": "None" - }, - { - "name": "ThreeMonths" - }, - { - "name": "SixMonths" - }, - { - "name": "OneYear" + "name": "InitializePool", + "fields": [ + { + "name": "deposit_authority", + "docs": [ + "Account responsible for charging mining owners" + ], + "type": "publicKey" + }, + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account that can distribute money among users after", + "if RewardVault had been filled with rewards" + ], + "type": "publicKey" + } + ] + }, + { + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } + ] }, { - "name": "Flex" - } - ] - } - }, - { - "name": "LockupKind", - "type": { - "kind": "enum", - "variants": [ - { - "name": "None" + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } + ] + }, + { + "name": "DepositMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to deposit" + ], + "type": "u64" + }, + { + "name": "lockup_period", + "docs": [ + "Lockup Period" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] + }, + { + "name": "Claim" + }, + { + "name": "RestakeDeposit", + "fields": [ + { + "name": "lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "amount", + "docs": [ + "Amount of tokens to be restaked" + ], + "type": "u64" + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + } + ] }, { - "name": "Constant" + "name": "DistributeRewards" } ] } @@ -2261,207 +2145,5 @@ export const IDL: VoterStakeRegistry = { } ] } - ], - "errors": [ - { - "code": 6000, - "name": "InvalidRate", - "msg": "Exchange rate must be greater than zero" - }, - { - "code": 6001, - "name": "RatesFull", - "msg": "" - }, - { - "code": 6002, - "name": "VotingMintNotFound", - "msg": "" - }, - { - "code": 6003, - "name": "DepositEntryNotFound", - "msg": "" - }, - { - "code": 6004, - "name": "DepositEntryFull", - "msg": "" - }, - { - "code": 6005, - "name": "VotingTokenNonZero", - "msg": "" - }, - { - "code": 6006, - "name": "OutOfBoundsDepositEntryIndex", - "msg": "" - }, - { - "code": 6007, - "name": "UnusedDepositEntryIndex", - "msg": "" - }, - { - "code": 6008, - "name": "InsufficientUnlockedTokens", - "msg": "" - }, - { - "code": 6009, - "name": "UnableToConvert", - "msg": "" - }, - { - "code": 6010, - "name": "InvalidLockupPeriod", - "msg": "" - }, - { - "code": 6011, - "name": "InvalidEndTs", - "msg": "" - }, - { - "code": 6012, - "name": "InvalidDays", - "msg": "" - }, - { - "code": 6013, - "name": "VotingMintConfigIndexAlreadyInUse", - "msg": "" - }, - { - "code": 6014, - "name": "OutOfBoundsVotingMintConfigIndex", - "msg": "" - }, - { - "code": 6015, - "name": "InvalidDecimals", - "msg": "Exchange rate decimals cannot be larger than registrar decimals" - }, - { - "code": 6016, - "name": "InvalidToDepositAndWithdrawInOneSlot", - "msg": "" - }, - { - "code": 6017, - "name": "ShouldBeTheFirstIxInATx", - "msg": "" - }, - { - "code": 6018, - "name": "ForbiddenCpi", - "msg": "" - }, - { - "code": 6019, - "name": "InvalidMint", - "msg": "" - }, - { - "code": 6020, - "name": "DebugInstruction", - "msg": "" - }, - { - "code": 6021, - "name": "ClawbackNotAllowedOnDeposit", - "msg": "" - }, - { - "code": 6022, - "name": "DepositStillLocked", - "msg": "" - }, - { - "code": 6023, - "name": "InvalidAuthority", - "msg": "" - }, - { - "code": 6024, - "name": "InvalidTokenOwnerRecord", - "msg": "" - }, - { - "code": 6025, - "name": "InvalidRealmAuthority", - "msg": "" - }, - { - "code": 6026, - "name": "VoterWeightOverflow", - "msg": "" - }, - { - "code": 6027, - "name": "LockupSaturationMustBePositive", - "msg": "" - }, - { - "code": 6028, - "name": "VotingMintConfiguredWithDifferentIndex", - "msg": "" - }, - { - "code": 6029, - "name": "InternalProgramError", - "msg": "" - }, - { - "code": 6030, - "name": "InsufficientLockedTokens", - "msg": "" - }, - { - "code": 6031, - "name": "MustKeepTokensLocked", - "msg": "" - }, - { - "code": 6032, - "name": "InvalidLockupKind", - "msg": "" - }, - { - "code": 6033, - "name": "InvalidChangeToClawbackDepositEntry", - "msg": "" - }, - { - "code": 6034, - "name": "InternalErrorBadLockupVoteWeight", - "msg": "" - }, - { - "code": 6035, - "name": "DepositStartTooFarInFuture", - "msg": "" - }, - { - "code": 6036, - "name": "VaultTokenNonZero", - "msg": "" - }, - { - "code": 6037, - "name": "InvalidTimestampArguments", - "msg": "" - }, - { - "code": 6038, - "name": "UnlockMustBeCalledFirst", - "msg": "" - }, - { - "code": 6039, - "name": "UnlockAlreadyRequested", - "msg": "" - } ] }; From f9998f29e59037c007ca4c47fcbf779d31d0095d Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 15:17:58 +0300 Subject: [PATCH 28/59] add initialize_pool cpi invocation on each registrar creation --- .../src/cpi_instructions.rs | 62 +++++++++++- .../src/instructions/create_registrar.rs | 96 ++++++++++++++----- .../src/instructions/create_voter.rs | 4 +- programs/voter-stake-registry/src/lib.rs | 9 +- 4 files changed, 141 insertions(+), 30 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 05b4d776..bffa8ada 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -11,7 +11,7 @@ use solana_program::{ }; pub const REWARD_CONTRACT_ID: Pubkey = - solana_program::pubkey!("J8oa8UUJBydrTKtCdkvwmQQ27ZFDq54zAxWJY5Ey72Ji"); + solana_program::pubkey!("BF5PatmRTQDgEKoXR7iHRbkibEEi83nVM38cUKWzQcTR"); #[derive(Debug, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)] @@ -134,10 +134,61 @@ pub enum RewardsInstruction { DistributeRewards, } +/// This function initializes pool. Some sort of a "root" +/// of the rewards contract +#[allow(clippy::too_many_arguments)] +pub fn initialize_pool<'a>( + program_id: AccountInfo<'a>, + reward_pool: AccountInfo<'a>, + reward_mint: AccountInfo<'a>, + reward_vault: AccountInfo<'a>, + payer: AccountInfo<'a>, + rent: AccountInfo<'a>, + token_program: AccountInfo<'a>, + system_program: AccountInfo<'a>, + deposit_authority: Pubkey, + fill_authority: Pubkey, + distribution_authority: Pubkey, +) -> ProgramResult { + let accounts = vec![ + AccountMeta::new(reward_pool.key(), false), + AccountMeta::new_readonly(reward_mint.key(), false), + AccountMeta::new(reward_vault.key(), false), + AccountMeta::new(payer.key(), true), + AccountMeta::new_readonly(rent.key(), false), + AccountMeta::new_readonly(token_program.key(), false), + AccountMeta::new_readonly(system_program::id(), false), + ]; + + let ix = Instruction::new_with_borsh( + program_id.key(), + &RewardsInstruction::InitializePool { + deposit_authority, + fill_authority, + distribution_authority, + }, + accounts, + ); + + invoke( + &ix, + &[ + reward_pool, + reward_mint, + reward_vault, + payer, + rent, + token_program, + system_program, + program_id, + ], + ) +} + /// Rewards initialize mining #[allow(clippy::too_many_arguments)] pub fn initialize_mining<'a>( - program_id: &Pubkey, + program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, mining_owner: &Pubkey, @@ -152,14 +203,17 @@ pub fn initialize_mining<'a>( ]; let ix = Instruction::new_with_borsh( - *program_id, + program_id.key(), &RewardsInstruction::InitializeMining { mining_owner: *mining_owner, }, accounts, ); - invoke(&ix, &[reward_pool, mining, payer, system_program]) + invoke( + &ix, + &[reward_pool, mining, payer, system_program, program_id], + ) } /// Rewards deposit mining diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index abde5822..8105a7a8 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -1,10 +1,13 @@ use anchor_lang::prelude::*; use anchor_spl::token::Mint; +use anchor_spl::token::Token; use mplx_staking_states::error::*; use mplx_staking_states::state::*; use spl_governance::state::realm; use std::mem::size_of; +use crate::cpi_instructions; + #[derive(Accounts)] pub struct CreateRegistrar<'info> { /// The voting registrar. There can only be a single registrar @@ -32,12 +35,28 @@ pub struct CreateRegistrar<'info> { /// Either the realm community mint or the council mint. pub realm_governing_token_mint: Account<'info, Mint>, pub realm_authority: Signer<'info>, + pub system_program: Program<'info, System>, + pub rent: Sysvar<'info, Rent>, + + /// CHECK: any address is allowed + /// Account that will be created via CPI to the rewards, + /// it's responsible for being a "root" for all entities + /// inside rewards contract + #[account(mut)] + reward_pool: UncheckedAccount<'info>, + + /// CHECK: any address is allowed + /// This account is responsible for storing money for rewards + #[account(mut)] + reward_vault: UncheckedAccount<'info>, #[account(mut)] pub payer: Signer<'info>, - pub system_program: Program<'info, System>, - pub rent: Sysvar<'info, Rent>, + pub token_program: Program<'info, Token>, + + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, } /// Creates a new voting registrar. @@ -47,28 +66,61 @@ pub struct CreateRegistrar<'info> { /// /// To use the registrar, call ConfigVotingMint to register token mints that may be /// used for voting. -pub fn create_registrar(ctx: Context, registrar_bump: u8) -> Result<()> { - let registrar = &mut ctx.accounts.registrar.load_init()?; - require_eq!(registrar_bump, *ctx.bumps.get("registrar").unwrap()); - registrar.bump = registrar_bump; - registrar.governance_program_id = ctx.accounts.governance_program_id.key(); - registrar.realm = ctx.accounts.realm.key(); - registrar.realm_governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); - registrar.realm_authority = ctx.accounts.realm_authority.key(); - registrar.time_offset = 0; +pub fn create_registrar( + ctx: Context, + registrar_bump: u8, + fill_authority: Pubkey, + distribution_authority: Pubkey, +) -> Result<()> { + { + let registrar = &mut ctx.accounts.registrar.load_init()?; + require_eq!(registrar_bump, *ctx.bumps.get("registrar").unwrap()); + registrar.bump = registrar_bump; + registrar.governance_program_id = ctx.accounts.governance_program_id.key(); + registrar.realm = ctx.accounts.realm.key(); + registrar.realm_governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); + registrar.realm_authority = ctx.accounts.realm_authority.key(); + registrar.time_offset = 0; + + // Verify that "realm_authority" is the expected authority on "realm" + // and that the mint matches one of the realm mints too. + let realm = realm::get_realm_data_for_governing_token_mint( + ®istrar.governance_program_id, + &ctx.accounts.realm.to_account_info(), + ®istrar.realm_governing_token_mint, + )?; + require_keys_eq!( + realm.authority.unwrap(), + ctx.accounts.realm_authority.key(), + VsrError::InvalidRealmAuthority + ); + } + + // we should initiate the rewards pool to proceed with + // staking and rewards logic + let rewards_program_id = ctx.accounts.rewards_program.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let reward_mint = ctx.accounts.realm_governing_token_mint.to_account_info(); + let reward_vault = ctx.accounts.reward_vault.to_account_info(); + let payer = ctx.accounts.payer.to_account_info(); + let rent = ctx.accounts.rent.to_account_info(); + let token_program = ctx.accounts.token_program.to_account_info(); + let system_program = ctx.accounts.system_program.to_account_info(); + let deposit_authority = ctx.accounts.registrar.key(); - // Verify that "realm_authority" is the expected authority on "realm" - // and that the mint matches one of the realm mints too. - let realm = realm::get_realm_data_for_governing_token_mint( - ®istrar.governance_program_id, - &ctx.accounts.realm.to_account_info(), - ®istrar.realm_governing_token_mint, + cpi_instructions::initialize_pool( + rewards_program_id, + reward_pool, + reward_mint, + reward_vault, + payer, + rent, + token_program, + system_program, + deposit_authority, + fill_authority, + distribution_authority, )?; - require_keys_eq!( - realm.authority.unwrap(), - ctx.accounts.realm_authority.key(), - VsrError::InvalidRealmAuthority - ); Ok(()) } diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 98474fb5..4e181d32 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -5,7 +5,6 @@ use mplx_staking_states::state::*; use std::mem::size_of; use crate::cpi_instructions; -use crate::cpi_instructions::REWARD_CONTRACT_ID; use crate::voter::VoterWeightRecord; #[derive(Accounts)] @@ -118,9 +117,10 @@ pub fn create_voter( let user = ctx.accounts.voter_authority.key; let system_program = ctx.accounts.system_program.to_account_info(); let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let rewards_program_id = ctx.accounts.rewards_program.to_account_info(); cpi_instructions::initialize_mining( - &REWARD_CONTRACT_ID, + rewards_program_id, reward_pool, mining, user, diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 6f5ac827..0087dbb8 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -61,8 +61,13 @@ pub mod voter_stake_registry { use super::*; - pub fn create_registrar(ctx: Context, registrar_bump: u8) -> Result<()> { - instructions::create_registrar(ctx, registrar_bump) + pub fn create_registrar( + ctx: Context, + registrar_bump: u8, + fill_authority: Pubkey, + distribution_authority: Pubkey, + ) -> Result<()> { + instructions::create_registrar(ctx, registrar_bump, fill_authority, distribution_authority) } pub fn configure_voting_mint( From 8c8da6e55970a79826b695527858ef4933272598 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 15:18:46 +0300 Subject: [PATCH 29/59] update tests accordingly to adding initialize_pool function via cpi --- .../tests/program_test/addin.rs | 40 ++++++++- .../tests/program_test/rewards.rs | 61 +------------- .../tests/test_all_deposits.rs | 29 +++---- .../voter-stake-registry/tests/test_basic.rs | 28 +++---- .../voter-stake-registry/tests/test_claim.rs | 37 ++++----- .../tests/test_deposit_constant.rs | 57 ++++++------- .../tests/test_deposit_no_locking.rs | 29 +++---- .../tests/test_internal_transfers.rs | 58 ++++++------- .../voter-stake-registry/tests/test_lockup.rs | 82 ++++++++----------- .../tests/test_log_voter_info.rs | 29 +++---- .../voter-stake-registry/tests/test_voting.rs | 28 +++---- 11 files changed, 192 insertions(+), 286 deletions(-) diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index c3ff7661..dface60e 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; +use anchor_lang::Key; use mplx_staking_states::state::LockupPeriod; use solana_sdk::pubkey::Pubkey; @@ -45,7 +46,10 @@ impl AddinCookie { realm: &GovernanceRealmCookie, authority: &Keypair, payer: &Keypair, - ) -> RegistrarCookie { + fill_authority: &Pubkey, + distribution_authority: &Pubkey, + rewards_program: &Pubkey, + ) -> (RegistrarCookie, Pubkey) { let community_token_mint = realm.community_token_mint.pubkey.unwrap(); let (registrar, registrar_bump) = Pubkey::find_program_address( @@ -58,7 +62,29 @@ impl AddinCookie { ); let data = anchor_lang::InstructionData::data( - &voter_stake_registry::instruction::CreateRegistrar { registrar_bump }, + &voter_stake_registry::instruction::CreateRegistrar { + registrar_bump, + fill_authority: *fill_authority, + distribution_authority: *distribution_authority, + }, + ); + + let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( + &[ + "reward_pool".as_bytes(), + ®istrar.key().to_bytes(), + &fill_authority.key().to_bytes(), + ], + rewards_program, + ); + + let (reward_vault, _reward_vault_bump) = Pubkey::find_program_address( + &[ + "vault".as_bytes(), + &reward_pool.key().to_bytes(), + &community_token_mint.to_bytes(), + ], + rewards_program, ); let accounts = anchor_lang::ToAccountMetas::to_account_metas( @@ -71,6 +97,10 @@ impl AddinCookie { payer: payer.pubkey(), system_program: solana_sdk::system_program::id(), rent: solana_program::sysvar::rent::id(), + reward_pool, + reward_vault, + token_program: spl_token::id(), + rewards_program: *rewards_program, }, None, ); @@ -86,14 +116,16 @@ impl AddinCookie { .await .unwrap(); - RegistrarCookie { + let registrar_cookie = RegistrarCookie { address: registrar, authority: realm.authority, mint: realm.community_token_mint.clone(), registrar_bump, realm_pubkey: realm.realm, realm_governing_token_mint_pubkey: community_token_mint, - } + }; + + (registrar_cookie, reward_pool) } pub async fn internal_transfer_unlocked( diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index dae64fbc..d1ea354e 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -5,7 +5,6 @@ use anchor_lang::AnchorDeserialize; use mplx_staking_states::state::LockupPeriod; use solana_program_test::*; use solana_sdk::program_pack::IsInitialized; -use solana_sdk::sysvar; use solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, @@ -23,61 +22,6 @@ pub struct RewardsCookie { } impl RewardsCookie { - pub async fn initialize_pool( - &self, - deposit_authority: &Pubkey, - fill_authority: &Pubkey, - distribution_authority: &Pubkey, - payer: &Keypair, - reward_mint: &Pubkey, - ) -> std::result::Result<(Pubkey, Pubkey), BanksClientError> { - let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( - &[ - "reward_pool".as_bytes(), - &deposit_authority.key().to_bytes(), - &fill_authority.key().to_bytes(), - ], - &self.program_id, - ); - - let (reward_vault, _reward_vault_bump) = Pubkey::find_program_address( - &[ - "vault".as_bytes(), - &reward_pool.key().to_bytes(), - &reward_mint.key().to_bytes(), - ], - &self.program_id, - ); - - let accounts = vec![ - AccountMeta::new(reward_pool, false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(reward_vault, false), - AccountMeta::new(payer.pubkey(), true), - AccountMeta::new_readonly(sysvar::rent::id(), false), - AccountMeta::new_readonly(spl_token::id(), false), - AccountMeta::new_readonly(system_program::id(), false), - ]; - - let ix = Instruction::new_with_borsh( - self.program_id, - &RewardsInstruction::InitializePool { - deposit_authority: *deposit_authority, - fill_authority: *fill_authority, - distribution_authority: *distribution_authority, - }, - accounts, - ); - - let signers = vec![payer]; - - self.solana - .process_transaction(&[ix], Some(&signers)) - .await?; - - Ok((reward_pool, reward_vault)) - } - pub async fn fill_vault( &self, reward_pool: &Pubkey, @@ -127,10 +71,9 @@ impl RewardsCookie { &self, reward_pool: &Pubkey, reward_mint: &Pubkey, - reward_vault: &Pubkey, distribute_authority: &Keypair, ) -> std::result::Result<(), BanksClientError> { - let (vault, _bump) = Pubkey::find_program_address( + let (reward_vault, _bump) = Pubkey::find_program_address( &[ "vault".as_bytes(), &reward_pool.to_bytes(), @@ -142,7 +85,7 @@ impl RewardsCookie { let accounts = vec![ AccountMeta::new(*reward_pool, false), AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(*reward_vault, false), + AccountMeta::new(reward_vault, false), AccountMeta::new_readonly(distribute_authority.pubkey(), true), ]; diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index cd168043..4597de15 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -29,8 +29,18 @@ async fn test_all_deposits() -> Result<(), TransportError> { .create_token_owner_record(voter_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -48,21 +58,6 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 3b85d0bf..90c13e18 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -29,9 +29,18 @@ async fn test_basic() -> Result<(), TransportError> { .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = context + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context .addin - .create_registrar(&realm, &realm_authority, payer) + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; context .addin @@ -66,21 +75,6 @@ async fn test_basic() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index b8532871..a91e8560 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -29,9 +29,18 @@ async fn successeful_claim() -> Result<(), TransportError> { .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = context + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context .addin - .create_registrar(&realm, &realm_authority, payer) + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; context .addin @@ -66,21 +75,6 @@ async fn successeful_claim() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( @@ -154,6 +148,8 @@ async fn successeful_claim() -> Result<(), TransportError> { .unwrap() .unix_timestamp as u64 + 86400; + + let reward_mint = &realm.community_token_mint.pubkey.unwrap(); context .rewards .fill_vault( @@ -169,12 +165,7 @@ async fn successeful_claim() -> Result<(), TransportError> { context .rewards - .distribute_rewards( - &rewards_pool, - reward_mint, - &rewards_vault, - &distribution_authority, - ) + .distribute_rewards(&rewards_pool, reward_mint, &distribution_authority) .await?; context diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index ff63c3fa..b76d7301 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -64,8 +64,18 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -83,21 +93,6 @@ async fn test_deposit_constant() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( @@ -237,8 +232,18 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { .create_token_owner_record(voter_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -256,20 +261,6 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index bb41b71b..029af101 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -69,8 +69,18 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .create_token_owner_record(deposit2_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -88,21 +98,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining_voter = find_deposit_mining_addr( diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index a37acb7e..6870605e 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -63,8 +63,18 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -82,21 +92,6 @@ async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( @@ -218,9 +213,18 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> let token_owner_record = realm .create_token_owner_record(voter_authority.pubkey(), payer) .await; - - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -238,20 +242,6 @@ async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 35c73565..d2a16c1d 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -29,9 +29,18 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = context + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context .addin - .create_registrar(&realm, &realm_authority, payer) + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; context .addin @@ -66,21 +75,6 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( @@ -180,9 +174,18 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { .create_token_owner_record(voter_authority.pubkey(), payer) .await; - let registrar = context + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context .addin - .create_registrar(&realm, &realm_authority, payer) + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; context .addin @@ -217,20 +220,6 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, @@ -338,9 +327,18 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran .create_token_owner_record(voter_authority.pubkey(), payer) .await; - let registrar = context + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context .addin - .create_registrar(&realm, &realm_authority, payer) + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; context .addin @@ -375,20 +373,6 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; let deposit_mining = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 8ea4df5c..44374c6d 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -50,8 +50,18 @@ async fn test_log_voter_info() -> Result<(), TransportError> { .create_token_owner_record(deposit_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -69,21 +79,6 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; - // TODO: ??? voter_authority == deposit_authority ??? let voter_authority = deposit_authority; let deposit_mining = find_deposit_mining_addr( diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 2000af48..15a58d22 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -34,8 +34,18 @@ async fn test_voting() -> Result<(), TransportError> { .create_token_owner_record(voter2_authority.pubkey(), payer) .await; - let registrar = addin - .create_registrar(&realm, &realm_authority, payer) + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) .await; let mngo_voting_mint = addin .configure_voting_mint( @@ -68,20 +78,6 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let reward_mint = &context.mints[0].pubkey.unwrap(); - let pool_deposit_authority = ®istrar.address; - let (rewards_pool, _rewards_vault) = context - .rewards - .initialize_pool( - pool_deposit_authority, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - payer, - reward_mint, - ) - .await?; let deposit_mining_voter = find_deposit_mining_addr( &voter_authority.pubkey(), &rewards_pool, From 5f6285f4b0bf6c8c558eea03b56397242c42a5a1 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 15:19:27 +0300 Subject: [PATCH 30/59] update IDL --- idl/voter_stake_registry.ts | 80 +++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 4be00c0f..8244d63a 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -93,18 +93,46 @@ export type VoterStakeRegistry = { "isMut": false, "isSigner": true }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "Account that will be created via CPI to the rewards,", + "it's responsible for being a \"root\" for all entities", + "inside rewards contract" + ] + }, + { + "name": "rewardVault", + "isMut": true, + "isSigner": false, + "docs": [ + "This account is responsible for storing money for rewards" + ] + }, { "name": "payer", "isMut": true, "isSigner": true }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "rent", + "name": "rewardsProgram", "isMut": false, "isSigner": false } @@ -113,6 +141,14 @@ export type VoterStakeRegistry = { { "name": "registrarBump", "type": "u8" + }, + { + "name": "fillAuthority", + "type": "publicKey" + }, + { + "name": "distributionAuthority", + "type": "publicKey" } ] }, @@ -1168,18 +1204,46 @@ export const IDL: VoterStakeRegistry = { "isMut": false, "isSigner": true }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "Account that will be created via CPI to the rewards,", + "it's responsible for being a \"root\" for all entities", + "inside rewards contract" + ] + }, + { + "name": "rewardVault", + "isMut": true, + "isSigner": false, + "docs": [ + "This account is responsible for storing money for rewards" + ] + }, { "name": "payer", "isMut": true, "isSigner": true }, { - "name": "systemProgram", + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "rent", + "name": "rewardsProgram", "isMut": false, "isSigner": false } @@ -1188,6 +1252,14 @@ export const IDL: VoterStakeRegistry = { { "name": "registrarBump", "type": "u8" + }, + { + "name": "fillAuthority", + "type": "publicKey" + }, + { + "name": "distributionAuthority", + "type": "publicKey" } ] }, From 3591690351a84aca04c8e2ab68afdb18229ce02d Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 16:13:11 +0300 Subject: [PATCH 31/59] add more comment about passed into ixs accounts --- .../voter-stake-registry/src/instructions/claim.rs | 3 +++ .../src/instructions/create_registrar.rs | 2 ++ .../src/instructions/create_voter.rs | 2 ++ .../voter-stake-registry/src/instructions/deposit.rs | 2 ++ .../src/instructions/extend_deposit.rs | 10 ++-------- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index c64aa496..b7c06bbf 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -10,6 +10,7 @@ use solana_program::program::get_return_data; #[derive(Accounts)] pub struct Claim<'info> { /// CHECK: Reward Pool PDA will be checked in the rewards contract + /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) pub reward_pool: UncheckedAccount<'info>, /// CHECK: Rewards mint addr will be checked in the rewards contract @@ -18,9 +19,11 @@ pub struct Claim<'info> { #[account(mut)] /// CHECK: Rewards vault is used as a source of rewards and /// is checked on the rewards contract + /// PDA("vault", reward_pool, reward_mint) pub vault: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract + /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index 8105a7a8..573f2cfb 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -42,11 +42,13 @@ pub struct CreateRegistrar<'info> { /// Account that will be created via CPI to the rewards, /// it's responsible for being a "root" for all entities /// inside rewards contract + /// It's the PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) #[account(mut)] reward_pool: UncheckedAccount<'info>, /// CHECK: any address is allowed /// This account is responsible for storing money for rewards + /// PDA("vault", reward_pool, reward_mint) #[account(mut)] reward_vault: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 4e181d32..ef60cc75 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -50,10 +50,12 @@ pub struct CreateVoter<'info> { pub instructions: UncheckedAccount<'info>, /// CHECK: Reward Pool PDA will be checked in the rewards contract + /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract + /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 31bee5f9..ee703e05 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -35,10 +35,12 @@ pub struct Deposit<'info> { pub token_program: Program<'info, Token>, /// CHECK: Reward Pool PDA will be checked in the rewards contract + /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract + /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index aa58a6a8..745d76c6 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -18,19 +18,12 @@ pub struct RestakeDeposit<'info> { bump = voter.load()?.voter_bump, has_one = registrar)] pub voter: AccountLoader<'info, Voter>, - - #[account( - mut, - associated_token::authority = voter, - associated_token::mint = deposit_token.mint, - )] - pub vault: Box>, - #[account( mut, constraint = deposit_token.owner == deposit_authority.key(), )] pub deposit_token: Box>, + /// The owner of the deposit and its reward's mining account pub deposit_authority: Signer<'info>, pub token_program: Program<'info, Token>, @@ -40,6 +33,7 @@ pub struct RestakeDeposit<'info> { pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract + /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, From 9513400ae5448d4a707ea99290bf9b1bb524cd71 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 16:14:49 +0300 Subject: [PATCH 32/59] update idl --- idl/voter_stake_registry.ts | 108 +++++++++++++++++++++++++----------- 1 file changed, 76 insertions(+), 32 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 8244d63a..1bd7b9da 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -110,7 +110,8 @@ export type VoterStakeRegistry = { "docs": [ "Account that will be created via CPI to the rewards,", "it's responsible for being a \"root\" for all entities", - "inside rewards contract" + "inside rewards contract", + "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" ] }, { @@ -118,7 +119,8 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "This account is responsible for storing money for rewards" + "This account is responsible for storing money for rewards", + "PDA(\"vault\", reward_pool, reward_mint)" ] }, { @@ -265,12 +267,18 @@ export type VoterStakeRegistry = { { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -404,12 +412,18 @@ export type VoterStakeRegistry = { { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -712,11 +726,6 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false }, - { - "name": "vault", - "isMut": true, - "isSigner": false - }, { "name": "depositToken", "isMut": true, @@ -725,7 +734,10 @@ export type VoterStakeRegistry = { { "name": "depositAuthority", "isMut": false, - "isSigner": true + "isSigner": true, + "docs": [ + "The owner of the deposit and its reward's mining account" + ] }, { "name": "tokenProgram", @@ -740,7 +752,10 @@ export type VoterStakeRegistry = { { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -779,7 +794,10 @@ export type VoterStakeRegistry = { { "name": "rewardPool", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "rewardMint", @@ -791,13 +809,17 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "is checked on the rewards contract" + "is checked on the rewards contract", + "PDA(\"vault\", reward_pool, reward_mint)" ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "miningOwner", @@ -1221,7 +1243,8 @@ export const IDL: VoterStakeRegistry = { "docs": [ "Account that will be created via CPI to the rewards,", "it's responsible for being a \"root\" for all entities", - "inside rewards contract" + "inside rewards contract", + "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" ] }, { @@ -1229,7 +1252,8 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "This account is responsible for storing money for rewards" + "This account is responsible for storing money for rewards", + "PDA(\"vault\", reward_pool, reward_mint)" ] }, { @@ -1376,12 +1400,18 @@ export const IDL: VoterStakeRegistry = { { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -1515,12 +1545,18 @@ export const IDL: VoterStakeRegistry = { { "name": "rewardPool", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -1823,11 +1859,6 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false }, - { - "name": "vault", - "isMut": true, - "isSigner": false - }, { "name": "depositToken", "isMut": true, @@ -1836,7 +1867,10 @@ export const IDL: VoterStakeRegistry = { { "name": "depositAuthority", "isMut": false, - "isSigner": true + "isSigner": true, + "docs": [ + "The owner of the deposit and its reward's mining account" + ] }, { "name": "tokenProgram", @@ -1851,7 +1885,10 @@ export const IDL: VoterStakeRegistry = { { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -1890,7 +1927,10 @@ export const IDL: VoterStakeRegistry = { { "name": "rewardPool", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "rewardMint", @@ -1902,13 +1942,17 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "is checked on the rewards contract" + "is checked on the rewards contract", + "PDA(\"vault\", reward_pool, reward_mint)" ] }, { "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "miningOwner", From 6334b1a977622c6b69942a5baf820f5769afb6b1 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 11 Jun 2024 21:07:10 +0300 Subject: [PATCH 33/59] update idl --- idl/voter_stake_registry.ts | 2735 +++++++++++++++++++---------------- 1 file changed, 1493 insertions(+), 1242 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 1bd7b9da..678fece3 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -1,53 +1,53 @@ export type VoterStakeRegistry = { - "version": "0.2.4", - "name": "voter_stake_registry", - "docs": [ - "# Introduction", - "", - "The governance registry is an \"addin\" to the SPL governance program that", - "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.", - "", - "The flow for voting with this program is as follows:", - "", - "- 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", + version: '0.2.4' + name: 'voter_stake_registry' + docs: [ + '# Introduction', + '', + 'The governance registry is an "addin" to the SPL governance program that', + '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.', + '', + 'The flow for voting with this program is as follows:', + '', + '- 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.", - "", - "# 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." - ], - "instructions": [ + '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.', + '', + '# 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.' + ] + instructions: [ { "name": "createRegistrar", "accounts": [ @@ -89,30 +89,39 @@ export type VoterStakeRegistry = { ] }, { - "name": "realmAuthority", - "isMut": false, - "isSigner": true + name: 'registrar' + isMut: true + isSigner: false + docs: [ + 'The voting registrar. There can only be a single registrar', + 'per governance realm and governing mint.' + ] }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'realm' + isMut: false + isSigner: false + docs: [ + 'An spl-governance realm', + '', + '- realm is owned by the governance_program_id', + '- realm_governing_token_mint must be the community or council mint', + '- realm_authority is realm.authority' + ] }, { - "name": "rent", - "isMut": false, - "isSigner": false + name: 'governanceProgramId' + isMut: false + isSigner: false + docs: [ + 'The program id of the spl-governance program the realm belongs to.' + ] }, { - "name": "rewardPool", - "isMut": true, - "isSigner": false, - "docs": [ - "Account that will be created via CPI to the rewards,", - "it's responsible for being a \"root\" for all entities", - "inside rewards contract", - "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" - ] + name: 'realmGoverningTokenMint' + isMut: false + isSigner: false + docs: ['Either the realm community mint or the council mint.'] }, { "name": "rewardVault", @@ -168,12 +177,10 @@ export type VoterStakeRegistry = { "isSigner": true }, { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Tokens of this mint will produce vote weight" - ] + name: 'mint' + isMut: false + isSigner: false + docs: ['Tokens of this mint will produce vote weight'] } ], "args": [ @@ -247,14 +254,23 @@ export type VoterStakeRegistry = { "isSigner": true }, { - "name": "systemProgram", - "isMut": false, - "isSigner": false + name: 'voterAuthority' + isMut: false + isSigner: true + docs: [ + 'The authority controling the voter. Must be the same as the', + '`governing_token_owner` in the token owner record used with', + 'spl-governance.' + ] }, { - "name": "rent", - "isMut": false, - "isSigner": false + name: 'voterWeightRecord' + isMut: true + isSigner: false + docs: [ + 'The voter weight record is the account that will be shown to spl-governance', + 'to prove how much vote weight the voter has. See update_voter_weight_record.' + ] }, { "name": "instructions", @@ -281,9 +297,10 @@ export type VoterStakeRegistry = { ] }, { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false + name: 'instructions' + isMut: false + isSigner: false + docs: ['NOTE: this account is currently unused'] } ], "args": [ @@ -369,9 +386,9 @@ export type VoterStakeRegistry = { } }, { - "name": "period", - "type": { - "defined": "LockupPeriod" + name: 'period' + type: { + defined: 'LockupPeriod' } } ] @@ -426,15 +443,28 @@ export type VoterStakeRegistry = { ] }, { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ + name: 'tokenOwnerRecord' + isMut: false + isSigner: false + docs: [ + 'The token_owner_record for the voter_authority. This is needed', + 'to be able to forbid withdraws while the voter is engaged with', + 'a vote or has an open proposal.', + '', + '- owned by registrar.governance_program_id', + '- for the registrar.realm', + '- for the registrar.realm_governing_token_mint', + '- governing_token_owner is voter_authority' + ] + }, { - "name": "depositEntryIndex", - "type": "u8" + name: 'voterWeightRecord' + isMut: true + isSigner: false + docs: [ + 'Withdraws must update the voter weight record, to prevent a stale', + 'record being used to vote after the withdraw.' + ] }, { "name": "amount", @@ -455,416 +485,267 @@ export type VoterStakeRegistry = { ] }, { - "name": "withdraw", - "accounts": [ + name: 'closeDepositEntry' + accounts: [ { "name": "registrar", "isMut": false, "isSigner": false }, { - "name": "voter", - "isMut": true, - "isSigner": false - }, + name: 'voterAuthority' + isMut: false + isSigner: true + } + ] + args: [ + { + name: 'depositEntryIndex' + type: 'u8' + } + ] + }, + { + name: 'updateVoterWeightRecord' + accounts: [ { "name": "voterAuthority", "isMut": false, "isSigner": true }, { - "name": "tokenOwnerRecord", - "isMut": false, - "isSigner": false, - "docs": [ - "The token_owner_record for the voter_authority. This is needed", - "to be able to forbid withdraws while the voter is engaged with", - "a vote or has an open proposal.", - "", - "- owned by registrar.governance_program_id", - "- for the registrar.realm", - "- for the registrar.realm_governing_token_mint", - "- governing_token_owner is voter_authority" - ] - }, - { - "name": "voterWeightRecord", - "isMut": true, - "isSigner": false, - "docs": [ - "Withdraws must update the voter weight record, to prevent a stale", - "record being used to vote after the withdraw." - ] + name: 'voter' + isMut: false + isSigner: false }, { - "name": "vault", - "isMut": true, - "isSigner": false + name: 'voterWeightRecord' + isMut: true + isSigner: false }, { - "name": "destination", - "isMut": true, - "isSigner": false - }, + name: 'systemProgram' + isMut: false + isSigner: false + } + ] + args: [] + }, + { + name: 'unlockTokens' + accounts: [ { - "name": "tokenProgram", + "name": "registrar", "isMut": false, "isSigner": false }, { - "name": "rewardPool", + "name": "voter", "isMut": true, "isSigner": false }, { - "name": "depositMining", + "name": "depositToken", "isMut": true, "isSigner": false }, { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false + name: 'depositEntryIndex' + type: 'u8' } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" - }, + ] + }, + { + name: 'closeVoter' + accounts: [ { - "name": "amount", - "type": "u64" + "name": "rewardPool", + "isMut": false, + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { - "name": "registrarBump", - "type": "u8" + "name": "rewardMint", + "isMut": false, + "isSigner": false }, { - "name": "realmGoverningMintPubkey", - "type": "publicKey" + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "is checked on the rewards contract", + "PDA(\"vault\", reward_pool, reward_mint)" + ] }, { - "name": "realmPubkey", - "type": "publicKey" - } - ] - }, - { - "name": "closeDepositEntry", - "accounts": [ - { - "name": "voter", + "name": "depositMining", "isMut": true, - "isSigner": false + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { - "name": "voterAuthority", - "isMut": false, + "name": "miningOwner", + "isMut": true, "isSigner": true - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" - } - ] - }, - { - "name": "updateVoterWeightRecord", - "accounts": [ + }, { "name": "registrar", "isMut": false, - "isSigner": false + "isSigner": false, + "docs": [ + "therefore their PDA that should sign the CPI call" + ] }, { - "name": "voter", - "isMut": false, + "name": "userRewardTokenAccount", + "isMut": true, "isSigner": false }, { - "name": "voterWeightRecord", - "isMut": true, + "name": "tokenProgram", + "isMut": false, "isSigner": false }, { - "name": "systemProgram", + "name": "rewardsProgram", "isMut": false, "isSigner": false } - ], - "args": [] + ] }, { - "name": "unlockTokens", - "accounts": [ + name: 'internalTransferUnlocked' + accounts: [ { - "name": "registrar", - "isMut": false, - "isSigner": false + name: 'registrar' + isMut: false + isSigner: false }, { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'voter' + isMut: true + isSigner: false }, { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" + name: 'voterAuthority' + isMut: false + isSigner: true } ] - }, - { - "name": "closeVoter", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, + args: [ { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'sourceDepositEntryIndex' + type: 'u8' }, { - "name": "voterAuthority", - "isMut": false, - "isSigner": true + name: 'targetDepositEntryIndex' + type: 'u8' }, { - "name": "solDestination", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [] - }, - { - "name": "logVoterInfo", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "depositEntryBegin", - "type": "u8" - }, - { - "name": "depositEntryCount", - "type": "u8" - } - ] - }, - { - "name": "internalTransferUnlocked", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", - "isMut": true, - "isSigner": false - }, - { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - } - ], - "args": [ - { - "name": "sourceDepositEntryIndex", - "type": "u8" - }, - { - "name": "targetDepositEntryIndex", - "type": "u8" - }, - { - "name": "amount", - "type": "u64" + name: 'amount' + type: 'u64' } ] - }, + } + ] + accounts: [ { - "name": "restakeDeposit", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", - "isMut": true, - "isSigner": false - }, - { - "name": "depositToken", - "isMut": true, - "isSigner": false - }, - { - "name": "depositAuthority", - "isMut": false, - "isSigner": true, - "docs": [ - "The owner of the deposit and its reward's mining account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" - ] - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" - }, - { - "name": "lockupPeriod", - "type": { - "defined": "LockupPeriod" + name: 'registrar' + docs: ['Instance of a voting rights distributor.'] + type: { + kind: 'struct' + fields: [ + { + name: 'governanceProgramId' + type: 'publicKey' + }, + { + name: 'realm' + type: 'publicKey' + }, + { + name: 'realmGoverningTokenMint' + type: 'publicKey' + }, + { + name: 'realmAuthority' + type: 'publicKey' + }, + { + name: 'votingMints' + docs: [ + 'Storage for voting mints and their configuration.', + "The length should be adjusted for one's use case." + ] + type: { + array: [ + { + defined: 'VotingMintConfig' + }, + 4 + ] + } + }, + { + name: 'timeOffset' + docs: [ + 'Debug only: time offset, to allow tests to move forward in time.' + ] + type: 'i64' + }, + { + name: 'bump' + type: 'u8' } - }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" - } - ] + ] + } }, { - "name": "claim", - "accounts": [ - { - "name": "rewardPool", - "isMut": false, - "isSigner": false, - "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" - ] - }, - { - "name": "rewardMint", - "isMut": false, - "isSigner": false - }, - { - "name": "vault", - "isMut": true, - "isSigner": false, - "docs": [ - "is checked on the rewards contract", - "PDA(\"vault\", reward_pool, reward_mint)" - ] - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" - ] - }, - { - "name": "miningOwner", - "isMut": true, - "isSigner": true - }, - { - "name": "registrar", - "isMut": false, - "isSigner": false, - "docs": [ - "therefore their PDA that should sign the CPI call" - ] - }, - { - "name": "userRewardTokenAccount", - "isMut": true, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" - } - ], - "returns": "u64" + name: 'voter' + docs: ['User account for minting voting rights.'] + type: { + kind: 'struct' + fields: [ + { + name: 'deposits' + type: { + array: [ + { + defined: 'DepositEntry' + }, + 32 + ] + } + }, + { + name: 'voterAuthority' + type: 'publicKey' + }, + { + name: 'registrar' + type: 'publicKey' + }, + { + name: 'voterBump' + type: 'u8' + }, + { + name: 'voterWeightRecordBump' + type: 'u8' + }, + { + name: 'reserved1' + type: { + array: ['u8', 14] + } + } + ] + } } ], "types": [ @@ -874,18 +755,14 @@ export type VoterStakeRegistry = { "kind": "struct", "fields": [ { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" - ], - "type": "u64" + name: 'rate' + docs: ['Amount of tokens vested each period'] + type: 'u64' }, { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" - ], - "type": "u64" + name: 'nextTimestamp' + docs: ['Time of the next upcoming vesting'] + type: 'u64' } ] } @@ -896,29 +773,25 @@ export type VoterStakeRegistry = { "kind": "struct", "fields": [ { - "name": "amount", - "docs": [ - "Amount of locked tokens" - ], - "type": "u64" + name: 'amount' + docs: ['Amount of locked tokens'] + type: 'u64' }, { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" - ], - "type": { - "option": "u64" + name: 'endTimestamp' + docs: [ + 'Time at which the lockup fully ends (None for Constant lockup)' + ] + type: { + option: 'u64' } }, { - "name": "vesting", - "docs": [ - "Information about vesting, if any" - ], - "type": { - "option": { - "defined": "VestingInfo" + name: 'vesting' + docs: ['Information about vesting, if any'] + type: { + option: { + defined: 'VestingInfo' } } } @@ -926,145 +799,198 @@ export type VoterStakeRegistry = { } }, { - "name": "RewardsInstruction", - "type": { - "kind": "enum", - "variants": [ + name: 'DepositEntry' + docs: [ + 'Bookkeeping for a single deposit for a given mint and lockup schedule.' + ] + type: { + kind: 'struct' + fields: [ { - "name": "InitializePool", - "fields": [ - { - "name": "deposit_authority", - "docs": [ - "Account responsible for charging mining owners" - ], - "type": "publicKey" - }, - { - "name": "fill_authority", - "docs": [ - "Account can fill the reward vault" - ], - "type": "publicKey" - }, - { - "name": "distribution_authority", - "docs": [ - "Account that can distribute money among users after", - "if RewardVault had been filled with rewards" - ], - "type": "publicKey" - } - ] + name: 'lockup' + type: { + defined: 'Lockup' + } }, { - "name": "FillVault", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to fill" - ], - "type": "u64" - }, - { - "name": "distribution_ends_at", - "docs": [ - "Rewards distribution ends at given date" - ], - "type": "u64" - } + name: 'amountDepositedNative' + docs: [ + 'Amount in deposited, in native currency. Withdraws of vested tokens', + 'directly reduce this amount.', + '', + 'This directly tracks the total amount added by the user. They may', + 'never withdraw more than this amount.' ] + type: 'u64' }, { - "name": "InitializeMining", - "fields": [ - { - "name": "mining_owner", - "docs": [ - "Represent the end-user, owner of the mining" - ], - "type": "publicKey" - } + name: 'votingMintConfigIdx' + type: 'u8' + }, + { + name: 'isUsed' + type: 'bool' + }, + { + name: 'reserved1' + type: { + array: ['u8', 6] + } + } + ] + } + }, + { + name: 'Lockup' + type: { + kind: 'struct' + fields: [ + { + name: 'startTs' + docs: [ + 'Note, that if start_ts is in the future, the funds are nevertheless', + 'locked up!', + 'Start of the lockup.' ] + type: 'u64' }, { - "name": "DepositMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to deposit" - ], - "type": "u64" - }, - { - "name": "lockup_period", - "docs": [ - "Lockup Period" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } + name: 'endTs' + docs: ['End of the lockup.'] + type: 'u64' + }, + { + name: 'cooldownEndsAt' + docs: ['End of the cooldown.'] + type: 'u64' + }, + { + name: 'cooldownRequested' + type: 'bool' + }, + { + name: 'kind' + docs: ['Type of lockup.'] + type: { + defined: 'LockupKind' + } + }, + { + name: 'period' + docs: ['Type of lockup'] + type: { + defined: 'LockupPeriod' + } + }, + { + name: 'reserved1' + docs: ['Padding after period to align the struct size to 8 bytes'] + type: { + array: ['u8', 5] + } + } + ] + } + }, + { + name: 'VotingMintConfig' + docs: [ + 'Exchange rate for an asset that can be used to mint voting rights.', + '', + 'See documentation of configure_voting_mint for details on how', + 'native token amounts convert to vote weight.' + ] + type: { + kind: 'struct' + fields: [ + { + name: 'mint' + docs: ['Mint for this entry.'] + type: 'publicKey' + }, + { + name: 'grantAuthority' + docs: ['The authority that is allowed to push grants into voters'] + type: 'publicKey' + }, + { + name: 'baselineVoteWeightScaledFactor' + docs: [ + 'Vote weight factor for all funds in the account, no matter if locked or not.', + '', + 'In 1/SCALED_FACTOR_BASE units.' ] + type: 'u64' }, { - "name": "WithdrawMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to withdraw" - ], - "type": "u64" - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } + name: 'maxExtraLockupVoteWeightScaledFactor' + docs: [ + 'Maximum extra vote weight factor for lockups.', + '', + 'This is the extra votes gained for lockups lasting lockup_saturation_secs or', + 'longer. Shorter lockups receive only a fraction of the maximum extra vote weight,', + 'based on lockup_time divided by lockup_saturation_secs.', + '', + 'In 1/SCALED_FACTOR_BASE units.' ] + type: 'u64' }, { - "name": "Claim" + name: 'lockupSaturationSecs' + docs: [ + 'Number of seconds of lockup needed to reach the maximum lockup bonus.' + ] + type: 'u64' }, { - "name": "RestakeDeposit", - "fields": [ - { - "name": "lockup_period", - "docs": [ - "Requested lockup period for restaking" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "amount", - "docs": [ - "Amount of tokens to be restaked" - ], - "type": "u64" - }, - { - "name": "deposit_start_ts", - "docs": [ - "Deposit start_ts" - ], - "type": "u64" - } + name: 'digitShift' + docs: [ + 'Number of digits to shift native amounts, applying a 10^digit_shift factor.' ] + type: 'i8' + }, + { + name: 'padding' + type: { + array: ['u8', 7] + } + } + ] + } + }, + { + name: 'LockupPeriod' + type: { + kind: 'enum' + variants: [ + { + name: 'None' + }, + { + name: 'Test' + }, + { + name: 'ThreeMonths' + }, + { + name: 'SixMonths' + }, + { + name: 'OneYear' + }, + { + name: 'Flex' + } + ] + } + }, + { + name: 'LockupKind' + type: { + kind: 'enum' + variants: [ + { + name: 'None' }, { "name": "DistributeRewards" @@ -1182,363 +1108,278 @@ export const IDL: VoterStakeRegistry = { ], "instructions": [ { - "name": "createRegistrar", - "accounts": [ + code: 6006 + name: 'OutOfBoundsDepositEntryIndex' + msg: '' + }, + { + code: 6007 + name: 'UnusedDepositEntryIndex' + msg: '' + }, + { + code: 6008 + name: 'InsufficientUnlockedTokens' + msg: '' + }, + { + code: 6009 + name: 'UnableToConvert' + msg: '' + }, + { + code: 6010 + name: 'InvalidLockupPeriod' + msg: '' + }, + { + code: 6011 + name: 'InvalidEndTs' + msg: '' + }, + { + code: 6012 + name: 'InvalidDays' + msg: '' + }, + { + code: 6013 + name: 'VotingMintConfigIndexAlreadyInUse' + msg: '' + }, + { + code: 6014 + name: 'OutOfBoundsVotingMintConfigIndex' + msg: '' + }, + { + code: 6015 + name: 'InvalidDecimals' + msg: 'Exchange rate decimals cannot be larger than registrar decimals' + }, + { + code: 6016 + name: 'InvalidToDepositAndWithdrawInOneSlot' + msg: '' + }, + { + code: 6017 + name: 'ShouldBeTheFirstIxInATx' + msg: '' + }, + { + code: 6018 + name: 'ForbiddenCpi' + msg: '' + }, + { + code: 6019 + name: 'InvalidMint' + msg: '' + }, + { + code: 6020 + name: 'DebugInstruction' + msg: '' + }, + { + code: 6021 + name: 'ClawbackNotAllowedOnDeposit' + msg: '' + }, + { + code: 6022 + name: 'DepositStillLocked' + msg: '' + }, + { + code: 6023 + name: 'InvalidAuthority' + msg: '' + }, + { + code: 6024 + name: 'InvalidTokenOwnerRecord' + msg: '' + }, + { + code: 6025 + name: 'InvalidRealmAuthority' + msg: '' + }, + { + code: 6026 + name: 'VoterWeightOverflow' + msg: '' + }, + { + code: 6027 + name: 'LockupSaturationMustBePositive' + msg: '' + }, + { + code: 6028 + name: 'VotingMintConfiguredWithDifferentIndex' + msg: '' + }, + { + code: 6029 + name: 'InternalProgramError' + msg: '' + }, + { + code: 6030 + name: 'InsufficientLockedTokens' + msg: '' + }, + { + code: 6031 + name: 'MustKeepTokensLocked' + msg: '' + }, + { + code: 6032 + name: 'InvalidLockupKind' + msg: '' + }, + { + code: 6033 + name: 'InvalidChangeToClawbackDepositEntry' + msg: '' + }, + { + code: 6034 + name: 'InternalErrorBadLockupVoteWeight' + msg: '' + }, + { + code: 6035 + name: 'DepositStartTooFarInFuture' + msg: '' + }, + { + code: 6036 + name: 'VaultTokenNonZero' + msg: '' + }, + { + code: 6037 + name: 'InvalidTimestampArguments' + msg: '' + }, + { + code: 6038 + name: 'UnlockMustBeCalledFirst' + msg: '' + }, + { + code: 6039 + name: 'UnlockAlreadyRequested' + msg: '' + } + ] +} + +export const IDL: VoterStakeRegistry = { + version: '0.2.4', + name: 'voter_stake_registry', + docs: [ + '# Introduction', + '', + 'The governance registry is an "addin" to the SPL governance program that', + '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.', + '', + 'The flow for voting with this program is as follows:', + '', + '- 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.', + '', + '# 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.', + ], + instructions: [ + { + name: 'createRegistrar', + accounts: [ { - "name": "registrar", - "isMut": true, - "isSigner": false, - "docs": [ - "The voting registrar. There can only be a single registrar", - "per governance realm and governing mint." - ] + name: 'registrar', + isMut: true, + isSigner: false, + docs: [ + 'The voting registrar. There can only be a single registrar', + 'per governance realm and governing mint.', + ], }, { - "name": "realm", - "isMut": false, - "isSigner": false, - "docs": [ - "An spl-governance realm", - "", - "- realm is owned by the governance_program_id", - "- realm_governing_token_mint must be the community or council mint", - "- realm_authority is realm.authority" - ] + name: 'realm', + isMut: false, + isSigner: false, + docs: [ + 'An spl-governance realm', + '', + '- realm is owned by the governance_program_id', + '- realm_governing_token_mint must be the community or council mint', + '- realm_authority is realm.authority', + ], }, { - "name": "governanceProgramId", - "isMut": false, - "isSigner": false, - "docs": [ - "The program id of the spl-governance program the realm belongs to." - ] + name: 'governanceProgramId', + isMut: false, + isSigner: false, + docs: [ + 'The program id of the spl-governance program the realm belongs to.', + ], }, { - "name": "realmGoverningTokenMint", - "isMut": false, - "isSigner": false, - "docs": [ - "Either the realm community mint or the council mint." - ] + name: 'realmGoverningTokenMint', + isMut: false, + isSigner: false, + docs: ['Either the realm community mint or the council mint.'], }, { "name": "realmAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false, - "docs": [ - "Account that will be created via CPI to the rewards,", - "it's responsible for being a \"root\" for all entities", - "inside rewards contract", - "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" - ] - }, - { - "name": "rewardVault", - "isMut": true, - "isSigner": false, - "docs": [ - "This account is responsible for storing money for rewards", - "PDA(\"vault\", reward_pool, reward_mint)" - ] - }, - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "fillAuthority", - "type": "publicKey" - }, - { - "name": "distributionAuthority", - "type": "publicKey" - } - ] - }, - { - "name": "configureVotingMint", - "accounts": [ - { - "name": "registrar", - "isMut": true, - "isSigner": false - }, - { - "name": "realmAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "mint", - "isMut": false, - "isSigner": false, - "docs": [ - "Tokens of this mint will produce vote weight" - ] - } - ], - "args": [ - { - "name": "idx", - "type": "u16" - }, - { - "name": "digitShift", - "type": "i8" - }, - { - "name": "baselineVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "maxExtraLockupVoteWeightScaledFactor", - "type": "u64" - }, - { - "name": "lockupSaturationSecs", - "type": "u64" - }, - { - "name": "grantAuthority", - "type": { - "option": "publicKey" - } - } - ] - }, - { - "name": "createVoter", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false, - "docs": [ - "Also, Registrar plays the role of deposit_authority on the Rewards Contract,", - "therefore their PDA that should sign the CPI call" - ] - }, - { - "name": "voter", - "isMut": true, - "isSigner": false - }, - { - "name": "voterAuthority", - "isMut": false, - "isSigner": true, - "docs": [ - "The authority controling the voter. Must be the same as the", - "`governing_token_owner` in the token owner record used with", - "spl-governance." - ] - }, - { - "name": "voterWeightRecord", - "isMut": true, - "isSigner": false, - "docs": [ - "The voter weight record is the account that will be shown to spl-governance", - "to prove how much vote weight the voter has. See update_voter_weight_record." - ] - }, - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - }, - { - "name": "instructions", - "isMut": false, - "isSigner": false, - "docs": [ - "NOTE: this account is currently unused" - ] - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" - ] - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" - ] - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "voterBump", - "type": "u8" - }, - { - "name": "voterWeightRecordBump", - "type": "u8" - } - ] - }, - { - "name": "createDepositEntry", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", - "isMut": true, - "isSigner": false - }, - { - "name": "vault", - "isMut": true, - "isSigner": false - }, - { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - }, - { - "name": "payer", - "isMut": true, - "isSigner": true - }, - { - "name": "depositMint", - "isMut": false, - "isSigner": false - }, - { - "name": "systemProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "associatedTokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rent", - "isMut": false, - "isSigner": false - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" - }, - { - "name": "kind", - "type": { - "defined": "LockupKind" - } - }, - { - "name": "startTs", - "type": { - "option": "u64" - } - }, - { - "name": "period", - "type": { - "defined": "LockupPeriod" - } - } - ] - }, - { - "name": "deposit", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", - "isMut": true, - "isSigner": false - }, - { - "name": "vault", - "isMut": true, - "isSigner": false - }, - { - "name": "depositToken", - "isMut": true, - "isSigner": false + "isMut": false, + "isSigner": true }, { - "name": "depositAuthority", + "name": "systemProgram", "isMut": false, - "isSigner": true + "isSigner": false }, { - "name": "tokenProgram", + "name": "rent", "isMut": false, "isSigner": false }, @@ -1547,87 +1388,142 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "Account that will be created via CPI to the rewards,", + "it's responsible for being a \"root\" for all entities", + "inside rewards contract", + "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" ] }, { - "name": "depositMining", + "name": "rewardVault", "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "This account is responsible for storing money for rewards", + "PDA(\"vault\", reward_pool, reward_mint)" ] }, { - "name": "rewardsProgram", + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "tokenProgram", "isMut": false, "isSigner": false - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" }, { - "name": "amount", - "type": "u64" + name: 'mint', + isMut: false, + isSigner: false, + docs: ['Tokens of this mint will produce vote weight'], }, + ], + "args": [ { "name": "registrarBump", "type": "u8" }, { - "name": "realmGoverningMintPubkey", + "name": "fillAuthority", "type": "publicKey" }, { - "name": "realmPubkey", + "name": "distributionAuthority", "type": "publicKey" } ] }, { - "name": "withdraw", + "name": "configureVotingMint", "accounts": [ { "name": "registrar", - "isMut": false, - "isSigner": false - }, - { - "name": "voter", "isMut": true, "isSigner": false }, { - "name": "voterAuthority", + "name": "realmAuthority", "isMut": false, "isSigner": true }, { - "name": "tokenOwnerRecord", + name: 'voterAuthority', + isMut: false, + isSigner: true, + docs: [ + 'The authority controling the voter. Must be the same as the', + '`governing_token_owner` in the token owner record used with', + 'spl-governance.', + ], + }, + { + name: 'voterWeightRecord', + isMut: true, + isSigner: false, + docs: [ + 'The voter weight record is the account that will be shown to spl-governance', + 'to prove how much vote weight the voter has. See update_voter_weight_record.', + ], + }, + { + "name": "digitShift", + "type": "i8" + }, + { + name: 'systemProgram', + isMut: false, + isSigner: false, + }, + { + name: 'rent', + isMut: false, + isSigner: false, + }, + { + name: 'instructions', + isMut: false, + isSigner: false, + docs: ['NOTE: this account is currently unused'], + }, + { + "name": "rewardsProgram", "isMut": false, - "isSigner": false, - "docs": [ - "The token_owner_record for the voter_authority. This is needed", - "to be able to forbid withdraws while the voter is engaged with", - "a vote or has an open proposal.", - "", - "- owned by registrar.governance_program_id", - "- for the registrar.realm", - "- for the registrar.realm_governing_token_mint", - "- governing_token_owner is voter_authority" - ] + "isSigner": false + } + ], + "args": [ + { + name: 'voterBump', + type: 'u8', }, { - "name": "voterWeightRecord", - "isMut": true, - "isSigner": false, - "docs": [ - "Withdraws must update the voter weight record, to prevent a stale", - "record being used to vote after the withdraw." - ] + name: 'voterWeightRecordBump', + type: 'u8', + }, + ], + }, + { + name: 'createDepositEntry', + accounts: [ + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + name: 'vault', + isMut: true, + isSigner: false, + }, + { + name: 'voterAuthority', + isMut: false, + isSigner: true, }, { "name": "vault", @@ -1662,12 +1558,8 @@ export const IDL: VoterStakeRegistry = { ], "args": [ { - "name": "depositEntryIndex", - "type": "u8" - }, - { - "name": "amount", - "type": "u64" + name: 'depositEntryIndex', + type: 'u8', }, { "name": "registrarBump", @@ -1678,24 +1570,11 @@ export const IDL: VoterStakeRegistry = { "type": "publicKey" }, { - "name": "realmPubkey", - "type": "publicKey" - } - ] - }, - { - "name": "closeDepositEntry", - "accounts": [ - { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'period', + type: { + defined: 'LockupPeriod', + }, }, - { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - } ], "args": [ { @@ -1705,22 +1584,17 @@ export const IDL: VoterStakeRegistry = { ] }, { - "name": "updateVoterWeightRecord", - "accounts": [ + name: 'deposit', + accounts: [ { "name": "registrar", "isMut": false, "isSigner": false }, { - "name": "voter", - "isMut": false, - "isSigner": false - }, - { - "name": "voterWeightRecord", - "isMut": true, - "isSigner": false + name: 'voter', + isMut: true, + isSigner: false, }, { "name": "systemProgram", @@ -1734,45 +1608,27 @@ export const IDL: VoterStakeRegistry = { "name": "unlockTokens", "accounts": [ { - "name": "registrar", - "isMut": false, - "isSigner": false + name: 'depositToken', + isMut: true, + isSigner: false, }, { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'depositAuthority', + isMut: false, + isSigner: true, }, { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - } - ], - "args": [ - { - "name": "depositEntryIndex", - "type": "u8" - } - ] - }, - { - "name": "closeVoter", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false + name: 'tokenProgram', + isMut: false, + isSigner: false, }, { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'depositEntryIndex', + type: 'u8', }, { - "name": "voterAuthority", - "isMut": false, - "isSigner": true + name: 'amount', + type: 'u64', }, { "name": "solDestination", @@ -1788,57 +1644,67 @@ export const IDL: VoterStakeRegistry = { "args": [] }, { - "name": "logVoterInfo", - "accounts": [ + name: 'withdraw', + accounts: [ { "name": "registrar", "isMut": false, "isSigner": false }, { - "name": "voter", - "isMut": false, - "isSigner": false - } - ], - "args": [ + name: 'voter', + isMut: true, + isSigner: false, + }, { - "name": "depositEntryBegin", - "type": "u8" + name: 'voterAuthority', + isMut: false, + isSigner: true, }, { - "name": "depositEntryCount", - "type": "u8" - } - ] - }, - { - "name": "internalTransferUnlocked", - "accounts": [ + name: 'tokenOwnerRecord', + isMut: false, + isSigner: false, + docs: [ + 'The token_owner_record for the voter_authority. This is needed', + 'to be able to forbid withdraws while the voter is engaged with', + 'a vote or has an open proposal.', + '', + '- owned by registrar.governance_program_id', + '- for the registrar.realm', + '- for the registrar.realm_governing_token_mint', + '- governing_token_owner is voter_authority', + ], + }, { - "name": "registrar", - "isMut": false, - "isSigner": false + name: 'voterWeightRecord', + isMut: true, + isSigner: false, + docs: [ + 'Withdraws must update the voter weight record, to prevent a stale', + 'record being used to vote after the withdraw.', + ], }, { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'vault', + isMut: true, + isSigner: false, }, { - "name": "voterAuthority", - "isMut": false, - "isSigner": true - } - ], - "args": [ + name: 'destination', + isMut: true, + isSigner: false, + }, { - "name": "sourceDepositEntryIndex", - "type": "u8" + name: 'tokenProgram', + isMut: false, + isSigner: false, }, + ], + "args": [ { - "name": "targetDepositEntryIndex", - "type": "u8" + name: 'depositEntryIndex', + type: 'u8', }, { "name": "amount", @@ -1847,17 +1713,12 @@ export const IDL: VoterStakeRegistry = { ] }, { - "name": "restakeDeposit", - "accounts": [ - { - "name": "registrar", - "isMut": false, - "isSigner": false - }, + name: 'closeDepositEntry', + accounts: [ { - "name": "voter", - "isMut": true, - "isSigner": false + name: 'voter', + isMut: true, + isSigner: false, }, { "name": "depositToken", @@ -1865,23 +1726,14 @@ export const IDL: VoterStakeRegistry = { "isSigner": false }, { - "name": "depositAuthority", - "isMut": false, - "isSigner": true, - "docs": [ - "The owner of the deposit and its reward's mining account" - ] - }, - { - "name": "tokenProgram", - "isMut": false, - "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false + name: 'depositEntryIndex', + type: 'u8', }, + ], + }, + { + name: 'updateVoterWeightRecord', + accounts: [ { "name": "depositMining", "isMut": true, @@ -1907,19 +1759,33 @@ export const IDL: VoterStakeRegistry = { "defined": "LockupPeriod" } }, + ], + args: [], + }, + { + name: 'unlockTokens', + accounts: [ { "name": "registrarBump", "type": "u8" }, { - "name": "realmGoverningMintPubkey", - "type": "publicKey" + name: 'voter', + isMut: true, + isSigner: false, }, { - "name": "realmPubkey", - "type": "publicKey" - } - ] + name: 'voterAuthority', + isMut: false, + isSigner: true, + }, + ], + args: [ + { + name: 'depositEntryIndex', + type: 'u8', + }, + ], }, { "name": "claim", @@ -1983,144 +1849,351 @@ export const IDL: VoterStakeRegistry = { "isSigner": false } ], - "args": [ + }, + { + name: 'internalTransferUnlocked', + accounts: [ { - "name": "registrarBump", - "type": "u8" + name: 'registrar', + isMut: false, + isSigner: false, }, { - "name": "realmGoverningMintPubkey", - "type": "publicKey" + name: 'voter', + isMut: true, + isSigner: false, }, { - "name": "realmPubkey", - "type": "publicKey" - } + name: 'voterAuthority', + isMut: false, + isSigner: true, + }, + { + name: 'sourceDepositEntryIndex', + type: 'u8', + }, + { + name: 'targetDepositEntryIndex', + type: 'u8', + }, + { + name: 'amount', + type: 'u64', + }, ], "returns": "u64" } ], "types": [ { - "name": "VestingInfo", - "type": { - "kind": "struct", - "fields": [ + name: 'registrar', + docs: ['Instance of a voting rights distributor.'], + type: { + kind: 'struct', + fields: [ + { + name: 'governanceProgramId', + type: 'publicKey', + }, + { + name: 'realm', + type: 'publicKey', + }, + { + name: 'realmGoverningTokenMint', + type: 'publicKey', + }, + { + name: 'realmAuthority', + type: 'publicKey', + }, { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" + name: 'votingMints', + docs: [ + 'Storage for voting mints and their configuration.', + "The length should be adjusted for one's use case.", ], - "type": "u64" + type: { + array: [ + { + defined: 'VotingMintConfig', + }, + 4, + ], + }, }, { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" + name: 'timeOffset', + docs: [ + 'Debug only: time offset, to allow tests to move forward in time.', ], - "type": "u64" - } - ] - } + type: 'i64', + }, + { + name: 'bump', + type: 'u8', + }, + ], + }, }, { - "name": "LockingInfo", - "type": { - "kind": "struct", - "fields": [ + name: 'voter', + docs: ['User account for minting voting rights.'], + type: { + kind: 'struct', + fields: [ + { + name: 'deposits', + type: { + array: [ + { + defined: 'DepositEntry', + }, + 32, + ], + }, + }, + { + name: 'voterAuthority', + type: 'publicKey', + }, + { + name: 'registrar', + type: 'publicKey', + }, + { + name: 'voterBump', + type: 'u8', + }, + { + name: 'voterWeightRecordBump', + type: 'u8', + }, + { + name: 'reserved1', + type: { + array: ['u8', 14], + }, + }, + ], + }, + }, + ], + types: [ + { + name: 'VestingInfo', + type: { + kind: 'struct', + fields: [ + { + name: 'rate', + docs: ['Amount of tokens vested each period'], + type: 'u64', + }, + { + name: 'nextTimestamp', + docs: ['Time of the next upcoming vesting'], + type: 'u64', + }, + ], + }, + }, + { + name: 'LockingInfo', + type: { + kind: 'struct', + fields: [ + { + name: 'amount', + docs: ['Amount of locked tokens'], + type: 'u64', + }, { - "name": "amount", - "docs": [ - "Amount of locked tokens" + name: 'endTimestamp', + docs: [ + 'Time at which the lockup fully ends (None for Constant lockup)', ], - "type": "u64" + type: { + option: 'u64', + }, + }, + { + name: 'vesting', + docs: ['Information about vesting, if any'], + type: { + option: { + defined: 'VestingInfo', + }, + }, + }, + ], + }, + }, + { + name: 'DepositEntry', + docs: [ + 'Bookkeeping for a single deposit for a given mint and lockup schedule.', + ], + type: { + kind: 'struct', + fields: [ + { + name: 'lockup', + type: { + defined: 'Lockup', + }, }, { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" + name: 'amountDepositedNative', + docs: [ + 'Amount in deposited, in native currency. Withdraws of vested tokens', + 'directly reduce this amount.', + '', + 'This directly tracks the total amount added by the user. They may', + 'never withdraw more than this amount.', ], - "type": { - "option": "u64" - } + type: 'u64', + }, + { + name: 'votingMintConfigIdx', + type: 'u8', + }, + { + name: 'isUsed', + type: 'bool', + }, + { + name: 'reserved1', + type: { + array: ['u8', 6], + }, }, + ], + }, + }, + { + name: 'Lockup', + type: { + kind: 'struct', + fields: [ { - "name": "vesting", - "docs": [ - "Information about vesting, if any" + name: 'startTs', + docs: [ + 'Note, that if start_ts is in the future, the funds are nevertheless', + 'locked up!', + 'Start of the lockup.', ], - "type": { - "option": { - "defined": "VestingInfo" - } - } - } - ] - } + type: 'u64', + }, + { + name: 'endTs', + docs: ['End of the lockup.'], + type: 'u64', + }, + { + name: 'cooldownEndsAt', + docs: ['End of the cooldown.'], + type: 'u64', + }, + { + name: 'cooldownRequested', + type: 'bool', + }, + { + name: 'kind', + docs: ['Type of lockup.'], + type: { + defined: 'LockupKind', + }, + }, + { + name: 'period', + docs: ['Type of lockup'], + type: { + defined: 'LockupPeriod', + }, + }, + { + name: 'reserved1', + docs: ['Padding after period to align the struct size to 8 bytes'], + type: { + array: ['u8', 5], + }, + }, + ], + }, }, { - "name": "RewardsInstruction", - "type": { - "kind": "enum", - "variants": [ + name: 'VotingMintConfig', + docs: [ + 'Exchange rate for an asset that can be used to mint voting rights.', + '', + 'See documentation of configure_voting_mint for details on how', + 'native token amounts convert to vote weight.', + ], + type: { + kind: 'struct', + fields: [ { - "name": "InitializePool", - "fields": [ - { - "name": "deposit_authority", - "docs": [ - "Account responsible for charging mining owners" - ], - "type": "publicKey" - }, - { - "name": "fill_authority", - "docs": [ - "Account can fill the reward vault" - ], - "type": "publicKey" - }, - { - "name": "distribution_authority", - "docs": [ - "Account that can distribute money among users after", - "if RewardVault had been filled with rewards" - ], - "type": "publicKey" - } - ] + name: 'mint', + docs: ['Mint for this entry.'], + type: 'publicKey', }, { - "name": "FillVault", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to fill" - ], - "type": "u64" - }, - { - "name": "distribution_ends_at", - "docs": [ - "Rewards distribution ends at given date" - ], - "type": "u64" - } - ] + name: 'grantAuthority', + docs: ['The authority that is allowed to push grants into voters'], + type: 'publicKey', }, { - "name": "InitializeMining", - "fields": [ - { - "name": "mining_owner", - "docs": [ - "Represent the end-user, owner of the mining" - ], - "type": "publicKey" - } - ] + name: 'baselineVoteWeightScaledFactor', + docs: [ + 'Vote weight factor for all funds in the account, no matter if locked or not.', + '', + 'In 1/SCALED_FACTOR_BASE units.', + ], + type: 'u64', + }, + { + name: 'maxExtraLockupVoteWeightScaledFactor', + docs: [ + 'Maximum extra vote weight factor for lockups.', + '', + 'This is the extra votes gained for lockups lasting lockup_saturation_secs or', + 'longer. Shorter lockups receive only a fraction of the maximum extra vote weight,', + 'based on lockup_time divided by lockup_saturation_secs.', + '', + 'In 1/SCALED_FACTOR_BASE units.', + ], + type: 'u64', + }, + { + name: 'lockupSaturationSecs', + docs: [ + 'Number of seconds of lockup needed to reach the maximum lockup bonus.', + ], + type: 'u64', + }, + { + name: 'digitShift', + docs: [ + 'Number of digits to shift native amounts, applying a 10^digit_shift factor.', + ], + type: 'i8', + }, + { + name: 'padding', + type: { + array: ['u8', 7], + }, }, + ], + }, + }, + { + name: 'LockupPeriod', + type: { + kind: 'enum', + variants: [ { "name": "DepositMining", "fields": [ @@ -2150,54 +2223,30 @@ export const IDL: VoterStakeRegistry = { ] }, { - "name": "WithdrawMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to withdraw" - ], - "type": "u64" - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } - ] + name: 'Test', }, { - "name": "Claim" + name: 'ThreeMonths', }, { - "name": "RestakeDeposit", - "fields": [ - { - "name": "lockup_period", - "docs": [ - "Requested lockup period for restaking" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "amount", - "docs": [ - "Amount of tokens to be restaked" - ], - "type": "u64" - }, - { - "name": "deposit_start_ts", - "docs": [ - "Deposit start_ts" - ], - "type": "u64" - } - ] + name: 'SixMonths', + }, + { + name: 'OneYear', + }, + { + name: 'Flex', + }, + ], + }, + }, + { + name: 'LockupKind', + type: { + kind: 'enum', + variants: [ + { + name: 'None', }, { "name": "DistributeRewards" @@ -2257,9 +2306,211 @@ export const IDL: VoterStakeRegistry = { "defined": "LockingInfo" } }, - "index": false - } - ] - } - ] -}; + index: false, + }, + ], + }, + ], + errors: [ + { + code: 6000, + name: 'InvalidRate', + msg: 'Exchange rate must be greater than zero', + }, + { + code: 6001, + name: 'RatesFull', + msg: '', + }, + { + code: 6002, + name: 'VotingMintNotFound', + msg: '', + }, + { + code: 6003, + name: 'DepositEntryNotFound', + msg: '', + }, + { + code: 6004, + name: 'DepositEntryFull', + msg: '', + }, + { + code: 6005, + name: 'VotingTokenNonZero', + msg: '', + }, + { + code: 6006, + name: 'OutOfBoundsDepositEntryIndex', + msg: '', + }, + { + code: 6007, + name: 'UnusedDepositEntryIndex', + msg: '', + }, + { + code: 6008, + name: 'InsufficientUnlockedTokens', + msg: '', + }, + { + code: 6009, + name: 'UnableToConvert', + msg: '', + }, + { + code: 6010, + name: 'InvalidLockupPeriod', + msg: '', + }, + { + code: 6011, + name: 'InvalidEndTs', + msg: '', + }, + { + code: 6012, + name: 'InvalidDays', + msg: '', + }, + { + code: 6013, + name: 'VotingMintConfigIndexAlreadyInUse', + msg: '', + }, + { + code: 6014, + name: 'OutOfBoundsVotingMintConfigIndex', + msg: '', + }, + { + code: 6015, + name: 'InvalidDecimals', + msg: 'Exchange rate decimals cannot be larger than registrar decimals', + }, + { + code: 6016, + name: 'InvalidToDepositAndWithdrawInOneSlot', + msg: '', + }, + { + code: 6017, + name: 'ShouldBeTheFirstIxInATx', + msg: '', + }, + { + code: 6018, + name: 'ForbiddenCpi', + msg: '', + }, + { + code: 6019, + name: 'InvalidMint', + msg: '', + }, + { + code: 6020, + name: 'DebugInstruction', + msg: '', + }, + { + code: 6021, + name: 'ClawbackNotAllowedOnDeposit', + msg: '', + }, + { + code: 6022, + name: 'DepositStillLocked', + msg: '', + }, + { + code: 6023, + name: 'InvalidAuthority', + msg: '', + }, + { + code: 6024, + name: 'InvalidTokenOwnerRecord', + msg: '', + }, + { + code: 6025, + name: 'InvalidRealmAuthority', + msg: '', + }, + { + code: 6026, + name: 'VoterWeightOverflow', + msg: '', + }, + { + code: 6027, + name: 'LockupSaturationMustBePositive', + msg: '', + }, + { + code: 6028, + name: 'VotingMintConfiguredWithDifferentIndex', + msg: '', + }, + { + code: 6029, + name: 'InternalProgramError', + msg: '', + }, + { + code: 6030, + name: 'InsufficientLockedTokens', + msg: '', + }, + { + code: 6031, + name: 'MustKeepTokensLocked', + msg: '', + }, + { + code: 6032, + name: 'InvalidLockupKind', + msg: '', + }, + { + code: 6033, + name: 'InvalidChangeToClawbackDepositEntry', + msg: '', + }, + { + code: 6034, + name: 'InternalErrorBadLockupVoteWeight', + msg: '', + }, + { + code: 6035, + name: 'DepositStartTooFarInFuture', + msg: '', + }, + { + code: 6036, + name: 'VaultTokenNonZero', + msg: '', + }, + { + code: 6037, + name: 'InvalidTimestampArguments', + msg: '', + }, + { + code: 6038, + name: 'UnlockMustBeCalledFirst', + msg: '', + }, + { + code: 6039, + name: 'UnlockAlreadyRequested', + msg: '', + }, + ], +} From 7ce105501c3cb25b44c5dfc1d0cb4bad47e52579 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 12 Jun 2024 12:25:01 +0300 Subject: [PATCH 34/59] update idl --- idl/voter_stake_registry.ts | 2673 ++++++++++++++++------------------- 1 file changed, 1211 insertions(+), 1462 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 678fece3..1bd7b9da 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -1,53 +1,53 @@ export type VoterStakeRegistry = { - version: '0.2.4' - name: 'voter_stake_registry' - docs: [ - '# Introduction', - '', - 'The governance registry is an "addin" to the SPL governance program that', - '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.', - '', - 'The flow for voting with this program is as follows:', - '', - '- 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', + "version": "0.2.4", + "name": "voter_stake_registry", + "docs": [ + "# Introduction", + "", + "The governance registry is an \"addin\" to the SPL governance program that", + "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.", + "", + "The flow for voting with this program is as follows:", + "", + "- 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.', - '', - '# 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.' - ] - instructions: [ + "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.", + "", + "# 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." + ], + "instructions": [ { "name": "createRegistrar", "accounts": [ @@ -89,39 +89,30 @@ export type VoterStakeRegistry = { ] }, { - name: 'registrar' - isMut: true - isSigner: false - docs: [ - 'The voting registrar. There can only be a single registrar', - 'per governance realm and governing mint.' - ] + "name": "realmAuthority", + "isMut": false, + "isSigner": true }, { - name: 'realm' - isMut: false - isSigner: false - docs: [ - 'An spl-governance realm', - '', - '- realm is owned by the governance_program_id', - '- realm_governing_token_mint must be the community or council mint', - '- realm_authority is realm.authority' - ] + "name": "systemProgram", + "isMut": false, + "isSigner": false }, { - name: 'governanceProgramId' - isMut: false - isSigner: false - docs: [ - 'The program id of the spl-governance program the realm belongs to.' - ] + "name": "rent", + "isMut": false, + "isSigner": false }, { - name: 'realmGoverningTokenMint' - isMut: false - isSigner: false - docs: ['Either the realm community mint or the council mint.'] + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "Account that will be created via CPI to the rewards,", + "it's responsible for being a \"root\" for all entities", + "inside rewards contract", + "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { "name": "rewardVault", @@ -177,10 +168,12 @@ export type VoterStakeRegistry = { "isSigner": true }, { - name: 'mint' - isMut: false - isSigner: false - docs: ['Tokens of this mint will produce vote weight'] + "name": "mint", + "isMut": false, + "isSigner": false, + "docs": [ + "Tokens of this mint will produce vote weight" + ] } ], "args": [ @@ -254,23 +247,14 @@ export type VoterStakeRegistry = { "isSigner": true }, { - name: 'voterAuthority' - isMut: false - isSigner: true - docs: [ - 'The authority controling the voter. Must be the same as the', - '`governing_token_owner` in the token owner record used with', - 'spl-governance.' - ] + "name": "systemProgram", + "isMut": false, + "isSigner": false }, { - name: 'voterWeightRecord' - isMut: true - isSigner: false - docs: [ - 'The voter weight record is the account that will be shown to spl-governance', - 'to prove how much vote weight the voter has. See update_voter_weight_record.' - ] + "name": "rent", + "isMut": false, + "isSigner": false }, { "name": "instructions", @@ -297,10 +281,9 @@ export type VoterStakeRegistry = { ] }, { - name: 'instructions' - isMut: false - isSigner: false - docs: ['NOTE: this account is currently unused'] + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -386,9 +369,9 @@ export type VoterStakeRegistry = { } }, { - name: 'period' - type: { - defined: 'LockupPeriod' + "name": "period", + "type": { + "defined": "LockupPeriod" } } ] @@ -443,28 +426,15 @@ export type VoterStakeRegistry = { ] }, { - name: 'tokenOwnerRecord' - isMut: false - isSigner: false - docs: [ - 'The token_owner_record for the voter_authority. This is needed', - 'to be able to forbid withdraws while the voter is engaged with', - 'a vote or has an open proposal.', - '', - '- owned by registrar.governance_program_id', - '- for the registrar.realm', - '- for the registrar.realm_governing_token_mint', - '- governing_token_owner is voter_authority' - ] - }, + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ { - name: 'voterWeightRecord' - isMut: true - isSigner: false - docs: [ - 'Withdraws must update the voter weight record, to prevent a stale', - 'record being used to vote after the withdraw.' - ] + "name": "depositEntryIndex", + "type": "u8" }, { "name": "amount", @@ -485,267 +455,416 @@ export type VoterStakeRegistry = { ] }, { - name: 'closeDepositEntry' - accounts: [ + "name": "withdraw", + "accounts": [ { "name": "registrar", "isMut": false, "isSigner": false }, { - name: 'voterAuthority' - isMut: false - isSigner: true - } - ] - args: [ - { - name: 'depositEntryIndex' - type: 'u8' - } - ] - }, - { - name: 'updateVoterWeightRecord' - accounts: [ + "name": "voter", + "isMut": true, + "isSigner": false + }, { "name": "voterAuthority", "isMut": false, "isSigner": true }, { - name: 'voter' - isMut: false - isSigner: false + "name": "tokenOwnerRecord", + "isMut": false, + "isSigner": false, + "docs": [ + "The token_owner_record for the voter_authority. This is needed", + "to be able to forbid withdraws while the voter is engaged with", + "a vote or has an open proposal.", + "", + "- owned by registrar.governance_program_id", + "- for the registrar.realm", + "- for the registrar.realm_governing_token_mint", + "- governing_token_owner is voter_authority" + ] }, { - name: 'voterWeightRecord' - isMut: true - isSigner: false + "name": "voterWeightRecord", + "isMut": true, + "isSigner": false, + "docs": [ + "Withdraws must update the voter weight record, to prevent a stale", + "record being used to vote after the withdraw." + ] }, { - name: 'systemProgram' - isMut: false - isSigner: false - } - ] - args: [] - }, - { - name: 'unlockTokens' - accounts: [ + "name": "vault", + "isMut": true, + "isSigner": false + }, { - "name": "registrar", + "name": "destination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", "isMut": false, "isSigner": false }, { - "name": "voter", + "name": "rewardPool", "isMut": true, "isSigner": false }, { - "name": "depositToken", + "name": "depositMining", "isMut": true, "isSigner": false }, { - name: 'depositEntryIndex' - type: 'u8' + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } - ] - }, - { - name: 'closeVoter' - accounts: [ + ], + "args": [ { - "name": "rewardPool", - "isMut": false, - "isSigner": false, - "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" - ] + "name": "depositEntryIndex", + "type": "u8" }, { - "name": "rewardMint", - "isMut": false, - "isSigner": false + "name": "amount", + "type": "u64" }, { - "name": "vault", - "isMut": true, - "isSigner": false, - "docs": [ - "is checked on the rewards contract", - "PDA(\"vault\", reward_pool, reward_mint)" - ] + "name": "registrarBump", + "type": "u8" }, { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" - ] + "name": "realmGoverningMintPubkey", + "type": "publicKey" }, { - "name": "miningOwner", + "name": "realmPubkey", + "type": "publicKey" + } + ] + }, + { + "name": "closeDepositEntry", + "accounts": [ + { + "name": "voter", "isMut": true, - "isSigner": true + "isSigner": false }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + } + ] + }, + { + "name": "updateVoterWeightRecord", + "accounts": [ { "name": "registrar", "isMut": false, - "isSigner": false, - "docs": [ - "therefore their PDA that should sign the CPI call" - ] + "isSigner": false }, { - "name": "userRewardTokenAccount", - "isMut": true, + "name": "voter", + "isMut": false, "isSigner": false }, { - "name": "tokenProgram", - "isMut": false, + "name": "voterWeightRecord", + "isMut": true, "isSigner": false }, { - "name": "rewardsProgram", + "name": "systemProgram", "isMut": false, "isSigner": false } - ] + ], + "args": [] }, { - name: 'internalTransferUnlocked' - accounts: [ + "name": "unlockTokens", + "accounts": [ { - name: 'registrar' - isMut: false - isSigner: false + "name": "registrar", + "isMut": false, + "isSigner": false }, { - name: 'voter' - isMut: true - isSigner: false + "name": "voter", + "isMut": true, + "isSigner": false }, { - name: 'voterAuthority' - isMut: false - isSigner: true + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" } ] - args: [ + }, + { + "name": "closeVoter", + "accounts": [ { - name: 'sourceDepositEntryIndex' - type: 'u8' + "name": "registrar", + "isMut": false, + "isSigner": false }, { - name: 'targetDepositEntryIndex' - type: 'u8' + "name": "voter", + "isMut": true, + "isSigner": false }, { - name: 'amount' - type: 'u64' - } - ] - } - ] - accounts: [ - { - name: 'registrar' - docs: ['Instance of a voting rights distributor.'] - type: { - kind: 'struct' - fields: [ - { - name: 'governanceProgramId' - type: 'publicKey' - }, - { - name: 'realm' - type: 'publicKey' - }, - { - name: 'realmGoverningTokenMint' - type: 'publicKey' - }, - { - name: 'realmAuthority' - type: 'publicKey' - }, - { - name: 'votingMints' - docs: [ - 'Storage for voting mints and their configuration.', - "The length should be adjusted for one's use case." - ] - type: { - array: [ - { - defined: 'VotingMintConfig' - }, - 4 - ] - } - }, - { - name: 'timeOffset' - docs: [ - 'Debug only: time offset, to allow tests to move forward in time.' - ] - type: 'i64' - }, - { - name: 'bump' - type: 'u8' - } - ] - } + "name": "voterAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "solDestination", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] }, { - name: 'voter' - docs: ['User account for minting voting rights.'] - type: { - kind: 'struct' - fields: [ - { - name: 'deposits' - type: { - array: [ - { - defined: 'DepositEntry' - }, - 32 - ] - } - }, - { - name: 'voterAuthority' - type: 'publicKey' - }, - { - name: 'registrar' - type: 'publicKey' - }, - { - name: 'voterBump' - type: 'u8' - }, - { - name: 'voterWeightRecordBump' - type: 'u8' - }, - { - name: 'reserved1' - type: { - array: ['u8', 14] - } + "name": "logVoterInfo", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "depositEntryBegin", + "type": "u8" + }, + { + "name": "depositEntryCount", + "type": "u8" + } + ] + }, + { + "name": "internalTransferUnlocked", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "sourceDepositEntryIndex", + "type": "u8" + }, + { + "name": "targetDepositEntryIndex", + "type": "u8" + }, + { + "name": "amount", + "type": "u64" + } + ] + }, + { + "name": "restakeDeposit", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "depositToken", + "isMut": true, + "isSigner": false + }, + { + "name": "depositAuthority", + "isMut": false, + "isSigner": true, + "docs": [ + "The owner of the deposit and its reward's mining account" + ] + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + }, + { + "name": "lockupPeriod", + "type": { + "defined": "LockupPeriod" } - ] - } + }, + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ] + }, + { + "name": "claim", + "accounts": [ + { + "name": "rewardPool", + "isMut": false, + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] + }, + { + "name": "rewardMint", + "isMut": false, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false, + "docs": [ + "is checked on the rewards contract", + "PDA(\"vault\", reward_pool, reward_mint)" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] + }, + { + "name": "miningOwner", + "isMut": true, + "isSigner": true + }, + { + "name": "registrar", + "isMut": false, + "isSigner": false, + "docs": [ + "therefore their PDA that should sign the CPI call" + ] + }, + { + "name": "userRewardTokenAccount", + "isMut": true, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ], + "returns": "u64" } ], "types": [ @@ -755,14 +874,18 @@ export type VoterStakeRegistry = { "kind": "struct", "fields": [ { - name: 'rate' - docs: ['Amount of tokens vested each period'] - type: 'u64' + "name": "rate", + "docs": [ + "Amount of tokens vested each period" + ], + "type": "u64" }, { - name: 'nextTimestamp' - docs: ['Time of the next upcoming vesting'] - type: 'u64' + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" + ], + "type": "u64" } ] } @@ -773,25 +896,29 @@ export type VoterStakeRegistry = { "kind": "struct", "fields": [ { - name: 'amount' - docs: ['Amount of locked tokens'] - type: 'u64' + "name": "amount", + "docs": [ + "Amount of locked tokens" + ], + "type": "u64" }, { - name: 'endTimestamp' - docs: [ - 'Time at which the lockup fully ends (None for Constant lockup)' - ] - type: { - option: 'u64' + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" + ], + "type": { + "option": "u64" } }, { - name: 'vesting' - docs: ['Information about vesting, if any'] - type: { - option: { - defined: 'VestingInfo' + "name": "vesting", + "docs": [ + "Information about vesting, if any" + ], + "type": { + "option": { + "defined": "VestingInfo" } } } @@ -799,198 +926,145 @@ export type VoterStakeRegistry = { } }, { - name: 'DepositEntry' - docs: [ - 'Bookkeeping for a single deposit for a given mint and lockup schedule.' - ] - type: { - kind: 'struct' - fields: [ - { - name: 'lockup' - type: { - defined: 'Lockup' - } - }, - { - name: 'amountDepositedNative' - docs: [ - 'Amount in deposited, in native currency. Withdraws of vested tokens', - 'directly reduce this amount.', - '', - 'This directly tracks the total amount added by the user. They may', - 'never withdraw more than this amount.' - ] - type: 'u64' - }, - { - name: 'votingMintConfigIdx' - type: 'u8' - }, - { - name: 'isUsed' - type: 'bool' - }, - { - name: 'reserved1' - type: { - array: ['u8', 6] - } - } - ] - } - }, - { - name: 'Lockup' - type: { - kind: 'struct' - fields: [ + "name": "RewardsInstruction", + "type": { + "kind": "enum", + "variants": [ { - name: 'startTs' - docs: [ - 'Note, that if start_ts is in the future, the funds are nevertheless', - 'locked up!', - 'Start of the lockup.' + "name": "InitializePool", + "fields": [ + { + "name": "deposit_authority", + "docs": [ + "Account responsible for charging mining owners" + ], + "type": "publicKey" + }, + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account that can distribute money among users after", + "if RewardVault had been filled with rewards" + ], + "type": "publicKey" + } ] - type: 'u64' - }, - { - name: 'endTs' - docs: ['End of the lockup.'] - type: 'u64' - }, - { - name: 'cooldownEndsAt' - docs: ['End of the cooldown.'] - type: 'u64' - }, - { - name: 'cooldownRequested' - type: 'bool' - }, - { - name: 'kind' - docs: ['Type of lockup.'] - type: { - defined: 'LockupKind' - } - }, - { - name: 'period' - docs: ['Type of lockup'] - type: { - defined: 'LockupPeriod' - } - }, - { - name: 'reserved1' - docs: ['Padding after period to align the struct size to 8 bytes'] - type: { - array: ['u8', 5] - } - } - ] - } - }, - { - name: 'VotingMintConfig' - docs: [ - 'Exchange rate for an asset that can be used to mint voting rights.', - '', - 'See documentation of configure_voting_mint for details on how', - 'native token amounts convert to vote weight.' - ] - type: { - kind: 'struct' - fields: [ - { - name: 'mint' - docs: ['Mint for this entry.'] - type: 'publicKey' }, { - name: 'grantAuthority' - docs: ['The authority that is allowed to push grants into voters'] - type: 'publicKey' - }, - { - name: 'baselineVoteWeightScaledFactor' - docs: [ - 'Vote weight factor for all funds in the account, no matter if locked or not.', - '', - 'In 1/SCALED_FACTOR_BASE units.' + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } ] - type: 'u64' }, { - name: 'maxExtraLockupVoteWeightScaledFactor' - docs: [ - 'Maximum extra vote weight factor for lockups.', - '', - 'This is the extra votes gained for lockups lasting lockup_saturation_secs or', - 'longer. Shorter lockups receive only a fraction of the maximum extra vote weight,', - 'based on lockup_time divided by lockup_saturation_secs.', - '', - 'In 1/SCALED_FACTOR_BASE units.' + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } ] - type: 'u64' }, { - name: 'lockupSaturationSecs' - docs: [ - 'Number of seconds of lockup needed to reach the maximum lockup bonus.' + "name": "DepositMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to deposit" + ], + "type": "u64" + }, + { + "name": "lockup_period", + "docs": [ + "Lockup Period" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } ] - type: 'u64' }, { - name: 'digitShift' - docs: [ - 'Number of digits to shift native amounts, applying a 10^digit_shift factor.' + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } ] - type: 'i8' - }, - { - name: 'padding' - type: { - array: ['u8', 7] - } - } - ] - } - }, - { - name: 'LockupPeriod' - type: { - kind: 'enum' - variants: [ - { - name: 'None' - }, - { - name: 'Test' }, { - name: 'ThreeMonths' + "name": "Claim" }, { - name: 'SixMonths' - }, - { - name: 'OneYear' - }, - { - name: 'Flex' - } - ] - } - }, - { - name: 'LockupKind' - type: { - kind: 'enum' - variants: [ - { - name: 'None' + "name": "RestakeDeposit", + "fields": [ + { + "name": "lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "amount", + "docs": [ + "Amount of tokens to be restaked" + ], + "type": "u64" + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + } + ] }, { "name": "DistributeRewards" @@ -1108,265 +1182,44 @@ export const IDL: VoterStakeRegistry = { ], "instructions": [ { - code: 6006 - name: 'OutOfBoundsDepositEntryIndex' - msg: '' - }, - { - code: 6007 - name: 'UnusedDepositEntryIndex' - msg: '' - }, - { - code: 6008 - name: 'InsufficientUnlockedTokens' - msg: '' - }, - { - code: 6009 - name: 'UnableToConvert' - msg: '' - }, - { - code: 6010 - name: 'InvalidLockupPeriod' - msg: '' - }, - { - code: 6011 - name: 'InvalidEndTs' - msg: '' - }, - { - code: 6012 - name: 'InvalidDays' - msg: '' - }, - { - code: 6013 - name: 'VotingMintConfigIndexAlreadyInUse' - msg: '' - }, - { - code: 6014 - name: 'OutOfBoundsVotingMintConfigIndex' - msg: '' - }, - { - code: 6015 - name: 'InvalidDecimals' - msg: 'Exchange rate decimals cannot be larger than registrar decimals' - }, - { - code: 6016 - name: 'InvalidToDepositAndWithdrawInOneSlot' - msg: '' - }, - { - code: 6017 - name: 'ShouldBeTheFirstIxInATx' - msg: '' - }, - { - code: 6018 - name: 'ForbiddenCpi' - msg: '' - }, - { - code: 6019 - name: 'InvalidMint' - msg: '' - }, - { - code: 6020 - name: 'DebugInstruction' - msg: '' - }, - { - code: 6021 - name: 'ClawbackNotAllowedOnDeposit' - msg: '' - }, - { - code: 6022 - name: 'DepositStillLocked' - msg: '' - }, - { - code: 6023 - name: 'InvalidAuthority' - msg: '' - }, - { - code: 6024 - name: 'InvalidTokenOwnerRecord' - msg: '' - }, - { - code: 6025 - name: 'InvalidRealmAuthority' - msg: '' - }, - { - code: 6026 - name: 'VoterWeightOverflow' - msg: '' - }, - { - code: 6027 - name: 'LockupSaturationMustBePositive' - msg: '' - }, - { - code: 6028 - name: 'VotingMintConfiguredWithDifferentIndex' - msg: '' - }, - { - code: 6029 - name: 'InternalProgramError' - msg: '' - }, - { - code: 6030 - name: 'InsufficientLockedTokens' - msg: '' - }, - { - code: 6031 - name: 'MustKeepTokensLocked' - msg: '' - }, - { - code: 6032 - name: 'InvalidLockupKind' - msg: '' - }, - { - code: 6033 - name: 'InvalidChangeToClawbackDepositEntry' - msg: '' - }, - { - code: 6034 - name: 'InternalErrorBadLockupVoteWeight' - msg: '' - }, - { - code: 6035 - name: 'DepositStartTooFarInFuture' - msg: '' - }, - { - code: 6036 - name: 'VaultTokenNonZero' - msg: '' - }, - { - code: 6037 - name: 'InvalidTimestampArguments' - msg: '' - }, - { - code: 6038 - name: 'UnlockMustBeCalledFirst' - msg: '' - }, - { - code: 6039 - name: 'UnlockAlreadyRequested' - msg: '' - } - ] -} - -export const IDL: VoterStakeRegistry = { - version: '0.2.4', - name: 'voter_stake_registry', - docs: [ - '# Introduction', - '', - 'The governance registry is an "addin" to the SPL governance program that', - '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.', - '', - 'The flow for voting with this program is as follows:', - '', - '- 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.', - '', - '# 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.', - ], - instructions: [ - { - name: 'createRegistrar', - accounts: [ + "name": "createRegistrar", + "accounts": [ { - name: 'registrar', - isMut: true, - isSigner: false, - docs: [ - 'The voting registrar. There can only be a single registrar', - 'per governance realm and governing mint.', - ], + "name": "registrar", + "isMut": true, + "isSigner": false, + "docs": [ + "The voting registrar. There can only be a single registrar", + "per governance realm and governing mint." + ] }, { - name: 'realm', - isMut: false, - isSigner: false, - docs: [ - 'An spl-governance realm', - '', - '- realm is owned by the governance_program_id', - '- realm_governing_token_mint must be the community or council mint', - '- realm_authority is realm.authority', - ], + "name": "realm", + "isMut": false, + "isSigner": false, + "docs": [ + "An spl-governance realm", + "", + "- realm is owned by the governance_program_id", + "- realm_governing_token_mint must be the community or council mint", + "- realm_authority is realm.authority" + ] }, { - name: 'governanceProgramId', - isMut: false, - isSigner: false, - docs: [ - 'The program id of the spl-governance program the realm belongs to.', - ], + "name": "governanceProgramId", + "isMut": false, + "isSigner": false, + "docs": [ + "The program id of the spl-governance program the realm belongs to." + ] }, { - name: 'realmGoverningTokenMint', - isMut: false, - isSigner: false, - docs: ['Either the realm community mint or the council mint.'], + "name": "realmGoverningTokenMint", + "isMut": false, + "isSigner": false, + "docs": [ + "Either the realm community mint or the council mint." + ] }, { "name": "realmAuthority", @@ -1414,11 +1267,10 @@ export const IDL: VoterStakeRegistry = { "isSigner": false }, { - name: 'mint', - isMut: false, - isSigner: false, - docs: ['Tokens of this mint will produce vote weight'], - }, + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } ], "args": [ { @@ -1439,53 +1291,272 @@ export const IDL: VoterStakeRegistry = { "name": "configureVotingMint", "accounts": [ { - "name": "registrar", + "name": "registrar", + "isMut": true, + "isSigner": false + }, + { + "name": "realmAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "mint", + "isMut": false, + "isSigner": false, + "docs": [ + "Tokens of this mint will produce vote weight" + ] + } + ], + "args": [ + { + "name": "idx", + "type": "u16" + }, + { + "name": "digitShift", + "type": "i8" + }, + { + "name": "baselineVoteWeightScaledFactor", + "type": "u64" + }, + { + "name": "maxExtraLockupVoteWeightScaledFactor", + "type": "u64" + }, + { + "name": "lockupSaturationSecs", + "type": "u64" + }, + { + "name": "grantAuthority", + "type": { + "option": "publicKey" + } + } + ] + }, + { + "name": "createVoter", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false, + "docs": [ + "Also, Registrar plays the role of deposit_authority on the Rewards Contract,", + "therefore their PDA that should sign the CPI call" + ] + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true, + "docs": [ + "The authority controling the voter. Must be the same as the", + "`governing_token_owner` in the token owner record used with", + "spl-governance." + ] + }, + { + "name": "voterWeightRecord", + "isMut": true, + "isSigner": false, + "docs": [ + "The voter weight record is the account that will be shown to spl-governance", + "to prove how much vote weight the voter has. See update_voter_weight_record." + ] + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + }, + { + "name": "instructions", + "isMut": false, + "isSigner": false, + "docs": [ + "NOTE: this account is currently unused" + ] + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "voterBump", + "type": "u8" + }, + { + "name": "voterWeightRecordBump", + "type": "u8" + } + ] + }, + { + "name": "createDepositEntry", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "depositMint", + "isMut": false, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "associatedTokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + }, + { + "name": "kind", + "type": { + "defined": "LockupKind" + } + }, + { + "name": "startTs", + "type": { + "option": "u64" + } + }, + { + "name": "period", + "type": { + "defined": "LockupPeriod" + } + } + ] + }, + { + "name": "deposit", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, + { + "name": "depositToken", "isMut": true, "isSigner": false }, { - "name": "realmAuthority", + "name": "depositAuthority", "isMut": false, "isSigner": true }, { - name: 'voterAuthority', - isMut: false, - isSigner: true, - docs: [ - 'The authority controling the voter. Must be the same as the', - '`governing_token_owner` in the token owner record used with', - 'spl-governance.', - ], - }, - { - name: 'voterWeightRecord', - isMut: true, - isSigner: false, - docs: [ - 'The voter weight record is the account that will be shown to spl-governance', - 'to prove how much vote weight the voter has. See update_voter_weight_record.', - ], - }, - { - "name": "digitShift", - "type": "i8" - }, - { - name: 'systemProgram', - isMut: false, - isSigner: false, + "name": "tokenProgram", + "isMut": false, + "isSigner": false }, { - name: 'rent', - isMut: false, - isSigner: false, + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + ] }, { - name: 'instructions', - isMut: false, - isSigner: false, - docs: ['NOTE: this account is currently unused'], + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + ] }, { "name": "rewardsProgram", @@ -1495,18 +1566,13 @@ export const IDL: VoterStakeRegistry = { ], "args": [ { - name: 'voterBump', - type: 'u8', + "name": "depositEntryIndex", + "type": "u8" }, { - name: 'voterWeightRecordBump', - type: 'u8', + "name": "amount", + "type": "u64" }, - ], - }, - { - name: 'createDepositEntry', - accounts: [ { "name": "registrarBump", "type": "u8" @@ -1516,14 +1582,52 @@ export const IDL: VoterStakeRegistry = { "type": "publicKey" }, { - name: 'vault', - isMut: true, - isSigner: false, + "name": "realmPubkey", + "type": "publicKey" + } + ] + }, + { + "name": "withdraw", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, + { + "name": "voter", + "isMut": true, + "isSigner": false + }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true + }, + { + "name": "tokenOwnerRecord", + "isMut": false, + "isSigner": false, + "docs": [ + "The token_owner_record for the voter_authority. This is needed", + "to be able to forbid withdraws while the voter is engaged with", + "a vote or has an open proposal.", + "", + "- owned by registrar.governance_program_id", + "- for the registrar.realm", + "- for the registrar.realm_governing_token_mint", + "- governing_token_owner is voter_authority" + ] }, { - name: 'voterAuthority', - isMut: false, - isSigner: true, + "name": "voterWeightRecord", + "isMut": true, + "isSigner": false, + "docs": [ + "Withdraws must update the voter weight record, to prevent a stale", + "record being used to vote after the withdraw." + ] }, { "name": "vault", @@ -1558,8 +1662,12 @@ export const IDL: VoterStakeRegistry = { ], "args": [ { - name: 'depositEntryIndex', - type: 'u8', + "name": "depositEntryIndex", + "type": "u8" + }, + { + "name": "amount", + "type": "u64" }, { "name": "registrarBump", @@ -1570,11 +1678,24 @@ export const IDL: VoterStakeRegistry = { "type": "publicKey" }, { - name: 'period', - type: { - defined: 'LockupPeriod', - }, + "name": "realmPubkey", + "type": "publicKey" + } + ] + }, + { + "name": "closeDepositEntry", + "accounts": [ + { + "name": "voter", + "isMut": true, + "isSigner": false }, + { + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } ], "args": [ { @@ -1584,17 +1705,22 @@ export const IDL: VoterStakeRegistry = { ] }, { - name: 'deposit', - accounts: [ + "name": "updateVoterWeightRecord", + "accounts": [ { "name": "registrar", "isMut": false, "isSigner": false }, { - name: 'voter', - isMut: true, - isSigner: false, + "name": "voter", + "isMut": false, + "isSigner": false + }, + { + "name": "voterWeightRecord", + "isMut": true, + "isSigner": false }, { "name": "systemProgram", @@ -1608,27 +1734,45 @@ export const IDL: VoterStakeRegistry = { "name": "unlockTokens", "accounts": [ { - name: 'depositToken', - isMut: true, - isSigner: false, + "name": "registrar", + "isMut": false, + "isSigner": false }, { - name: 'depositAuthority', - isMut: false, - isSigner: true, + "name": "voter", + "isMut": true, + "isSigner": false }, { - name: 'tokenProgram', - isMut: false, - isSigner: false, + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } + ], + "args": [ + { + "name": "depositEntryIndex", + "type": "u8" + } + ] + }, + { + "name": "closeVoter", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false }, { - name: 'depositEntryIndex', - type: 'u8', + "name": "voter", + "isMut": true, + "isSigner": false }, { - name: 'amount', - type: 'u64', + "name": "voterAuthority", + "isMut": false, + "isSigner": true }, { "name": "solDestination", @@ -1644,67 +1788,57 @@ export const IDL: VoterStakeRegistry = { "args": [] }, { - name: 'withdraw', - accounts: [ + "name": "logVoterInfo", + "accounts": [ { "name": "registrar", "isMut": false, "isSigner": false }, { - name: 'voter', - isMut: true, - isSigner: false, - }, - { - name: 'voterAuthority', - isMut: false, - isSigner: true, - }, + "name": "voter", + "isMut": false, + "isSigner": false + } + ], + "args": [ { - name: 'tokenOwnerRecord', - isMut: false, - isSigner: false, - docs: [ - 'The token_owner_record for the voter_authority. This is needed', - 'to be able to forbid withdraws while the voter is engaged with', - 'a vote or has an open proposal.', - '', - '- owned by registrar.governance_program_id', - '- for the registrar.realm', - '- for the registrar.realm_governing_token_mint', - '- governing_token_owner is voter_authority', - ], + "name": "depositEntryBegin", + "type": "u8" }, { - name: 'voterWeightRecord', - isMut: true, - isSigner: false, - docs: [ - 'Withdraws must update the voter weight record, to prevent a stale', - 'record being used to vote after the withdraw.', - ], - }, + "name": "depositEntryCount", + "type": "u8" + } + ] + }, + { + "name": "internalTransferUnlocked", + "accounts": [ { - name: 'vault', - isMut: true, - isSigner: false, + "name": "registrar", + "isMut": false, + "isSigner": false }, { - name: 'destination', - isMut: true, - isSigner: false, + "name": "voter", + "isMut": true, + "isSigner": false }, { - name: 'tokenProgram', - isMut: false, - isSigner: false, - }, + "name": "voterAuthority", + "isMut": false, + "isSigner": true + } ], "args": [ { - name: 'depositEntryIndex', - type: 'u8', + "name": "sourceDepositEntryIndex", + "type": "u8" + }, + { + "name": "targetDepositEntryIndex", + "type": "u8" }, { "name": "amount", @@ -1713,12 +1847,17 @@ export const IDL: VoterStakeRegistry = { ] }, { - name: 'closeDepositEntry', - accounts: [ + "name": "restakeDeposit", + "accounts": [ + { + "name": "registrar", + "isMut": false, + "isSigner": false + }, { - name: 'voter', - isMut: true, - isSigner: false, + "name": "voter", + "isMut": true, + "isSigner": false }, { "name": "depositToken", @@ -1726,14 +1865,23 @@ export const IDL: VoterStakeRegistry = { "isSigner": false }, { - name: 'depositEntryIndex', - type: 'u8', + "name": "depositAuthority", + "isMut": false, + "isSigner": true, + "docs": [ + "The owner of the deposit and its reward's mining account" + ] + }, + { + "name": "tokenProgram", + "isMut": false, + "isSigner": false + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false }, - ], - }, - { - name: 'updateVoterWeightRecord', - accounts: [ { "name": "depositMining", "isMut": true, @@ -1759,33 +1907,19 @@ export const IDL: VoterStakeRegistry = { "defined": "LockupPeriod" } }, - ], - args: [], - }, - { - name: 'unlockTokens', - accounts: [ { "name": "registrarBump", "type": "u8" }, { - name: 'voter', - isMut: true, - isSigner: false, - }, - { - name: 'voterAuthority', - isMut: false, - isSigner: true, + "name": "realmGoverningMintPubkey", + "type": "publicKey" }, - ], - args: [ { - name: 'depositEntryIndex', - type: 'u8', - }, - ], + "name": "realmPubkey", + "type": "publicKey" + } + ] }, { "name": "claim", @@ -1849,351 +1983,144 @@ export const IDL: VoterStakeRegistry = { "isSigner": false } ], - }, - { - name: 'internalTransferUnlocked', - accounts: [ - { - name: 'registrar', - isMut: false, - isSigner: false, - }, - { - name: 'voter', - isMut: true, - isSigner: false, - }, - { - name: 'voterAuthority', - isMut: false, - isSigner: true, - }, - { - name: 'sourceDepositEntryIndex', - type: 'u8', - }, - { - name: 'targetDepositEntryIndex', - type: 'u8', - }, - { - name: 'amount', - type: 'u64', - }, - ], - "returns": "u64" - } - ], - "types": [ - { - name: 'registrar', - docs: ['Instance of a voting rights distributor.'], - type: { - kind: 'struct', - fields: [ - { - name: 'governanceProgramId', - type: 'publicKey', - }, - { - name: 'realm', - type: 'publicKey', - }, - { - name: 'realmGoverningTokenMint', - type: 'publicKey', - }, - { - name: 'realmAuthority', - type: 'publicKey', - }, - { - name: 'votingMints', - docs: [ - 'Storage for voting mints and their configuration.', - "The length should be adjusted for one's use case.", - ], - type: { - array: [ - { - defined: 'VotingMintConfig', - }, - 4, - ], - }, - }, - { - name: 'timeOffset', - docs: [ - 'Debug only: time offset, to allow tests to move forward in time.', - ], - type: 'i64', - }, - { - name: 'bump', - type: 'u8', - }, - ], - }, - }, - { - name: 'voter', - docs: ['User account for minting voting rights.'], - type: { - kind: 'struct', - fields: [ - { - name: 'deposits', - type: { - array: [ - { - defined: 'DepositEntry', - }, - 32, - ], - }, - }, - { - name: 'voterAuthority', - type: 'publicKey', - }, - { - name: 'registrar', - type: 'publicKey', - }, - { - name: 'voterBump', - type: 'u8', - }, - { - name: 'voterWeightRecordBump', - type: 'u8', - }, - { - name: 'reserved1', - type: { - array: ['u8', 14], - }, - }, - ], - }, - }, + "args": [ + { + "name": "registrarBump", + "type": "u8" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" + } + ], + "returns": "u64" + } ], - types: [ - { - name: 'VestingInfo', - type: { - kind: 'struct', - fields: [ - { - name: 'rate', - docs: ['Amount of tokens vested each period'], - type: 'u64', - }, - { - name: 'nextTimestamp', - docs: ['Time of the next upcoming vesting'], - type: 'u64', - }, - ], - }, - }, + "types": [ { - name: 'LockingInfo', - type: { - kind: 'struct', - fields: [ - { - name: 'amount', - docs: ['Amount of locked tokens'], - type: 'u64', - }, + "name": "VestingInfo", + "type": { + "kind": "struct", + "fields": [ { - name: 'endTimestamp', - docs: [ - 'Time at which the lockup fully ends (None for Constant lockup)', + "name": "rate", + "docs": [ + "Amount of tokens vested each period" ], - type: { - option: 'u64', - }, - }, - { - name: 'vesting', - docs: ['Information about vesting, if any'], - type: { - option: { - defined: 'VestingInfo', - }, - }, - }, - ], - }, - }, - { - name: 'DepositEntry', - docs: [ - 'Bookkeeping for a single deposit for a given mint and lockup schedule.', - ], - type: { - kind: 'struct', - fields: [ - { - name: 'lockup', - type: { - defined: 'Lockup', - }, + "type": "u64" }, { - name: 'amountDepositedNative', - docs: [ - 'Amount in deposited, in native currency. Withdraws of vested tokens', - 'directly reduce this amount.', - '', - 'This directly tracks the total amount added by the user. They may', - 'never withdraw more than this amount.', + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" ], - type: 'u64', - }, - { - name: 'votingMintConfigIdx', - type: 'u8', - }, - { - name: 'isUsed', - type: 'bool', - }, - { - name: 'reserved1', - type: { - array: ['u8', 6], - }, - }, - ], - }, + "type": "u64" + } + ] + } }, { - name: 'Lockup', - type: { - kind: 'struct', - fields: [ + "name": "LockingInfo", + "type": { + "kind": "struct", + "fields": [ { - name: 'startTs', - docs: [ - 'Note, that if start_ts is in the future, the funds are nevertheless', - 'locked up!', - 'Start of the lockup.', + "name": "amount", + "docs": [ + "Amount of locked tokens" ], - type: 'u64', - }, - { - name: 'endTs', - docs: ['End of the lockup.'], - type: 'u64', - }, - { - name: 'cooldownEndsAt', - docs: ['End of the cooldown.'], - type: 'u64', - }, - { - name: 'cooldownRequested', - type: 'bool', - }, - { - name: 'kind', - docs: ['Type of lockup.'], - type: { - defined: 'LockupKind', - }, - }, - { - name: 'period', - docs: ['Type of lockup'], - type: { - defined: 'LockupPeriod', - }, - }, - { - name: 'reserved1', - docs: ['Padding after period to align the struct size to 8 bytes'], - type: { - array: ['u8', 5], - }, - }, - ], - }, - }, - { - name: 'VotingMintConfig', - docs: [ - 'Exchange rate for an asset that can be used to mint voting rights.', - '', - 'See documentation of configure_voting_mint for details on how', - 'native token amounts convert to vote weight.', - ], - type: { - kind: 'struct', - fields: [ - { - name: 'mint', - docs: ['Mint for this entry.'], - type: 'publicKey', + "type": "u64" }, { - name: 'grantAuthority', - docs: ['The authority that is allowed to push grants into voters'], - type: 'publicKey', - }, - { - name: 'baselineVoteWeightScaledFactor', - docs: [ - 'Vote weight factor for all funds in the account, no matter if locked or not.', - '', - 'In 1/SCALED_FACTOR_BASE units.', + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" ], - type: 'u64', + "type": { + "option": "u64" + } }, { - name: 'maxExtraLockupVoteWeightScaledFactor', - docs: [ - 'Maximum extra vote weight factor for lockups.', - '', - 'This is the extra votes gained for lockups lasting lockup_saturation_secs or', - 'longer. Shorter lockups receive only a fraction of the maximum extra vote weight,', - 'based on lockup_time divided by lockup_saturation_secs.', - '', - 'In 1/SCALED_FACTOR_BASE units.', + "name": "vesting", + "docs": [ + "Information about vesting, if any" ], - type: 'u64', - }, + "type": { + "option": { + "defined": "VestingInfo" + } + } + } + ] + } + }, + { + "name": "RewardsInstruction", + "type": { + "kind": "enum", + "variants": [ { - name: 'lockupSaturationSecs', - docs: [ - 'Number of seconds of lockup needed to reach the maximum lockup bonus.', - ], - type: 'u64', + "name": "InitializePool", + "fields": [ + { + "name": "deposit_authority", + "docs": [ + "Account responsible for charging mining owners" + ], + "type": "publicKey" + }, + { + "name": "fill_authority", + "docs": [ + "Account can fill the reward vault" + ], + "type": "publicKey" + }, + { + "name": "distribution_authority", + "docs": [ + "Account that can distribute money among users after", + "if RewardVault had been filled with rewards" + ], + "type": "publicKey" + } + ] }, { - name: 'digitShift', - docs: [ - 'Number of digits to shift native amounts, applying a 10^digit_shift factor.', - ], - type: 'i8', + "name": "FillVault", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to fill" + ], + "type": "u64" + }, + { + "name": "distribution_ends_at", + "docs": [ + "Rewards distribution ends at given date" + ], + "type": "u64" + } + ] }, { - name: 'padding', - type: { - array: ['u8', 7], - }, + "name": "InitializeMining", + "fields": [ + { + "name": "mining_owner", + "docs": [ + "Represent the end-user, owner of the mining" + ], + "type": "publicKey" + } + ] }, - ], - }, - }, - { - name: 'LockupPeriod', - type: { - kind: 'enum', - variants: [ { "name": "DepositMining", "fields": [ @@ -2223,30 +2150,54 @@ export const IDL: VoterStakeRegistry = { ] }, { - name: 'Test', - }, - { - name: 'ThreeMonths', - }, - { - name: 'SixMonths', - }, - { - name: 'OneYear', + "name": "WithdrawMining", + "fields": [ + { + "name": "amount", + "docs": [ + "Amount to withdraw" + ], + "type": "u64" + }, + { + "name": "owner", + "docs": [ + "Specifies the owner of the Mining Account" + ], + "type": "publicKey" + } + ] }, { - name: 'Flex', + "name": "Claim" }, - ], - }, - }, - { - name: 'LockupKind', - type: { - kind: 'enum', - variants: [ { - name: 'None', + "name": "RestakeDeposit", + "fields": [ + { + "name": "lockup_period", + "docs": [ + "Requested lockup period for restaking" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "amount", + "docs": [ + "Amount of tokens to be restaked" + ], + "type": "u64" + }, + { + "name": "deposit_start_ts", + "docs": [ + "Deposit start_ts" + ], + "type": "u64" + } + ] }, { "name": "DistributeRewards" @@ -2306,211 +2257,9 @@ export const IDL: VoterStakeRegistry = { "defined": "LockingInfo" } }, - index: false, - }, - ], - }, - ], - errors: [ - { - code: 6000, - name: 'InvalidRate', - msg: 'Exchange rate must be greater than zero', - }, - { - code: 6001, - name: 'RatesFull', - msg: '', - }, - { - code: 6002, - name: 'VotingMintNotFound', - msg: '', - }, - { - code: 6003, - name: 'DepositEntryNotFound', - msg: '', - }, - { - code: 6004, - name: 'DepositEntryFull', - msg: '', - }, - { - code: 6005, - name: 'VotingTokenNonZero', - msg: '', - }, - { - code: 6006, - name: 'OutOfBoundsDepositEntryIndex', - msg: '', - }, - { - code: 6007, - name: 'UnusedDepositEntryIndex', - msg: '', - }, - { - code: 6008, - name: 'InsufficientUnlockedTokens', - msg: '', - }, - { - code: 6009, - name: 'UnableToConvert', - msg: '', - }, - { - code: 6010, - name: 'InvalidLockupPeriod', - msg: '', - }, - { - code: 6011, - name: 'InvalidEndTs', - msg: '', - }, - { - code: 6012, - name: 'InvalidDays', - msg: '', - }, - { - code: 6013, - name: 'VotingMintConfigIndexAlreadyInUse', - msg: '', - }, - { - code: 6014, - name: 'OutOfBoundsVotingMintConfigIndex', - msg: '', - }, - { - code: 6015, - name: 'InvalidDecimals', - msg: 'Exchange rate decimals cannot be larger than registrar decimals', - }, - { - code: 6016, - name: 'InvalidToDepositAndWithdrawInOneSlot', - msg: '', - }, - { - code: 6017, - name: 'ShouldBeTheFirstIxInATx', - msg: '', - }, - { - code: 6018, - name: 'ForbiddenCpi', - msg: '', - }, - { - code: 6019, - name: 'InvalidMint', - msg: '', - }, - { - code: 6020, - name: 'DebugInstruction', - msg: '', - }, - { - code: 6021, - name: 'ClawbackNotAllowedOnDeposit', - msg: '', - }, - { - code: 6022, - name: 'DepositStillLocked', - msg: '', - }, - { - code: 6023, - name: 'InvalidAuthority', - msg: '', - }, - { - code: 6024, - name: 'InvalidTokenOwnerRecord', - msg: '', - }, - { - code: 6025, - name: 'InvalidRealmAuthority', - msg: '', - }, - { - code: 6026, - name: 'VoterWeightOverflow', - msg: '', - }, - { - code: 6027, - name: 'LockupSaturationMustBePositive', - msg: '', - }, - { - code: 6028, - name: 'VotingMintConfiguredWithDifferentIndex', - msg: '', - }, - { - code: 6029, - name: 'InternalProgramError', - msg: '', - }, - { - code: 6030, - name: 'InsufficientLockedTokens', - msg: '', - }, - { - code: 6031, - name: 'MustKeepTokensLocked', - msg: '', - }, - { - code: 6032, - name: 'InvalidLockupKind', - msg: '', - }, - { - code: 6033, - name: 'InvalidChangeToClawbackDepositEntry', - msg: '', - }, - { - code: 6034, - name: 'InternalErrorBadLockupVoteWeight', - msg: '', - }, - { - code: 6035, - name: 'DepositStartTooFarInFuture', - msg: '', - }, - { - code: 6036, - name: 'VaultTokenNonZero', - msg: '', - }, - { - code: 6037, - name: 'InvalidTimestampArguments', - msg: '', - }, - { - code: 6038, - name: 'UnlockMustBeCalledFirst', - msg: '', - }, - { - code: 6039, - name: 'UnlockAlreadyRequested', - msg: '', - }, - ], -} + "index": false + } + ] + } + ] +}; From ba000f957d3d11d52e6abf1c261271851870a0a4 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 12 Jun 2024 12:34:19 +0300 Subject: [PATCH 35/59] fix for updating idl --- idl/voter_stake_registry.ts | 1080 ++++++++++++++++++++++++++++++++++- 1 file changed, 1079 insertions(+), 1 deletion(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 1bd7b9da..4a39b631 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -867,6 +867,108 @@ export type VoterStakeRegistry = { "returns": "u64" } ], + "accounts": [ + { + "name": "registrar", + "docs": [ + "Instance of a voting rights distributor." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "governanceProgramId", + "type": "publicKey" + }, + { + "name": "realm", + "type": "publicKey" + }, + { + "name": "realmGoverningTokenMint", + "type": "publicKey" + }, + { + "name": "realmAuthority", + "type": "publicKey" + }, + { + "name": "votingMints", + "docs": [ + "Storage for voting mints and their configuration.", + "The length should be adjusted for one's use case." + ], + "type": { + "array": [ + { + "defined": "VotingMintConfig" + }, + 2 + ] + } + }, + { + "name": "timeOffset", + "docs": [ + "Debug only: time offset, to allow tests to move forward in time." + ], + "type": "i64" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + }, + { + "name": "voter", + "docs": [ + "User account for minting voting rights." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "deposits", + "type": { + "array": [ + { + "defined": "DepositEntry" + }, + 32 + ] + } + }, + { + "name": "voterAuthority", + "type": "publicKey" + }, + { + "name": "registrar", + "type": "publicKey" + }, + { + "name": "voterBump", + "type": "u8" + }, + { + "name": "voterWeightRecordBump", + "type": "u8" + }, + { + "name": "reserved1", + "type": { + "array": [ + "u8", + 14 + ] + } + } + ] + } + } + ], "types": [ { "name": "VestingInfo", @@ -925,6 +1027,189 @@ export type VoterStakeRegistry = { ] } }, + { + "name": "DepositEntry", + "docs": [ + "Bookkeeping for a single deposit for a given mint and lockup schedule." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "lockup", + "type": { + "defined": "Lockup" + } + }, + { + "name": "amountDepositedNative", + "docs": [ + "Amount in deposited, in native currency. Withdraws of vested tokens", + "directly reduce this amount.", + "", + "This directly tracks the total amount added by the user. They may", + "never withdraw more than this amount." + ], + "type": "u64" + }, + { + "name": "votingMintConfigIdx", + "type": "u8" + }, + { + "name": "isUsed", + "type": "bool" + }, + { + "name": "reserved1", + "type": { + "array": [ + "u8", + 6 + ] + } + } + ] + } + }, + { + "name": "Lockup", + "type": { + "kind": "struct", + "fields": [ + { + "name": "startTs", + "docs": [ + "Note, that if start_ts is in the future, the funds are nevertheless", + "locked up!", + "Start of the lockup." + ], + "type": "u64" + }, + { + "name": "endTs", + "docs": [ + "End of the lockup." + ], + "type": "u64" + }, + { + "name": "cooldownEndsAt", + "docs": [ + "End of the cooldown." + ], + "type": "u64" + }, + { + "name": "cooldownRequested", + "type": "bool" + }, + { + "name": "kind", + "docs": [ + "Type of lockup." + ], + "type": { + "defined": "LockupKind" + } + }, + { + "name": "period", + "docs": [ + "Type of lockup" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "reserved1", + "docs": [ + "Padding after period to align the struct size to 8 bytes" + ], + "type": { + "array": [ + "u8", + 5 + ] + } + } + ] + } + }, + { + "name": "VotingMintConfig", + "docs": [ + "Exchange rate for an asset that can be used to mint voting rights.", + "", + "See documentation of configure_voting_mint for details on how", + "native token amounts convert to vote weight." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "docs": [ + "Mint for this entry." + ], + "type": "publicKey" + }, + { + "name": "grantAuthority", + "docs": [ + "The authority that is allowed to push grants into voters" + ], + "type": "publicKey" + }, + { + "name": "baselineVoteWeightScaledFactor", + "docs": [ + "Vote weight factor for all funds in the account, no matter if locked or not.", + "", + "In 1/SCALED_FACTOR_BASE units." + ], + "type": "u64" + }, + { + "name": "maxExtraLockupVoteWeightScaledFactor", + "docs": [ + "Maximum extra vote weight factor for lockups.", + "", + "This is the extra votes gained for lockups lasting lockup_saturation_secs or", + "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", + "based on lockup_time divided by lockup_saturation_secs.", + "", + "In 1/SCALED_FACTOR_BASE units." + ], + "type": "u64" + }, + { + "name": "lockupSaturationSecs", + "docs": [ + "Number of seconds of lockup needed to reach the maximum lockup bonus." + ], + "type": "u64" + }, + { + "name": "digitShift", + "docs": [ + "Number of digits to shift native amounts, applying a 10^digit_shift factor." + ], + "type": "i8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 7 + ] + } + } + ] + } + }, { "name": "RewardsInstruction", "type": { @@ -1071,6 +1356,43 @@ export type VoterStakeRegistry = { } ] } + }, + { + "name": "LockupPeriod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "ThreeMonths" + }, + { + "name": "SixMonths" + }, + { + "name": "OneYear" + }, + { + "name": "Flex" + } + ] + } + }, + { + "name": "LockupKind", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "Constant" + } + ] + } } ], "events": [ @@ -1128,6 +1450,223 @@ export type VoterStakeRegistry = { } ] } + ], + "errors": [ + { + "code": 6000, + "name": "InvalidRate", + "msg": "Exchange rate must be greater than zero" + }, + { + "code": 6001, + "name": "RatesFull", + "msg": "" + }, + { + "code": 6002, + "name": "VotingMintNotFound", + "msg": "" + }, + { + "code": 6003, + "name": "DepositEntryNotFound", + "msg": "" + }, + { + "code": 6004, + "name": "DepositEntryFull", + "msg": "" + }, + { + "code": 6005, + "name": "VotingTokenNonZero", + "msg": "" + }, + { + "code": 6006, + "name": "OutOfBoundsDepositEntryIndex", + "msg": "" + }, + { + "code": 6007, + "name": "UnusedDepositEntryIndex", + "msg": "" + }, + { + "code": 6008, + "name": "InsufficientUnlockedTokens", + "msg": "" + }, + { + "code": 6009, + "name": "UnableToConvert", + "msg": "" + }, + { + "code": 6010, + "name": "InvalidLockupPeriod", + "msg": "" + }, + { + "code": 6011, + "name": "InvalidEndTs", + "msg": "" + }, + { + "code": 6012, + "name": "InvalidDays", + "msg": "" + }, + { + "code": 6013, + "name": "VotingMintConfigIndexAlreadyInUse", + "msg": "" + }, + { + "code": 6014, + "name": "OutOfBoundsVotingMintConfigIndex", + "msg": "" + }, + { + "code": 6015, + "name": "InvalidDecimals", + "msg": "Exchange rate decimals cannot be larger than registrar decimals" + }, + { + "code": 6016, + "name": "InvalidToDepositAndWithdrawInOneSlot", + "msg": "" + }, + { + "code": 6017, + "name": "ShouldBeTheFirstIxInATx", + "msg": "" + }, + { + "code": 6018, + "name": "ForbiddenCpi", + "msg": "" + }, + { + "code": 6019, + "name": "InvalidMint", + "msg": "" + }, + { + "code": 6020, + "name": "DebugInstruction", + "msg": "" + }, + { + "code": 6021, + "name": "ClawbackNotAllowedOnDeposit", + "msg": "" + }, + { + "code": 6022, + "name": "DepositStillLocked", + "msg": "" + }, + { + "code": 6023, + "name": "InvalidAuthority", + "msg": "" + }, + { + "code": 6024, + "name": "InvalidTokenOwnerRecord", + "msg": "" + }, + { + "code": 6025, + "name": "InvalidRealmAuthority", + "msg": "" + }, + { + "code": 6026, + "name": "VoterWeightOverflow", + "msg": "" + }, + { + "code": 6027, + "name": "LockupSaturationMustBePositive", + "msg": "" + }, + { + "code": 6028, + "name": "VotingMintConfiguredWithDifferentIndex", + "msg": "" + }, + { + "code": 6029, + "name": "InternalProgramError", + "msg": "" + }, + { + "code": 6030, + "name": "InsufficientLockedTokens", + "msg": "" + }, + { + "code": 6031, + "name": "MustKeepTokensLocked", + "msg": "" + }, + { + "code": 6032, + "name": "InvalidLockupKind", + "msg": "" + }, + { + "code": 6033, + "name": "InvalidChangeToClawbackDepositEntry", + "msg": "" + }, + { + "code": 6034, + "name": "InternalErrorBadLockupVoteWeight", + "msg": "" + }, + { + "code": 6035, + "name": "DepositStartTooFarInFuture", + "msg": "" + }, + { + "code": 6036, + "name": "VaultTokenNonZero", + "msg": "" + }, + { + "code": 6037, + "name": "InvalidTimestampArguments", + "msg": "" + }, + { + "code": 6038, + "name": "UnlockMustBeCalledFirst", + "msg": "" + }, + { + "code": 6039, + "name": "UnlockAlreadyRequested", + "msg": "" + }, + { + "code": 6040, + "name": "RestakeDepositIsNotAllowed", + "msg": "" + }, + { + "code": 6041, + "name": "DepositingIsForbidded", + "msg": "To deposit additional tokens, extend the deposit" + }, + { + "code": 6042, + "name": "CpiReturnDataIsAbsent", + "msg": "Cpi call must return data, but data is absent" + } ] }; @@ -1997,7 +2536,109 @@ export const IDL: VoterStakeRegistry = { "type": "publicKey" } ], - "returns": "u64" + "returns": "u64" + } + ], + "accounts": [ + { + "name": "registrar", + "docs": [ + "Instance of a voting rights distributor." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "governanceProgramId", + "type": "publicKey" + }, + { + "name": "realm", + "type": "publicKey" + }, + { + "name": "realmGoverningTokenMint", + "type": "publicKey" + }, + { + "name": "realmAuthority", + "type": "publicKey" + }, + { + "name": "votingMints", + "docs": [ + "Storage for voting mints and their configuration.", + "The length should be adjusted for one's use case." + ], + "type": { + "array": [ + { + "defined": "VotingMintConfig" + }, + 2 + ] + } + }, + { + "name": "timeOffset", + "docs": [ + "Debug only: time offset, to allow tests to move forward in time." + ], + "type": "i64" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + }, + { + "name": "voter", + "docs": [ + "User account for minting voting rights." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "deposits", + "type": { + "array": [ + { + "defined": "DepositEntry" + }, + 32 + ] + } + }, + { + "name": "voterAuthority", + "type": "publicKey" + }, + { + "name": "registrar", + "type": "publicKey" + }, + { + "name": "voterBump", + "type": "u8" + }, + { + "name": "voterWeightRecordBump", + "type": "u8" + }, + { + "name": "reserved1", + "type": { + "array": [ + "u8", + 14 + ] + } + } + ] + } } ], "types": [ @@ -2058,6 +2699,189 @@ export const IDL: VoterStakeRegistry = { ] } }, + { + "name": "DepositEntry", + "docs": [ + "Bookkeeping for a single deposit for a given mint and lockup schedule." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "lockup", + "type": { + "defined": "Lockup" + } + }, + { + "name": "amountDepositedNative", + "docs": [ + "Amount in deposited, in native currency. Withdraws of vested tokens", + "directly reduce this amount.", + "", + "This directly tracks the total amount added by the user. They may", + "never withdraw more than this amount." + ], + "type": "u64" + }, + { + "name": "votingMintConfigIdx", + "type": "u8" + }, + { + "name": "isUsed", + "type": "bool" + }, + { + "name": "reserved1", + "type": { + "array": [ + "u8", + 6 + ] + } + } + ] + } + }, + { + "name": "Lockup", + "type": { + "kind": "struct", + "fields": [ + { + "name": "startTs", + "docs": [ + "Note, that if start_ts is in the future, the funds are nevertheless", + "locked up!", + "Start of the lockup." + ], + "type": "u64" + }, + { + "name": "endTs", + "docs": [ + "End of the lockup." + ], + "type": "u64" + }, + { + "name": "cooldownEndsAt", + "docs": [ + "End of the cooldown." + ], + "type": "u64" + }, + { + "name": "cooldownRequested", + "type": "bool" + }, + { + "name": "kind", + "docs": [ + "Type of lockup." + ], + "type": { + "defined": "LockupKind" + } + }, + { + "name": "period", + "docs": [ + "Type of lockup" + ], + "type": { + "defined": "LockupPeriod" + } + }, + { + "name": "reserved1", + "docs": [ + "Padding after period to align the struct size to 8 bytes" + ], + "type": { + "array": [ + "u8", + 5 + ] + } + } + ] + } + }, + { + "name": "VotingMintConfig", + "docs": [ + "Exchange rate for an asset that can be used to mint voting rights.", + "", + "See documentation of configure_voting_mint for details on how", + "native token amounts convert to vote weight." + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "mint", + "docs": [ + "Mint for this entry." + ], + "type": "publicKey" + }, + { + "name": "grantAuthority", + "docs": [ + "The authority that is allowed to push grants into voters" + ], + "type": "publicKey" + }, + { + "name": "baselineVoteWeightScaledFactor", + "docs": [ + "Vote weight factor for all funds in the account, no matter if locked or not.", + "", + "In 1/SCALED_FACTOR_BASE units." + ], + "type": "u64" + }, + { + "name": "maxExtraLockupVoteWeightScaledFactor", + "docs": [ + "Maximum extra vote weight factor for lockups.", + "", + "This is the extra votes gained for lockups lasting lockup_saturation_secs or", + "longer. Shorter lockups receive only a fraction of the maximum extra vote weight,", + "based on lockup_time divided by lockup_saturation_secs.", + "", + "In 1/SCALED_FACTOR_BASE units." + ], + "type": "u64" + }, + { + "name": "lockupSaturationSecs", + "docs": [ + "Number of seconds of lockup needed to reach the maximum lockup bonus." + ], + "type": "u64" + }, + { + "name": "digitShift", + "docs": [ + "Number of digits to shift native amounts, applying a 10^digit_shift factor." + ], + "type": "i8" + }, + { + "name": "padding", + "type": { + "array": [ + "u8", + 7 + ] + } + } + ] + } + }, { "name": "RewardsInstruction", "type": { @@ -2204,6 +3028,43 @@ export const IDL: VoterStakeRegistry = { } ] } + }, + { + "name": "LockupPeriod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "ThreeMonths" + }, + { + "name": "SixMonths" + }, + { + "name": "OneYear" + }, + { + "name": "Flex" + } + ] + } + }, + { + "name": "LockupKind", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "Constant" + } + ] + } } ], "events": [ @@ -2261,5 +3122,222 @@ export const IDL: VoterStakeRegistry = { } ] } + ], + "errors": [ + { + "code": 6000, + "name": "InvalidRate", + "msg": "Exchange rate must be greater than zero" + }, + { + "code": 6001, + "name": "RatesFull", + "msg": "" + }, + { + "code": 6002, + "name": "VotingMintNotFound", + "msg": "" + }, + { + "code": 6003, + "name": "DepositEntryNotFound", + "msg": "" + }, + { + "code": 6004, + "name": "DepositEntryFull", + "msg": "" + }, + { + "code": 6005, + "name": "VotingTokenNonZero", + "msg": "" + }, + { + "code": 6006, + "name": "OutOfBoundsDepositEntryIndex", + "msg": "" + }, + { + "code": 6007, + "name": "UnusedDepositEntryIndex", + "msg": "" + }, + { + "code": 6008, + "name": "InsufficientUnlockedTokens", + "msg": "" + }, + { + "code": 6009, + "name": "UnableToConvert", + "msg": "" + }, + { + "code": 6010, + "name": "InvalidLockupPeriod", + "msg": "" + }, + { + "code": 6011, + "name": "InvalidEndTs", + "msg": "" + }, + { + "code": 6012, + "name": "InvalidDays", + "msg": "" + }, + { + "code": 6013, + "name": "VotingMintConfigIndexAlreadyInUse", + "msg": "" + }, + { + "code": 6014, + "name": "OutOfBoundsVotingMintConfigIndex", + "msg": "" + }, + { + "code": 6015, + "name": "InvalidDecimals", + "msg": "Exchange rate decimals cannot be larger than registrar decimals" + }, + { + "code": 6016, + "name": "InvalidToDepositAndWithdrawInOneSlot", + "msg": "" + }, + { + "code": 6017, + "name": "ShouldBeTheFirstIxInATx", + "msg": "" + }, + { + "code": 6018, + "name": "ForbiddenCpi", + "msg": "" + }, + { + "code": 6019, + "name": "InvalidMint", + "msg": "" + }, + { + "code": 6020, + "name": "DebugInstruction", + "msg": "" + }, + { + "code": 6021, + "name": "ClawbackNotAllowedOnDeposit", + "msg": "" + }, + { + "code": 6022, + "name": "DepositStillLocked", + "msg": "" + }, + { + "code": 6023, + "name": "InvalidAuthority", + "msg": "" + }, + { + "code": 6024, + "name": "InvalidTokenOwnerRecord", + "msg": "" + }, + { + "code": 6025, + "name": "InvalidRealmAuthority", + "msg": "" + }, + { + "code": 6026, + "name": "VoterWeightOverflow", + "msg": "" + }, + { + "code": 6027, + "name": "LockupSaturationMustBePositive", + "msg": "" + }, + { + "code": 6028, + "name": "VotingMintConfiguredWithDifferentIndex", + "msg": "" + }, + { + "code": 6029, + "name": "InternalProgramError", + "msg": "" + }, + { + "code": 6030, + "name": "InsufficientLockedTokens", + "msg": "" + }, + { + "code": 6031, + "name": "MustKeepTokensLocked", + "msg": "" + }, + { + "code": 6032, + "name": "InvalidLockupKind", + "msg": "" + }, + { + "code": 6033, + "name": "InvalidChangeToClawbackDepositEntry", + "msg": "" + }, + { + "code": 6034, + "name": "InternalErrorBadLockupVoteWeight", + "msg": "" + }, + { + "code": 6035, + "name": "DepositStartTooFarInFuture", + "msg": "" + }, + { + "code": 6036, + "name": "VaultTokenNonZero", + "msg": "" + }, + { + "code": 6037, + "name": "InvalidTimestampArguments", + "msg": "" + }, + { + "code": 6038, + "name": "UnlockMustBeCalledFirst", + "msg": "" + }, + { + "code": 6039, + "name": "UnlockAlreadyRequested", + "msg": "" + }, + { + "code": 6040, + "name": "RestakeDepositIsNotAllowed", + "msg": "" + }, + { + "code": 6041, + "name": "DepositingIsForbidded", + "msg": "To deposit additional tokens, extend the deposit" + }, + { + "code": 6042, + "name": "CpiReturnDataIsAbsent", + "msg": "Cpi call must return data, but data is absent" + } ] }; From 9886d519a17c8b3fb9e354e5b91b92396db46159 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 12 Jun 2024 16:18:42 +0300 Subject: [PATCH 36/59] update fixture for testing --- .../tests/fixtures/mplx_rewards.so | Bin 296472 -> 296920 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index cdb32e955133f5201056088719128e0a6cb292ee..b4e525d7df51b9d2e699653bfab7bc3fae892906 100755 GIT binary patch delta 33781 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 delta 33126 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 B+|>X8 From ab8bef73e690ee3b66bef3ac4755719c856d8e5e Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 12 Jun 2024 18:05:56 +0300 Subject: [PATCH 37/59] remove mentions about fill_authority --- .../src/instructions/claim.rs | 2 +- .../src/instructions/create_registrar.rs | 2 +- .../tests/fixtures/mplx_rewards.so | Bin 296920 -> 298760 bytes .../tests/program_test/addin.rs | 6 +----- 4 files changed, 3 insertions(+), 7 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index b7c06bbf..55075187 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -10,7 +10,7 @@ use solana_program::program::get_return_data; #[derive(Accounts)] pub struct Claim<'info> { /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) + /// PDA("reward_pool", deposit_authority[aka registrar in our case]) pub reward_pool: UncheckedAccount<'info>, /// CHECK: Rewards mint addr will be checked in the rewards contract diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index 573f2cfb..4fd6b881 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -42,7 +42,7 @@ pub struct CreateRegistrar<'info> { /// Account that will be created via CPI to the rewards, /// it's responsible for being a "root" for all entities /// inside rewards contract - /// It's the PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) + /// PDA("reward_pool", deposit_authority[aka registrar in our case]) #[account(mut)] reward_pool: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index b4e525d7df51b9d2e699653bfab7bc3fae892906..ee970b8fc2361b3cf0e4e5360e8fee55fbfbf26b 100755 GIT binary patch delta 54557 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 T(ecQ8xfCAc-2~C@$XojlA1Sw+ diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index dface60e..70268cdd 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -70,11 +70,7 @@ impl AddinCookie { ); let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( - &[ - "reward_pool".as_bytes(), - ®istrar.key().to_bytes(), - &fill_authority.key().to_bytes(), - ], + &["reward_pool".as_bytes(), ®istrar.key().to_bytes()], rewards_program, ); From 788136ef350331bb3e7d00aec145deeb3f9ec7fd Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 12 Jun 2024 18:43:16 +0300 Subject: [PATCH 38/59] Clarify comments regarding PDA calculations for the Rewards contract --- programs/voter-stake-registry/src/instructions/claim.rs | 6 +++--- .../src/instructions/create_registrar.rs | 4 ++-- .../voter-stake-registry/src/instructions/create_voter.rs | 4 ++-- programs/voter-stake-registry/src/instructions/deposit.rs | 4 ++-- .../voter-stake-registry/src/instructions/extend_deposit.rs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index 55075187..a05edc45 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -10,7 +10,7 @@ use solana_program::program::get_return_data; #[derive(Accounts)] pub struct Claim<'info> { /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA("reward_pool", deposit_authority[aka registrar in our case]) + /// PDA(["reward_pool", deposit_authority[aka registrar in our case]], rewards_program) pub reward_pool: UncheckedAccount<'info>, /// CHECK: Rewards mint addr will be checked in the rewards contract @@ -19,11 +19,11 @@ pub struct Claim<'info> { #[account(mut)] /// CHECK: Rewards vault is used as a source of rewards and /// is checked on the rewards contract - /// PDA("vault", reward_pool, reward_mint) + /// PDA(["vault", reward_pool, reward_mint], reward_program) pub vault: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) + /// PDA(["mining", mining owner , reward_pool], reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index 4fd6b881..d81901ca 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -42,13 +42,13 @@ pub struct CreateRegistrar<'info> { /// Account that will be created via CPI to the rewards, /// it's responsible for being a "root" for all entities /// inside rewards contract - /// PDA("reward_pool", deposit_authority[aka registrar in our case]) + /// PDA(["reward_pool", deposit_authority[aka registrar in our case]], rewards_program) #[account(mut)] reward_pool: UncheckedAccount<'info>, /// CHECK: any address is allowed /// This account is responsible for storing money for rewards - /// PDA("vault", reward_pool, reward_mint) + /// PDA(["vault", reward_pool, reward_mint], reward_program) #[account(mut)] reward_vault: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index ef60cc75..9afd04f9 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -50,12 +50,12 @@ pub struct CreateVoter<'info> { pub instructions: UncheckedAccount<'info>, /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) + /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) + /// PDA(["mining", mining owner , reward_pool], reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index ee703e05..84e7c9c6 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -35,12 +35,12 @@ pub struct Deposit<'info> { pub token_program: Program<'info, Token>, /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA("reward_pool", deposit_authority[aka registrar in our case], fill_authority) + /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) + /// PDA(["mining", mining owner , reward_pool], reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 745d76c6..15af2a00 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -33,7 +33,7 @@ pub struct RestakeDeposit<'info> { pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA("mining", mining owner[aka voter_authority in our case], reward_pool) + /// PDA(["mining", mining owner , reward_pool], reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, From 98ed53850f1b1043731ef5eda6b6786e0d6ad66b Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Sun, 16 Jun 2024 18:59:34 +0300 Subject: [PATCH 39/59] remove redundant dependencies --- Cargo.lock | 6 ------ program-states/Cargo.toml | 2 -- programs/voter-stake-registry/Cargo.toml | 4 ---- 3 files changed, 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ec76d36..287b89da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1974,10 +1974,8 @@ version = "0.0.1" dependencies = [ "anchor-lang", "anchor-spl", - "borsh", "bytemuck", "static_assertions", - "thiserror", ] [[package]] @@ -4705,13 +4703,10 @@ dependencies = [ "anchor-lang", "anchor-spl", "base64 0.13.0", - "bincode", "bytemuck", "env_logger", "log", "mplx-staking-states", - "serde", - "solana-logger", "solana-program", "solana-program-test", "solana-sdk", @@ -4719,7 +4714,6 @@ dependencies = [ "spl-governance", "spl-governance-addin-api", "spl-token", - "static_assertions", ] [[package]] diff --git a/program-states/Cargo.toml b/program-states/Cargo.toml index d90b67e3..629449b7 100644 --- a/program-states/Cargo.toml +++ b/program-states/Cargo.toml @@ -14,8 +14,6 @@ crate-type = ["cdylib", "lib"] test-sbf = [] [dependencies] -borsh = "^0.9" -thiserror = "^1.0" anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } anchor-spl = { version = "0.26.0" } static_assertions = "1.1" diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index af188dc4..f484046f 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -40,18 +40,14 @@ spl-governance = { version = "=2.2.1", features = ["no-entrypoint"] } spl-governance-addin-api = "=0.1.1" solana-program = "1.14.10" -static_assertions = "1.1" mplx-staking-states = { path="../../program-states" } [dev-dependencies] solana-sdk = "1.14.10" solana-program-test = "1.14.10" -solana-logger = "1.14.10" spl-token = { version = "^3.0.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] } bytemuck = "^1.7.2" -serde = "^1.0" -bincode = "^1.3.1" log = "0.4.14" env_logger = "0.9.0" base64 = "0.13.0" \ No newline at end of file From 871b12dac800a3434bfe896d4a58c4dc9be7fd5a Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 17 Jun 2024 12:44:14 +0300 Subject: [PATCH 40/59] update restake function accordingly to the changes in the rewards --- .../src/cpi_instructions.rs | 39 +++++---- .../src/instructions/extend_deposit.rs | 78 +++++++++++++----- programs/voter-stake-registry/src/lib.rs | 6 +- .../tests/fixtures/mplx_rewards.so | Bin 298760 -> 300560 bytes 4 files changed, 85 insertions(+), 38 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index bffa8ada..f1b2fb6d 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -31,8 +31,7 @@ pub enum RewardsInstruction { deposit_authority: Pubkey, /// Account can fill the reward vault fill_authority: Pubkey, - /// Account that can distribute money among users after - /// if RewardVault had been filled with rewards + /// Account can distribute rewards for stakers distribution_authority: Pubkey, }, @@ -116,12 +115,22 @@ pub enum RewardsInstruction { /// [R] Mining owner /// [RS] Deposit authority RestakeDeposit { + /// Lockup period before restaking. Actually it's only needed + /// for Flex to AnyPeriod edge case + old_lockup_period: LockupPeriod, /// Requested lockup period for restaking - lockup_period: LockupPeriod, - /// Amount of tokens to be restaked - amount: u64, + new_lockup_period: LockupPeriod, /// Deposit start_ts deposit_start_ts: u64, + /// Amount of tokens to be restaked, this + /// number cannot be decreased. It reflects the number of staked tokens + /// before the restake function call + base_amount: u64, + /// In case user wants to increase it's staked number of tokens, + /// the addition amount might be provided + additional_amount: u64, + /// The wallet who owns the mining account + mining_owner: Pubkey, }, /// Distributes tokens among mining owners @@ -257,35 +266,37 @@ pub fn extend_deposit<'a>( program_id: &Pubkey, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, - reward_mint: &Pubkey, - user: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, - amount: u64, - lockup_period: LockupPeriod, + old_lockup_period: LockupPeriod, + new_lockup_period: LockupPeriod, deposit_start_ts: u64, + base_amount: u64, + additional_amount: u64, + mining_owner: &Pubkey, signers_seeds: &[&[u8]], ) -> ProgramResult { let accounts = vec![ AccountMeta::new(reward_pool.key(), false), AccountMeta::new(mining.key(), false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new_readonly(user.key(), false), AccountMeta::new_readonly(deposit_authority.key(), true), ]; let ix = Instruction::new_with_borsh( *program_id, &RewardsInstruction::RestakeDeposit { - lockup_period, - amount, + old_lockup_period, + new_lockup_period, deposit_start_ts, + base_amount, + additional_amount, + mining_owner: *mining_owner, }, accounts, ); invoke_signed( &ix, - &[reward_pool, mining, user, deposit_authority], + &[reward_pool, mining, deposit_authority], &[signers_seeds], )?; diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 15af2a00..9349d4fa 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; +use anchor_spl::token::{self, Token, TokenAccount, Transfer}; use mplx_staking_states::error::*; use mplx_staking_states::state::*; @@ -28,6 +28,13 @@ pub struct RestakeDeposit<'info> { pub token_program: Program<'info, Token>, + #[account( + mut, + associated_token::authority = voter, + associated_token::mint = deposit_token.mint, + )] + pub vault: Box>, + /// CHECK: Reward Pool PDA will be checked in the rewards contract #[account(mut)] pub reward_pool: UncheckedAccount<'info>, @@ -41,25 +48,54 @@ pub struct RestakeDeposit<'info> { pub rewards_program: UncheckedAccount<'info>, } +impl<'info> RestakeDeposit<'info> { + pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, Transfer<'info>> { + let program = self.token_program.to_account_info(); + let accounts = Transfer { + from: self.deposit_token.to_account_info(), + to: self.vault.to_account_info(), + authority: self.deposit_authority.to_account_info(), + }; + CpiContext::new(program, accounts) + } +} + /// Prolongs the deposit /// -/// The deposit will be restaked with the same lockup period as it was previously. -/// +/// The deposit will be restaked with the same lockup period as it was previously in case it's not expired. +/// If the deposit has expired, it can be restaked with any LockupPeriod. /// The deposit entry must have been initialized with create_deposit_entry. /// /// `deposit_entry_index`: Index of the deposit entry. pub fn restake_deposit( ctx: Context, deposit_entry_index: u8, - lockup_period: LockupPeriod, + new_lockup_period: LockupPeriod, registrar_bump: u8, realm_governing_mint_pubkey: Pubkey, realm_pubkey: Pubkey, + additional_amount: u64, ) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; - let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + let start_ts = d_entry.lockup.start_ts; + let curr_ts = registrar.clock_unix_timestamp(); + let amount = d_entry.amount_deposited_native; + let old_lockup_period = if d_entry.lockup.expired(curr_ts) { + LockupPeriod::Flex + } else { + d_entry.lockup.period + }; + + // different type of deposit is only allowed if + // the current deposit has expired + if old_lockup_period != LockupPeriod::Flex { + require!( + new_lockup_period == d_entry.lockup.period, + VsrError::RestakeDepositIsNotAllowed + ); + } // Get the exchange rate entry associated with this deposit. let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; @@ -69,22 +105,19 @@ pub fn restake_deposit( VsrError::InvalidMint ); - let start_ts = d_entry.lockup.start_ts; - let curr_ts = registrar.clock_unix_timestamp(); - let amount = d_entry.amount_deposited_native; - - if lockup_period != LockupPeriod::Flex { - require!( - lockup_period == d_entry.lockup.period, - VsrError::RestakeDepositIsNotAllowed - ); - } - let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let pool_deposit_authority = &ctx.accounts.registrar; - let reward_mint = &ctx.accounts.deposit_token.mint; let voter = &ctx.accounts.voter; + let base_amount = d_entry.amount_deposited_native; + let mining_owner = &voter.load()?.voter_authority; + + if additional_amount > 0 { + // Deposit tokens into the vault and increase the lockup amount too. + token::transfer(ctx.accounts.transfer_ctx(), amount)?; + d_entry.amount_deposited_native = + d_entry.amount_deposited_native.checked_add(amount).unwrap(); + } let signers_seeds = &[ &realm_pubkey.key().to_bytes(), @@ -97,18 +130,19 @@ pub fn restake_deposit( &REWARD_CONTRACT_ID, reward_pool.to_account_info(), mining.to_account_info(), - reward_mint, - voter.to_account_info(), pool_deposit_authority.to_account_info(), - amount, - lockup_period, + old_lockup_period, + new_lockup_period, start_ts, + base_amount, + additional_amount, + mining_owner, signers_seeds, )?; d_entry.lockup.start_ts = curr_ts; d_entry.lockup.end_ts = curr_ts - .checked_add(lockup_period.to_secs()) + .checked_add(new_lockup_period.to_secs()) .ok_or(VsrError::InvalidTimestampArguments)?; msg!( diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 0087dbb8..6c8307c8 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -188,18 +188,20 @@ pub mod voter_stake_registry { pub fn restake_deposit( ctx: Context, deposit_entry_index: u8, - lockup_period: LockupPeriod, + new_lockup_period: LockupPeriod, registrar_bump: u8, realm_governing_mint_pubkey: Pubkey, realm_pubkey: Pubkey, + additional_amount: u64, ) -> Result<()> { instructions::restake_deposit( ctx, deposit_entry_index, - lockup_period, + new_lockup_period, registrar_bump, realm_governing_mint_pubkey, realm_pubkey, + additional_amount, ) } diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index ee970b8fc2361b3cf0e4e5360e8fee55fbfbf26b..949627439d6130a9b507b03c5108a495d79ed42b 100755 GIT binary patch delta 58032 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 O_S^Gfo<4fP+4(=(Hdf^T delta 57681 zcmcG%3tW}O_BZ}Kv)O`J2ndK<#qA9d1M!AviI|FL1yqV;iI|Gy1=*s=c9)wvrDWy7 zo^q&D1gMBzjB_YA-GFY!sgu#oL#HCT`J+yGs8ePa@xRtw_TJA}=k$Mn@B4hp;X7;1 znwd3g)~uOl_TH6`hF4UFS4M}oR}&4NH1@BiwcoinK6y^fPZow{s)cg04oN@wzwKvE zSJR}KoG)o_?chZ<$pH+hY4;M&BvUCI8-l`nr}1*W)FAp>Bw@R8LQf@~qm3wXR1Yy; zCrbe|#af~WX>%H>)*Phmv+f`fx{=v0YEYt^cWMNpPS}Zn(k78&D)#6y7n$ zEF*q{ae6>1QjaGN4poT5o0ebjkxpjoMrue@c=T9v(GS)e1tELFla%bko16L#%+$&9 zG9xGS6q#LSq=)S!*=0?~!*&o7G}_3FNVH^}vl#A(v0)WTjRD4!5wjJh@EV2bU+bFk zB6D=IcU#lZs69Gav8^dRI*%-BR`{kkeEMtMi({Z^(O@+y-ylCeF)A>4;hNI1^mRtm z(3r4drIwM#{GlVl)m+VC92q&PscPtr`uMx7KE-*!#D<7=*p+eDjakE|>x-JU4xb)C z9x7-$GV&;~6rZ&iS=UB}H7Kna+;rENK+A6#+Mf%3UH@%Ed$`azJuaP`Ofgd9_K@#q z7{}v|lc`&c>hZ_)p~jl{>3T%dgYgpsr17LE4Ssd4aXK+szurigm_^FA7&|7$l9DY( z{lrqTdP`GG(sA-zCDYd$QB%4|&T8ZIl)Ugl#jqb*X%tL7LME@KeVfTBBW2nLdbkmp zoOK@K@|CpfZIU@^P>H5lHSQ2_o;?5~YSqwHa5enZVI)pZB$GE7JEljGrS6UY9`j5u&dDF><4kKq?ss5?)`n-vx9TYOJ zK%Z=^nSY41jWt5j6T>@{hP=7j$V_h`gDpnLf_lBjcyhsPy|U^11;6O-F>H*q$N)Zg z9!TQEpv5xP+Zcm$Ob}1UMzgdvhb7ut@YYzeHI3te`8;*@$VF@50Z>vXxCI79G_|XR)#8@?`R6anrHOTlDbgSaTb0TxaadOeQZ`jN_T$ ziWKU@tBli`N6A&i#*wUKa#^u)GV27Qmh4orsJN*wJ6Vta%HorVUS}G#54~T8B5T&F zro5|m=wwEIQ`gntx9c_;iAxg61DlMzC7Iz#3OBvMXjxK9Zr;?CoKv8a(oIJ7(qyt} zlW}5c7rAHpLGl|zQM}SPmS03h78%JalZmaUsc2=QPNIs8 z>g!XkBMz!)o_NW@LD!QqRzpM$xKN;tDX1tcnV6P^cj*jT5V4$zuU%0%2_m zHK1wi>Rq6QUpJ8B`NoMgspPheMpQv8Dc@+M7NnE=HX1t%5{a?VI9hO&EZb-ltxYCd zHyZV8PkNi!sZ_H$-#A*BPTmbL`U=7ADb&^ZM(hoxWR=yZz9A|+NLe1T^NnLS^pUWQ z#<6w#$OjvYy!DynyA8&X^#$bR4Mx<4bkYTx8@fo%hNjewvHEWb@~klS6rCo{4aUw* z`^dZvMoe)gxo(3|R9rw3HW(+0(@8pHI!@|=P1emzb<(%q$ScVq|6Xq#Eji7-Qp~8! z&zw8gx{}ROEgB|->ZLx+YdW>1h3I>X$F_}=2Ar>0=MHV>P;NE3nn~^P37V?=QfqV1?*soRPOPnM~?u?Aw`21_c@?0G+lPA-fXE z-}@PVA;-Kp}Zpbp9_IY!9QBUrHI=xIo3-UR(S z6u0%Z( z#)T$M)H97+pXnyM{zdbW^fAWnXM-n~D~%qv*xac?g+6+*aq8nz^65+?^|>SD^_j-0 z=c>t5GmSkh>15@_O($APVN|~|GTZJMmI=np<0Ean+2sFxwb9p>PDWgBq(7fdqG0WP z{umlT&I?^+{N+aSi;s@K8Ov&sgs@oK1ERtJTeTf5AMu3@-)IbY$%#tZ`BI)m`<~Fp zA0ReEe|f^-n5$rJ)fS$^0?xWJmRhx|jkzz6B*x`N!OI1f23$Q~jvSTZ7p9>ji@V6& z-tQM1{f=EV!r5Ryi_$RWDZf(J62jpRc&0;Z02}UO&tXeaUqN*ndtON=uU^@7;*}N) z`DmoE@3lPgYQAy$wIY3S)0)4Hkg@nAb==>%+B^b1$_QAD*I(a8UQ994j~^kAr5LA= zSCjivjD2sUlQ}btt~XjpS&C8JafCdZ(vN)&Rv5AG zq?2b>7)9@##MbA$%UWOmZaTQTuit$G$ED>&7kO}DQ}y2=hy#{Woy?Fk-%BTVXBqq6 zOV_Q&hwrWZ-RfL>m9gU=efntQruWC`F~-67#|;?rJwLX<-TU9)UqfD*W~6?QPMY(K zogb9ybDGY6a7gYI&+}f{SDFjMb|^ojPt%sq)+I2G`W2dJ+A8~f!}kK=q&^d<9N>{7gYR$HDNUm&!sI^;1CkN z>!+6ilcy+9-$}@jdGvID@KWx%G$$Cm)FX2}$AWb*sQ1#0kZ)IewqKyX8bBt_rRib7 zb?>FLK1?qqZI^mt!gWyZzh(p1D$idB=`Z%vM|$qE=@SW=W}_{`^dsT`9@?I6XQI@YjoCju$qSt9?SC&_b}WFyHI=r!F^zhTsFzyd5&HXq>mZ6gak78 zhhag|FBbYnj~+R!ca+&VJ-pCo{zg({gz>=c&>`4P>_yH*UoRr{FzCfif6Ou-ZH*X% zP7EJctk_mY0XsbCT7x0AYCoJazG@kM**>1{+R3+@NXN&jearQ_sOD$Hu9=N2Z5@vY z_RUpYB$5qtAcv*4_AC8E9SgP6S(SUV`^oRySR_{;2+)R&N=F@ zWOYUMo2Fs2UAr52T@6f@76?si_yxwVPGDfd@~t;>jC=o&;ZM}aIN0B>ti6iKTV<9TK`~ISWL@nq#szz0^dMux-f?J=r5dPVUjxfV72VywP9_3{`tfUW;Qb!Cd`+Mr@o-KP-nfNI!- znyEZ%RPT!-T}v<`bsuWx11YT9Sp_BFK|8}5oqu2u2E2iF&3~+#!CGDMHN49<6A!YXVwm9IJXJs{`@}UA{yi7VwcbTj+5)ftRdGiIbIhP&rJgNfK`6c zkSm^#F!I5FU|PcTWb&n(q)4= zwBbE_u$=R^&WG?PeyFo9j`cH=h><&x#ISRJO4G$pP(5N4>)*XXFd7Pfjgz7VfBiCi z_Tt>jU8pmRmvc((?gy8I%57&8?tJBT{EW&aoj)6QdScxFk8}rGYWw-zIjgppkDZ+M z-bPlkSa~@8c)UK!_|q2)jQc&I{|_5ec@`V9nKj1WNwIZcKi6R3#W2wufw2|#2KeCA zyfGdZ1a3)7^mG<*IcvqDX5*_8`0teho?qVqr%8x@p% zI?5;67aSyO#!K(o-6#b8PLF8DTG)6ww~6`T;3*cu$EsbkUx~1gEp|rt z@At3yIc(IoUFXiNtZj&76WiA+Py#%6H$)rl?+%-YJ;KJdK^_*$#K!Fsm7dqQ>4tG= z94u1L92x>Ml9znm&P%3(zw~cb5Huep_W#)EI~3!__OAjp;bYa-v(Z~wcPE=Yt73<- zd}rOQ47*|@1in#V^u^Gyx-9~8=kJr}Wce%WUSgf~$$OSzke46?JRvfqz()l(3%t8w zm>UaNwPEL(ne0~&gey2qpgeEKoI+!`X5xTawF#_Y=m#p51)*ZT&jBF4%uKgvx6Y?6 zNqW70fUcROXOVBNrY$jW7SMAw?VAK~cGgFpJtHuze(Y-6W!Ga#0}%WUG~Swp2O33_ z?S5Nc91n|9Z;(PiH_roy8F>L1-1{A5TsapHGET|OY`E&r-k$#}hZuCxZ2eL~Blsys z!d2`Pqxl1ViV<-YKg9?L##0RPMwhYY>5J%9bM$zq+H{toAECo9)j!hjp=U4EC%Svh zn$o7uHEXg{p(pv#y}{}=JSvUsKObWY&AF7%f5%ot5CAS)%0_;THIBM6=G7Y6$P_|pf%Q;tZ18Sto6_&IAO6Su%5aW zonijEmCMtdhj<6j2H)*Zk8jjr_F)6-c?Vms$C@;aCVi|&xX<-s?5i=|MCu)^OU}9= zhF$U9Tov(+EOpj}iu@jt-z@T@MSicyZxi`;k$*KS7hf)AY&`+I zeqhiUqgCVHG?Ki+T8tU@e^El$w)1)?Ox__%Cbl{lzrerH@R8y`C4Lspkf z44sV+iN6D{#CXZZhY;n|OxJ||S-~nkCbd`{-`c(bDs+jUzV(pEhi2hWS=nlfs%!E*?6> zmv`i2^|*@%@rtk%JdVxiG5s~bV@)i;1bH>U3wSk9GLV(a8qE^Tv6XM`*TMj>25sic zKJqg;A~+o8a0asyYhiY=7H#FzEDHj8`ovm&jC(1FV0|6SMK1;AG0a=8Ero1`%Q@ZQ z!1)bbkh*Y!@(_byRy7|R&;^hSLl3clR)Jbd-${xLaU5$BUwvhgl2AuF-xA^-=C^ zPz6%6q!Oe7>|;eV9B8b?S;u8%-NN&o2Y6k$L?uBM_9X?S!`p~;vGNCaAF{a4 zG%k-0iJj6u|PGwR|cBg1nDl zPJ5stJIGClILWl*n%T|c!uSrZlv*2Vzrb+<%ax~C;9x<|7I=Wb zg#r_|$Y>N97J-`u4iq?FU~UVrl^575=xqXXTX5NGICh{(`pv}6u-?HVCzvbV-7Od= zF@kq$Z=TGt^eSRTy5c(pKbowc;8HlMg~vC=XEE%R=S<~Tbro*MC0<6HCXTX;8O}n7 z0*_-e59z!M$5wui6{ipLIbzy6Hcw#uUcN0CaSXG7@r_{+vWOcs!$I*(Jj? z_J>zcNixUYl|}G5_=}*zLs*iZcuRv*I|}$`>vT4 ze2h!@1{2p*J5=p(!0Q5duwWddS=?kahZ7OX8OKuB%qm`f>qwD5O5|7a{4IPe93`Xq z?JH+J@5%ageEOlwwL=kSJ=Y9gW<$J9q**)Z?yHF1#+$j`#%&%4jFkdC&`TDO;doqv zD%-?lCJNXFM$0@jr1*H(&G{U! zTE;!GYaL%+TsGl0?(cVwUYD)MxF;|V3?{%Fp&U_XO$Sp*7FGg}b3b>)*dWYu)^bOT z{Hy~);m-ZsAio|&tS;Y;-3}@v^LG297in1bkzH1rJ$2hPk1LoP0h`9S z8f0;Qqb&t%WAm2#Cumr-oVNhP@FJsE##M^cZZ1%`dN~6K1;<3Nt@1-X*ZthsoCk!8VXT=zr~uP#BF)MG01sA#b<5)jd8*)A zB+BE95<4>JK0#Q(Yc#?ZWB`G&BQ`MWHuwh~*RpP=83zOw4BOHKmT^Urz;f9x6Id+U zZX55ZqgKWN<${3-ffWMF$fi;Uy3+tWhmAl=oqTGF&3;588WtO7Ffo1jRMQij5}jTtqjeId4n96 zp+!gc4#ySV0(Rst%j6g=H4gsrMNUh2zL?Gq zM-Ine+{|B$WDp`E?h0{sxzXAkTd!smIlmnx*}Nb)5Jume#pyDjD7nr|-*PRdi}lvE zsE})3O9zC0#ttR3Zs+wwq;m}y460%c*t(2knD49s!qYh%xn4e|nNnPOL6BHi z9gewPI+If}k7H;slOt9?Z;0mR9nu_I7H~$fdN~{!UO_4Fmv@tSfUDMfiUIuicvW7U z&M+=MtbtqRGb}fuj^Y&zJIeA|>Xy6Rta66oAXTUk0vP7Ir}KPy*F*-NS=_dmPsg5m zH?@0(oQ7^@1I~j&^`J>RkYv!d4U7+PqL#&NLR%lqU8daK%3UzriDQB=FtO8F3-zJy zRWrHsgV$bsSjDJ0Yxva0&NFRe!{Bk&@)_JJLJMatpW!k(k^#1(mhawV4CL6)T?X2( z-{`qD$3gBYaG79QuH~-6yQ7cizDXS=SXfzuod>y(lI5iiWIU-Zq>laExAFK7W|`w4 z-&xBDRvJ7CjDsFK_VWf{0cJi|Q`0@TlCh7Z+t))B>$SQ%?(*bi z8R@AUwcI7jje*OS#`{;_&c>{DHLBmY{>nf+5bAvhPAq)OrgXt_*<=F*imsLGW;PS- zs1@s`txRCKY-R{7m(3J`<+3SG4G(e4an#D?vP$sD<&s+pKN$h%;5#be>m^)z5(VUP z$w$;tE0@boL6^&=aD}yUxvUU$xm=bDESJklfnm8s=Y;R@QH8#vk79x4a#<*_TrP7t zuHnn2ZhTtP z{awi@;D++T-?@DU1U$fZIP9E37k;mJ1jBMa>wk8RoQ@K{E}}s6Gg4mb?W`C?GU8|( zdc`#=jY-<1L*OVI z$8c65OW+(?Y%IjVwQC)&Ua8Q7QfwEbW&BXyci_xUv^B>zevs{2Bs@N}gV_vhhv@@Z z`mpOTK)@WiNs_u+3kxiEC94eOBfZDF&LsiHrlvqDLyPshx$A?iJL6k#i#bOR)ahg0 z!W{9i1PjVHC#&ukzOGIjn#Nc13Z(hg%80zeTQM^`ZWM59On8`hzQsVZ#|AqNa8D!W zjoY|J+_9gx?jIKGJ`5()h&bDD)QV+antu&0`)*JGeCjDrYL)FTDTEa+WXWiF`jJ=a1u%SVgv%@$z6pDa&!pMU>{K z73)5S^C6KPlg7Q?b{!i+ zr&v{_@n==?^4?WOyz}Q7fYfzSVTr^IoX& zRU|p|Gt}sYx9K0@AM6G6NJta*z5`Xl+$|oC3F{uS5B04q<3he+UOw!iymKI5`#Ls@ zwHP{zPph3JVoi2K1!7MB_Ul?%04EsdXEUL6FvHS`NrRI+yjD@6*Fc5Ay5hO!*c(fz zUs?{dh>sRFh|Ohh0lB1ciPC`Nq=Q%N9gMJgPc>xv3G$4N6Br zbz(OZ-^c~Q$RWvh*x2l?fSKAzR!oyya7P{A^k9crx&7#bVRm_!lhM3@^N|AJ10n)|(*;I9D9%D;N4#etP(Kso?J++Cz#e11qh3UV>l(Ofq375T zZzx_bw)E@TymU514FS+!nA^K+SiTI+r zy8S;~E)DzC|8nUjaBa9W#Bt+}@8J8^x+H-+1s3lb{`Z$p-9qpgCfF6<&Qss*Q*4ih zro$9?GlQDEe2Vw~9)S*6wVABpk=9Q5tUS(2+z>-5?{bR!wyyizcn8E?g!Q&n9$w+1 z22K!BgZyFyoCG*gOKe9OeU-RoEB)B|_$P!w9_p6ZS_Q6P4Oj~0&(XV%=wrjHd>J6n zSyy?MKD%8Xle)_GlqkY?S*!@-C}Sn0Ly!}Hz3n;1kAoZ)OpiJAG45WL)9Qp`@Qj0v zAU0aYJF+ta#n~6E)Qn%=9rnL~ih;hrnc{6s_+&Ph{|6?XeP%&}C}E|zue8LihcjpD ztmk&|-vqY$3Ho8QPmu2e6Wo_L5B)xQ7o#0K~*(oQq4Nf_ERPGAqzFTe|0>NTVVF$}takwDQ`FuMzH}}oH8}e`dh*es({>(?C zrqr*qE13?O2RC+*gI$vGfh(Q|l}p{=LQ!|}79p-; zjBzkz!sFT=#8kEv{^orAVA;!u-G|2&+{+sv?z+g?hJ484tm$Rv#<=RC1<8HNV&|cJ zR*;1{$mnlkt~NxvFlPflL03NBVsWrdM+^aqxDZleOA-w2OrXoQRNypb6E52}jt`t+ba|&|8&sSG zl1gk>2!8&SL>*FR1Am)h9!fW;xQ@Ry@y!PQTE%O;LIaGBD!zrQ&>Lpoz_GKYj5X9Z z39`8{OU;N$uxil))=+lhjt+1AqRZ%IPI!e@AH-D&nauj7@f^rz`7BrkerK(n^GU(t ze9Ifmt`-e~i(B~Dd77w?`_+2f@F`th5eR0s&=ATQVT!R?cC!%N(Cf{&LzcWsgNr5N ztT^}w9yL0~*wYUASqqSgn(tw9R>e<3jesp2C}tTYi*lHXR^6Y?$Fz>WkSd8U6_{K1 zD)>wu3i4X_N<^U1Wzz&-2a~rxelf%AgZanloORJGUk*7ooZs&{n8XNPKQWdIbk=2< z3OEm>a6ae})6Auu&ub&%5W=;61*_P#n=c_~Q+8lQqs7##v5^447C|Lde9WLlI0U?9 zkbkh58P7ax^JbJa#$({&?m4{i|C>;Fy4dfa zB-0oJMkPRW!#+BSbhyOD`Y;ZbIP9ETewD>Q2a=JYhY!l`ZkGPHfcDoSq;OvO7%PAd;akNapo>x*!f(a;{tn{gKk?Jv(m}{?6H0>$P@%$6*gj*` z$`9$mjblFXZC{D}D$x(VRdLzAEH-57p6u+?woqbHCv-hWSQ`wC~goi#*$ z_7wZQ`PU}N46YwJNm^^^mmBrq|8Qh5#Vsm;>4cACGp|Wj;Mihk`SOLccR3SBl`J>|UCEt=L%hh4 z!Jd3oBey^f$8aW3(p~O#+_gJOhw+L;kme}iQ5YWbg4=-m5`3k^f=nagEYojj1o6G2U1)Y!m!BCdE?(b%U=5X?@1s-YQ5IDA? zc+kMTEb-obm)DfN$K=VJz7`TY{4atx_(&QodYJK>H%0s|av-0>!FP$Mi8w4LG~p-_ zFR!G=oZ`g}G?ZyTObthgc=-iv`*?E8~9nH^<5u9|C z(ta=mMy<;m8(CtFLo-Q&eAF&>Lmo&w5AcS{V_N5a-jS@izyyzL8GmWNex~_8#5SNM zVFW2>)T-UwGs+LZ{P*hy|M}&(qgLJ_t74jz6T%@r^3FO8^@ES#XZnu)qJ?sJ#kF^J zLO2eJ7e{g;uop)-)s=~GP+Uz}R>>P6`D?`0l;8?}2@O@s>?a%kfX8uA+~|M;YyuqM znvoy!kQe+N2gOwsFJAyS3ef0c{1<%}ABv?g)@-W{DL&qPpnTopok7wys2457bvtiE zxh=x0HgR(kCJJk4gLut?y@BVvt0xD=)e~qi>u@CMpD`+TPsB-H7N3Y9=zn>&y*#!vz4Q3lI^dC$k* zwY!t`*eP!;i<^<&Wri-b2W+8bgbNlQm8Fdptg1?Sgk$e8KPr3p_3t=b*#EiSjsH zp4++>@pv3p8v4OxeavN6?HeX)WgUN+yeeLZ^xjbtBE<_s*CHWNoB+c|Vt5naB>1vB z^of^#!o>Z~7XE4j;d|i5xUJf|Tu`0pYW#1+Mc(i0XcVfD`^yG~{i8p&?^}ff!M8Ni zPGXeTt~8j{GY51mioEeDugJAqei;+D58CU}BXU`l2YHohpeZj5;ONGG zi%oe_G(|?fHLPaWb{^rv#~nmVv_9jP_oMX&zMW-3ty;$)M4e)_!#b9+7d;1lR~`Op z^Oqk8j>;4?|AGCg2h3x?ispZZouTLGFHe(UwDMj(LZ9zBc&|Q~$S+|XseY$zuJVOG z4*EUSR|NmC0A@XSg3i%2uS(Ae{-+POROx$yJAF9kKD{9LRUbZfpPm=|oDV175Bv}N z@Ui>#oxxQ;obxCBNbt>G>X`h$A1UPEtsP~W7_qhCL$KNOtg z<)-_p^<;17;{1Lj1HZ7hSn1p!J=TZx)Axq_z9v0}Ox9@36MEDryWfv+U^CMEFxtcC(`n8V`fU9+ zdL)n}lZtqH{0R`#In|T+q~1r)`+1$S6FsTTdS9gYO6V~4+muQ@YmV!W>*R{pJW(Bb zpRTvl$hY*5$fNIgPQL}1WDoG1en)>NK!4Tq+I#wl0DTVq=ZE?YKh-~y12#&v#wumNPEMkwpXo*V zVEXxI`Z@gq&*z`(gLQobJ$q93ezEX*Hm!b7^Ud_Z|Iv3@#Gu&4ztP*ifDYj_vp*k)aVl4t@9WuW z`IO(^dCs_GzTLeGRx2!sUjXOuGV^*JwSQ*JtD-pez~sZns;%Tg>IzvJ2+MI5ITaw^zk%8223xd4f1^9U)GrykniX|HQ}Ds6aD}&OSFn7ve5tGOP`~d3!nS{$ zfO|8lN+P^i7?%x=+tMePf z_iP|Pz&qy4)VbjBYngwIbm^(=fedlxd4(U&4(UYI+ zqiE7MdT?PGE8Du6B`_w%e5`Ng*cG44aKhc94xVTU4n7-4hzK#EY!fTslwa>YD4*fh zNnFlA2fhDGJuaz$bBP({3g=wTT5iG4L5;sws{gg3R6c$Gq&{-sH{<+gd^jEOKl+k^ zR*hS!{}i&TQ)rx@I-RO-h&1^ISUlkQE#Kh#MnjCZ;U|K2u>^cKpM&T5iUv*(4AP*T z;48fP&7Q~Xy*A+^4->ci~Pnk5r$H=O^!X%&9&r@*3 zffXU(z|k zdp&M@Lu4EIiHiRhB{%x%(pi_y_*)I88@3is#J1IP90+2V?r-6kHeYYKAbyp?ffA(_ zocsA`VlStCM{|1hG34+$deP_lydbN_y(mrkUSF+mr1yTWkHeU$<$FEHw)_(KI`g%> z8rOEth<+aEiKq1)!Ha+{@X^x{ZlH zK3w_>M31-ma2I0y>KehHeimAGgAccy{SWb9yAn>c(}X^K1xdSz?&;I3!;ek?lU~Uc zgxa0)iYN7)9u=rRN0;;?6Gy4xVKyAxx`Kn)_~Hy#;y`^r-55yH)UeKorx4c7rMCx> zYr|BL0iN%J$gY6%@*VW93&}&|f;VVl2uVJVyx6lfggmaFmp{xiEtHHPB<~o_2`9%% z+<5p=z@eTwgUF|WBrcAFq2!L={*}Kv&-cU0N5B6k1Xt3tBgi)KyH;W9?^<0!AGDK0 z`VcyQBw4D5&<977+2R+Z!qlG_vs3FRvQ6)xH;p0-^jADDk0MmpkJ9C10N&v_I0hhT z8%(OeSAK2x9GOHQMCM<-eBQ5Bo*ak2 z2so5}Glh&so?rRR%J)38rjl0qqm^Omk5)cF$0n18It%~XM(6gCzJNJRHpqK|F+ZL80tk!3L7bozZ6nNEJ1;7N3n5U6VcP2En4$X63QN4ArF zgnTyClYSFehMrDe-$AD9v#Hfhvcl9~%bQ8pxXC7ctmj=!aN^xeHqisOz#I6#-b~&n zU&njuZz1q|B>eaA!qnfxYo{;YO2+->@8Pu!$6b-0`T~`?Ecf z6~v9d6GD&I!s)|bZYPtyzq~{B-DHBELlbtBAM_9C*gJsp5qj_rFvSPx@H@#kLdVlQ zgT(50(~SW2yXd`ll8O2~o|o?=)2$Zx{TF&}FPX2er1S4459u$`&+mq^PtfHCFh5Rj z!{lN5m;odBgvWXh`N-nk+J&jVjIn}VSVjIt&cxHcDo}C78?@*?lButz&)r9sPKr}D zf~z6m$Hf6wWVYGpR@Rh!&;0vImhP=CT&?a4_%)J0k)0%M0!@Dah7#)QdVu7S?*@BP zAA}xzOP<%zKhnFZ$u3epfkr(90-#{^L(tG_&({w@cYnLw3_7cp{E;+Hz>&zqa*1_N z?s9rl9eK!G3^yI*%n+KspX~5vp0^MFbO=4QpA`5Q)M)n2AvE(KbfcSYJxJD)7vG^> z2TA(G;$K9}2$K=_2*V+@YVWYc*2DgV-A%uf>@Tam1OCP0FFcc_Ot;9>8rO>&}uq~&XjbWvt zA}(OPSHL5`O6L(eHt@JoK>};QDsTCp6*}+03ZR1)*7P5s^@Ben`-eiSG1VEiYBw_h zXcI`cGSfr)h2n@IgwGD($0Gu(#x;ig9OGO-{sCGbOD|w6EXv0-5UaM82j^ITXW+m5 zSjkH7eV9xP!*cT1k&!KkzWXrzk~y58CO$%T$Z%rhe+wrP>F19?RMa_+?s$|G=)>se zkHR|gAYJnqdBt-1FBZ?lBjj@(j_V>D$wWL>%^Pn?r*}4zY+(AZkxX-6$fiZ4p2%7# zuZrRCdrY|iJIf!BMZBzDFkrG^2lJQ|<5B}){(C_3ve zWblmVmFyT+oWHvFukIs-Z-(*p?SsSYO70-K{Btrm%&laPrXT(VR;uOa=<#bUd1S*m zns}Y10HV!=Ct$%R)^jv7%M#_e9hYJcH^_mQ2G_-+zNk0yiXeR4fMJ9VFPf*PqaGT5)-fxnnWw#Cl|rS(fT2o zZMzsU?&VE1*`G6n=6y(Zz~1V^4btLKA7}CnTL* z4x0T0RCdikdg2pOO0pAZ=BK2Z+#W(teG2)F3AEp5B$2!mLX$ruIpo5D_zSHhDS_61 z2AX(0gdYElq}z6d_%wlWUL+@=2g;AQvs7Fws+elGF$4x@Z0=vdK!W{(+`ufIW>}TEF z_cJLVj)AoAXBg6y_i6esFl6O0#J`X|`dp9oEb-|2x1Q(v$T+LNZu=~jlabPzT{E@s>`#l0F!38KK`vpNqFVPAZaU(F98@RAC%X zi6vD@o|Xxg6FSLH!r!)+*~LkoQwf$G@G&`I6^%)B=R`{kv4`MKBa`+d+A`4szs>qD z?rbhfvJ~nT_{~EPon(2v9~|neNwGXc4xd8PiJ@a=Skj0#i5ASTr0S1*4$iQ^k6Z6J z#ipOW-E+momWjH)*mKh*mR(j`Z?I1(k>pu657C8mhSHczEvY0coaS9>NhYs{(|wp; z7e)q9Mciuv~M1CJtdsR%!jlil%`^u9Y*soyN zxd<@OVWD8rY|C0(W2mo^NWGPh2KdsX z0W|MQkh(X5R$mD%`z3;&xDtv7M9_X$0nUu1saFAh8>|M?XMhjW)ChVU)4zZ@^L9_tV{V+_B+M50PY1O89`}sfwZ9%@^ zNZN&IR3z=gGz8j{1L@ERnu_VQ(0)t{BIyoHKLcyWw096~!St;N+J$LX1nt8#3(7Br zbS0Eu3g%N5N$Zz_S*1*-ry#X`_~oy!u?>-)%v{S3i=ItizZP8H3Yu`8Wi6ThB~5&S z%%mOHSt3Z`Wag_HBAE*-o9szlZaJpwUr_4`3&k3$SAgODBa(*XTcW}%(GbLg=F>RY zlbmlkWg(qhOk5PrS#3E%Zi=FbYhc7HqG-_?7>SxZTD=B*To1@A0Go_}jXB8|nP(<@ zAhXL7UOd20Iv8p30J>+LCCcWXKkznQeKeew0lnosdYYhvsNw;hQ|m0p$^Q$%iA9!r zIM$s;Pi(TZke8z9zG9fjK~w0-VoRPS{|yWNa`O?hM#>a=qy!8yF3Q*u5ko&KvE;(vG<6Hm;Pj(q3&f&!Y@CQBRf)<^OqDO87(O*Q-V>eokkk6v%oJFaPv2vfkO5@c5E}9n8+#i26pR6kYyDGTLQw4VQy{xyD$RKs#u@UDJPpGD`Ec9^ zbv4mOTV5FSB11$)85^UIcpCG`dHmEib}weruxzFIiG;Cx-ZDCN3Wr;jlwi zBAgMv1f3WiLr);z6T{4Wc$|fN0mD6sFM}r{5rc6BOC;@6JYDS&z+gtqt2`s~RZBX{ zU~Al?uUVpz?bvG~H{^BBmioFS*~fP0an4qMT;z7W!P!DOEc<+H&%SBt25GeVEin72 z!8Gq}P${JQ-Ucl|8v726IHY^tu^dBP=DZ93mfSOfmj2zcFZ`YnzHZ?L7V={{K~S`v zc6CC5hH13^J*c*Fn&-rOmg89y+J3fZtAP<8e2LFb7C>KcT80Nu^1<`rH^v8yy)g7* z&?-EkGeBYR83D8!Fy!#<^Nf&?LKxpDcKz|G9t3}=e z;EklZ-y$>*=@zMKK`d^_i z|BwTdpO~Pu_eY@PnI=BiUX|V-4!``$?@{T0g7SX-ZBglK#PJr&uh!`)q*vw$Ku)+jFn~phf~@Teq}4#5*#`Oih>26a15DYbf&&TjTI&gga^YKEMl=BR1 zsGyv?6Uz1HtnA;90Rx-&`2;W?Aj;<%z|xC+1ethjiO+wNa~R5nb5{1RumFBm{d)wQ z1_AsGl1uq-6^6ixp?`(g;mI&${vE)N9`KV>O#^xt(Bu8-HvwJB$hpv5|D6}om?2u(SO1goKAP-;g-~;p>Dt!si?TpIkM}ThWeuvN%GXf$< z*YK}phJrqUCxKZO=mf@(#s@?WD1Hq*13cagP00vOd0leQ)Tsf3gI|?UNXYn`Ipf0O zcf1*Jmi|prK*R+?W)D!rx|j9=XG(&G#<8U^IRAZ~1)YtV2FhIpC!=zW`WBifEzf@g zicOyoO7noCfM~hS);i_pC_O=Iw2uVy{9OUCu11YqfJ($uB zGXjDx%w5E(4L}u5?GyznAq}M?;{zgsr*bC9VH09jhsaRduoKQ!q$04Qy?|w%i1V+M zwOm?w9hVupoTo`Z!soA%J_AMk7+`#niO)!|eQB_{SJJ#00a3w~qTGd1bO&JfV%U~R z?}24pfcca`q-pyAOItwP0N8&ajRWleya3q03o1?%?Z+~}N7v5?=zm#Up6C?3F0}he zdrPTYt?VG@K^Xk!2UM17;r2~5>FdUz90!8-467Ae)vDjgA0-6P{6+jYJq(~ zmr8~=rfkQD^8Y-9CSC&m?ax3TD=Ng9KSBI`)d-r0^s8XYB!h?RfF|qOK7#H6I)SwQ zl7RTIjufsJX`PMq<4XcYOa}e4NA{^EnP*6y$R%7(jNa*V@~nWIk>!WEC3TAC;2?y9 zX4|VpzUT0)0JqhhyNEML9TqFNJj+aPS8%t2lQ6h~N6O1ka6^uX?$#=onKHBn1)sUr zOb5o{tkq%i9Fx#EI+ss|8;7SEIE4Wj^+8t(jaSCoQOmw%_ZqCrk zs!R-8r-F+gG}9{-+@Rpjhs^x;8h=brF9-;Cr`7uBfhpISam@iU?s-_jN6fhKaWn2v zaK;lRx?5{7XK39=O$^!@1vh)l^u}g0?taFMlb$u>%IEy?6qq}v3{C5K-k(9!vR^dg za=LI~zzBEyF>`jef_oL5)NbZ4S8&;JGrdy5jb?nQ*7lCMK*k9(u2OL0-_7)HI%E;_ zBjtT_cD91^72N)TnZN22Gj3FHhl1O_F!8&!-Y-oVTJ}H9IQo zxI@9&@n(9Xf_pA9)3pRMZc}h`qK}UCYZa4y8HgJcT$ya9=T0}{G6nZsY^G;iV#ejE zUd-y(s(cw(zm|*pN_eEov(wDDY_1u%D>!bxnVzBG@^ne(^=suBGK1BtwJSJdp_yK( z;BtqV-mc){&1QP@7J)_mTE6&s9@*oDZRP?UJIpxxW;4$JgBcgzYQ}MQnQ@Q!U?2OH zMAm1+%n+*J3I+ElxcnY-`CbL*-mB0#p5Pth@_lB86l%uBRdi-1#3*O(Gv|liZ^m{7 zM;|fMQxu%9;C2OPJZ>uQ*7D66TCsxL6nsX(xlJa4TH#;KxLm>Z4u3irO!oW!8E}nP z!D%0v>A4CnR56~Bz$0z1T)~z8cnL7ZeQGX{rr-_*_b52@q`7?DNgAIWkm%0nG3WOv zSo^|EuT*e@g1i4|=0Bt2f0^iRt+3aWq4g>_`x`U8UBUU^n(1l(Hsk#7%sA<^Kc+jc zgorHq2md_S?kf0=~1wqnCW)7 zCg{)Kj2|+EN6N1W^2cD``N94fz=*Hu{R-F>9BMPu;}qPe;C2N^5A!RJ4mrs`18|w$ zj{$I1oEfLZn{np^Gww++V-4;f;UhJW?T6WdQ0SL|j44S<0R@LnGSe%P&A3^?-3m^b z?&W9oYlXfHtY52CaPFnv0*v0h(u~h2IPQ8gJx#&cUd-#)%Dfp&zSf}Nb_I7UIBu1> z!dwM+I?VLA%>s-1wKSQ*>(?rs<^r8N%(z#oPCd(znEjU*D5>iH8b=mxO|^NS8y~n(`%~CxLLv3_aPnhFQ-K#%kbe& z1!w=sEU-|)K@XVe844~`aPEV^kK->JP>dOVSQAHak{+$#N(DD4xbh*hydDMHYY6{z zjjSLSGW;7*q2Oi(_b52J)?B_!!Bq-wt|i?5Wdk}DhBFF|t}|DVqTpNwmnpbP!Oe9e z%mOBe!z^igJv8}--BZAN+r8P!R3d{#j3=atW-z0g3BK^^EWHF z*No}5YXZXD*^ihDl_|JJ!R-p}RdDp9W_h7cnsMQCChXSA%^6y=g4 zI0ct0xJto|COi)eTD*Og0;3gtrbB7Kn`Uf(liIF@c(M3xbAE$@I~06I!ExfHu53rP zf*Vel<-?~#{HG%vUYRqrHU$TLU@D;564P*$OT zmnpbP!EFi-{m4%qx{5ge48Vm7E?01ef_oI4`;A`(Ag@@#m42AHss_Id9GngX_bNE( zTXO|*3QkjSzJkkqn7hg z?F#Nuu=btOAO)vLEb7;CWd^TbD^_r&f*TavuHbG3XMAsNP^G}$P^nE}NIGjSkfGqD zLHu>Boc9?D&Q)-Oibt{XzEG)`XLtjL<}v09x)mHWR-wn5ap*WRPEl}k9MVDmvIiY3 z!-snn95mi6ushz28{y^yK2lz`xYQ|ep@JI_O4ZiONG60^Vz1(zwfLBX904w_|_ zmjsyEpKL&`!cd{$W(D^sI6Bo_L56}06uU+(Rvfd+-4UBNvH)@GY4v@1AG!TAa< zpY1jas8Sf372K)dGYSq(Gs{a-aJGVr-AYEKf*Tdwq2OKxht5$Npy0Gi&GfKBC8M0q zSP8*lx00PP*Ice#!5#E46kBPZXJ$)NaK3^&6dW|)T;8tWd^+QLc>9=5pMu0)xWLTa zpy0HHW_r1T8x>qBE(Xc@(4gQRGajYcFEdw=t>6lJ&1z_8kCGjIxw%+|g3A=#px{mg z2VG&7mtw|nCTGsjsubL;;7$eiDmXOLTw#)evrQQ0c(Ex1&O#L2sNfC-_bNCj%iMrC z1!wqU^f8708GtJk+@Rog1@|a8C|hZOf;0T^G<0!=ei?`>6x^%e6gqwlm`;+oF(*wj zU%?d$?#VT`EB6{RE>>{4f{T}%_}yBKIYTSUGcjn*3hq&G+6ps&rGk4D+`h`6e+-1@ zrVI$r6&$ph&Mbs?8Y!#&3&N_p){LVSoTT6k1?MZcOu_aWO!DDUmpMaAT4!R=Diz$W z;EeTV{;~~bd`7{c8~y2EajkhwtaU(C2z z!MzGjYcliKDEN$mlb-P7$MvJaKLbtuNk0a_J@~mec;wKAK4r!k3NBM{gMziEedYV1 z;L2y|(;LASI-m6sScT7-aifBJ6&%!J=C><2U%_Q&e5rQkd9#4H7tFZuMKi8aa5p_% z1WqFNn3=6Y!ELXY>Aeb$`>UCrtKi;O&GeqvOn8dc@HbP2)~(>;*Uj|K*XgoNFaT{G z=KS0@&A3p(ac`OFy$ZI!tX!7ghrRk+9^b7^(E4Wa>GK!OaS8Q*ft0#>v?0pMk0pUmGbk5USv41@~%{QV9*Zbw~TwdXTjQ3ifV36_pX3hRV^EF6U1$` zV#FbcyXxYa{c5H}MZVa#r?%JZ`r#}U&|+~O*aa>Ddush%dttE&;sBWIbn!5ex^21* z!7TNh+>kV|qh`+93;LzbvTia>&<1M#tUZ6c2jU@cYC#T?11x@eoXuO1q z+B#=19BzTQ0~`T!A{)CIsk!s|JyljM>n7tY%DLs$5u+`t72O;!fwm0n0XKks;I_JU zUT>$q7~1>81JGIr)cy1J%VxJP=s8r>kqg?msn#`RI#l3=ml3J!MmzxVNZr<*4m;1w zWfg!+z-3@h&DHd)Mjym2;12MnI-+FO7NMCr+I~4ZM{fiW)&s+g^CNbyr zal*FQfda*Pa|fS7t(jXCO3mD$Na>g<5}G-nNN46U1uipNznKN;{bxauBAJ;h6tT>l zrHEwa5IFsZ+@UH3=d!)`&`v#IGM@LCW^?`7pg^&(%teZAW$pvJPfCYbiZo^W2Czqw zrmS}sDMXD0BMK#D4k%KTIekhF=m9r@L*Nl``gICX;{l2k*~wf5F1{@%FkF>fUX$!! zkQ`9pC;Ly;>(ZbMT>4n*y$#7eutjl`JV2M?BAFW$*2wJFKa&pgpGyvbixlO?4)VHM z;`E2g1#SWRx1@bNyCnr};MTWN-vuuGDE0mw$szDS@{3mbuIx|*_JCXWqH(Y(Gd)DkxopQJ9I+A?0hc=YSYmO>r;``SLx`3^!gJ)M;UR4JF-Io`I=sc{xSCE zf5oY93?>$07 z7#Mw%5<6I5{6y-zpGq!$DcPl{c<#TM+<)rhYv}!)T=NIa44+Qe94K}lPxwKjpSq`; z*FX8)iGD`2yFXzwX!hBJcWEx(W!Ak-F{1WQ>e3ba=1lwFiNYEe^HRq)>_>N*0-5GV zx9Y78`(XSXLh-i{sT&*i!t9XVo(vDF`y2M5S*`=qKBV?t)jV7>d#Nw?k~*U~E+@lf WWjgA{ReNcs@zKQ0RjXI+`Tqg-9swEv From 62a1fe957ace7132c1e70a89f5a9066338827395 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 17 Jun 2024 13:24:40 +0300 Subject: [PATCH 41/59] add tests for the restake function --- .../src/instructions/extend_deposit.rs | 5 +- .../tests/program_test/addin.rs | 54 +++++ .../tests/test_extend_deposit.rs | 188 ++++++++++++++++++ 3 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 programs/voter-stake-registry/tests/test_extend_deposit.rs diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 9349d4fa..ccbbca3c 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -78,6 +78,7 @@ pub fn restake_deposit( ) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; + let mining_owner = voter.voter_authority; let d_entry = voter.active_deposit_mut(deposit_entry_index)?; let start_ts = d_entry.lockup.start_ts; let curr_ts = registrar.clock_unix_timestamp(); @@ -108,9 +109,7 @@ pub fn restake_deposit( let reward_pool = &ctx.accounts.reward_pool; let mining = &ctx.accounts.deposit_mining; let pool_deposit_authority = &ctx.accounts.registrar; - let voter = &ctx.accounts.voter; let base_amount = d_entry.amount_deposited_native; - let mining_owner = &voter.load()?.voter_authority; if additional_amount > 0 { // Deposit tokens into the vault and increase the lockup amount too. @@ -136,7 +135,7 @@ pub fn restake_deposit( start_ts, base_amount, additional_amount, - mining_owner, + &mining_owner, signers_seeds, )?; diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 70268cdd..fb3b8750 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -394,6 +394,60 @@ impl AddinCookie { .await } + #[allow(clippy::too_many_arguments)] + pub async fn extend_deposit( + &self, + registrar: &RegistrarCookie, + voter: &VoterCookie, + voting_mint: &VotingMintConfigCookie, + deposit_authority: &Keypair, + token_address: Pubkey, + deposit_entry_index: u8, + new_lockup_period: LockupPeriod, + additional_amount: u64, + reward_pool: &Pubkey, + deposit_mining: &Pubkey, + rewards_program: &Pubkey, + ) -> std::result::Result<(), BanksClientError> { + let vault = voter.vault_address(voting_mint); + + let data = anchor_lang::InstructionData::data( + &voter_stake_registry::instruction::RestakeDeposit { + deposit_entry_index, + new_lockup_period, + registrar_bump: registrar.registrar_bump, + realm_governing_mint_pubkey: registrar.realm_governing_token_mint_pubkey, + realm_pubkey: registrar.realm_pubkey, + additional_amount, + }, + ); + + let accounts = anchor_lang::ToAccountMetas::to_account_metas( + &voter_stake_registry::accounts::RestakeDeposit { + registrar: registrar.address, + voter: voter.address, + vault, + deposit_token: token_address, + deposit_authority: deposit_authority.pubkey(), + token_program: spl_token::id(), + reward_pool: *reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, + }, + None, + ); + + let instructions = vec![Instruction { + program_id: self.program_id, + accounts, + data, + }]; + + self.solana + .process_transaction(&instructions, Some(&[deposit_authority])) + .await + } + pub async fn unlock_tokens( &self, registrar: &RegistrarCookie, diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs new file mode 100644 index 00000000..05fe8605 --- /dev/null +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -0,0 +1,188 @@ +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{ + clock::SECONDS_PER_DAY, pubkey::Pubkey, signature::Keypair, signer::Signer, + transport::TransportError, +}; + +mod program_test; + +struct Balances { + token: u64, + vault: u64, + deposit: u64, + voter_weight: u64, +} + +async fn balances( + context: &TestContext, + registrar: &RegistrarCookie, + address: Pubkey, + voter: &VoterCookie, + voting_mint: &VotingMintConfigCookie, + deposit_id: u8, +) -> Balances { + // Advance slots to avoid caching of the UpdateVoterWeightRecord call + // TODO: Is this something that could be an issue on a live node? + context.solana.advance_clock_by_slots(2).await; + + let token = context.solana.token_account_balance(address).await; + let vault = voting_mint.vault_balance(&context.solana, voter).await; + let deposit = voter.deposit_amount(&context.solana, deposit_id).await; + let vwr = context + .addin + .update_voter_weight_record(registrar, voter) + .await + .unwrap(); + Balances { + token, + vault, + deposit, + voter_weight: vwr.voter_weight, + } +} + +#[tokio::test] +async fn restake_from_flex() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + advance_clock_by_ts( + &mut context.solana.context.borrow_mut(), + (SECONDS_PER_DAY * 365) as i64, + ) + .await; + + context + .addin + .extend_deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + LockupPeriod::OneYear, + 0, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + + Ok(()) +} From e75bf0e425e0e3ca7cf627e15b1f64b70a864da2 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 17 Jun 2024 14:37:30 +0300 Subject: [PATCH 42/59] Fix bug with restake && add tests for restake --- .../src/cpi_instructions.rs | 6 +- .../src/instructions/extend_deposit.rs | 11 +- .../tests/test_extend_deposit.rs | 639 +++++++++++++++++- 3 files changed, 611 insertions(+), 45 deletions(-) diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index f1b2fb6d..8dcfe893 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -263,7 +263,7 @@ pub fn deposit_mining<'a>( /// Restake deposit #[allow(clippy::too_many_arguments)] pub fn extend_deposit<'a>( - program_id: &Pubkey, + program_id: AccountInfo<'a>, reward_pool: AccountInfo<'a>, mining: AccountInfo<'a>, deposit_authority: AccountInfo<'a>, @@ -282,7 +282,7 @@ pub fn extend_deposit<'a>( ]; let ix = Instruction::new_with_borsh( - *program_id, + program_id.key(), &RewardsInstruction::RestakeDeposit { old_lockup_period, new_lockup_period, @@ -296,7 +296,7 @@ pub fn extend_deposit<'a>( invoke_signed( &ix, - &[reward_pool, mining, deposit_authority], + &[reward_pool, mining, deposit_authority, program_id], &[signers_seeds], )?; diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index ccbbca3c..b1bdb81c 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -4,7 +4,6 @@ use mplx_staking_states::error::*; use mplx_staking_states::state::*; use crate::cpi_instructions::extend_deposit; -use crate::cpi_instructions::REWARD_CONTRACT_ID; #[derive(Accounts)] pub struct RestakeDeposit<'info> { @@ -113,9 +112,11 @@ pub fn restake_deposit( if additional_amount > 0 { // Deposit tokens into the vault and increase the lockup amount too. - token::transfer(ctx.accounts.transfer_ctx(), amount)?; - d_entry.amount_deposited_native = - d_entry.amount_deposited_native.checked_add(amount).unwrap(); + token::transfer(ctx.accounts.transfer_ctx(), additional_amount)?; + d_entry.amount_deposited_native = d_entry + .amount_deposited_native + .checked_add(additional_amount) + .unwrap(); } let signers_seeds = &[ @@ -126,7 +127,7 @@ pub fn restake_deposit( ]; extend_deposit( - &REWARD_CONTRACT_ID, + ctx.accounts.rewards_program.to_account_info(), reward_pool.to_account_info(), mining.to_account_info(), pool_deposit_authority.to_account_info(), diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 05fe8605..ee8f81be 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -3,47 +3,11 @@ use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{ - clock::SECONDS_PER_DAY, pubkey::Pubkey, signature::Keypair, signer::Signer, - transport::TransportError, + clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, }; mod program_test; -struct Balances { - token: u64, - vault: u64, - deposit: u64, - voter_weight: u64, -} - -async fn balances( - context: &TestContext, - registrar: &RegistrarCookie, - address: Pubkey, - voter: &VoterCookie, - voting_mint: &VotingMintConfigCookie, - deposit_id: u8, -) -> Balances { - // Advance slots to avoid caching of the UpdateVoterWeightRecord call - // TODO: Is this something that could be an issue on a live node? - context.solana.advance_clock_by_slots(2).await; - - let token = context.solana.token_account_balance(address).await; - let vault = voting_mint.vault_balance(&context.solana, voter).await; - let deposit = voter.deposit_amount(&context.solana, deposit_id).await; - let vwr = context - .addin - .update_voter_weight_record(registrar, voter) - .await - .unwrap(); - Balances { - token, - vault, - deposit, - voter_weight: vwr.voter_weight, - } -} - #[tokio::test] async fn restake_from_flex() -> Result<(), TransportError> { let context = TestContext::new().await; @@ -184,5 +148,606 @@ async fn restake_from_flex() -> Result<(), TransportError> { ) .await?; + let vault_balance = mngo_voting_mint + .vault_balance(&context.solana, &voter) + .await; + let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + + assert_eq!(vault_balance, 10000); + assert_eq!(deposit_amount, 10000); + + Ok(()) +} + +#[tokio::test] +async fn restake_from_three_months_deposit() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::ThreeMonths, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + advance_clock_by_ts( + &mut context.solana.context.borrow_mut(), + (SECONDS_PER_DAY * 30) as i64, + ) + .await; + + context + .addin + .extend_deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + LockupPeriod::ThreeMonths, + 0, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + + let vault_balance = mngo_voting_mint + .vault_balance(&context.solana, &voter) + .await; + let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + + assert_eq!(vault_balance, 10000); + assert_eq!(deposit_amount, 10000); + + Ok(()) +} + +#[tokio::test] +async fn restake_from_three_months_deposit_with_top_up() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::ThreeMonths, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + advance_clock_by_ts( + &mut context.solana.context.borrow_mut(), + (SECONDS_PER_DAY * 30) as i64, + ) + .await; + + context + .addin + .extend_deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + LockupPeriod::ThreeMonths, + 500, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + + let vault_balance = mngo_voting_mint + .vault_balance(&context.solana, &voter) + .await; + let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + + assert_eq!(vault_balance, 10500); + assert_eq!(deposit_amount, 10500); + + Ok(()) +} + +#[tokio::test] +async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::ThreeMonths, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + advance_clock_by_ts( + &mut context.solana.context.borrow_mut(), + (SECONDS_PER_DAY * 100) as i64, + ) + .await; + + context + .addin + .extend_deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + LockupPeriod::ThreeMonths, + 500, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + + let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + + assert_eq!(deposit_amount, 10500); + + Ok(()) +} + +#[tokio::test] +async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::Constant, + None, + LockupPeriod::ThreeMonths, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await?; + advance_clock_by_ts( + &mut context.solana.context.borrow_mut(), + (SECONDS_PER_DAY * 10) as i64, + ) + .await; + + context + .addin + .extend_deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + LockupPeriod::OneYear, + 500, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await + .expect_err("Impossible to restake from existing deposit (not flex) to another period"); + Ok(()) } From 181e8e70fca376ae078907d47d6ee7a0c51e3324 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Mon, 17 Jun 2024 13:53:11 +0300 Subject: [PATCH 43/59] Add test option to the contract --- Cargo.toml | 3 +++ idl/voter_stake_registry.ts | 6 ++++++ program-states/src/state/lockup.rs | 2 ++ .../src/instructions/unlock_tokens.rs | 13 ++++++++++--- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f6158355..66bbf035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,6 @@ members = [ # Drop everything below this line before running "anchor publish": # Otherwise the build will fail since Anchor uploads only parts of the source tree. ] + +[profile.release] +overflow-checks = true diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 4a39b631..8f878df4 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -1376,6 +1376,9 @@ export type VoterStakeRegistry = { }, { "name": "Flex" + }, + { + "name": "Test" } ] } @@ -3048,6 +3051,9 @@ export const IDL: VoterStakeRegistry = { }, { "name": "Flex" + }, + { + "name": "Test" } ] } diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index 82d48ade..216a9c7f 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -154,6 +154,7 @@ pub enum LockupPeriod { SixMonths, OneYear, Flex, + Test, } impl Default for LockupPeriod { @@ -170,6 +171,7 @@ impl LockupPeriod { LockupPeriod::OneYear => SECS_PER_MONTH * 12, LockupPeriod::Flex => SECS_PER_DAY * 5, LockupPeriod::None => 0, + LockupPeriod::Test => 120, } } } diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index 7acd6302..70cd4f3c 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -35,9 +35,16 @@ pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Res VsrError::DepositStillLocked ); + deposit_entry.lockup.cooldown_ends_at = if deposit_entry.lockup.period == LockupPeriod::Test { + curr_ts + .checked_add(60) + .ok_or(VsrError::InvalidTimestampArguments)? + } else { + curr_ts + .checked_add(COOLDOWN_SECS) + .ok_or(VsrError::InvalidTimestampArguments)? + }; + deposit_entry.lockup.cooldown_requested = true; - deposit_entry.lockup.cooldown_ends_at = curr_ts - .checked_add(COOLDOWN_SECS) - .ok_or(VsrError::InvalidTimestampArguments)?; Ok(()) } From 13c7cddbdc8d7d0251f631c85bf3c1710e9205c4 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 11:53:04 +0300 Subject: [PATCH 44/59] merge packages back in order to produce correct idls --- Cargo.lock | 12 +- Cargo.toml | 1 - program-states/Cargo.lock | 2784 ----------------- program-states/Cargo.toml | 20 - program-states/src/lib.rs | 10 - programs/voter-stake-registry/Cargo.toml | 2 +- .../src/cpi_instructions.rs | 2 +- .../voter-stake-registry}/src/error.rs | 0 .../src/instructions/claim.rs | 2 +- .../src/instructions/close_deposit_entry.rs | 4 +- .../src/instructions/close_voter.rs | 4 +- .../src/instructions/configure_voting_mint.rs | 4 +- .../src/instructions/create_deposit_entry.rs | 4 +- .../src/instructions/create_registrar.rs | 4 +- .../src/instructions/create_voter.rs | 4 +- .../src/instructions/deposit.rs | 4 +- .../src/instructions/extend_deposit.rs | 4 +- .../internal_transfer_unlocked.rs | 4 +- .../src/instructions/log_voter_info.rs | 2 +- .../src/instructions/unlock_tokens.rs | 4 +- .../update_voter_weight_record.rs | 2 +- .../src/instructions/withdraw.rs | 2 +- programs/voter-stake-registry/src/lib.rs | 7 +- .../src/state/deposit_entry.rs | 1 + .../voter-stake-registry}/src/state/lockup.rs | 1 + .../voter-stake-registry}/src/state/mod.rs | 0 .../src/state/registrar.rs | 1 + .../voter-stake-registry}/src/state/voter.rs | 1 + .../src/state/voting_mint_config.rs | 1 + programs/voter-stake-registry/src/voter.rs | 2 +- .../tests/program_test/addin.rs | 9 +- .../tests/program_test/rewards.rs | 2 +- .../tests/test_all_deposits.rs | 2 +- .../voter-stake-registry/tests/test_basic.rs | 2 +- .../voter-stake-registry/tests/test_claim.rs | 2 +- .../tests/test_deposit_constant.rs | 2 +- .../tests/test_deposit_no_locking.rs | 2 +- .../tests/test_extend_deposit.rs | 2 +- .../tests/test_internal_transfers.rs | 7 +- .../voter-stake-registry/tests/test_lockup.rs | 2 +- .../tests/test_log_voter_info.rs | 2 +- .../voter-stake-registry/tests/test_voting.rs | 2 +- 42 files changed, 54 insertions(+), 2875 deletions(-) delete mode 100644 program-states/Cargo.lock delete mode 100644 program-states/Cargo.toml delete mode 100644 program-states/src/lib.rs rename {program-states => programs/voter-stake-registry}/src/error.rs (100%) rename {program-states => programs/voter-stake-registry}/src/state/deposit_entry.rs (99%) rename {program-states => programs/voter-stake-registry}/src/state/lockup.rs (99%) rename {program-states => programs/voter-stake-registry}/src/state/mod.rs (100%) rename {program-states => programs/voter-stake-registry}/src/state/registrar.rs (98%) rename {program-states => programs/voter-stake-registry}/src/state/voter.rs (98%) rename {program-states => programs/voter-stake-registry}/src/state/voting_mint_config.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 287b89da..70c1c08e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1968,16 +1968,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "mplx-staking-states" -version = "0.0.1" -dependencies = [ - "anchor-lang", - "anchor-spl", - "bytemuck", - "static_assertions", -] - [[package]] name = "nix" version = "0.24.3" @@ -4706,7 +4696,6 @@ dependencies = [ "bytemuck", "env_logger", "log", - "mplx-staking-states", "solana-program", "solana-program-test", "solana-sdk", @@ -4714,6 +4703,7 @@ dependencies = [ "spl-governance", "spl-governance-addin-api", "spl-token", + "static_assertions", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 66bbf035..9c65bed6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,6 @@ [workspace] members = [ "programs/*", - "program-states", # Drop everything below this line before running "anchor publish": # Otherwise the build will fail since Anchor uploads only parts of the source tree. ] diff --git a/program-states/Cargo.lock b/program-states/Cargo.lock deleted file mode 100644 index dccf59f5..00000000 --- a/program-states/Cargo.lock +++ /dev/null @@ -1,2784 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aead" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" -dependencies = [ - "generic-array", -] - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "opaque-debug", -] - -[[package]] -name = "aes-gcm-siv" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "polyval", - "subtle", - "zeroize", -] - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.14", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "anchor-attribute-access-control" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf7d535e1381be3de2c0716c0a1c1e32ad9df1042cddcf7bc18d743569e53319" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "regex", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-account" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3bcd731f21048a032be27c7791701120e44f3f6371358fc4261a7f716283d29" -dependencies = [ - "anchor-syn", - "anyhow", - "bs58 0.4.0", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-constant" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1be64a48e395fe00b8217287f226078be2cf32dae42fdf8a885b997945c3d28" -dependencies = [ - "anchor-syn", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-error" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38ea6713d1938c0da03656ff8a693b17dc0396da66d1ba320557f07e86eca0d4" -dependencies = [ - "anchor-syn", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-event" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401f11efb3644285685f8339829a9786d43ed7490bb1699f33c478d04d5a582" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-interface" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6700a6f5c888a9c33fe8afc0c64fd8575fa28d05446037306d0f96102ae4480" -dependencies = [ - "anchor-syn", - "anyhow", - "heck", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-program" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad769993b5266714e8939e47fbdede90e5c030333c7522d99a4d4748cf26712" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-attribute-state" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e677fae4a016a554acdd0e3b7f178d3acafaa7e7ffac6b8690cf4e171f1c116" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-derive-accounts" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "340beef6809d1c3fcc7ae219153d981e95a8a277ff31985bd7050e32645dc9a8" -dependencies = [ - "anchor-syn", - "anyhow", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "anchor-lang" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662ceafe667448ee4199a4be2ee83b6bb76da28566eee5cea05f96ab38255af8" -dependencies = [ - "anchor-attribute-access-control", - "anchor-attribute-account", - "anchor-attribute-constant", - "anchor-attribute-error", - "anchor-attribute-event", - "anchor-attribute-interface", - "anchor-attribute-program", - "anchor-attribute-state", - "anchor-derive-accounts", - "arrayref", - "base64 0.13.1", - "bincode", - "borsh 0.9.3", - "bytemuck", - "solana-program", - "thiserror", -] - -[[package]] -name = "anchor-spl" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f32390ce8356f54c0f0245ea156f8190717e37285b8bf4f406a613dc4b954cde" -dependencies = [ - "anchor-lang", - "solana-program", - "spl-associated-token-account", - "spl-token", -] - -[[package]] -name = "anchor-syn" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0418bcb5daac3b8cb1b60d8fdb1d468ca36f5509f31fb51179326fae1028fdcc" -dependencies = [ - "anyhow", - "bs58 0.3.1", - "heck", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "serde", - "serde_json", - "sha2 0.9.9", - "syn 1.0.109", - "thiserror", -] - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" - -[[package]] -name = "ark-bn254" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" -dependencies = [ - "ark-ec", - "ark-ff", - "ark-std", -] - -[[package]] -name = "ark-ec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" -dependencies = [ - "ark-ff", - "ark-poly", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.13.2", - "itertools", - "num-traits", - "zeroize", -] - -[[package]] -name = "ark-ff" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" -dependencies = [ - "ark-ff-asm", - "ark-ff-macros", - "ark-serialize", - "ark-std", - "derivative", - "digest 0.10.7", - "itertools", - "num-bigint", - "num-traits", - "paste", - "rustc_version", - "zeroize", -] - -[[package]] -name = "ark-ff-asm" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-ff-macros" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" -dependencies = [ - "num-bigint", - "num-traits", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-poly" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" -dependencies = [ - "ark-ff", - "ark-serialize", - "ark-std", - "derivative", - "hashbrown 0.13.2", -] - -[[package]] -name = "ark-serialize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" -dependencies = [ - "ark-serialize-derive", - "ark-std", - "digest 0.10.7", - "num-bigint", -] - -[[package]] -name = "ark-serialize-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ark-std" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "arrayref" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "assert_matches" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" - -[[package]] -name = "base64" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" -dependencies = [ - "serde", -] - -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - -[[package]] -name = "blake3" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", - "digest 0.10.7", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "block-padding", - "generic-array", -] - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-padding" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" - -[[package]] -name = "borsh" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" -dependencies = [ - "borsh-derive 0.9.3", - "hashbrown 0.11.2", -] - -[[package]] -name = "borsh" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" -dependencies = [ - "borsh-derive 0.10.3", - "hashbrown 0.13.2", -] - -[[package]] -name = "borsh" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" -dependencies = [ - "borsh-derive 1.4.0", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" -dependencies = [ - "borsh-derive-internal 0.9.3", - "borsh-schema-derive-internal 0.9.3", - "proc-macro-crate 0.1.5", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "borsh-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" -dependencies = [ - "borsh-derive-internal 0.10.3", - "borsh-schema-derive-internal 0.10.3", - "proc-macro-crate 0.1.5", - "proc-macro2", - "syn 1.0.109", -] - -[[package]] -name = "borsh-derive" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" -dependencies = [ - "once_cell", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.60", - "syn_derive", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "borsh-derive-internal" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "borsh-schema-derive-internal" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bs58" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bv" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" -dependencies = [ - "feature-probe", - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "cc" -version = "1.0.95" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" -dependencies = [ - "jobserver", - "libc", - "once_cell", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "num-traits", - "serde", - "windows-targets 0.52.5", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "console_log" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" -dependencies = [ - "log", - "web-sys", -] - -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "crypto-mac" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "serde", - "subtle", - "zeroize", -] - -[[package]] -name = "darling" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.60", -] - -[[package]] -name = "darling_macro" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivation-path" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "crypto-common", - "subtle", -] - -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "zeroize", -] - -[[package]] -name = "ed25519-dalek-bip32" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" -dependencies = [ - "derivation-path", - "ed25519-dalek", - "hmac 0.12.1", - "sha2 0.10.8", -] - -[[package]] -name = "either" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" - -[[package]] -name = "env_logger" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "feature-probe" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "serde", - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash 0.8.11", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" -dependencies = [ - "crypto-mac", - "digest 0.9.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac-drbg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" -dependencies = [ - "digest 0.9.0", - "generic-array", - "hmac 0.8.1", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "iana-time-zone" -version = "0.1.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "im" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" -dependencies = [ - "bitmaps", - "rand_core 0.6.4", - "rand_xoshiro", - "rayon", - "serde", - "sized-chunks", - "typenum", - "version_check", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jobserver" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libsecp256k1" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" -dependencies = [ - "arrayref", - "base64 0.12.3", - "digest 0.9.0", - "hmac-drbg", - "libsecp256k1-core", - "libsecp256k1-gen-ecmult", - "libsecp256k1-gen-genmult", - "rand 0.7.3", - "serde", - "sha2 0.9.9", - "typenum", -] - -[[package]] -name = "libsecp256k1-core" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" -dependencies = [ - "crunchy", - "digest 0.9.0", - "subtle", -] - -[[package]] -name = "libsecp256k1-gen-ecmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "libsecp256k1-gen-genmult" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" -dependencies = [ - "libsecp256k1-core", -] - -[[package]] -name = "light-poseidon" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" -dependencies = [ - "ark-bn254", - "ark-ff", - "num-bigint", - "thiserror", -] - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "memchr" -version = "2.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "merlin" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" -dependencies = [ - "byteorder", - "keccak", - "rand_core 0.6.4", - "zeroize", -] - -[[package]] -name = "mplx-staking-sdk" -version = "1.2.0" -dependencies = [ - "anchor-lang", - "anchor-spl", - "borsh 0.9.3", - "bytemuck", - "num-derive 0.3.3", - "num-traits", - "serde", - "serde_with 3.7.0", - "static_assertions", - "thiserror", -] - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive 0.5.11", -] - -[[package]] -name = "num_enum" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" -dependencies = [ - "num_enum_derive 0.7.2", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "num_enum_derive" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" -dependencies = [ - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.48.5", -] - -[[package]] -name = "paste" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" - -[[package]] -name = "pbkdf2" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" -dependencies = [ - "crypto-mac", -] - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "polyval" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro-crate" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" -dependencies = [ - "toml", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", - "yansi", -] - -[[package]] -name = "qstring" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "qualifier_attr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "quote" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.14", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "semver" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" - -[[package]] -name = "serde" -version = "1.0.198" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_bytes" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.198" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "serde_json" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" -dependencies = [ - "serde", - "serde_with_macros 2.3.3", -] - -[[package]] -name = "serde_with" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" -dependencies = [ - "base64 0.21.7", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.2.6", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros 3.7.0", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "serde_with_macros" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer 0.9.0", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - -[[package]] -name = "sha3" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" -dependencies = [ - "block-buffer 0.9.0", - "digest 0.9.0", - "keccak", - "opaque-debug", -] - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest 0.10.7", - "keccak", -] - -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "solana-frozen-abi" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b8177685ab2bc8cc8b3bf63aa1eaa0580d5af850ecefac323ca1c2473085d77" -dependencies = [ - "block-buffer 0.10.4", - "bs58 0.4.0", - "bv", - "either", - "generic-array", - "im", - "lazy_static", - "log", - "memmap2", - "rustc_version", - "serde", - "serde_bytes", - "serde_derive", - "sha2 0.10.8", - "solana-frozen-abi-macro", - "subtle", - "thiserror", -] - -[[package]] -name = "solana-frozen-abi-macro" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a68241cad17b74c6034a68ba4890632d409a2c886e7bead9c1e1432befdb7c9" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.60", -] - -[[package]] -name = "solana-logger" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea560989ef67ba4a1a0fd62a248721f1aa5bac8fa5ede9ccf4fe9ee484ccadf" -dependencies = [ - "env_logger", - "lazy_static", - "log", -] - -[[package]] -name = "solana-program" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bddf573103c890b4ab8f9a6641d4f969d4148bce9a451c263f4a62afa949fae" -dependencies = [ - "ark-bn254", - "ark-ec", - "ark-ff", - "ark-serialize", - "base64 0.21.7", - "bincode", - "bitflags 2.5.0", - "blake3", - "borsh 0.10.3", - "borsh 0.9.3", - "borsh 1.4.0", - "bs58 0.4.0", - "bv", - "bytemuck", - "cc", - "console_error_panic_hook", - "console_log", - "curve25519-dalek", - "getrandom 0.2.14", - "itertools", - "js-sys", - "lazy_static", - "libc", - "libsecp256k1", - "light-poseidon", - "log", - "memoffset", - "num-bigint", - "num-derive 0.4.2", - "num-traits", - "parking_lot", - "rand 0.8.5", - "rustc_version", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "sha2 0.10.8", - "sha3 0.10.8", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk-macro", - "thiserror", - "tiny-bip39", - "wasm-bindgen", - "zeroize", -] - -[[package]] -name = "solana-sdk" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08b24b06fa176209ddb2a2f8172a00b07e8a3b18229fbfc49f1eb3ce6ad11ff1" -dependencies = [ - "assert_matches", - "base64 0.21.7", - "bincode", - "bitflags 2.5.0", - "borsh 1.4.0", - "bs58 0.4.0", - "bytemuck", - "byteorder", - "chrono", - "derivation-path", - "digest 0.10.7", - "ed25519-dalek", - "ed25519-dalek-bip32", - "generic-array", - "hmac 0.12.1", - "itertools", - "js-sys", - "lazy_static", - "libsecp256k1", - "log", - "memmap2", - "num-derive 0.4.2", - "num-traits", - "num_enum 0.7.2", - "pbkdf2 0.11.0", - "qstring", - "qualifier_attr", - "rand 0.7.3", - "rand 0.8.5", - "rustc_version", - "rustversion", - "serde", - "serde_bytes", - "serde_derive", - "serde_json", - "serde_with 2.3.3", - "sha2 0.10.8", - "sha3 0.10.8", - "siphasher", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-logger", - "solana-program", - "solana-sdk-macro", - "thiserror", - "uriparse", - "wasm-bindgen", -] - -[[package]] -name = "solana-sdk-macro" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869483c05f18d37d4d95a08d9e05e00a4f76a8c8349aeedeee9ba2d013cbacde" -dependencies = [ - "bs58 0.4.0", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.60", -] - -[[package]] -name = "solana-zk-token-sdk" -version = "1.18.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459c27f7b954798677d8243aa53b8080cfb314ecfecbf8889a5a65c91ad11fee" -dependencies = [ - "aes-gcm-siv", - "base64 0.21.7", - "bincode", - "bytemuck", - "byteorder", - "curve25519-dalek", - "getrandom 0.1.16", - "itertools", - "lazy_static", - "merlin", - "num-derive 0.4.2", - "num-traits", - "rand 0.7.3", - "serde", - "serde_json", - "sha3 0.9.1", - "solana-program", - "solana-sdk", - "subtle", - "thiserror", - "zeroize", -] - -[[package]] -name = "spl-associated-token-account" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" -dependencies = [ - "assert_matches", - "borsh 0.9.3", - "num-derive 0.3.3", - "num-traits", - "solana-program", - "spl-token", - "spl-token-2022", - "thiserror", -] - -[[package]] -name = "spl-memo" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" -dependencies = [ - "solana-program", -] - -[[package]] -name = "spl-token" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" -dependencies = [ - "arrayref", - "bytemuck", - "num-derive 0.3.3", - "num-traits", - "num_enum 0.5.11", - "solana-program", - "thiserror", -] - -[[package]] -name = "spl-token-2022" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" -dependencies = [ - "arrayref", - "bytemuck", - "num-derive 0.3.3", - "num-traits", - "num_enum 0.5.11", - "solana-program", - "solana-zk-token-sdk", - "spl-memo", - "spl-token", - "thiserror", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "thiserror" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tiny-bip39" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" -dependencies = [ - "anyhow", - "hmac 0.8.1", - "once_cell", - "pbkdf2 0.4.0", - "rand 0.7.3", - "rustc-hash", - "sha2 0.9.9", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "universal-hash" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" -dependencies = [ - "generic-array", - "subtle", -] - -[[package]] -name = "uriparse" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" -dependencies = [ - "fnv", - "lazy_static", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.60", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.5", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" -dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] - -[[package]] -name = "zeroize" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.60", -] diff --git a/program-states/Cargo.toml b/program-states/Cargo.toml deleted file mode 100644 index 629449b7..00000000 --- a/program-states/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "mplx-staking-states" -version = "0.0.1" -homepage = "https://github.com/blockworks-foundation/voter-stake-registry" -description = "MPLX staking program's state dependencies" -license = "GPL-3.0-or-later" -edition = "2021" -readme = "README.md" - -[lib] -crate-type = ["cdylib", "lib"] - -[features] -test-sbf = [] - -[dependencies] -anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } -anchor-spl = { version = "0.26.0" } -static_assertions = "1.1" -bytemuck = "1.9.1" \ No newline at end of file diff --git a/program-states/src/lib.rs b/program-states/src/lib.rs deleted file mode 100644 index a5afd1e4..00000000 --- a/program-states/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -use anchor_lang::prelude::*; - -#[macro_use] -extern crate static_assertions; - -pub mod error; -pub mod state; - -// Requires by Anchor to declare accounts in crate -declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index f484046f..65beb01d 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -40,7 +40,7 @@ spl-governance = { version = "=2.2.1", features = ["no-entrypoint"] } spl-governance-addin-api = "=0.1.1" solana-program = "1.14.10" -mplx-staking-states = { path="../../program-states" } +static_assertions = "1.1" [dev-dependencies] solana-sdk = "1.14.10" diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 8dcfe893..46c6b9f4 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,7 +1,7 @@ +use crate::state::LockupPeriod; use anchor_lang::prelude::borsh; use anchor_lang::Key; use anchor_lang::{prelude::Pubkey, AnchorDeserialize, AnchorSerialize}; -use mplx_staking_states::state::LockupPeriod; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, diff --git a/program-states/src/error.rs b/programs/voter-stake-registry/src/error.rs similarity index 100% rename from program-states/src/error.rs rename to programs/voter-stake-registry/src/error.rs diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index a05edc45..d1cf552b 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -2,9 +2,9 @@ use std::borrow::Borrow; use crate::borsh::BorshDeserialize; use crate::cpi_instructions; +use crate::error::VsrError; use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; -use mplx_staking_states::error::VsrError; use solana_program::program::get_return_data; #[derive(Accounts)] diff --git a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs index 12cb8a5e..37de2797 100644 --- a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs @@ -1,6 +1,6 @@ +use crate::error::*; +use crate::state::*; use anchor_lang::prelude::*; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; #[derive(Accounts)] pub struct CloseDepositEntry<'info> { diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index 9aa4abc2..59ed7b99 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -1,11 +1,11 @@ use std::ops::DerefMut; +use crate::error::*; use anchor_lang::prelude::*; use anchor_spl::token::Transfer; use anchor_spl::token::{self, CloseAccount, Token, TokenAccount}; use bytemuck::bytes_of_mut; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::state::*; // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent diff --git a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs index abf91580..700b4b02 100644 --- a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs +++ b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::token::Mint; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; // Remaining accounts must be all the token mints that have registered // as voting mints, including the newly registered one. diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index 1cc81471..e54e21d2 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -1,8 +1,8 @@ use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index d81901ca..bcbe8e98 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -1,8 +1,8 @@ +use crate::error::*; use anchor_lang::prelude::*; use anchor_spl::token::Mint; use anchor_spl::token::Token; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::state::*; use spl_governance::state::realm; use std::mem::size_of; diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 9afd04f9..4f01fded 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; use std::mem::size_of; use crate::cpi_instructions; diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 84e7c9c6..8e9e32c8 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; use crate::cpi_instructions; diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index b1bdb81c..e1d10d9d 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,7 +1,7 @@ use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount, Transfer}; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; use crate::cpi_instructions::extend_deposit; diff --git a/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs b/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs index 59c41e6f..1272d5ea 100644 --- a/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs +++ b/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; #[derive(Accounts)] pub struct InternalTransferUnlocked<'info> { diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index dca58eb0..f814d908 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -1,6 +1,6 @@ use crate::events::*; use anchor_lang::prelude::*; -use mplx_staking_states::state::*; +use crate::state::*; #[derive(Accounts)] pub struct LogVoterInfo<'info> { diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index 70cd4f3c..858814c4 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -1,6 +1,6 @@ use anchor_lang::prelude::*; -use mplx_staking_states::error::*; -use mplx_staking_states::state::*; +use crate::error::*; +use crate::state::*; #[derive(Accounts)] pub struct UnlockTokens<'info> { diff --git a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs index 2cf866fb..97bf43fb 100644 --- a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs +++ b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use mplx_staking_states::state::*; +use crate::state::*; use crate::voter::VoterWeightRecord; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index b9ded8dc..e8de3ff7 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,8 +1,8 @@ use crate::cpi_instructions::withdraw_mining; use crate::voter::{load_token_owner_record, VoterWeightRecord}; +use crate::{error::*, state::*}; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; -use mplx_staking_states::{error::*, state::*}; #[derive(Accounts)] pub struct Withdraw<'info> { diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 6c8307c8..753fd340 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -1,11 +1,14 @@ use anchor_lang::prelude::*; +use error::*; use instructions::*; -use mplx_staking_states::state::*; +use state::*; pub mod cpi_instructions; +pub mod error; pub mod events; mod governance; mod instructions; +pub mod state; pub mod voter; // The program address. @@ -57,7 +60,7 @@ declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); /// a u64. #[program] pub mod voter_stake_registry { - use mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}; + use crate::state::lockup::{LockupKind, LockupPeriod}; use super::*; diff --git a/program-states/src/state/deposit_entry.rs b/programs/voter-stake-registry/src/state/deposit_entry.rs similarity index 99% rename from program-states/src/state/deposit_entry.rs rename to programs/voter-stake-registry/src/state/deposit_entry.rs index 0c2bc499..1781b825 100644 --- a/program-states/src/state/deposit_entry.rs +++ b/programs/voter-stake-registry/src/state/deposit_entry.rs @@ -1,6 +1,7 @@ use crate::state::lockup::{Lockup, LockupKind}; use anchor_lang::prelude::*; +use static_assertions::const_assert; /// Bookkeeping for a single deposit for a given mint and lockup schedule. #[zero_copy] diff --git a/program-states/src/state/lockup.rs b/programs/voter-stake-registry/src/state/lockup.rs similarity index 99% rename from program-states/src/state/lockup.rs rename to programs/voter-stake-registry/src/state/lockup.rs index 216a9c7f..9c5df8af 100644 --- a/program-states/src/state/lockup.rs +++ b/programs/voter-stake-registry/src/state/lockup.rs @@ -1,5 +1,6 @@ use crate::error::*; use anchor_lang::prelude::*; +use static_assertions::const_assert; /// Seconds in one day. pub const SECS_PER_DAY: u64 = 86_400; diff --git a/program-states/src/state/mod.rs b/programs/voter-stake-registry/src/state/mod.rs similarity index 100% rename from program-states/src/state/mod.rs rename to programs/voter-stake-registry/src/state/mod.rs diff --git a/program-states/src/state/registrar.rs b/programs/voter-stake-registry/src/state/registrar.rs similarity index 98% rename from program-states/src/state/registrar.rs rename to programs/voter-stake-registry/src/state/registrar.rs index 8a780b58..f0351200 100644 --- a/program-states/src/state/registrar.rs +++ b/programs/voter-stake-registry/src/state/registrar.rs @@ -2,6 +2,7 @@ use crate::error::*; use crate::state::voting_mint_config::VotingMintConfig; use anchor_lang::prelude::*; use anchor_spl::token::Mint; +use static_assertions::const_assert; use std::convert::TryInto; /// Instance of a voting rights distributor. diff --git a/program-states/src/state/voter.rs b/programs/voter-stake-registry/src/state/voter.rs similarity index 98% rename from program-states/src/state/voter.rs rename to programs/voter-stake-registry/src/state/voter.rs index 51545512..00a30536 100644 --- a/program-states/src/state/voter.rs +++ b/programs/voter-stake-registry/src/state/voter.rs @@ -88,4 +88,5 @@ macro_rules! voter_seeds { }; } +use static_assertions::const_assert; pub use voter_seeds; diff --git a/program-states/src/state/voting_mint_config.rs b/programs/voter-stake-registry/src/state/voting_mint_config.rs similarity index 99% rename from program-states/src/state/voting_mint_config.rs rename to programs/voter-stake-registry/src/state/voting_mint_config.rs index e7abf2d3..803fbc40 100644 --- a/program-states/src/state/voting_mint_config.rs +++ b/programs/voter-stake-registry/src/state/voting_mint_config.rs @@ -1,5 +1,6 @@ use crate::error::*; use anchor_lang::prelude::*; +use static_assertions::const_assert; use std::convert::TryFrom; const SCALED_FACTOR_BASE: u64 = 1_000_000_000; diff --git a/programs/voter-stake-registry/src/voter.rs b/programs/voter-stake-registry/src/voter.rs index 78501825..4513cc8d 100644 --- a/programs/voter-stake-registry/src/voter.rs +++ b/programs/voter-stake-registry/src/voter.rs @@ -1,5 +1,5 @@ +use crate::{error::VsrError, state::registrar::Registrar}; use anchor_lang::prelude::*; -use mplx_staking_states::{error::VsrError, state::registrar::Registrar}; use spl_governance::state::token_owner_record; use crate::vote_weight_record; diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index fb3b8750..fe1c871e 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -2,13 +2,13 @@ use std::cell::RefCell; use std::rc::Rc; use anchor_lang::Key; -use mplx_staking_states::state::LockupPeriod; use solana_sdk::pubkey::Pubkey; use solana_sdk::{ instruction::Instruction, signature::{Keypair, Signer}, }; +use voter_stake_registry::state::{LockupPeriod, Voter}; use crate::*; @@ -301,7 +301,7 @@ impl AddinCookie { voter_authority: &Keypair, voting_mint: &VotingMintConfigCookie, deposit_entry_index: u8, - lockup_kind: mplx_staking_states::state::LockupKind, + lockup_kind: LockupKind, start_ts: Option, period: LockupPeriod, ) -> std::result::Result<(), BanksClientError> { @@ -759,10 +759,7 @@ impl VotingMintConfigCookie { impl VoterCookie { pub async fn deposit_amount(&self, solana: &SolanaCookie, deposit_id: u8) -> u64 { - solana - .get_account::(self.address) - .await - .deposits[deposit_id as usize] + solana.get_account::(self.address).await.deposits[deposit_id as usize] .amount_deposited_native } diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index d1ea354e..4d0fe1a3 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -2,7 +2,6 @@ use std::rc::Rc; use anchor_lang::prelude::*; use anchor_lang::AnchorDeserialize; -use mplx_staking_states::state::LockupPeriod; use solana_program_test::*; use solana_sdk::program_pack::IsInitialized; use solana_sdk::{ @@ -13,6 +12,7 @@ use solana_sdk::{ system_program, }; use voter_stake_registry::cpi_instructions::RewardsInstruction; +use voter_stake_registry::state::LockupPeriod; use crate::SolanaCookie; diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 4597de15..67fcabae 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; #[tokio::test] diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 90c13e18..220bb7f7 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -2,8 +2,8 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index a91e8560..23e1bbca 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -2,8 +2,8 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index b76d7301..7ba685cf 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 029af101..5b360327 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -2,8 +2,8 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index ee8f81be..1c5aa45a 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -1,10 +1,10 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{ clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, }; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index 6870605e..e66074d0 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -1,9 +1,10 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; use std::{cell::RefCell, rc::Rc}; +use voter_stake_registry::state::Voter; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; const DEPOSIT_A: u8 = 0; const DEPOSIT_B: u8 = 1; @@ -27,9 +28,7 @@ async fn get_lockup_data( time_offset: i64, ) -> LockupData { let now = solana.get_clock().await.unix_timestamp + time_offset; - let voter = solana - .get_account::(voter) - .await; + let voter = solana.get_account::(voter).await; let d = voter.deposits[index as usize]; let duration = d.lockup.period.to_secs(); LockupData::new( diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index d2a16c1d..8a017d12 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -2,8 +2,8 @@ use anchor_spl::token::TokenAccount; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 44374c6d..20f8133e 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 15a58d22..931d7630 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; #[tokio::test] From 56da1066f6b34b2ff181f7a32bb264c02574e9eb Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 11:53:46 +0300 Subject: [PATCH 45/59] update ts types --- idl/voter_stake_registry.ts | 142 +++++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 36 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 8f878df4..775ecf11 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -111,7 +111,7 @@ export type VoterStakeRegistry = { "Account that will be created via CPI to the rewards,", "it's responsible for being a \"root\" for all entities", "inside rewards contract", - "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" ] }, { @@ -120,7 +120,7 @@ export type VoterStakeRegistry = { "isSigner": false, "docs": [ "This account is responsible for storing money for rewards", - "PDA(\"vault\", reward_pool, reward_mint)" + "PDA([\"vault\", reward_pool, reward_mint], reward_program)" ] }, { @@ -269,7 +269,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" ] }, { @@ -277,7 +277,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -414,7 +414,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" ] }, { @@ -422,7 +422,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -744,6 +744,11 @@ export type VoterStakeRegistry = { "isMut": false, "isSigner": false }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, { "name": "rewardPool", "isMut": true, @@ -754,7 +759,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -769,7 +774,7 @@ export type VoterStakeRegistry = { "type": "u8" }, { - "name": "lockupPeriod", + "name": "newLockupPeriod", "type": { "defined": "LockupPeriod" } @@ -785,6 +790,10 @@ export type VoterStakeRegistry = { { "name": "realmPubkey", "type": "publicKey" + }, + { + "name": "additionalAmount", + "type": "u64" } ] }, @@ -796,7 +805,7 @@ export type VoterStakeRegistry = { "isMut": false, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" ] }, { @@ -810,7 +819,7 @@ export type VoterStakeRegistry = { "isSigner": false, "docs": [ "is checked on the rewards contract", - "PDA(\"vault\", reward_pool, reward_mint)" + "PDA([\"vault\", reward_pool, reward_mint], reward_program)" ] }, { @@ -818,7 +827,7 @@ export type VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -1235,8 +1244,7 @@ export type VoterStakeRegistry = { { "name": "distribution_authority", "docs": [ - "Account that can distribute money among users after", - "if RewardVault had been filled with rewards" + "Account can distribute rewards for stakers" ], "type": "publicKey" } @@ -1327,20 +1335,23 @@ export type VoterStakeRegistry = { "name": "RestakeDeposit", "fields": [ { - "name": "lockup_period", + "name": "old_lockup_period", "docs": [ - "Requested lockup period for restaking" + "Lockup period before restaking. Actually it's only needed", + "for Flex to AnyPeriod edge case" ], "type": { "defined": "LockupPeriod" } }, { - "name": "amount", + "name": "new_lockup_period", "docs": [ - "Amount of tokens to be restaked" + "Requested lockup period for restaking" ], - "type": "u64" + "type": { + "defined": "LockupPeriod" + } }, { "name": "deposit_start_ts", @@ -1348,6 +1359,30 @@ export type VoterStakeRegistry = { "Deposit start_ts" ], "type": "u64" + }, + { + "name": "base_amount", + "docs": [ + "Amount of tokens to be restaked, this", + "number cannot be decreased. It reflects the number of staked tokens", + "before the restake function call" + ], + "type": "u64" + }, + { + "name": "additional_amount", + "docs": [ + "In case user wants to increase it's staked number of tokens,", + "the addition amount might be provided" + ], + "type": "u64" + }, + { + "name": "mining_owner", + "docs": [ + "The wallet who owns the mining account" + ], + "type": "publicKey" } ] }, @@ -1786,7 +1821,7 @@ export const IDL: VoterStakeRegistry = { "Account that will be created via CPI to the rewards,", "it's responsible for being a \"root\" for all entities", "inside rewards contract", - "It's the PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" ] }, { @@ -1795,7 +1830,7 @@ export const IDL: VoterStakeRegistry = { "isSigner": false, "docs": [ "This account is responsible for storing money for rewards", - "PDA(\"vault\", reward_pool, reward_mint)" + "PDA([\"vault\", reward_pool, reward_mint], reward_program)" ] }, { @@ -1944,7 +1979,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" ] }, { @@ -1952,7 +1987,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -2089,7 +2124,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" ] }, { @@ -2097,7 +2132,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -2419,6 +2454,11 @@ export const IDL: VoterStakeRegistry = { "isMut": false, "isSigner": false }, + { + "name": "vault", + "isMut": true, + "isSigner": false + }, { "name": "rewardPool", "isMut": true, @@ -2429,7 +2469,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -2444,7 +2484,7 @@ export const IDL: VoterStakeRegistry = { "type": "u8" }, { - "name": "lockupPeriod", + "name": "newLockupPeriod", "type": { "defined": "LockupPeriod" } @@ -2460,6 +2500,10 @@ export const IDL: VoterStakeRegistry = { { "name": "realmPubkey", "type": "publicKey" + }, + { + "name": "additionalAmount", + "type": "u64" } ] }, @@ -2471,7 +2515,7 @@ export const IDL: VoterStakeRegistry = { "isMut": false, "isSigner": false, "docs": [ - "PDA(\"reward_pool\", deposit_authority[aka registrar in our case], fill_authority)" + "PDA([\"reward_pool\", deposit_authority[aka registrar in our case]], rewards_program)" ] }, { @@ -2485,7 +2529,7 @@ export const IDL: VoterStakeRegistry = { "isSigner": false, "docs": [ "is checked on the rewards contract", - "PDA(\"vault\", reward_pool, reward_mint)" + "PDA([\"vault\", reward_pool, reward_mint], reward_program)" ] }, { @@ -2493,7 +2537,7 @@ export const IDL: VoterStakeRegistry = { "isMut": true, "isSigner": false, "docs": [ - "PDA(\"mining\", mining owner[aka voter_authority in our case], reward_pool)" + "PDA([\"mining\", mining owner , reward_pool], reward_program)" ] }, { @@ -2910,8 +2954,7 @@ export const IDL: VoterStakeRegistry = { { "name": "distribution_authority", "docs": [ - "Account that can distribute money among users after", - "if RewardVault had been filled with rewards" + "Account can distribute rewards for stakers" ], "type": "publicKey" } @@ -3002,20 +3045,23 @@ export const IDL: VoterStakeRegistry = { "name": "RestakeDeposit", "fields": [ { - "name": "lockup_period", + "name": "old_lockup_period", "docs": [ - "Requested lockup period for restaking" + "Lockup period before restaking. Actually it's only needed", + "for Flex to AnyPeriod edge case" ], "type": { "defined": "LockupPeriod" } }, { - "name": "amount", + "name": "new_lockup_period", "docs": [ - "Amount of tokens to be restaked" + "Requested lockup period for restaking" ], - "type": "u64" + "type": { + "defined": "LockupPeriod" + } }, { "name": "deposit_start_ts", @@ -3023,6 +3069,30 @@ export const IDL: VoterStakeRegistry = { "Deposit start_ts" ], "type": "u64" + }, + { + "name": "base_amount", + "docs": [ + "Amount of tokens to be restaked, this", + "number cannot be decreased. It reflects the number of staked tokens", + "before the restake function call" + ], + "type": "u64" + }, + { + "name": "additional_amount", + "docs": [ + "In case user wants to increase it's staked number of tokens,", + "the addition amount might be provided" + ], + "type": "u64" + }, + { + "name": "mining_owner", + "docs": [ + "The wallet who owns the mining account" + ], + "type": "publicKey" } ] }, From 0fcb0d40745036f828dcd23c57f58a4d93a9a09a Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 17:56:03 +0300 Subject: [PATCH 46/59] experimental commit with freshly redifined deposit && lock_tokens functions --- programs/voter-stake-registry/src/error.rs | 6 + .../src/instructions/close_voter.rs | 2 +- .../src/instructions/create_deposit_entry.rs | 9 +- .../src/instructions/deposit.rs | 61 +- .../src/instructions/extend_deposit.rs | 8 +- .../internal_transfer_unlocked.rs | 61 -- .../src/instructions/lock_tokens.rs | 107 ++ .../src/instructions/mod.rs | 4 +- .../src/instructions/withdraw.rs | 10 + programs/voter-stake-registry/src/lib.rs | 31 +- .../src/state/deposit_entry.rs | 9 +- .../voter-stake-registry/src/state/voter.rs | 2 +- .../tests/program_test/addin.rs | 42 +- .../tests/test_all_deposits.rs | 318 +++--- .../voter-stake-registry/tests/test_basic.rs | 35 +- .../voter-stake-registry/tests/test_claim.rs | 28 +- .../tests/test_deposit_constant.rs | 86 +- .../tests/test_deposit_no_locking.rs | 10 - .../tests/test_extend_deposit.rs | 166 +++- .../tests/test_internal_transfers.rs | 724 +++++++------- .../voter-stake-registry/tests/test_lockup.rs | 923 +++++++++--------- .../tests/test_log_voter_info.rs | 293 +++--- .../voter-stake-registry/tests/test_voting.rs | 609 ++++++------ 23 files changed, 1880 insertions(+), 1664 deletions(-) delete mode 100644 programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs create mode 100644 programs/voter-stake-registry/src/instructions/lock_tokens.rs diff --git a/programs/voter-stake-registry/src/error.rs b/programs/voter-stake-registry/src/error.rs index d7ab5ad4..cde913c0 100644 --- a/programs/voter-stake-registry/src/error.rs +++ b/programs/voter-stake-registry/src/error.rs @@ -131,4 +131,10 @@ pub enum VsrError { // 6042 / 0x179a #[msg("Cpi call must return data, but data is absent")] CpiReturnDataIsAbsent, + // 6043 / 0x179b + #[msg("The source for the transfer only can be a deposit on DAO")] + LockingIsForbidded, + // 6044 / 0x179c + #[msg("Locking up tokens is only allowed for freshly-deposited deposit entry")] + DepositEntryIsOld, } diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index 59ed7b99..f9e50421 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -1,11 +1,11 @@ use std::ops::DerefMut; use crate::error::*; +use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::Transfer; use anchor_spl::token::{self, CloseAccount, Token, TokenAccount}; use bytemuck::bytes_of_mut; -use crate::state::*; // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index e54e21d2..5cd8fc95 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -1,8 +1,8 @@ +use crate::error::*; +use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; -use crate::error::*; -use crate::state::*; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { @@ -57,7 +57,6 @@ pub fn create_deposit_entry( ctx: Context, deposit_entry_index: u8, kind: LockupKind, - start_ts: Option, period: LockupPeriod, ) -> Result<()> { // Load accounts. @@ -76,9 +75,7 @@ pub fn create_deposit_entry( let d_entry = &mut voter.deposits[deposit_entry_index as usize]; require!(!d_entry.is_used, VsrError::UnusedDepositEntryIndex); - let curr_ts = registrar.clock_unix_timestamp(); - let start_ts = start_ts.unwrap_or(curr_ts); - + let start_ts = registrar.clock_unix_timestamp(); *d_entry = DepositEntry::default(); d_entry.is_used = true; d_entry.voting_mint_config_idx = mint_idx as u8; diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 8e9e32c8..4137b83c 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -1,9 +1,7 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount}; use crate::error::*; use crate::state::*; - -use crate::cpi_instructions; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, Token, TokenAccount}; #[derive(Accounts)] pub struct Deposit<'info> { @@ -33,19 +31,6 @@ pub struct Deposit<'info> { pub deposit_authority: Signer<'info>, pub token_program: Program<'info, Token>, - - /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) - #[account(mut)] - pub reward_pool: UncheckedAccount<'info>, - - /// CHECK: mining PDA will be checked in the rewards contract - /// PDA(["mining", mining owner , reward_pool], reward_program) - #[account(mut)] - pub deposit_mining: UncheckedAccount<'info>, - - /// CHECK: Rewards Program account - pub rewards_program: UncheckedAccount<'info>, } impl<'info> Deposit<'info> { @@ -68,14 +53,7 @@ impl<'info> Deposit<'info> { /// /// `deposit_entry_index`: Index of the deposit entry. /// `amount`: Number of native tokens to transfer. -pub fn deposit( - ctx: Context, - deposit_entry_index: u8, - amount: u64, - registrar_bump: u8, - realm_governing_mint_pubkey: Pubkey, - realm_pubkey: Pubkey, -) -> Result<()> { +pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { if amount == 0 { return Ok(()); } @@ -90,6 +68,12 @@ pub fn deposit( d_entry.amount_deposited_native == 0, VsrError::DepositingIsForbidded ); + require!( + d_entry.lockup.kind == LockupKind::None + && d_entry.lockup.period == LockupPeriod::None + && d_entry.is_used, + VsrError::DepositingIsForbidded, + ); // Get the exchange rate entry associated with this deposit. let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; @@ -106,35 +90,8 @@ pub fn deposit( } let voter = &ctx.accounts.voter.load()?; - let owner = &voter.voter_authority; let d_entry = voter.active_deposit(deposit_entry_index)?; - // no sense to call cpi if lockup had not been staked - if d_entry.lockup.kind == LockupKind::None || d_entry.lockup.period == LockupPeriod::None { - return Ok(()); - } - - let reward_pool = &ctx.accounts.reward_pool; - let mining = &ctx.accounts.deposit_mining; - let pool_deposit_authority = &ctx.accounts.registrar.to_account_info(); - let signers_seeds = &[ - &realm_pubkey.key().to_bytes(), - b"registrar".as_ref(), - &realm_governing_mint_pubkey.key().to_bytes(), - &[registrar_bump][..], - ]; - - cpi_instructions::deposit_mining( - ctx.accounts.rewards_program.to_account_info(), - reward_pool.to_account_info(), - mining.to_account_info(), - pool_deposit_authority.to_account_info(), - amount, - d_entry.lockup.period, - owner, - signers_seeds, - )?; - msg!( "Deposited amount {} at deposit index {} with lockup kind {:?} with lockup period {:?} and {} seconds left. It's used now: {:?}", amount, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index e1d10d9d..9c41d529 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,7 +1,7 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount, Transfer}; use crate::error::*; use crate::state::*; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, Token, TokenAccount, Transfer}; use crate::cpi_instructions::extend_deposit; @@ -79,6 +79,10 @@ pub fn restake_deposit( let voter = &mut ctx.accounts.voter.load_mut()?; let mining_owner = voter.voter_authority; let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + require!( + d_entry.lockup.period != LockupPeriod::None && d_entry.lockup.kind != LockupKind::None, + VsrError::RestakeDepositIsNotAllowed + ); let start_ts = d_entry.lockup.start_ts; let curr_ts = registrar.clock_unix_timestamp(); let amount = d_entry.amount_deposited_native; diff --git a/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs b/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs deleted file mode 100644 index 1272d5ea..00000000 --- a/programs/voter-stake-registry/src/instructions/internal_transfer_unlocked.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anchor_lang::prelude::*; -use crate::error::*; -use crate::state::*; - -#[derive(Accounts)] -pub struct InternalTransferUnlocked<'info> { - pub registrar: AccountLoader<'info, Registrar>, - - // checking the PDA address it just an extra precaution, - // the other constraints must be exhaustive - #[account( - mut, - seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], - bump = voter.load()?.voter_bump, - has_one = voter_authority, - has_one = registrar)] - pub voter: AccountLoader<'info, Voter>, - pub voter_authority: Signer<'info>, -} - -/// Transfers unlocked tokens from the source deposit entry to the target deposit entry. -/// -/// Note that this never transfers locked tokens, only tokens that are unlocked. -/// -/// The primary usecase is moving some deposited funds from one deposit entry to -/// another because the user wants to lock them, without having to withdraw and re-deposit -/// them. -pub fn internal_transfer_unlocked( - ctx: Context, - source_deposit_entry_index: u8, - target_deposit_entry_index: u8, - amount: u64, -) -> Result<()> { - let registrar = &ctx.accounts.registrar.load()?; - let voter = &mut ctx.accounts.voter.load_mut()?; - let curr_ts = registrar.clock_unix_timestamp(); - - let source = voter.active_deposit_mut(source_deposit_entry_index)?; - let source_mint_idx = source.voting_mint_config_idx; - - // Reduce source amounts - require_gte!( - source.amount_unlocked(curr_ts), - amount, - VsrError::InsufficientUnlockedTokens - ); - source.amount_deposited_native = source.amount_deposited_native.checked_sub(amount).unwrap(); - - // Check target compatibility - let target = voter.active_deposit_mut(target_deposit_entry_index)?; - require_eq!( - target.voting_mint_config_idx, - source_mint_idx, - VsrError::InvalidMint - ); - - // Add target amounts - target.amount_deposited_native = target.amount_deposited_native.checked_add(amount).unwrap(); - - Ok(()) -} diff --git a/programs/voter-stake-registry/src/instructions/lock_tokens.rs b/programs/voter-stake-registry/src/instructions/lock_tokens.rs new file mode 100644 index 00000000..8ab09cf1 --- /dev/null +++ b/programs/voter-stake-registry/src/instructions/lock_tokens.rs @@ -0,0 +1,107 @@ +use crate::error::*; +use crate::state::*; +use anchor_lang::prelude::*; + +use crate::cpi_instructions; + +#[derive(Accounts)] +pub struct LockTokens<'info> { + pub registrar: AccountLoader<'info, Registrar>, + + // checking the PDA address it just an extra precaution, + // the other constraints must be exhaustive + #[account( + mut, + seeds = [registrar.key().as_ref(), b"voter".as_ref(), voter_authority.key().as_ref()], + bump = voter.load()?.voter_bump, + has_one = voter_authority, + has_one = registrar)] + pub voter: AccountLoader<'info, Voter>, + pub voter_authority: Signer<'info>, + + /// CHECK: Reward Pool PDA will be checked in the rewards contract + /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) + #[account(mut)] + pub reward_pool: UncheckedAccount<'info>, + + /// CHECK: mining PDA will be checked in the rewards contract + /// PDA(["mining", mining owner , reward_pool], reward_program) + #[account(mut)] + pub deposit_mining: UncheckedAccount<'info>, + + /// CHECK: Rewards Program account + pub rewards_program: UncheckedAccount<'info>, +} + +/// Transfers unlocked tokens from the source deposit entry to the target deposit entry. +/// +/// Transfers token from one DepositEntry that is not LockupKind::None to another that is LockupKind::Constant. +/// In terms of business logic that means we want to deposit some tokens on DAO, +/// then we want to lock them up in order to receice rewards +pub fn lock_tokens( + ctx: Context, + source_deposit_entry_index: u8, + target_deposit_entry_index: u8, + amount: u64, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, +) -> Result<()> { + let registrar = &ctx.accounts.registrar.load()?; + let voter = &mut ctx.accounts.voter.load_mut()?; + let curr_ts = registrar.clock_unix_timestamp(); + + let source = voter.active_deposit_mut(source_deposit_entry_index)?; + let source_mint_idx = source.voting_mint_config_idx; + require!( + source.lockup.kind == LockupKind::None, + VsrError::LockingIsForbidded + ); + + // Reduce source amounts + require_gte!( + source.amount_unlocked(curr_ts), + amount, + VsrError::InsufficientUnlockedTokens + ); + source.amount_deposited_native = source.amount_deposited_native.checked_sub(amount).unwrap(); + + // Check target compatibility + let target = voter.active_deposit_mut(target_deposit_entry_index)?; + require_eq!( + target.voting_mint_config_idx, + source_mint_idx, + VsrError::InvalidMint + ); + + // Checks that target doesn't have any stored tokens yet + require!( + target.amount_deposited_native == 0, + VsrError::DepositEntryIsOld + ); + // Add target amounts + target.amount_deposited_native = target.amount_deposited_native.checked_add(amount).unwrap(); + + let reward_pool = &ctx.accounts.reward_pool; + let mining = &ctx.accounts.deposit_mining; + let pool_deposit_authority = &ctx.accounts.registrar.to_account_info(); + let signers_seeds = &[ + &realm_pubkey.key().to_bytes(), + b"registrar".as_ref(), + &realm_governing_mint_pubkey.key().to_bytes(), + &[registrar.bump][..], + ]; + let owner = &ctx.accounts.voter_authority.key(); + + cpi_instructions::deposit_mining( + ctx.accounts.rewards_program.to_account_info(), + reward_pool.to_account_info(), + mining.to_account_info(), + pool_deposit_authority.to_account_info(), + amount, + target.lockup.period, + owner, + signers_seeds, + )?; + + Ok(()) +} diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 5527c83f..41126408 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -7,7 +7,7 @@ pub use create_registrar::*; pub use create_voter::*; pub use deposit::*; pub use extend_deposit::*; -pub use internal_transfer_unlocked::*; +pub use lock_tokens::*; pub use log_voter_info::*; pub use unlock_tokens::*; pub use update_voter_weight_record::*; @@ -22,7 +22,7 @@ mod create_registrar; mod create_voter; mod deposit; mod extend_deposit; -mod internal_transfer_unlocked; +mod lock_tokens; mod log_voter_info; mod unlock_tokens; mod update_voter_weight_record; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index e8de3ff7..9617b00b 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -158,6 +158,16 @@ pub fn withdraw( .checked_sub(amount) .unwrap(); + // if deposit doesn't have tokens after withdrawal + // then is shouldn't be used + if deposit_entry.amount_deposited_native == 0 + && deposit_entry.lockup.kind != LockupKind::None + && deposit_entry.lockup.period != LockupPeriod::None + { + *deposit_entry = DepositEntry::default(); + deposit_entry.is_used = false; + } + msg!( "Withdrew amount {} at deposit index {} with lockup kind {:?} and {} seconds left", amount, diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 753fd340..fc5b16fa 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -105,28 +105,13 @@ pub mod voter_stake_registry { ctx: Context, deposit_entry_index: u8, kind: LockupKind, - start_ts: Option, period: LockupPeriod, ) -> Result<()> { - instructions::create_deposit_entry(ctx, deposit_entry_index, kind, start_ts, period) + instructions::create_deposit_entry(ctx, deposit_entry_index, kind, period) } - pub fn deposit( - ctx: Context, - deposit_entry_index: u8, - amount: u64, - registrar_bump: u8, - realm_governing_mint_pubkey: Pubkey, - realm_pubkey: Pubkey, - ) -> Result<()> { - instructions::deposit( - ctx, - deposit_entry_index, - amount, - registrar_bump, - realm_governing_mint_pubkey, - realm_pubkey, - ) + pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { + instructions::deposit(ctx, deposit_entry_index, amount) } pub fn withdraw( @@ -174,17 +159,21 @@ pub mod voter_stake_registry { instructions::log_voter_info(ctx, deposit_entry_begin, deposit_entry_count) } - pub fn internal_transfer_unlocked( - ctx: Context, + pub fn lock_tokens( + ctx: Context, source_deposit_entry_index: u8, target_deposit_entry_index: u8, amount: u64, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> Result<()> { - instructions::internal_transfer_unlocked( + instructions::lock_tokens( ctx, source_deposit_entry_index, target_deposit_entry_index, amount, + realm_governing_mint_pubkey, + realm_pubkey, ) } diff --git a/programs/voter-stake-registry/src/state/deposit_entry.rs b/programs/voter-stake-registry/src/state/deposit_entry.rs index 1781b825..122616c3 100644 --- a/programs/voter-stake-registry/src/state/deposit_entry.rs +++ b/programs/voter-stake-registry/src/state/deposit_entry.rs @@ -7,9 +7,10 @@ use static_assertions::const_assert; #[zero_copy] #[derive(Default)] pub struct DepositEntry { - // Locked state. + /// Locked state. pub lockup: Lockup, - + /// Delegated staker + pub delegate: Pubkey, /// Amount in deposited, in native currency. Withdraws of vested tokens /// directly reduce this amount. /// @@ -22,7 +23,7 @@ pub struct DepositEntry { pub is_used: bool, pub _reserved1: [u8; 6], } -const_assert!(std::mem::size_of::() == 32 + 8 + 1 + 1 + 6); +const_assert!(std::mem::size_of::() == 32 + 32 + 8 + 1 + 1 + 6); const_assert!(std::mem::size_of::() % 8 == 0); impl DepositEntry { @@ -74,8 +75,10 @@ mod tests { let saturation: i64 = 5 * day; let lockup_start = 10_000_000_000; // arbitrary point let period = LockupPeriod::Flex; + let delegate = Pubkey::new_unique(); let deposit = DepositEntry { amount_deposited_native: 20_000, + delegate, lockup: Lockup { start_ts: lockup_start, end_ts: lockup_start + LockupPeriod::Flex.to_secs(), // start + cooldown + period diff --git a/programs/voter-stake-registry/src/state/voter.rs b/programs/voter-stake-registry/src/state/voter.rs index 00a30536..94876b8d 100644 --- a/programs/voter-stake-registry/src/state/voter.rs +++ b/programs/voter-stake-registry/src/state/voter.rs @@ -13,7 +13,7 @@ pub struct Voter { pub voter_weight_record_bump: u8, pub _reserved1: [u8; 14], } -const_assert!(std::mem::size_of::() == 48 * 32 + 32 + 32 + 1 + 1 + 14); +const_assert!(std::mem::size_of::() == 80 * 32 + 32 + 32 + 1 + 1 + 14); const_assert!(std::mem::size_of::() % 8 == 0); pub const VOTER_DISCRIMINATOR: [u8; 8] = [241, 93, 35, 191, 254, 147, 17, 202]; diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index fe1c871e..7f2b7d29 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -124,28 +124,43 @@ impl AddinCookie { (registrar_cookie, reward_pool) } - pub async fn internal_transfer_unlocked( + pub async fn lock_tokens( &self, + // accounts registrar: &RegistrarCookie, voter: &VoterCookie, - authority: &Keypair, + voter_authority: &Keypair, + deposit_mining: &Pubkey, + rewards_program: &Pubkey, + // params source_deposit_entry_index: u8, target_deposit_entry_index: u8, amount: u64, + realm_governing_mint_pubkey: Pubkey, + realm_pubkey: Pubkey, ) -> std::result::Result<(), BanksClientError> { - let data = anchor_lang::InstructionData::data( - &voter_stake_registry::instruction::InternalTransferUnlocked { + let data = + anchor_lang::InstructionData::data(&voter_stake_registry::instruction::LockTokens { source_deposit_entry_index, target_deposit_entry_index, amount, - }, + realm_governing_mint_pubkey, + realm_pubkey, + }); + + let (reward_pool, _reward_pool_bump) = Pubkey::find_program_address( + &["reward_pool".as_bytes(), ®istrar.address.to_bytes()], + rewards_program, ); let accounts = anchor_lang::ToAccountMetas::to_account_metas( - &voter_stake_registry::accounts::InternalTransferUnlocked { + &voter_stake_registry::accounts::LockTokens { registrar: registrar.address, voter: voter.address, - voter_authority: authority.pubkey(), + voter_authority: voter_authority.pubkey(), + reward_pool, + deposit_mining: *deposit_mining, + rewards_program: *rewards_program, }, None, ); @@ -157,7 +172,7 @@ impl AddinCookie { }]; self.solana - .process_transaction(&instructions, Some(&[authority])) + .process_transaction(&instructions, Some(&[voter_authority])) .await } @@ -302,7 +317,6 @@ impl AddinCookie { voting_mint: &VotingMintConfigCookie, deposit_entry_index: u8, lockup_kind: LockupKind, - start_ts: Option, period: LockupPeriod, ) -> std::result::Result<(), BanksClientError> { let vault = voter.vault_address(voting_mint); @@ -311,7 +325,6 @@ impl AddinCookie { &voter_stake_registry::instruction::CreateDepositEntry { deposit_entry_index, kind: lockup_kind, - start_ts, period, }, ); @@ -353,9 +366,6 @@ impl AddinCookie { token_address: Pubkey, deposit_entry_index: u8, amount: u64, - reward_pool: &Pubkey, - deposit_mining: &Pubkey, - rewards_program: &Pubkey, ) -> std::result::Result<(), BanksClientError> { let vault = voter.vault_address(voting_mint); @@ -363,9 +373,6 @@ impl AddinCookie { anchor_lang::InstructionData::data(&voter_stake_registry::instruction::Deposit { deposit_entry_index, amount, - realm_governing_mint_pubkey: registrar.realm_governing_token_mint_pubkey, - registrar_bump: registrar.registrar_bump, - realm_pubkey: registrar.realm_pubkey, }); let accounts = anchor_lang::ToAccountMetas::to_account_metas( @@ -376,9 +383,6 @@ impl AddinCookie { deposit_token: token_address, deposit_authority: deposit_authority.pubkey(), token_program: spl_token::id(), - reward_pool: *reward_pool, - deposit_mining: *deposit_mining, - rewards_program: *rewards_program, }, None, ); diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 67fcabae..a5d41e4e 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,153 +1,165 @@ -use anchor_spl::token::TokenAccount; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -mod program_test; -#[tokio::test] -async fn test_all_deposits() -> Result<(), TransportError> { - let context = TestContext::new().await; - let addin = &context.addin; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let voter_authority = &context.users[1].key; - let voter_mngo = context.users[1].token_accounts[0]; - let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) - .await; - - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - let mngo_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 1.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - None, - ) - .await; - - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - - let voter = addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - for i in 0..32 { - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - i, - LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, - ) - .await - .unwrap(); - addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - i, - 12000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .unwrap(); - } - - // advance time, to be in the middle of all deposit lockups - advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 45 * 86400).await; - - // the two most expensive calls which scale with number of deposits - // are update_voter_weight_record and withdraw - both compute the vote weight - - let vwr = addin - .update_voter_weight_record(®istrar, &voter) - .await - .unwrap(); - assert_eq!(vwr.voter_weight, 12000 * 32); - - advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 50 * 86400).await; - - context - .addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) - .await - .unwrap(); - - advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; - - // make sure withdrawing works with all deposits filled - addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 1000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .unwrap(); - - // logging can take a lot of cu/mem - addin.log_voter_info(®istrar, &voter, 0).await; - - Ok(()) -} +// use anchor_spl::token::TokenAccount; +// use program_test::*; +// use solana_program_test::*; +// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +// use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +// mod program_test; +// #[tokio::test] +// async fn test_all_deposits() -> Result<(), TransportError> { +// let context = TestContext::new().await; +// let addin = &context.addin; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let voter_authority = &context.users[1].key; +// let voter_mngo = context.users[1].token_accounts[0]; +// let token_owner_record = realm +// .create_token_owner_record(voter_authority.pubkey(), payer) +// .await; + +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// let mngo_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 1.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; + +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); + +// let voter = addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// for i in 0..32 { +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// i, +// LockupKind::Constant, +// LockupPeriod::ThreeMonths, +// ) +// .await +// .unwrap(); +// addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// i, +// 12000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); + +// addin.lock_tokens( +// ®istrar, +// &voter, +// authority, +// source_deposit_entry_index, +// target_deposit_entry_index, +// amount, +// realm_governing_mint_pubkey, +// realm_pubkey, +// &deposit_mining, +// rewards_program, +// ) +// } + +// // advance time, to be in the middle of all deposit lockups +// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 45 * 86400).await; + +// // the two most expensive calls which scale with number of deposits +// // are update_voter_weight_record and withdraw - both compute the vote weight + +// let vwr = addin +// .update_voter_weight_record(®istrar, &voter) +// .await +// .unwrap(); +// assert_eq!(vwr.voter_weight, 12000 * 32); + +// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 50 * 86400).await; + +// context +// .addin +// .unlock_tokens(®istrar, &voter, voter_authority, 0) +// .await +// .unwrap(); + +// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; + +// // make sure withdrawing works with all deposits filled +// addin +// .withdraw( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// 0, +// 1000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); + +// // logging can take a lot of cu/mem +// addin.log_voter_info(®istrar, &voter, 0).await; + +// Ok(()) +// } diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 220bb7f7..3cc2db42 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -113,8 +113,20 @@ async fn test_basic() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, LockupPeriod::ThreeMonths, ) .await?; @@ -129,9 +141,22 @@ async fn test_basic() -> Result<(), TransportError> { reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + + context + .addin + .lock_tokens( + ®istrar, + &voter, + deposit_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; @@ -144,7 +169,7 @@ async fn test_basic() -> Result<(), TransportError> { .vault_balance(&context.solana, &voter) .await; assert_eq!(vault_after_deposit, 10000); - let balance_after_deposit = voter.deposit_amount(&context.solana, 0).await; + let balance_after_deposit = voter.deposit_amount(&context.solana, 1).await; assert_eq!(balance_after_deposit, 10000); context @@ -154,7 +179,7 @@ async fn test_basic() -> Result<(), TransportError> { context .addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 1) .await .unwrap(); @@ -171,7 +196,7 @@ async fn test_basic() -> Result<(), TransportError> { &mngo_voting_mint, deposit_authority, reference_account, - 0, + 1, 10000, &rewards_pool, &deposit_mining, diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 23e1bbca..d3c23f03 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -114,8 +114,19 @@ async fn successeful_claim() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, LockupPeriod::ThreeMonths, ) .await?; @@ -130,9 +141,22 @@ async fn successeful_claim() -> Result<(), TransportError> { depositer_token_account, 0, 10000, - &rewards_pool, + ) + .await?; + + context + .addin + .lock_tokens( + ®istrar, + &voter, + deposit_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 7ba685cf..44226fb5 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -124,32 +124,29 @@ async fn test_deposit_constant() -> Result<(), TransportError> { depot_id, ) }; - let withdraw = |amount: u64| { + let withdraw = |amount: u64, d_entry_index: u8| { addin.withdraw( ®istrar, &voter, &mngo_voting_mint, voter_authority, reference_account, - 0, + d_entry_index, amount, &rewards_pool, &deposit_mining, &context.rewards.program_id, ) }; - let deposit = |amount: u64| { + let deposit = |amount: u64, d_entry_index: u8| { addin.deposit( ®istrar, &voter, &mngo_voting_mint, voter_authority, reference_account, - 0, + d_entry_index, amount, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, ) }; @@ -166,20 +163,45 @@ async fn test_deposit_constant() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, LockupPeriod::ThreeMonths, ) .await .unwrap(); - deposit(10_000).await.unwrap(); + deposit(10_000, 0).await.unwrap(); + addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; - let after_deposit = get_balances(0).await; + let after_deposit = get_balances(1).await; assert_eq!(token, after_deposit.token + after_deposit.vault); assert_eq!(after_deposit.voter_weight, after_deposit.vault); // unchanged assert_eq!(after_deposit.vault, 10_000); assert_eq!(after_deposit.deposit, 10_000); - withdraw(1).await.expect_err("all locked up"); + withdraw(1, 1).await.expect_err("all locked up"); // advance to day 95. Just to be sure withdraw isn't possible without unlocking first // even at lockup period + cooldown period (90 + 5 respectively in that case) @@ -190,10 +212,10 @@ async fn test_deposit_constant() -> Result<(), TransportError> { // request unlock addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) + .unlock_tokens(®istrar, &voter, voter_authority, 1) .await .unwrap(); - withdraw(10_000) + withdraw(10_000, 1) .await .expect_err("Cooldown still not passed"); @@ -204,7 +226,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .await; // request claim && withdraw - withdraw(10_000).await.unwrap(); + withdraw(10_000, 1).await.unwrap(); Ok(()) } @@ -294,18 +316,15 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { &context.rewards.program_id, ) }; - let deposit = |amount: u64| { + let deposit = |amount: u64, d_entry_index: u8| { addin.deposit( ®istrar, &voter, &mngo_voting_mint, voter_authority, reference_account, - 0, + d_entry_index, amount, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, ) }; @@ -317,12 +336,37 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { &mngo_voting_mint, 0, LockupKind::Constant, - None, LockupPeriod::ThreeMonths, ) .await .unwrap(); - deposit(10000).await.unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::ThreeMonths, + ) + .await + .unwrap(); + deposit(10000, 0).await.unwrap(); + addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; // advance to 100 days addin diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 5b360327..618c7086 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -169,9 +169,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { reference_account, deposit_id, amount, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, ) }; // test deposit and withdraw @@ -188,7 +185,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { &mngo_voting_mint, 0, LockupKind::None, - None, LockupPeriod::None, ) .await @@ -210,7 +206,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { &mngo_voting_mint, 1, LockupKind::None, - None, LockupPeriod::None, ) .await @@ -282,7 +277,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { &mngo_voting_mint, 5, LockupKind::None, - None, LockupPeriod::None, ) .await @@ -296,9 +290,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { context.users[2].token_accounts[0], 5, 1000, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, ) .await .unwrap(); @@ -325,7 +316,6 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { &mngo_voting_mint, 0, LockupKind::None, - None, LockupPeriod::None, ) .await diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 1c5aa45a..c3e7ab68 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -105,8 +105,19 @@ async fn restake_from_flex() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, LockupPeriod::OneYear, ) .await?; @@ -120,9 +131,21 @@ async fn restake_from_flex() -> Result<(), TransportError> { reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; advance_clock_by_ts( @@ -139,7 +162,7 @@ async fn restake_from_flex() -> Result<(), TransportError> { &mngo_voting_mint, voter_authority, reference_account, - 0, + 1, LockupPeriod::OneYear, 0, &rewards_pool, @@ -151,7 +174,7 @@ async fn restake_from_flex() -> Result<(), TransportError> { let vault_balance = mngo_voting_mint .vault_balance(&context.solana, &voter) .await; - let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; assert_eq!(vault_balance, 10000); assert_eq!(deposit_amount, 10000); @@ -256,9 +279,20 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, + LockupPeriod::OneYear, ) .await?; context @@ -271,9 +305,21 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; advance_clock_by_ts( @@ -290,7 +336,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { &mngo_voting_mint, voter_authority, reference_account, - 0, + 1, LockupPeriod::ThreeMonths, 0, &rewards_pool, @@ -302,7 +348,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { let vault_balance = mngo_voting_mint .vault_balance(&context.solana, &voter) .await; - let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; assert_eq!(vault_balance, 10000); assert_eq!(deposit_amount, 10000); @@ -407,9 +453,20 @@ async fn restake_from_three_months_deposit_with_top_up() -> Result<(), Transport voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, + LockupPeriod::OneYear, ) .await?; context @@ -422,14 +479,26 @@ async fn restake_from_three_months_deposit_with_top_up() -> Result<(), Transport reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; advance_clock_by_ts( &mut context.solana.context.borrow_mut(), - (SECONDS_PER_DAY * 30) as i64, + (SECONDS_PER_DAY * 365) as i64, ) .await; @@ -441,7 +510,7 @@ async fn restake_from_three_months_deposit_with_top_up() -> Result<(), Transport &mngo_voting_mint, voter_authority, reference_account, - 0, + 1, LockupPeriod::ThreeMonths, 500, &rewards_pool, @@ -453,7 +522,7 @@ async fn restake_from_three_months_deposit_with_top_up() -> Result<(), Transport let vault_balance = mngo_voting_mint .vault_balance(&context.solana, &voter) .await; - let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; assert_eq!(vault_balance, 10500); assert_eq!(deposit_amount, 10500); @@ -558,9 +627,20 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, + LockupPeriod::OneYear, ) .await?; context @@ -573,14 +653,26 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; advance_clock_by_ts( &mut context.solana.context.borrow_mut(), - (SECONDS_PER_DAY * 100) as i64, + (SECONDS_PER_DAY * 365) as i64, ) .await; @@ -592,7 +684,7 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { &mngo_voting_mint, voter_authority, reference_account, - 0, + 1, LockupPeriod::ThreeMonths, 500, &rewards_pool, @@ -601,7 +693,7 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { ) .await?; - let deposit_amount = voter.deposit_amount(&context.solana, 0).await; + let deposit_amount = voter.deposit_amount(&context.solana, 1).await; assert_eq!(deposit_amount, 10500); @@ -697,6 +789,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let reference_account = context.users[1].token_accounts[0]; context .addin .create_deposit_entry( @@ -705,9 +798,20 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, + LockupPeriod::OneYear, ) .await?; context @@ -720,9 +824,21 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { reference_account, 0, 10000, - &rewards_pool, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, &deposit_mining, &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, ) .await?; advance_clock_by_ts( @@ -739,7 +855,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { &mngo_voting_mint, voter_authority, reference_account, - 0, + 1, LockupPeriod::OneYear, 500, &rewards_pool, diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs index e66074d0..7ab2d62b 100644 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ b/programs/voter-stake-registry/tests/test_internal_transfers.rs @@ -1,364 +1,360 @@ -use anchor_spl::token::TokenAccount; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; -use std::{cell::RefCell, rc::Rc}; -use voter_stake_registry::state::Voter; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -const DEPOSIT_A: u8 = 0; -const DEPOSIT_B: u8 = 1; - -mod program_test; - -struct LockupData { - deposited: u64, -} - -impl LockupData { - pub fn new(_duration: u64, _time_left: u64, deposited: u64, _amount_unlocked: u64) -> Self { - Self { deposited } - } -} - -async fn get_lockup_data( - solana: &SolanaCookie, - voter: Pubkey, - index: u8, - time_offset: i64, -) -> LockupData { - let now = solana.get_clock().await.unix_timestamp + time_offset; - let voter = solana.get_account::(voter).await; - let d = voter.deposits[index as usize]; - let duration = d.lockup.period.to_secs(); - LockupData::new( - duration - d.lockup.seconds_left(now as u64), - duration, - d.amount_deposited_native, - d.amount_deposited_native, - ) -} - -#[tokio::test] -async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { - let context = TestContext::new().await; - let addin = &context.addin; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let deposit_authority = &context.users[1].key; - let token_owner_record = realm - .create_token_owner_record(deposit_authority.pubkey(), payer) - .await; - - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - let mngo_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, // dump values, they doen't matter - 1.0, // dump values, they doen't matter - 1.0, // dump values, they doen't matter - 1, // dump values, they doen't matter - None, // dump values, they doen't matter - None, // dump values, they doen't matter - ) - .await; - - // TODO: ??? voter_authority == deposit_authority ??? - let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - let voter = addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - let reference_account = context.users[1].token_accounts[0]; - let deposit = |index: u8, amount: u64| { - addin.deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - reference_account, - index, - amount, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - }; - - let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { - addin.internal_transfer_unlocked( - ®istrar, - &voter, - voter_authority, - source, - target, - amount, - ) - }; - let time_offset = Rc::new(RefCell::new(0i64)); - let lockup_status = - |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); - - // - // test transferring without any restrictions - // - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - DEPOSIT_A, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - deposit(DEPOSIT_A, 300).await.unwrap(); - - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - DEPOSIT_B, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - - // - // test transfering unlocked funds - // - - // successeful move - internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 150).await?; - // number is too hight because 300 - 150 < 300 - internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 300) - .await - .expect_err("amount too high"); - internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 10).await?; - - assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 160); - assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 140); - - Ok(()) -} - -#[tokio::test] -async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> { - let context = TestContext::new().await; - let addin = &context.addin; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let voter_authority = &context.users[1].key; - let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) - .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - let mngo_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, // dump values, they don't matter - 1.0, // dump values, they don't matter - 1.0, // dump values, they don't matter - 1, // dump values, they don't matter - None, // dump values, they don't matter - None, // dump values, they don't matter - ) - .await; - - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - let voter = addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - let reference_account = context.users[1].token_accounts[0]; - let deposit = |index: u8, amount: u64| { - addin.deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - reference_account, - index, - amount, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - }; - - let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { - addin.internal_transfer_unlocked( - ®istrar, - &voter, - voter_authority, - source, - target, - amount, - ) - }; - let time_offset = Rc::new(RefCell::new(0i64)); - let advance_time = |extra: u64| { - *time_offset.borrow_mut() += extra as i64; - addin.set_time_offset(®istrar, &realm_authority, *time_offset.borrow()) - }; - let lockup_status = - |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); - - let month = 24 * 60 * 60 * 30; - let day = 24 * 60 * 60; - - // - // test transferring without any restrictions - // - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - DEPOSIT_A, - LockupKind::Constant, - None, - LockupPeriod::ThreeMonths, - ) - .await - .unwrap(); - deposit(DEPOSIT_A, 300).await.unwrap(); - - addin - .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) - .await - .expect_err("try to unlock instanly should faild"); - - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - DEPOSIT_B, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - - internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) - .await - .expect_err("still locked"); - - // Advance slots to avoid caching of the UpdateVoterWeightRecord call - // TODO: Is this something that could be an issue on a live node? - context.solana.advance_clock_by_slots(2).await; - advance_time(month * 3 + 60 * 60 * 6).await; - - internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) - .await - .expect_err("time has passed, but unlock hasn't been requested"); - - // Advance slots to avoid caching of the UpdateVoterWeightRecord call - // TODO: Is this something that could be an issue on a live node? - context.solana.advance_clock_by_slots(2).await; - advance_time(month * 3 + 60 * 60 * 6).await; - addin - .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) - .await?; - - // advance to cool down - advance_time(day * 2).await; - // cooled down, must work - internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300).await?; - - assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 0); - assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 300); - Ok(()) -} +// use anchor_spl::token::TokenAccount; +// use program_test::*; +// use solana_program_test::*; +// use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; +// use std::{cell::RefCell, rc::Rc}; +// use voter_stake_registry::state::Voter; +// use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +// const DEPOSIT_A: u8 = 0; +// const DEPOSIT_B: u8 = 1; + +// mod program_test; + +// struct LockupData { +// deposited: u64, +// } + +// impl LockupData { +// pub fn new(_duration: u64, _time_left: u64, deposited: u64, _amount_unlocked: u64) -> Self { +// Self { deposited } +// } +// } + +// async fn get_lockup_data( +// solana: &SolanaCookie, +// voter: Pubkey, +// index: u8, +// time_offset: i64, +// ) -> LockupData { +// let now = solana.get_clock().await.unix_timestamp + time_offset; +// let voter = solana.get_account::(voter).await; +// let d = voter.deposits[index as usize]; +// let duration = d.lockup.period.to_secs(); +// LockupData::new( +// duration - d.lockup.seconds_left(now as u64), +// duration, +// d.amount_deposited_native, +// d.amount_deposited_native, +// ) +// } + +// #[tokio::test] +// async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { +// let context = TestContext::new().await; +// let addin = &context.addin; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let deposit_authority = &context.users[1].key; +// let token_owner_record = realm +// .create_token_owner_record(deposit_authority.pubkey(), payer) +// .await; + +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// let mngo_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, // dump values, they doen't matter +// 1.0, // dump values, they doen't matter +// 1.0, // dump values, they doen't matter +// 1, // dump values, they doen't matter +// None, // dump values, they doen't matter +// None, // dump values, they doen't matter +// ) +// .await; + +// // TODO: ??? voter_authority == deposit_authority ??? +// let voter_authority = deposit_authority; +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); +// let voter = addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// let reference_account = context.users[1].token_accounts[0]; +// let deposit = |index: u8, amount: u64| { +// addin.deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// reference_account, +// index, +// amount, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// }; + +// let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { +// addin.internal_transfer_unlocked( +// ®istrar, +// &voter, +// voter_authority, +// source, +// target, +// amount, +// ) +// }; +// let time_offset = Rc::new(RefCell::new(0i64)); +// let lockup_status = +// |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); + +// // +// // test transferring without any restrictions +// // +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// DEPOSIT_A, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); +// deposit(DEPOSIT_A, 300).await.unwrap(); + +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// DEPOSIT_B, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); + +// // +// // test transfering unlocked funds +// // + +// // successeful move +// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 150).await?; +// // number is too hight because 300 - 150 < 300 +// internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 300) +// .await +// .expect_err("amount too high"); +// internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 10).await?; + +// assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 160); +// assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 140); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> { +// let context = TestContext::new().await; +// let addin = &context.addin; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let voter_authority = &context.users[1].key; +// let token_owner_record = realm +// .create_token_owner_record(voter_authority.pubkey(), payer) +// .await; +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// let mngo_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, // dump values, they don't matter +// 1.0, // dump values, they don't matter +// 1.0, // dump values, they don't matter +// 1, // dump values, they don't matter +// None, // dump values, they don't matter +// None, // dump values, they don't matter +// ) +// .await; + +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); +// let voter = addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// let reference_account = context.users[1].token_accounts[0]; +// let deposit = |index: u8, amount: u64| { +// addin.deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// reference_account, +// index, +// amount, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// }; + +// let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { +// addin.internal_transfer_unlocked( +// ®istrar, +// &voter, +// voter_authority, +// source, +// target, +// amount, +// ) +// }; +// let time_offset = Rc::new(RefCell::new(0i64)); +// let advance_time = |extra: u64| { +// *time_offset.borrow_mut() += extra as i64; +// addin.set_time_offset(®istrar, &realm_authority, *time_offset.borrow()) +// }; +// let lockup_status = +// |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); + +// let month = 24 * 60 * 60 * 30; +// let day = 24 * 60 * 60; + +// // +// // test transferring without any restrictions +// // +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// DEPOSIT_A, +// LockupKind::Constant, +// LockupPeriod::ThreeMonths, +// ) +// .await +// .unwrap(); +// deposit(DEPOSIT_A, 300).await.unwrap(); + +// addin +// .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) +// .await +// .expect_err("try to unlock instanly should faild"); + +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// DEPOSIT_B, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); + +// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) +// .await +// .expect_err("still locked"); + +// // Advance slots to avoid caching of the UpdateVoterWeightRecord call +// // TODO: Is this something that could be an issue on a live node? +// context.solana.advance_clock_by_slots(2).await; +// advance_time(month * 3 + 60 * 60 * 6).await; + +// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) +// .await +// .expect_err("time has passed, but unlock hasn't been requested"); + +// // Advance slots to avoid caching of the UpdateVoterWeightRecord call +// // TODO: Is this something that could be an issue on a live node? +// context.solana.advance_clock_by_slots(2).await; +// advance_time(month * 3 + 60 * 60 * 6).await; +// addin +// .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) +// .await?; + +// // advance to cool down +// advance_time(day * 2).await; +// // cooled down, must work +// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300).await?; + +// assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 0); +// assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 300); +// Ok(()) +// } diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 8a017d12..b2da5eed 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,463 +1,460 @@ -use anchor_spl::token::TokenAccount; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -use program_test::*; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -mod program_test; - -#[tokio::test] -async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> { - let context = TestContext::new().await; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let deposit_authority = &context.users[1].key; - let token_owner_record = realm - .create_token_owner_record(deposit_authority.pubkey(), payer) - .await; - - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 10, - 0.0, - 0.0, - 1, - None, - None, - ) - .await; - let mngo_voting_mint = context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 1.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - None, - ) - .await; - - // TODO: ??? voter_authority == deposit_authority ??? - let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - - let voter = context - .addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - // test deposit and withdraw - let reference_account = context.users[1].token_accounts[0]; - context - .addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - 0, - LockupKind::Constant, - None, - LockupPeriod::OneYear, - ) - .await?; - context - .addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await?; - - context - .addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) - .await - .expect_err("fails because it's too early to unlock is invalid"); - context - .addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - &context.users[1].key, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .expect_err("fails because it's impossible to withdraw without unlock"); - - Ok(()) -} - -#[tokio::test] -async fn test_unlock_after_end_ts() -> Result<(), TransportError> { - let context = TestContext::new().await; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let voter_authority = &context.users[1].key; - let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) - .await; - - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 10, - 0.0, - 0.0, - 1, - None, - None, - ) - .await; - let mngo_voting_mint = context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 1.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - None, - ) - .await; - - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - - let voter = context - .addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - // test deposit and withdraw - let reference_account = context.users[1].token_accounts[0]; - context - .addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - 0, - LockupKind::Constant, - None, - LockupPeriod::OneYear, - ) - .await?; - context - .addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await?; - - // advance to 365 days - let secs_per_day = 24 * 60 * 60; - context - .addin - .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) - .await; - - // unlock is possible - context - .addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) - .await - .unwrap(); - - // unlocked, but cooldown hasn't passed yet - context - .addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - &context.users[1].key, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .expect_err("fails because cooldown is ongoing"); - - Ok(()) -} - -#[tokio::test] -async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), TransportError> { - let context = TestContext::new().await; - - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; - - let voter_authority = &context.users[1].key; - let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) - .await; - - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 10, - 0.0, - 0.0, - 1, - None, - None, - ) - .await; - let mngo_voting_mint = context - .addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 1.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - None, - ) - .await; - - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - - let voter = context - .addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; - - // test deposit and withdraw - let reference_account = context.users[1].token_accounts[0]; - context - .addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - 0, - LockupKind::Constant, - None, - LockupPeriod::OneYear, - ) - .await?; - context - .addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await?; - let secs_per_day = 24 * 60 * 60; - // advance to day 365 - context - .addin - .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) - .await; - - context - .addin - .unlock_tokens(®istrar, &voter, voter_authority, 0) - .await - .unwrap(); - - // advance to day 370 (one year + cooldown (5 days)) - context - .addin - .set_time_offset(®istrar, &realm_authority, 370 * secs_per_day) - .await; - - // withdraw must be successful - context - .addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - &context.users[1].key, - reference_account, - 0, - 10000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .unwrap(); - - Ok(()) -} +// use anchor_spl::token::TokenAccount; +// use solana_program_test::*; +// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +// use program_test::*; +// use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +// mod program_test; + +// #[tokio::test] +// async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> { +// let context = TestContext::new().await; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let deposit_authority = &context.users[1].key; +// let token_owner_record = realm +// .create_token_owner_record(deposit_authority.pubkey(), payer) +// .await; + +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 10, +// 0.0, +// 0.0, +// 1, +// None, +// None, +// ) +// .await; +// let mngo_voting_mint = context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 1.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; + +// // TODO: ??? voter_authority == deposit_authority ??? +// let voter_authority = deposit_authority; +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); + +// let voter = context +// .addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// // test deposit and withdraw +// let reference_account = context.users[1].token_accounts[0]; +// context +// .addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::Constant, +// LockupPeriod::OneYear, +// ) +// .await?; +// context +// .addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await?; + +// context +// .addin +// .unlock_tokens(®istrar, &voter, voter_authority, 0) +// .await +// .expect_err("fails because it's too early to unlock is invalid"); +// context +// .addin +// .withdraw( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// &context.users[1].key, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .expect_err("fails because it's impossible to withdraw without unlock"); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_unlock_after_end_ts() -> Result<(), TransportError> { +// let context = TestContext::new().await; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let voter_authority = &context.users[1].key; +// let token_owner_record = realm +// .create_token_owner_record(voter_authority.pubkey(), payer) +// .await; + +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 10, +// 0.0, +// 0.0, +// 1, +// None, +// None, +// ) +// .await; +// let mngo_voting_mint = context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 1.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; + +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); + +// let voter = context +// .addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// // test deposit and withdraw +// let reference_account = context.users[1].token_accounts[0]; +// context +// .addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::Constant, +// LockupPeriod::OneYear, +// ) +// .await?; +// context +// .addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await?; + +// // advance to 365 days +// let secs_per_day = 24 * 60 * 60; +// context +// .addin +// .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) +// .await; + +// // unlock is possible +// context +// .addin +// .unlock_tokens(®istrar, &voter, voter_authority, 0) +// .await +// .unwrap(); + +// // unlocked, but cooldown hasn't passed yet +// context +// .addin +// .withdraw( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// &context.users[1].key, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .expect_err("fails because cooldown is ongoing"); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), TransportError> { +// let context = TestContext::new().await; + +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; + +// let voter_authority = &context.users[1].key; +// let token_owner_record = realm +// .create_token_owner_record(voter_authority.pubkey(), payer) +// .await; + +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 10, +// 0.0, +// 0.0, +// 1, +// None, +// None, +// ) +// .await; +// let mngo_voting_mint = context +// .addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 1.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; + +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); + +// let voter = context +// .addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; + +// // test deposit and withdraw +// let reference_account = context.users[1].token_accounts[0]; +// context +// .addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::Constant, +// LockupPeriod::OneYear, +// ) +// .await?; +// context +// .addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await?; +// let secs_per_day = 24 * 60 * 60; +// // advance to day 365 +// context +// .addin +// .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) +// .await; + +// context +// .addin +// .unlock_tokens(®istrar, &voter, voter_authority, 0) +// .await +// .unwrap(); + +// // advance to day 370 (one year + cooldown (5 days)) +// context +// .addin +// .set_time_offset(®istrar, &realm_authority, 370 * secs_per_day) +// .await; + +// // withdraw must be successful +// context +// .addin +// .withdraw( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// &context.users[1].key, +// reference_account, +// 0, +// 10000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); + +// Ok(()) +// } diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 20f8133e..9da9fa3d 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,162 +1,161 @@ -use anchor_spl::token::TokenAccount; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; +// use anchor_spl::token::TokenAccount; +// use program_test::*; +// use solana_program_test::*; +// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +// use voter_stake_registry::state::{LockupKind, LockupPeriod}; -mod program_test; +// mod program_test; -fn deserialize_event(event: &str) -> Option { - let data = base64::decode(event).ok()?; - if data.len() < 8 || data[0..8] != T::discriminator() { - return None; - } - T::try_from_slice(&data[8..]).ok() -} +// fn deserialize_event(event: &str) -> Option { +// let data = base64::decode(event).ok()?; +// if data.len() < 8 || data[0..8] != T::discriminator() { +// return None; +// } +// T::try_from_slice(&data[8..]).ok() +// } -#[tokio::test] -async fn test_print_event() -> Result<(), TransportError> { - println!( - "{:#?}", - deserialize_event::( - "LP4gbyknBZQAABhzAQAAAAAAGHMBAAAAAAAYcwEAAAAAAAEAAAAAAAAAAAGK6hx3fgEAAAA=" - ) - .ok_or(()) - ); - Ok(()) -} +// #[tokio::test] +// async fn test_print_event() -> Result<(), TransportError> { +// println!( +// "{:#?}", +// deserialize_event::( +// "LP4gbyknBZQAABhzAQAAAAAAGHMBAAAAAAAYcwEAAAAAAAEAAAAAAAAAAAGK6hx3fgEAAAA=" +// ) +// .ok_or(()) +// ); +// Ok(()) +// } -#[tokio::test] -async fn test_log_voter_info() -> Result<(), TransportError> { - let context = TestContext::new().await; - let addin = &context.addin; +// #[tokio::test] +// async fn test_log_voter_info() -> Result<(), TransportError> { +// let context = TestContext::new().await; +// let addin = &context.addin; - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; - let deposit_authority = &context.users[1].key; - let voter_mngo = context.users[1].token_accounts[0]; - let token_owner_record = realm - .create_token_owner_record(deposit_authority.pubkey(), payer) - .await; +// let deposit_authority = &context.users[1].key; +// let voter_mngo = context.users[1].token_accounts[0]; +// let token_owner_record = realm +// .create_token_owner_record(deposit_authority.pubkey(), payer) +// .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - let mngo_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 1.0, - 1.0, - 365 * 24 * 60 * 60, - None, - None, - ) - .await; +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// let mngo_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 1.0, +// 1.0, +// 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; - // TODO: ??? voter_authority == deposit_authority ??? - let voter_authority = deposit_authority; - let deposit_mining = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); +// // TODO: ??? voter_authority == deposit_authority ??? +// let voter_authority = deposit_authority; +// let deposit_mining = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); - let voter = addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await; +// let voter = addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await; - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - 0, - LockupKind::Constant, - None, - LockupPeriod::OneYear, - ) - .await - .unwrap(); - addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 12000, - &rewards_pool, - &deposit_mining, - &context.rewards.program_id, - ) - .await - .unwrap(); +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::Constant, +// LockupPeriod::OneYear, +// ) +// .await +// .unwrap(); +// addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// 0, +// 12000, +// &rewards_pool, +// &deposit_mining, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - // advance time to one month ahead - addin - .set_time_offset(®istrar, &realm_authority, 365 * 24 * 60 * 60 / 12) - .await; - context.solana.advance_clock_by_slots(2).await; +// // advance time to one month ahead +// addin +// .set_time_offset(®istrar, &realm_authority, 365 * 24 * 60 * 60 / 12) +// .await; +// context.solana.advance_clock_by_slots(2).await; - addin.log_voter_info(®istrar, &voter, 0).await; - let data_log = context.solana.program_output().data; - assert_eq!(data_log.len(), 2); +// addin.log_voter_info(®istrar, &voter, 0).await; +// let data_log = context.solana.program_output().data; +// assert_eq!(data_log.len(), 2); - let voter_event = - deserialize_event::(&data_log[0]).unwrap(); - assert_eq!(voter_event.voting_power_baseline, 12000); - assert_eq!(voter_event.voting_power, 12000); +// let voter_event = +// deserialize_event::(&data_log[0]).unwrap(); +// assert_eq!(voter_event.voting_power_baseline, 12000); +// assert_eq!(voter_event.voting_power, 12000); - let deposit_event = - deserialize_event::(&data_log[1]).unwrap(); - assert_eq!(deposit_event.deposit_entry_index, 0); - assert_eq!(deposit_event.voting_mint_config_index, 0); - assert_eq!(deposit_event.unlocked, 0); - assert_eq!(deposit_event.voting_power, voter_event.voting_power); - assert_eq!( - deposit_event.voting_power_baseline, - voter_event.voting_power_baseline - ); - assert!(deposit_event.locking.is_some()); - let locking = deposit_event.locking.unwrap(); - assert!(locking.vesting.is_none()); - assert_eq!(locking.amount, 12000); +// let deposit_event = +// deserialize_event::(&data_log[1]).unwrap(); +// assert_eq!(deposit_event.deposit_entry_index, 0); +// assert_eq!(deposit_event.voting_mint_config_index, 0); +// assert_eq!(deposit_event.unlocked, 0); +// assert_eq!(deposit_event.voting_power, voter_event.voting_power); +// assert_eq!( +// deposit_event.voting_power_baseline, +// voter_event.voting_power_baseline +// ); +// assert!(deposit_event.locking.is_some()); +// let locking = deposit_event.locking.unwrap(); +// assert!(locking.vesting.is_none()); +// assert_eq!(locking.amount, 12000); - Ok(()) -} +// Ok(()) +// } diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 931d7630..f320b10c 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,325 +1,322 @@ -use anchor_spl::token::TokenAccount; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; +// use anchor_spl::token::TokenAccount; +// use program_test::*; +// use solana_program_test::*; +// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +// use voter_stake_registry::state::{LockupKind, LockupPeriod}; -mod program_test; -#[tokio::test] -async fn test_voting() -> Result<(), TransportError> { - let context = TestContext::new().await; - let addin = &context.addin; +// mod program_test; +// #[tokio::test] +// async fn test_voting() -> Result<(), TransportError> { +// let context = TestContext::new().await; +// let addin = &context.addin; - let payer = &context.users[0].key; - let realm_authority = Keypair::new(); - let realm = context - .governance - .create_realm( - "testrealm", - realm_authority.pubkey(), - &context.mints[0], - payer, - &context.addin.program_id, - ) - .await; +// let payer = &context.users[0].key; +// let realm_authority = Keypair::new(); +// let realm = context +// .governance +// .create_realm( +// "testrealm", +// realm_authority.pubkey(), +// &context.mints[0], +// payer, +// &context.addin.program_id, +// ) +// .await; - let voter_authority = &context.users[1].key; - let voter2_authority = &context.users[2].key; - let voter_mngo = context.users[1].token_accounts[0]; - let voter_usdc = context.users[1].token_accounts[1]; - let token_owner_record = realm - .create_token_owner_record(voter_authority.pubkey(), payer) - .await; - let token_owner_record2 = realm - .create_token_owner_record(voter2_authority.pubkey(), payer) - .await; +// let voter_authority = &context.users[1].key; +// let voter2_authority = &context.users[2].key; +// let voter_mngo = context.users[1].token_accounts[0]; +// let voter_usdc = context.users[1].token_accounts[1]; +// let token_owner_record = realm +// .create_token_owner_record(voter_authority.pubkey(), payer) +// .await; +// let token_owner_record2 = realm +// .create_token_owner_record(voter2_authority.pubkey(), payer) +// .await; - let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); - let distribution_authority = Keypair::new(); - let (registrar, rewards_pool) = context - .addin - .create_registrar( - &realm, - &realm_authority, - payer, - &fill_authority.pubkey(), - &distribution_authority.pubkey(), - &context.rewards.program_id, - ) - .await; - let mngo_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 0, - &context.mints[0], - 0, - 2.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - None, - ) - .await; - let usdc_voting_mint = addin - .configure_voting_mint( - ®istrar, - &realm_authority, - payer, - 1, - &context.mints[1], - 0, - 0.0, - 0.0, - 5 * 365 * 24 * 60 * 60, - None, - Some(&[context.mints[0].pubkey.unwrap()]), - ) - .await; +// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); +// let distribution_authority = Keypair::new(); +// let (registrar, rewards_pool) = context +// .addin +// .create_registrar( +// &realm, +// &realm_authority, +// payer, +// &fill_authority.pubkey(), +// &distribution_authority.pubkey(), +// &context.rewards.program_id, +// ) +// .await; +// let mngo_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 0, +// &context.mints[0], +// 0, +// 2.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// None, +// ) +// .await; +// let usdc_voting_mint = addin +// .configure_voting_mint( +// ®istrar, +// &realm_authority, +// payer, +// 1, +// &context.mints[1], +// 0, +// 0.0, +// 0.0, +// 5 * 365 * 24 * 60 * 60, +// None, +// Some(&[context.mints[0].pubkey.unwrap()]), +// ) +// .await; - let deposit_mining_voter = find_deposit_mining_addr( - &voter_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - let voter = addin - .create_voter( - ®istrar, - &token_owner_record, - voter_authority, - payer, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, - ) - .await; +// let deposit_mining_voter = find_deposit_mining_addr( +// &voter_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); +// let voter = addin +// .create_voter( +// ®istrar, +// &token_owner_record, +// voter_authority, +// payer, +// &rewards_pool, +// &deposit_mining_voter, +// &context.rewards.program_id, +// ) +// .await; - let deposit_mining_voter2 = find_deposit_mining_addr( - &voter2_authority.pubkey(), - &rewards_pool, - &context.rewards.program_id, - ); - let voter2 = addin - .create_voter( - ®istrar, - &token_owner_record2, - voter2_authority, - payer, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await; +// let deposit_mining_voter2 = find_deposit_mining_addr( +// &voter2_authority.pubkey(), +// &rewards_pool, +// &context.rewards.program_id, +// ); +// let voter2 = addin +// .create_voter( +// ®istrar, +// &token_owner_record2, +// voter2_authority, +// payer, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await; - let mint_governance = realm - .create_mint_governance( - context.mints[0].pubkey.unwrap(), - &context.mints[0].authority, - &voter, - voter_authority, - payer, - addin.update_voter_weight_record_instruction(®istrar, &voter), - ) - .await; +// let mint_governance = realm +// .create_mint_governance( +// context.mints[0].pubkey.unwrap(), +// &context.mints[0].authority, +// &voter, +// voter_authority, +// payer, +// addin.update_voter_weight_record_instruction(®istrar, &voter), +// ) +// .await; - addin - .create_deposit_entry( - ®istrar, - &voter, - voter_authority, - &mngo_voting_mint, - 0, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - addin - .deposit( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 1000, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, - ) - .await - .unwrap(); +// addin +// .create_deposit_entry( +// ®istrar, +// &voter, +// voter_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); +// addin +// .deposit( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// 0, +// 1000, +// &rewards_pool, +// &deposit_mining_voter, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - let proposal = realm - .create_proposal( - mint_governance.address, - voter_authority, - &voter, - payer, - addin.update_voter_weight_record_instruction(®istrar, &voter), - ) - .await - .unwrap(); +// let proposal = realm +// .create_proposal( +// mint_governance.address, +// voter_authority, +// &voter, +// payer, +// addin.update_voter_weight_record_instruction(®istrar, &voter), +// ) +// .await +// .unwrap(); - // having created a proposal, withdrawing is impossible - context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again - addin - .withdraw( - ®istrar, - &voter, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 1, - &rewards_pool, - &deposit_mining_voter, - &context.rewards.program_id, - ) - .await - .expect_err("could not withdraw"); +// // having created a proposal, withdrawing is impossible +// context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again +// addin +// .withdraw( +// ®istrar, +// &voter, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// 0, +// 1, +// &rewards_pool, +// &deposit_mining_voter, +// &context.rewards.program_id, +// ) +// .await +// .expect_err("could not withdraw"); - addin - .create_deposit_entry( - ®istrar, - &voter2, - voter2_authority, - &mngo_voting_mint, - 0, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - addin - .deposit( - ®istrar, - &voter2, - &mngo_voting_mint, - voter_authority, - voter_mngo, - 0, - 750, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await - .unwrap(); +// addin +// .create_deposit_entry( +// ®istrar, +// &voter2, +// voter2_authority, +// &mngo_voting_mint, +// 0, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); +// addin +// .deposit( +// ®istrar, +// &voter2, +// &mngo_voting_mint, +// voter_authority, +// voter_mngo, +// 0, +// 750, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - addin - .create_deposit_entry( - ®istrar, - &voter2, - voter2_authority, - &usdc_voting_mint, - 1, - LockupKind::None, - None, - LockupPeriod::None, - ) - .await - .unwrap(); - addin - .deposit( - ®istrar, - &voter2, - &usdc_voting_mint, - voter_authority, - voter_usdc, - 1, - 1000, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await - .unwrap(); +// addin +// .create_deposit_entry( +// ®istrar, +// &voter2, +// voter2_authority, +// &usdc_voting_mint, +// 1, +// LockupKind::None, +// LockupPeriod::None, +// ) +// .await +// .unwrap(); +// addin +// .deposit( +// ®istrar, +// &voter2, +// &usdc_voting_mint, +// voter_authority, +// voter_usdc, +// 1, +// 1000, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - realm - .cast_vote( - mint_governance.address, - &proposal, - &voter2, - voter2_authority, - payer, - addin.update_voter_weight_record_instruction(®istrar, &voter2), - ) - .await - .unwrap(); +// realm +// .cast_vote( +// mint_governance.address, +// &proposal, +// &voter2, +// voter2_authority, +// payer, +// addin.update_voter_weight_record_instruction(®istrar, &voter2), +// ) +// .await +// .unwrap(); - let proposal_data = context.solana.get_account_data(proposal.address).await; - let mut data_slice: &[u8] = &proposal_data; - let proposal_state: spl_governance::state::proposal::ProposalV2 = - anchor_lang::AnchorDeserialize::deserialize(&mut data_slice).unwrap(); - assert_eq!(proposal_state.options[0].vote_weight, 1750); - assert_eq!(proposal_state.deny_vote_weight.unwrap(), 0); +// let proposal_data = context.solana.get_account_data(proposal.address).await; +// let mut data_slice: &[u8] = &proposal_data; +// let proposal_state: spl_governance::state::proposal::ProposalV2 = +// anchor_lang::AnchorDeserialize::deserialize(&mut data_slice).unwrap(); +// assert_eq!(proposal_state.options[0].vote_weight, 1750); +// assert_eq!(proposal_state.deny_vote_weight.unwrap(), 0); - // having voted, the funds are now locked, withdrawing is impossible - context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again - addin - .withdraw( - ®istrar, - &voter2, - &mngo_voting_mint, - voter2_authority, - voter_mngo, - 0, - 1, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await - .expect_err("could not withdraw"); +// // having voted, the funds are now locked, withdrawing is impossible +// context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again +// addin +// .withdraw( +// ®istrar, +// &voter2, +// &mngo_voting_mint, +// voter2_authority, +// voter_mngo, +// 0, +// 1, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await +// .expect_err("could not withdraw"); - // but can withdraw USDC - addin - .withdraw( - ®istrar, - &voter2, - &usdc_voting_mint, - voter2_authority, - voter_usdc, - 1, - 1, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await - .unwrap(); +// // but can withdraw USDC +// addin +// .withdraw( +// ®istrar, +// &voter2, +// &usdc_voting_mint, +// voter2_authority, +// voter_usdc, +// 1, +// 1, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - realm - .relinquish_vote( - mint_governance.address, - &proposal, - voter2.token_owner_record, - voter2_authority, - payer.pubkey(), - ) - .await - .unwrap(); +// realm +// .relinquish_vote( +// mint_governance.address, +// &proposal, +// voter2.token_owner_record, +// voter2_authority, +// payer.pubkey(), +// ) +// .await +// .unwrap(); - // can withdraw again - addin - .withdraw( - ®istrar, - &voter2, - &mngo_voting_mint, - voter2_authority, - voter_mngo, - 0, - 750, - &rewards_pool, - &deposit_mining_voter2, - &context.rewards.program_id, - ) - .await - .unwrap(); +// // can withdraw again +// addin +// .withdraw( +// ®istrar, +// &voter2, +// &mngo_voting_mint, +// voter2_authority, +// voter_mngo, +// 0, +// 750, +// &rewards_pool, +// &deposit_mining_voter2, +// &context.rewards.program_id, +// ) +// .await +// .unwrap(); - Ok(()) -} +// Ok(()) +// } From 6b3f8870125127d2101ead10a4e12dfdd1a1e9c8 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 18:26:55 +0300 Subject: [PATCH 47/59] make tests green --- .../tests/test_all_deposits.rs | 341 +++--- .../tests/test_deposit_constant.rs | 4 +- .../tests/test_extend_deposit.rs | 7 +- .../tests/test_internal_transfers.rs | 360 ------- .../voter-stake-registry/tests/test_lockup.rs | 992 ++++++++++-------- .../tests/test_log_voter_info.rs | 348 +++--- .../voter-stake-registry/tests/test_voting.rs | 597 ++++++----- 7 files changed, 1195 insertions(+), 1454 deletions(-) delete mode 100644 programs/voter-stake-registry/tests/test_internal_transfers.rs diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index a5d41e4e..2e4fd954 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,165 +1,176 @@ -// use anchor_spl::token::TokenAccount; -// use program_test::*; -// use solana_program_test::*; -// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -// use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -// mod program_test; -// #[tokio::test] -// async fn test_all_deposits() -> Result<(), TransportError> { -// let context = TestContext::new().await; -// let addin = &context.addin; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let voter_authority = &context.users[1].key; -// let voter_mngo = context.users[1].token_accounts[0]; -// let token_owner_record = realm -// .create_token_owner_record(voter_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// let mngo_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 1.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; - -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); - -// let voter = addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// for i in 0..32 { -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// i, -// LockupKind::Constant, -// LockupPeriod::ThreeMonths, -// ) -// .await -// .unwrap(); -// addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// i, -// 12000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); - -// addin.lock_tokens( -// ®istrar, -// &voter, -// authority, -// source_deposit_entry_index, -// target_deposit_entry_index, -// amount, -// realm_governing_mint_pubkey, -// realm_pubkey, -// &deposit_mining, -// rewards_program, -// ) -// } - -// // advance time, to be in the middle of all deposit lockups -// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 45 * 86400).await; - -// // the two most expensive calls which scale with number of deposits -// // are update_voter_weight_record and withdraw - both compute the vote weight - -// let vwr = addin -// .update_voter_weight_record(®istrar, &voter) -// .await -// .unwrap(); -// assert_eq!(vwr.voter_weight, 12000 * 32); - -// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 50 * 86400).await; - -// context -// .addin -// .unlock_tokens(®istrar, &voter, voter_authority, 0) -// .await -// .unwrap(); - -// advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; - -// // make sure withdrawing works with all deposits filled -// addin -// .withdraw( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// 0, -// 1000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); - -// // logging can take a lot of cu/mem -// addin.log_voter_info(®istrar, &voter, 0).await; - -// Ok(()) -// } +use anchor_spl::token::TokenAccount; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +mod program_test; +#[tokio::test] +async fn test_all_deposits() -> Result<(), TransportError> { + let context = TestContext::new().await; + let addin = &context.addin; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let voter_mngo = context.users[1].token_accounts[0]; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + let mngo_voting_mint = addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 32000, + ) + .await + .unwrap(); + + for i in 1..32 { + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + i, + LockupKind::Constant, + LockupPeriod::ThreeMonths, + ) + .await + .unwrap(); + addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + i, + 1000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; + } + + // advance time, to be in the middle of all deposit lockups + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 45 * 86400).await; + + // the two most expensive calls which scale with number of deposits + // are update_voter_weight_record and withdraw - both compute the vote weight + + let vwr = addin + .update_voter_weight_record(®istrar, &voter) + .await + .unwrap(); + assert_eq!(vwr.voter_weight, 1000 * 32); + + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 50 * 86400).await; + + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 0) + .await + .unwrap(); + + advance_clock_by_ts(&mut context.solana.context.borrow_mut(), 5 * 86400).await; + + // make sure withdrawing works with all deposits filled + addin + .withdraw( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 1000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await + .unwrap(); + + // logging can take a lot of cu/mem + addin.log_voter_info(®istrar, &voter, 0).await; + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 44226fb5..f883e4ed 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -335,8 +335,8 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { voter_authority, &mngo_voting_mint, 0, - LockupKind::Constant, - LockupPeriod::ThreeMonths, + LockupKind::None, + LockupPeriod::None, ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index c3e7ab68..35bd9331 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -324,7 +324,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { .await?; advance_clock_by_ts( &mut context.solana.context.borrow_mut(), - (SECONDS_PER_DAY * 30) as i64, + (SECONDS_PER_DAY * 365) as i64, ) .await; @@ -357,7 +357,8 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { } #[tokio::test] -async fn restake_from_three_months_deposit_with_top_up() -> Result<(), TransportError> { +async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result<(), TransportError> +{ let context = TestContext::new().await; let payer = &context.users[0].key; @@ -811,7 +812,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { &mngo_voting_mint, 1, LockupKind::Constant, - LockupPeriod::OneYear, + LockupPeriod::ThreeMonths, ) .await?; context diff --git a/programs/voter-stake-registry/tests/test_internal_transfers.rs b/programs/voter-stake-registry/tests/test_internal_transfers.rs deleted file mode 100644 index 7ab2d62b..00000000 --- a/programs/voter-stake-registry/tests/test_internal_transfers.rs +++ /dev/null @@ -1,360 +0,0 @@ -// use anchor_spl::token::TokenAccount; -// use program_test::*; -// use solana_program_test::*; -// use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; -// use std::{cell::RefCell, rc::Rc}; -// use voter_stake_registry::state::Voter; -// use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -// const DEPOSIT_A: u8 = 0; -// const DEPOSIT_B: u8 = 1; - -// mod program_test; - -// struct LockupData { -// deposited: u64, -// } - -// impl LockupData { -// pub fn new(_duration: u64, _time_left: u64, deposited: u64, _amount_unlocked: u64) -> Self { -// Self { deposited } -// } -// } - -// async fn get_lockup_data( -// solana: &SolanaCookie, -// voter: Pubkey, -// index: u8, -// time_offset: i64, -// ) -> LockupData { -// let now = solana.get_clock().await.unix_timestamp + time_offset; -// let voter = solana.get_account::(voter).await; -// let d = voter.deposits[index as usize]; -// let duration = d.lockup.period.to_secs(); -// LockupData::new( -// duration - d.lockup.seconds_left(now as u64), -// duration, -// d.amount_deposited_native, -// d.amount_deposited_native, -// ) -// } - -// #[tokio::test] -// async fn test_internal_transfer_kind_of_none() -> Result<(), TransportError> { -// let context = TestContext::new().await; -// let addin = &context.addin; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let deposit_authority = &context.users[1].key; -// let token_owner_record = realm -// .create_token_owner_record(deposit_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// let mngo_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, // dump values, they doen't matter -// 1.0, // dump values, they doen't matter -// 1.0, // dump values, they doen't matter -// 1, // dump values, they doen't matter -// None, // dump values, they doen't matter -// None, // dump values, they doen't matter -// ) -// .await; - -// // TODO: ??? voter_authority == deposit_authority ??? -// let voter_authority = deposit_authority; -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); -// let voter = addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// let reference_account = context.users[1].token_accounts[0]; -// let deposit = |index: u8, amount: u64| { -// addin.deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// reference_account, -// index, -// amount, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// }; - -// let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { -// addin.internal_transfer_unlocked( -// ®istrar, -// &voter, -// voter_authority, -// source, -// target, -// amount, -// ) -// }; -// let time_offset = Rc::new(RefCell::new(0i64)); -// let lockup_status = -// |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); - -// // -// // test transferring without any restrictions -// // -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// DEPOSIT_A, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); -// deposit(DEPOSIT_A, 300).await.unwrap(); - -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// DEPOSIT_B, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); - -// // -// // test transfering unlocked funds -// // - -// // successeful move -// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 150).await?; -// // number is too hight because 300 - 150 < 300 -// internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 300) -// .await -// .expect_err("amount too high"); -// internal_transfer_unlocked(DEPOSIT_B, DEPOSIT_A, 10).await?; - -// assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 160); -// assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 140); - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_internal_transfer_kind_of_constant() -> Result<(), TransportError> { -// let context = TestContext::new().await; -// let addin = &context.addin; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let voter_authority = &context.users[1].key; -// let token_owner_record = realm -// .create_token_owner_record(voter_authority.pubkey(), payer) -// .await; -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// let mngo_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, // dump values, they don't matter -// 1.0, // dump values, they don't matter -// 1.0, // dump values, they don't matter -// 1, // dump values, they don't matter -// None, // dump values, they don't matter -// None, // dump values, they don't matter -// ) -// .await; - -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); -// let voter = addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// let reference_account = context.users[1].token_accounts[0]; -// let deposit = |index: u8, amount: u64| { -// addin.deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// reference_account, -// index, -// amount, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// }; - -// let internal_transfer_unlocked = |source: u8, target: u8, amount: u64| { -// addin.internal_transfer_unlocked( -// ®istrar, -// &voter, -// voter_authority, -// source, -// target, -// amount, -// ) -// }; -// let time_offset = Rc::new(RefCell::new(0i64)); -// let advance_time = |extra: u64| { -// *time_offset.borrow_mut() += extra as i64; -// addin.set_time_offset(®istrar, &realm_authority, *time_offset.borrow()) -// }; -// let lockup_status = -// |index: u8| get_lockup_data(&context.solana, voter.address, index, *time_offset.borrow()); - -// let month = 24 * 60 * 60 * 30; -// let day = 24 * 60 * 60; - -// // -// // test transferring without any restrictions -// // -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// DEPOSIT_A, -// LockupKind::Constant, -// LockupPeriod::ThreeMonths, -// ) -// .await -// .unwrap(); -// deposit(DEPOSIT_A, 300).await.unwrap(); - -// addin -// .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) -// .await -// .expect_err("try to unlock instanly should faild"); - -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// DEPOSIT_B, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); - -// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) -// .await -// .expect_err("still locked"); - -// // Advance slots to avoid caching of the UpdateVoterWeightRecord call -// // TODO: Is this something that could be an issue on a live node? -// context.solana.advance_clock_by_slots(2).await; -// advance_time(month * 3 + 60 * 60 * 6).await; - -// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300) -// .await -// .expect_err("time has passed, but unlock hasn't been requested"); - -// // Advance slots to avoid caching of the UpdateVoterWeightRecord call -// // TODO: Is this something that could be an issue on a live node? -// context.solana.advance_clock_by_slots(2).await; -// advance_time(month * 3 + 60 * 60 * 6).await; -// addin -// .unlock_tokens(®istrar, &voter, voter_authority, DEPOSIT_A) -// .await?; - -// // advance to cool down -// advance_time(day * 2).await; -// // cooled down, must work -// internal_transfer_unlocked(DEPOSIT_A, DEPOSIT_B, 300).await?; - -// assert_eq!(lockup_status(DEPOSIT_A).await.deposited, 0); -// assert_eq!(lockup_status(DEPOSIT_B).await.deposited, 300); -// Ok(()) -// } diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index b2da5eed..54a86451 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,460 +1,532 @@ -// use anchor_spl::token::TokenAccount; -// use solana_program_test::*; -// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -// use program_test::*; -// use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -// mod program_test; - -// #[tokio::test] -// async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> { -// let context = TestContext::new().await; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let deposit_authority = &context.users[1].key; -// let token_owner_record = realm -// .create_token_owner_record(deposit_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 10, -// 0.0, -// 0.0, -// 1, -// None, -// None, -// ) -// .await; -// let mngo_voting_mint = context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 1.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; - -// // TODO: ??? voter_authority == deposit_authority ??? -// let voter_authority = deposit_authority; -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); - -// let voter = context -// .addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// // test deposit and withdraw -// let reference_account = context.users[1].token_accounts[0]; -// context -// .addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::Constant, -// LockupPeriod::OneYear, -// ) -// .await?; -// context -// .addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await?; - -// context -// .addin -// .unlock_tokens(®istrar, &voter, voter_authority, 0) -// .await -// .expect_err("fails because it's too early to unlock is invalid"); -// context -// .addin -// .withdraw( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// &context.users[1].key, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .expect_err("fails because it's impossible to withdraw without unlock"); - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_unlock_after_end_ts() -> Result<(), TransportError> { -// let context = TestContext::new().await; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let voter_authority = &context.users[1].key; -// let token_owner_record = realm -// .create_token_owner_record(voter_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 10, -// 0.0, -// 0.0, -// 1, -// None, -// None, -// ) -// .await; -// let mngo_voting_mint = context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 1.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; - -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); - -// let voter = context -// .addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// // test deposit and withdraw -// let reference_account = context.users[1].token_accounts[0]; -// context -// .addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::Constant, -// LockupPeriod::OneYear, -// ) -// .await?; -// context -// .addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await?; - -// // advance to 365 days -// let secs_per_day = 24 * 60 * 60; -// context -// .addin -// .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) -// .await; - -// // unlock is possible -// context -// .addin -// .unlock_tokens(®istrar, &voter, voter_authority, 0) -// .await -// .unwrap(); - -// // unlocked, but cooldown hasn't passed yet -// context -// .addin -// .withdraw( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// &context.users[1].key, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .expect_err("fails because cooldown is ongoing"); - -// Ok(()) -// } - -// #[tokio::test] -// async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), TransportError> { -// let context = TestContext::new().await; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let voter_authority = &context.users[1].key; -// let token_owner_record = realm -// .create_token_owner_record(voter_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 10, -// 0.0, -// 0.0, -// 1, -// None, -// None, -// ) -// .await; -// let mngo_voting_mint = context -// .addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 1.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; - -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); - -// let voter = context -// .addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// // test deposit and withdraw -// let reference_account = context.users[1].token_accounts[0]; -// context -// .addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::Constant, -// LockupPeriod::OneYear, -// ) -// .await?; -// context -// .addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await?; -// let secs_per_day = 24 * 60 * 60; -// // advance to day 365 -// context -// .addin -// .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) -// .await; - -// context -// .addin -// .unlock_tokens(®istrar, &voter, voter_authority, 0) -// .await -// .unwrap(); - -// // advance to day 370 (one year + cooldown (5 days)) -// context -// .addin -// .set_time_offset(®istrar, &realm_authority, 370 * secs_per_day) -// .await; - -// // withdraw must be successful -// context -// .addin -// .withdraw( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// &context.users[1].key, -// reference_account, -// 0, -// 10000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); - -// Ok(()) -// } +use anchor_spl::token::TokenAccount; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; + +use program_test::*; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +mod program_test; + +#[tokio::test] +async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let deposit_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(deposit_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; + + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 1) + .await + .expect_err("fails because it's too early to unlock is invalid"); + context + .addin + .withdraw( + ®istrar, + &voter, + &mngo_voting_mint, + &context.users[1].key, + reference_account, + 1, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await + .expect_err("fails because it's impossible to withdraw without unlock"); + + Ok(()) +} + +#[tokio::test] +async fn test_unlock_after_end_ts() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; + + // advance to 365 days + let secs_per_day = 24 * 60 * 60; + context + .addin + .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) + .await; + + // unlock is possible + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 1) + .await + .unwrap(); + + // unlocked, but cooldown hasn't passed yet + context + .addin + .withdraw( + ®istrar, + &voter, + &mngo_voting_mint, + &context.users[1].key, + reference_account, + 1, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await + .expect_err("fails because cooldown is ongoing"); + + Ok(()) +} + +#[tokio::test] +async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), TransportError> { + let context = TestContext::new().await; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let voter_authority = &context.users[1].key; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 10, + 0.0, + 0.0, + 1, + None, + None, + ) + .await; + let mngo_voting_mint = context + .addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = context + .addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + // test deposit and withdraw + let reference_account = context.users[1].token_accounts[0]; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await?; + context + .addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await?; + context + .addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + reference_account, + 0, + 10000, + ) + .await?; + context + .addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 10000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; + let secs_per_day = 24 * 60 * 60; + // advance to day 365 + context + .addin + .set_time_offset(®istrar, &realm_authority, 365 * secs_per_day) + .await; + + context + .addin + .unlock_tokens(®istrar, &voter, voter_authority, 1) + .await + .unwrap(); + + // advance to day 370 (one year + cooldown (5 days)) + context + .addin + .set_time_offset(®istrar, &realm_authority, 370 * secs_per_day) + .await; + + // withdraw must be successful + context + .addin + .withdraw( + ®istrar, + &voter, + &mngo_voting_mint, + &context.users[1].key, + reference_account, + 1, + 10000, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await + .unwrap(); + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 9da9fa3d..1d6af812 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,161 +1,187 @@ -// use anchor_spl::token::TokenAccount; -// use program_test::*; -// use solana_program_test::*; -// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -// use voter_stake_registry::state::{LockupKind, LockupPeriod}; - -// mod program_test; - -// fn deserialize_event(event: &str) -> Option { -// let data = base64::decode(event).ok()?; -// if data.len() < 8 || data[0..8] != T::discriminator() { -// return None; -// } -// T::try_from_slice(&data[8..]).ok() -// } - -// #[tokio::test] -// async fn test_print_event() -> Result<(), TransportError> { -// println!( -// "{:#?}", -// deserialize_event::( -// "LP4gbyknBZQAABhzAQAAAAAAGHMBAAAAAAAYcwEAAAAAAAEAAAAAAAAAAAGK6hx3fgEAAAA=" -// ) -// .ok_or(()) -// ); -// Ok(()) -// } - -// #[tokio::test] -// async fn test_log_voter_info() -> Result<(), TransportError> { -// let context = TestContext::new().await; -// let addin = &context.addin; - -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; - -// let deposit_authority = &context.users[1].key; -// let voter_mngo = context.users[1].token_accounts[0]; -// let token_owner_record = realm -// .create_token_owner_record(deposit_authority.pubkey(), payer) -// .await; - -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// let mngo_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 1.0, -// 1.0, -// 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; - -// // TODO: ??? voter_authority == deposit_authority ??? -// let voter_authority = deposit_authority; -// let deposit_mining = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); - -// let voter = addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await; - -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::Constant, -// LockupPeriod::OneYear, -// ) -// .await -// .unwrap(); -// addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// 0, -// 12000, -// &rewards_pool, -// &deposit_mining, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); - -// // advance time to one month ahead -// addin -// .set_time_offset(®istrar, &realm_authority, 365 * 24 * 60 * 60 / 12) -// .await; -// context.solana.advance_clock_by_slots(2).await; - -// addin.log_voter_info(®istrar, &voter, 0).await; -// let data_log = context.solana.program_output().data; -// assert_eq!(data_log.len(), 2); - -// let voter_event = -// deserialize_event::(&data_log[0]).unwrap(); -// assert_eq!(voter_event.voting_power_baseline, 12000); -// assert_eq!(voter_event.voting_power, 12000); - -// let deposit_event = -// deserialize_event::(&data_log[1]).unwrap(); -// assert_eq!(deposit_event.deposit_entry_index, 0); -// assert_eq!(deposit_event.voting_mint_config_index, 0); -// assert_eq!(deposit_event.unlocked, 0); -// assert_eq!(deposit_event.voting_power, voter_event.voting_power); -// assert_eq!( -// deposit_event.voting_power_baseline, -// voter_event.voting_power_baseline -// ); -// assert!(deposit_event.locking.is_some()); -// let locking = deposit_event.locking.unwrap(); -// assert!(locking.vesting.is_none()); -// assert_eq!(locking.amount, 12000); - -// Ok(()) -// } +use anchor_spl::token::TokenAccount; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; + +mod program_test; + +fn deserialize_event(event: &str) -> Option { + let data = base64::decode(event).ok()?; + if data.len() < 8 || data[0..8] != T::discriminator() { + return None; + } + T::try_from_slice(&data[8..]).ok() +} + +#[tokio::test] +async fn test_print_event() -> Result<(), TransportError> { + println!( + "{:#?}", + deserialize_event::( + "LP4gbyknBZQAABhzAQAAAAAAGHMBAAAAAAAYcwEAAAAAAAEAAAAAAAAAAAGK6hx3fgEAAAA=" + ) + .ok_or(()) + ); + Ok(()) +} + +#[tokio::test] +async fn test_log_voter_info() -> Result<(), TransportError> { + let context = TestContext::new().await; + let addin = &context.addin; + + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; + + let deposit_authority = &context.users[1].key; + let voter_mngo = context.users[1].token_accounts[0]; + let token_owner_record = realm + .create_token_owner_record(deposit_authority.pubkey(), payer) + .await; + + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + let mngo_voting_mint = addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 1.0, + 1.0, + 365 * 24 * 60 * 60, + None, + None, + ) + .await; + + // TODO: ??? voter_authority == deposit_authority ??? + let voter_authority = deposit_authority; + let deposit_mining = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + + let voter = addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining, + &context.rewards.program_id, + ) + .await; + + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 1, + LockupKind::Constant, + LockupPeriod::OneYear, + ) + .await + .unwrap(); + addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 12000, + ) + .await + .unwrap(); + addin + .lock_tokens( + ®istrar, + &voter, + voter_authority, + &deposit_mining, + &context.rewards.program_id, + 0, + 1, + 12000, + mngo_voting_mint.mint.pubkey.unwrap(), + realm.realm, + ) + .await?; + + // advance time to one month ahead + addin + .set_time_offset(®istrar, &realm_authority, 365 * 24 * 60 * 60 / 12) + .await; + context.solana.advance_clock_by_slots(2).await; + + addin.log_voter_info(®istrar, &voter, 0).await; + let data_log = context.solana.program_output().data; + assert_eq!(data_log.len(), 3); + + let voter_event = + deserialize_event::(&data_log[0]).unwrap(); + assert_eq!(voter_event.voting_power_baseline, 12000); + assert_eq!(voter_event.voting_power, 12000); + + let deposit_event = + deserialize_event::(&data_log[1]).unwrap(); + assert_eq!(deposit_event.deposit_entry_index, 0); + assert_eq!(deposit_event.voting_mint_config_index, 0); + assert_eq!(deposit_event.unlocked, 0); + + let deposit_event = + deserialize_event::(&data_log[2]).unwrap(); + assert_eq!(deposit_event.voting_power, voter_event.voting_power); + assert_eq!( + deposit_event.voting_power_baseline, + voter_event.voting_power_baseline + ); + assert!(deposit_event.locking.is_some()); + let locking = deposit_event.locking.unwrap(); + assert!(locking.vesting.is_none()); + assert_eq!(locking.amount, 12000); + + Ok(()) +} diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index f320b10c..94055a9b 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,322 +1,313 @@ -// use anchor_spl::token::TokenAccount; -// use program_test::*; -// use solana_program_test::*; -// use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -// use voter_stake_registry::state::{LockupKind, LockupPeriod}; +use anchor_spl::token::TokenAccount; +use program_test::*; +use solana_program_test::*; +use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use voter_stake_registry::state::{LockupKind, LockupPeriod}; -// mod program_test; -// #[tokio::test] -// async fn test_voting() -> Result<(), TransportError> { -// let context = TestContext::new().await; -// let addin = &context.addin; +mod program_test; +#[tokio::test] +async fn test_voting() -> Result<(), TransportError> { + let context = TestContext::new().await; + let addin = &context.addin; -// let payer = &context.users[0].key; -// let realm_authority = Keypair::new(); -// let realm = context -// .governance -// .create_realm( -// "testrealm", -// realm_authority.pubkey(), -// &context.mints[0], -// payer, -// &context.addin.program_id, -// ) -// .await; + let payer = &context.users[0].key; + let realm_authority = Keypair::new(); + let realm = context + .governance + .create_realm( + "testrealm", + realm_authority.pubkey(), + &context.mints[0], + payer, + &context.addin.program_id, + ) + .await; -// let voter_authority = &context.users[1].key; -// let voter2_authority = &context.users[2].key; -// let voter_mngo = context.users[1].token_accounts[0]; -// let voter_usdc = context.users[1].token_accounts[1]; -// let token_owner_record = realm -// .create_token_owner_record(voter_authority.pubkey(), payer) -// .await; -// let token_owner_record2 = realm -// .create_token_owner_record(voter2_authority.pubkey(), payer) -// .await; + let voter_authority = &context.users[1].key; + let voter2_authority = &context.users[2].key; + let voter_mngo = context.users[1].token_accounts[0]; + let voter_usdc = context.users[1].token_accounts[1]; + let token_owner_record = realm + .create_token_owner_record(voter_authority.pubkey(), payer) + .await; + let token_owner_record2 = realm + .create_token_owner_record(voter2_authority.pubkey(), payer) + .await; -// let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); -// let distribution_authority = Keypair::new(); -// let (registrar, rewards_pool) = context -// .addin -// .create_registrar( -// &realm, -// &realm_authority, -// payer, -// &fill_authority.pubkey(), -// &distribution_authority.pubkey(), -// &context.rewards.program_id, -// ) -// .await; -// let mngo_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 0, -// &context.mints[0], -// 0, -// 2.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// None, -// ) -// .await; -// let usdc_voting_mint = addin -// .configure_voting_mint( -// ®istrar, -// &realm_authority, -// payer, -// 1, -// &context.mints[1], -// 0, -// 0.0, -// 0.0, -// 5 * 365 * 24 * 60 * 60, -// None, -// Some(&[context.mints[0].pubkey.unwrap()]), -// ) -// .await; + let fill_authority = Keypair::from_bytes(&context.users[3].key.to_bytes()).unwrap(); + let distribution_authority = Keypair::new(); + let (registrar, rewards_pool) = context + .addin + .create_registrar( + &realm, + &realm_authority, + payer, + &fill_authority.pubkey(), + &distribution_authority.pubkey(), + &context.rewards.program_id, + ) + .await; + let mngo_voting_mint = addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 0, + &context.mints[0], + 0, + 2.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + None, + ) + .await; + let usdc_voting_mint = addin + .configure_voting_mint( + ®istrar, + &realm_authority, + payer, + 1, + &context.mints[1], + 0, + 0.0, + 0.0, + 5 * 365 * 24 * 60 * 60, + None, + Some(&[context.mints[0].pubkey.unwrap()]), + ) + .await; -// let deposit_mining_voter = find_deposit_mining_addr( -// &voter_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); -// let voter = addin -// .create_voter( -// ®istrar, -// &token_owner_record, -// voter_authority, -// payer, -// &rewards_pool, -// &deposit_mining_voter, -// &context.rewards.program_id, -// ) -// .await; + let deposit_mining_voter = find_deposit_mining_addr( + &voter_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter = addin + .create_voter( + ®istrar, + &token_owner_record, + voter_authority, + payer, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, + ) + .await; -// let deposit_mining_voter2 = find_deposit_mining_addr( -// &voter2_authority.pubkey(), -// &rewards_pool, -// &context.rewards.program_id, -// ); -// let voter2 = addin -// .create_voter( -// ®istrar, -// &token_owner_record2, -// voter2_authority, -// payer, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await; + let deposit_mining_voter2 = find_deposit_mining_addr( + &voter2_authority.pubkey(), + &rewards_pool, + &context.rewards.program_id, + ); + let voter2 = addin + .create_voter( + ®istrar, + &token_owner_record2, + voter2_authority, + payer, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) + .await; -// let mint_governance = realm -// .create_mint_governance( -// context.mints[0].pubkey.unwrap(), -// &context.mints[0].authority, -// &voter, -// voter_authority, -// payer, -// addin.update_voter_weight_record_instruction(®istrar, &voter), -// ) -// .await; + let mint_governance = realm + .create_mint_governance( + context.mints[0].pubkey.unwrap(), + &context.mints[0].authority, + &voter, + voter_authority, + payer, + addin.update_voter_weight_record_instruction(®istrar, &voter), + ) + .await; -// addin -// .create_deposit_entry( -// ®istrar, -// &voter, -// voter_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); -// addin -// .deposit( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// 0, -// 1000, -// &rewards_pool, -// &deposit_mining_voter, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter, + voter_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .deposit( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 1000, + ) + .await + .unwrap(); -// let proposal = realm -// .create_proposal( -// mint_governance.address, -// voter_authority, -// &voter, -// payer, -// addin.update_voter_weight_record_instruction(®istrar, &voter), -// ) -// .await -// .unwrap(); + let proposal = realm + .create_proposal( + mint_governance.address, + voter_authority, + &voter, + payer, + addin.update_voter_weight_record_instruction(®istrar, &voter), + ) + .await + .unwrap(); -// // having created a proposal, withdrawing is impossible -// context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again -// addin -// .withdraw( -// ®istrar, -// &voter, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// 0, -// 1, -// &rewards_pool, -// &deposit_mining_voter, -// &context.rewards.program_id, -// ) -// .await -// .expect_err("could not withdraw"); + // having created a proposal, withdrawing is impossible + context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again + addin + .withdraw( + ®istrar, + &voter, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 1, + &rewards_pool, + &deposit_mining_voter, + &context.rewards.program_id, + ) + .await + .expect_err("could not withdraw"); -// addin -// .create_deposit_entry( -// ®istrar, -// &voter2, -// voter2_authority, -// &mngo_voting_mint, -// 0, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); -// addin -// .deposit( -// ®istrar, -// &voter2, -// &mngo_voting_mint, -// voter_authority, -// voter_mngo, -// 0, -// 750, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter2, + voter2_authority, + &mngo_voting_mint, + 0, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .deposit( + ®istrar, + &voter2, + &mngo_voting_mint, + voter_authority, + voter_mngo, + 0, + 750, + ) + .await + .unwrap(); -// addin -// .create_deposit_entry( -// ®istrar, -// &voter2, -// voter2_authority, -// &usdc_voting_mint, -// 1, -// LockupKind::None, -// LockupPeriod::None, -// ) -// .await -// .unwrap(); -// addin -// .deposit( -// ®istrar, -// &voter2, -// &usdc_voting_mint, -// voter_authority, -// voter_usdc, -// 1, -// 1000, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); + addin + .create_deposit_entry( + ®istrar, + &voter2, + voter2_authority, + &usdc_voting_mint, + 1, + LockupKind::None, + LockupPeriod::None, + ) + .await + .unwrap(); + addin + .deposit( + ®istrar, + &voter2, + &usdc_voting_mint, + voter_authority, + voter_usdc, + 1, + 1000, + ) + .await + .unwrap(); -// realm -// .cast_vote( -// mint_governance.address, -// &proposal, -// &voter2, -// voter2_authority, -// payer, -// addin.update_voter_weight_record_instruction(®istrar, &voter2), -// ) -// .await -// .unwrap(); + realm + .cast_vote( + mint_governance.address, + &proposal, + &voter2, + voter2_authority, + payer, + addin.update_voter_weight_record_instruction(®istrar, &voter2), + ) + .await + .unwrap(); -// let proposal_data = context.solana.get_account_data(proposal.address).await; -// let mut data_slice: &[u8] = &proposal_data; -// let proposal_state: spl_governance::state::proposal::ProposalV2 = -// anchor_lang::AnchorDeserialize::deserialize(&mut data_slice).unwrap(); -// assert_eq!(proposal_state.options[0].vote_weight, 1750); -// assert_eq!(proposal_state.deny_vote_weight.unwrap(), 0); + let proposal_data = context.solana.get_account_data(proposal.address).await; + let mut data_slice: &[u8] = &proposal_data; + let proposal_state: spl_governance::state::proposal::ProposalV2 = + anchor_lang::AnchorDeserialize::deserialize(&mut data_slice).unwrap(); + assert_eq!(proposal_state.options[0].vote_weight, 1750); + assert_eq!(proposal_state.deny_vote_weight.unwrap(), 0); -// // having voted, the funds are now locked, withdrawing is impossible -// context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again -// addin -// .withdraw( -// ®istrar, -// &voter2, -// &mngo_voting_mint, -// voter2_authority, -// voter_mngo, -// 0, -// 1, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await -// .expect_err("could not withdraw"); + // having voted, the funds are now locked, withdrawing is impossible + context.solana.advance_clock_by_slots(2).await; // avoid cache when sending same transaction again + addin + .withdraw( + ®istrar, + &voter2, + &mngo_voting_mint, + voter2_authority, + voter_mngo, + 0, + 1, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) + .await + .expect_err("could not withdraw"); -// // but can withdraw USDC -// addin -// .withdraw( -// ®istrar, -// &voter2, -// &usdc_voting_mint, -// voter2_authority, -// voter_usdc, -// 1, -// 1, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); + // but can withdraw USDC + addin + .withdraw( + ®istrar, + &voter2, + &usdc_voting_mint, + voter2_authority, + voter_usdc, + 1, + 1, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) + .await + .unwrap(); -// realm -// .relinquish_vote( -// mint_governance.address, -// &proposal, -// voter2.token_owner_record, -// voter2_authority, -// payer.pubkey(), -// ) -// .await -// .unwrap(); + realm + .relinquish_vote( + mint_governance.address, + &proposal, + voter2.token_owner_record, + voter2_authority, + payer.pubkey(), + ) + .await + .unwrap(); -// // can withdraw again -// addin -// .withdraw( -// ®istrar, -// &voter2, -// &mngo_voting_mint, -// voter2_authority, -// voter_mngo, -// 0, -// 750, -// &rewards_pool, -// &deposit_mining_voter2, -// &context.rewards.program_id, -// ) -// .await -// .unwrap(); + // can withdraw again + addin + .withdraw( + ®istrar, + &voter2, + &mngo_voting_mint, + voter2_authority, + voter_mngo, + 0, + 750, + &rewards_pool, + &deposit_mining_voter2, + &context.rewards.program_id, + ) + .await + .unwrap(); -// Ok(()) -// } + Ok(()) +} From 3f6b213687bf3f6e3aa3d1c5715d45337d11000e Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 18:28:54 +0300 Subject: [PATCH 48/59] update fixture --- .../tests/fixtures/mplx_rewards.so | Bin 300560 -> 296848 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index 949627439d6130a9b507b03c5108a495d79ed42b..99ab813e447f3112f898afbaebb942530d9e1a2e 100755 GIT binary patch delta 52239 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 delta 55547 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 EA9ImlUjP6A From 12a01ef209de1bc54b755ad381a620684c6c1059 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 18:30:07 +0300 Subject: [PATCH 49/59] update idl --- idl/voter_stake_registry.ts | 180 ++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 80 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 775ecf11..09955534 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -362,12 +362,6 @@ export type VoterStakeRegistry = { "defined": "LockupKind" } }, - { - "name": "startTs", - "type": { - "option": "u64" - } - }, { "name": "period", "type": { @@ -408,27 +402,6 @@ export type VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" - ] - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" - ] - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false } ], "args": [ @@ -439,18 +412,6 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, @@ -680,7 +641,7 @@ export type VoterStakeRegistry = { ] }, { - "name": "internalTransferUnlocked", + "name": "lockTokens", "accounts": [ { "name": "registrar", @@ -696,6 +657,27 @@ export type VoterStakeRegistry = { "name": "voterAuthority", "isMut": false, "isSigner": true + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"mining\", mining owner , reward_pool], reward_program)" + ] + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -710,6 +692,14 @@ export type VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -1046,10 +1036,20 @@ export type VoterStakeRegistry = { "fields": [ { "name": "lockup", + "docs": [ + "Locked state." + ], "type": { "defined": "Lockup" } }, + { + "name": "delegate", + "docs": [ + "Delegated staker" + ], + "type": "publicKey" + }, { "name": "amountDepositedNative", "docs": [ @@ -1704,6 +1704,16 @@ export type VoterStakeRegistry = { "code": 6042, "name": "CpiReturnDataIsAbsent", "msg": "Cpi call must return data, but data is absent" + }, + { + "code": 6043, + "name": "LockingIsForbidded", + "msg": "The source for the transfer only can be a deposit on DAO" + }, + { + "code": 6044, + "name": "DepositEntryIsOld", + "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" } ] }; @@ -2072,12 +2082,6 @@ export const IDL: VoterStakeRegistry = { "defined": "LockupKind" } }, - { - "name": "startTs", - "type": { - "option": "u64" - } - }, { "name": "period", "type": { @@ -2118,27 +2122,6 @@ export const IDL: VoterStakeRegistry = { "name": "tokenProgram", "isMut": false, "isSigner": false - }, - { - "name": "rewardPool", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" - ] - }, - { - "name": "depositMining", - "isMut": true, - "isSigner": false, - "docs": [ - "PDA([\"mining\", mining owner , reward_pool], reward_program)" - ] - }, - { - "name": "rewardsProgram", - "isMut": false, - "isSigner": false } ], "args": [ @@ -2149,18 +2132,6 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" - }, - { - "name": "registrarBump", - "type": "u8" - }, - { - "name": "realmGoverningMintPubkey", - "type": "publicKey" - }, - { - "name": "realmPubkey", - "type": "publicKey" } ] }, @@ -2390,7 +2361,7 @@ export const IDL: VoterStakeRegistry = { ] }, { - "name": "internalTransferUnlocked", + "name": "lockTokens", "accounts": [ { "name": "registrar", @@ -2406,6 +2377,27 @@ export const IDL: VoterStakeRegistry = { "name": "voterAuthority", "isMut": false, "isSigner": true + }, + { + "name": "rewardPool", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"reward_pool\", deposit_authority , fill_authority], reward_program)" + ] + }, + { + "name": "depositMining", + "isMut": true, + "isSigner": false, + "docs": [ + "PDA([\"mining\", mining owner , reward_pool], reward_program)" + ] + }, + { + "name": "rewardsProgram", + "isMut": false, + "isSigner": false } ], "args": [ @@ -2420,6 +2412,14 @@ export const IDL: VoterStakeRegistry = { { "name": "amount", "type": "u64" + }, + { + "name": "realmGoverningMintPubkey", + "type": "publicKey" + }, + { + "name": "realmPubkey", + "type": "publicKey" } ] }, @@ -2756,10 +2756,20 @@ export const IDL: VoterStakeRegistry = { "fields": [ { "name": "lockup", + "docs": [ + "Locked state." + ], "type": { "defined": "Lockup" } }, + { + "name": "delegate", + "docs": [ + "Delegated staker" + ], + "type": "publicKey" + }, { "name": "amountDepositedNative", "docs": [ @@ -3414,6 +3424,16 @@ export const IDL: VoterStakeRegistry = { "code": 6042, "name": "CpiReturnDataIsAbsent", "msg": "Cpi call must return data, but data is absent" + }, + { + "code": 6043, + "name": "LockingIsForbidded", + "msg": "The source for the transfer only can be a deposit on DAO" + }, + { + "code": 6044, + "name": "DepositEntryIsOld", + "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" } ] }; From 8c0e0bd7d1fa5bbabc701d55bd12f3da213bf0f1 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Tue, 18 Jun 2024 19:27:16 +0300 Subject: [PATCH 50/59] separate states && contract itself --- Cargo.lock | 11 + Cargo.toml | 1 + program-states/Cargo.lock | 2784 +++++++++++++++++ program-states/Cargo.toml | 20 + .../src/error.rs | 0 program-states/src/lib.rs | 10 + .../src/state/deposit_entry.rs | 3 +- .../src/state/lockup.rs | 3 - .../src/state/mod.rs | 0 .../src/state/registrar.rs | 1 - .../src/state/voter.rs | 1 - .../src/state/voting_mint_config.rs | 1 - programs/voter-stake-registry/Cargo.toml | 2 +- .../src/cpi_instructions.rs | 2 +- .../src/instructions/claim.rs | 2 +- .../src/instructions/close_deposit_entry.rs | 6 +- .../src/instructions/close_voter.rs | 5 +- .../src/instructions/configure_voting_mint.rs | 6 +- .../src/instructions/create_deposit_entry.rs | 5 +- .../src/instructions/create_registrar.rs | 4 +- .../src/instructions/create_voter.rs | 5 +- .../src/instructions/deposit.rs | 6 +- .../src/instructions/extend_deposit.rs | 7 +- .../src/instructions/lock_tokens.rs | 6 +- .../src/instructions/log_voter_info.rs | 5 +- .../src/instructions/unlock_tokens.rs | 19 +- .../update_voter_weight_record.rs | 2 +- .../src/instructions/withdraw.rs | 4 +- programs/voter-stake-registry/src/lib.rs | 7 +- programs/voter-stake-registry/src/voter.rs | 2 +- .../tests/program_test/addin.rs | 2 +- .../tests/program_test/rewards.rs | 2 +- .../tests/test_all_deposits.rs | 2 +- .../voter-stake-registry/tests/test_basic.rs | 2 +- .../voter-stake-registry/tests/test_claim.rs | 2 +- .../tests/test_deposit_constant.rs | 2 +- .../tests/test_deposit_no_locking.rs | 2 +- .../tests/test_extend_deposit.rs | 2 +- .../voter-stake-registry/tests/test_lockup.rs | 2 +- .../tests/test_log_voter_info.rs | 2 +- .../voter-stake-registry/tests/test_voting.rs | 3 +- 41 files changed, 2890 insertions(+), 63 deletions(-) create mode 100644 program-states/Cargo.lock create mode 100644 program-states/Cargo.toml rename {programs/voter-stake-registry => program-states}/src/error.rs (100%) create mode 100644 program-states/src/lib.rs rename {programs/voter-stake-registry => program-states}/src/state/deposit_entry.rs (98%) rename {programs/voter-stake-registry => program-states}/src/state/lockup.rs (98%) rename {programs/voter-stake-registry => program-states}/src/state/mod.rs (100%) rename {programs/voter-stake-registry => program-states}/src/state/registrar.rs (98%) rename {programs/voter-stake-registry => program-states}/src/state/voter.rs (98%) rename {programs/voter-stake-registry => program-states}/src/state/voting_mint_config.rs (99%) diff --git a/Cargo.lock b/Cargo.lock index 70c1c08e..a79731d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1968,6 +1968,16 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "mplx-staking-states" +version = "0.0.1" +dependencies = [ + "anchor-lang", + "anchor-spl", + "bytemuck", + "static_assertions", +] + [[package]] name = "nix" version = "0.24.3" @@ -4696,6 +4706,7 @@ dependencies = [ "bytemuck", "env_logger", "log", + "mplx-staking-states", "solana-program", "solana-program-test", "solana-sdk", diff --git a/Cargo.toml b/Cargo.toml index 9c65bed6..ae5a23fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "programs/*", + "program-states" # Drop everything below this line before running "anchor publish": # Otherwise the build will fail since Anchor uploads only parts of the source tree. ] diff --git a/program-states/Cargo.lock b/program-states/Cargo.lock new file mode 100644 index 00000000..dccf59f5 --- /dev/null +++ b/program-states/Cargo.lock @@ -0,0 +1,2784 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aead" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +dependencies = [ + "generic-array", +] + +[[package]] +name = "aes" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", + "opaque-debug", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589c637f0e68c877bbd59a4599bbe849cac8e5f3e4b5a3ebae8f528cd218dcdc" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom 0.2.14", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anchor-attribute-access-control" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf7d535e1381be3de2c0716c0a1c1e32ad9df1042cddcf7bc18d743569e53319" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2", + "quote", + "regex", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-account" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3bcd731f21048a032be27c7791701120e44f3f6371358fc4261a7f716283d29" +dependencies = [ + "anchor-syn", + "anyhow", + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-constant" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1be64a48e395fe00b8217287f226078be2cf32dae42fdf8a885b997945c3d28" +dependencies = [ + "anchor-syn", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-error" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38ea6713d1938c0da03656ff8a693b17dc0396da66d1ba320557f07e86eca0d4" +dependencies = [ + "anchor-syn", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-event" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401f11efb3644285685f8339829a9786d43ed7490bb1699f33c478d04d5a582" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-interface" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6700a6f5c888a9c33fe8afc0c64fd8575fa28d05446037306d0f96102ae4480" +dependencies = [ + "anchor-syn", + "anyhow", + "heck", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-program" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad769993b5266714e8939e47fbdede90e5c030333c7522d99a4d4748cf26712" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-attribute-state" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e677fae4a016a554acdd0e3b7f178d3acafaa7e7ffac6b8690cf4e171f1c116" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-derive-accounts" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340beef6809d1c3fcc7ae219153d981e95a8a277ff31985bd7050e32645dc9a8" +dependencies = [ + "anchor-syn", + "anyhow", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "anchor-lang" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "662ceafe667448ee4199a4be2ee83b6bb76da28566eee5cea05f96ab38255af8" +dependencies = [ + "anchor-attribute-access-control", + "anchor-attribute-account", + "anchor-attribute-constant", + "anchor-attribute-error", + "anchor-attribute-event", + "anchor-attribute-interface", + "anchor-attribute-program", + "anchor-attribute-state", + "anchor-derive-accounts", + "arrayref", + "base64 0.13.1", + "bincode", + "borsh 0.9.3", + "bytemuck", + "solana-program", + "thiserror", +] + +[[package]] +name = "anchor-spl" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f32390ce8356f54c0f0245ea156f8190717e37285b8bf4f406a613dc4b954cde" +dependencies = [ + "anchor-lang", + "solana-program", + "spl-associated-token-account", + "spl-token", +] + +[[package]] +name = "anchor-syn" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0418bcb5daac3b8cb1b60d8fdb1d468ca36f5509f31fb51179326fae1028fdcc" +dependencies = [ + "anyhow", + "bs58 0.3.1", + "heck", + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "serde", + "serde_json", + "sha2 0.9.9", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + +[[package]] +name = "arrayref" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "assert_matches" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" + +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cca6d3674597c30ddf2c587bf8d9d65c9a84d2326d941cc79c9842dfe0ef52" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "digest 0.10.7", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "borsh" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15bf3650200d8bffa99015595e10f1fbd17de07abbc25bb067da79e769939bfa" +dependencies = [ + "borsh-derive 0.9.3", + "hashbrown 0.11.2", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive 0.10.3", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0901fc8eb0aca4c83be0106d6f2db17d86a08dfc2c25f0e84464bf381158add6" +dependencies = [ + "borsh-derive 1.4.0", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6441c552f230375d18e3cc377677914d2ca2b0d36e52129fe15450a2dce46775" +dependencies = [ + "borsh-derive-internal 0.9.3", + "borsh-schema-derive-internal 0.9.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal 0.10.3", + "borsh-schema-derive-internal 0.10.3", + "proc-macro-crate 0.1.5", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51670c3aa053938b0ee3bd67c3817e471e626151131b934038e83c5bf8de48f5" +dependencies = [ + "once_cell", + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.60", + "syn_derive", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bs58" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" + +[[package]] +name = "bs58" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4da9a32f3fed317401fa3c862968128267c3106685286e15d5aaa3d7389c2f60" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +dependencies = [ + "jobserver", + "libc", + "once_cell", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.5", +] + +[[package]] +name = "cipher" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" +dependencies = [ + "generic-array", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "console_log" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89f72f65e8501878b8a004d5a1afb780987e2ce2b4532c562e367a72c57499f" +dependencies = [ + "log", + "web-sys", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "ctr" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.60", +] + +[[package]] +name = "darling_macro" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +dependencies = [ + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "ed25519-dalek-bip32" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2be62a4061b872c8c0873ee4fc6f101ce7b889d039f019c5fa2af471a59908" +dependencies = [ + "derivation-path", + "ed25519-dalek", + "hmac 0.12.1", + "sha2 0.10.8", +] + +[[package]] +name = "either" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" + +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "serde", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash 0.7.8", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.11", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.3", + "serde", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "jobserver" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +dependencies = [ + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "libsecp256k1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9d220bc1feda2ac231cb78c3d26f27676b8cf82c96971f7aeef3d0cf2797c73" +dependencies = [ + "arrayref", + "base64 0.12.3", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.7.3", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254", + "ark-ff", + "num-bigint", + "thiserror", +] + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core 0.6.4", + "zeroize", +] + +[[package]] +name = "mplx-staking-sdk" +version = "1.2.0" +dependencies = [ + "anchor-lang", + "anchor-spl", + "borsh 0.9.3", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "serde", + "serde_with 3.7.0", + "static_assertions", + "thiserror", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive 0.5.11", +] + +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive 0.7.2", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "pbkdf2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216eaa586a190f0a738f2f918511eecfa90f13295abec0e457cdebcceda80cbd" +dependencies = [ + "crypto-mac", +] + +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "polyval" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", + "yansi", +] + +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.14", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" + +[[package]] +name = "serde" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.198" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "serde", + "serde_with_macros 2.3.3", +] + +[[package]] +name = "serde_with" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" +dependencies = [ + "base64 0.21.7", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.2.6", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros 3.7.0", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "serde_with_macros" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer 0.9.0", + "digest 0.9.0", + "keccak", + "opaque-debug", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "signature" +version = "1.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "solana-frozen-abi" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b8177685ab2bc8cc8b3bf63aa1eaa0580d5af850ecefac323ca1c2473085d77" +dependencies = [ + "block-buffer 0.10.4", + "bs58 0.4.0", + "bv", + "either", + "generic-array", + "im", + "lazy_static", + "log", + "memmap2", + "rustc_version", + "serde", + "serde_bytes", + "serde_derive", + "sha2 0.10.8", + "solana-frozen-abi-macro", + "subtle", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a68241cad17b74c6034a68ba4890632d409a2c886e7bead9c1e1432befdb7c9" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.60", +] + +[[package]] +name = "solana-logger" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea560989ef67ba4a1a0fd62a248721f1aa5bac8fa5ede9ccf4fe9ee484ccadf" +dependencies = [ + "env_logger", + "lazy_static", + "log", +] + +[[package]] +name = "solana-program" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bddf573103c890b4ab8f9a6641d4f969d4148bce9a451c263f4a62afa949fae" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "base64 0.21.7", + "bincode", + "bitflags 2.5.0", + "blake3", + "borsh 0.10.3", + "borsh 0.9.3", + "borsh 1.4.0", + "bs58 0.4.0", + "bv", + "bytemuck", + "cc", + "console_error_panic_hook", + "console_log", + "curve25519-dalek", + "getrandom 0.2.14", + "itertools", + "js-sys", + "lazy_static", + "libc", + "libsecp256k1", + "light-poseidon", + "log", + "memoffset", + "num-bigint", + "num-derive 0.4.2", + "num-traits", + "parking_lot", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "sha2 0.10.8", + "sha3 0.10.8", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-macro", + "thiserror", + "tiny-bip39", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "solana-sdk" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08b24b06fa176209ddb2a2f8172a00b07e8a3b18229fbfc49f1eb3ce6ad11ff1" +dependencies = [ + "assert_matches", + "base64 0.21.7", + "bincode", + "bitflags 2.5.0", + "borsh 1.4.0", + "bs58 0.4.0", + "bytemuck", + "byteorder", + "chrono", + "derivation-path", + "digest 0.10.7", + "ed25519-dalek", + "ed25519-dalek-bip32", + "generic-array", + "hmac 0.12.1", + "itertools", + "js-sys", + "lazy_static", + "libsecp256k1", + "log", + "memmap2", + "num-derive 0.4.2", + "num-traits", + "num_enum 0.7.2", + "pbkdf2 0.11.0", + "qstring", + "qualifier_attr", + "rand 0.7.3", + "rand 0.8.5", + "rustc_version", + "rustversion", + "serde", + "serde_bytes", + "serde_derive", + "serde_json", + "serde_with 2.3.3", + "sha2 0.10.8", + "sha3 0.10.8", + "siphasher", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-logger", + "solana-program", + "solana-sdk-macro", + "thiserror", + "uriparse", + "wasm-bindgen", +] + +[[package]] +name = "solana-sdk-macro" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869483c05f18d37d4d95a08d9e05e00a4f76a8c8349aeedeee9ba2d013cbacde" +dependencies = [ + "bs58 0.4.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.60", +] + +[[package]] +name = "solana-zk-token-sdk" +version = "1.18.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "459c27f7b954798677d8243aa53b8080cfb314ecfecbf8889a5a65c91ad11fee" +dependencies = [ + "aes-gcm-siv", + "base64 0.21.7", + "bincode", + "bytemuck", + "byteorder", + "curve25519-dalek", + "getrandom 0.1.16", + "itertools", + "lazy_static", + "merlin", + "num-derive 0.4.2", + "num-traits", + "rand 0.7.3", + "serde", + "serde_json", + "sha3 0.9.1", + "solana-program", + "solana-sdk", + "subtle", + "thiserror", + "zeroize", +] + +[[package]] +name = "spl-associated-token-account" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978dba3bcbe88d0c2c58366c254d9ea41c5f73357e72fc0bdee4d6b5fc99c8f4" +dependencies = [ + "assert_matches", + "borsh 0.9.3", + "num-derive 0.3.3", + "num-traits", + "solana-program", + "spl-token", + "spl-token-2022", + "thiserror", +] + +[[package]] +name = "spl-memo" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0dc6f70db6bacea7ff25870b016a65ba1d1b6013536f08e4fd79a8f9005325" +dependencies = [ + "solana-program", +] + +[[package]] +name = "spl-token" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e85e168a785e82564160dcb87b2a8e04cee9bfd1f4d488c729d53d6a4bd300d" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.5.11", + "solana-program", + "thiserror", +] + +[[package]] +name = "spl-token-2022" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0043b590232c400bad5ee9eb983ced003d15163c4c5d56b090ac6d9a57457b47" +dependencies = [ + "arrayref", + "bytemuck", + "num-derive 0.3.3", + "num-traits", + "num_enum 0.5.11", + "solana-program", + "solana-zk-token-sdk", + "spl-memo", + "spl-token", + "thiserror", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-bip39" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc59cb9dfc85bb312c3a78fd6aa8a8582e310b0fa885d5bb877f6dcc601839d" +dependencies = [ + "anyhow", + "hmac 0.8.1", + "once_cell", + "pbkdf2 0.4.0", + "rand 0.7.3", + "rustc-hash", + "sha2 0.9.9", + "thiserror", + "unicode-normalization", + "wasm-bindgen", + "zeroize", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "universal-hash" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "zeroize" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", +] diff --git a/program-states/Cargo.toml b/program-states/Cargo.toml new file mode 100644 index 00000000..629449b7 --- /dev/null +++ b/program-states/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mplx-staking-states" +version = "0.0.1" +homepage = "https://github.com/blockworks-foundation/voter-stake-registry" +description = "MPLX staking program's state dependencies" +license = "GPL-3.0-or-later" +edition = "2021" +readme = "README.md" + +[lib] +crate-type = ["cdylib", "lib"] + +[features] +test-sbf = [] + +[dependencies] +anchor-lang = { version = "0.26.0", features = ["init-if-needed"] } +anchor-spl = { version = "0.26.0" } +static_assertions = "1.1" +bytemuck = "1.9.1" \ No newline at end of file diff --git a/programs/voter-stake-registry/src/error.rs b/program-states/src/error.rs similarity index 100% rename from programs/voter-stake-registry/src/error.rs rename to program-states/src/error.rs diff --git a/program-states/src/lib.rs b/program-states/src/lib.rs new file mode 100644 index 00000000..a5afd1e4 --- /dev/null +++ b/program-states/src/lib.rs @@ -0,0 +1,10 @@ +use anchor_lang::prelude::*; + +#[macro_use] +extern crate static_assertions; + +pub mod error; +pub mod state; + +// Requires by Anchor to declare accounts in crate +declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); diff --git a/programs/voter-stake-registry/src/state/deposit_entry.rs b/program-states/src/state/deposit_entry.rs similarity index 98% rename from programs/voter-stake-registry/src/state/deposit_entry.rs rename to program-states/src/state/deposit_entry.rs index 122616c3..89727e96 100644 --- a/programs/voter-stake-registry/src/state/deposit_entry.rs +++ b/program-states/src/state/deposit_entry.rs @@ -1,13 +1,12 @@ use crate::state::lockup::{Lockup, LockupKind}; use anchor_lang::prelude::*; -use static_assertions::const_assert; /// Bookkeeping for a single deposit for a given mint and lockup schedule. #[zero_copy] #[derive(Default)] pub struct DepositEntry { - /// Locked state. + // Locked state. pub lockup: Lockup, /// Delegated staker pub delegate: Pubkey, diff --git a/programs/voter-stake-registry/src/state/lockup.rs b/program-states/src/state/lockup.rs similarity index 98% rename from programs/voter-stake-registry/src/state/lockup.rs rename to program-states/src/state/lockup.rs index 9c5df8af..82d48ade 100644 --- a/programs/voter-stake-registry/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -1,6 +1,5 @@ use crate::error::*; use anchor_lang::prelude::*; -use static_assertions::const_assert; /// Seconds in one day. pub const SECS_PER_DAY: u64 = 86_400; @@ -155,7 +154,6 @@ pub enum LockupPeriod { SixMonths, OneYear, Flex, - Test, } impl Default for LockupPeriod { @@ -172,7 +170,6 @@ impl LockupPeriod { LockupPeriod::OneYear => SECS_PER_MONTH * 12, LockupPeriod::Flex => SECS_PER_DAY * 5, LockupPeriod::None => 0, - LockupPeriod::Test => 120, } } } diff --git a/programs/voter-stake-registry/src/state/mod.rs b/program-states/src/state/mod.rs similarity index 100% rename from programs/voter-stake-registry/src/state/mod.rs rename to program-states/src/state/mod.rs diff --git a/programs/voter-stake-registry/src/state/registrar.rs b/program-states/src/state/registrar.rs similarity index 98% rename from programs/voter-stake-registry/src/state/registrar.rs rename to program-states/src/state/registrar.rs index f0351200..8a780b58 100644 --- a/programs/voter-stake-registry/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -2,7 +2,6 @@ use crate::error::*; use crate::state::voting_mint_config::VotingMintConfig; use anchor_lang::prelude::*; use anchor_spl::token::Mint; -use static_assertions::const_assert; use std::convert::TryInto; /// Instance of a voting rights distributor. diff --git a/programs/voter-stake-registry/src/state/voter.rs b/program-states/src/state/voter.rs similarity index 98% rename from programs/voter-stake-registry/src/state/voter.rs rename to program-states/src/state/voter.rs index 94876b8d..f45ee737 100644 --- a/programs/voter-stake-registry/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -88,5 +88,4 @@ macro_rules! voter_seeds { }; } -use static_assertions::const_assert; pub use voter_seeds; diff --git a/programs/voter-stake-registry/src/state/voting_mint_config.rs b/program-states/src/state/voting_mint_config.rs similarity index 99% rename from programs/voter-stake-registry/src/state/voting_mint_config.rs rename to program-states/src/state/voting_mint_config.rs index 803fbc40..e7abf2d3 100644 --- a/programs/voter-stake-registry/src/state/voting_mint_config.rs +++ b/program-states/src/state/voting_mint_config.rs @@ -1,6 +1,5 @@ use crate::error::*; use anchor_lang::prelude::*; -use static_assertions::const_assert; use std::convert::TryFrom; const SCALED_FACTOR_BASE: u64 = 1_000_000_000; diff --git a/programs/voter-stake-registry/Cargo.toml b/programs/voter-stake-registry/Cargo.toml index 65beb01d..d95fc1f9 100644 --- a/programs/voter-stake-registry/Cargo.toml +++ b/programs/voter-stake-registry/Cargo.toml @@ -41,13 +41,13 @@ spl-governance-addin-api = "=0.1.1" solana-program = "1.14.10" static_assertions = "1.1" +mplx-staking-states = { path="../../program-states" } [dev-dependencies] solana-sdk = "1.14.10" solana-program-test = "1.14.10" spl-token = { version = "^3.0.0", features = ["no-entrypoint"] } spl-associated-token-account = { version = "^1.0.3", features = ["no-entrypoint"] } -bytemuck = "^1.7.2" log = "0.4.14" env_logger = "0.9.0" base64 = "0.13.0" \ No newline at end of file diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 46c6b9f4..8dcfe893 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,7 +1,7 @@ -use crate::state::LockupPeriod; use anchor_lang::prelude::borsh; use anchor_lang::Key; use anchor_lang::{prelude::Pubkey, AnchorDeserialize, AnchorSerialize}; +use mplx_staking_states::state::LockupPeriod; use solana_program::{ account_info::AccountInfo, entrypoint::ProgramResult, diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index d1cf552b..a05edc45 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -2,9 +2,9 @@ use std::borrow::Borrow; use crate::borsh::BorshDeserialize; use crate::cpi_instructions; -use crate::error::VsrError; use anchor_lang::prelude::*; use anchor_spl::token::{Token, TokenAccount}; +use mplx_staking_states::error::VsrError; use solana_program::program::get_return_data; #[derive(Accounts)] diff --git a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs index 37de2797..2508e10f 100644 --- a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs @@ -1,6 +1,8 @@ -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; +use mplx_staking_states::{ + error::VsrError, + state::{DepositEntry, Voter}, +}; #[derive(Accounts)] pub struct CloseDepositEntry<'info> { diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index f9e50421..d8ac6a00 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -1,11 +1,12 @@ use std::ops::DerefMut; -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::Transfer; use anchor_spl::token::{self, CloseAccount, Token, TokenAccount}; use bytemuck::bytes_of_mut; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{Registrar, Voter}; +use mplx_staking_states::voter_seeds; // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent diff --git a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs index 700b4b02..ea7d14f0 100644 --- a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs +++ b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs @@ -1,7 +1,9 @@ use anchor_lang::prelude::*; use anchor_spl::token::Mint; -use crate::error::*; -use crate::state::*; +use mplx_staking_states::{ + error::VsrError, + state::{Registrar, VotingMintConfig}, +}; // Remaining accounts must be all the token mints that have registered // as voting mints, including the newly registered one. diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index 5cd8fc95..4402e891 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -1,8 +1,9 @@ -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{Mint, Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{DepositEntry, Lockup, LockupKind, Voter}; +use mplx_staking_states::state::{LockupPeriod, Registrar}; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index bcbe8e98..57207113 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -1,8 +1,8 @@ -use crate::error::*; use anchor_lang::prelude::*; use anchor_spl::token::Mint; use anchor_spl::token::Token; -use crate::state::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::Registrar; use spl_governance::state::realm; use std::mem::size_of; diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 4f01fded..64018ef1 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -1,7 +1,8 @@ use anchor_lang::prelude::*; use anchor_lang::solana_program::sysvar::instructions as tx_instructions; -use crate::error::*; -use crate::state::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::Registrar; +use mplx_staking_states::state::Voter; use std::mem::size_of; use crate::cpi_instructions; diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 4137b83c..f8ff8fc3 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -1,7 +1,9 @@ -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; +use mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod, Registrar, Voter}, +}; #[derive(Accounts)] pub struct Deposit<'info> { diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 9c41d529..9689751f 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,7 +1,10 @@ -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount, Transfer}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::LockupKind; +use mplx_staking_states::state::LockupPeriod; +use mplx_staking_states::state::Registrar; +use mplx_staking_states::state::Voter; use crate::cpi_instructions::extend_deposit; diff --git a/programs/voter-stake-registry/src/instructions/lock_tokens.rs b/programs/voter-stake-registry/src/instructions/lock_tokens.rs index 8ab09cf1..4247c29b 100644 --- a/programs/voter-stake-registry/src/instructions/lock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/lock_tokens.rs @@ -1,6 +1,8 @@ -use crate::error::*; -use crate::state::*; use anchor_lang::prelude::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::LockupKind; +use mplx_staking_states::state::Registrar; +use mplx_staking_states::state::Voter; use crate::cpi_instructions; diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index f814d908..a4ed9ea0 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -1,6 +1,9 @@ use crate::events::*; + use anchor_lang::prelude::*; -use crate::state::*; +use mplx_staking_states::state::LockupKind; +use mplx_staking_states::state::Registrar; +use mplx_staking_states::state::Voter; #[derive(Accounts)] pub struct LogVoterInfo<'info> { diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index 858814c4..eaac4247 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -1,6 +1,8 @@ use anchor_lang::prelude::*; -use crate::error::*; -use crate::state::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::Registrar; +use mplx_staking_states::state::Voter; +use mplx_staking_states::state::COOLDOWN_SECS; #[derive(Accounts)] pub struct UnlockTokens<'info> { @@ -35,16 +37,9 @@ pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Res VsrError::DepositStillLocked ); - deposit_entry.lockup.cooldown_ends_at = if deposit_entry.lockup.period == LockupPeriod::Test { - curr_ts - .checked_add(60) - .ok_or(VsrError::InvalidTimestampArguments)? - } else { - curr_ts - .checked_add(COOLDOWN_SECS) - .ok_or(VsrError::InvalidTimestampArguments)? - }; - deposit_entry.lockup.cooldown_requested = true; + deposit_entry.lockup.cooldown_ends_at = curr_ts + .checked_add(COOLDOWN_SECS) + .ok_or(VsrError::InvalidTimestampArguments)?; Ok(()) } diff --git a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs index 97bf43fb..1db5987f 100644 --- a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs +++ b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs @@ -1,5 +1,5 @@ use anchor_lang::prelude::*; -use crate::state::*; +use mplx_staking_states::state::{Registrar, Voter}; use crate::voter::VoterWeightRecord; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 9617b00b..80210083 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,8 +1,10 @@ use crate::cpi_instructions::withdraw_mining; use crate::voter::{load_token_owner_record, VoterWeightRecord}; -use crate::{error::*, state::*}; use anchor_lang::prelude::*; use anchor_spl::token::{self, Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{DepositEntry, LockupKind, LockupPeriod, Registrar, Voter}; +use mplx_staking_states::voter_seeds; #[derive(Accounts)] pub struct Withdraw<'info> { diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index fc5b16fa..b295b1ed 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -1,14 +1,11 @@ use anchor_lang::prelude::*; -use error::*; use instructions::*; -use state::*; +use mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}; pub mod cpi_instructions; -pub mod error; pub mod events; mod governance; mod instructions; -pub mod state; pub mod voter; // The program address. @@ -60,8 +57,6 @@ declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); /// a u64. #[program] pub mod voter_stake_registry { - use crate::state::lockup::{LockupKind, LockupPeriod}; - use super::*; pub fn create_registrar( diff --git a/programs/voter-stake-registry/src/voter.rs b/programs/voter-stake-registry/src/voter.rs index 4513cc8d..de0519ec 100644 --- a/programs/voter-stake-registry/src/voter.rs +++ b/programs/voter-stake-registry/src/voter.rs @@ -1,5 +1,5 @@ -use crate::{error::VsrError, state::registrar::Registrar}; use anchor_lang::prelude::*; +use mplx_staking_states::{error::VsrError, state::Registrar}; use spl_governance::state::token_owner_record; use crate::vote_weight_record; diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 7f2b7d29..7bdb6b22 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -3,12 +3,12 @@ use std::rc::Rc; use anchor_lang::Key; +use mplx_staking_states::state::Voter; use solana_sdk::pubkey::Pubkey; use solana_sdk::{ instruction::Instruction, signature::{Keypair, Signer}, }; -use voter_stake_registry::state::{LockupPeriod, Voter}; use crate::*; diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 4d0fe1a3..d1ea354e 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -2,6 +2,7 @@ use std::rc::Rc; use anchor_lang::prelude::*; use anchor_lang::AnchorDeserialize; +use mplx_staking_states::state::LockupPeriod; use solana_program_test::*; use solana_sdk::program_pack::IsInitialized; use solana_sdk::{ @@ -12,7 +13,6 @@ use solana_sdk::{ system_program, }; use voter_stake_registry::cpi_instructions::RewardsInstruction; -use voter_stake_registry::state::LockupPeriod; use crate::SolanaCookie; diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 2e4fd954..2e1d7d70 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; #[tokio::test] diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 3cc2db42..6244391c 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -1,9 +1,9 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; use program_test::*; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index d3c23f03..4a751c47 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -1,9 +1,9 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; use program_test::*; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index f883e4ed..bd42c77a 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 618c7086..ee6d2460 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -1,9 +1,9 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use solana_program_test::*; use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; use program_test::*; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 35bd9331..d70a36ea 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -1,10 +1,10 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{ clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, }; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 54a86451..10424d01 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,9 +1,9 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; use program_test::*; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 1d6af812..66965da5 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,8 +1,8 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 94055a9b..7f2585cf 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,9 +1,8 @@ use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; use program_test::*; use solana_program_test::*; use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; -use voter_stake_registry::state::{LockupKind, LockupPeriod}; - mod program_test; #[tokio::test] async fn test_voting() -> Result<(), TransportError> { From b7300cb92274b871abb40ac41aefdeb09c0385f0 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 19 Jun 2024 13:40:06 +0300 Subject: [PATCH 51/59] fix: delegate adding on deposit entry creation --- .../src/instructions/create_deposit_entry.rs | 2 ++ programs/voter-stake-registry/src/lib.rs | 3 ++- .../tests/program_test/addin.rs | 2 ++ .../tests/test_all_deposits.rs | 4 ++++ programs/voter-stake-registry/tests/test_basic.rs | 3 +++ programs/voter-stake-registry/tests/test_claim.rs | 3 +++ .../tests/test_deposit_constant.rs | 6 ++++++ .../tests/test_deposit_no_locking.rs | 8 ++++++++ .../tests/test_extend_deposit.rs | 15 +++++++++++++++ .../voter-stake-registry/tests/test_lockup.rs | 9 +++++++++ .../tests/test_log_voter_info.rs | 3 +++ .../voter-stake-registry/tests/test_voting.rs | 6 ++++++ 12 files changed, 63 insertions(+), 1 deletion(-) diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index 4402e891..a4ab86c7 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -59,6 +59,7 @@ pub fn create_deposit_entry( deposit_entry_index: u8, kind: LockupKind, period: LockupPeriod, + delegate: Pubkey, ) -> Result<()> { // Load accounts. let registrar = &ctx.accounts.registrar.load()?; @@ -78,6 +79,7 @@ pub fn create_deposit_entry( let start_ts = registrar.clock_unix_timestamp(); *d_entry = DepositEntry::default(); + d_entry.delegate = delegate; d_entry.is_used = true; d_entry.voting_mint_config_idx = mint_idx as u8; d_entry.amount_deposited_native = 0; diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index b295b1ed..5b65fc9c 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -101,8 +101,9 @@ pub mod voter_stake_registry { deposit_entry_index: u8, kind: LockupKind, period: LockupPeriod, + delegate: Pubkey, ) -> Result<()> { - instructions::create_deposit_entry(ctx, deposit_entry_index, kind, period) + instructions::create_deposit_entry(ctx, deposit_entry_index, kind, period, delegate) } pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> Result<()> { diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 7bdb6b22..92678f59 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -318,6 +318,7 @@ impl AddinCookie { deposit_entry_index: u8, lockup_kind: LockupKind, period: LockupPeriod, + delegate: Pubkey, ) -> std::result::Result<(), BanksClientError> { let vault = voter.vault_address(voting_mint); @@ -326,6 +327,7 @@ impl AddinCookie { deposit_entry_index, kind: lockup_kind, period, + delegate, }, ); diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 2e1d7d70..298e11f4 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -76,6 +76,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { ) .await; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -85,6 +86,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -102,6 +104,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { .unwrap(); for i in 1..32 { + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -111,6 +114,7 @@ async fn test_all_deposits() -> Result<(), TransportError> { i, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 6244391c..2c45522b 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -105,6 +105,7 @@ async fn test_basic() -> Result<(), TransportError> { let balance_initial = voter.deposit_amount(&context.solana, 0).await; assert_eq!(balance_initial, 0); + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -115,6 +116,7 @@ async fn test_basic() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; @@ -128,6 +130,7 @@ async fn test_basic() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await?; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 4a751c47..09dfcf7f 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -106,6 +106,7 @@ async fn successeful_claim() -> Result<(), TransportError> { let depositer_token_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -116,6 +117,7 @@ async fn successeful_claim() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -128,6 +130,7 @@ async fn successeful_claim() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await?; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index bd42c77a..69c7faa7 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -156,6 +156,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { .token_account_balance(reference_account) .await; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -165,6 +166,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -177,6 +179,7 @@ async fn test_deposit_constant() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await .unwrap(); @@ -328,6 +331,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { ) }; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -337,6 +341,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -349,6 +354,7 @@ async fn test_withdrawing_without_unlocking() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index ee6d2460..ba398864 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -177,6 +177,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { .token_account_balance(reference_account) .await; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -186,6 +187,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -197,6 +199,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(after_deposit.vault, 15000); assert_eq!(after_deposit.deposit, 15000); + let delegate = Keypair::new(); // create a separate deposit (index 1) addin .create_deposit_entry( @@ -207,6 +210,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { 1, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -269,6 +273,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(voter2_voter_weight, 0); // now voter2 deposits + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -278,6 +283,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { 5, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -308,6 +314,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { assert_eq!(voter2_balances.vault, 1000); // when voter1 deposits again, they can reuse deposit index 0 + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -317,6 +324,7 @@ async fn test_deposit_no_locking() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index d70a36ea..6e4a0eb2 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -97,6 +97,7 @@ async fn restake_from_flex() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -107,6 +108,7 @@ async fn restake_from_flex() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -119,6 +121,7 @@ async fn restake_from_flex() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -271,6 +274,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -281,6 +285,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -293,6 +298,7 @@ async fn restake_from_three_months_deposit() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -446,6 +452,7 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -456,6 +463,7 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -468,6 +476,7 @@ async fn extend_deposit_after_one_year_for_three_months_with_top_up() -> Result< 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -620,6 +629,7 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -630,6 +640,7 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -642,6 +653,7 @@ async fn restake_from_flex_deposit_with_top_up() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -791,6 +803,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -801,6 +814,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -813,6 +827,7 @@ async fn restake_from_three_month_to_one_year() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::ThreeMonths, + delegate.pubkey(), ) .await?; context diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 10424d01..3cb0a9ad 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -98,6 +98,7 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -108,6 +109,7 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -120,6 +122,7 @@ async fn test_unlock_and_withdraw_before_end_ts() -> Result<(), TransportError> 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -264,6 +267,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -274,6 +278,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -286,6 +291,7 @@ async fn test_unlock_after_end_ts() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context @@ -440,6 +446,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran // test deposit and withdraw let reference_account = context.users[1].token_accounts[0]; + let delegate = Keypair::new(); context .addin .create_deposit_entry( @@ -450,6 +457,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await?; context @@ -462,6 +470,7 @@ async fn test_unlock_and_withdraw_after_end_ts_and_cooldown() -> Result<(), Tran 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await?; context diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index 66965da5..ca78c14c 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -99,6 +99,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { ) .await; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -108,6 +109,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -120,6 +122,7 @@ async fn test_log_voter_info() -> Result<(), TransportError> { 1, LockupKind::Constant, LockupPeriod::OneYear, + delegate.pubkey(), ) .await .unwrap(); diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 7f2585cf..d858ad47 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -122,6 +122,7 @@ async fn test_voting() -> Result<(), TransportError> { ) .await; + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -131,6 +132,7 @@ async fn test_voting() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -176,6 +178,7 @@ async fn test_voting() -> Result<(), TransportError> { .await .expect_err("could not withdraw"); + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -185,6 +188,7 @@ async fn test_voting() -> Result<(), TransportError> { 0, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); @@ -201,6 +205,7 @@ async fn test_voting() -> Result<(), TransportError> { .await .unwrap(); + let delegate = Keypair::new(); addin .create_deposit_entry( ®istrar, @@ -210,6 +215,7 @@ async fn test_voting() -> Result<(), TransportError> { 1, LockupKind::None, LockupPeriod::None, + delegate.pubkey(), ) .await .unwrap(); From 2b73a78f0a8dbd120dc49a62d1ade920d85ca8bc Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Wed, 19 Jun 2024 14:56:10 +0300 Subject: [PATCH 52/59] fix: now it's possible to deposit to DepositEntry that has some tokens already --- programs/voter-stake-registry/src/instructions/deposit.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index f8ff8fc3..6105fa9f 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -66,10 +66,6 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R { let voter = &mut ctx.accounts.voter.load_mut()?; let d_entry = voter.active_deposit_mut(deposit_entry_index)?; - require!( - d_entry.amount_deposited_native == 0, - VsrError::DepositingIsForbidded - ); require!( d_entry.lockup.kind == LockupKind::None && d_entry.lockup.period == LockupPeriod::None From f424171b60ba79c02c4b4720bdac14eb68b555e6 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Thu, 20 Jun 2024 14:32:43 +0300 Subject: [PATCH 53/59] Remove redundat accounts && change get_unix_time function --- program-states/src/state/lockup.rs | 3 - program-states/src/state/registrar.rs | 16 +- program-states/src/state/voter.rs | 2 - .../src/instructions/claim.rs | 1 + .../src/instructions/close_voter.rs | 8 +- .../src/instructions/create_deposit_entry.rs | 6 +- .../src/instructions/create_registrar.rs | 42 +++-- .../src/instructions/create_voter.rs | 71 ++++---- .../src/instructions/deposit.rs | 46 +++-- .../src/instructions/extend_deposit.rs | 3 +- .../src/instructions/lock_tokens.rs | 4 +- .../src/instructions/log_voter_info.rs | 3 +- .../src/instructions/mod.rs | 5 + .../src/instructions/unlock_tokens.rs | 4 +- .../src/instructions/withdraw.rs | 159 +++++++++--------- .../tests/program_test/addin.rs | 1 - 16 files changed, 179 insertions(+), 195 deletions(-) diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index 82d48ade..4199a4f5 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -13,9 +13,6 @@ pub const COOLDOWN_SECS: u64 = 86_400 * 5; #[zero_copy] #[derive(Default)] pub struct Lockup { - /// Note, that if start_ts is in the future, the funds are nevertheless - /// locked up! - /// Start of the lockup. pub start_ts: u64, diff --git a/program-states/src/state/registrar.rs b/program-states/src/state/registrar.rs index 8a780b58..80b30f46 100644 --- a/program-states/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -2,7 +2,6 @@ use crate::error::*; use crate::state::voting_mint_config::VotingMintConfig; use anchor_lang::prelude::*; use anchor_spl::token::Mint; -use std::convert::TryInto; /// Instance of a voting rights distributor. #[account(zero_copy)] @@ -16,27 +15,14 @@ pub struct Registrar { /// Storage for voting mints and their configuration. /// The length should be adjusted for one's use case. pub voting_mints: [VotingMintConfig; 2], - - /// Debug only: time offset, to allow tests to move forward in time. - pub time_offset: i64, pub bump: u8, } -const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 2 * 96 + 8 + 1); +const_assert!(std::mem::size_of::() == 7 + 4 * 32 + 2 * 96 + 1); const_assert!(std::mem::size_of::() % 8 == 0); pub const REGISTRAR_DISCRIMINATOR: [u8; 8] = [193, 202, 205, 51, 78, 168, 150, 128]; impl Registrar { - pub fn clock_unix_timestamp(&self) -> u64 { - Clock::get() - .unwrap() - .unix_timestamp - .checked_add(self.time_offset) - .unwrap() - .try_into() - .unwrap() - } - pub fn voting_mint_config_index(&self, mint: Pubkey) -> Result { self.voting_mints .iter() diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index f45ee737..c6fb8033 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -16,8 +16,6 @@ pub struct Voter { const_assert!(std::mem::size_of::() == 80 * 32 + 32 + 32 + 1 + 1 + 14); const_assert!(std::mem::size_of::() % 8 == 0); -pub const VOTER_DISCRIMINATOR: [u8; 8] = [241, 93, 35, 191, 254, 147, 17, 202]; - impl Voter { /// The full vote weight available to the voter pub fn weight(&self) -> Result { diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index a05edc45..fca94603 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -40,6 +40,7 @@ pub struct Claim<'info> { pub token_program: Program<'info, Token>, /// CHECK: Rewards Program account + #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index d8ac6a00..96ac1c1e 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -8,6 +8,8 @@ use mplx_staking_states::error::VsrError; use mplx_staking_states::state::{Registrar, Voter}; use mplx_staking_states::voter_seeds; +use crate::clock_unix_timestamp; + // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent // can then be sent back to the sol_destination @@ -42,8 +44,7 @@ pub fn close_voter<'key, 'accounts, 'remaining, 'info>( ctx: Context<'key, 'accounts, 'remaining, 'info, CloseVoter<'info>>, ) -> Result<()> { let registrar = ctx.accounts.registrar.load()?; - let curr_ts = registrar.clock_unix_timestamp(); - + let curr_ts = clock_unix_timestamp(); { let voter = ctx.accounts.voter.load()?; @@ -104,9 +105,8 @@ pub fn close_voter<'key, 'accounts, 'remaining, 'info>( } } - // zero out voter account to prevent reinit attacks - // appease rust borrow checker { + // zero out voter account to prevent reinit attacks let mut voter = ctx.accounts.voter.load_mut()?; let voter_dereffed = voter.deref_mut(); let voter_bytes = bytes_of_mut(voter_dereffed); diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index a4ab86c7..fec788bf 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -5,6 +5,8 @@ use mplx_staking_states::error::VsrError; use mplx_staking_states::state::{DepositEntry, Lockup, LockupKind, Voter}; use mplx_staking_states::state::{LockupPeriod, Registrar}; +use crate::clock_unix_timestamp; + #[derive(Accounts)] pub struct CreateDepositEntry<'info> { pub registrar: AccountLoader<'info, Registrar>, @@ -33,11 +35,9 @@ pub struct CreateDepositEntry<'info> { pub payer: Signer<'info>, pub deposit_mint: Box>, - pub system_program: Program<'info, System>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, - pub rent: Sysvar<'info, Rent>, } /// Creates a new deposit entry. @@ -77,7 +77,7 @@ pub fn create_deposit_entry( let d_entry = &mut voter.deposits[deposit_entry_index as usize]; require!(!d_entry.is_used, VsrError::UnusedDepositEntryIndex); - let start_ts = registrar.clock_unix_timestamp(); + let start_ts = clock_unix_timestamp(); *d_entry = DepositEntry::default(); d_entry.delegate = delegate; d_entry.is_used = true; diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index 57207113..f0b7f8b1 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -58,6 +58,7 @@ pub struct CreateRegistrar<'info> { pub token_program: Program<'info, Token>, /// CHECK: Rewards Program account + #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } @@ -74,29 +75,26 @@ pub fn create_registrar( fill_authority: Pubkey, distribution_authority: Pubkey, ) -> Result<()> { - { - let registrar = &mut ctx.accounts.registrar.load_init()?; - require_eq!(registrar_bump, *ctx.bumps.get("registrar").unwrap()); - registrar.bump = registrar_bump; - registrar.governance_program_id = ctx.accounts.governance_program_id.key(); - registrar.realm = ctx.accounts.realm.key(); - registrar.realm_governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); - registrar.realm_authority = ctx.accounts.realm_authority.key(); - registrar.time_offset = 0; + let registrar = &mut ctx.accounts.registrar.load_init()?; + require_eq!(registrar_bump, *ctx.bumps.get("registrar").unwrap()); + registrar.bump = registrar_bump; + registrar.governance_program_id = ctx.accounts.governance_program_id.key(); + registrar.realm = ctx.accounts.realm.key(); + registrar.realm_governing_token_mint = ctx.accounts.realm_governing_token_mint.key(); + registrar.realm_authority = ctx.accounts.realm_authority.key(); - // Verify that "realm_authority" is the expected authority on "realm" - // and that the mint matches one of the realm mints too. - let realm = realm::get_realm_data_for_governing_token_mint( - ®istrar.governance_program_id, - &ctx.accounts.realm.to_account_info(), - ®istrar.realm_governing_token_mint, - )?; - require_keys_eq!( - realm.authority.unwrap(), - ctx.accounts.realm_authority.key(), - VsrError::InvalidRealmAuthority - ); - } + // Verify that "realm_authority" is the expected authority on "realm" + // and that the mint matches one of the realm mints too. + let realm = realm::get_realm_data_for_governing_token_mint( + ®istrar.governance_program_id, + &ctx.accounts.realm.to_account_info(), + ®istrar.realm_governing_token_mint, + )?; + require_keys_eq!( + realm.authority.unwrap(), + ctx.accounts.realm_authority.key(), + VsrError::InvalidRealmAuthority + ); // we should initiate the rewards pool to proceed with // staking and rewards logic diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 64018ef1..94ea1d69 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -61,6 +61,7 @@ pub struct CreateVoter<'info> { pub deposit_mining: UncheckedAccount<'info>, /// CHECK: Rewards program ID + #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } @@ -94,43 +95,39 @@ pub fn create_voter( *ctx.bumps.get("voter_weight_record").unwrap() ); - { - // Load accounts. - let registrar = &ctx.accounts.registrar.load()?; - let voter_authority = ctx.accounts.voter_authority.key(); - - let voter = &mut ctx.accounts.voter.load_init()?; - voter.voter_bump = voter_bump; - voter.voter_weight_record_bump = voter_weight_record_bump; - voter.voter_authority = voter_authority; - voter.registrar = ctx.accounts.registrar.key(); - - let voter_weight_record = &mut ctx.accounts.voter_weight_record; - voter_weight_record.account_discriminator = - spl_governance_addin_api::voter_weight::VoterWeightRecord::ACCOUNT_DISCRIMINATOR; - voter_weight_record.realm = registrar.realm; - voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; - voter_weight_record.governing_token_owner = voter_authority; - } - - { - // initialize Mining account for Voter - let mining = ctx.accounts.deposit_mining.to_account_info(); - let payer = ctx.accounts.payer.to_account_info(); - let user = ctx.accounts.voter_authority.key; - let system_program = ctx.accounts.system_program.to_account_info(); - let reward_pool = ctx.accounts.reward_pool.to_account_info(); - let rewards_program_id = ctx.accounts.rewards_program.to_account_info(); - - cpi_instructions::initialize_mining( - rewards_program_id, - reward_pool, - mining, - user, - payer, - system_program, - )?; - } + // Load accounts. + let registrar = &ctx.accounts.registrar.load()?; + let voter_authority = ctx.accounts.voter_authority.key(); + + let voter = &mut ctx.accounts.voter.load_init()?; + voter.voter_bump = voter_bump; + voter.voter_weight_record_bump = voter_weight_record_bump; + voter.voter_authority = voter_authority; + voter.registrar = ctx.accounts.registrar.key(); + + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + voter_weight_record.account_discriminator = + spl_governance_addin_api::voter_weight::VoterWeightRecord::ACCOUNT_DISCRIMINATOR; + voter_weight_record.realm = registrar.realm; + voter_weight_record.governing_token_mint = registrar.realm_governing_token_mint; + voter_weight_record.governing_token_owner = voter_authority; + + // initialize Mining account for Voter + let mining = ctx.accounts.deposit_mining.to_account_info(); + let payer = ctx.accounts.payer.to_account_info(); + let user = ctx.accounts.voter_authority.key; + let system_program = ctx.accounts.system_program.to_account_info(); + let reward_pool = ctx.accounts.reward_pool.to_account_info(); + let rewards_program_id = ctx.accounts.rewards_program.to_account_info(); + + cpi_instructions::initialize_mining( + rewards_program_id, + reward_pool, + mining, + user, + payer, + system_program, + )?; Ok(()) } diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 6105fa9f..518e6998 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -5,6 +5,8 @@ use mplx_staking_states::{ state::{LockupKind, LockupPeriod, Registrar, Voter}, }; +use crate::clock_unix_timestamp; + #[derive(Accounts)] pub struct Deposit<'info> { pub registrar: AccountLoader<'info, Registrar>, @@ -61,34 +63,28 @@ pub fn deposit(ctx: Context, deposit_entry_index: u8, amount: u64) -> R } let registrar = &ctx.accounts.registrar.load()?; - let curr_ts = registrar.clock_unix_timestamp(); - - { - let voter = &mut ctx.accounts.voter.load_mut()?; - let d_entry = voter.active_deposit_mut(deposit_entry_index)?; - require!( - d_entry.lockup.kind == LockupKind::None - && d_entry.lockup.period == LockupPeriod::None - && d_entry.is_used, - VsrError::DepositingIsForbidded, - ); + let curr_ts = clock_unix_timestamp(); - // Get the exchange rate entry associated with this deposit. - let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; - require_eq!( - mint_idx, - d_entry.voting_mint_config_idx as usize, - VsrError::InvalidMint - ); + let voter = &mut ctx.accounts.voter.load_mut()?; + let d_entry = voter.active_deposit_mut(deposit_entry_index)?; + require!( + d_entry.lockup.kind == LockupKind::None + && d_entry.lockup.period == LockupPeriod::None + && d_entry.is_used, + VsrError::DepositingIsForbidded, + ); - // Deposit tokens into the vault and increase the lockup amount too. - token::transfer(ctx.accounts.transfer_ctx(), amount)?; - d_entry.amount_deposited_native = - d_entry.amount_deposited_native.checked_add(amount).unwrap(); - } + // Get the exchange rate entry associated with this deposit. + let mint_idx = registrar.voting_mint_config_index(ctx.accounts.deposit_token.mint)?; + require_eq!( + mint_idx, + d_entry.voting_mint_config_idx as usize, + VsrError::InvalidMint + ); - let voter = &ctx.accounts.voter.load()?; - let d_entry = voter.active_deposit(deposit_entry_index)?; + // Deposit tokens into the vault and increase the lockup amount too. + token::transfer(ctx.accounts.transfer_ctx(), amount)?; + d_entry.amount_deposited_native = d_entry.amount_deposited_native.checked_add(amount).unwrap(); msg!( "Deposited amount {} at deposit index {} with lockup kind {:?} with lockup period {:?} and {} seconds left. It's used now: {:?}", diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 9689751f..5e3c90ef 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -6,6 +6,7 @@ use mplx_staking_states::state::LockupPeriod; use mplx_staking_states::state::Registrar; use mplx_staking_states::state::Voter; +use crate::clock_unix_timestamp; use crate::cpi_instructions::extend_deposit; #[derive(Accounts)] @@ -87,7 +88,7 @@ pub fn restake_deposit( VsrError::RestakeDepositIsNotAllowed ); let start_ts = d_entry.lockup.start_ts; - let curr_ts = registrar.clock_unix_timestamp(); + let curr_ts = clock_unix_timestamp(); let amount = d_entry.amount_deposited_native; let old_lockup_period = if d_entry.lockup.expired(curr_ts) { LockupPeriod::Flex diff --git a/programs/voter-stake-registry/src/instructions/lock_tokens.rs b/programs/voter-stake-registry/src/instructions/lock_tokens.rs index 4247c29b..ab089f70 100644 --- a/programs/voter-stake-registry/src/instructions/lock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/lock_tokens.rs @@ -4,6 +4,7 @@ use mplx_staking_states::state::LockupKind; use mplx_staking_states::state::Registrar; use mplx_staking_states::state::Voter; +use crate::clock_unix_timestamp; use crate::cpi_instructions; #[derive(Accounts)] @@ -32,6 +33,7 @@ pub struct LockTokens<'info> { pub deposit_mining: UncheckedAccount<'info>, /// CHECK: Rewards Program account + #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } @@ -50,7 +52,7 @@ pub fn lock_tokens( ) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; - let curr_ts = registrar.clock_unix_timestamp(); + let curr_ts = clock_unix_timestamp(); let source = voter.active_deposit_mut(source_deposit_entry_index)?; let source_mint_idx = source.voting_mint_config_idx; diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index a4ed9ea0..267aa517 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -1,3 +1,4 @@ +use crate::clock_unix_timestamp; use crate::events::*; use anchor_lang::prelude::*; @@ -27,7 +28,7 @@ pub fn log_voter_info( ) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = ctx.accounts.voter.load()?; - let curr_ts = registrar.clock_unix_timestamp(); + let curr_ts = clock_unix_timestamp(); let deposit_entry_begin = deposit_entry_begin as usize; let deposit_entry_count = deposit_entry_count as usize; diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 41126408..60bc0974 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -9,6 +9,7 @@ pub use deposit::*; pub use extend_deposit::*; pub use lock_tokens::*; pub use log_voter_info::*; +use solana_program::{clock::Clock, sysvar::Sysvar}; pub use unlock_tokens::*; pub use update_voter_weight_record::*; pub use withdraw::*; @@ -27,3 +28,7 @@ mod log_voter_info; mod unlock_tokens; mod update_voter_weight_record; mod withdraw; + +pub fn clock_unix_timestamp() -> u64 { + Clock::get().unwrap().unix_timestamp as u64 +} diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index eaac4247..a38a00e7 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -4,6 +4,8 @@ use mplx_staking_states::state::Registrar; use mplx_staking_states::state::Voter; use mplx_staking_states::state::COOLDOWN_SECS; +use crate::clock_unix_timestamp; + #[derive(Accounts)] pub struct UnlockTokens<'info> { pub registrar: AccountLoader<'info, Registrar>, @@ -23,7 +25,7 @@ pub struct UnlockTokens<'info> { pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; - let curr_ts = registrar.clock_unix_timestamp(); + let curr_ts = clock_unix_timestamp(); let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?; diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 80210083..927df426 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,3 +1,4 @@ +use crate::clock_unix_timestamp; use crate::cpi_instructions::withdraw_mining; use crate::voter::{load_token_owner_record, VoterWeightRecord}; use anchor_lang::prelude::*; @@ -66,6 +67,7 @@ pub struct Withdraw<'info> { pub deposit_mining: UncheckedAccount<'info>, /// CHECK: Rewards Program account + #[account(executable)] pub rewards_program: UncheckedAccount<'info>, } @@ -93,100 +95,99 @@ pub fn withdraw( realm_governing_mint_pubkey: Pubkey, realm_pubkey: Pubkey, ) -> Result<()> { + // we need that block to free all references borrowed from registart/voter/etc, + // otherwise later, during transfer we would pass references that are already borrowed { - // Transfer the tokens to withdraw. - let voter = &mut ctx.accounts.voter.load()?; + let voter = ctx.accounts.voter.load()?; let voter_seeds = voter_seeds!(voter); token::transfer( ctx.accounts.transfer_ctx().with_signer(&[voter_seeds]), amount, )?; } - { - // Load the accounts. - let registrar = &ctx.accounts.registrar.load()?; - let voter = &mut ctx.accounts.voter.load_mut()?; - - // Get the exchange rate for the token being withdrawn. - let mint_idx = registrar.voting_mint_config_index(ctx.accounts.destination.mint)?; - - // Governance may forbid withdraws, for example when engaged in a vote. - // Not applicable for tokens that don't contribute to voting power. - if registrar.voting_mints[mint_idx].grants_vote_weight() { - let token_owner_record = load_token_owner_record( - &voter.voter_authority, - &ctx.accounts.token_owner_record.to_account_info(), - registrar, - )?; - token_owner_record.assert_can_withdraw_governing_tokens()?; - } - - // Get the deposit being withdrawn from. - let curr_ts = registrar.clock_unix_timestamp(); - let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?; - - // check whether funds are cooled down - if deposit_entry.lockup.kind == LockupKind::Constant { - require!( - deposit_entry.lockup.cooldown_requested, - VsrError::UnlockMustBeCalledFirst - ); - require!( - curr_ts >= deposit_entry.lockup.cooldown_ends_at, - VsrError::InvalidTimestampArguments - ); - } - - require_gte!( - deposit_entry.amount_unlocked(curr_ts), - amount, - VsrError::InsufficientUnlockedTokens + + // Load the accounts. + let registrar = &ctx.accounts.registrar.load()?; + let voter = &mut ctx.accounts.voter.load_mut()?; + + // Get the exchange rate for the token being withdrawn. + let mint_idx = registrar.voting_mint_config_index(ctx.accounts.destination.mint)?; + + // Governance may forbid withdraws, for example when engaged in a vote. + // Not applicable for tokens that don't contribute to voting power. + if registrar.voting_mints[mint_idx].grants_vote_weight() { + let token_owner_record = load_token_owner_record( + &voter.voter_authority, + &ctx.accounts.token_owner_record.to_account_info(), + registrar, + )?; + token_owner_record.assert_can_withdraw_governing_tokens()?; + } + + // Get the deposit being withdrawn from. + let curr_ts = clock_unix_timestamp(); + let deposit_entry = voter.active_deposit_mut(deposit_entry_index)?; + + // check whether funds are cooled down + if deposit_entry.lockup.kind == LockupKind::Constant { + require!( + deposit_entry.lockup.cooldown_requested, + VsrError::UnlockMustBeCalledFirst ); - require_eq!( - mint_idx, - deposit_entry.voting_mint_config_idx as usize, - VsrError::InvalidMint + require!( + curr_ts >= deposit_entry.lockup.cooldown_ends_at, + VsrError::InvalidTimestampArguments ); + } - // Bookkeeping for withdrawn funds. - require_gte!( - deposit_entry.amount_deposited_native, - amount, - VsrError::InternalProgramError - ); + require_gte!( + deposit_entry.amount_unlocked(curr_ts), + amount, + VsrError::InsufficientUnlockedTokens + ); + require_eq!( + mint_idx, + deposit_entry.voting_mint_config_idx as usize, + VsrError::InvalidMint + ); + + // Bookkeeping for withdrawn funds. + require_gte!( + deposit_entry.amount_deposited_native, + amount, + VsrError::InternalProgramError + ); + + deposit_entry.amount_deposited_native = deposit_entry + .amount_deposited_native + .checked_sub(amount) + .unwrap(); + + // if deposit doesn't have tokens after withdrawal + // then is shouldn't be used + if deposit_entry.amount_deposited_native == 0 + && deposit_entry.lockup.kind != LockupKind::None + && deposit_entry.lockup.period != LockupPeriod::None + { + *deposit_entry = DepositEntry::default(); + deposit_entry.is_used = false; + } - deposit_entry.amount_deposited_native = deposit_entry - .amount_deposited_native - .checked_sub(amount) - .unwrap(); - - // if deposit doesn't have tokens after withdrawal - // then is shouldn't be used - if deposit_entry.amount_deposited_native == 0 - && deposit_entry.lockup.kind != LockupKind::None - && deposit_entry.lockup.period != LockupPeriod::None - { - *deposit_entry = DepositEntry::default(); - deposit_entry.is_used = false; - } - - msg!( - "Withdrew amount {} at deposit index {} with lockup kind {:?} and {} seconds left", - amount, - deposit_entry_index, - deposit_entry.lockup.kind, - deposit_entry.lockup.seconds_left(curr_ts), - ); + msg!( + "Withdrew amount {} at deposit index {} with lockup kind {:?} and {} seconds left", + amount, + deposit_entry_index, + deposit_entry.lockup.kind, + deposit_entry.lockup.seconds_left(curr_ts), + ); - if deposit_entry.lockup.kind == LockupKind::None - && deposit_entry.lockup.period == LockupPeriod::None - { - return Ok(()); - } + if deposit_entry.lockup.kind == LockupKind::None + && deposit_entry.lockup.period == LockupPeriod::None + { + return Ok(()); } // Update the voter weight record - let voter = &ctx.accounts.voter.load()?; let record = &mut ctx.accounts.voter_weight_record; record.voter_weight = voter.weight()?; record.voter_weight_expiry = Some(Clock::get()?.slot); diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 92678f59..9b8206bd 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -342,7 +342,6 @@ impl AddinCookie { system_program: solana_sdk::system_program::id(), token_program: spl_token::id(), associated_token_program: spl_associated_token_account::id(), - rent: solana_program::sysvar::rent::id(), }, None, ); From 902616ee3e0fb1310c2564eb28abc62d9a398155 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Fri, 21 Jun 2024 19:01:06 +0300 Subject: [PATCH 54/59] minor fix: remove registrar parsing from unlock_tokens() --- programs/voter-stake-registry/src/instructions/unlock_tokens.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index a38a00e7..d4a8a89f 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -23,7 +23,6 @@ pub struct UnlockTokens<'info> { } pub fn unlock_tokens(ctx: Context, deposit_entry_index: u8) -> Result<()> { - let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_mut()?; let curr_ts = clock_unix_timestamp(); From 6b7c7d4b3afb6be56e6b1d7d5eaadedc135abf30 Mon Sep 17 00:00:00 2001 From: Kyrylo Stepanov Date: Thu, 27 Jun 2024 12:45:35 +0300 Subject: [PATCH 55/59] update contract accordingly to the latest on rewards program --- .../tests/fixtures/mplx_rewards.so | Bin 296848 -> 293504 bytes .../tests/program_test/rewards.rs | 12 ------------ .../voter-stake-registry/tests/test_claim.rs | 2 +- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so b/programs/voter-stake-registry/tests/fixtures/mplx_rewards.so index 99ab813e447f3112f898afbaebb942530d9e1a2e..fa34356e7059c1f773e47c3ddceaa1bf76449304 100755 GIT binary patch delta 84809 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 CC=?|C delta 86616 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 std::result::Result<(), BanksClientError> { - let (reward_vault, _bump) = Pubkey::find_program_address( - &[ - "vault".as_bytes(), - &reward_pool.to_bytes(), - &reward_mint.to_bytes(), - ], - &self.program_id, - ); - let accounts = vec![ AccountMeta::new(*reward_pool, false), - AccountMeta::new_readonly(*reward_mint, false), - AccountMeta::new(reward_vault, false), AccountMeta::new_readonly(distribute_authority.pubkey(), true), ]; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 09dfcf7f..8711a05d 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -192,7 +192,7 @@ async fn successeful_claim() -> Result<(), TransportError> { context .rewards - .distribute_rewards(&rewards_pool, reward_mint, &distribution_authority) + .distribute_rewards(&rewards_pool, &distribution_authority) .await?; context From 60d7afedf308b34d4e09a8d8538a930178169506 Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Thu, 27 Jun 2024 15:46:28 +0100 Subject: [PATCH 56/59] rust fmt file with the reformatting --- README.md | 1 + program-states/src/state/deposit_entry.rs | 13 ++++--- program-states/src/state/lockup.rs | 3 +- program-states/src/state/mod.rs | 6 +--- program-states/src/state/registrar.rs | 9 ++--- program-states/src/state/voter.rs | 11 +++--- .../src/state/voting_mint_config.rs | 4 +-- .../src/cpi_instructions.rs | 23 ++++++------ .../src/instructions/claim.rs | 19 +++++----- .../src/instructions/close_deposit_entry.rs | 10 +++--- .../src/instructions/close_voter.rs | 23 ++++++------ .../src/instructions/configure_voting_mint.rs | 36 +++++++++++-------- .../src/instructions/create_deposit_entry.rs | 24 +++++++------ .../src/instructions/create_registrar.rs | 17 +++++---- .../src/instructions/create_voter.rs | 24 +++++++------ .../src/instructions/deposit.rs | 15 ++++---- .../src/instructions/extend_deposit.rs | 26 +++++++------- .../src/instructions/lock_tokens.rs | 28 ++++++++------- .../src/instructions/log_voter_info.rs | 12 +++---- .../src/instructions/mod.rs | 20 ++++------- .../src/instructions/unlock_tokens.rs | 15 ++++---- .../update_voter_weight_record.rs | 9 ++--- .../src/instructions/withdraw.rs | 22 +++++++----- programs/voter-stake-registry/src/lib.rs | 15 ++++---- programs/voter-stake-registry/src/voter.rs | 11 +++--- .../tests/program_test/addin.rs | 22 ++++++------ .../tests/program_test/cookies.rs | 4 +-- .../tests/program_test/governance.rs | 18 +++++----- .../tests/program_test/mod.rs | 36 +++++++++---------- .../tests/program_test/rewards.rs | 31 ++++++++-------- .../tests/program_test/solana.rs | 31 ++++++++-------- .../tests/program_test/utils.rs | 21 ++++++----- .../tests/test_all_deposits.rs | 12 ++++--- .../voter-stake-registry/tests/test_basic.rs | 13 +++---- .../voter-stake-registry/tests/test_claim.rs | 13 +++---- .../tests/test_deposit_constant.rs | 12 ++++--- .../tests/test_deposit_no_locking.rs | 13 +++---- .../tests/test_extend_deposit.rs | 14 ++++---- .../voter-stake-registry/tests/test_lockup.rs | 13 +++---- .../tests/test_log_voter_info.rs | 12 ++++--- .../voter-stake-registry/tests/test_voting.rs | 12 ++++--- rustfmt.toml | 5 +++ 42 files changed, 358 insertions(+), 320 deletions(-) create mode 100644 rustfmt.toml diff --git a/README.md b/README.md index 86f7801f..44213425 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Users can: * Run rust based tests - `cargo test-bpf` * To install the typescript client, do - `yarn add @blockworks-foundation/voter-stake-registry-client` * usage +* Run `cargo +nightly fmt` before pushing your changes ## Node/Typescript * Built and developed using - node (`v16.13.1`) diff --git a/program-states/src/state/deposit_entry.rs b/program-states/src/state/deposit_entry.rs index 89727e96..cab700fb 100644 --- a/program-states/src/state/deposit_entry.rs +++ b/program-states/src/state/deposit_entry.rs @@ -1,6 +1,7 @@ -use crate::state::lockup::{Lockup, LockupKind}; - -use anchor_lang::prelude::*; +use { + crate::state::lockup::{Lockup, LockupKind}, + anchor_lang::prelude::*, +}; /// Bookkeeping for a single deposit for a given mint and lockup schedule. #[zero_copy] @@ -63,8 +64,10 @@ impl DepositEntry { #[cfg(test)] mod tests { - use super::*; - use crate::state::{LockupKind::Constant, LockupPeriod, VotingMintConfig}; + use { + super::*, + crate::state::{LockupKind::Constant, LockupPeriod, VotingMintConfig}, + }; #[test] pub fn far_future_lockup_start_test() -> Result<()> { diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index 4199a4f5..fd9a3953 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -1,5 +1,4 @@ -use crate::error::*; -use anchor_lang::prelude::*; +use {crate::error::*, anchor_lang::prelude::*}; /// Seconds in one day. pub const SECS_PER_DAY: u64 = 86_400; diff --git a/program-states/src/state/mod.rs b/program-states/src/state/mod.rs index 2c692bf5..e3b0cd99 100644 --- a/program-states/src/state/mod.rs +++ b/program-states/src/state/mod.rs @@ -1,8 +1,4 @@ -pub use deposit_entry::*; -pub use lockup::*; -pub use registrar::*; -pub use voter::*; -pub use voting_mint_config::*; +pub use {deposit_entry::*, lockup::*, registrar::*, voter::*, voting_mint_config::*}; pub mod deposit_entry; pub mod lockup; diff --git a/program-states/src/state/registrar.rs b/program-states/src/state/registrar.rs index 80b30f46..e00eccb2 100644 --- a/program-states/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -1,7 +1,8 @@ -use crate::error::*; -use crate::state::voting_mint_config::VotingMintConfig; -use anchor_lang::prelude::*; -use anchor_spl::token::Mint; +use { + crate::{error::*, state::voting_mint_config::VotingMintConfig}, + anchor_lang::prelude::*, + anchor_spl::token::Mint, +}; /// Instance of a voting rights distributor. #[account(zero_copy)] diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index c6fb8033..4e10bd6a 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -1,7 +1,10 @@ -use crate::error::*; -use crate::state::deposit_entry::DepositEntry; -use crate::state::registrar::Registrar; -use anchor_lang::prelude::*; +use { + crate::{ + error::*, + state::{deposit_entry::DepositEntry, registrar::Registrar}, + }, + anchor_lang::prelude::*, +}; /// User account for minting voting rights. #[account(zero_copy)] diff --git a/program-states/src/state/voting_mint_config.rs b/program-states/src/state/voting_mint_config.rs index e7abf2d3..9bb96a91 100644 --- a/program-states/src/state/voting_mint_config.rs +++ b/program-states/src/state/voting_mint_config.rs @@ -1,6 +1,4 @@ -use crate::error::*; -use anchor_lang::prelude::*; -use std::convert::TryFrom; +use {crate::error::*, anchor_lang::prelude::*, std::convert::TryFrom}; const SCALED_FACTOR_BASE: u64 = 1_000_000_000; diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 8dcfe893..1c2ab0cd 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,13 +1,16 @@ -use anchor_lang::prelude::borsh; -use anchor_lang::Key; -use anchor_lang::{prelude::Pubkey, AnchorDeserialize, AnchorSerialize}; -use mplx_staking_states::state::LockupPeriod; -use solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - instruction::{AccountMeta, Instruction}, - program::{invoke, invoke_signed}, - system_program, +use { + anchor_lang::{ + prelude::{borsh, Pubkey}, + AnchorDeserialize, AnchorSerialize, Key, + }, + mplx_staking_states::state::LockupPeriod, + solana_program::{ + account_info::AccountInfo, + entrypoint::ProgramResult, + instruction::{AccountMeta, Instruction}, + program::{invoke, invoke_signed}, + system_program, + }, }; pub const REWARD_CONTRACT_ID: Pubkey = diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index fca94603..55736f4a 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -1,11 +1,11 @@ -use std::borrow::Borrow; - -use crate::borsh::BorshDeserialize; -use crate::cpi_instructions; -use anchor_lang::prelude::*; -use anchor_spl::token::{Token, TokenAccount}; -use mplx_staking_states::error::VsrError; -use solana_program::program::get_return_data; +use { + crate::{borsh::BorshDeserialize, cpi_instructions}, + anchor_lang::prelude::*, + anchor_spl::token::{Token, TokenAccount}, + mplx_staking_states::error::VsrError, + solana_program::program::get_return_data, + std::borrow::Borrow, +}; #[derive(Accounts)] pub struct Claim<'info> { @@ -23,7 +23,8 @@ pub struct Claim<'info> { pub vault: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA(["mining", mining owner , reward_pool], reward_program) + /// PDA(["mining", mining owner , reward_pool], + /// reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs index 2508e10f..22279e24 100644 --- a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs @@ -1,7 +1,9 @@ -use anchor_lang::prelude::*; -use mplx_staking_states::{ - error::VsrError, - state::{DepositEntry, Voter}, +use { + anchor_lang::prelude::*, + mplx_staking_states::{ + error::VsrError, + state::{DepositEntry, Voter}, + }, }; #[derive(Accounts)] diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index 96ac1c1e..57b8060b 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -1,14 +1,15 @@ -use std::ops::DerefMut; - -use anchor_lang::prelude::*; -use anchor_spl::token::Transfer; -use anchor_spl::token::{self, CloseAccount, Token, TokenAccount}; -use bytemuck::bytes_of_mut; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::{Registrar, Voter}; -use mplx_staking_states::voter_seeds; - -use crate::clock_unix_timestamp; +use { + crate::clock_unix_timestamp, + anchor_lang::prelude::*, + anchor_spl::token::{self, CloseAccount, Token, TokenAccount, Transfer}, + bytemuck::bytes_of_mut, + mplx_staking_states::{ + error::VsrError, + state::{Registrar, Voter}, + voter_seeds, + }, + std::ops::DerefMut, +}; // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent diff --git a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs index ea7d14f0..68ccb85c 100644 --- a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs +++ b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs @@ -1,8 +1,10 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::Mint; -use mplx_staking_states::{ - error::VsrError, - state::{Registrar, VotingMintConfig}, +use { + anchor_lang::prelude::*, + anchor_spl::token::Mint, + mplx_staking_states::{ + error::VsrError, + state::{Registrar, VotingMintConfig}, + }, }; // Remaining accounts must be all the token mints that have registered @@ -25,10 +27,11 @@ pub struct ConfigureVotingMint<'info> { /// /// * `idx`: index of the rate to be set /// * `digit_shift`: how many digits to shift the native token amount, see below -/// * `baseline_vote_weight_scaled_factor`: vote weight factor for all funds in vault, in 1/1e9 units +/// * `baseline_vote_weight_scaled_factor`: vote weight factor for all funds in vault, in 1/1e9 +/// units /// * `max_extra_lockup_vote_weight_scaled_factor`: max extra weight for lockups, in 1/1e9 units -/// * `lockup_saturation_secs`: lockup duration at which the full vote weight -/// bonus is given to locked up deposits +/// * `lockup_saturation_secs`: lockup duration at which the full vote weight bonus is given to +/// locked up deposits /// /// This instruction can be called several times for the same mint and index to /// change the voting mint configuration. @@ -49,8 +52,8 @@ pub struct ConfigureVotingMint<'info> { /// do your own checking too. /// /// If you use a single mint, prefer digit_shift=0 and baseline_vote_weight_scaled_factor + -/// max_extra_lockup_vote_weight_scaled_factor <= 1e9. That way you won't have issues with overflow no -/// matter the size of the mint's supply. +/// max_extra_lockup_vote_weight_scaled_factor <= 1e9. That way you won't have issues with overflow +/// no matter the size of the mint's supply. /// /// Digit shifting is particularly useful when using several voting token mints /// that have a different number of decimals. It can be used to align them to @@ -58,8 +61,10 @@ pub struct ConfigureVotingMint<'info> { /// /// Example: If you have token A with 6 decimals and token B with 9 decimals, you /// could set up: -/// * A with digit_shift=0, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0 -/// * B with digit_shift=-3, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_scaled_factor=1e9 +/// * A with digit_shift=0, baseline_vote_weight_scaled_factor=2e9, +/// max_extra_lockup_vote_weight_scaled_factor=0 +/// * B with digit_shift=-3, baseline_vote_weight_scaled_factor=1e9, +/// max_extra_lockup_vote_weight_scaled_factor=1e9 /// /// That would make 1.0 decimaled tokens of A as valuable as 2.0 decimaled tokens /// of B when unlocked. B tokens could be locked up to double their vote weight. As @@ -67,10 +72,11 @@ pub struct ConfigureVotingMint<'info> { /// /// Note that in this example, you need 1000 native B tokens before receiving 1 /// unit of vote weight. If the supplies were significantly lower, you could use -/// * A with digit_shift=3, baseline_vote_weight_scaled_factor=2e9, max_extra_lockup_vote_weight_scaled_factor=0 -/// * B with digit_shift=0, baseline_vote_weight_scaled_factor=1e9, max_extra_lockup_vote_weight_scaled_factor=1e9 +/// * A with digit_shift=3, baseline_vote_weight_scaled_factor=2e9, +/// max_extra_lockup_vote_weight_scaled_factor=0 +/// * B with digit_shift=0, baseline_vote_weight_scaled_factor=1e9, +/// max_extra_lockup_vote_weight_scaled_factor=1e9 /// to not lose precision on B tokens. -/// pub fn configure_voting_mint( ctx: Context, idx: u16, diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index fec788bf..ebe11ac1 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -1,11 +1,15 @@ -use anchor_lang::prelude::*; -use anchor_spl::associated_token::AssociatedToken; -use anchor_spl::token::{Mint, Token, TokenAccount}; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::{DepositEntry, Lockup, LockupKind, Voter}; -use mplx_staking_states::state::{LockupPeriod, Registrar}; - -use crate::clock_unix_timestamp; +use { + crate::clock_unix_timestamp, + anchor_lang::prelude::*, + anchor_spl::{ + associated_token::AssociatedToken, + token::{Mint, Token, TokenAccount}, + }, + mplx_staking_states::{ + error::VsrError, + state::{DepositEntry, Lockup, LockupKind, LockupPeriod, Registrar, Voter}, + }, +}; #[derive(Accounts)] pub struct CreateDepositEntry<'info> { @@ -47,8 +51,8 @@ pub struct CreateDepositEntry<'info> { /// /// - `deposit_entry_index`: deposit entry to use /// - `kind`: Type of lockup to use. -/// - `start_ts`: Start timestamp in seconds, defaults to current clock. -/// The lockup will end after `start + LockupPeriod::to_ts + COOLDOWNS_SECS. +/// - `start_ts`: Start timestamp in seconds, defaults to current clock. The lockup will end after +/// `start + LockupPeriod::to_ts + COOLDOWNS_SECS. /// /// Note that tokens will already be locked before start_ts, it only defines /// the vesting start time and the anchor for the periods computation. diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index f0b7f8b1..0222c19c 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -1,12 +1,11 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::Mint; -use anchor_spl::token::Token; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::Registrar; -use spl_governance::state::realm; -use std::mem::size_of; - -use crate::cpi_instructions; +use { + crate::cpi_instructions, + anchor_lang::prelude::*, + anchor_spl::token::{Mint, Token}, + mplx_staking_states::{error::VsrError, state::Registrar}, + spl_governance::state::realm, + std::mem::size_of, +}; #[derive(Accounts)] pub struct CreateRegistrar<'info> { diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 94ea1d69..5cf75830 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -1,12 +1,12 @@ -use anchor_lang::prelude::*; -use anchor_lang::solana_program::sysvar::instructions as tx_instructions; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::Registrar; -use mplx_staking_states::state::Voter; -use std::mem::size_of; - -use crate::cpi_instructions; -use crate::voter::VoterWeightRecord; +use { + crate::{cpi_instructions, voter::VoterWeightRecord}, + anchor_lang::{prelude::*, solana_program::sysvar::instructions as tx_instructions}, + mplx_staking_states::{ + error::VsrError, + state::{Registrar, Voter}, + }, + std::mem::size_of, +}; #[derive(Accounts)] pub struct CreateVoter<'info> { @@ -51,12 +51,14 @@ pub struct CreateVoter<'info> { pub instructions: UncheckedAccount<'info>, /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) + /// PDA(["reward_pool", deposit_authority , fill_authority], + /// reward_program) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA(["mining", mining owner , reward_pool], reward_program) + /// PDA(["mining", mining owner , reward_pool], + /// reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 518e6998..5a2cec88 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -1,12 +1,13 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount}; -use mplx_staking_states::{ - error::VsrError, - state::{LockupKind, LockupPeriod, Registrar, Voter}, +use { + crate::clock_unix_timestamp, + anchor_lang::prelude::*, + anchor_spl::token::{self, Token, TokenAccount}, + mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod, Registrar, Voter}, + }, }; -use crate::clock_unix_timestamp; - #[derive(Accounts)] pub struct Deposit<'info> { pub registrar: AccountLoader<'info, Registrar>, diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 5e3c90ef..47d035fe 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,13 +1,12 @@ -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount, Transfer}; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::LockupKind; -use mplx_staking_states::state::LockupPeriod; -use mplx_staking_states::state::Registrar; -use mplx_staking_states::state::Voter; - -use crate::clock_unix_timestamp; -use crate::cpi_instructions::extend_deposit; +use { + crate::{clock_unix_timestamp, cpi_instructions::extend_deposit}, + anchor_lang::prelude::*, + anchor_spl::token::{self, Token, TokenAccount, Transfer}, + mplx_staking_states::{ + error::VsrError, + state::{LockupKind, LockupPeriod, Registrar, Voter}, + }, +}; #[derive(Accounts)] pub struct RestakeDeposit<'info> { @@ -43,7 +42,8 @@ pub struct RestakeDeposit<'info> { pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA(["mining", mining owner , reward_pool], reward_program) + /// PDA(["mining", mining owner , reward_pool], + /// reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, @@ -65,8 +65,8 @@ impl<'info> RestakeDeposit<'info> { /// Prolongs the deposit /// -/// The deposit will be restaked with the same lockup period as it was previously in case it's not expired. -/// If the deposit has expired, it can be restaked with any LockupPeriod. +/// The deposit will be restaked with the same lockup period as it was previously in case it's not +/// expired. If the deposit has expired, it can be restaked with any LockupPeriod. /// The deposit entry must have been initialized with create_deposit_entry. /// /// `deposit_entry_index`: Index of the deposit entry. diff --git a/programs/voter-stake-registry/src/instructions/lock_tokens.rs b/programs/voter-stake-registry/src/instructions/lock_tokens.rs index ab089f70..abfc56e5 100644 --- a/programs/voter-stake-registry/src/instructions/lock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/lock_tokens.rs @@ -1,11 +1,11 @@ -use anchor_lang::prelude::*; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::LockupKind; -use mplx_staking_states::state::Registrar; -use mplx_staking_states::state::Voter; - -use crate::clock_unix_timestamp; -use crate::cpi_instructions; +use { + crate::{clock_unix_timestamp, cpi_instructions}, + anchor_lang::prelude::*, + mplx_staking_states::{ + error::VsrError, + state::{LockupKind, Registrar, Voter}, + }, +}; #[derive(Accounts)] pub struct LockTokens<'info> { @@ -23,12 +23,14 @@ pub struct LockTokens<'info> { pub voter_authority: Signer<'info>, /// CHECK: Reward Pool PDA will be checked in the rewards contract - /// PDA(["reward_pool", deposit_authority , fill_authority], reward_program) + /// PDA(["reward_pool", deposit_authority , fill_authority], + /// reward_program) #[account(mut)] pub reward_pool: UncheckedAccount<'info>, /// CHECK: mining PDA will be checked in the rewards contract - /// PDA(["mining", mining owner , reward_pool], reward_program) + /// PDA(["mining", mining owner , reward_pool], + /// reward_program) #[account(mut)] pub deposit_mining: UncheckedAccount<'info>, @@ -39,9 +41,9 @@ pub struct LockTokens<'info> { /// Transfers unlocked tokens from the source deposit entry to the target deposit entry. /// -/// Transfers token from one DepositEntry that is not LockupKind::None to another that is LockupKind::Constant. -/// In terms of business logic that means we want to deposit some tokens on DAO, -/// then we want to lock them up in order to receice rewards +/// Transfers token from one DepositEntry that is not LockupKind::None to another that is +/// LockupKind::Constant. In terms of business logic that means we want to deposit some tokens on +/// DAO, then we want to lock them up in order to receice rewards pub fn lock_tokens( ctx: Context, source_deposit_entry_index: u8, diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index 267aa517..d40ddfb7 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -1,10 +1,8 @@ -use crate::clock_unix_timestamp; -use crate::events::*; - -use anchor_lang::prelude::*; -use mplx_staking_states::state::LockupKind; -use mplx_staking_states::state::Registrar; -use mplx_staking_states::state::Voter; +use { + crate::{clock_unix_timestamp, events::*}, + anchor_lang::prelude::*, + mplx_staking_states::state::{LockupKind, Registrar, Voter}, +}; #[derive(Accounts)] pub struct LogVoterInfo<'info> { diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index 60bc0974..ae5c4509 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,18 +1,10 @@ -pub use claim::*; -pub use close_deposit_entry::*; -pub use close_voter::*; -pub use configure_voting_mint::*; -pub use create_deposit_entry::*; -pub use create_registrar::*; -pub use create_voter::*; -pub use deposit::*; -pub use extend_deposit::*; -pub use lock_tokens::*; -pub use log_voter_info::*; use solana_program::{clock::Clock, sysvar::Sysvar}; -pub use unlock_tokens::*; -pub use update_voter_weight_record::*; -pub use withdraw::*; +pub use { + claim::*, close_deposit_entry::*, close_voter::*, configure_voting_mint::*, + create_deposit_entry::*, create_registrar::*, create_voter::*, deposit::*, extend_deposit::*, + lock_tokens::*, log_voter_info::*, unlock_tokens::*, update_voter_weight_record::*, + withdraw::*, +}; mod claim; mod close_deposit_entry; diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index d4a8a89f..c15dcc6e 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -1,10 +1,11 @@ -use anchor_lang::prelude::*; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::Registrar; -use mplx_staking_states::state::Voter; -use mplx_staking_states::state::COOLDOWN_SECS; - -use crate::clock_unix_timestamp; +use { + crate::clock_unix_timestamp, + anchor_lang::prelude::*, + mplx_staking_states::{ + error::VsrError, + state::{Registrar, Voter, COOLDOWN_SECS}, + }, +}; #[derive(Accounts)] pub struct UnlockTokens<'info> { diff --git a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs index 1db5987f..377bb78b 100644 --- a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs +++ b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs @@ -1,7 +1,8 @@ -use anchor_lang::prelude::*; -use mplx_staking_states::state::{Registrar, Voter}; - -use crate::voter::VoterWeightRecord; +use { + crate::voter::VoterWeightRecord, + anchor_lang::prelude::*, + mplx_staking_states::state::{Registrar, Voter}, +}; #[derive(Accounts)] pub struct UpdateVoterWeightRecord<'info> { diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 927df426..2d8b400e 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,11 +1,17 @@ -use crate::clock_unix_timestamp; -use crate::cpi_instructions::withdraw_mining; -use crate::voter::{load_token_owner_record, VoterWeightRecord}; -use anchor_lang::prelude::*; -use anchor_spl::token::{self, Token, TokenAccount}; -use mplx_staking_states::error::VsrError; -use mplx_staking_states::state::{DepositEntry, LockupKind, LockupPeriod, Registrar, Voter}; -use mplx_staking_states::voter_seeds; +use { + crate::{ + clock_unix_timestamp, + cpi_instructions::withdraw_mining, + voter::{load_token_owner_record, VoterWeightRecord}, + }, + anchor_lang::prelude::*, + anchor_spl::token::{self, Token, TokenAccount}, + mplx_staking_states::{ + error::VsrError, + state::{DepositEntry, LockupKind, LockupPeriod, Registrar, Voter}, + voter_seeds, + }, +}; #[derive(Accounts)] pub struct Withdraw<'info> { diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 5b65fc9c..6c46537a 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -1,6 +1,8 @@ -use anchor_lang::prelude::*; -use instructions::*; -use mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}; +use { + anchor_lang::prelude::*, + instructions::*, + mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}, +}; pub mod cpi_instructions; pub mod events; @@ -22,10 +24,9 @@ declare_id!("9XZ7Ku7FYGVk3veKba6BRKTFXoYJyh4b4ZHC6MfaTUE8"); /// /// - 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. +/// - 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. diff --git a/programs/voter-stake-registry/src/voter.rs b/programs/voter-stake-registry/src/voter.rs index de0519ec..f1f08e8b 100644 --- a/programs/voter-stake-registry/src/voter.rs +++ b/programs/voter-stake-registry/src/voter.rs @@ -1,8 +1,9 @@ -use anchor_lang::prelude::*; -use mplx_staking_states::{error::VsrError, state::Registrar}; -use spl_governance::state::token_owner_record; - -use crate::vote_weight_record; +use { + crate::vote_weight_record, + anchor_lang::prelude::*, + mplx_staking_states::{error::VsrError, state::Registrar}, + spl_governance::state::token_owner_record, +}; pub fn load_token_owner_record( voter_authority: &Pubkey, diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index 9b8206bd..d8088bc6 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -1,17 +1,15 @@ -use std::cell::RefCell; -use std::rc::Rc; - -use anchor_lang::Key; - -use mplx_staking_states::state::Voter; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::{ - instruction::Instruction, - signature::{Keypair, Signer}, +use { + crate::*, + anchor_lang::Key, + mplx_staking_states::state::Voter, + solana_sdk::{ + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, Signer}, + }, + std::{cell::RefCell, rc::Rc}, }; -use crate::*; - #[derive(Clone)] pub struct AddinCookie { pub solana: Rc, diff --git a/programs/voter-stake-registry/tests/program_test/cookies.rs b/programs/voter-stake-registry/tests/program_test/cookies.rs index 42e5e797..e2d4fe4a 100644 --- a/programs/voter-stake-registry/tests/program_test/cookies.rs +++ b/programs/voter-stake-registry/tests/program_test/cookies.rs @@ -1,6 +1,4 @@ -use crate::utils::*; -use solana_program::pubkey::*; -use solana_sdk::signature::Keypair; +use {crate::utils::*, solana_program::pubkey::*, solana_sdk::signature::Keypair}; pub struct MintCookie { pub index: usize, diff --git a/programs/voter-stake-registry/tests/program_test/governance.rs b/programs/voter-stake-registry/tests/program_test/governance.rs index 7986044b..fc0d64b5 100644 --- a/programs/voter-stake-registry/tests/program_test/governance.rs +++ b/programs/voter-stake-registry/tests/program_test/governance.rs @@ -1,11 +1,13 @@ -use std::rc::Rc; - -use solana_program::instruction::Instruction; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::{Keypair, Signer}; -use spl_governance::state::{proposal, vote_record}; - -use crate::*; +use { + crate::*, + solana_program::instruction::Instruction, + solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer}, + }, + spl_governance::state::{proposal, vote_record}, + std::rc::Rc, +}; #[derive(Clone)] pub struct GovernanceCookie { diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index c180f8f6..d472467c 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -1,23 +1,21 @@ -use log::*; -use std::cell::RefCell; -use std::rc::Rc; -use std::{str::FromStr, sync::Arc, sync::RwLock}; - -use solana_program::{program_option::COption, program_pack::Pack}; -use solana_program_test::*; -use solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, +use { + self::rewards::RewardsCookie, + log::*, + solana_program::{program_option::COption, program_pack::Pack}, + solana_program_test::*, + solana_sdk::{ + pubkey::Pubkey, + signature::{Keypair, Signer}, + }, + spl_token::{state::*, *}, + std::{ + cell::RefCell, + rc::Rc, + str::FromStr, + sync::{Arc, RwLock}, + }, }; -use spl_token::{state::*, *}; - -pub use addin::*; -pub use cookies::*; -pub use governance::*; -pub use solana::*; -pub use utils::*; - -use self::rewards::RewardsCookie; +pub use {addin::*, cookies::*, governance::*, solana::*, utils::*}; pub mod addin; pub mod cookies; diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index 9eabdbef..bf8124c5 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -1,20 +1,19 @@ -use std::rc::Rc; - -use anchor_lang::prelude::*; -use anchor_lang::AnchorDeserialize; -use mplx_staking_states::state::LockupPeriod; -use solana_program_test::*; -use solana_sdk::program_pack::IsInitialized; -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_program, +use { + crate::SolanaCookie, + anchor_lang::{prelude::*, AnchorDeserialize}, + mplx_staking_states::state::LockupPeriod, + solana_program_test::*, + solana_sdk::{ + instruction::{AccountMeta, Instruction}, + program_pack::IsInitialized, + pubkey::Pubkey, + signature::Keypair, + signer::Signer, + system_program, + }, + std::rc::Rc, + voter_stake_registry::cpi_instructions::RewardsInstruction, }; -use voter_stake_registry::cpi_instructions::RewardsInstruction; - -use crate::SolanaCookie; pub struct RewardsCookie { pub solana: Rc, diff --git a/programs/voter-stake-registry/tests/program_test/solana.rs b/programs/voter-stake-registry/tests/program_test/solana.rs index 1144810f..dc1d177f 100644 --- a/programs/voter-stake-registry/tests/program_test/solana.rs +++ b/programs/voter-stake-registry/tests/program_test/solana.rs @@ -1,18 +1,21 @@ -use std::cell::RefCell; -use std::sync::{Arc, RwLock}; - -use anchor_lang::AccountDeserialize; -use anchor_spl::token::TokenAccount; -use solana_program::{program_pack::Pack, rent::*, system_instruction}; -use solana_program_test::*; -use solana_sdk::{ - account::ReadableAccount, - instruction::Instruction, - pubkey::Pubkey, - signature::{Keypair, Signer}, - transaction::Transaction, +use { + anchor_lang::AccountDeserialize, + anchor_spl::token::TokenAccount, + solana_program::{program_pack::Pack, rent::*, system_instruction}, + solana_program_test::*, + solana_sdk::{ + account::ReadableAccount, + instruction::Instruction, + pubkey::Pubkey, + signature::{Keypair, Signer}, + transaction::Transaction, + }, + spl_token::*, + std::{ + cell::RefCell, + sync::{Arc, RwLock}, + }, }; -use spl_token::*; pub struct SolanaCookie { pub context: RefCell, diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index 19cd16d6..ff7ad3af 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,14 +1,13 @@ -use std::borrow::BorrowMut; - -use bytemuck::{bytes_of, Contiguous}; -use solana_program::program_error::ProgramError; -use solana_program_test::{BanksClientError, ProgramTestContext}; -use solana_sdk::program_pack::Pack; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::Keypair; -use solana_sdk::signer::Signer; -use solana_sdk::system_instruction; -use solana_sdk::transaction::Transaction; +use { + bytemuck::{bytes_of, Contiguous}, + solana_program::program_error::ProgramError, + solana_program_test::{BanksClientError, ProgramTestContext}, + solana_sdk::{ + program_pack::Pack, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, + transaction::Transaction, + }, + std::borrow::BorrowMut, +}; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index 298e11f4..cc4cda18 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,8 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; #[tokio::test] diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 2c45522b..5dd8850a 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -1,9 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -use program_test::*; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 8711a05d..3a537d74 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -1,9 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -use program_test::*; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 69c7faa7..3f1ff98e 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -1,8 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index ba398864..9d1f4d0a 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -1,9 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use solana_program_test::*; -use solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}; - -use program_test::*; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 6e4a0eb2..56447f42 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -1,9 +1,11 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{ - clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{ + clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, + }, }; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 3cb0a9ad..2a388e77 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,9 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; - -use program_test::*; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index ca78c14c..a21358e7 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,8 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index d858ad47..558911d0 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,8 +1,10 @@ -use anchor_spl::token::TokenAccount; -use mplx_staking_states::state::{LockupKind, LockupPeriod}; -use program_test::*; -use solana_program_test::*; -use solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}; +use { + anchor_spl::token::TokenAccount, + mplx_staking_states::state::{LockupKind, LockupPeriod}, + program_test::*, + solana_program_test::*, + solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, +}; mod program_test; #[tokio::test] async fn test_voting() -> Result<(), TransportError> { diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..63bbd58f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,5 @@ +comment_width = 100 +edition = "2021" +group_imports = "One" +imports_granularity = "One" +wrap_comments = true From b5270efd357c51dffe2dba534decf26b6cddfceb Mon Sep 17 00:00:00 2001 From: Vadim Date: Mon, 1 Jul 2024 13:34:12 +0200 Subject: [PATCH 57/59] refactor: drop unused errors --- program-states/src/error.rs | 105 ++++++++++-------------------------- 1 file changed, 27 insertions(+), 78 deletions(-) diff --git a/program-states/src/error.rs b/program-states/src/error.rs index cde913c0..fa4028bf 100644 --- a/program-states/src/error.rs +++ b/program-states/src/error.rs @@ -3,138 +3,87 @@ use anchor_lang::prelude::*; #[error_code] pub enum VsrError { // 6000 / 0x1770 - #[msg("Exchange rate must be greater than zero")] - InvalidRate, - // 6001 / 0x1771 - #[msg("")] - RatesFull, - // 6002 / 0x1772 #[msg("")] VotingMintNotFound, - // 6003 / 0x1773 - #[msg("")] - DepositEntryNotFound, - // 6004 / 0x1774 - #[msg("")] - DepositEntryFull, - // 6005 / 0x1775 + // 6001 / 0x1771 #[msg("")] VotingTokenNonZero, - // 6006 / 0x1776 + // 6002 / 0x1772 #[msg("")] OutOfBoundsDepositEntryIndex, - // 6007 / 0x1777 + // 6003 / 0x1773 #[msg("")] UnusedDepositEntryIndex, - // 6008 / 0x1778 + // 6004 / 0x1774 #[msg("")] InsufficientUnlockedTokens, - // 6009 / 0x1779 - #[msg("")] - UnableToConvert, - // 6010 / 0x177a + // 6005 / 0x1775 #[msg("")] InvalidLockupPeriod, - // 6011 / 0x177b - #[msg("")] - InvalidEndTs, - // 6012 / 0x177c - #[msg("")] - InvalidDays, - // 6013 / 0x177d + // 6006 / 0x1776 #[msg("")] VotingMintConfigIndexAlreadyInUse, - // 6014 / 0x177e + // 6007 / 0x1777 #[msg("")] OutOfBoundsVotingMintConfigIndex, - // 6015 / 0x177f - #[msg("Exchange rate decimals cannot be larger than registrar decimals")] - InvalidDecimals, - // 6016 / 0x1780 - #[msg("")] - InvalidToDepositAndWithdrawInOneSlot, - // 6017 / 0x1781 - #[msg("")] - ShouldBeTheFirstIxInATx, - // 6018 / 0x1782 + // 6008 / 0x1778 #[msg("")] ForbiddenCpi, - // 6019 / 0x1783 + // 6009 / 0x1779 #[msg("")] InvalidMint, - // 6020 / 0x1784 - #[msg("")] - DebugInstruction, - // 6021 / 0x1785 - #[msg("")] - ClawbackNotAllowedOnDeposit, - // 6022 / 0x1786 + // 6010 / 0x177a #[msg("")] DepositStillLocked, - // 6023 / 0x1787 + // 6011 / 0x177b #[msg("")] InvalidAuthority, - // 6024 / 0x1788 + // 6012 / 0x177c #[msg("")] InvalidTokenOwnerRecord, - // 6025 / 0x1789 + // 6013 / 0x177d #[msg("")] InvalidRealmAuthority, - // 6026 / 0x178a + // 6014 / 0x177e #[msg("")] VoterWeightOverflow, - // 6027 / 0x178b + // 6015 / 0x177f #[msg("")] LockupSaturationMustBePositive, - // 6028 / 0x178c + // 6016 / 0x1780 #[msg("")] VotingMintConfiguredWithDifferentIndex, - // 6029 / 0x178d + // 6017 / 0x1781 #[msg("")] InternalProgramError, - // 6030 / 0x178e - #[msg("")] - InsufficientLockedTokens, - // 6031 / 0x178f - #[msg("")] - MustKeepTokensLocked, - // 6032 / 0x1790 + // 6018 / 0x1782 #[msg("")] InvalidLockupKind, - // 6033 / 0x1791 - #[msg("")] - InvalidChangeToClawbackDepositEntry, - // 6034 / 0x1792 - #[msg("")] - InternalErrorBadLockupVoteWeight, - // 6035 / 0x1793 - #[msg("")] - DepositStartTooFarInFuture, - // 6036 / 0x1794 + // 6019 / 0x1783 #[msg("")] VaultTokenNonZero, - // 6037 / 0x1795 + // 6020 / 0x1784 #[msg("")] InvalidTimestampArguments, - // 6038 / 0x1796 + // 6021 / 0x1785 #[msg("")] UnlockMustBeCalledFirst, - // 6039 / 0x1797 + // 6022 / 0x1786 #[msg("")] UnlockAlreadyRequested, - // 6040 / 0x1798 + // 6023 / 0x1787 #[msg("")] RestakeDepositIsNotAllowed, - // 6041 / 0x1799 + // 6024 / 0x1788 #[msg("To deposit additional tokens, extend the deposit")] DepositingIsForbidded, - // 6042 / 0x179a + // 6025 / 0x1789 #[msg("Cpi call must return data, but data is absent")] CpiReturnDataIsAbsent, - // 6043 / 0x179b + // 6026 / 0x178a #[msg("The source for the transfer only can be a deposit on DAO")] LockingIsForbidded, - // 6044 / 0x179c + // 6027 / 0x178b #[msg("Locking up tokens is only allowed for freshly-deposited deposit entry")] DepositEntryIsOld, } From d7860d385a8a1951b40e9067cd79098b599a4b86 Mon Sep 17 00:00:00 2001 From: Vadim Date: Mon, 1 Jul 2024 13:55:29 +0200 Subject: [PATCH 58/59] feat: update IDL --- idl/voter_stake_registry.ts | 1022 +++++++++-------------------------- 1 file changed, 254 insertions(+), 768 deletions(-) diff --git a/idl/voter_stake_registry.ts b/idl/voter_stake_registry.ts index 09955534..95aab438 100644 --- a/idl/voter_stake_registry.ts +++ b/idl/voter_stake_registry.ts @@ -367,6 +367,10 @@ export type VoterStakeRegistry = { "type": { "defined": "LockupPeriod" } + }, + { + "name": "delegate", + "type": "publicKey" } ] }, @@ -868,7 +872,7 @@ export type VoterStakeRegistry = { ], "accounts": [ { - "name": "registrar", + "name": "Registrar", "docs": [ "Instance of a voting rights distributor." ], @@ -921,7 +925,7 @@ export type VoterStakeRegistry = { } }, { - "name": "voter", + "name": "Voter", "docs": [ "User account for minting voting rights." ], @@ -969,63 +973,6 @@ export type VoterStakeRegistry = { } ], "types": [ - { - "name": "VestingInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" - ], - "type": "u64" - }, - { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" - ], - "type": "u64" - } - ] - } - }, - { - "name": "LockingInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount of locked tokens" - ], - "type": "u64" - }, - { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" - ], - "type": { - "option": "u64" - } - }, - { - "name": "vesting", - "docs": [ - "Information about vesting, if any" - ], - "type": { - "option": { - "defined": "VestingInfo" - } - } - } - ] - } - }, { "name": "DepositEntry", "docs": [ @@ -1036,9 +983,6 @@ export type VoterStakeRegistry = { "fields": [ { "name": "lockup", - "docs": [ - "Locked state." - ], "type": { "defined": "Lockup" } @@ -1146,6 +1090,46 @@ export type VoterStakeRegistry = { ] } }, + { + "name": "LockupKind", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "Constant" + } + ] + } + }, + { + "name": "LockupPeriod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "ThreeMonths" + }, + { + "name": "SixMonths" + }, + { + "name": "OneYear" + }, + { + "name": "Flex" + }, + { + "name": "Test" + } + ] + } + }, { "name": "VotingMintConfig", "docs": [ @@ -1220,235 +1204,64 @@ export type VoterStakeRegistry = { } }, { - "name": "RewardsInstruction", - "type": { - "kind": "enum", - "variants": [ - { - "name": "InitializePool", - "fields": [ - { - "name": "deposit_authority", - "docs": [ - "Account responsible for charging mining owners" - ], - "type": "publicKey" - }, - { - "name": "fill_authority", - "docs": [ - "Account can fill the reward vault" - ], - "type": "publicKey" - }, - { - "name": "distribution_authority", - "docs": [ - "Account can distribute rewards for stakers" - ], - "type": "publicKey" - } - ] - }, - { - "name": "FillVault", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to fill" - ], - "type": "u64" - }, - { - "name": "distribution_ends_at", - "docs": [ - "Rewards distribution ends at given date" - ], - "type": "u64" - } - ] - }, - { - "name": "InitializeMining", - "fields": [ - { - "name": "mining_owner", - "docs": [ - "Represent the end-user, owner of the mining" - ], - "type": "publicKey" - } - ] - }, - { - "name": "DepositMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to deposit" - ], - "type": "u64" - }, - { - "name": "lockup_period", - "docs": [ - "Lockup Period" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "WithdrawMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to withdraw" - ], - "type": "u64" - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "Claim" - }, - { - "name": "RestakeDeposit", - "fields": [ - { - "name": "old_lockup_period", - "docs": [ - "Lockup period before restaking. Actually it's only needed", - "for Flex to AnyPeriod edge case" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "new_lockup_period", - "docs": [ - "Requested lockup period for restaking" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "deposit_start_ts", - "docs": [ - "Deposit start_ts" - ], - "type": "u64" - }, - { - "name": "base_amount", - "docs": [ - "Amount of tokens to be restaked, this", - "number cannot be decreased. It reflects the number of staked tokens", - "before the restake function call" - ], - "type": "u64" - }, - { - "name": "additional_amount", - "docs": [ - "In case user wants to increase it's staked number of tokens,", - "the addition amount might be provided" - ], - "type": "u64" - }, - { - "name": "mining_owner", - "docs": [ - "The wallet who owns the mining account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "DistributeRewards" - } - ] - } - }, - { - "name": "LockupPeriod", + "name": "LockingInfo", "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "ThreeMonths" - }, - { - "name": "SixMonths" - }, + "kind": "struct", + "fields": [ { - "name": "OneYear" + "name": "amount", + "docs": [ + "Amount of locked tokens" + ], + "type": "u64" }, { - "name": "Flex" + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" + ], + "type": { + "option": "u64" + } }, { - "name": "Test" + "name": "vesting", + "docs": [ + "Information about vesting, if any" + ], + "type": { + "option": { + "defined": "VestingInfo" + } + } } ] } }, { - "name": "LockupKind", + "name": "VestingInfo", "type": { - "kind": "enum", - "variants": [ + "kind": "struct", + "fields": [ { - "name": "None" + "name": "rate", + "docs": [ + "Amount of tokens vested each period" + ], + "type": "u64" }, { - "name": "Constant" + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" + ], + "type": "u64" } ] } } ], "events": [ - { - "name": "VoterInfo", - "fields": [ - { - "name": "votingPower", - "type": "u64", - "index": false - }, - { - "name": "votingPowerBaseline", - "type": "u64", - "index": false - } - ] - }, { "name": "DepositEntryInfo", "fields": [ @@ -1487,231 +1300,161 @@ export type VoterStakeRegistry = { "index": false } ] + }, + { + "name": "VoterInfo", + "fields": [ + { + "name": "votingPower", + "type": "u64", + "index": false + }, + { + "name": "votingPowerBaseline", + "type": "u64", + "index": false + } + ] } ], "errors": [ { "code": 6000, - "name": "InvalidRate", - "msg": "Exchange rate must be greater than zero" + "name": "VotingMintNotFound", + "msg": "" }, { "code": 6001, - "name": "RatesFull", + "name": "VotingTokenNonZero", "msg": "" }, { "code": 6002, - "name": "VotingMintNotFound", + "name": "OutOfBoundsDepositEntryIndex", "msg": "" }, { "code": 6003, - "name": "DepositEntryNotFound", + "name": "UnusedDepositEntryIndex", "msg": "" }, { "code": 6004, - "name": "DepositEntryFull", + "name": "InsufficientUnlockedTokens", "msg": "" }, { "code": 6005, - "name": "VotingTokenNonZero", + "name": "InvalidLockupPeriod", "msg": "" }, { "code": 6006, - "name": "OutOfBoundsDepositEntryIndex", + "name": "VotingMintConfigIndexAlreadyInUse", "msg": "" }, { "code": 6007, - "name": "UnusedDepositEntryIndex", + "name": "OutOfBoundsVotingMintConfigIndex", "msg": "" }, { "code": 6008, - "name": "InsufficientUnlockedTokens", + "name": "ForbiddenCpi", "msg": "" }, { "code": 6009, - "name": "UnableToConvert", + "name": "InvalidMint", "msg": "" }, { "code": 6010, - "name": "InvalidLockupPeriod", + "name": "DepositStillLocked", "msg": "" }, { "code": 6011, - "name": "InvalidEndTs", + "name": "InvalidAuthority", "msg": "" }, { "code": 6012, - "name": "InvalidDays", + "name": "InvalidTokenOwnerRecord", "msg": "" }, { "code": 6013, - "name": "VotingMintConfigIndexAlreadyInUse", + "name": "InvalidRealmAuthority", "msg": "" }, { "code": 6014, - "name": "OutOfBoundsVotingMintConfigIndex", + "name": "VoterWeightOverflow", "msg": "" }, { "code": 6015, - "name": "InvalidDecimals", - "msg": "Exchange rate decimals cannot be larger than registrar decimals" + "name": "LockupSaturationMustBePositive", + "msg": "" }, { "code": 6016, - "name": "InvalidToDepositAndWithdrawInOneSlot", + "name": "VotingMintConfiguredWithDifferentIndex", "msg": "" }, { "code": 6017, - "name": "ShouldBeTheFirstIxInATx", + "name": "InternalProgramError", "msg": "" }, { "code": 6018, - "name": "ForbiddenCpi", + "name": "InvalidLockupKind", "msg": "" }, { "code": 6019, - "name": "InvalidMint", + "name": "VaultTokenNonZero", "msg": "" }, { "code": 6020, - "name": "DebugInstruction", + "name": "InvalidTimestampArguments", "msg": "" }, { "code": 6021, - "name": "ClawbackNotAllowedOnDeposit", + "name": "UnlockMustBeCalledFirst", "msg": "" }, { "code": 6022, - "name": "DepositStillLocked", + "name": "UnlockAlreadyRequested", "msg": "" }, { "code": 6023, - "name": "InvalidAuthority", + "name": "RestakeDepositIsNotAllowed", "msg": "" }, { "code": 6024, - "name": "InvalidTokenOwnerRecord", - "msg": "" + "name": "DepositingIsForbidded", + "msg": "To deposit additional tokens, extend the deposit" }, { "code": 6025, - "name": "InvalidRealmAuthority", - "msg": "" + "name": "CpiReturnDataIsAbsent", + "msg": "Cpi call must return data, but data is absent" }, { "code": 6026, - "name": "VoterWeightOverflow", - "msg": "" - }, - { - "code": 6027, - "name": "LockupSaturationMustBePositive", - "msg": "" - }, - { - "code": 6028, - "name": "VotingMintConfiguredWithDifferentIndex", - "msg": "" - }, - { - "code": 6029, - "name": "InternalProgramError", - "msg": "" - }, - { - "code": 6030, - "name": "InsufficientLockedTokens", - "msg": "" - }, - { - "code": 6031, - "name": "MustKeepTokensLocked", - "msg": "" - }, - { - "code": 6032, - "name": "InvalidLockupKind", - "msg": "" - }, - { - "code": 6033, - "name": "InvalidChangeToClawbackDepositEntry", - "msg": "" - }, - { - "code": 6034, - "name": "InternalErrorBadLockupVoteWeight", - "msg": "" - }, - { - "code": 6035, - "name": "DepositStartTooFarInFuture", - "msg": "" - }, - { - "code": 6036, - "name": "VaultTokenNonZero", - "msg": "" - }, - { - "code": 6037, - "name": "InvalidTimestampArguments", - "msg": "" - }, - { - "code": 6038, - "name": "UnlockMustBeCalledFirst", - "msg": "" - }, - { - "code": 6039, - "name": "UnlockAlreadyRequested", - "msg": "" - }, - { - "code": 6040, - "name": "RestakeDepositIsNotAllowed", - "msg": "" - }, - { - "code": 6041, - "name": "DepositingIsForbidded", - "msg": "To deposit additional tokens, extend the deposit" - }, - { - "code": 6042, - "name": "CpiReturnDataIsAbsent", - "msg": "Cpi call must return data, but data is absent" - }, - { - "code": 6043, "name": "LockingIsForbidded", "msg": "The source for the transfer only can be a deposit on DAO" }, { - "code": 6044, + "code": 6027, "name": "DepositEntryIsOld", "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" } @@ -2087,6 +1830,10 @@ export const IDL: VoterStakeRegistry = { "type": { "defined": "LockupPeriod" } + }, + { + "name": "delegate", + "type": "publicKey" } ] }, @@ -2588,7 +2335,7 @@ export const IDL: VoterStakeRegistry = { ], "accounts": [ { - "name": "registrar", + "name": "Registrar", "docs": [ "Instance of a voting rights distributor." ], @@ -2641,7 +2388,7 @@ export const IDL: VoterStakeRegistry = { } }, { - "name": "voter", + "name": "Voter", "docs": [ "User account for minting voting rights." ], @@ -2689,63 +2436,6 @@ export const IDL: VoterStakeRegistry = { } ], "types": [ - { - "name": "VestingInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "rate", - "docs": [ - "Amount of tokens vested each period" - ], - "type": "u64" - }, - { - "name": "nextTimestamp", - "docs": [ - "Time of the next upcoming vesting" - ], - "type": "u64" - } - ] - } - }, - { - "name": "LockingInfo", - "type": { - "kind": "struct", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount of locked tokens" - ], - "type": "u64" - }, - { - "name": "endTimestamp", - "docs": [ - "Time at which the lockup fully ends (None for Constant lockup)" - ], - "type": { - "option": "u64" - } - }, - { - "name": "vesting", - "docs": [ - "Information about vesting, if any" - ], - "type": { - "option": { - "defined": "VestingInfo" - } - } - } - ] - } - }, { "name": "DepositEntry", "docs": [ @@ -2756,9 +2446,6 @@ export const IDL: VoterStakeRegistry = { "fields": [ { "name": "lockup", - "docs": [ - "Locked state." - ], "type": { "defined": "Lockup" } @@ -2866,6 +2553,46 @@ export const IDL: VoterStakeRegistry = { ] } }, + { + "name": "LockupKind", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "Constant" + } + ] + } + }, + { + "name": "LockupPeriod", + "type": { + "kind": "enum", + "variants": [ + { + "name": "None" + }, + { + "name": "ThreeMonths" + }, + { + "name": "SixMonths" + }, + { + "name": "OneYear" + }, + { + "name": "Flex" + }, + { + "name": "Test" + } + ] + } + }, { "name": "VotingMintConfig", "docs": [ @@ -2940,235 +2667,64 @@ export const IDL: VoterStakeRegistry = { } }, { - "name": "RewardsInstruction", - "type": { - "kind": "enum", - "variants": [ - { - "name": "InitializePool", - "fields": [ - { - "name": "deposit_authority", - "docs": [ - "Account responsible for charging mining owners" - ], - "type": "publicKey" - }, - { - "name": "fill_authority", - "docs": [ - "Account can fill the reward vault" - ], - "type": "publicKey" - }, - { - "name": "distribution_authority", - "docs": [ - "Account can distribute rewards for stakers" - ], - "type": "publicKey" - } - ] - }, - { - "name": "FillVault", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to fill" - ], - "type": "u64" - }, - { - "name": "distribution_ends_at", - "docs": [ - "Rewards distribution ends at given date" - ], - "type": "u64" - } - ] - }, - { - "name": "InitializeMining", - "fields": [ - { - "name": "mining_owner", - "docs": [ - "Represent the end-user, owner of the mining" - ], - "type": "publicKey" - } - ] - }, - { - "name": "DepositMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to deposit" - ], - "type": "u64" - }, - { - "name": "lockup_period", - "docs": [ - "Lockup Period" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "WithdrawMining", - "fields": [ - { - "name": "amount", - "docs": [ - "Amount to withdraw" - ], - "type": "u64" - }, - { - "name": "owner", - "docs": [ - "Specifies the owner of the Mining Account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "Claim" - }, - { - "name": "RestakeDeposit", - "fields": [ - { - "name": "old_lockup_period", - "docs": [ - "Lockup period before restaking. Actually it's only needed", - "for Flex to AnyPeriod edge case" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "new_lockup_period", - "docs": [ - "Requested lockup period for restaking" - ], - "type": { - "defined": "LockupPeriod" - } - }, - { - "name": "deposit_start_ts", - "docs": [ - "Deposit start_ts" - ], - "type": "u64" - }, - { - "name": "base_amount", - "docs": [ - "Amount of tokens to be restaked, this", - "number cannot be decreased. It reflects the number of staked tokens", - "before the restake function call" - ], - "type": "u64" - }, - { - "name": "additional_amount", - "docs": [ - "In case user wants to increase it's staked number of tokens,", - "the addition amount might be provided" - ], - "type": "u64" - }, - { - "name": "mining_owner", - "docs": [ - "The wallet who owns the mining account" - ], - "type": "publicKey" - } - ] - }, - { - "name": "DistributeRewards" - } - ] - } - }, - { - "name": "LockupPeriod", + "name": "LockingInfo", "type": { - "kind": "enum", - "variants": [ - { - "name": "None" - }, - { - "name": "ThreeMonths" - }, - { - "name": "SixMonths" - }, + "kind": "struct", + "fields": [ { - "name": "OneYear" + "name": "amount", + "docs": [ + "Amount of locked tokens" + ], + "type": "u64" }, { - "name": "Flex" + "name": "endTimestamp", + "docs": [ + "Time at which the lockup fully ends (None for Constant lockup)" + ], + "type": { + "option": "u64" + } }, { - "name": "Test" + "name": "vesting", + "docs": [ + "Information about vesting, if any" + ], + "type": { + "option": { + "defined": "VestingInfo" + } + } } ] } }, { - "name": "LockupKind", + "name": "VestingInfo", "type": { - "kind": "enum", - "variants": [ + "kind": "struct", + "fields": [ { - "name": "None" + "name": "rate", + "docs": [ + "Amount of tokens vested each period" + ], + "type": "u64" }, { - "name": "Constant" + "name": "nextTimestamp", + "docs": [ + "Time of the next upcoming vesting" + ], + "type": "u64" } ] } } ], "events": [ - { - "name": "VoterInfo", - "fields": [ - { - "name": "votingPower", - "type": "u64", - "index": false - }, - { - "name": "votingPowerBaseline", - "type": "u64", - "index": false - } - ] - }, { "name": "DepositEntryInfo", "fields": [ @@ -3207,231 +2763,161 @@ export const IDL: VoterStakeRegistry = { "index": false } ] + }, + { + "name": "VoterInfo", + "fields": [ + { + "name": "votingPower", + "type": "u64", + "index": false + }, + { + "name": "votingPowerBaseline", + "type": "u64", + "index": false + } + ] } ], "errors": [ { "code": 6000, - "name": "InvalidRate", - "msg": "Exchange rate must be greater than zero" - }, - { - "code": 6001, - "name": "RatesFull", - "msg": "" - }, - { - "code": 6002, "name": "VotingMintNotFound", "msg": "" }, { - "code": 6003, - "name": "DepositEntryNotFound", - "msg": "" - }, - { - "code": 6004, - "name": "DepositEntryFull", - "msg": "" - }, - { - "code": 6005, + "code": 6001, "name": "VotingTokenNonZero", "msg": "" }, { - "code": 6006, + "code": 6002, "name": "OutOfBoundsDepositEntryIndex", "msg": "" }, { - "code": 6007, + "code": 6003, "name": "UnusedDepositEntryIndex", "msg": "" }, { - "code": 6008, + "code": 6004, "name": "InsufficientUnlockedTokens", "msg": "" }, { - "code": 6009, - "name": "UnableToConvert", - "msg": "" - }, - { - "code": 6010, + "code": 6005, "name": "InvalidLockupPeriod", "msg": "" }, { - "code": 6011, - "name": "InvalidEndTs", - "msg": "" - }, - { - "code": 6012, - "name": "InvalidDays", - "msg": "" - }, - { - "code": 6013, + "code": 6006, "name": "VotingMintConfigIndexAlreadyInUse", "msg": "" }, { - "code": 6014, + "code": 6007, "name": "OutOfBoundsVotingMintConfigIndex", "msg": "" }, { - "code": 6015, - "name": "InvalidDecimals", - "msg": "Exchange rate decimals cannot be larger than registrar decimals" - }, - { - "code": 6016, - "name": "InvalidToDepositAndWithdrawInOneSlot", - "msg": "" - }, - { - "code": 6017, - "name": "ShouldBeTheFirstIxInATx", - "msg": "" - }, - { - "code": 6018, + "code": 6008, "name": "ForbiddenCpi", "msg": "" }, { - "code": 6019, + "code": 6009, "name": "InvalidMint", "msg": "" }, { - "code": 6020, - "name": "DebugInstruction", - "msg": "" - }, - { - "code": 6021, - "name": "ClawbackNotAllowedOnDeposit", - "msg": "" - }, - { - "code": 6022, + "code": 6010, "name": "DepositStillLocked", "msg": "" }, { - "code": 6023, + "code": 6011, "name": "InvalidAuthority", "msg": "" }, { - "code": 6024, + "code": 6012, "name": "InvalidTokenOwnerRecord", "msg": "" }, { - "code": 6025, + "code": 6013, "name": "InvalidRealmAuthority", "msg": "" }, { - "code": 6026, + "code": 6014, "name": "VoterWeightOverflow", "msg": "" }, { - "code": 6027, + "code": 6015, "name": "LockupSaturationMustBePositive", "msg": "" }, { - "code": 6028, + "code": 6016, "name": "VotingMintConfiguredWithDifferentIndex", "msg": "" }, { - "code": 6029, + "code": 6017, "name": "InternalProgramError", "msg": "" }, { - "code": 6030, - "name": "InsufficientLockedTokens", - "msg": "" - }, - { - "code": 6031, - "name": "MustKeepTokensLocked", - "msg": "" - }, - { - "code": 6032, + "code": 6018, "name": "InvalidLockupKind", "msg": "" }, { - "code": 6033, - "name": "InvalidChangeToClawbackDepositEntry", - "msg": "" - }, - { - "code": 6034, - "name": "InternalErrorBadLockupVoteWeight", - "msg": "" - }, - { - "code": 6035, - "name": "DepositStartTooFarInFuture", - "msg": "" - }, - { - "code": 6036, + "code": 6019, "name": "VaultTokenNonZero", "msg": "" }, { - "code": 6037, + "code": 6020, "name": "InvalidTimestampArguments", "msg": "" }, { - "code": 6038, + "code": 6021, "name": "UnlockMustBeCalledFirst", "msg": "" }, { - "code": 6039, + "code": 6022, "name": "UnlockAlreadyRequested", "msg": "" }, { - "code": 6040, + "code": 6023, "name": "RestakeDepositIsNotAllowed", "msg": "" }, { - "code": 6041, + "code": 6024, "name": "DepositingIsForbidded", "msg": "To deposit additional tokens, extend the deposit" }, { - "code": 6042, + "code": 6025, "name": "CpiReturnDataIsAbsent", "msg": "Cpi call must return data, but data is absent" }, { - "code": 6043, + "code": 6026, "name": "LockingIsForbidded", "msg": "The source for the transfer only can be a deposit on DAO" }, { - "code": 6044, + "code": 6027, "name": "DepositEntryIsOld", "msg": "Locking up tokens is only allowed for freshly-deposited deposit entry" } From 036713fff364f7c5a0afe1f873a44fa6d3157e7a Mon Sep 17 00:00:00 2001 From: Stanislav Cherviakov Date: Wed, 3 Jul 2024 11:10:37 +0100 Subject: [PATCH 59/59] updated imports granularity to module --- program-states/src/state/deposit_entry.rs | 13 +++---- program-states/src/state/lockup.rs | 3 +- program-states/src/state/mod.rs | 6 +++- program-states/src/state/registrar.rs | 9 +++-- program-states/src/state/voter.rs | 11 +++--- .../src/state/voting_mint_config.rs | 4 ++- .../src/cpi_instructions.rs | 22 +++++------- .../src/instructions/claim.rs | 15 ++++---- .../src/instructions/close_deposit_entry.rs | 10 ++---- .../src/instructions/close_voter.rs | 20 +++++------ .../src/instructions/configure_voting_mint.rs | 12 +++---- .../src/instructions/create_deposit_entry.rs | 18 ++++------ .../src/instructions/create_registrar.rs | 15 ++++---- .../src/instructions/create_voter.rs | 16 ++++----- .../src/instructions/deposit.rs | 14 +++----- .../src/instructions/extend_deposit.rs | 15 ++++---- .../src/instructions/lock_tokens.rs | 12 +++---- .../src/instructions/log_voter_info.rs | 9 +++-- .../src/instructions/mod.rs | 23 ++++++++---- .../src/instructions/unlock_tokens.rs | 12 +++---- .../update_voter_weight_record.rs | 8 ++--- .../src/instructions/withdraw.rs | 22 +++++------- programs/voter-stake-registry/src/lib.rs | 8 ++--- programs/voter-stake-registry/src/voter.rs | 11 +++--- .../tests/program_test/addin.rs | 19 +++++----- .../tests/program_test/cookies.rs | 4 ++- .../tests/program_test/governance.rs | 16 ++++----- .../tests/program_test/mod.rs | 36 +++++++++---------- .../tests/program_test/rewards.rs | 29 +++++++-------- .../tests/program_test/solana.rs | 32 ++++++++--------- .../tests/program_test/utils.rs | 20 +++++------ .../tests/test_all_deposits.rs | 14 ++++---- .../voter-stake-registry/tests/test_basic.rs | 14 ++++---- .../voter-stake-registry/tests/test_claim.rs | 14 ++++---- .../tests/test_deposit_constant.rs | 15 ++++---- .../tests/test_deposit_no_locking.rs | 15 ++++---- .../tests/test_extend_deposit.rs | 17 +++++---- .../voter-stake-registry/tests/test_lockup.rs | 14 ++++---- .../tests/test_log_voter_info.rs | 14 ++++---- .../voter-stake-registry/tests/test_voting.rs | 14 ++++---- rustfmt.toml | 2 +- 41 files changed, 271 insertions(+), 326 deletions(-) diff --git a/program-states/src/state/deposit_entry.rs b/program-states/src/state/deposit_entry.rs index cab700fb..117731c5 100644 --- a/program-states/src/state/deposit_entry.rs +++ b/program-states/src/state/deposit_entry.rs @@ -1,7 +1,5 @@ -use { - crate::state::lockup::{Lockup, LockupKind}, - anchor_lang::prelude::*, -}; +use crate::state::lockup::{Lockup, LockupKind}; +use anchor_lang::prelude::*; /// Bookkeeping for a single deposit for a given mint and lockup schedule. #[zero_copy] @@ -64,10 +62,9 @@ impl DepositEntry { #[cfg(test)] mod tests { - use { - super::*, - crate::state::{LockupKind::Constant, LockupPeriod, VotingMintConfig}, - }; + use super::*; + use crate::state::LockupKind::Constant; + use crate::state::{LockupPeriod, VotingMintConfig}; #[test] pub fn far_future_lockup_start_test() -> Result<()> { diff --git a/program-states/src/state/lockup.rs b/program-states/src/state/lockup.rs index fd9a3953..4199a4f5 100644 --- a/program-states/src/state/lockup.rs +++ b/program-states/src/state/lockup.rs @@ -1,4 +1,5 @@ -use {crate::error::*, anchor_lang::prelude::*}; +use crate::error::*; +use anchor_lang::prelude::*; /// Seconds in one day. pub const SECS_PER_DAY: u64 = 86_400; diff --git a/program-states/src/state/mod.rs b/program-states/src/state/mod.rs index e3b0cd99..2c692bf5 100644 --- a/program-states/src/state/mod.rs +++ b/program-states/src/state/mod.rs @@ -1,4 +1,8 @@ -pub use {deposit_entry::*, lockup::*, registrar::*, voter::*, voting_mint_config::*}; +pub use deposit_entry::*; +pub use lockup::*; +pub use registrar::*; +pub use voter::*; +pub use voting_mint_config::*; pub mod deposit_entry; pub mod lockup; diff --git a/program-states/src/state/registrar.rs b/program-states/src/state/registrar.rs index e00eccb2..80b30f46 100644 --- a/program-states/src/state/registrar.rs +++ b/program-states/src/state/registrar.rs @@ -1,8 +1,7 @@ -use { - crate::{error::*, state::voting_mint_config::VotingMintConfig}, - anchor_lang::prelude::*, - anchor_spl::token::Mint, -}; +use crate::error::*; +use crate::state::voting_mint_config::VotingMintConfig; +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; /// Instance of a voting rights distributor. #[account(zero_copy)] diff --git a/program-states/src/state/voter.rs b/program-states/src/state/voter.rs index 4e10bd6a..c6fb8033 100644 --- a/program-states/src/state/voter.rs +++ b/program-states/src/state/voter.rs @@ -1,10 +1,7 @@ -use { - crate::{ - error::*, - state::{deposit_entry::DepositEntry, registrar::Registrar}, - }, - anchor_lang::prelude::*, -}; +use crate::error::*; +use crate::state::deposit_entry::DepositEntry; +use crate::state::registrar::Registrar; +use anchor_lang::prelude::*; /// User account for minting voting rights. #[account(zero_copy)] diff --git a/program-states/src/state/voting_mint_config.rs b/program-states/src/state/voting_mint_config.rs index 9bb96a91..e7abf2d3 100644 --- a/program-states/src/state/voting_mint_config.rs +++ b/program-states/src/state/voting_mint_config.rs @@ -1,4 +1,6 @@ -use {crate::error::*, anchor_lang::prelude::*, std::convert::TryFrom}; +use crate::error::*; +use anchor_lang::prelude::*; +use std::convert::TryFrom; const SCALED_FACTOR_BASE: u64 = 1_000_000_000; diff --git a/programs/voter-stake-registry/src/cpi_instructions.rs b/programs/voter-stake-registry/src/cpi_instructions.rs index 1c2ab0cd..0e9f36a6 100644 --- a/programs/voter-stake-registry/src/cpi_instructions.rs +++ b/programs/voter-stake-registry/src/cpi_instructions.rs @@ -1,17 +1,11 @@ -use { - anchor_lang::{ - prelude::{borsh, Pubkey}, - AnchorDeserialize, AnchorSerialize, Key, - }, - mplx_staking_states::state::LockupPeriod, - solana_program::{ - account_info::AccountInfo, - entrypoint::ProgramResult, - instruction::{AccountMeta, Instruction}, - program::{invoke, invoke_signed}, - system_program, - }, -}; +use anchor_lang::prelude::{borsh, Pubkey}; +use anchor_lang::{AnchorDeserialize, AnchorSerialize, Key}; +use mplx_staking_states::state::LockupPeriod; +use solana_program::account_info::AccountInfo; +use solana_program::entrypoint::ProgramResult; +use solana_program::instruction::{AccountMeta, Instruction}; +use solana_program::program::{invoke, invoke_signed}; +use solana_program::system_program; pub const REWARD_CONTRACT_ID: Pubkey = solana_program::pubkey!("BF5PatmRTQDgEKoXR7iHRbkibEEi83nVM38cUKWzQcTR"); diff --git a/programs/voter-stake-registry/src/instructions/claim.rs b/programs/voter-stake-registry/src/instructions/claim.rs index 55736f4a..215ddc7f 100644 --- a/programs/voter-stake-registry/src/instructions/claim.rs +++ b/programs/voter-stake-registry/src/instructions/claim.rs @@ -1,11 +1,10 @@ -use { - crate::{borsh::BorshDeserialize, cpi_instructions}, - anchor_lang::prelude::*, - anchor_spl::token::{Token, TokenAccount}, - mplx_staking_states::error::VsrError, - solana_program::program::get_return_data, - std::borrow::Borrow, -}; +use crate::borsh::BorshDeserialize; +use crate::cpi_instructions; +use anchor_lang::prelude::*; +use anchor_spl::token::{Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use solana_program::program::get_return_data; +use std::borrow::Borrow; #[derive(Accounts)] pub struct Claim<'info> { diff --git a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs index 22279e24..be80b77e 100644 --- a/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/close_deposit_entry.rs @@ -1,10 +1,6 @@ -use { - anchor_lang::prelude::*, - mplx_staking_states::{ - error::VsrError, - state::{DepositEntry, Voter}, - }, -}; +use anchor_lang::prelude::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{DepositEntry, Voter}; #[derive(Accounts)] pub struct CloseDepositEntry<'info> { diff --git a/programs/voter-stake-registry/src/instructions/close_voter.rs b/programs/voter-stake-registry/src/instructions/close_voter.rs index 57b8060b..b01ce9e6 100644 --- a/programs/voter-stake-registry/src/instructions/close_voter.rs +++ b/programs/voter-stake-registry/src/instructions/close_voter.rs @@ -1,15 +1,11 @@ -use { - crate::clock_unix_timestamp, - anchor_lang::prelude::*, - anchor_spl::token::{self, CloseAccount, Token, TokenAccount, Transfer}, - bytemuck::bytes_of_mut, - mplx_staking_states::{ - error::VsrError, - state::{Registrar, Voter}, - voter_seeds, - }, - std::ops::DerefMut, -}; +use crate::clock_unix_timestamp; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, CloseAccount, Token, TokenAccount, Transfer}; +use bytemuck::bytes_of_mut; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{Registrar, Voter}; +use mplx_staking_states::voter_seeds; +use std::ops::DerefMut; // Remaining accounts must be all the token token accounts owned by voter, he wants to close, // they should be writable so that they can be closed and sol required for rent diff --git a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs index 68ccb85c..0a27b2b7 100644 --- a/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs +++ b/programs/voter-stake-registry/src/instructions/configure_voting_mint.rs @@ -1,11 +1,7 @@ -use { - anchor_lang::prelude::*, - anchor_spl::token::Mint, - mplx_staking_states::{ - error::VsrError, - state::{Registrar, VotingMintConfig}, - }, -}; +use anchor_lang::prelude::*; +use anchor_spl::token::Mint; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{Registrar, VotingMintConfig}; // Remaining accounts must be all the token mints that have registered // as voting mints, including the newly registered one. diff --git a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs index ebe11ac1..da8a4928 100644 --- a/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs +++ b/programs/voter-stake-registry/src/instructions/create_deposit_entry.rs @@ -1,14 +1,10 @@ -use { - crate::clock_unix_timestamp, - anchor_lang::prelude::*, - anchor_spl::{ - associated_token::AssociatedToken, - token::{Mint, Token, TokenAccount}, - }, - mplx_staking_states::{ - error::VsrError, - state::{DepositEntry, Lockup, LockupKind, LockupPeriod, Registrar, Voter}, - }, +use crate::clock_unix_timestamp; +use anchor_lang::prelude::*; +use anchor_spl::associated_token::AssociatedToken; +use anchor_spl::token::{Mint, Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{ + DepositEntry, Lockup, LockupKind, LockupPeriod, Registrar, Voter, }; #[derive(Accounts)] diff --git a/programs/voter-stake-registry/src/instructions/create_registrar.rs b/programs/voter-stake-registry/src/instructions/create_registrar.rs index 0222c19c..b16d8c6f 100644 --- a/programs/voter-stake-registry/src/instructions/create_registrar.rs +++ b/programs/voter-stake-registry/src/instructions/create_registrar.rs @@ -1,11 +1,10 @@ -use { - crate::cpi_instructions, - anchor_lang::prelude::*, - anchor_spl::token::{Mint, Token}, - mplx_staking_states::{error::VsrError, state::Registrar}, - spl_governance::state::realm, - std::mem::size_of, -}; +use crate::cpi_instructions; +use anchor_lang::prelude::*; +use anchor_spl::token::{Mint, Token}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::Registrar; +use spl_governance::state::realm; +use std::mem::size_of; #[derive(Accounts)] pub struct CreateRegistrar<'info> { diff --git a/programs/voter-stake-registry/src/instructions/create_voter.rs b/programs/voter-stake-registry/src/instructions/create_voter.rs index 5cf75830..446188db 100644 --- a/programs/voter-stake-registry/src/instructions/create_voter.rs +++ b/programs/voter-stake-registry/src/instructions/create_voter.rs @@ -1,12 +1,10 @@ -use { - crate::{cpi_instructions, voter::VoterWeightRecord}, - anchor_lang::{prelude::*, solana_program::sysvar::instructions as tx_instructions}, - mplx_staking_states::{ - error::VsrError, - state::{Registrar, Voter}, - }, - std::mem::size_of, -}; +use crate::cpi_instructions; +use crate::voter::VoterWeightRecord; +use anchor_lang::prelude::*; +use anchor_lang::solana_program::sysvar::instructions as tx_instructions; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{Registrar, Voter}; +use std::mem::size_of; #[derive(Accounts)] pub struct CreateVoter<'info> { diff --git a/programs/voter-stake-registry/src/instructions/deposit.rs b/programs/voter-stake-registry/src/instructions/deposit.rs index 5a2cec88..c09c1649 100644 --- a/programs/voter-stake-registry/src/instructions/deposit.rs +++ b/programs/voter-stake-registry/src/instructions/deposit.rs @@ -1,12 +1,8 @@ -use { - crate::clock_unix_timestamp, - anchor_lang::prelude::*, - anchor_spl::token::{self, Token, TokenAccount}, - mplx_staking_states::{ - error::VsrError, - state::{LockupKind, LockupPeriod, Registrar, Voter}, - }, -}; +use crate::clock_unix_timestamp; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{LockupKind, LockupPeriod, Registrar, Voter}; #[derive(Accounts)] pub struct Deposit<'info> { diff --git a/programs/voter-stake-registry/src/instructions/extend_deposit.rs b/programs/voter-stake-registry/src/instructions/extend_deposit.rs index 47d035fe..fe003d17 100644 --- a/programs/voter-stake-registry/src/instructions/extend_deposit.rs +++ b/programs/voter-stake-registry/src/instructions/extend_deposit.rs @@ -1,12 +1,9 @@ -use { - crate::{clock_unix_timestamp, cpi_instructions::extend_deposit}, - anchor_lang::prelude::*, - anchor_spl::token::{self, Token, TokenAccount, Transfer}, - mplx_staking_states::{ - error::VsrError, - state::{LockupKind, LockupPeriod, Registrar, Voter}, - }, -}; +use crate::clock_unix_timestamp; +use crate::cpi_instructions::extend_deposit; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, Token, TokenAccount, Transfer}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{LockupKind, LockupPeriod, Registrar, Voter}; #[derive(Accounts)] pub struct RestakeDeposit<'info> { diff --git a/programs/voter-stake-registry/src/instructions/lock_tokens.rs b/programs/voter-stake-registry/src/instructions/lock_tokens.rs index abfc56e5..de7d4915 100644 --- a/programs/voter-stake-registry/src/instructions/lock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/lock_tokens.rs @@ -1,11 +1,7 @@ -use { - crate::{clock_unix_timestamp, cpi_instructions}, - anchor_lang::prelude::*, - mplx_staking_states::{ - error::VsrError, - state::{LockupKind, Registrar, Voter}, - }, -}; +use crate::{clock_unix_timestamp, cpi_instructions}; +use anchor_lang::prelude::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{LockupKind, Registrar, Voter}; #[derive(Accounts)] pub struct LockTokens<'info> { diff --git a/programs/voter-stake-registry/src/instructions/log_voter_info.rs b/programs/voter-stake-registry/src/instructions/log_voter_info.rs index d40ddfb7..bdcbc338 100644 --- a/programs/voter-stake-registry/src/instructions/log_voter_info.rs +++ b/programs/voter-stake-registry/src/instructions/log_voter_info.rs @@ -1,8 +1,7 @@ -use { - crate::{clock_unix_timestamp, events::*}, - anchor_lang::prelude::*, - mplx_staking_states::state::{LockupKind, Registrar, Voter}, -}; +use crate::clock_unix_timestamp; +use crate::events::*; +use anchor_lang::prelude::*; +use mplx_staking_states::state::{LockupKind, Registrar, Voter}; #[derive(Accounts)] pub struct LogVoterInfo<'info> { diff --git a/programs/voter-stake-registry/src/instructions/mod.rs b/programs/voter-stake-registry/src/instructions/mod.rs index ae5c4509..f219c6bc 100644 --- a/programs/voter-stake-registry/src/instructions/mod.rs +++ b/programs/voter-stake-registry/src/instructions/mod.rs @@ -1,10 +1,19 @@ -use solana_program::{clock::Clock, sysvar::Sysvar}; -pub use { - claim::*, close_deposit_entry::*, close_voter::*, configure_voting_mint::*, - create_deposit_entry::*, create_registrar::*, create_voter::*, deposit::*, extend_deposit::*, - lock_tokens::*, log_voter_info::*, unlock_tokens::*, update_voter_weight_record::*, - withdraw::*, -}; +pub use claim::*; +pub use close_deposit_entry::*; +pub use close_voter::*; +pub use configure_voting_mint::*; +pub use create_deposit_entry::*; +pub use create_registrar::*; +pub use create_voter::*; +pub use deposit::*; +pub use extend_deposit::*; +pub use lock_tokens::*; +pub use log_voter_info::*; +use solana_program::clock::Clock; +use solana_program::sysvar::Sysvar; +pub use unlock_tokens::*; +pub use update_voter_weight_record::*; +pub use withdraw::*; mod claim; mod close_deposit_entry; diff --git a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs index c15dcc6e..bf9babbf 100644 --- a/programs/voter-stake-registry/src/instructions/unlock_tokens.rs +++ b/programs/voter-stake-registry/src/instructions/unlock_tokens.rs @@ -1,11 +1,7 @@ -use { - crate::clock_unix_timestamp, - anchor_lang::prelude::*, - mplx_staking_states::{ - error::VsrError, - state::{Registrar, Voter, COOLDOWN_SECS}, - }, -}; +use crate::clock_unix_timestamp; +use anchor_lang::prelude::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{Registrar, Voter, COOLDOWN_SECS}; #[derive(Accounts)] pub struct UnlockTokens<'info> { diff --git a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs index 377bb78b..9baba923 100644 --- a/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs +++ b/programs/voter-stake-registry/src/instructions/update_voter_weight_record.rs @@ -1,8 +1,6 @@ -use { - crate::voter::VoterWeightRecord, - anchor_lang::prelude::*, - mplx_staking_states::state::{Registrar, Voter}, -}; +use crate::voter::VoterWeightRecord; +use anchor_lang::prelude::*; +use mplx_staking_states::state::{Registrar, Voter}; #[derive(Accounts)] pub struct UpdateVoterWeightRecord<'info> { diff --git a/programs/voter-stake-registry/src/instructions/withdraw.rs b/programs/voter-stake-registry/src/instructions/withdraw.rs index 2d8b400e..927df426 100644 --- a/programs/voter-stake-registry/src/instructions/withdraw.rs +++ b/programs/voter-stake-registry/src/instructions/withdraw.rs @@ -1,17 +1,11 @@ -use { - crate::{ - clock_unix_timestamp, - cpi_instructions::withdraw_mining, - voter::{load_token_owner_record, VoterWeightRecord}, - }, - anchor_lang::prelude::*, - anchor_spl::token::{self, Token, TokenAccount}, - mplx_staking_states::{ - error::VsrError, - state::{DepositEntry, LockupKind, LockupPeriod, Registrar, Voter}, - voter_seeds, - }, -}; +use crate::clock_unix_timestamp; +use crate::cpi_instructions::withdraw_mining; +use crate::voter::{load_token_owner_record, VoterWeightRecord}; +use anchor_lang::prelude::*; +use anchor_spl::token::{self, Token, TokenAccount}; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::{DepositEntry, LockupKind, LockupPeriod, Registrar, Voter}; +use mplx_staking_states::voter_seeds; #[derive(Accounts)] pub struct Withdraw<'info> { diff --git a/programs/voter-stake-registry/src/lib.rs b/programs/voter-stake-registry/src/lib.rs index 6c46537a..cdebe9f9 100644 --- a/programs/voter-stake-registry/src/lib.rs +++ b/programs/voter-stake-registry/src/lib.rs @@ -1,8 +1,6 @@ -use { - anchor_lang::prelude::*, - instructions::*, - mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}, -}; +use anchor_lang::prelude::*; +use instructions::*; +use mplx_staking_states::state::lockup::{LockupKind, LockupPeriod}; pub mod cpi_instructions; pub mod events; diff --git a/programs/voter-stake-registry/src/voter.rs b/programs/voter-stake-registry/src/voter.rs index f1f08e8b..73a07874 100644 --- a/programs/voter-stake-registry/src/voter.rs +++ b/programs/voter-stake-registry/src/voter.rs @@ -1,9 +1,8 @@ -use { - crate::vote_weight_record, - anchor_lang::prelude::*, - mplx_staking_states::{error::VsrError, state::Registrar}, - spl_governance::state::token_owner_record, -}; +use crate::vote_weight_record; +use anchor_lang::prelude::*; +use mplx_staking_states::error::VsrError; +use mplx_staking_states::state::Registrar; +use spl_governance::state::token_owner_record; pub fn load_token_owner_record( voter_authority: &Pubkey, diff --git a/programs/voter-stake-registry/tests/program_test/addin.rs b/programs/voter-stake-registry/tests/program_test/addin.rs index d8088bc6..e4b7b9d0 100644 --- a/programs/voter-stake-registry/tests/program_test/addin.rs +++ b/programs/voter-stake-registry/tests/program_test/addin.rs @@ -1,14 +1,11 @@ -use { - crate::*, - anchor_lang::Key, - mplx_staking_states::state::Voter, - solana_sdk::{ - instruction::Instruction, - pubkey::Pubkey, - signature::{Keypair, Signer}, - }, - std::{cell::RefCell, rc::Rc}, -}; +use crate::*; +use anchor_lang::Key; +use mplx_staking_states::state::Voter; +use solana_sdk::instruction::Instruction; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signer}; +use std::cell::RefCell; +use std::rc::Rc; #[derive(Clone)] pub struct AddinCookie { diff --git a/programs/voter-stake-registry/tests/program_test/cookies.rs b/programs/voter-stake-registry/tests/program_test/cookies.rs index e2d4fe4a..42e5e797 100644 --- a/programs/voter-stake-registry/tests/program_test/cookies.rs +++ b/programs/voter-stake-registry/tests/program_test/cookies.rs @@ -1,4 +1,6 @@ -use {crate::utils::*, solana_program::pubkey::*, solana_sdk::signature::Keypair}; +use crate::utils::*; +use solana_program::pubkey::*; +use solana_sdk::signature::Keypair; pub struct MintCookie { pub index: usize, diff --git a/programs/voter-stake-registry/tests/program_test/governance.rs b/programs/voter-stake-registry/tests/program_test/governance.rs index fc0d64b5..38fb084c 100644 --- a/programs/voter-stake-registry/tests/program_test/governance.rs +++ b/programs/voter-stake-registry/tests/program_test/governance.rs @@ -1,13 +1,9 @@ -use { - crate::*, - solana_program::instruction::Instruction, - solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, - }, - spl_governance::state::{proposal, vote_record}, - std::rc::Rc, -}; +use crate::*; +use solana_program::instruction::Instruction; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signer}; +use spl_governance::state::{proposal, vote_record}; +use std::rc::Rc; #[derive(Clone)] pub struct GovernanceCookie { diff --git a/programs/voter-stake-registry/tests/program_test/mod.rs b/programs/voter-stake-registry/tests/program_test/mod.rs index d472467c..430bbba7 100644 --- a/programs/voter-stake-registry/tests/program_test/mod.rs +++ b/programs/voter-stake-registry/tests/program_test/mod.rs @@ -1,21 +1,21 @@ -use { - self::rewards::RewardsCookie, - log::*, - solana_program::{program_option::COption, program_pack::Pack}, - solana_program_test::*, - solana_sdk::{ - pubkey::Pubkey, - signature::{Keypair, Signer}, - }, - spl_token::{state::*, *}, - std::{ - cell::RefCell, - rc::Rc, - str::FromStr, - sync::{Arc, RwLock}, - }, -}; -pub use {addin::*, cookies::*, governance::*, solana::*, utils::*}; +use self::rewards::RewardsCookie; +pub use addin::*; +pub use cookies::*; +pub use governance::*; +use log::*; +pub use solana::*; +use solana_program::program_option::COption; +use solana_program::program_pack::Pack; +use solana_program_test::*; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signer}; +use spl_token::state::*; +use spl_token::*; +use std::cell::RefCell; +use std::rc::Rc; +use std::str::FromStr; +use std::sync::{Arc, RwLock}; +pub use utils::*; pub mod addin; pub mod cookies; diff --git a/programs/voter-stake-registry/tests/program_test/rewards.rs b/programs/voter-stake-registry/tests/program_test/rewards.rs index bf8124c5..fad1bb57 100644 --- a/programs/voter-stake-registry/tests/program_test/rewards.rs +++ b/programs/voter-stake-registry/tests/program_test/rewards.rs @@ -1,19 +1,16 @@ -use { - crate::SolanaCookie, - anchor_lang::{prelude::*, AnchorDeserialize}, - mplx_staking_states::state::LockupPeriod, - solana_program_test::*, - solana_sdk::{ - instruction::{AccountMeta, Instruction}, - program_pack::IsInitialized, - pubkey::Pubkey, - signature::Keypair, - signer::Signer, - system_program, - }, - std::rc::Rc, - voter_stake_registry::cpi_instructions::RewardsInstruction, -}; +use crate::SolanaCookie; +use anchor_lang::prelude::*; +use anchor_lang::AnchorDeserialize; +use mplx_staking_states::state::LockupPeriod; +use solana_program_test::*; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::program_pack::IsInitialized; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::system_program; +use std::rc::Rc; +use voter_stake_registry::cpi_instructions::RewardsInstruction; pub struct RewardsCookie { pub solana: Rc, diff --git a/programs/voter-stake-registry/tests/program_test/solana.rs b/programs/voter-stake-registry/tests/program_test/solana.rs index dc1d177f..4d3507bc 100644 --- a/programs/voter-stake-registry/tests/program_test/solana.rs +++ b/programs/voter-stake-registry/tests/program_test/solana.rs @@ -1,21 +1,17 @@ -use { - anchor_lang::AccountDeserialize, - anchor_spl::token::TokenAccount, - solana_program::{program_pack::Pack, rent::*, system_instruction}, - solana_program_test::*, - solana_sdk::{ - account::ReadableAccount, - instruction::Instruction, - pubkey::Pubkey, - signature::{Keypair, Signer}, - transaction::Transaction, - }, - spl_token::*, - std::{ - cell::RefCell, - sync::{Arc, RwLock}, - }, -}; +use anchor_lang::AccountDeserialize; +use anchor_spl::token::TokenAccount; +use solana_program::program_pack::Pack; +use solana_program::rent::*; +use solana_program::system_instruction; +use solana_program_test::*; +use solana_sdk::account::ReadableAccount; +use solana_sdk::instruction::Instruction; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{Keypair, Signer}; +use solana_sdk::transaction::Transaction; +use spl_token::*; +use std::cell::RefCell; +use std::sync::{Arc, RwLock}; pub struct SolanaCookie { pub context: RefCell, diff --git a/programs/voter-stake-registry/tests/program_test/utils.rs b/programs/voter-stake-registry/tests/program_test/utils.rs index ff7ad3af..03a16322 100644 --- a/programs/voter-stake-registry/tests/program_test/utils.rs +++ b/programs/voter-stake-registry/tests/program_test/utils.rs @@ -1,13 +1,13 @@ -use { - bytemuck::{bytes_of, Contiguous}, - solana_program::program_error::ProgramError, - solana_program_test::{BanksClientError, ProgramTestContext}, - solana_sdk::{ - program_pack::Pack, pubkey::Pubkey, signature::Keypair, signer::Signer, system_instruction, - transaction::Transaction, - }, - std::borrow::BorrowMut, -}; +use bytemuck::{bytes_of, Contiguous}; +use solana_program::program_error::ProgramError; +use solana_program_test::{BanksClientError, ProgramTestContext}; +use solana_sdk::program_pack::Pack; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::system_instruction; +use solana_sdk::transaction::Transaction; +use std::borrow::BorrowMut; #[allow(dead_code)] pub fn gen_signer_seeds<'a>(nonce: &'a u64, acc_pk: &'a Pubkey) -> [&'a [u8]; 2] { diff --git a/programs/voter-stake-registry/tests/test_all_deposits.rs b/programs/voter-stake-registry/tests/test_all_deposits.rs index cc4cda18..eea6427f 100644 --- a/programs/voter-stake-registry/tests/test_all_deposits.rs +++ b/programs/voter-stake-registry/tests/test_all_deposits.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; #[tokio::test] diff --git a/programs/voter-stake-registry/tests/test_basic.rs b/programs/voter-stake-registry/tests/test_basic.rs index 5dd8850a..8947a059 100644 --- a/programs/voter-stake-registry/tests/test_basic.rs +++ b/programs/voter-stake-registry/tests/test_basic.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_claim.rs b/programs/voter-stake-registry/tests/test_claim.rs index 3a537d74..f36f6ad4 100644 --- a/programs/voter-stake-registry/tests/test_claim.rs +++ b/programs/voter-stake-registry/tests/test_claim.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_constant.rs b/programs/voter-stake-registry/tests/test_deposit_constant.rs index 3f1ff98e..0dcb6890 100644 --- a/programs/voter-stake-registry/tests/test_deposit_constant.rs +++ b/programs/voter-stake-registry/tests/test_deposit_constant.rs @@ -1,10 +1,11 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs index 9d1f4d0a..08977fcf 100644 --- a/programs/voter-stake-registry/tests/test_deposit_no_locking.rs +++ b/programs/voter-stake-registry/tests/test_deposit_no_locking.rs @@ -1,10 +1,11 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{pubkey::Pubkey, signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_extend_deposit.rs b/programs/voter-stake-registry/tests/test_extend_deposit.rs index 56447f42..1ba640ce 100644 --- a/programs/voter-stake-registry/tests/test_extend_deposit.rs +++ b/programs/voter-stake-registry/tests/test_extend_deposit.rs @@ -1,12 +1,11 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{ - clock::SECONDS_PER_DAY, signature::Keypair, signer::Signer, transport::TransportError, - }, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::clock::SECONDS_PER_DAY; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_lockup.rs b/programs/voter-stake-registry/tests/test_lockup.rs index 2a388e77..3fc118d6 100644 --- a/programs/voter-stake-registry/tests/test_lockup.rs +++ b/programs/voter-stake-registry/tests/test_lockup.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_log_voter_info.rs b/programs/voter-stake-registry/tests/test_log_voter_info.rs index a21358e7..6e726caf 100644 --- a/programs/voter-stake-registry/tests/test_log_voter_info.rs +++ b/programs/voter-stake-registry/tests/test_log_voter_info.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; diff --git a/programs/voter-stake-registry/tests/test_voting.rs b/programs/voter-stake-registry/tests/test_voting.rs index 558911d0..421cd898 100644 --- a/programs/voter-stake-registry/tests/test_voting.rs +++ b/programs/voter-stake-registry/tests/test_voting.rs @@ -1,10 +1,10 @@ -use { - anchor_spl::token::TokenAccount, - mplx_staking_states::state::{LockupKind, LockupPeriod}, - program_test::*, - solana_program_test::*, - solana_sdk::{signature::Keypair, signer::Signer, transport::TransportError}, -}; +use anchor_spl::token::TokenAccount; +use mplx_staking_states::state::{LockupKind, LockupPeriod}; +use program_test::*; +use solana_program_test::*; +use solana_sdk::signature::Keypair; +use solana_sdk::signer::Signer; +use solana_sdk::transport::TransportError; mod program_test; #[tokio::test] async fn test_voting() -> Result<(), TransportError> { diff --git a/rustfmt.toml b/rustfmt.toml index 63bbd58f..e6803834 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,5 @@ comment_width = 100 edition = "2021" group_imports = "One" -imports_granularity = "One" +imports_granularity = "Module" wrap_comments = true