Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions programs/boilerplate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "gpl-boilerplate"
version = "0.1.1"
description = "SPL Governance addin boilerplate"
license = "Apache-2.0"
edition = "2018"

[lib]
crate-type = ["cdylib", "lib"]
name = "gpl_boilerplate"

[features]
no-entrypoint = []
no-idl = []
no-log-ix-name = []
cpi = ["no-entrypoint"]
default = []

[dependencies]
arrayref = "0.3.6"
anchor-lang = { version = "0.24.2", features = ["init-if-needed"] }
anchor-spl = "0.24.2"
itertools = "0.10.2"
solana-program = "1.9.13"
spl-governance = { version = "2.2.2", features = ["no-entrypoint"] }
spl-governance-tools= "0.1.2"
spl-token = { version = "3.3", features = [ "no-entrypoint" ] }

[dev-dependencies]
borsh = "0.9.1"
solana-sdk = "1.9.5"
solana-program-test = "1.9.13"
2 changes: 2 additions & 0 deletions programs/boilerplate/Xargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []
31 changes: 31 additions & 0 deletions programs/boilerplate/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use anchor_lang::prelude::*;

#[error_code]
pub enum BoilerplateError {
#[msg("Invalid Realm Authority")]
InvalidRealmAuthority,

#[msg("Invalid Realm for Registrar")]
InvalidRealmForRegistrar,

#[msg("Invalid MaxVoterWeightRecord Realm")]
InvalidMaxVoterWeightRecordRealm,

#[msg("Invalid MaxVoterWeightRecord Mint")]
InvalidMaxVoterWeightRecordMint,

#[msg("CastVote Is Not Allowed")]
CastVoteIsNotAllowed,

#[msg("Invalid VoterWeightRecord Realm")]
InvalidVoterWeightRecordRealm,

#[msg("Invalid VoterWeightRecord Mint")]
InvalidVoterWeightRecordMint,

#[msg("Invalid TokenOwner for VoterWeightRecord")]
InvalidTokenOwnerForVoterWeightRecord,

#[msg("Invalid account owner")]
InvalidAccountOwner,
}
80 changes: 80 additions & 0 deletions programs/boilerplate/src/instructions/cast_vote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::error::BoilerplateError;
use crate::{state::*};
use anchor_lang::prelude::*;
use anchor_lang::Accounts;

/// Casts a vote using the voter weight record.
/// This instruction updates VoterWeightRecord which is valid for the current Slot and the target Proposal only
/// and hance the instruction has to be executed inside the same transaction as spl-gov.CastVote
///
/// CastVote is accumulative and can be invoked using several transactions
/// In this scenario only the last CastVote should be bundled with spl-gov.CastVote in the same transaction
///
/// NOTE - Boilerplate: All implementations of this boilerplate should prevent multiple voting
/// with the same tokens - this is not added by the boilerplate because it is use-case-specific
///
/// CastVote instruction is not directional. It does not record vote choice (ex Yes/No)
/// VoteChoice is recorded by spl-gov in VoteRecord
///
#[derive(Accounts)]
#[instruction(proposal: Pubkey)]
pub struct CastVote<'info> {
/// The voting registrar
pub registrar: Account<'info, Registrar>,

#[account(
mut,
constraint = voter_weight_record.realm == registrar.realm
@ BoilerplateError::InvalidVoterWeightRecordRealm,

constraint = voter_weight_record.governing_token_mint == registrar.governing_token_mint
@ BoilerplateError::InvalidVoterWeightRecordMint,
)]
pub voter_weight_record: Account<'info, VoterWeightRecord>,

/// The token owner who casts the vote
#[account(
address = voter_weight_record.governing_token_owner @ BoilerplateError::InvalidTokenOwnerForVoterWeightRecord
)]
pub governing_token_owner: Signer<'info>,

/// The account which pays for the transaction
#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

