Skip to content
This repository was archived by the owner on Feb 9, 2025. It is now read-only.

Squads Voter Skeleton #55

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
16 changes: 16 additions & 0 deletions Cargo.lock

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

31 changes: 31 additions & 0 deletions programs/squads-voter/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[package]
name = "gpl-squads-voter"
version = "0.0.1"
description = "SPL Governance addin implementing Squads Protocol based governance"
license = "Apache-2.0"
edition = "2018"

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

[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"
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/squads-voter/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/squads-voter/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 SquadsVoterError {
#[msg("Invalid Realm Authority")]
InvalidRealmAuthority,

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

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

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

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

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

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

#[msg("Squad not found")]
SquadNotFound,

#[msg("Duplicated Squad detected")]
DuplicatedSquadDetected,
}
77 changes: 77 additions & 0 deletions programs/squads-voter/src/instructions/configure_squad.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use anchor_lang::{
account,
prelude::{Context, Signer},
Accounts,
};

use anchor_lang::prelude::*;
use spl_governance::state::realm;

use crate::error::SquadsVoterError;
use crate::state::{Registrar, SquadConfig};

/// Creates or updates Squad configuration which defines what Squads can be used for governances
/// and what weight they have
#[derive(Accounts)]
pub struct ConfigureSquad<'info> {
/// Registrar for which we configure this Squad
#[account(mut)]
pub registrar: Account<'info, Registrar>,

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

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

// Squad which is going to be used for governance
/// CHECK: Owned by squads-protocol
pub squad: UncheckedAccount<'info>,
}

pub fn configure_squad(ctx: Context<ConfigureSquad>, weight: u64) -> Result<()> {
let registrar = &mut ctx.accounts.registrar;

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(),
SquadsVoterError::InvalidRealmAuthority
);

let squad = &ctx.accounts.squad;

// TODO: Assert Squad owned by squads-protocol

let squad_config = SquadConfig {
squad: squad.key(),
weight,
reserved: [0; 8],
};

let squad_idx = registrar
.squads_configs
.iter()
.position(|cc| cc.squad == squad.key());

if let Some(squad_idx) = squad_idx {
registrar.squads_configs[squad_idx] = squad_config;
} else {
// Note: In the current runtime version push() would throw an error if we exceed
// max_squads specified when the Registrar was created
registrar.squads_configs.push(squad_config);
}

// TODO: if weight == 0 then remove the Squad from config
// If weight is set to 0 then the Squad won't be removed but it won't have any governance power

Ok(())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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();

// Set expiry to expired
max_voter_weight_record.max_voter_weight_expiry = Some(0);

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

/// Creates Registrar storing Squads governance configuration for spl-gov Realm
/// This instruction should only be executed once per realm/governing_token_mint to create the account
#[derive(Accounts)]
#[instruction(max_squads: u8)]
pub struct CreateRegistrar<'info> {
/// The Squads voting 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(max_squads)
)]
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 Squads 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 Squads voting configuration for given Realm
///
/// To use the registrar, call ConfigureSquad to register Squads which will be
/// used for governance
///
/// max_squads is used to allocate account size for the maximum number of governing Squads
/// Note: Once Solana runtime supports account resizing the max value won't be required
pub fn create_registrar(ctx: Context<CreateRegistrar>, _max_squads: 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(),
SquadsVoterError::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(())
}
17 changes: 17 additions & 0 deletions programs/squads-voter/src/instructions/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pub use configure_squad::*;
mod configure_squad;

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 update_max_voter_weight_record::*;
mod update_max_voter_weight_record;
Loading