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

View File

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

View File

@ -21,7 +21,8 @@ serde = "1.0.127"
serde_derive = "1.0.103" serde_derive = "1.0.103"
solana-program = "1.8.0" solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] } 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" thiserror = "1.0"

View File

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

View File

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

View File

@ -4,7 +4,8 @@ use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::AccountInfo, clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey, 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 /// Defines all GovernanceChat accounts types
#[repr(C)] #[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] [package]
name = "spl-governance" name = "spl-governance"
version = "2.1.1" version = "2.1.2"
description = "Solana Program Library Governance Program" description = "Solana Program Library Governance Program"
authors = ["Solana Maintainers <maintainers@solana.foundation>"] authors = ["Solana Maintainers <maintainers@solana.foundation>"]
repository = "https://github.com/solana-labs/solana-program-library" repository = "https://github.com/solana-labs/solana-program-library"
@ -21,17 +21,18 @@ serde = "1.0.130"
serde_derive = "1.0.103" serde_derive = "1.0.103"
solana-program = "1.8.0" solana-program = "1.8.0"
spl-token = { version = "3.2", path = "../../token/program", features = [ "no-entrypoint" ] } spl-token = { version = "3.2", path = "../../token/program", features = [ "no-entrypoint" ] }
spl-governance-tools= { version = "0.1.0", path ="../tools"}
thiserror = "1.0" thiserror = "1.0"
[dev-dependencies] [dev-dependencies]
assert_matches = "1.5.0" assert_matches = "1.5.0"
base64 = "0.13" base64 = "0.13"
lazy_static = "1.4.0"
proptest = "1.0" proptest = "1.0"
solana-program-test = "1.8.0" solana-program-test = "1.8.0"
solana-sdk = "1.8.0" solana-sdk = "1.8.0"
spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"} spl-governance-test-sdk = { version = "0.1.0", path ="../test-sdk"}
spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] } spl-governance-v1 = {package="spl-governance", version = "1.1.1", features = [ "no-entrypoint" ] }
[lib] [lib]
crate-type = ["cdylib", "lib"] crate-type = ["cdylib", "lib"]

View File

@ -9,12 +9,9 @@ use solana_program::{
pubkey::Pubkey, pubkey::Pubkey,
sysvar::Sysvar, sysvar::Sysvar,
}; };
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{ use crate::{error::GovernanceError, state::token_owner_record::TokenOwnerRecord};
error::GovernanceError,
state::token_owner_record::TokenOwnerRecord,
tools::account::{get_account_data, AccountMaxSize},
};
/// VoterWeight account type /// VoterWeight account type
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
@ -82,7 +79,7 @@ pub fn get_voter_weight_record_data(
program_id: &Pubkey, program_id: &Pubkey,
voter_weight_record_info: &AccountInfo, voter_weight_record_info: &AccountInfo,
) -> Result<VoterWeightRecord, ProgramError> { ) -> 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 /// 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")] #[error("Invalid Signatory Mint")]
InvalidSignatoryMint, 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 /// Proposal does not belong to the given Governance
#[error("Proposal does not belong to the given Governance")] #[error("Proposal does not belong to the given Governance")]
InvalidGovernanceForProposal, InvalidGovernanceForProposal,

View File

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

View File

