Decompose deposits into separate instructions

This commit is contained in:
armaniferrante 2021-10-16 09:47:57 -07:00
parent 627d572774
commit 8db7c5b449
No known key found for this signature in database
GPG Key ID: 58BEF301E91F7828
4 changed files with 413 additions and 148 deletions

View File

@ -1,18 +1,20 @@
{ {
"scripts": { "scripts": {
"test": "anchor test" "lint:fix": "prettier tests/** -w",
}, "test": "anchor test"
"dependencies": { },
"@project-serum/anchor": "^0.17.1-beta.1", "dependencies": {
"@project-serum/common": "^0.0.1-beta.3", "@project-serum/anchor": "^0.17.1-beta.1",
"@solana/spl-token": "^0.1.8", "@project-serum/common": "^0.0.1-beta.3",
"bn.js": "^5.2.0" "@solana/spl-token": "^0.1.8",
}, "bn.js": "^5.2.0"
"devDependencies": { },
"@types/mocha": "^9.0.0", "devDependencies": {
"chai": "^4.3.4", "@types/mocha": "^9.0.0",
"mocha": "^9.0.3", "chai": "^4.3.4",
"ts-mocha": "^8.0.0", "mocha": "^9.0.3",
"typescript": "^4.3.5" "prettier": "^2.4.1",
} "ts-mocha": "^8.0.0",
"typescript": "^4.3.5"
}
} }

View File

