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