Governance: Native Treasuries (#2692)
* feat: Implement CreateNativeTreasury instruction * chore: fix fmt * chore: make clippy happy * chore: remove account check * feat: sign with treasury account seeds * chore: comment out unused code * feat: Use system as the treasury account owner * feat: Assert valid governance account * chore: Make clippy happy * fix: Use explicit system program id * feat: add treasury seeds only when required by the instruction * chore: review celanup * chore: make clippy happy * chore: rename create_and_serialize_account_signed2 Co-authored-by: Jon Cinque <jon.cinque@gmail.com> * chore: update create_and_serialize_account_signed2 name * fix: remove NativeTreasury from account enums * chore: update treasury seeds prefix Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
This commit is contained in:
parent
78cb324352
commit
24bb1c8158
|
@ -607,16 +607,6 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa"
|
||||
dependencies = [
|
||||
"quote 1.0.14",
|
||||
"syn 1.0.84",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "curve25519-dalek"
|
||||
version = "3.2.0"
|
||||
|
@ -1091,17 +1081,6 @@ dependencies = [
|
|||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ghost"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a5bcf1bbeab73aa4cf2fde60a846858dc036163c7c33bec309f8d17de785479"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.14",
|
||||
"syn 1.0.84",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.1"
|
||||
|
@ -1391,28 +1370,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0eb5160c60ba1e809707918ee329adb99d222888155835c6feedba19f6c3fd4"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"ghost",
|
||||
"inventory-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inventory-impl"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e41b53715c6f0c4be49510bb82dee2c1e51c8586d885abe65396e82ed518548"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.36",
|
||||
"quote 1.0.14",
|
||||
"syn 1.0.84",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.3.1"
|
||||
|
@ -2051,38 +2008,48 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.12.4"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf6bbbe8f70d179260b3728e5d04eb012f4f0c7988e58c11433dd689cecaa72e"
|
||||
checksum = "7cf01dbf1c05af0a14c7779ed6f3aa9deac9c3419606ac9de537a2d649005720"
|
||||
dependencies = [
|
||||
"ctor",
|
||||
"cfg-if",
|
||||
"indoc",
|
||||
"inventory",
|
||||
"libc",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"pyo3cls",
|
||||
"pyo3-build-config",
|
||||
"pyo3-macros",
|
||||
"unindent",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-derive-backend"
|
||||
version = "0.12.4"
|
||||
name = "pyo3-build-config"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10ecd0eb6ed7b3d9965b4f4370b5b9e99e3e5e8742000e1c452c018f8c2a322f"
|
||||
checksum = "dbf9e4d128bfbddc898ad3409900080d8d5095c379632fbbfbb9c8cfb1fb852b"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.36",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67701eb32b1f9a9722b4bc54b548ff9d7ebfded011c12daece7b9063be1fd755"
|
||||
dependencies = [
|
||||
"pyo3-macros-backend",
|
||||
"quote 1.0.14",
|
||||
"syn 1.0.84",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyo3cls"
|
||||
version = "0.12.4"
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d344fdaa6a834a06dd1720ff104ea12fe101dad2e8db89345af9db74c0bb11a0"
|
||||
checksum = "f44f09e825ee49a105f2c7b23ebee50886a9aee0746f4dd5a704138a64b0218a"
|
||||
dependencies = [
|
||||
"pyo3-derive-backend",
|
||||
"proc-macro2 1.0.36",
|
||||
"pyo3-build-config",
|
||||
"quote 1.0.14",
|
||||
"syn 1.0.84",
|
||||
]
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
|||
get_account_governance_address, get_mint_governance_address,
|
||||
get_program_governance_address, get_token_governance_address, GovernanceConfig,
|
||||
},
|
||||
native_treasury::get_native_treasury_address,
|
||||
program_metadata::get_program_metadata_address,
|
||||
proposal::{get_proposal_address, VoteType},
|
||||
proposal_instruction::{get_proposal_instruction_address, InstructionData},
|
||||
|
@ -452,6 +453,15 @@ pub enum GovernanceInstruction {
|
|||
/// 1. `[signer]` Payer
|
||||
/// 2. `[]` System
|
||||
UpdateProgramMetadata {},
|
||||
|
||||
/// Creates native SOL treasury account for a Governance account
|
||||
/// The account has no data and can be used as a payer for instructions signed by Governance PDAs or as a native SOL treasury
|
||||
///
|
||||
/// 0. `[]` Governance account the treasury account is for
|
||||
/// 1. `[writable]` NativeTreasury account. PDA seeds: ['treasury', governance]
|
||||
/// 2. `[signer]` Payer
|
||||
/// 3. `[]` System
|
||||
CreateNativeTreasury,
|
||||
}
|
||||
|
||||
/// Creates CreateRealm instruction
|
||||
|
@ -1412,3 +1422,28 @@ pub fn upgrade_program_metadata(
|
|||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates CreateNativeTreasury instruction
|
||||
pub fn create_native_treasury(
|
||||
program_id: &Pubkey,
|
||||
// Accounts
|
||||
governance: &Pubkey,
|
||||
payer: &Pubkey,
|
||||
) -> Instruction {
|
||||
let native_treasury_address = get_native_treasury_address(program_id, governance);
|
||||
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(*governance, false),
|
||||
AccountMeta::new(native_treasury_address, false),
|
||||
AccountMeta::new(*payer, true),
|
||||
AccountMeta::new_readonly(system_program::id(), false),
|
||||
];
|
||||
|
||||
let instruction = GovernanceInstruction::CreateNativeTreasury {};
|
||||
|
||||
Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data: instruction.try_to_vec().unwrap(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ mod process_cancel_proposal;
|
|||
mod process_cast_vote;
|
||||
mod process_create_account_governance;
|
||||
mod process_create_mint_governance;
|
||||
mod process_create_native_treasury;
|
||||
mod process_create_program_governance;
|
||||
mod process_create_proposal;
|
||||
mod process_create_realm;
|
||||
|
@ -33,6 +34,7 @@ use process_cancel_proposal::*;
|
|||
use process_cast_vote::*;
|
||||
use process_create_account_governance::*;
|
||||
use process_create_mint_governance::*;
|
||||
use process_create_native_treasury::*;
|
||||
use process_create_program_governance::*;
|
||||
use process_create_proposal::*;
|
||||
use process_create_realm::*;
|
||||
|
@ -200,5 +202,8 @@ pub fn process_instruction(
|
|||
GovernanceInstruction::UpdateProgramMetadata {} => {
|
||||
process_update_program_metadata(program_id, accounts)
|
||||
}
|
||||
GovernanceInstruction::CreateNativeTreasury {} => {
|
||||
process_create_native_treasury(program_id, accounts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
//! Program state processor
|
||||
|
||||
use solana_program::{
|
||||
account_info::{next_account_info, AccountInfo},
|
||||
entrypoint::ProgramResult,
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
system_program,
|
||||
sysvar::Sysvar,
|
||||
};
|
||||
use spl_governance_tools::account::create_and_serialize_account_with_owner_signed;
|
||||
|
||||
use crate::state::{
|
||||
governance::assert_is_valid_governance,
|
||||
native_treasury::{get_native_treasury_address_seeds, NativeTreasury},
|
||||
};
|
||||
|
||||
/// Processes CreateNativeTreasury instruction
|
||||
pub fn process_create_native_treasury(
|
||||
program_id: &Pubkey,
|
||||
accounts: &[AccountInfo],
|
||||
) -> ProgramResult {
|
||||
let account_info_iter = &mut accounts.iter();
|
||||
|
||||
let governance_info = next_account_info(account_info_iter)?; // 0
|
||||
let native_treasury_info = next_account_info(account_info_iter)?; // 1
|
||||
let payer_info = next_account_info(account_info_iter)?; // 2
|
||||
let system_info = next_account_info(account_info_iter)?; // 3
|
||||
|
||||
let rent = Rent::get()?;
|
||||
|
||||
assert_is_valid_governance(program_id, governance_info)?;
|
||||
|
||||
let native_treasury_data = NativeTreasury {};
|
||||
|
||||
create_and_serialize_account_with_owner_signed(
|
||||
payer_info,
|
||||
native_treasury_info,
|
||||
&native_treasury_data,
|
||||
&get_native_treasury_address_seeds(governance_info.key),
|
||||
program_id,
|
||||
&system_program::id(), // System program as the PDA owner
|
||||
system_info,
|
||||
&rent,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -13,6 +13,7 @@ use solana_program::{
|
|||
use crate::state::{
|
||||
enums::{InstructionExecutionStatus, ProposalState},
|
||||
governance::get_governance_data,
|
||||
native_treasury::get_native_treasury_address_seeds,
|
||||
proposal::{get_proposal_data_for_governance, OptionVoteResult},
|
||||
proposal_instruction::get_proposal_instruction_data_for_proposal,
|
||||
};
|
||||
|
@ -47,16 +48,31 @@ pub fn process_execute_instruction(program_id: &Pubkey, accounts: &[AccountInfo]
|
|||
|
||||
let instruction_account_infos = account_info_iter.as_slice();
|
||||
|
||||
let mut signers_seeds: Vec<&[&[u8]]> = vec![];
|
||||
|
||||
// Sign the transaction using the governance PDA
|
||||
let mut governance_seeds = governance_data.get_governance_address_seeds()?.to_vec();
|
||||
let (_, bump_seed) = Pubkey::find_program_address(&governance_seeds, program_id);
|
||||
let bump = &[bump_seed];
|
||||
governance_seeds.push(bump);
|
||||
|
||||
invoke_signed(
|
||||
&instruction,
|
||||
instruction_account_infos,
|
||||
&[&governance_seeds[..]],
|
||||
)?;
|
||||
signers_seeds.push(&governance_seeds[..]);
|
||||
|
||||
// Sign the transaction using the governance treasury PDA if required by the instruction
|
||||
let mut treasury_seeds = get_native_treasury_address_seeds(governance_info.key).to_vec();
|
||||
let (treasury_address, treasury_bump_seed) =
|
||||
Pubkey::find_program_address(&treasury_seeds, program_id);
|
||||
let treasury_bump = &[treasury_bump_seed];
|
||||
|
||||
if instruction_account_infos
|
||||
.iter()
|
||||
.any(|a| a.key == &treasury_address)
|
||||
{
|
||||
treasury_seeds.push(treasury_bump);
|
||||
signers_seeds.push(&treasury_seeds[..]);
|
||||
}
|
||||
|
||||
invoke_signed(&instruction, instruction_account_infos, &signers_seeds[..])?;
|
||||
|
||||
// Update proposal and instruction accounts
|
||||
if proposal_data.state == ProposalState::Succeeded {
|
||||
|
|
|
@ -13,7 +13,7 @@ use solana_program::{
|
|||
pubkey::Pubkey,
|
||||
};
|
||||
use spl_governance_tools::{
|
||||
account::{get_account_data, AccountMaxSize},
|
||||
account::{assert_is_valid_account2, get_account_data, AccountMaxSize},
|
||||
error::GovernanceToolsError,
|
||||
};
|
||||
|
||||
|
@ -225,6 +225,23 @@ pub fn get_account_governance_address<'a>(
|
|||
.0
|
||||
}
|
||||
|
||||
/// Checks whether governance account exists, is initialized and owned by the Governance program
|
||||
pub fn assert_is_valid_governance(
|
||||
program_id: &Pubkey,
|
||||
governance_info: &AccountInfo,
|
||||
) -> Result<(), ProgramError> {
|
||||
assert_is_valid_account2(
|
||||
governance_info,
|
||||
&[
|
||||
GovernanceAccountType::AccountGovernance,
|
||||
GovernanceAccountType::ProgramGovernance,
|
||||
GovernanceAccountType::TokenGovernance,
|
||||
GovernanceAccountType::MintGovernance,
|
||||
],
|
||||
program_id,
|
||||
)
|
||||
}
|
||||
|
||||
/// Validates args supplied to create governance account
|
||||
pub fn assert_valid_create_governance_args(
|
||||
program_id: &Pubkey,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
pub mod enums;
|
||||
pub mod governance;
|
||||
pub mod legacy;
|
||||
pub mod native_treasury;
|
||||
pub mod program_metadata;
|
||||
pub mod proposal;
|
||||
pub mod proposal_instruction;
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
//! Native treasury account
|
||||
|
||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||
use solana_program::pubkey::Pubkey;
|
||||
use spl_governance_tools::account::AccountMaxSize;
|
||||
|
||||
/// Treasury account
|
||||
/// The account has no data and can be used as a payer for instruction signed by Governance PDAs or as a native SOL treasury
|
||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||
pub struct NativeTreasury {}
|
||||
|
||||
impl AccountMaxSize for NativeTreasury {
|
||||
fn get_max_size(&self) -> Option<usize> {
|
||||
Some(0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns NativeTreasury PDA seeds
|
||||
pub fn get_native_treasury_address_seeds(governance: &Pubkey) -> [&[u8]; 2] {
|
||||
[b"native-treasury", governance.as_ref()]
|
||||
}
|
||||
|
||||
/// Returns NativeTreasury PDA address
|
||||
pub fn get_native_treasury_address(program_id: &Pubkey, governance: &Pubkey) -> Pubkey {
|
||||
Pubkey::find_program_address(&get_native_treasury_address_seeds(governance), program_id).0
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
#![cfg(feature = "test-bpf")]
|
||||
|
||||
use solana_program_test::*;
|
||||
|
||||
mod program_test;
|
||||
|
||||
use program_test::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_create_native_treasury() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let account_governance_cookie = governance_test
|
||||
.with_account_governance(
|
||||
&realm_cookie,
|
||||
&governed_account_cookie,
|
||||
&token_owner_record_cookie,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Act
|
||||
let native_treasury_cookie = governance_test
|
||||
.with_native_treasury(&account_governance_cookie)
|
||||
.await;
|
||||
|
||||
// Assert
|
||||
|
||||
let native_treasury_account = governance_test
|
||||
.get_native_treasury_account(&native_treasury_cookie.address)
|
||||
.await;
|
||||
|
||||
assert_eq!(native_treasury_cookie.account, native_treasury_account);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_execute_transfer_from_native_treasury() {
|
||||
// Arrange
|
||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||
|
||||
let realm_cookie = governance_test.with_realm().await;
|
||||
let governed_account_cookie = governance_test.with_governed_account().await;
|
||||
|
||||
let token_owner_record_cookie = governance_test
|
||||
.with_community_token_deposit(&realm_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut account_governance_cookie = governance_test
|
||||
.with_account_governance(
|
||||
&realm_cookie,
|
||||
&governed_account_cookie,
|
||||
&token_owner_record_cookie,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
governance_test
|
||||
.with_native_treasury(&account_governance_cookie)
|
||||
.await;
|
||||
|
||||
let mut proposal_cookie = governance_test
|
||||
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let signatory_record_cookie = governance_test
|
||||
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let wallet_cookie = governance_test.bench.with_wallet().await;
|
||||
let transfer_amount = 100;
|
||||
|
||||
let proposal_instruction_cookie = governance_test
|
||||
.with_native_transfer_instruction(
|
||||
&account_governance_cookie,
|
||||
&mut proposal_cookie,
|
||||
&token_owner_record_cookie,
|
||||
&wallet_cookie,
|
||||
transfer_amount,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
governance_test
|
||||
.sign_off_proposal(&proposal_cookie, &signatory_record_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
governance_test
|
||||
.with_cast_vote(&proposal_cookie, &token_owner_record_cookie, YesNoVote::Yes)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Advance timestamp past hold_up_time
|
||||
governance_test
|
||||
.advance_clock_by_min_timespan(proposal_instruction_cookie.account.hold_up_time as u64)
|
||||
.await;
|
||||
|
||||
// Act
|
||||
governance_test
|
||||
.execute_instruction(&proposal_cookie, &proposal_instruction_cookie)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Assert
|
||||
let wallet_account = governance_test
|
||||
.bench
|
||||
.get_account(&wallet_cookie.address)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
wallet_account.lamports,
|
||||
wallet_cookie.account.lamports + transfer_amount
|
||||
)
|
||||
}
|
|
@ -3,8 +3,8 @@ use solana_sdk::signature::Keypair;
|
|||
use spl_governance::{
|
||||
addins::voter_weight::VoterWeightRecord,
|
||||
state::{
|
||||
governance::Governance, program_metadata::ProgramMetadata, proposal::ProposalV2,
|
||||
proposal_instruction::ProposalInstructionV2, realm::Realm,
|
||||
governance::Governance, native_treasury::NativeTreasury, program_metadata::ProgramMetadata,
|
||||
proposal::ProposalV2, proposal_instruction::ProposalInstructionV2, realm::Realm,
|
||||
realm_config::RealmConfigAccount, signatory_record::SignatoryRecord,
|
||||
token_owner_record::TokenOwnerRecord, vote_record::VoteRecordV2,
|
||||
},
|
||||
|
@ -171,3 +171,9 @@ pub struct ProgramMetadataCookie {
|
|||
pub address: Pubkey,
|
||||
pub account: ProgramMetadata,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NativeTreasuryCookie {
|
||||
pub address: Pubkey,
|
||||
pub account: NativeTreasury,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use solana_program::{
|
|||
program_error::ProgramError,
|
||||
program_pack::{IsInitialized, Pack},
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
system_instruction, system_program,
|
||||
};
|
||||
|
||||
use solana_program_test::*;
|
||||
|
@ -18,8 +18,8 @@ use spl_governance::{
|
|||
addins::voter_weight::{VoterWeightAccountType, VoterWeightRecord},
|
||||
instruction::{
|
||||
add_signatory, cancel_proposal, cast_vote, create_account_governance,
|
||||
create_mint_governance, create_program_governance, create_proposal, create_realm,
|
||||
create_token_governance, create_token_owner_record, deposit_governing_tokens,
|
||||
create_mint_governance, create_native_treasury, create_program_governance, create_proposal,
|
||||
create_realm, create_token_governance, create_token_owner_record, deposit_governing_tokens,
|
||||
execute_instruction, finalize_vote, flag_instruction_error, insert_instruction,
|
||||
relinquish_vote, remove_instruction, remove_signatory, set_governance_config,
|
||||
set_governance_delegate, set_realm_authority, set_realm_config, sign_off_proposal,
|
||||
|
@ -36,6 +36,7 @@ use spl_governance::{
|
|||
get_program_governance_address, get_token_governance_address, Governance,
|
||||
GovernanceConfig,
|
||||
},
|
||||
native_treasury::{get_native_treasury_address, NativeTreasury},
|
||||
program_metadata::{get_program_metadata_address, ProgramMetadata},
|
||||
proposal::{get_proposal_address, OptionVoteResult, ProposalOption, ProposalV2, VoteType},
|
||||
proposal_instruction::{
|
||||
|
@ -61,6 +62,7 @@ use crate::program_test::cookies::{
|
|||
};
|
||||
|
||||
use spl_governance_test_sdk::{
|
||||
cookies::WalletCookie,
|
||||
tools::{clone_keypair, NopOverride},
|
||||
ProgramTestBench,
|
||||
};
|
||||
|
@ -69,8 +71,8 @@ use self::{
|
|||
addins::ensure_voter_weight_addin_is_built,
|
||||
cookies::{
|
||||
GovernanceCookie, GovernedAccountCookie, GovernedMintCookie, GovernedProgramCookie,
|
||||
GovernedTokenCookie, ProgramMetadataCookie, ProposalCookie, ProposalInstructionCookie,
|
||||
RealmCookie, TokenOwnerRecordCookie, VoteRecordCookie,
|
||||
GovernedTokenCookie, NativeTreasuryCookie, ProgramMetadataCookie, ProposalCookie,
|
||||
ProposalInstructionCookie, RealmCookie, TokenOwnerRecordCookie, VoteRecordCookie,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -454,6 +456,37 @@ impl GovernanceProgramTest {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn with_native_treasury(
|
||||
&mut self,
|
||||
governance_cookie: &GovernanceCookie,
|
||||
) -> NativeTreasuryCookie {
|
||||
let create_treasury_ix = create_native_treasury(
|
||||
&self.program_id,
|
||||
&governance_cookie.address,
|
||||
&self.bench.payer.pubkey(),
|
||||
);
|
||||
|
||||
let treasury_address =
|
||||
get_native_treasury_address(&self.program_id, &governance_cookie.address);
|
||||
|
||||
let transfer_ix = system_instruction::transfer(
|
||||
&self.bench.payer.pubkey(),
|
||||
&treasury_address,
|
||||
1_000_000_000,
|
||||
);
|
||||
|
||||
self.bench
|
||||
.process_transaction(&[create_treasury_ix, transfer_ix], None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
NativeTreasuryCookie {
|
||||
address: treasury_address,
|
||||
account: NativeTreasury {},
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn with_community_token_deposit_amount(
|
||||
&mut self,
|
||||
|
@ -1992,6 +2025,31 @@ impl GovernanceProgramTest {
|
|||
.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn with_native_transfer_instruction(
|
||||
&mut self,
|
||||
governance_cookie: &GovernanceCookie,
|
||||
proposal_cookie: &mut ProposalCookie,
|
||||
token_owner_record_cookie: &TokenOwnerRecordCookie,
|
||||
to_wallet_cookie: &WalletCookie,
|
||||
lamports: u64,
|
||||
) -> Result<ProposalInstructionCookie, ProgramError> {
|
||||
let treasury_address =
|
||||
get_native_treasury_address(&self.program_id, &governance_cookie.address);
|
||||
|
||||
let mut transfer_ix =
|
||||
system_instruction::transfer(&treasury_address, &to_wallet_cookie.address, lamports);
|
||||
|
||||
self.with_instruction(
|
||||
proposal_cookie,
|
||||
token_owner_record_cookie,
|
||||
0,
|
||||
None,
|
||||
&mut transfer_ix,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn with_upgrade_program_instruction(
|
||||
&mut self,
|
||||
|
@ -2245,6 +2303,13 @@ impl GovernanceProgramTest {
|
|||
.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_native_treasury_account(&mut self, address: &Pubkey) -> NativeTreasury {
|
||||
self.bench
|
||||
.get_borsh_account::<NativeTreasury>(address)
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn get_realm_account(&mut self, realm_address: &Pubkey) -> Realm {
|
||||
self.bench.get_borsh_account::<Realm>(realm_address).await
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
use solana_program::pubkey::Pubkey;
|
||||
use solana_sdk::account::Account;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TokenAccountCookie {
|
||||
pub address: Pubkey,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WalletCookie {
|
||||
pub address: Pubkey,
|
||||
pub account: Account,
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::borrow::Borrow;
|
||||
|
||||
use borsh::BorshDeserialize;
|
||||
use cookies::TokenAccountCookie;
|
||||
use cookies::{TokenAccountCookie, WalletCookie};
|
||||
use solana_program::{
|
||||
borsh::try_from_slice_unchecked, clock::Clock, instruction::Instruction,
|
||||
program_error::ProgramError, program_pack::Pack, pubkey::Pubkey, rent::Rent,
|
||||
system_instruction, sysvar,
|
||||
system_instruction, system_program, sysvar,
|
||||
};
|
||||
use solana_program_test::{ProgramTest, ProgramTestContext};
|
||||
use solana_sdk::{account::Account, signature::Keypair, signer::Signer, transaction::Transaction};
|
||||
|
@ -82,6 +82,36 @@ impl ProgramTestBench {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn with_wallet(&mut self) -> WalletCookie {
|
||||
let account_rent = self.rent.minimum_balance(0);
|
||||
let account_keypair = Keypair::new();
|
||||
|
||||
let create_account_ix = system_instruction::create_account(
|
||||
&self.context.payer.pubkey(),
|
||||
&account_keypair.pubkey(),
|
||||
account_rent,
|
||||
0,
|
||||
&system_program::id(),
|
||||
);
|
||||
|
||||
self.process_transaction(&[create_account_ix], Some(&[&account_keypair]))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let account = Account {
|
||||
lamports: account_rent,
|
||||
data: vec![],
|
||||
owner: system_program::id(),
|
||||
executable: false,
|
||||
rent_epoch: 0,
|
||||
};
|
||||
|
||||
WalletCookie {
|
||||
address: account_keypair.pubkey(),
|
||||
account,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_mint(&mut self, mint_keypair: &Keypair, mint_authority: &Pubkey) {
|
||||
let mint_rent = self.rent.minimum_balance(spl_token::state::Mint::LEN);
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@ pub fn create_and_serialize_account<'a, T: BorshSerialize + AccountMaxSize>(
|
|||
}
|
||||
|
||||
/// Creates a new account and serializes data into it using the provided seeds to invoke signed CPI call
|
||||
/// The owner of the account is set to the PDA program
|
||||
/// Note: This functions also checks the provided account PDA matches the supplied seeds
|
||||
pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>(
|
||||
payer_info: &AccountInfo<'a>,
|
||||
|
@ -79,6 +80,31 @@ pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSiz
|
|||
program_id: &Pubkey,
|
||||
system_info: &AccountInfo<'a>,
|
||||
rent: &Rent,
|
||||
) -> Result<(), ProgramError> {
|
||||
create_and_serialize_account_with_owner_signed(
|
||||
payer_info,
|
||||
account_info,
|
||||
account_data,
|
||||
account_address_seeds,
|
||||
program_id,
|
||||
program_id, // By default use PDA program_id as the owner of the account
|
||||
system_info,
|
||||
rent,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn create_and_serialize_account_with_owner_signed<'a, T: BorshSerialize + AccountMaxSize>(
|
||||
payer_info: &AccountInfo<'a>,
|
||||
account_info: &AccountInfo<'a>,
|
||||
account_data: &T,
|
||||
account_address_seeds: &[&[u8]],
|
||||
program_id: &Pubkey,
|
||||
owner_program_id: &Pubkey,
|
||||
system_info: &AccountInfo<'a>,
|
||||
rent: &Rent,
|
||||
) -> Result<(), ProgramError> {
|
||||
// Get PDA and assert it's the same as the requested account address
|
||||
let (account_address, bump_seed) =
|
||||
|
@ -106,7 +132,7 @@ pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSiz
|
|||
account_info.key,
|
||||
rent.minimum_balance(account_size),
|
||||
account_size as u64,
|
||||
program_id,
|
||||
owner_program_id,
|
||||
);
|
||||
|
||||
let mut signers_seeds = account_address_seeds.to_vec();
|
||||
|
@ -128,7 +154,7 @@ pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSiz
|
|||
.data
|
||||
.borrow_mut()
|
||||
.copy_from_slice(&serialized_data);
|
||||
} else {
|
||||
} else if account_size > 0 {
|
||||
account_data.serialize(&mut *account_info.data.borrow_mut())?;
|
||||
}
|
||||
|
||||
|
@ -161,6 +187,15 @@ pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
|
|||
account_info: &AccountInfo,
|
||||
expected_account_type: T,
|
||||
owner_program_id: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
assert_is_valid_account2(account_info, &[expected_account_type], owner_program_id)
|
||||
}
|
||||
/// Asserts the given account is not empty, owned by the given program and one of the expected types
|
||||
/// Note: The function assumes the account type T is stored as the first element in the account data
|
||||
pub fn assert_is_valid_account2<T: BorshDeserialize + PartialEq>(
|
||||
account_info: &AccountInfo,
|
||||
expected_account_types: &[T],
|
||||
owner_program_id: &Pubkey,
|
||||
) -> Result<(), ProgramError> {
|
||||
if account_info.owner != owner_program_id {
|
||||
return Err(GovernanceToolsError::InvalidAccountOwner.into());
|
||||
|
@ -172,7 +207,7 @@ pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
|
|||
|
||||
let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
|
||||
|
||||
if account_type != expected_account_type {
|
||||
if expected_account_types.iter().all(|a| a != &account_type) {
|
||||
return Err(GovernanceToolsError::InvalidAccountType.into());
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue