Init
This commit is contained in:
commit
e4f63f7c8a
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
.anchor
|
||||||
|
.DS_Store
|
||||||
|
target
|
||||||
|
**/*.rs.bk
|
||||||
|
node_modules
|
|
@ -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"
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,4 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"programs/*"
|
||||||
|
]
|
|
@ -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.
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -0,0 +1,2 @@
|
||||||
|
[target.bpfel-unknown-unknown.dependencies.std]
|
||||||
|
features = []
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 () => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"types": ["mocha", "chai"],
|
||||||
|
"typeRoots": ["./node_modules/@types"],
|
||||||
|
"lib": ["es2015"],
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"esModuleInterop": true
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue