diff --git a/package.json b/package.json index 2c8b1ec..6ffdfcd 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "scripts": { + "scripts": { "lint:fix": "prettier tests/** -w", "test": "anchor test" }, diff --git a/programs/governance-registry/src/access_control.rs b/programs/governance-registry/src/access_control.rs index 75ecb84..7d7386e 100644 --- a/programs/governance-registry/src/access_control.rs +++ b/programs/governance-registry/src/access_control.rs @@ -1,4 +1,3 @@ -use crate::account::*; use crate::context::*; use crate::error::*; use anchor_lang::prelude::*; diff --git a/programs/governance-registry/src/account.rs b/programs/governance-registry/src/account.rs index 6f600fc..e1f58eb 100644 --- a/programs/governance-registry/src/account.rs +++ b/programs/governance-registry/src/account.rs @@ -20,6 +20,7 @@ pub const MAX_DAYS_LOCKED: u64 = 2555; pub struct Registrar { pub authority: Pubkey, pub realm: Pubkey, + pub realm_community_mint: Pubkey, pub warmup_secs: i64, pub bump: u8, // The length should be adjusted for one's use case. @@ -32,6 +33,7 @@ pub struct Voter { pub authority: Pubkey, pub registrar: Pubkey, pub voter_bump: u8, + pub voter_weight_record_bump: u8, pub deposits: [DepositEntry; 32], } @@ -286,7 +288,7 @@ impl Lockup { u64::try_from({ let secs_elapsed = curr_ts.checked_sub(self.start_ts).unwrap(); - secs_elapsed.checked_sub(SECS_PER_DAY).unwrap() + secs_elapsed.checked_div(SECS_PER_DAY).unwrap() }) .map_err(|_| ErrorCode::UnableToConvert.into()) } diff --git a/programs/governance-registry/src/context.rs b/programs/governance-registry/src/context.rs index d9d633c..38175ce 100644 --- a/programs/governance-registry/src/context.rs +++ b/programs/governance-registry/src/context.rs @@ -4,6 +4,8 @@ use anchor_spl::associated_token::AssociatedToken; use anchor_spl::token::{self, Mint, Token, TokenAccount}; use std::mem::size_of; +pub const VOTER_WEIGHT_RECORD: [u8; 19] = *b"voter-weight-record"; + #[derive(Accounts)] #[instruction(warmup_secs: i64, registrar_bump: u8)] pub struct CreateRegistrar<'info> { @@ -15,7 +17,10 @@ pub struct CreateRegistrar<'info> { space = 8 + size_of::() )] pub registrar: AccountLoader<'info, Registrar>, + // Unsafe and untrusted. This instruction needs to be invoked immediatley + // after the realm is created. pub realm: UncheckedAccount<'info>, + pub realm_community_mint: Account<'info, Mint>, pub authority: UncheckedAccount<'info>, pub payer: Signer<'info>, pub system_program: Program<'info, System>, @@ -24,7 +29,7 @@ pub struct CreateRegistrar<'info> { } #[derive(Accounts)] -#[instruction(voter_bump: u8)] +#[instruction(voter_bump: u8, voter_weight_record_bump: u8)] pub struct CreateVoter<'info> { #[account( init, @@ -34,8 +39,17 @@ pub struct CreateVoter<'info> { space = 8 + size_of::(), )] pub voter: AccountLoader<'info, Voter>, + #[account( + init +, seeds = [VOTER_WEIGHT_RECORD.as_ref(), registrar.key().as_ref(), authority.key().as_ref()], + bump = voter_weight_record_bump, + payer = payer, + space = 150, + )] + pub voter_weight_record: Account<'info, VoterWeightRecord>, pub registrar: AccountLoader<'info, Registrar>, pub authority: Signer<'info>, + pub payer: Signer<'info>, pub token_program: Program<'info, Token>, pub associated_token_program: Program<'info, AssociatedToken>, pub system_program: Program<'info, System>, @@ -237,9 +251,9 @@ pub struct UpdateSchedule<'info> { } #[derive(Accounts)] -pub struct DecayVotingPower<'info> { +pub struct UpdateVoterWeightRecord<'info> { #[account( - seeds = [vote_weight_record.realm.as_ref()], + seeds = [voter_weight_record.realm.as_ref()], bump = registrar.load()?.bump, )] pub registrar: AccountLoader<'info, Registrar>, @@ -249,11 +263,15 @@ pub struct DecayVotingPower<'info> { )] pub voter: AccountLoader<'info, Voter>, #[account( - mut, - constraint = vote_weight_record.governing_token_owner == voter.load()?.authority, + mut, + seeds = [VOTER_WEIGHT_RECORD.as_ref(), registrar.key().as_ref(), authority.key().as_ref()], + bump = voter.load()?.voter_weight_record_bump, + constraint = voter_weight_record.realm == registrar.load()?.realm, + constraint = voter_weight_record.governing_token_owner == voter.load()?.authority, )] - pub vote_weight_record: Account<'info, VoterWeightRecord>, + pub voter_weight_record: Account<'info, VoterWeightRecord>, pub authority: Signer<'info>, + pub system_program: Program<'info, System>, } #[derive(Accounts)] diff --git a/programs/governance-registry/src/lib.rs b/programs/governance-registry/src/lib.rs index d3cdc1b..fcf84af 100644 --- a/programs/governance-registry/src/lib.rs +++ b/programs/governance-registry/src/lib.rs @@ -4,6 +4,7 @@ use anchor_lang::prelude::*; use anchor_spl::token; use context::*; use error::*; +use spl_governance::addins::voter_weight::VoterWeightAccountType; mod access_control; mod account; @@ -71,6 +72,7 @@ pub mod governance_registry { let registrar = &mut ctx.accounts.registrar.load_init()?; registrar.bump = registrar_bump; registrar.realm = ctx.accounts.realm.key(); + registrar.realm_community_mint = ctx.accounts.realm_community_mint.key(); registrar.authority = ctx.accounts.authority.key(); registrar.warmup_secs = warmup_secs; @@ -97,12 +99,28 @@ pub mod governance_registry { /// Creates a new voter account. There can only be a single voter per /// user wallet. - pub fn create_voter(ctx: Context, voter_bump: u8) -> Result<()> { + pub fn create_voter( + ctx: Context, + voter_bump: u8, + voter_weight_record_bump: u8, + ) -> Result<()> { + // Load accounts. + let registrar = &ctx.accounts.registrar.load()?; let voter = &mut ctx.accounts.voter.load_init()?; + let voter_weight_record = &mut ctx.accounts.voter_weight_record; + + // Init the voter. voter.voter_bump = voter_bump; + voter.voter_weight_record_bump = voter_weight_record_bump; voter.authority = ctx.accounts.authority.key(); voter.registrar = ctx.accounts.registrar.key(); + // Init the voter weight record. + voter_weight_record.account_type = VoterWeightAccountType::VoterWeightRecord; + voter_weight_record.realm = registrar.realm; + voter_weight_record.governing_token_mint = registrar.realm_community_mint; + voter_weight_record.governing_token_owner = ctx.accounts.authority.key(); + Ok(()) } @@ -306,11 +324,12 @@ pub mod governance_registry { /// /// This "revise" instruction should be called in the same transaction, /// immediately before voting. - pub fn decay_voting_power(ctx: Context) -> Result<()> { + pub fn update_voter_weight_record(ctx: Context) -> Result<()> { let voter = ctx.accounts.voter.load()?; - let record = &mut ctx.accounts.vote_weight_record; + let record = &mut ctx.accounts.voter_weight_record; record.voter_weight = voter.weight()?; record.voter_weight_expiry = Some(Clock::get()?.slot); + Ok(()) } diff --git a/tests/governance-registry.ts b/tests/governance-registry.ts index 10c11ad..02a24f2 100644 --- a/tests/governance-registry.ts +++ b/tests/governance-registry.ts @@ -32,21 +32,27 @@ describe("voting-rights", () => { // Uninitialized variables shared across tests. let registrar: PublicKey, - votingMintA: PublicKey, - votingMintB: PublicKey, + votingMintA: PublicKey, + votingMintB: PublicKey, voter: PublicKey, + voterWeightRecord: PublicKey, votingToken: PublicKey, exchangeVaultA: PublicKey, exchangeVaultB: PublicKey; let registrarBump: number, - votingMintBumpA: number, - votingMintBumpB: number, - voterBump: number; - let mintA: PublicKey, mintB: PublicKey, godA: PublicKey, godB: PublicKey; + votingMintBumpA: number, + votingMintBumpB: number, + voterBump: number, + voterWeightRecordBump: number; + let mintA: PublicKey, + mintB: PublicKey, + godA: PublicKey, + godB: PublicKey, + realmCommunityMint: PublicKey; let tokenAClient: Token, - tokenBClient: Token, - votingTokenClientA: Token, - votingTokenClientB: Token; + tokenBClient: Token, + votingTokenClientA: Token, + votingTokenClientB: Token; it("Creates tokens and mints", async () => { const [_mintA, _godA] = await createMintAndVault( @@ -66,6 +72,7 @@ describe("voting-rights", () => { mintB = _mintB; godA = _godA; godB = _godB; + realmCommunityMint = mintA; }); it("Creates PDAs", async () => { @@ -85,6 +92,15 @@ describe("voting-rights", () => { [_registrar.toBuffer(), program.provider.wallet.publicKey.toBuffer()], program.programId ); + const [_voterWeightRecord, _voterWeightRecordBump] = + await PublicKey.findProgramAddress( + [ + anchor.utils.bytes.utf8.encode("voter-weight-record"), + _registrar.toBuffer(), + program.provider.wallet.publicKey.toBuffer(), + ], + program.programId + ); votingToken = await Token.getAssociatedTokenAddress( associatedTokenProgram, tokenProgram, @@ -108,13 +124,15 @@ describe("voting-rights", () => { registrar = _registrar; votingMintA = _votingMintA; - votingMintB = _votingMintB; + votingMintB = _votingMintB; voter = _voter; registrarBump = _registrarBump; votingMintBumpA = _votingMintBumpA; - votingMintBumpB = _votingMintBumpB; + votingMintBumpB = _votingMintBumpB; voterBump = _voterBump; + voterWeightRecord = _voterWeightRecord; + voterWeightRecordBump = _voterWeightRecordBump; }); it("Creates token clients", async () => { @@ -149,21 +167,18 @@ describe("voting-rights", () => { }); it("Initializes a registrar", async () => { - await program.rpc.createRegistrar( - new BN(0), - registrarBump, - { - accounts: { - registrar, - realm, - authority: program.provider.wallet.publicKey, - payer: program.provider.wallet.publicKey, - systemProgram, - tokenProgram, - rent, - }, - } - ); + await program.rpc.createRegistrar(new BN(0), registrarBump, { + accounts: { + registrar, + realm, + realmCommunityMint, + authority: program.provider.wallet.publicKey, + payer: program.provider.wallet.publicKey, + systemProgram, + tokenProgram, + rent, + }, + }); }); it("Adds an exchange rate A", async () => { @@ -176,7 +191,7 @@ describe("voting-rights", () => { accounts: { exchangeVault: exchangeVaultA, depositMint: mintA, - votingMint: votingMintA, + votingMint: votingMintA, registrar, authority: program.provider.wallet.publicKey, rent, @@ -197,7 +212,7 @@ describe("voting-rights", () => { accounts: { exchangeVault: exchangeVaultB, depositMint: mintB, - votingMint: votingMintB, + votingMint: votingMintB, registrar, authority: program.provider.wallet.publicKey, rent, @@ -209,11 +224,13 @@ describe("voting-rights", () => { }); it("Initializes a voter", async () => { - await program.rpc.createVoter(voterBump, { + await program.rpc.createVoter(voterBump, voterWeightRecordBump, { accounts: { voter, + voterWeightRecord, registrar, authority: program.provider.wallet.publicKey, + payer: program.provider.wallet.publicKey, systemProgram, associatedTokenProgram, tokenProgram, @@ -238,9 +255,9 @@ describe("voting-rights", () => { depositMint: mintA, votingMint: votingMintA, tokenProgram, - systemProgram, - associatedTokenProgram, - rent, + systemProgram, + associatedTokenProgram, + rent, }, }, }); @@ -300,9 +317,9 @@ describe("voting-rights", () => { depositMint: mintA, votingMint: votingMintA, tokenProgram, - systemProgram, - associatedTokenProgram, - rent, + systemProgram, + associatedTokenProgram, + rent, }, }, }); @@ -313,4 +330,16 @@ describe("voting-rights", () => { assert.ok(deposit.amountDeposited.toNumber() === 10); assert.ok(deposit.rateIdx === 0); }); + + it("Updates a vote weight record", async () => { + await program.rpc.updateVoterWeightRecord({ + accounts: { + registrar, + voter, + voterWeightRecord, + authority: program.provider.wallet.publicKey, + systemProgram, + }, + }); + }); });