@ -8,6 +8,7 @@ use solana_program::{
rent::Rent, rent::Rent,
sysvar::Sysvar, sysvar::Sysvar,
}; };
use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
@ -23,7 +24,7 @@ use crate::{
}, },
vote_record::{get_vote_record_address_seeds, VoteRecord}, 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; use borsh::BorshSerialize;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,16 +5,14 @@ use solana_program::{
entrypoint::ProgramResult, entrypoint::ProgramResult,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::account::dispose_account;
use crate::{ use crate::state::{
state::{
enums::{ProposalState, VoteWeight}, enums::{ProposalState, VoteWeight},
governance::get_governance_data, governance::get_governance_data,
proposal::get_proposal_data_for_governance_and_governing_mint, proposal::get_proposal_data_for_governance_and_governing_mint,
token_owner_record::get_token_owner_record_data_for_realm_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, vote_record::get_vote_record_data_for_proposal_and_token_owner,
},
tools::account::dispose_account,
}; };
use borsh::BorshSerialize; use borsh::BorshSerialize;

View File

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

View File

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

View File

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

View File

@ -6,13 +6,16 @@ use crate::{
enums::{GovernanceAccountType, VoteThresholdPercentage, VoteWeightSource}, enums::{GovernanceAccountType, VoteThresholdPercentage, VoteWeightSource},
realm::assert_is_valid_realm, realm::assert_is_valid_realm,
}, },
tools::account::{get_account_data, AccountMaxSize},
}; };
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized, account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::{
account::{get_account_data, AccountMaxSize},
error::GovernanceToolsError,
};
/// Governance config /// Governance config
#[repr(C)] #[repr(C)]
@ -94,7 +97,7 @@ impl Governance {
GovernanceAccountType::TokenGovernance => { GovernanceAccountType::TokenGovernance => {
get_token_governance_address_seeds(&self.realm, &self.governed_account) get_token_governance_address_seeds(&self.realm, &self.governed_account)
} }
_ => return Err(GovernanceError::InvalidAccountType.into()), _ => return Err(GovernanceToolsError::InvalidAccountType.into()),
}; };
Ok(seeds) Ok(seeds)
@ -106,7 +109,7 @@ pub fn get_governance_data(
program_id: &Pubkey, program_id: &Pubkey,
governance_info: &AccountInfo, governance_info: &AccountInfo,
) -> Result<Governance, ProgramError> { ) -> 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 /// 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, account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
@ -18,7 +19,6 @@ use crate::{
proposal_instruction::ProposalInstruction, proposal_instruction::ProposalInstruction,
realm::Realm, realm::Realm,
}, },
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
}; };
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
@ -450,7 +450,7 @@ pub fn get_proposal_data(
program_id: &Pubkey, program_id: &Pubkey,
proposal_info: &AccountInfo, proposal_info: &AccountInfo,
) -> Result<Proposal, ProgramError> { ) -> 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 /// Deserializes Proposal and validates it belongs to the given Governance and Governing Mint

View File

@ -3,7 +3,6 @@
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::enums::{GovernanceAccountType, InstructionExecutionStatus}, state::enums::{GovernanceAccountType, InstructionExecutionStatus},
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
}; };
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
@ -15,6 +14,7 @@ use solana_program::{
program_pack::IsInitialized, program_pack::IsInitialized,
pubkey::Pubkey, 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 /// InstructionData wrapper. It can be removed once Borsh serialization for Instruction is supported in the SDK
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
@ -146,7 +146,7 @@ pub fn get_proposal_instruction_data(
program_id: &Pubkey, program_id: &Pubkey,
proposal_instruction_info: &AccountInfo, proposal_instruction_info: &AccountInfo,
) -> Result<ProposalInstruction, ProgramError> { ) -> 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 /// 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, account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize};
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::enums::{GovernanceAccountType, MintMaxVoteWeightSource}, state::enums::{GovernanceAccountType, MintMaxVoteWeightSource},
tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
}; };
@ -155,7 +155,7 @@ pub fn get_realm_data(
program_id: &Pubkey, program_id: &Pubkey,
realm_info: &AccountInfo, realm_info: &AccountInfo,
) -> Result<Realm, ProgramError> { ) -> 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 /// 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_info: &AccountInfo,
realm_authority: &Pubkey, realm_authority: &Pubkey,
) -> Result<Realm, ProgramError> { ) -> 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() { if realm_data.authority.is_none() {
return Err(GovernanceError::RealmHasNoAuthority.into()); return Err(GovernanceError::RealmHasNoAuthority.into());

View File

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

View File

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

View File

@ -9,7 +9,6 @@ use crate::{
enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm, enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm,
realm_config::get_realm_config_data_for_realm, realm_config::get_realm_config_data_for_realm,
}, },
tools::account::{get_account_data, AccountMaxSize},
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
}; };
@ -20,6 +19,7 @@ use solana_program::{
program_pack::IsInitialized, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// Governance Token Owner Record /// Governance Token Owner Record
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ] /// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
@ -241,7 +241,7 @@ pub fn get_token_owner_record_data(
program_id: &Pubkey, program_id: &Pubkey,
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
) -> Result<TokenOwnerRecord, ProgramError> { ) -> 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 /// 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::account_info::AccountInfo;
use solana_program::program_error::ProgramError; use solana_program::program_error::ProgramError;
use solana_program::{program_pack::IsInitialized, pubkey::Pubkey}; use solana_program::{program_pack::IsInitialized, pubkey::Pubkey};
use spl_governance_tools::account::{get_account_data, AccountMaxSize};
use crate::error::GovernanceError; 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}; use crate::state::enums::{GovernanceAccountType, VoteWeight};
@ -55,7 +56,7 @@ pub fn get_vote_record_data(
program_id: &Pubkey, program_id: &Pubkey,
vote_record_info: &AccountInfo, vote_record_info: &AccountInfo,
) -> Result<VoteRecord, ProgramError> { ) -> 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 /// Deserializes VoteRecord and checks it belongs to the provided Proposal and Governing Token Owner

