Governance: Voter-weight-addin cleanup (#2512)

* chore: Ensure voter-weight-addin is built for tests

fix: build addin during test run

fix: build voter weight addin for tests using the addin only

chore: use mutex to build addin only once

chore: move build guard to separate file

chore: update governance version for chat

* chore: create tools crate for common utility functions in governance ecosystem

* chore: add test-sdk and tools readme

* chore: rename reserved addins to specific names

* chore: remove todo comment

* chore: remove unnecessary var drop

* chore: move all account tools to shared crate

* chore: fix chat compilation

* chore: move program_id to first position
This commit is contained in:
Sebastian Bor 2021-10-19 11:43:01 +01:00 committed by GitHub
parent f95e390dd4
commit d6d0b92ae7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 316 additions and 235 deletions

27
Cargo.lock generated
View File

@ -3674,13 +3674,14 @@ dependencies = [
[[package]]
name = "spl-governance"
version = "2.1.1"
version = "2.1.2"
dependencies = [
"arrayref",
"assert_matches",
"base64 0.13.0",
"bincode",
"borsh",
"lazy_static",
"num-derive",
"num-traits",
"proptest",
@ -3691,6 +3692,7 @@ dependencies = [
"solana-sdk",
"spl-governance 1.1.1",
"spl-governance-test-sdk",
"spl-governance-tools",
"spl-token 3.2.0",
"thiserror",
]
@ -3712,8 +3714,9 @@ dependencies = [
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-governance 2.1.1",
"spl-governance 2.1.2",
"spl-governance-test-sdk",
"spl-governance-tools",
"spl-token 3.2.0",
"thiserror",
]
@ -3736,6 +3739,22 @@ dependencies = [
"thiserror",
]
[[package]]
name = "spl-governance-tools"
version = "0.1.0"
dependencies = [
"arrayref",
"bincode",
"borsh",
"num-derive",
"num-traits",
"serde",
"serde_derive",
"solana-program",
"spl-token 3.2.0",
"thiserror",
]
[[package]]
name = "spl-governance-voter-weight-addin"
version = "0.1.0"
@ -3753,9 +3772,9 @@ dependencies = [
"solana-program",
"solana-program-test",
"solana-sdk",
"spl-governance 2.1.1",
"spl-governance-chat",
"spl-governance 2.1.2",
"spl-governance-test-sdk",
"spl-governance-tools",
"spl-token 3.2.0",
"thiserror",
]

View File

@ -13,6 +13,7 @@ members = [
"governance/voter-weight-addin/program",
"governance/program",
"governance/test-sdk",
"governance/tools",
"governance/chat/program",
"libraries/math",
"memo/program",

View File

@ -21,7 +21,8 @@ serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.0", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
thiserror = "1.0"

View File

@ -6,7 +6,6 @@ pub mod error;
pub mod instruction;
pub mod processor;
pub mod state;
pub mod tools;
// Export current sdk types for downstream users building with a different sdk version
pub use solana_program;

View File

@ -4,7 +4,6 @@ use crate::{
error::GovernanceChatError,
instruction::GovernanceChatInstruction,
state::{assert_is_valid_chat_message, ChatMessage, GovernanceChatAccountType, MessageBody},
tools::account::create_and_serialize_account,
};
use borsh::BorshDeserialize;
@ -21,6 +20,7 @@ use spl_governance::state::{
governance::get_governance_data, proposal::get_proposal_data_for_governance,
token_owner_record::get_token_owner_record_data_for_realm,
};
use spl_governance_tools::account::create_and_serialize_account;
/// Processes an instruction
pub fn process_instruction(

View File

@ -4,7 +4,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::AccountInfo, clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey,
};
use spl_governance::tools::account::{assert_is_valid_account, AccountMaxSize};
use spl_governance_tools::account::{assert_is_valid_account, AccountMaxSize};
/// Defines all GovernanceChat accounts types
#[repr(C)]

View File

@ -1,62 +0,0 @@
//! General purpose account utility functions
use borsh::BorshSerialize;
use solana_program::{
account_info::AccountInfo, program::invoke, program_error::ProgramError, pubkey::Pubkey,
rent::Rent, system_instruction::create_account, system_program, sysvar::Sysvar,
};
use spl_governance::tools::account::AccountMaxSize;
use crate::error::GovernanceChatError;
/// Creates a new account and serializes data into it using AccountMaxSize to determine the account's size
pub fn create_and_serialize_account<'a, T: BorshSerialize + AccountMaxSize>(
payer_info: &AccountInfo<'a>,
account_info: &AccountInfo<'a>,
account_data: &T,
program_id: &Pubkey,
system_info: &AccountInfo<'a>,
) -> Result<(), ProgramError> {
// Assert the account is not initialized yet
if !(account_info.data_is_empty() && *account_info.owner == system_program::id()) {
return Err(GovernanceChatError::AccountAlreadyInitialized.into());
}
let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
(None, max_size)
} else {
let serialized_data = account_data.try_to_vec()?;
let account_size = serialized_data.len();
(Some(serialized_data), account_size)
};
let rent = Rent::get()?;
let create_account_instruction = create_account(
payer_info.key,
account_info.key,
rent.minimum_balance(account_size),
account_size as u64,
program_id,
);
invoke(
&create_account_instruction,
&[
payer_info.clone(),
account_info.clone(),
system_info.clone(),
],
)?;
if let Some(serialized_data) = serialized_data {
account_info
.data
.borrow_mut()
.copy_from_slice(&serialized_data);
} else {
account_data.serialize(&mut *account_info.data.borrow_mut())?;
}
Ok(())
}

View File

@ -1,3 +0,0 @@
//! Utility functions
pub mod account;

View File

@ -1,6 +1,6 @@
[package]
name = "spl-governance"
version = "2.1.1"
version = "2.1.2"
description = "Solana Program Library Governance Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
@ -21,17 +21,18 @@ serde = "1.0.130"
serde_derive = "1.0.103"
solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../token/program", features = [ "no-entrypoint" ] }
spl-governance-tools= { version = "0.1.0", path ="../tools"}
thiserror = "1.0"
[dev-dependencies]
assert_matches = "1.5.0"
base64 = "0.13"
lazy_static = "1.4.0"
proptest = "1.0"
solana-program-test = "1.8.0"
solana-sdk = "1.8.0"
spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"}
spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] }
[lib]
crate-type = ["cdylib", "lib"]