@ -10,14 +10,59 @@ use std::ops::Deref;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
/// # Introduction
///
/// The governance registry is an "addin" to the SPL governance program that
/// allows one to both 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 overall process 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).
///
/// # Locked Tokens
///
/// Locked tokens scale voting power linearly. For example, if you were to
/// lockup your tokens for 10 years, with a single vesting period, then your
/// voting power would scale by 10x. If you were to lockup your tokens for
/// 10 years with two vesting periods, your voting power would scale by
/// 1/2 * deposit * 5 + 1/2 * deposit * 10--since the first half of the lockup
/// would unlock at year 5 and the other half would unlock at year 10.
///
/// # Decayed Voting Power
///
/// Although there is a premium awarded to voters for locking up their tokens,
/// that premium decays over time, since the lockup period decreases over time.
///
/// # 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.
#[program] #[program]
pub mod governance_registry { pub mod governance_registry {
use super::*; use super::*;
/// Creates a new voting registrar. There can only be a single regsitrar /// Creates a new voting registrar. There can only be a single regsitrar
/// per governance realm. /// per governance realm.
pub fn init_registrar( pub fn create_registrar(
ctx: Context<InitRegistrar>, ctx: Context<CreateRegistrar>,
registrar_bump: u8, registrar_bump: u8,
voting_mint_bump: u8, voting_mint_bump: u8,
_voting_mint_decimals: u8, _voting_mint_decimals: u8,
@ -34,7 +79,7 @@ pub mod governance_registry {
/// Creates a new voter account. There can only be a single voter per /// Creates a new voter account. There can only be a single voter per
/// user wallet. /// user wallet.
pub fn init_voter(ctx: Context<InitVoter>, voter_bump: u8) -> Result<()> { pub fn create_voter(ctx: Context<CreateVoter>, voter_bump: u8) -> Result<()> {
let voter = &mut ctx.accounts.voter.load_init()?; let voter = &mut ctx.accounts.voter.load_init()?;
voter.voter_bump = voter_bump; voter.voter_bump = voter_bump;
voter.authority = ctx.accounts.authority.key(); voter.authority = ctx.accounts.authority.key();
@ -46,7 +91,10 @@ pub mod governance_registry {
/// Creates a new exchange rate for a given mint. This allows a voter to /// Creates a new exchange rate for a given mint. This allows a voter to
/// deposit the mint in exchange for vTokens. There can only be a single /// deposit the mint in exchange for vTokens. There can only be a single
/// exchange rate per mint. /// exchange rate per mint.
pub fn add_exchange_rate(ctx: Context<AddExchangeRate>, er: ExchangeRateEntry) -> Result<()> { pub fn create_exchange_rate(
ctx: Context<CreateExchangeRate>,
er: ExchangeRateEntry,
) -> Result<()> {
require!(er.rate > 0, InvalidRate); require!(er.rate > 0, InvalidRate);
let mut er = er; let mut er = er;
@ -62,10 +110,55 @@ pub mod governance_registry {
Ok(()) Ok(())
} }
/// Deposits tokens into the registrar in exchange for *frozen* voting /// Creates a new deposit entry and updates it by transferring in tokens.
/// tokens. These tokens are not used for anything other than displaying pub fn create_deposit(
/// the amount in wallets. ctx: Context<CreateDeposit>,
pub fn deposit(ctx: Context<Deposit>, amount: u64) -> Result<()> { amount: u64,
lockup: Option<Lockup>,
) -> Result<()> {
// Creates the new deposit.
let deposit_id = {
let registrar = &ctx.accounts.deposit.registrar.load()?;
let voter = &mut ctx.accounts.deposit.voter.load_mut()?;
// Get the exchange rate entry associated with this deposit.
let er_idx = registrar
.rates
.iter()
.position(|r| r.mint == ctx.accounts.deposit.deposit_mint.key())
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
// Get and set up the first free deposit entry.
let free_entry_idx = voter
.deposits
.iter()
.position(|d_entry| !d_entry.is_used)
.ok_or(ErrorCode::DepositEntryFull)?;
let d_entry = &mut voter.deposits[free_entry_idx];
d_entry.is_used = true;
d_entry.rate_idx = free_entry_idx as u8;
d_entry.rate_idx = er_idx as u8;
if let Some(l) = lockup {
d_entry.start_ts = l.start_ts;
d_entry.end_ts = l.end_ts;
d_entry.period_count = l.period_count;
}
free_entry_idx as u8
};
// Updates the entry by transferring in tokens.
let update_ctx = Context::new(ctx.program_id, &mut ctx.accounts.deposit, &[]);
update_deposit(update_ctx, deposit_id, amount)?;
Ok(())
}
/// Updates a deposit entry by depositing tokens into the registrar in
/// exchange for *frozen* voting tokens. These tokens are not used for
/// anything other than displaying the amount in wallets.
pub fn update_deposit(ctx: Context<UpdateDeposit>, id: u8, amount: u64) -> Result<()> {
let registrar = &ctx.accounts.registrar.load()?; let registrar = &ctx.accounts.registrar.load()?;
let voter = &mut ctx.accounts.voter.load_mut()?; let voter = &mut ctx.accounts.voter.load_mut()?;
@ -77,31 +170,10 @@ pub mod governance_registry {
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?; .ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
let er_entry = registrar.rates[er_idx]; let er_entry = registrar.rates[er_idx];
// Get the deposit entry associated with this deposit. require!(voter.deposits.len() > id as usize, InvalidDepositId);
let deposit_entry = { let d_entry = &mut voter.deposits[id as usize];
match voter.deposits.iter().position(|deposit_entry| {
registrar.rates[deposit_entry.rate_idx as usize].mint
== ctx.accounts.deposit_mint.key()
}) {
// Lazily instantiate the deposit if needed.
None => {
let free_entry_idx = voter
.deposits
.iter()
.position(|deposit_entry| !deposit_entry.is_used)
.ok_or(ErrorCode::DepositEntryFull)?;
let entry = &mut voter.deposits[free_entry_idx];
entry.is_used = true;
entry.rate_idx = free_entry_idx as u8;
entry
}
// Use the existing deposit.
Some(e) => &mut voter.deposits[e],
}
};
// Update the amount deposited. d_entry.amount += amount;
deposit_entry.amount += amount;
// Calculate the amount of voting tokens to mint at the specified // Calculate the amount of voting tokens to mint at the specified
// exchange rate. // exchange rate.
@ -130,13 +202,53 @@ pub mod governance_registry {
/// Withdraws tokens from a deposit entry, if they are unlocked according /// Withdraws tokens from a deposit entry, if they are unlocked according
/// to a vesting schedule. /// to a vesting schedule.
pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> { ///
// todo /// `amount` is in units of the native currency being withdrawn.
pub fn withdraw(ctx: Context<Withdraw>, deposit_id: u8, amount: u64) -> Result<()> {
let registrar = &ctx.accounts.registrar.load()?;
let voter = &mut ctx.accounts.voter.load_mut()?;
require!(voter.deposits.len() > deposit_id.into(), InvalidDepositId);
// Update the deposit bookkeeping.
let deposit_entry = &mut voter.deposits[deposit_id as usize];
require!(deposit_entry.is_used, InvalidDepositId);
require!(deposit_entry.vested() >= amount, InsufficientVestedTokens);
deposit_entry.amount -= amount;
// Get the exchange rate for the token being withdrawn.
let er_idx = registrar
.rates
.iter()
.position(|r| r.mint == ctx.accounts.withdraw_mint.key())
.ok_or(ErrorCode::ExchangeRateEntryNotFound)?;
let er_entry = registrar.rates[er_idx];
let scaled_amount = er_entry.rate * amount;
// Transfer the tokens to withdraw.
token::transfer(
ctx.accounts
.transfer_ctx()
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
amount,
)?;
// Unfreeze the voting mint.
token::thaw_account(
ctx.accounts
.thaw_ctx()
.with_signer(&[&[registrar.realm.as_ref(), &[registrar.bump]]]),
)?;
// Burn the voting tokens.
token::burn(ctx.accounts.burn_ctx(), scaled_amount)?;
Ok(()) Ok(())
} }
/// Updates a vesting schedule. Can only increase the lockup time. If all /// Updates a vesting schedule. Can only increase the lockup time or reduce
/// tokens are unlocked, then the period count can also be updated. /// the period count (since that has the effect of increasing lockup time).
/// If all tokens are unlocked, then both can be updated arbitrarily.
pub fn update_schedule(ctx: Context<UpdateSchedule>) -> Result<()> { pub fn update_schedule(ctx: Context<UpdateSchedule>) -> Result<()> {
// todo // todo
Ok(()) Ok(())
@ -165,7 +277,7 @@ pub mod governance_registry {
#[derive(Accounts)] #[derive(Accounts)]
#[instruction(registrar_bump: u8, voting_mint_bump: u8, voting_mint_decimals: u8)] #[instruction(registrar_bump: u8, voting_mint_bump: u8, voting_mint_decimals: u8)]
pub struct InitRegistrar<'info> { pub struct CreateRegistrar<'info> {
#[account( #[account(
init, init,
seeds = [realm.key().as_ref()], seeds = [realm.key().as_ref()],
@ -180,8 +292,8 @@ pub struct InitRegistrar<'info> {
bump = voting_mint_bump, bump = voting_mint_bump,
payer = payer, payer = payer,
mint::authority = registrar, mint::authority = registrar,
mint::decimals = voting_mint_decimals,
mint::freeze_authority = registrar, mint::freeze_authority = registrar,
mint::decimals = voting_mint_decimals,
)] )]
voting_mint: Account<'info, Mint>, voting_mint: Account<'info, Mint>,
realm: UncheckedAccount<'info>, realm: UncheckedAccount<'info>,
@ -194,7 +306,7 @@ pub struct InitRegistrar<'info> {
#[derive(Accounts)] #[derive(Accounts)]
#[instruction(voter_bump: u8)] #[instruction(voter_bump: u8)]
pub struct InitVoter<'info> { pub struct CreateVoter<'info> {
#[account( #[account(
init, init,
seeds = [registrar.key().as_ref(), authority.key().as_ref()], seeds = [registrar.key().as_ref(), authority.key().as_ref()],
@ -204,11 +316,11 @@ pub struct InitVoter<'info> {
)] )]
voter: AccountLoader<'info, Voter>, voter: AccountLoader<'info, Voter>,
#[account( #[account(
init, init,
payer = authority, payer = authority,
associated_token::authority = authority, associated_token::authority = authority,
associated_token::mint = voting_mint, associated_token::mint = voting_mint,
)] )]
voting_token: Account<'info, TokenAccount>, voting_token: Account<'info, TokenAccount>,
voting_mint: Account<'info, Mint>, voting_mint: Account<'info, Mint>,
registrar: AccountLoader<'info, Registrar>, registrar: AccountLoader<'info, Registrar>,
@ -221,7 +333,7 @@ pub struct InitVoter<'info> {
#[derive(Accounts)] #[derive(Accounts)]
#[instruction(rate: ExchangeRateEntry)] #[instruction(rate: ExchangeRateEntry)]
pub struct AddExchangeRate<'info> { pub struct CreateExchangeRate<'info> {
#[account( #[account(
init, init,
payer = payer, payer = payer,
@ -241,34 +353,11 @@ pub struct AddExchangeRate<'info> {
} }
#[derive(Accounts)] #[derive(Accounts)]
pub struct Deposit<'info> { pub struct CreateDeposit<'info> {
#[account(mut, has_one = authority)] deposit: UpdateDeposit<'info>,
voter: AccountLoader<'info, Voter>,
#[account(
mut,
associated_token::authority = registrar,
associated_token::mint = deposit_mint,
)]
exchange_vault: Account<'info, TokenAccount>,
#[account(
mut,
constraint = deposit_token.mint == deposit_mint.key(),
)]
deposit_token: Account<'info, TokenAccount>,
#[account(
mut,
constraint = registrar.load()?.voting_mint == voting_token.mint,
)]
voting_token: Account<'info, TokenAccount>,
authority: Signer<'info>,
registrar: AccountLoader<'info, Registrar>,
deposit_mint: Account<'info, Mint>,
#[account(mut)]
voting_mint: Account<'info, Mint>,
token_program: Program<'info, Token>,
} }
impl<'info> Deposit<'info> { impl<'info> UpdateDeposit<'info> {
fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> { fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.to_account_info(); let program = self.token_program.to_account_info();
let accounts = token::Transfer { let accounts = token::Transfer {
@ -301,8 +390,92 @@ impl<'info> Deposit<'info> {
} }
#[derive(Accounts)] #[derive(Accounts)]
pub struct Withdraw { pub struct UpdateDeposit<'info> {
// todo #[account(has_one = voting_mint)]
registrar: AccountLoader<'info, Registrar>,
#[account(mut, has_one = authority, has_one = registrar)]
voter: AccountLoader<'info, Voter>,
#[account(
mut,
associated_token::authority = registrar,
associated_token::mint = deposit_mint,
)]
exchange_vault: Account<'info, TokenAccount>,
#[account(
mut,
constraint = deposit_token.mint == deposit_mint.key(),
)]
deposit_token: Account<'info, TokenAccount>,
#[account(
mut,
associated_token::authority = authority,
associated_token::mint = voting_mint,
)]
voting_token: Account<'info, TokenAccount>,
authority: Signer<'info>,
deposit_mint: Account<'info, Mint>,
#[account(mut)]
voting_mint: Account<'info, Mint>,
token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct Withdraw<'info> {
#[account(has_one = voting_mint)]
registrar: AccountLoader<'info, Registrar>,
#[account(mut, has_one = registrar, has_one = authority)]
voter: AccountLoader<'info, Voter>,
#[account(
mut,
associated_token::authority = registrar,
associated_token::mint = withdraw_mint,
)]
exchange_vault: Account<'info, TokenAccount>,
withdraw_mint: Account<'info, Mint>,
#[account(
mut,
associated_token::authority = authority,
associated_token::mint = voting_mint,
)]
voting_token: Account<'info, TokenAccount>,
#[account(mut)]
voting_mint: Account<'info, Mint>,
#[account(mut)]
destination: Account<'info, TokenAccount>,
authority: Signer<'info>,
token_program: Program<'info, Token>,
}
impl<'info> Withdraw<'info> {
pub fn transfer_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Transfer<'info>> {
let program = self.token_program.to_account_info();
let accounts = token::Transfer {
from: self.exchange_vault.to_account_info(),
to: self.destination.to_account_info(),
authority: self.registrar.to_account_info(),
};
CpiContext::new(program, accounts)
}
pub fn thaw_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::ThawAccount<'info>> {
let program = self.token_program.to_account_info();
let accounts = token::ThawAccount {
account: self.voting_token.to_account_info(),
mint: self.voting_mint.to_account_info(),
authority: self.registrar.to_account_info(),
};
CpiContext::new(program, accounts)
}
pub fn burn_ctx(&self) -> CpiContext<'_, '_, '_, 'info, token::Burn<'info>> {
let program = self.token_program.to_account_info();
let accounts = token::Burn {
mint: self.voting_mint.to_account_info(),
to: self.voting_token.to_account_info(),
authority: self.authority.to_account_info(),
};
CpiContext::new(program, accounts)
}
} }
#[derive(Accounts)] #[derive(Accounts)]
@ -370,7 +543,7 @@ pub struct DepositEntry {
pub amount: u64, pub amount: u64,
// Locked state. // Locked state.
pub period_count: u64, pub period_count: u32,
pub start_ts: i64, pub start_ts: i64,
pub end_ts: i64, pub end_ts: i64,
} }
@ -382,6 +555,19 @@ impl DepositEntry {
let locked_multiplier = 1; // todo let locked_multiplier = 1; // todo
self.amount * locked_multiplier self.amount * locked_multiplier
} }
/// Returns the amount of unlocked tokens for this deposit.
pub fn vested(&self) -> u64 {
// todo
self.amount
}
}
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct Lockup {
pub period_count: u32,
pub start_ts: i64,
pub end_ts: i64,
} }
/// Anchor wrapper for the SPL governance program's VoterWeightRecord type. /// Anchor wrapper for the SPL governance program's VoterWeightRecord type.
@ -449,4 +635,6 @@ pub enum ErrorCode {
DepositEntryNotFound, DepositEntryNotFound,
DepositEntryFull, DepositEntryFull,
VotingTokenNonZero, VotingTokenNonZero,
InvalidDepositId,
InsufficientVestedTokens,
} }