/// Casts vote using a dummy voter weight of 1
pub fn cast_vote<'a, 'b, 'c, 'info>(
ctx: Context<'a, 'b, 'c, 'info, CastVote<'info>>,
proposal: Pubkey,
) -> Result<()> {
// Boilerplate: your logic here
let voter_weight = 1;
let voter_weight_record = &mut ctx.accounts.voter_weight_record;

if voter_weight_record.weight_action_target == Some(proposal)
&& voter_weight_record.weight_action == Some(VoterWeightAction::CastVote)
{
// If cast_vote is called for the same proposal then we keep accumulating the weight
// this way cast_vote can be called multiple times in different transactions
// NOTE - Boilerplate: All implementations of this boilerplate should prevent multiple voting
// with the same tokens - this is not added by the boilerplate because it is use-case-specific
voter_weight_record.voter_weight = voter_weight_record
.voter_weight
.checked_add(voter_weight)
.unwrap();
} else {
voter_weight_record.voter_weight = voter_weight;
}

// The record is only valid as of the current slot
voter_weight_record.voter_weight_expiry = Some(Clock::get()?.slot);

// The record is only valid for casting vote on the given Proposal
voter_weight_record.weight_action = Some(VoterWeightAction::CastVote);
voter_weight_record.weight_action_target = Some(proposal);

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use spl_governance::state::realm;

use crate::state::max_voter_weight_record::MaxVoterWeightRecord;

/// Creates MaxVoterWeightRecord used by spl-gov
/// This instruction should only be executed once per realm/governing_token_mint to create the account
#[derive(Accounts)]
pub struct CreateMaxVoterWeightRecord<'info> {
#[account(
init,
seeds = [ b"max-voter-weight-record".as_ref(),
realm.key().as_ref(),
realm_governing_token_mint.key().as_ref()],
bump,
payer = payer,
space = MaxVoterWeightRecord::get_space()
)]
pub max_voter_weight_record: Account<'info, MaxVoterWeightRecord>,

/// The program id of the spl-governance program the realm belongs to
/// CHECK: Can be any instance of spl-governance and it's not known at the compilation time
#[account(executable)]
pub governance_program_id: UncheckedAccount<'info>,

#[account(owner = governance_program_id.key())]
/// CHECK: Owned by spl-governance instance specified in governance_program_id
pub realm: UncheckedAccount<'info>,

/// Either the realm community mint or the council mint.
pub realm_governing_token_mint: Account<'info, Mint>,

#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

pub fn create_max_voter_weight_record(ctx: Context<CreateMaxVoterWeightRecord>) -> Result<()> {
// Deserialize the Realm to validate it
let _realm = realm::get_realm_data_for_governing_token_mint(
&ctx.accounts.governance_program_id.key(),
&ctx.accounts.realm,
&ctx.accounts.realm_governing_token_mint.key(),
)?;

let max_voter_weight_record = &mut ctx.accounts.max_voter_weight_record;

max_voter_weight_record.realm = ctx.accounts.realm.key();
max_voter_weight_record.governing_token_mint = ctx.accounts.realm_governing_token_mint.key();

// In the boilerplate, the max_voter_weight never expires
// Boilerplate: your logic here
max_voter_weight_record.max_voter_weight_expiry = None;

Ok(())
}
81 changes: 81 additions & 0 deletions programs/boilerplate/src/instructions/create_registrar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use crate::error::BoilerplateError;
use crate::state::*;
use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use spl_governance::state::realm;

/// Creates an add-in Registrar for spl-gov Realm
/// This instruction should only be executed once per realm/governing_token_mint to create the account
#[derive(Accounts)]
#[instruction(max_collections: u8)]
pub struct CreateRegistrar<'info> {
/// The Boilerplate Registrar
/// There can only be a single registrar per governance Realm and governing mint of the Realm
#[account(
init,
seeds = [b"registrar".as_ref(),realm.key().as_ref(), governing_token_mint.key().as_ref()],
bump,
payer = payer,
space = Registrar::get_space()
)]
pub registrar: Account<'info, Registrar>,

/// The program id of the spl-governance program the realm belongs to
/// CHECK: Can be any instance of spl-governance and it's not known at the compilation time
#[account(executable)]
pub governance_program_id: UncheckedAccount<'info>,

/// An spl-governance Realm
///
/// Realm is validated in the instruction:
/// - Realm is owned by the governance_program_id
/// - governing_token_mint must be the community or council mint
/// - realm_authority is realm.authority
/// CHECK: Owned by spl-governance instance specified in governance_program_id
#[account(owner = governance_program_id.key())]
pub realm: UncheckedAccount<'info>,