View File

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

View File

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

View File

@ -6,6 +6,7 @@ use solana_program_test::*;
use program_test::*; use program_test::*;
use solana_sdk::{signature::Keypair, signer::Signer}; use solana_sdk::{signature::Keypair, signer::Signer};
use spl_governance::error::GovernanceError; use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;
use spl_token::error::TokenError; use spl_token::error::TokenError;
#[tokio::test] #[tokio::test]
@ -223,5 +224,5 @@ async fn test_create_mint_governance_with_invalid_realm_error() {
.unwrap(); .unwrap();
// Assert // 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, error::GovernanceError, tools::bpf_loader_upgradeable::get_program_upgrade_authority,
}; };
use spl_governance_test_sdk::tools::ProgramInstructionError; use spl_governance_test_sdk::tools::ProgramInstructionError;
use spl_governance_tools::error::GovernanceToolsError;
#[tokio::test] #[tokio::test]
async fn test_create_program_governance() { async fn test_create_program_governance() {
@ -232,5 +233,5 @@ async fn test_create_program_governance_with_invalid_realm_error() {
.unwrap(); .unwrap();
// Assert // 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 program_test::*;
use solana_sdk::{signature::Keypair, signer::Signer}; use solana_sdk::{signature::Keypair, signer::Signer};
use spl_governance::error::GovernanceError; use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;
use spl_token::error::TokenError; use spl_token::error::TokenError;
#[tokio::test] #[tokio::test]
@ -221,5 +223,5 @@ async fn test_create_token_governance_with_invalid_realm_error() {
.unwrap(); .unwrap();
// Assert // 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_test_sdk::tools::ProgramInstructionError;
use spl_governance_tools::error::GovernanceToolsError;
#[tokio::test] #[tokio::test]
async fn test_set_governance_config() { async fn test_set_governance_config() {
// Arrange // Arrange
@ -145,7 +147,7 @@ async fn test_set_governance_config_with_fake_governance_signer_error() {
.unwrap(); .unwrap();
// Assert // Assert
assert_eq!(err, GovernanceError::AccountDoesNotExist.into()); assert_eq!(err, GovernanceToolsError::AccountDoesNotExist.into());
} }
#[tokio::test] #[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, tools::bpf_loader_upgradeable::get_program_data_address,
}; };
pub mod addins;
pub mod cookies; pub mod cookies;
use crate::program_test::cookies::{ use crate::program_test::cookies::{
@ -63,10 +64,13 @@ use spl_governance_test_sdk::{
ProgramTestBench, TestBenchProgram, ProgramTestBench, TestBenchProgram,
}; };
use self::cookies::{ use self::{
addins::ensure_voter_weight_addin_is_built,
cookies::{
GovernanceCookie, GovernedAccountCookie, GovernedMintCookie, GovernedProgramCookie, GovernanceCookie, GovernedAccountCookie, GovernedMintCookie, GovernedProgramCookie,
GovernedTokenCookie, ProposalCookie, ProposalInstructionCookie, RealmCookie, GovernedTokenCookie, ProposalCookie, ProposalInstructionCookie, RealmCookie,
TokenOwnerRecordCookie, VoteRecordCookie, TokenOwnerRecordCookie, VoteRecordCookie,
},
}; };
pub struct GovernanceProgramTest { pub struct GovernanceProgramTest {
@ -84,6 +88,8 @@ impl GovernanceProgramTest {
#[allow(dead_code)] #[allow(dead_code)]
pub async fn start_with_voter_weight_addin() -> Self { pub async fn start_with_voter_weight_addin() -> Self {
ensure_voter_weight_addin_is_built();
Self::start_impl(true).await Self::start_impl(true).await
} }
@ -251,9 +257,9 @@ impl GovernanceProgramTest {
account_type: GovernanceAccountType::RealmConfig, account_type: GovernanceAccountType::RealmConfig,
realm: realm_address, realm: realm_address,
community_voter_weight_addin: self.voter_weight_addin_id, community_voter_weight_addin: self.voter_weight_addin_id,
reserved_1: None, community_max_vote_weight_addin: None,
reserved_2: None, council_voter_weight_addin: None,
reserved_3: None, council_max_vote_weight_addin: None,
reserved: [0; 128], reserved: [0; 128],
}, },
}) })
@ -815,9 +821,9 @@ impl GovernanceProgramTest {
community_voter_weight_addin: Some( community_voter_weight_addin: Some(
set_realm_config_ix.accounts[community_voter_weight_addin_index].pubkey, set_realm_config_ix.accounts[community_voter_weight_addin_index].pubkey,
), ),
reserved_1: None, community_max_vote_weight_addin: None,
reserved_2: None, council_voter_weight_addin: None,
reserved_3: None, council_max_vote_weight_addin: None,
reserved: [0; 128], 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 // 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 // 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![ let accounts = vec![
AccountMeta::new_readonly(self.program_id, false), AccountMeta::new_readonly(self.program_id, false),
AccountMeta::new_readonly(token_owner_record_cookie.account.realm, 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 borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke_signed, account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke,
program_error::ProgramError, program_pack::IsInitialized, pubkey::Pubkey, rent::Rent, program::invoke_signed, program_error::ProgramError, program_pack::IsInitialized,
system_instruction::create_account, 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 /// Trait for accounts to return their max size
pub trait AccountMaxSize { 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 /// 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 /// Note: This functions also checks the provided account PDA matches the supplied seeds
pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>( 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 /// Deserializes account and checks it's initialized and owned by the specified program
pub fn get_account_data<T: BorshDeserialize + IsInitialized>( pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
account_info: &AccountInfo,
owner_program_id: &Pubkey, owner_program_id: &Pubkey,
account_info: &AccountInfo,
) -> Result<T, ProgramError> { ) -> Result<T, ProgramError> {
if account_info.data_is_empty() { if account_info.data_is_empty() {
return Err(GovernanceError::AccountDoesNotExist.into()); return Err(GovernanceToolsError::AccountDoesNotExist.into());
} }
if account_info.owner != owner_program_id { 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())?; 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, owner_program_id: &Pubkey,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
if account_info.owner != owner_program_id { if account_info.owner != owner_program_id {
return Err(GovernanceError::InvalidAccountOwner.into()); return Err(GovernanceToolsError::InvalidAccountOwner.into());
} }
if account_info.data_is_empty() { 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())?; let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
if account_type != expected_account_type { if account_type != expected_account_type {
return Err(GovernanceError::InvalidAccountType.into()); return Err(GovernanceToolsError::InvalidAccountType.into());
}; };
Ok(()) 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" serde_derive = "1.0.103"
solana-program = "1.7.11" solana-program = "1.7.11"
spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] } spl-token = { version = "3.2", path = "../../../token/program", features = [ "no-entrypoint" ] }
spl-governance= { version = "2.1.1", path ="../../program", features = [ "no-entrypoint" ]} spl-governance= { version = "2.1.2", path ="../../program", features = [ "no-entrypoint" ]}
spl-governance-chat= { version = "0.1.0", path ="../../chat/program", features = [ "no-entrypoint" ]} spl-governance-tools= { version = "0.1.0", path ="../../tools"}
thiserror = "1.0" thiserror = "1.0"

View File

@ -5,8 +5,6 @@ use spl_governance::{
addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord}, addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord},
state::token_owner_record::get_token_owner_record_data_for_realm_and_governing_mint, 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::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
@ -15,6 +13,7 @@ use solana_program::{
program_error::ProgramError, program_error::ProgramError,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_tools::account::create_and_serialize_account;
use crate::instruction::VoterWeightAddinInstruction; use crate::instruction::VoterWeightAddinInstruction;