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:
Sebastian Bor 2022-01-03 22:30:34 +00:00 committed by GitHub
parent 78cb324352
commit 24bb1c8158
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 459 additions and 74 deletions

79
Cargo.lock generated
View File

@ -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",
]

View File

@ -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(),
}
}

View File

@ -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)
}
}
}

View File

@ -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(())
}

View File

@ -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 {

View File

@ -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,

View File

@ -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;

View File

@ -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
}

View File

@ -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
)
}

View File

@ -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,
}

View File

@ -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

View File

@ -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,
}

View File

@ -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);

View File

@ -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());
};