/// Either the realm community mint or the council mint.
/// It must match Realm.community_mint or Realm.config.council_mint
///
/// Note: Once the NFT plugin is enabled the governing_token_mint is used only as identity
/// for the voting population and the tokens of that are no longer used
pub governing_token_mint: Account<'info, Mint>,

/// realm_authority must sign and match Realm.authority
pub realm_authority: Signer<'info>,

#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

/// Creates a new Registrar which stores NFT voting configuration for given Realm
///
/// To use the registrar, call ConfigureCollection to register NFT collections that may be
/// used for governance
///
/// max_collections is used allocate account size for the maximum number of governing NFT collections
/// Note: Once Solana runtime supports account resizing the max value won't be required
pub fn create_registrar(ctx: Context<CreateRegistrar>, _max_collections: u8) -> Result<()> {
let registrar = &mut ctx.accounts.registrar;
registrar.governance_program_id = ctx.accounts.governance_program_id.key();
registrar.realm = ctx.accounts.realm.key();
registrar.governing_token_mint = ctx.accounts.governing_token_mint.key();

// Verify that realm_authority is the expected authority of the Realm
// and that the mint matches one of the realm mints too
let realm = realm::get_realm_data_for_governing_token_mint(
&registrar.governance_program_id,
&ctx.accounts.realm,
&registrar.governing_token_mint,
)?;

require!(
realm.authority.unwrap() == ctx.accounts.realm_authority.key(),
BoilerplateError::InvalidRealmAuthority
);

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use crate::state::*;
use anchor_lang::prelude::*;
use anchor_spl::token::Mint;
use spl_governance::state::realm;

/// Creates VoterWeightRecord used by spl-gov
/// This instruction should only be executed once per realm/governing_token_mint/governing_token_owner
/// to create the account
#[derive(Accounts)]
#[instruction(governing_token_owner: Pubkey)]
pub struct CreateVoterWeightRecord<'info> {
#[account(
init,
seeds = [ b"voter-weight-record".as_ref(),
realm.key().as_ref(),
realm_governing_token_mint.key().as_ref(),
governing_token_owner.as_ref()],
bump,
payer = payer,
space = VoterWeightRecord::get_space()
)]
pub voter_weight_record: Account<'info, VoterWeightRecord>,

/// The program id of the spl-governance program the realm belongs to
/// CHECK: Can be any instance of spl-governance and it's not known at the compilation time
#[account(executable)]
pub governance_program_id: UncheckedAccount<'info>,

/// CHECK: Owned by spl-governance instance specified in governance_program_id
#[account(owner = governance_program_id.key())]
pub realm: UncheckedAccount<'info>,

/// Either the realm community mint or the council mint.
pub realm_governing_token_mint: Account<'info, Mint>,

#[account(mut)]
pub payer: Signer<'info>,

pub system_program: Program<'info, System>,
}

pub fn create_voter_weight_record(
ctx: Context<CreateVoterWeightRecord>,
governing_token_owner: Pubkey,
) -> Result<()> {
// Deserialize the Realm to validate it
let _realm = realm::get_realm_data_for_governing_token_mint(
&ctx.accounts.governance_program_id.key(),
&ctx.accounts.realm,
&ctx.accounts.realm_governing_token_mint.key(),
)?;

let voter_weight_record = &mut ctx.accounts.voter_weight_record;

voter_weight_record.realm = ctx.accounts.realm.key();
voter_weight_record.governing_token_mint = ctx.accounts.realm_governing_token_mint.key();
voter_weight_record.governing_token_owner = governing_token_owner;

// Set expiry to expired
voter_weight_record.voter_weight_expiry = Some(0);

Ok(())
}
14 changes: 14 additions & 0 deletions programs/boilerplate/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pub use create_registrar::*;
mod create_registrar;

pub use create_voter_weight_record::*;
mod create_voter_weight_record;

pub use create_max_voter_weight_record::*;
mod create_max_voter_weight_record;

pub use update_voter_weight_record::*;
mod update_voter_weight_record;

pub use cast_vote::*;
mod cast_vote;
Loading