Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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