View File

@ -9,12 +9,9 @@ use solana_program::{
pubkey::Pubkey,
sysvar::Sysvar,
};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
state::token_owner_record::TokenOwnerRecord,
tools::account::{get_account_data, AccountMaxSize},
};
use crate::{error::GovernanceError, state::token_owner_record::TokenOwnerRecord};
/// VoterWeight account type
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
@ -82,7 +79,7 @@ pub fn get_voter_weight_record_data(
program_id: &Pubkey,
voter_weight_record_info: &AccountInfo,
) -> Result<VoterWeightRecord, ProgramError> {
get_account_data::<VoterWeightRecord>(voter_weight_record_info, program_id)
get_account_data::<VoterWeightRecord>(program_id, voter_weight_record_info)
}
/// Deserializes VoterWeightRecord account, checks owner program and asserts it's for the same realm, mint and token owner as the provided TokenOwnerRecord

View File

@ -162,20 +162,6 @@ pub enum GovernanceError {
#[error("Invalid Signatory Mint")]
InvalidSignatoryMint,
/// ---- Account Tools Errors ----
/// Invalid account owner
#[error("Invalid account owner")]
InvalidAccountOwner,
/// Account doesn't exist
#[error("Account doesn't exist")]
AccountDoesNotExist,
/// Invalid Account type
#[error("Invalid Account type")]
InvalidAccountType,
/// Proposal does not belong to the given Governance
#[error("Proposal does not belong to the given Governance")]
InvalidGovernanceForProposal,

View File

@ -8,15 +8,13 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
state::{
use crate::state::{
enums::GovernanceAccountType,
proposal::get_proposal_data,
signatory_record::{get_signatory_record_address_seeds, SignatoryRecord},
token_owner_record::get_token_owner_record_data_for_proposal_owner,
},
tools::account::create_and_serialize_account_signed,
};
/// Processes AddSignatory instruction

View File

@ -8,6 +8,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -23,7 +24,7 @@ use crate::{
},
vote_record::{get_vote_record_address_seeds, VoteRecord},
},
tools::{account::create_and_serialize_account_signed, spl_token::get_spl_token_mint_supply},
tools::spl_token::get_spl_token_mint_supply,
};
use borsh::BorshSerialize;

View File

