This commit is contained in:
armaniferrante 2021-10-05 13:40:01 -07:00
commit e4f63f7c8a
No known key found for this signature in database
GPG Key ID: 58BEF301E91F7828
12 changed files with 2932 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.anchor
.DS_Store
target
**/*.rs.bk
node_modules

9
Anchor.toml Normal file
View File

@ -0,0 +1,9 @@
[programs.localnet]
governance_registry = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"
[provider]
cluster = "localnet"
wallet = "~/.config/solana/id.json"
[scripts]
test = "ts-mocha -p ./tsconfig.json -t 1000000 tests/*.ts"

1292
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

4
Cargo.toml Normal file
View File

@ -0,0 +1,4 @@
[workspace]
members = [
"programs/*"
]

12
migrations/deploy.ts Normal file
View File

@ -0,0 +1,12 @@
// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@project-serum/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
}

12
package.json Normal file
View File

@ -0,0 +1,12 @@
{
"dependencies": {
"@project-serum/anchor": "^0.16.1"
},
"devDependencies": {
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^8.0.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5"
}
}

View File

@ -0,0 +1,19 @@
[package]
name = "governance-registry"
version = "0.1.0"
description = "Created with Anchor"
edition = "2018"
[lib]
crate-type = ["cdylib", "lib"]
name = "governance_registry"
[features]
no-entrypoint = []
no-idl = []
cpi = ["no-entrypoint"]
default = []
[dependencies]
anchor-lang = "0.16.1"
anchor-spl = "0.16.1"

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,272 @@
use anchor_lang::prelude::*;
use anchor_spl::token::{self, Mint, Token, TokenAccount};
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod governance_registry {
use super::*;
/// Creates a new voting registrar.
pub fn init_registrar(
ctx: Context<InitRegistrar>,
_voting_mint_decimals: u8,
registrar_bump: u8,
voting_mint_bump: u8,
) -> Result<()> {
let registrar = &mut ctx.accounts.registrar;
registrar.registrar_bump = registrar_bump;
registrar.voting_mint_bump = voting_mint_bump;
registrar.realm = ctx.accounts.realm.key();
registrar.voting_mint = ctx.accounts.voting_mint.key();
registrar.authority = ctx.accounts.authority.key();
if true {
panic!("HELLO WORLD");
}
Ok(())
}
/// Creates a new voter account.
pub fn init_voter(ctx: Context<InitVoter>, voter_bump: u8) -> Result<()> {
let voter = &mut ctx.accounts.voter;
voter.voter_bump = voter_bump;
voter.authority = ctx.accounts.authority.key();
voter.registrar = ctx.accounts.registrar.key();
voter.rights_outstanding = 0;
Ok(())
}
/// Creates a new exchange rate for a given mint. The exchange rate
/// allows one to deposit one token into the registrar and receive voting
/// tokens in response. Only the registrar authority can invoke this.
pub fn add_exchange_rate(
ctx: Context<AddExchangeRate>,
rate: u64,
rate_bump: u8,
vault_bump: u8,
) -> Result<()> {
require!(rate > 0, InvalidRate);
let rate = &mut ctx.accounts.exchange_rate;
rate.deposit_mint = ctx.accounts.deposit_mint.key();
rate.rate_bump = rate_bump;
rate.vault_bump = vault_bump;
Ok(())
}
/// Deposits tokens into the registrar in exchange for voting rights that
/// can be used with a DAO.
pub fn mint_voting_rights(ctx: Context<MintVotingRights>, amount: u64) -> Result<()> {
// Deposit tokens into the registrar.
token::transfer((&*ctx.accounts).into(), amount)?;
// Calculate the amount of voting tokens to mint.
let scaled_amount = { amount };
// Mint vote tokens to the depositor.
token::mint_to((&*ctx.accounts).into(), scaled_amount)?;
Ok(())
}
/// Updates the Voter's voting rights by decaying locked deposits.
pub fn decay_voting_rights(ctx: Context<DecayVotingRights>) -> Result<()> {
// todo
Ok(())
}
}
// Contexts.
#[derive(Accounts)]
#[instruction(voting_mint_decimals: u8, registrar_bump: u8, voting_mint_bump: u8)]
pub struct InitRegistrar<'info> {
#[account(
init,
seeds = [realm.key().as_ref()],
bump = registrar_bump,
payer = payer,
)]
registrar: Account<'info, Registrar>,
#[account(
init,
seeds = [registrar.key().as_ref()],
bump = voting_mint_bump,
payer = payer,
mint::authority = registrar,
mint::decimals = voting_mint_decimals,
)]
voting_mint: Account<'info, Mint>,
realm: UncheckedAccount<'info>,
authority: UncheckedAccount<'info>,
payer: Signer<'info>,
system_program: Program<'info, System>,
token_program: Program<'info, Token>,
rent: Sysvar<'info, Rent>,
}
#[derive(Accounts)]
#[instruction(voter_bump: u8)]
pub struct InitVoter<'info> {
#[account(
init,
seeds = [registrar.key().as_ref(), authority.key().as_ref()],
bump = voter_bump,
payer = authority,
)]
voter: Account<'info, Voter>,
registrar: Account<'info, Registrar>,
authority: Signer<'info>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
#[instruction(rate_bump: u8, vault_bump: u8)]
pub struct AddExchangeRate<'info> {
#[account(
init,
seeds = [b"exchange-rate", registrar.key().as_ref(), deposit_mint.key().as_ref()],
bump = rate_bump,
payer = payer,
)]
exchange_rate: Account<'info, ExchangeRate>,
#[account(
init,
seeds = [b"exchange-vault", exchange_rate.key().as_ref()],
bump = vault_bump,
payer = payer,
token::authority = registrar,
token::mint = deposit_mint,
)]
exchange_vault: Account<'info, TokenAccount>,
deposit_mint: Account<'info, Mint>,
#[account(has_one = authority)]
registrar: Account<'info, Registrar>,
authority: Signer<'info>,
payer: Signer<'info>,
rent: Sysvar<'info, Rent>,
token_program: Program<'info, Token>,
system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct MintVotingRights<'info> {
#[account(
seeds = [b"exchange-rate", registrar.key().as_ref(), deposit_mint.key().as_ref()],
bump = exchange_rate.rate_bump,
)]
exchange_rate: Account<'info, ExchangeRate>,
#[account(
seeds = [b"exchange-vault", exchange_rate.key().as_ref()],
bump = exchange_rate.vault_bump,
)]
exchange_vault: Account<'info, TokenAccount>,
#[account(
constraint = deposit_token.mint == deposit_mint.key(),
)]
deposit_token: Account<'info, TokenAccount>,
#[account(
constraint = registrar.voting_mint == voting_token.mint,
)]
voting_token: Account<'info, TokenAccount>,
authority: Signer<'info>,
registrar: Account<'info, Registrar>,
deposit_mint: Account<'info, Mint>,
voting_mint: Account<'info, Mint>,
token_program: Program<'info, Token>,
}
#[derive(Accounts)]
pub struct DecayVotingRights<'info> {
voter: Account<'info, Voter>,
deposit: Account<'info, VoterDeposit>,
}
// Accounts.
/// Instance of a voting rights distributor.
#[account]
#[derive(Default)]
pub struct Registrar {
pub authority: Pubkey,
pub realm: Pubkey,
pub voting_mint: Pubkey,
pub voting_mint_bump: u8,
pub registrar_bump: u8,
}
/// User account for minting voting rights.
#[account]
#[derive(Default)]
pub struct Voter {
pub authority: Pubkey,
pub registrar: Pubkey,
pub rights_outstanding: u64,
pub voter_bump: u8,
}
pub struct Deposit {
pub mint_idx: u8,
pub lockup_years: u8,
pub amount: u64,
}
/// Sub account for a
#[account]
pub struct VoterDeposit {
amount: u64,
exchange_rate: Pubkey,
}
/// Exchange rate for an asset that can be used to mint voting rights.
#[account]
#[derive(Default)]
pub struct ExchangeRate {
deposit_mint: Pubkey,
rate: u64,
rate_bump: u8,
vault_bump: u8,
// Locked state.
period_count: u64,
start_ts: i64,
end_ts: i64,
}
// Error.
#[error]
pub enum ErrorCode {
#[msg("Exchange rate must be greater than zero")]
InvalidRate,
}
// CpiContext.
impl<'info> From<&MintVotingRights<'info>>
for CpiContext<'_, '_, '_, 'info, token::Transfer<'info>>
{
fn from(accs: &MintVotingRights<'info>) -> Self {
let program = accs.token_program.to_account_info();
let accounts = token::Transfer {
from: accs.deposit_token.to_account_info(),
to: accs.exchange_vault.to_account_info(),
authority: accs.registrar.to_account_info(),
};
CpiContext::new(program, accounts)
}
}
impl<'info> From<&MintVotingRights<'info>> for CpiContext<'_, '_, '_, 'info, token::MintTo<'info>> {
fn from(accs: &MintVotingRights<'info>) -> Self {
let program = accs.token_program.to_account_info();
let accounts = token::MintTo {
mint: accs.voting_mint.to_account_info(),
to: accs.voting_token.to_account_info(),
authority: accs.registrar.to_account_info(),
};
CpiContext::new(program, accounts)
}
}

View File

@ -0,0 +1,80 @@
import * as anchor from '@project-serum/anchor';
import { PublicKey, Keypair, SystemProgram, SYSVAR_RENT_PUBKEY } from '@solana/web3.js';
import { TOKEN_PROGRAM_ID } from '@solana/spl-token';
describe('voting-rights', () => {
// Configure the client to use the local cluster.
anchor.setProvider(anchor.Provider.env());
const program = anchor.workspace.GovernanceRegistry;
const realm = Keypair.generate().publicKey;
const votingMintDecimals = 6;
let registrar: PublicKey, votingMint: PublicKey, voter: PublicKey;
let registrarBump: number, votingMintBump: number, voterBump: number;
it('Creates PDAs', async () => {
const [_registrar, _registrarBump] = await PublicKey.findProgramAddress(
[realm.toBuffer()],
program.programId,
);
const [_votingMint, _votingMintBump] = await PublicKey.findProgramAddress(
[_registrar.toBuffer()],
program.programId,
);
const [_voter, _voterBump] = await PublicKey.findProgramAddress(
[_registrar.toBuffer(), program.provider.wallet.publicKey.toBuffer()],
program.programId,
);
registrar = _registrar;
votingMint = _votingMint;
voter = _voter;
registrarBump = _registrarBump;
votingMintBump = _votingMintBump;
voterBump = _voterBump;
});
it('Initializes a registrar', async () => {
await program.rpc.initRegistrar(votingMintDecimals, registrarBump, votingMintBump, {
accounts: {
registrar,
votingMint,
realm,
authority: program.provider.wallet.publicKey,
payer: program.provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
tokenProgram: TOKEN_PROGRAM_ID,
rent: SYSVAR_RENT_PUBKEY,
}
});
const registrarAccount = await program.account.registrar.fetch(registrar);
console.log(registrarAccount);
});
it('Initializes a voter', async () => {
await program.rpc.initVoter(voterBump, {
accounts: {
voter,
registrar,
authority: program.provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
}
});
const voterAccount = await program.account.voter.fetch(voter);
console.log(voterAccount);
});
it('Adds an exchange rate', async () => {
});
it('Mints voting rights', async () => {
});
});

10
tsconfig.json Normal file
View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

1214
yarn.lock Normal file

File diff suppressed because it is too large Load Diff