View File

@ -1,16 +1,26 @@
import * as assert from 'assert'; import * as assert from "assert";
import * as anchor from '@project-serum/anchor'; import * as anchor from "@project-serum/anchor";
import { Program } from '@project-serum/anchor'; import { Program } from "@project-serum/anchor";
import { createMintAndVault } from '@project-serum/common'; import { createMintAndVault } from "@project-serum/common";
import BN from 'bn.js'; import BN from "bn.js";
import { PublicKey, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js'; import {
import { Token, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } from '@solana/spl-token'; PublicKey,
import { GovernanceRegistry } from '../target/types/governance_registry'; Keypair,
SystemProgram,
SYSVAR_RENT_PUBKEY,
} from "@solana/web3.js";
import {
Token,
TOKEN_PROGRAM_ID,
ASSOCIATED_TOKEN_PROGRAM_ID,
} from "@solana/spl-token";
import { GovernanceRegistry } from "../target/types/governance_registry";
describe('voting-rights', () => { describe("voting-rights", () => {
anchor.setProvider(anchor.Provider.env()); anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.GovernanceRegistry as Program<GovernanceRegistry>; const program = anchor.workspace
.GovernanceRegistry as Program<GovernanceRegistry>;
// Initialized variables shared across tests. // Initialized variables shared across tests.
const realm = Keypair.generate().publicKey; const realm = Keypair.generate().publicKey;
@ -22,15 +32,16 @@ describe('voting-rights', () => {
// Uninitialized variables shared across tests. // Uninitialized variables shared across tests.
let registrar: PublicKey, let registrar: PublicKey,
votingMint: PublicKey, votingMint: PublicKey,
voter: PublicKey, voter: PublicKey,
votingToken: PublicKey, votingToken: PublicKey,
exchangeVaultA: PublicKey, exchangeVaultA: PublicKey,
exchangeVaultB: PublicKey; exchangeVaultB: PublicKey;
let registrarBump: number, votingMintBump: number, voterBump: number; let registrarBump: number, votingMintBump: number, voterBump: number;
let mintA: PublicKey, mintB: PublicKey, godA: PublicKey, godB: PublicKey; let mintA: PublicKey, mintB: PublicKey, godA: PublicKey, godB: PublicKey;
let tokenAClient: Token, tokenBClient: Token, votingTokenClient: Token;
it('Creates tokens and mints', async () => { it("Creates tokens and mints", async () => {
const decimals = 6; const decimals = 6;
const [_mintA, _godA] = await createMintAndVault( const [_mintA, _godA] = await createMintAndVault(
program.provider, program.provider,
@ -51,38 +62,38 @@ describe('voting-rights', () => {
godB = _godB; godB = _godB;
}); });
it('Creates PDAs', async () => { it("Creates PDAs", async () => {
const [_registrar, _registrarBump] = await PublicKey.findProgramAddress( const [_registrar, _registrarBump] = await PublicKey.findProgramAddress(
[realm.toBuffer()], [realm.toBuffer()],
program.programId, program.programId
); );
const [_votingMint, _votingMintBump] = await PublicKey.findProgramAddress( const [_votingMint, _votingMintBump] = await PublicKey.findProgramAddress(
[_registrar.toBuffer()], [_registrar.toBuffer()],
program.programId, program.programId
); );
const [_voter, _voterBump] = await PublicKey.findProgramAddress( const [_voter, _voterBump] = await PublicKey.findProgramAddress(
[_registrar.toBuffer(), program.provider.wallet.publicKey.toBuffer()], [_registrar.toBuffer(), program.provider.wallet.publicKey.toBuffer()],
program.programId, program.programId
); );
votingToken = await Token.getAssociatedTokenAddress( votingToken = await Token.getAssociatedTokenAddress(
associatedTokenProgram, associatedTokenProgram,
tokenProgram, tokenProgram,
_votingMint, _votingMint,
program.provider.wallet.publicKey, program.provider.wallet.publicKey
); );
exchangeVaultA = await Token.getAssociatedTokenAddress( exchangeVaultA = await Token.getAssociatedTokenAddress(
associatedTokenProgram, associatedTokenProgram,
tokenProgram, tokenProgram,
mintA, mintA,
_registrar, _registrar,
true, true
); );
exchangeVaultB = await Token.getAssociatedTokenAddress( exchangeVaultB = await Token.getAssociatedTokenAddress(
associatedTokenProgram, associatedTokenProgram,
tokenProgram, tokenProgram,
mintB, mintB,
_registrar, _registrar,
true, true
); );
registrar = _registrar; registrar = _registrar;
@ -94,23 +105,52 @@ describe('voting-rights', () => {
voterBump = _voterBump; voterBump = _voterBump;
}); });
it('Initializes a registrar', async () => { it("Creates token clients", async () => {
await program.rpc.initRegistrar(registrarBump, votingMintBump, votingMintDecimals, { tokenAClient = new Token(
accounts: { program.provider.connection,
registrar, mintA,
votingMint, TOKEN_PROGRAM_ID,
realm, // @ts-ignore
authority: program.provider.wallet.publicKey, program.provider.wallet.payer
payer: program.provider.wallet.publicKey, );
systemProgram, tokenBClient = new Token(
tokenProgram, program.provider.connection,
rent, mintB,
}, TOKEN_PROGRAM_ID,
}); // @ts-ignore
program.provider.wallet.payer
);
votingTokenClient = new Token(
program.provider.connection,
votingMint,
TOKEN_PROGRAM_ID,
// @ts-ignore
program.provider.wallet.payer
);
}); });
it('Initializes a voter', async () => { it("Initializes a registrar", async () => {
await program.rpc.initVoter(voterBump, { await program.rpc.createRegistrar(
registrarBump,
votingMintBump,
votingMintDecimals,
{
accounts: {
registrar,
votingMint,
realm,
authority: program.provider.wallet.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram,
tokenProgram,
rent,
},
}
);
});
it("Initializes a voter", async () => {
await program.rpc.createVoter(voterBump, {
accounts: { accounts: {
voter, voter,
votingToken, votingToken,
@ -121,17 +161,17 @@ describe('voting-rights', () => {
associatedTokenProgram, associatedTokenProgram,
tokenProgram, tokenProgram,
rent, rent,
} },
}); });
}); });
it('Adds an exchange rate', async () => { it("Adds an exchange rate", async () => {
const er = { const er = {
isUsed: false, isUsed: false,
mint: mintA, mint: mintA,
rate: new BN(1), rate: new BN(1),
} };
await program.rpc.addExchangeRate(er, { await program.rpc.createExchangeRate(er, {
accounts: { accounts: {
exchangeVault: exchangeVaultA, exchangeVault: exchangeVaultA,
depositMint: mintA, depositMint: mintA,
@ -142,36 +182,66 @@ describe('voting-rights', () => {
tokenProgram, tokenProgram,
associatedTokenProgram, associatedTokenProgram,
systemProgram, systemProgram,
} },
}) });
}); });
it('Deposits unlocked A tokens', async () => { it("Deposits unlocked A tokens", async () => {
const amount = new BN(10); const amount = new BN(10);
await program.rpc.deposit(amount, { await program.rpc.createDeposit(amount, null, {
accounts: { accounts: {
deposit: {
voter,
exchangeVault: exchangeVaultA,
depositToken: godA,
votingToken,
authority: program.provider.wallet.publicKey,
registrar,
depositMint: mintA,
votingMint,
tokenProgram,
},
},
});
const voterAccount = await program.account.voter.fetch(voter);
const deposit = voterAccount.deposits[0];
assert.ok(deposit.isUsed);
assert.ok(deposit.amount.toNumber() === 10);
assert.ok(deposit.rateIdx === 0);
const vtAccount = await votingTokenClient.getAccountInfo(votingToken);
assert.ok(vtAccount.amount.toNumber() === 10);
});
it("Withdraws unlocked A tokens", async () => {
const depositId = 0;
const amount = new BN(10);
await program.rpc.withdraw(depositId, amount, {
accounts: {
registrar,
voter, voter,
exchangeVault: exchangeVaultA, exchangeVault: exchangeVaultA,
depositToken: godA, withdrawMint: mintA,
votingToken, votingToken,
authority: program.provider.wallet.publicKey,
registrar,
depositMint: mintA,
votingMint, votingMint,
destination: godA,
authority: program.provider.wallet.publicKey,
tokenProgram, tokenProgram,
}, },
}); });
const voterAccount = await program.account.voter.fetch(voter); const voterAccount = await program.account.voter.fetch(voter);
console.log(voterAccount); const deposit = voterAccount.deposits[0];
assert.ok(deposit.isUsed);
assert.ok(deposit.amount.toNumber() === 0);
assert.ok(deposit.rateIdx === 0);
const vtAccount = await votingTokenClient.getAccountInfo(votingToken);
assert.ok(vtAccount.amount.toNumber() === 0);
}); });
it ('Deposits locked tokens', async () => { it("Deposits locked A tokens", async () => {
// todo
});
it('Mints voting rights', async () => {
}); });
}); });

View File

@ -972,6 +972,11 @@ picomatch@^2.0.4, picomatch@^2.2.1:
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
prettier@^2.4.1:
version "2.4.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c"
integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA==
randombytes@^2.1.0: randombytes@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"