@ -1,7 +1,6 @@
//! Program state processor
use crate::{
state::{
use crate::state::{
enums::GovernanceAccountType,
governance::{
assert_valid_create_governance_args, get_account_governance_address_seeds, Governance,
@ -9,8 +8,6 @@ use crate::{
},
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::account::create_and_serialize_account_signed,
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -19,6 +16,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
/// Processes CreateAccountGovernance instruction
pub fn process_create_account_governance(

View File

@ -10,10 +10,7 @@ use crate::{
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::{
account::create_and_serialize_account_signed,
spl_token::{assert_spl_token_mint_authority_is_signer, set_spl_token_mint_authority},
},
tools::spl_token::{assert_spl_token_mint_authority_is_signer, set_spl_token_mint_authority},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -22,6 +19,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
/// Processes CreateMintGovernance instruction
pub fn process_create_mint_governance(

View File

@ -11,12 +11,9 @@ use crate::{
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::{
account::create_and_serialize_account_signed,
bpf_loader_upgradeable::{
tools::bpf_loader_upgradeable::{
assert_program_upgrade_authority_is_signer, set_program_upgrade_authority,
},
},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -25,6 +22,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
/// Processes CreateProgramGovernance instruction
pub fn process_create_program_governance(

View File

@ -9,6 +9,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -19,7 +20,6 @@ use crate::{
realm::get_realm_data_for_governing_token_mint,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::account::create_and_serialize_account_signed,
};
/// Processes CreateProposal instruction

View File

@ -7,6 +7,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -18,9 +19,7 @@ use crate::{
},
realm_config::{get_realm_config_address_seeds, RealmConfigAccount},
},
tools::{
account::create_and_serialize_account_signed, spl_token::create_spl_token_account_signed,
},
tools::spl_token::create_spl_token_account_signed,
};
/// Processes CreateRealm instruction
@ -92,9 +91,9 @@ pub fn process_create_realm(
account_type: GovernanceAccountType::RealmConfig,
realm: *realm_info.key,
community_voter_weight_addin: Some(*community_voter_weight_addin_info.key),
reserved_1: None,
reserved_2: None,
reserved_3: None,
community_max_vote_weight_addin: None,
council_voter_weight_addin: None,
council_max_vote_weight_addin: None,
reserved: [0; 128],
};

View File

@ -10,10 +10,7 @@ use crate::{
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::{
account::create_and_serialize_account_signed,
spl_token::{assert_spl_token_owner_is_signer, set_spl_token_owner},
},
tools::spl_token::{assert_spl_token_owner_is_signer, set_spl_token_owner},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -22,6 +19,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
/// Processes CreateTokenGovernance instruction
pub fn process_create_token_governance(

View File

@ -7,6 +7,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -15,7 +16,6 @@ use crate::{
realm::get_realm_data,
token_owner_record::{get_token_owner_record_address_seeds, TokenOwnerRecord},
},
tools::account::create_and_serialize_account_signed,
};
/// Processes CreateTokenOwnerRecord instruction

View File

@ -8,6 +8,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -19,10 +20,7 @@ use crate::{
TokenOwnerRecord,
},
},
tools::{
account::create_and_serialize_account_signed,
spl_token::{get_spl_token_mint, get_spl_token_owner, transfer_spl_tokens},
},
tools::spl_token::{get_spl_token_mint, get_spl_token_owner, transfer_spl_tokens},
};
/// Processes DepositGoverningTokens instruction

View File

@ -10,6 +10,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -22,7 +23,6 @@ use crate::{
},
token_owner_record::get_token_owner_record_data_for_proposal_owner,
},
tools::account::create_and_serialize_account_signed,
};
/// Processes InsertInstruction instruction

View File

@ -5,16 +5,14 @@ use solana_program::{
entrypoint::ProgramResult,
pubkey::Pubkey,
};
use spl_governance_tools::account::dispose_account;
use crate::{
state::{
use crate::state::{
enums::{ProposalState, VoteWeight},
governance::get_governance_data,
proposal::get_proposal_data_for_governance_and_governing_mint,
token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
vote_record::get_vote_record_data_for_proposal_and_token_owner,
},
tools::account::dispose_account,
};
use borsh::BorshSerialize;

View File

@ -6,14 +6,11 @@ use solana_program::{
entrypoint::ProgramResult,
pubkey::Pubkey,
};
use spl_governance_tools::account::dispose_account;
use crate::{
state::{
proposal::get_proposal_data,
proposal_instruction::assert_proposal_instruction_for_proposal,
use crate::state::{
proposal::get_proposal_data, proposal_instruction::assert_proposal_instruction_for_proposal,
token_owner_record::get_token_owner_record_data_for_proposal_owner,
},
tools::account::dispose_account,
};
/// Processes RemoveInstruction instruction

View File

@ -6,13 +6,11 @@ use solana_program::{
entrypoint::ProgramResult,
pubkey::Pubkey,
};
use spl_governance_tools::account::dispose_account;
use crate::{
state::{
use crate::state::{
proposal::get_proposal_data, signatory_record::get_signatory_record_data_for_seeds,
token_owner_record::get_token_owner_record_data_for_proposal_owner,
},
tools::account::dispose_account,
};
/// Processes RemoveSignatory instruction

View File

@ -8,6 +8,7 @@ use solana_program::{
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{
error::GovernanceError,
@ -18,7 +19,6 @@ use crate::{
get_realm_config_address_seeds, get_realm_config_data_for_realm, RealmConfigAccount,
},
},
tools::account::create_and_serialize_account_signed,
};
/// Processes SetRealmConfig instruction
@ -77,9 +77,9 @@ pub fn process_set_realm_config(
account_type: GovernanceAccountType::RealmConfig,
realm: *realm_info.key,
community_voter_weight_addin: Some(*community_voter_weight_addin_info.key),
reserved_1: None,
reserved_2: None,
reserved_3: None,
community_max_vote_weight_addin: None,
council_voter_weight_addin: None,
council_max_vote_weight_addin: None,
reserved: [0; 128],
};

View File

@ -6,13 +6,16 @@ use crate::{
enums::{GovernanceAccountType, VoteThresholdPercentage, VoteWeightSource},
realm::assert_is_valid_realm,
},
tools::account::{get_account_data, AccountMaxSize},
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::{
account::{get_account_data, AccountMaxSize},
error::GovernanceToolsError,
};
/// Governance config
#[repr(C)]
@ -94,7 +97,7 @@ impl Governance {
GovernanceAccountType::TokenGovernance => {
get_token_governance_address_seeds(&self.realm, &self.governed_account)
}
_ => return Err(GovernanceError::InvalidAccountType.into()),
_ => return Err(GovernanceToolsError::InvalidAccountType.into()),
};
Ok(seeds)
@ -106,7 +109,7 @@ pub fn get_governance_data(
program_id: &Pubkey,
governance_info: &AccountInfo,
) -> Result<Governance, ProgramError> {
get_account_data::<Governance>(governance_info, program_id)
get_account_data::<Governance>(program_id, governance_info)
}
/// Deserializes Governance account, checks owner program and asserts governance belongs to the given ream

View File

@ -6,6 +6,7 @@ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
@ -18,7 +19,6 @@ use crate::{
proposal_instruction::ProposalInstruction,
realm::Realm,
},
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
@ -450,7 +450,7 @@ pub fn get_proposal_data(
program_id: &Pubkey,
proposal_info: &AccountInfo,
) -> Result<Proposal, ProgramError> {
get_account_data::<Proposal>(proposal_info, program_id)
get_account_data::<Proposal>(program_id, proposal_info)
}
/// Deserializes Proposal and validates it belongs to the given Governance and Governing Mint

View File

@ -3,7 +3,6 @@
use crate::{
error::GovernanceError,
state::enums::{GovernanceAccountType, InstructionExecutionStatus},
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
@ -15,6 +14,7 @@ use solana_program::{
program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// InstructionData wrapper. It can be removed once Borsh serialization for Instruction is supported in the SDK
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
@ -146,7 +146,7 @@ pub fn get_proposal_instruction_data(
program_id: &Pubkey,
proposal_instruction_info: &AccountInfo,
) -> Result<ProposalInstruction, ProgramError> {
get_account_data::<ProposalInstruction>(proposal_instruction_info, program_id)
get_account_data::<ProposalInstruction>(program_id, proposal_instruction_info)
}
/// Deserializes and returns ProposalInstruction account and checks it belongs to the given Proposal

View File

@ -5,11 +5,11 @@ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
state::enums::{GovernanceAccountType, MintMaxVoteWeightSource},
tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
@ -155,7 +155,7 @@ pub fn get_realm_data(
program_id: &Pubkey,
realm_info: &AccountInfo,
) -> Result<Realm, ProgramError> {
get_account_data::<Realm>(realm_info, program_id)
get_account_data::<Realm>(program_id, realm_info)
}
/// Deserializes account and checks the given authority is Realm's authority
@ -164,7 +164,7 @@ pub fn get_realm_data_for_authority(
realm_info: &AccountInfo,
realm_authority: &Pubkey,
) -> Result<Realm, ProgramError> {
let realm_data = get_account_data::<Realm>(realm_info, program_id)?;
let realm_data = get_account_data::<Realm>(program_id, realm_info)?;
if realm_data.authority.is_none() {
return Err(GovernanceError::RealmHasNoAuthority.into());

View File

@ -6,12 +6,9 @@ use solana_program::{
};
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
state::enums::GovernanceAccountType,
tools::account::{get_account_data, AccountMaxSize},
};
use crate::{error::GovernanceError, state::enums::GovernanceAccountType};
/// RealmConfig account
/// The account is an optional extension to RealmConfig stored on Realm account
@ -26,14 +23,17 @@ pub struct RealmConfigAccount {
/// Addin providing voter weights for community token
pub community_voter_weight_addin: Option<Pubkey>,
/// Reserved for community max vote weight addin
pub reserved_1: Option<Pubkey>,
/// Addin providing max vote weight for community token
/// Note: This field is not implemented in the current version
pub community_max_vote_weight_addin: Option<Pubkey>,
/// Reserved for council voter weight addin
pub reserved_2: Option<Pubkey>,
/// Addin providing voter weights for council token
/// Note: This field is not implemented in the current version
pub council_voter_weight_addin: Option<Pubkey>,
/// Reserved for council max vote weight addin
pub reserved_3: Option<Pubkey>,
/// Addin providing max vote weight for council token
/// Note: This field is not implemented in the current version
pub council_max_vote_weight_addin: Option<Pubkey>,
/// Reserved
pub reserved: [u8; 128],
@ -56,7 +56,7 @@ pub fn get_realm_config_data(
program_id: &Pubkey,
realm_config_info: &AccountInfo,
) -> Result<RealmConfigAccount, ProgramError> {
get_account_data::<RealmConfigAccount>(realm_config_info, program_id)
get_account_data::<RealmConfigAccount>(program_id, realm_config_info)
}
/// Deserializes RealmConfig account and checks the owner program and the Realm it belongs to
@ -95,9 +95,9 @@ mod test {
account_type: GovernanceAccountType::Realm,
realm: Pubkey::new_unique(),
community_voter_weight_addin: Some(Pubkey::new_unique()),
reserved_1: Some(Pubkey::new_unique()),
reserved_2: Some(Pubkey::new_unique()),
reserved_3: Some(Pubkey::new_unique()),
community_max_vote_weight_addin: Some(Pubkey::new_unique()),
council_voter_weight_addin: Some(Pubkey::new_unique()),
council_max_vote_weight_addin: Some(Pubkey::new_unique()),
reserved: [0; 128],
};

View File

@ -5,12 +5,9 @@ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{
error::GovernanceError,
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
use crate::{error::GovernanceError, PROGRAM_AUTHORITY_SEED};
use crate::state::enums::GovernanceAccountType;
@ -93,7 +90,7 @@ pub fn get_signatory_record_data(
program_id: &Pubkey,
signatory_record_info: &AccountInfo,
) -> Result<SignatoryRecord, ProgramError> {
get_account_data::<SignatoryRecord>(signatory_record_info, program_id)
get_account_data::<SignatoryRecord>(program_id, signatory_record_info)
}
/// Deserializes SignatoryRecord and validates its PDA

View File

@ -9,7 +9,6 @@ use crate::{
enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm,
realm_config::get_realm_config_data_for_realm,
},
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED,
};
@ -20,6 +19,7 @@ use solana_program::{
program_pack::IsInitialized,
pubkey::Pubkey,
};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// Governance Token Owner Record
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
@ -241,7 +241,7 @@ pub fn get_token_owner_record_data(
program_id: &Pubkey,
token_owner_record_info: &AccountInfo,
) -> Result<TokenOwnerRecord, ProgramError> {
get_account_data::<TokenOwnerRecord>(token_owner_record_info, program_id)
get_account_data::<TokenOwnerRecord>(program_id, token_owner_record_info)
}
/// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds

View File

@ -4,10 +4,11 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::account_info::AccountInfo;
use solana_program::program_error::ProgramError;
use solana_program::{program_pack::IsInitialized, pubkey::Pubkey};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::error::GovernanceError;
use crate::tools::account::get_account_data;
use crate::{tools::account::AccountMaxSize, PROGRAM_AUTHORITY_SEED};
use crate::PROGRAM_AUTHORITY_SEED;
use crate::state::enums::{GovernanceAccountType, VoteWeight};
@ -55,7 +56,7 @@ pub fn get_vote_record_data(
program_id: &Pubkey,
vote_record_info: &AccountInfo,
) -> Result<VoteRecord, ProgramError> {
get_account_data::<VoteRecord>(vote_record_info, program_id)
get_account_data::<VoteRecord>(program_id, vote_record_info)
}
/// Deserializes VoteRecord and checks it belongs to the provided Proposal and Governing Token Owner

View File

@ -1,7 +1,5 @@
//! Utility functions
pub mod account;
pub mod spl_token;
pub mod bpf_loader_upgradeable;

View File

@ -5,6 +5,7 @@ use solana_program_test::*;
use program_test::*;
use spl_governance::{error::GovernanceError, state::enums::VoteThresholdPercentage};
use spl_governance_tools::error::GovernanceToolsError;
#[tokio::test]
async fn test_create_account_governance() {
@ -77,7 +78,7 @@ async fn test_create_account_governance_with_invalid_realm_error() {
// Assert
assert_eq!(err, GovernanceError::InvalidAccountType.into());
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}
#[tokio::test]

View File

@ -6,6 +6,7 @@ use solana_program_test::*;
use program_test::*;
use solana_sdk::{signature::Keypair, signer::Signer};
use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;
use spl_token::error::TokenError;
#[tokio::test]
@ -223,5 +224,5 @@ async fn test_create_mint_governance_with_invalid_realm_error() {
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidAccountType.into());
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}

View File

@ -9,6 +9,7 @@ use spl_governance::{
error::GovernanceError, tools::bpf_loader_upgradeable::get_program_upgrade_authority,
};
use spl_governance_test_sdk::tools::ProgramInstructionError;
use spl_governance_tools::error::GovernanceToolsError;
#[tokio::test]
async fn test_create_program_governance() {
@ -232,5 +233,5 @@ async fn test_create_program_governance_with_invalid_realm_error() {
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidAccountType.into());
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}

View File

@ -6,6 +6,8 @@ use solana_program_test::*;
use program_test::*;
use solana_sdk::{signature::Keypair, signer::Signer};
use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;
use spl_token::error::TokenError;
#[tokio::test]
@ -221,5 +223,5 @@ async fn test_create_token_governance_with_invalid_realm_error() {
.unwrap();
// Assert
assert_eq!(err, GovernanceError::InvalidAccountType.into());
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}

View File

@ -12,6 +12,8 @@ use spl_governance::{
};
use spl_governance_test_sdk::tools::ProgramInstructionError;
use spl_governance_tools::error::GovernanceToolsError;
#[tokio::test]
async fn test_set_governance_config() {
// Arrange
@ -145,7 +147,7 @@ async fn test_set_governance_config_with_fake_governance_signer_error() {
.unwrap();
// Assert
assert_eq!(err, GovernanceError::AccountDoesNotExist.into());
assert_eq!(err, GovernanceToolsError::AccountDoesNotExist.into());
}
#[tokio::test]

View File

@ -0,0 +1,24 @@
use lazy_static::lazy_static;
use solana_program_test::find_file;
use std::{process::Command, sync::Mutex};
lazy_static! {
pub static ref VOTER_WEIGHT_ADDIN_BUILD_GUARD: Mutex::<u8> = Mutex::new(0);
}
pub fn ensure_voter_weight_addin_is_built() {
if find_file("spl_governance_voter_weight_addin.so").is_none() {
let _guard = VOTER_WEIGHT_ADDIN_BUILD_GUARD.lock().unwrap();
if find_file("spl_governance_voter_weight_addin.so").is_none() {
assert!(Command::new("cargo")
.args(&[
"build-bpf",
"--manifest-path",
"../voter-weight-addin/program/Cargo.toml",
])
.status()
.expect("Failed to build voter-weight-addin program")
.success());
}
}
}

View File

@ -52,6 +52,7 @@ use spl_governance::{
tools::bpf_loader_upgradeable::get_program_data_address,
};
pub mod addins;
pub mod cookies;
use crate::program_test::cookies::{
@ -63,10 +64,13 @@ use spl_governance_test_sdk::{
ProgramTestBench, TestBenchProgram,
};
use self::cookies::{
use self::{
addins::ensure_voter_weight_addin_is_built,
cookies::{
GovernanceCookie, GovernedAccountCookie, GovernedMintCookie, GovernedProgramCookie,
GovernedTokenCookie, ProposalCookie, ProposalInstructionCookie, RealmCookie,
TokenOwnerRecordCookie, VoteRecordCookie,
},
};
pub struct GovernanceProgramTest {
@ -84,6 +88,8 @@ impl GovernanceProgramTest {
#[allow(dead_code)]
pub async fn start_with_voter_weight_addin() -> Self {
ensure_voter_weight_addin_is_built();
Self::start_impl(true).await
}
@ -251,9 +257,9 @@ impl GovernanceProgramTest {
account_type: GovernanceAccountType::RealmConfig,
realm: realm_address,
community_voter_weight_addin: self.voter_weight_addin_id,
reserved_1: None,
reserved_2: None,
reserved_3: None,
community_max_vote_weight_addin: None,
council_voter_weight_addin: None,
council_max_vote_weight_addin: None,
reserved: [0; 128],
},
})
@ -815,9 +821,9 @@ impl GovernanceProgramTest {
community_voter_weight_addin: Some(
set_realm_config_ix.accounts[community_voter_weight_addin_index].pubkey,
),
reserved_1: None,
reserved_2: None,
reserved_3: None,
community_max_vote_weight_addin: None,
council_voter_weight_addin: None,
council_max_vote_weight_addin: None,
reserved: [0; 128],
},
})
@ -2194,8 +2200,6 @@ impl GovernanceProgramTest {
// Governance program has no dependency on the voter-weight-addin program and hence we can't use its instruction creator here
// and the instruction has to be created manually
// TODO: Currently the addin spl_governance_voter_weight_addin.so must be manually copied to tests/fixtures to work on CI
// We should automate this step as part of the build to build the addin before governance
let accounts = vec![
AccountMeta::new_readonly(self.program_id, false),
AccountMeta::new_readonly(token_owner_record_cookie.account.realm, false),

View File

@ -0,0 +1,3 @@
# Governance Test SDK
Governance test SDK is a set of test utility functions used across the governance programs ecosystem

View File

@ -0,0 +1,20 @@
[package]
name = "spl-governance-tools"
version = "0.1.0"
description = "Solana Program Library Governance Tools"
authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library"
license = "Apache-2.0"
edition = "2018"
[dependencies]
arrayref = "0.3.6"
bincode = "1.3.2"
borsh = "0.9.1"
num-derive = "0.3"
num-traits = "0.2"
serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../token/program", features = [ "no-entrypoint" ] }
thiserror = "1.0"

View File

@ -0,0 +1,3 @@
# Governance Tool
Governance tools is a set of general purpose utility functions used across the governance programs ecosystem

View File

@ -2,12 +2,12 @@
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke_signed,
program_error::ProgramError, program_pack::IsInitialized, pubkey::Pubkey, rent::Rent,
system_instruction::create_account,
account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke,
program::invoke_signed, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey, rent::Rent, system_instruction::create_account, system_program, sysvar::Sysvar,
};
use crate::error::GovernanceError;
use crate::error::GovernanceToolsError;
/// Trait for accounts to return their max size
pub trait AccountMaxSize {
@ -17,6 +17,58 @@ pub trait AccountMaxSize {
}
}
/// Creates a new account and serializes data into it using AccountMaxSize to determine the account's size
pub fn create_and_serialize_account<'a, T: BorshSerialize + AccountMaxSize>(
payer_info: &AccountInfo<'a>,
account_info: &AccountInfo<'a>,
account_data: &T,
program_id: &Pubkey,
system_info: &AccountInfo<'a>,
) -> Result<(), ProgramError> {
// Assert the account is not initialized yet
if !(account_info.data_is_empty() && *account_info.owner == system_program::id()) {
return Err(GovernanceToolsError::AccountAlreadyInitialized.into());
}
let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
(None, max_size)
} else {
let serialized_data = account_data.try_to_vec()?;
let account_size = serialized_data.len();
(Some(serialized_data), account_size)
};
let rent = Rent::get()?;
let create_account_instruction = create_account(
payer_info.key,
account_info.key,
rent.minimum_balance(account_size),
account_size as u64,
program_id,
);
invoke(
&create_account_instruction,
&[
payer_info.clone(),
account_info.clone(),
system_info.clone(),
],
)?;
if let Some(serialized_data) = serialized_data {
account_info
.data
.borrow_mut()
.copy_from_slice(&serialized_data);
} else {
account_data.serialize(&mut *account_info.data.borrow_mut())?;
}
Ok(())
}
/// Creates a new account and serializes data into it using the provided seeds to invoke signed CPI call
/// Note: This functions also checks the provided account PDA matches the supplied seeds
pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>(
@ -85,14 +137,14 @@ pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSiz
/// Deserializes account and checks it's initialized and owned by the specified program
pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
account_info: &AccountInfo,
owner_program_id: &Pubkey,
account_info: &AccountInfo,
) -> Result<T, ProgramError> {
if account_info.data_is_empty() {
return Err(GovernanceError::AccountDoesNotExist.into());
return Err(GovernanceToolsError::AccountDoesNotExist.into());
}
if account_info.owner != owner_program_id {
return Err(GovernanceError::InvalidAccountOwner.into());
return Err(GovernanceToolsError::InvalidAccountOwner.into());
}
let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
@ -111,17 +163,17 @@ pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
owner_program_id: &Pubkey,
) -> Result<(), ProgramError> {
if account_info.owner != owner_program_id {
return Err(GovernanceError::InvalidAccountOwner.into());
return Err(GovernanceToolsError::InvalidAccountOwner.into());
}
if account_info.data_is_empty() {
return Err(GovernanceError::AccountDoesNotExist.into());
return Err(GovernanceToolsError::AccountDoesNotExist.into());
}
let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
if account_type != expected_account_type {
return Err(GovernanceError::InvalidAccountType.into());
return Err(GovernanceToolsError::InvalidAccountType.into());
};
Ok(())

View File

@ -0,0 +1,47 @@
//! Error types
use num_derive::FromPrimitive;
use solana_program::{
decode_error::DecodeError,
msg,
program_error::{PrintProgramError, ProgramError},
};
use thiserror::Error;
/// Errors that may be returned by the GovernanceTools
#[derive(Clone, Debug, Eq, Error, FromPrimitive, PartialEq)]
pub enum GovernanceToolsError {
/// Account already initialized
#[error("Account already initialized")]
AccountAlreadyInitialized = 1100,
/// Account doesn't exist
#[error("Account doesn't exist")]
AccountDoesNotExist,
/// Invalid account owner
#[error("Invalid account owner")]
InvalidAccountOwner,
/// Invalid Account type
#[error("Invalid Account type")]
InvalidAccountType,
}
impl PrintProgramError for GovernanceToolsError {
fn print<E>(&self) {
msg!("GOVERNANCE-TOOLS-ERROR: {}", &self.to_string());
}
}
impl From<GovernanceToolsError> for ProgramError {
fn from(e: GovernanceToolsError) -> Self {
ProgramError::Custom(e as u32)
}
}
impl<T> DecodeError<T> for GovernanceToolsError {
fn type_of() -> &'static str {
"Governance Tools Error"
}
}

View File

@ -0,0 +1,2 @@
pub mod account;
pub mod error;

View File

@ -21,8 +21,8 @@ serde = "1.0.127"
serde_derive = "1.0.103"
solana-program = "1.7.11"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.1", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-chat= { version = "0.1.0", path ="../../chat/program", features = [ "no-entrypoint" ]}
spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-tools= { version = "0.1.0", path ="../../tools"}
thiserror = "1.0"

View File

@ -5,8 +5,6 @@ use spl_governance::{
addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord},
state::token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint,
};
// TODO: Move to shared governance tools
use spl_governance_chat::tools::account::create_and_serialize_account;
use solana_program::{
account_info::{next_account_info, AccountInfo},
@ -15,6 +13,7 @@ use solana_program::{
program_error::ProgramError,
pubkey::Pubkey,
};
use spl_governance_tools::account::create_and_serialize_account;
use crate::instruction::VoterWeightAddinInstruction;