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

Commit 0094255

Browse files
authored
Governance: Max voter weight addin (#2815)
* chore: change voter-weight-addin to mock * chore: fix tests * chore: add action, target and expiry validation tests * chore: add full target test checks * feat: add max community voter weight adding structures * chore: move payer to last account position * wip: update max_community_voter_weight_addin * chore: simplify logic to update realm config * wip: setup addins when realm is created * wip: setup voter weights accounts * chore: make clippy happy * core: fix merge * chore: remove old readme * chore: add SetupMaxVoterWeightRecord to addin mock * chore: test_create_realm_with_max_voter_weight_addin test * chore: add SetRealmConfigArgs and refactor tests * chore: update max voter weight tests * chore: setup cast_vote_with_max_voter_weight_addin test * wip: get max voter weight from plugin * chore: update comments and make clippy happy * wip: use max voter weight to tip proposals * chore: rename get_max_voter_weight * chore: load addins using different ids * chore: add tests with all addins * fix: coerce max vote weight when cast votes are higher * chore: add tests with all adins being used * chore: add test for expired max voter weight record * chore: simplify addin mock * wip: Create addin api crate * chore: use native build order and dependency * chore: fix build * feat: add reserved space to addin records * chore: bump versions * fix: use [u8; 8] discriminator for addin account types * chore: update instruction comments * chore: use saturating_sub instead of checked_sub Co-authored-by: Jon Cinque
1 parent 32e77fb commit 0094255

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+2331
-579
lines changed

Cargo.lock

Lines changed: 17 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ members = [
1111
"feature-proposal/program",
1212
"feature-proposal/cli",
1313
"governance/addin-mock/program",
14+
"governance/addin-api",
1415
"governance/program",
1516
"governance/test-sdk",
1617
"governance/tools",

governance/addin-api/Cargo.toml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[package]
2+
name = "spl-governance-addin-api"
3+
version = "0.1.0"
4+
description = "Solana Program Library Governance Addin Api"
5+
authors = ["Solana Maintainers <[email protected]>"]
6+
repository = "https://github.com/solana-labs/solana-program-library"
7+
license = "Apache-2.0"
8+
edition = "2018"
9+
10+
[dependencies]
11+
borsh = "0.9.1"
12+
spl-governance-tools= { version = "0.1.1", path ="../tools"}
13+
solana-program = "1.9.5"
14+

governance/addin-api/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Governance Addin Api
2+
3+
Governance Addin Api interface

governance/addin-api/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
//! Governance add-ins interfaces
2+
pub mod max_voter_weight;
3+
pub mod voter_weight;
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//! MaxVoterWeight Addin interface
2+
3+
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4+
use solana_program::{clock::Slot, program_pack::IsInitialized, pubkey::Pubkey};
5+
use spl_governance_tools::account::AccountMaxSize;
6+
7+
/// MaxVoterWeightRecord account
8+
/// The account is used as an api interface to provide max voting power to the governance program from external addin contracts
9+
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
10+
pub struct MaxVoterWeightRecord {
11+
/// VoterWeightRecord discriminator sha256("account:MaxVoterWeightRecord")[..8]
12+
/// Note: The discriminator size must match the addin implementing program discriminator size
13+
/// to ensure it's stored in the private space of the account data and it's unique
14+
pub account_discriminator: [u8; 8],
15+
16+
/// The Realm the MaxVoterWeightRecord belongs to
17+
pub realm: Pubkey,
18+
19+
/// Governing Token Mint the MaxVoterWeightRecord is associated with
20+
/// Note: The addin can take deposits of any tokens and is not restricted to the community or council tokens only
21+
// The mint here is to link the record to either community or council mint of the realm
22+
pub governing_token_mint: Pubkey,
23+
24+
/// Max voter weight
25+
/// The max voter weight provided by the addin for the given realm and governing_token_mint
26+
pub max_voter_weight: u64,
27+
28+
/// The slot when the max voting weight expires
29+
/// It should be set to None if the weight never expires
30+
/// If the max vote weight decays with time, for example for time locked based weights, then the expiry must be set
31+
/// As a pattern Revise instruction to update the max weight should be invoked before governance instruction within the same transaction
32+
/// and the expiry set to the current slot to provide up to date weight
33+
pub max_voter_weight_expiry: Option<Slot>,
34+
35+
/// Reserved space for future versions
36+
pub reserved: [u8; 8],
37+
}
38+
39+
impl AccountMaxSize for MaxVoterWeightRecord {}
40+
41+
impl MaxVoterWeightRecord {
42+
/// sha256("account:MaxVoterWeightRecord")[..8]
43+
pub const ACCOUNT_DISCRIMINATOR: [u8; 8] = *b"9d5ff297";
44+
}
45+
46+
impl IsInitialized for MaxVoterWeightRecord {
47+
fn is_initialized(&self) -> bool {
48+
self.account_discriminator == MaxVoterWeightRecord::ACCOUNT_DISCRIMINATOR
49+
}
50+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
//! VoterWeight Addin interface
2+
3+
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
4+
use solana_program::{clock::Slot, program_pack::IsInitialized, pubkey::Pubkey};
5+
use spl_governance_tools::account::AccountMaxSize;
6+
7+
/// The governance action VoterWeight is evaluated for
8+
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
9+
pub enum VoterWeightAction {
10+
/// Cast vote for a proposal. Target: Proposal
11+
CastVote,
12+
13+
/// Comment a proposal. Target: Proposal
14+
CommentProposal,
15+
16+
/// Create Governance within a realm. Target: Realm
17+
CreateGovernance,
18+
19+
/// Create a proposal for a governance. Target: Governance
20+
CreateProposal,
21+
}
22+
23+
/// VoterWeightRecord account
24+
/// The account is used as an api interface to provide voting power to the governance program from external addin contracts
25+
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
26+
pub struct VoterWeightRecord {
27+
/// VoterWeightRecord discriminator sha256("account:VoterWeightRecord")[..8]
28+
/// Note: The discriminator size must match the addin implementing program discriminator size
29+
/// to ensure it's stored in the private space of the account data and it's unique
30+
pub account_discriminator: [u8; 8],
31+
32+
/// The Realm the VoterWeightRecord belongs to
33+
pub realm: Pubkey,
34+
35+
/// Governing Token Mint the VoterWeightRecord is associated with
36+
/// Note: The addin can take deposits of any tokens and is not restricted to the community or council tokens only
37+
// The mint here is to link the record to either community or council mint of the realm
38+
pub governing_token_mint: Pubkey,
39+
40+
/// The owner of the governing token and voter
41+
/// This is the actual owner (voter) and corresponds to TokenOwnerRecord.governing_token_owner
42+
pub governing_token_owner: Pubkey,
43+
44+
/// Voter's weight
45+
/// The weight of the voter provided by the addin for the given realm, governing_token_mint and governing_token_owner (voter)
46+
pub voter_weight: u64,
47+
48+
/// The slot when the voting weight expires
49+
/// It should be set to None if the weight never expires
50+
/// If the voter weight decays with time, for example for time locked based weights, then the expiry must be set
51+
/// As a common pattern Revise instruction to update the weight should be invoked before governance instruction within the same transaction
52+
/// and the expiry set to the current slot to provide up to date weight
53+
pub voter_weight_expiry: Option<Slot>,
54+
55+
/// The governance action the voter's weight pertains to
56+
/// It allows to provided voter's weight specific to the particular action the weight is evaluated for
57+
/// When the action is provided then the governance program asserts the executing action is the same as specified by the addin
58+
pub weight_action: Option<VoterWeightAction>,
59+
60+
/// The target the voter's weight action pertains to
61+
/// It allows to provided voter's weight specific to the target the weight is evaluated for
62+
/// For example when addin supplies weight to vote on a particular proposal then it must specify the proposal as the action target
63+
/// When the target is provided then the governance program asserts the target is the same as specified by the addin
64+
pub weight_action_target: Option<Pubkey>,
65+
66+
/// Reserved space for future versions
67+
pub reserved: [u8; 8],
68+
}
69+
70+
impl VoterWeightRecord {
71+
/// sha256("account:VoterWeightRecord")[..8]
72+
pub const ACCOUNT_DISCRIMINATOR: [u8; 8] = *b"2ef99b4b";
73+
}
74+
75+
impl AccountMaxSize for VoterWeightRecord {}
76+
77+
impl IsInitialized for VoterWeightRecord {
78+
fn is_initialized(&self) -> bool {
79+
self.account_discriminator == VoterWeightRecord::ACCOUNT_DISCRIMINATOR
80+
}
81+
}

governance/addin-mock/program/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ serde = "1.0.127"
2121
serde_derive = "1.0.103"
2222
solana-program = "1.9.5"
2323
spl-token = { version = "3.3", path = "../../../token/program", features = [ "no-entrypoint" ] }
24-
spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
25-
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
24+
spl-governance-addin-api= { version = "0.1.0", path ="../../addin-api"}
25+
spl-governance-tools= { version = "0.1.1", path ="../../tools"}
2626
thiserror = "1.0"
2727

2828

@@ -32,7 +32,7 @@ base64 = "0.13"
3232
proptest = "1.0"
3333
solana-program-test = "1.9.5"
3434
solana-sdk = "1.9.5"
35-
spl-governance-test-sdk = { version = "0.1.0", path ="../../test-sdk"}
35+
spl-governance-test-sdk = { version = "0.1.1", path ="../../test-sdk"}
3636

3737

3838
[lib]

governance/addin-mock/program/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub enum VoterWeightAddinError {}
1414

1515
impl PrintProgramError for VoterWeightAddinError {
1616
fn print<E>(&self) {
17-
msg!("VOTER-WEIGHT-ADDIN-ERROR: {}", &self.to_string());
17+
msg!("GOVERNANCE-ADDIN-MOCK-ERROR: {}", &self.to_string());
1818
}
1919
}
2020

@@ -26,6 +26,6 @@ impl From<VoterWeightAddinError> for ProgramError {
2626

2727
impl<T> DecodeError<T> for VoterWeightAddinError {
2828
fn type_of() -> &'static str {
29-
"Voter Weight Addin Error"
29+
"Governance Addin Mock Error"
3030
}
3131
}

governance/addin-mock/program/src/instruction.rs

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use solana_program::{
77
pubkey::Pubkey,
88
system_program,
99
};
10-
use spl_governance::addins::voter_weight::VoterWeightAction;
10+
use spl_governance_addin_api::voter_weight::VoterWeightAction;
1111

1212
/// Instructions supported by the VoterWeight addin program
1313
/// This program is a mock program used by spl-governance for testing and not real addin
@@ -19,7 +19,7 @@ pub enum VoterWeightAddinInstruction {
1919
/// 0. `[]` Governance Program Id
2020
/// 1. `[]` Realm account
2121
/// 2. `[]` Governing Token mint
22-
/// 3. `[]` TokenOwnerRecord
22+
/// 3. `[]` Governing token owner
2323
/// 4. `[writable]` VoterWeightRecord
2424
/// 5. `[signer]` Payer
2525
/// 6. `[]` System
@@ -40,17 +40,32 @@ pub enum VoterWeightAddinInstruction {
4040
#[allow(dead_code)]
4141
weight_action_target: Option<Pubkey>,
4242
},
43+
/// Sets up MaxVoterWeightRecord owned by the program
44+
///
45+
/// 0. `[]` Realm account
46+
/// 1. `[]` Governing Token mint
47+
/// 2. `[writable]` MaxVoterWeightRecord
48+
/// 3. `[signer]` Payer
49+
/// 4. `[]` System
50+
SetupMaxVoterWeightRecord {
51+
/// Max Voter weight
52+
#[allow(dead_code)]
53+
max_voter_weight: u64,
54+
55+
/// Voter weight expiry
56+
#[allow(dead_code)]
57+
max_voter_weight_expiry: Option<Slot>,
58+
},
4359
}
4460

4561
/// Creates SetupVoterWeightRecord instruction
4662
#[allow(clippy::too_many_arguments)]
4763
pub fn setup_voter_weight_record(
4864
program_id: &Pubkey,
4965
// Accounts
50-
governance_program_id: &Pubkey,
5166
realm: &Pubkey,
5267
governing_token_mint: &Pubkey,
53-
token_owner_record: &Pubkey,
68+
governing_token_owner: &Pubkey,
5469
voter_weight_record: &Pubkey,
5570
payer: &Pubkey,
5671
// Args
@@ -60,10 +75,9 @@ pub fn setup_voter_weight_record(
6075
weight_action_target: Option<Pubkey>,
6176
) -> Instruction {
6277
let accounts = vec![
63-
AccountMeta::new_readonly(*governance_program_id, false),
6478
AccountMeta::new_readonly(*realm, false),
6579
AccountMeta::new_readonly(*governing_token_mint, false),
66-
AccountMeta::new_readonly(*token_owner_record, false),
80+
AccountMeta::new_readonly(*governing_token_owner, false),
6781
AccountMeta::new(*voter_weight_record, true),
6882
AccountMeta::new_readonly(*payer, true),
6983
AccountMeta::new_readonly(system_program::id(), false),
@@ -82,3 +96,36 @@ pub fn setup_voter_weight_record(
8296
data: instruction.try_to_vec().unwrap(),
8397
}
8498
}
99+
100+
/// Creates SetupMaxVoterWeightRecord instruction
101+
#[allow(clippy::too_many_arguments)]
102+
pub fn setup_max_voter_weight_record(
103+
program_id: &Pubkey,
104+
// Accounts
105+
realm: &Pubkey,
106+
governing_token_mint: &Pubkey,
107+
max_voter_weight_record: &Pubkey,
108+
payer: &Pubkey,
109+
// Args
110+
max_voter_weight: u64,
111+
max_voter_weight_expiry: Option<Slot>,
112+
) -> Instruction {
113+
let accounts = vec![
114+
AccountMeta::new_readonly(*realm, false),
115+
AccountMeta::new_readonly(*governing_token_mint, false),
116+
AccountMeta::new(*max_voter_weight_record, true),
117+
AccountMeta::new_readonly(*payer, true),
118+
AccountMeta::new_readonly(system_program::id(), false),
119+
];
120+
121+
let instruction = VoterWeightAddinInstruction::SetupMaxVoterWeightRecord {
122+
max_voter_weight,
123+
max_voter_weight_expiry,
124+
};
125+
126+
Instruction {
127+
program_id: *program_id,
128+
accounts,
129+
data: instruction.try_to_vec().unwrap(),
130+
}
131+
}

0 commit comments

Comments
 (0)