From d6d0b92ae762ac0d4bf726bb28a99cc642e5771b Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Tue, 19 Oct 2021 11:43:01 +0100 Subject: [PATCH] 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 --- Cargo.lock | 27 ++++++- Cargo.toml | 1 + governance/chat/program/Cargo.toml | 3 +- governance/chat/program/src/lib.rs | 1 - governance/chat/program/src/processor.rs | 2 +- governance/chat/program/src/state.rs | 3 +- governance/chat/program/src/tools/account.rs | 62 --------------- governance/chat/program/src/tools/mod.rs | 3 - governance/program/Cargo.toml | 5 +- governance/program/src/addins/voter_weight.rs | 9 +-- governance/program/src/error.rs | 14 ---- .../src/processor/process_add_signatory.rs | 14 ++-- .../src/processor/process_cast_vote.rs | 3 +- .../process_create_account_governance.rs | 18 ++--- .../process_create_mint_governance.rs | 6 +- .../process_create_program_governance.rs | 8 +- .../src/processor/process_create_proposal.rs | 2 +- .../src/processor/process_create_realm.rs | 11 ++- .../process_create_token_governance.rs | 6 +- .../process_create_token_owner_record.rs | 2 +- .../process_deposit_governing_tokens.rs | 6 +- .../processor/process_insert_instruction.rs | 2 +- .../src/processor/process_relinquish_vote.rs | 18 ++--- .../processor/process_remove_instruction.rs | 11 +-- .../src/processor/process_remove_signatory.rs | 10 +-- .../src/processor/process_set_realm_config.rs | 8 +- governance/program/src/state/governance.rs | 9 ++- governance/program/src/state/proposal.rs | 4 +- .../program/src/state/proposal_instruction.rs | 4 +- governance/program/src/state/realm.rs | 6 +- governance/program/src/state/realm_config.rs | 30 ++++---- .../program/src/state/signatory_record.rs | 9 +-- .../program/src/state/token_owner_record.rs | 4 +- governance/program/src/state/vote_record.rs | 7 +- governance/program/src/tools/mod.rs | 2 - .../spl_governance_voter_weight_addin.so | Bin 131496 -> 0 bytes .../process_create_account_governance.rs | 3 +- .../tests/process_create_mint_governance.rs | 3 +- .../process_create_program_governance.rs | 3 +- .../tests/process_create_token_governance.rs | 4 +- .../tests/process_set_governance_config.rs | 4 +- .../program/tests/program_test/addins.rs | 24 ++++++ governance/program/tests/program_test/mod.rs | 28 ++++--- governance/test-sdk/README.md | 3 + governance/tools/Cargo.toml | 20 +++++ governance/tools/README.md | 3 + .../src/tools => tools/src}/account.rs | 72 +++++++++++++++--- governance/tools/src/error.rs | 47 ++++++++++++ governance/tools/src/lib.rs | 2 + .../voter-weight-addin/program/Cargo.toml | 4 +- .../program/src/processor.rs | 3 +- 51 files changed, 317 insertions(+), 236 deletions(-) delete mode 100644 governance/chat/program/src/tools/account.rs delete mode 100644 governance/chat/program/src/tools/mod.rs delete mode 100755 governance/program/tests/fixtures/spl_governance_voter_weight_addin.so create mode 100644 governance/program/tests/program_test/addins.rs create mode 100644 governance/test-sdk/README.md create mode 100644 governance/tools/Cargo.toml create mode 100644 governance/tools/README.md rename governance/{program/src/tools => tools/src}/account.rs (66%) create mode 100644 governance/tools/src/error.rs create mode 100644 governance/tools/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 263f1f209bc..ece475e14eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index ad852ae1198..4b12838ec8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "governance/voter-weight-addin/program", "governance/program", "governance/test-sdk", + "governance/tools", "governance/chat/program", "libraries/math", "memo/program", diff --git a/governance/chat/program/Cargo.toml b/governance/chat/program/Cargo.toml index 705dcb7dd57..a26957dbb12 100644 --- a/governance/chat/program/Cargo.toml +++ b/governance/chat/program/Cargo.toml @@ -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" diff --git a/governance/chat/program/src/lib.rs b/governance/chat/program/src/lib.rs index 04eaac9c5b7..3e314df865f 100644 --- a/governance/chat/program/src/lib.rs +++ b/governance/chat/program/src/lib.rs @@ -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; diff --git a/governance/chat/program/src/processor.rs b/governance/chat/program/src/processor.rs index e7099ed6283..90c7a1f3959 100644 --- a/governance/chat/program/src/processor.rs +++ b/governance/chat/program/src/processor.rs @@ -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( diff --git a/governance/chat/program/src/state.rs b/governance/chat/program/src/state.rs index f67623df8fc..fe0d816914f 100644 --- a/governance/chat/program/src/state.rs +++ b/governance/chat/program/src/state.rs @@ -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)] diff --git a/governance/chat/program/src/tools/account.rs b/governance/chat/program/src/tools/account.rs deleted file mode 100644 index 48bf5a186ac..00000000000 --- a/governance/chat/program/src/tools/account.rs +++ /dev/null @@ -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(()) -} diff --git a/governance/chat/program/src/tools/mod.rs b/governance/chat/program/src/tools/mod.rs deleted file mode 100644 index 5cb1ab5f753..00000000000 --- a/governance/chat/program/src/tools/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Utility functions - -pub mod account; diff --git a/governance/program/Cargo.toml b/governance/program/Cargo.toml index 1fc8c17bb85..af1a411025b 100644 --- a/governance/program/Cargo.toml +++ b/governance/program/Cargo.toml @@ -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 "] 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"] diff --git a/governance/program/src/addins/voter_weight.rs b/governance/program/src/addins/voter_weight.rs index 2ef864b37ee..e8ee8813f24 100644 --- a/governance/program/src/addins/voter_weight.rs +++ b/governance/program/src/addins/voter_weight.rs @@ -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 { - get_account_data::(voter_weight_record_info, program_id) + get_account_data::(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 diff --git a/governance/program/src/error.rs b/governance/program/src/error.rs index 75b04b1654f..06a84c5c291 100644 --- a/governance/program/src/error.rs +++ b/governance/program/src/error.rs @@ -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, diff --git a/governance/program/src/processor/process_add_signatory.rs b/governance/program/src/processor/process_add_signatory.rs index 2e44cc32099..f443b4983ee 100644 --- a/governance/program/src/processor/process_add_signatory.rs +++ b/governance/program/src/processor/process_add_signatory.rs @@ -8,15 +8,13 @@ use solana_program::{ rent::Rent, sysvar::Sysvar, }; +use spl_governance_tools::account::create_and_serialize_account_signed; -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, +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, }; /// Processes AddSignatory instruction diff --git a/governance/program/src/processor/process_cast_vote.rs b/governance/program/src/processor/process_cast_vote.rs index 13a130f450c..49b0b846ff8 100644 --- a/governance/program/src/processor/process_cast_vote.rs +++ b/governance/program/src/processor/process_cast_vote.rs @@ -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; diff --git a/governance/program/src/processor/process_create_account_governance.rs b/governance/program/src/processor/process_create_account_governance.rs index 52e1560723e..116dfc416de 100644 --- a/governance/program/src/processor/process_create_account_governance.rs +++ b/governance/program/src/processor/process_create_account_governance.rs @@ -1,16 +1,13 @@ //! Program state processor -use crate::{ - state::{ - enums::GovernanceAccountType, - governance::{ - assert_valid_create_governance_args, get_account_governance_address_seeds, Governance, - GovernanceConfig, - }, - realm::get_realm_data, - token_owner_record::get_token_owner_record_data_for_realm, +use crate::state::{ + enums::GovernanceAccountType, + governance::{ + assert_valid_create_governance_args, get_account_governance_address_seeds, Governance, + GovernanceConfig, }, - tools::account::create_and_serialize_account_signed, + realm::get_realm_data, + token_owner_record::get_token_owner_record_data_for_realm, }; 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( diff --git a/governance/program/src/processor/process_create_mint_governance.rs b/governance/program/src/processor/process_create_mint_governance.rs index 9ecd2b34049..2a235bdf419 100644 --- a/governance/program/src/processor/process_create_mint_governance.rs +++ b/governance/program/src/processor/process_create_mint_governance.rs @@ -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( diff --git a/governance/program/src/processor/process_create_program_governance.rs b/governance/program/src/processor/process_create_program_governance.rs index b7359ed70e6..15ce01f38ae 100644 --- a/governance/program/src/processor/process_create_program_governance.rs +++ b/governance/program/src/processor/process_create_program_governance.rs @@ -11,11 +11,8 @@ 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::{ - assert_program_upgrade_authority_is_signer, set_program_upgrade_authority, - }, + tools::bpf_loader_upgradeable::{ + assert_program_upgrade_authority_is_signer, set_program_upgrade_authority, }, }; use solana_program::{ @@ -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( diff --git a/governance/program/src/processor/process_create_proposal.rs b/governance/program/src/processor/process_create_proposal.rs index 4a094a6409d..52d6197cd24 100644 --- a/governance/program/src/processor/process_create_proposal.rs +++ b/governance/program/src/processor/process_create_proposal.rs @@ -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 diff --git a/governance/program/src/processor/process_create_realm.rs b/governance/program/src/processor/process_create_realm.rs index 0e425efdfea..a4f62572256 100644 --- a/governance/program/src/processor/process_create_realm.rs +++ b/governance/program/src/processor/process_create_realm.rs @@ -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], }; diff --git a/governance/program/src/processor/process_create_token_governance.rs b/governance/program/src/processor/process_create_token_governance.rs index a1dde948154..f7f2c32eba5 100644 --- a/governance/program/src/processor/process_create_token_governance.rs +++ b/governance/program/src/processor/process_create_token_governance.rs @@ -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( diff --git a/governance/program/src/processor/process_create_token_owner_record.rs b/governance/program/src/processor/process_create_token_owner_record.rs index d643592fdc0..20bc544750c 100644 --- a/governance/program/src/processor/process_create_token_owner_record.rs +++ b/governance/program/src/processor/process_create_token_owner_record.rs @@ -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 diff --git a/governance/program/src/processor/process_deposit_governing_tokens.rs b/governance/program/src/processor/process_deposit_governing_tokens.rs index c5588bd9f69..b09b0a4630e 100644 --- a/governance/program/src/processor/process_deposit_governing_tokens.rs +++ b/governance/program/src/processor/process_deposit_governing_tokens.rs @@ -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 diff --git a/governance/program/src/processor/process_insert_instruction.rs b/governance/program/src/processor/process_insert_instruction.rs index a4e8196efb6..5a072863a63 100644 --- a/governance/program/src/processor/process_insert_instruction.rs +++ b/governance/program/src/processor/process_insert_instruction.rs @@ -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 diff --git a/governance/program/src/processor/process_relinquish_vote.rs b/governance/program/src/processor/process_relinquish_vote.rs index 0e0d6c74357..ab81363e209 100644 --- a/governance/program/src/processor/process_relinquish_vote.rs +++ b/governance/program/src/processor/process_relinquish_vote.rs @@ -5,16 +5,14 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; - -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 spl_governance_tools::account::dispose_account; + +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, }; use borsh::BorshSerialize; diff --git a/governance/program/src/processor/process_remove_instruction.rs b/governance/program/src/processor/process_remove_instruction.rs index 8e9ffb6d6fd..3387766a472 100644 --- a/governance/program/src/processor/process_remove_instruction.rs +++ b/governance/program/src/processor/process_remove_instruction.rs @@ -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, - token_owner_record::get_token_owner_record_data_for_proposal_owner, - }, - tools::account::dispose_account, +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, }; /// Processes RemoveInstruction instruction diff --git a/governance/program/src/processor/process_remove_signatory.rs b/governance/program/src/processor/process_remove_signatory.rs index 01dbed95200..7f40b7aa970 100644 --- a/governance/program/src/processor/process_remove_signatory.rs +++ b/governance/program/src/processor/process_remove_signatory.rs @@ -6,13 +6,11 @@ use solana_program::{ entrypoint::ProgramResult, pubkey::Pubkey, }; +use spl_governance_tools::account::dispose_account; -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, +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, }; /// Processes RemoveSignatory instruction diff --git a/governance/program/src/processor/process_set_realm_config.rs b/governance/program/src/processor/process_set_realm_config.rs index a9093a76d6f..dd597af9644 100644 --- a/governance/program/src/processor/process_set_realm_config.rs +++ b/governance/program/src/processor/process_set_realm_config.rs @@ -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], }; diff --git a/governance/program/src/state/governance.rs b/governance/program/src/state/governance.rs index e21c2aae0d2..875da18e8b9 100644 --- a/governance/program/src/state/governance.rs +++ b/governance/program/src/state/governance.rs @@ -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 { - get_account_data::(governance_info, program_id) + get_account_data::(program_id, governance_info) } /// Deserializes Governance account, checks owner program and asserts governance belongs to the given ream diff --git a/governance/program/src/state/proposal.rs b/governance/program/src/state/proposal.rs index d48c8673ff3..48a20b271cf 100644 --- a/governance/program/src/state/proposal.rs +++ b/governance/program/src/state/proposal.rs @@ -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 { - get_account_data::(proposal_info, program_id) + get_account_data::(program_id, proposal_info) } /// Deserializes Proposal and validates it belongs to the given Governance and Governing Mint diff --git a/governance/program/src/state/proposal_instruction.rs b/governance/program/src/state/proposal_instruction.rs index e6b706f8073..e3ffb4925ca 100644 --- a/governance/program/src/state/proposal_instruction.rs +++ b/governance/program/src/state/proposal_instruction.rs @@ -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 { - get_account_data::(proposal_instruction_info, program_id) + get_account_data::(program_id, proposal_instruction_info) } /// Deserializes and returns ProposalInstruction account and checks it belongs to the given Proposal diff --git a/governance/program/src/state/realm.rs b/governance/program/src/state/realm.rs index 6e04188df72..9f81b0fc79f 100644 --- a/governance/program/src/state/realm.rs +++ b/governance/program/src/state/realm.rs @@ -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 { - get_account_data::(realm_info, program_id) + get_account_data::(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 { - let realm_data = get_account_data::(realm_info, program_id)?; + let realm_data = get_account_data::(program_id, realm_info)?; if realm_data.authority.is_none() { return Err(GovernanceError::RealmHasNoAuthority.into()); diff --git a/governance/program/src/state/realm_config.rs b/governance/program/src/state/realm_config.rs index 4b131d77579..9f0f05326cd 100644 --- a/governance/program/src/state/realm_config.rs +++ b/governance/program/src/state/realm_config.rs @@ -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, - /// Reserved for community max vote weight addin - pub reserved_1: Option, + /// 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, - /// Reserved for council voter weight addin - pub reserved_2: Option, + /// Addin providing voter weights for council token + /// Note: This field is not implemented in the current version + pub council_voter_weight_addin: Option, - /// Reserved for council max vote weight addin - pub reserved_3: Option, + /// 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, /// Reserved pub reserved: [u8; 128], @@ -56,7 +56,7 @@ pub fn get_realm_config_data( program_id: &Pubkey, realm_config_info: &AccountInfo, ) -> Result { - get_account_data::(realm_config_info, program_id) + get_account_data::(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], }; diff --git a/governance/program/src/state/signatory_record.rs b/governance/program/src/state/signatory_record.rs index d9e6d058a0e..337b1efe2ae 100644 --- a/governance/program/src/state/signatory_record.rs +++ b/governance/program/src/state/signatory_record.rs @@ -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 { - get_account_data::(signatory_record_info, program_id) + get_account_data::(program_id, signatory_record_info) } /// Deserializes SignatoryRecord and validates its PDA diff --git a/governance/program/src/state/token_owner_record.rs b/governance/program/src/state/token_owner_record.rs index 3aeed227729..79081f2f3f7 100644 --- a/governance/program/src/state/token_owner_record.rs +++ b/governance/program/src/state/token_owner_record.rs @@ -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 { - get_account_data::(token_owner_record_info, program_id) + get_account_data::(program_id, token_owner_record_info) } /// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds diff --git a/governance/program/src/state/vote_record.rs b/governance/program/src/state/vote_record.rs index a946e090cc4..29dd6ebc936 100644 --- a/governance/program/src/state/vote_record.rs +++ b/governance/program/src/state/vote_record.rs @@ -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 { - get_account_data::(vote_record_info, program_id) + get_account_data::(program_id, vote_record_info) } /// Deserializes VoteRecord and checks it belongs to the provided Proposal and Governing Token Owner diff --git a/governance/program/src/tools/mod.rs b/governance/program/src/tools/mod.rs index 6d895ce73ee..92d42769369 100644 --- a/governance/program/src/tools/mod.rs +++ b/governance/program/src/tools/mod.rs @@ -1,7 +1,5 @@ //! Utility functions -pub mod account; - pub mod spl_token; pub mod bpf_loader_upgradeable; diff --git a/governance/program/tests/fixtures/spl_governance_voter_weight_addin.so b/governance/program/tests/fixtures/spl_governance_voter_weight_addin.so deleted file mode 100755 index ef99326389d0c668147b6f9ea8e8197929a0e3ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 131496 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 = 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()); + } + } +} diff --git a/governance/program/tests/program_test/mod.rs b/governance/program/tests/program_test/mod.rs index 5c62a795009..88296b23680 100644 --- a/governance/program/tests/program_test/mod.rs +++ b/governance/program/tests/program_test/mod.rs @@ -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::{ - GovernanceCookie, GovernedAccountCookie, GovernedMintCookie, GovernedProgramCookie, - GovernedTokenCookie, ProposalCookie, ProposalInstructionCookie, RealmCookie, - TokenOwnerRecordCookie, VoteRecordCookie, +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), diff --git a/governance/test-sdk/README.md b/governance/test-sdk/README.md new file mode 100644 index 00000000000..6783b5109fd --- /dev/null +++ b/governance/test-sdk/README.md @@ -0,0 +1,3 @@ +# Governance Test SDK + +Governance test SDK is a set of test utility functions used across the governance programs ecosystem diff --git a/governance/tools/Cargo.toml b/governance/tools/Cargo.toml new file mode 100644 index 00000000000..766999595df --- /dev/null +++ b/governance/tools/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "spl-governance-tools" +version = "0.1.0" +description = "Solana Program Library Governance Tools" +authors = ["Solana Maintainers "] +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" diff --git a/governance/tools/README.md b/governance/tools/README.md new file mode 100644 index 00000000000..070f802463d --- /dev/null +++ b/governance/tools/README.md @@ -0,0 +1,3 @@ +# Governance Tool + +Governance tools is a set of general purpose utility functions used across the governance programs ecosystem diff --git a/governance/program/src/tools/account.rs b/governance/tools/src/account.rs similarity index 66% rename from governance/program/src/tools/account.rs rename to governance/tools/src/account.rs index 07c7059bd13..526d67041f6 100644 --- a/governance/program/src/tools/account.rs +++ b/governance/tools/src/account.rs @@ -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( - account_info: &AccountInfo, owner_program_id: &Pubkey, + account_info: &AccountInfo, ) -> Result { 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( 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(()) diff --git a/governance/tools/src/error.rs b/governance/tools/src/error.rs new file mode 100644 index 00000000000..33aebdb3c53 --- /dev/null +++ b/governance/tools/src/error.rs @@ -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(&self) { + msg!("GOVERNANCE-TOOLS-ERROR: {}", &self.to_string()); + } +} + +impl From for ProgramError { + fn from(e: GovernanceToolsError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl DecodeError for GovernanceToolsError { + fn type_of() -> &'static str { + "Governance Tools Error" + } +} diff --git a/governance/tools/src/lib.rs b/governance/tools/src/lib.rs new file mode 100644 index 00000000000..5537a42d369 --- /dev/null +++ b/governance/tools/src/lib.rs @@ -0,0 +1,2 @@ +pub mod account; +pub mod error; diff --git a/governance/voter-weight-addin/program/Cargo.toml b/governance/voter-weight-addin/program/Cargo.toml index abc5468551e..e7812434d2c 100644 --- a/governance/voter-weight-addin/program/Cargo.toml +++ b/governance/voter-weight-addin/program/Cargo.toml @@ -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" diff --git a/governance/voter-weight-addin/program/src/processor.rs b/governance/voter-weight-addin/program/src/processor.rs index 01cacd7ace2..e630b225466 100644 --- a/governance/voter-weight-addin/program/src/processor.rs +++ b/governance/voter-weight-addin/program/src/processor.rs @@ -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;