Governance: add v2 account padding (#2842)

* chore: create RealmV2 account type

* feat: add padding to Realm account

* chore: move RealmConfigArgsV1 and GovernanceInstructionV1 to tests

* chore: Add RealmV2 comments

* chore: rename TokenOwnerRecord to V2 and add reserved space

* feat: translate TokenOwnerRecordV1

* chore: rename Governance to GovernanceV2

* feat: add reserved_v2 to Governance

* chore: make clippy happy

* feat: translate GovernanceV1 accounts

* chore: fix chat build

* fix: update seeds logic for governance V1 accounts

* fix: add check for RealmV1 and GovernanceV1

* chore: add padding to ProposalTransaction

* chore: add padding to VoteRecordV2

* chore: add padding to SignatoryRecord V2

* feat: translate SignatoryRecordV1 account
This commit is contained in:
Sebastian Bor 2022-01-31 03:58:28 +00:00 committed by GitHub
parent 925f8f5711
commit ebc91e871a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 665 additions and 325 deletions

View File

@ -5,7 +5,7 @@ use solana_program::{
account_info::AccountInfo, clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey, account_info::AccountInfo, clock::UnixTimestamp, program_error::ProgramError, pubkey::Pubkey,
}; };
use spl_governance_tools::account::{assert_is_valid_account, AccountMaxSize}; use spl_governance_tools::account::{assert_is_valid_account_of_type, AccountMaxSize};
/// Defines all GovernanceChat accounts types /// Defines all GovernanceChat accounts types
#[repr(C)] #[repr(C)]
@ -67,7 +67,7 @@ pub fn assert_is_valid_chat_message(
program_id: &Pubkey, program_id: &Pubkey,
chat_message_info: &AccountInfo, chat_message_info: &AccountInfo,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
assert_is_valid_account( assert_is_valid_account_of_type(
chat_message_info, chat_message_info,
GovernanceChatAccountType::ChatMessage, GovernanceChatAccountType::ChatMessage,
program_id, program_id,

View File

@ -223,7 +223,7 @@ impl GovernanceChatProgramTest {
let create_governance_ix = create_governance( let create_governance_ix = create_governance(
&self.governance_program_id, &self.governance_program_id,
&realm_address, &realm_address,
&governed_account_address, Some(&governed_account_address),
&token_owner_record_address, &token_owner_record_address,
&self.bench.payer.pubkey(), &self.bench.payer.pubkey(),
&token_owner.pubkey(), &token_owner.pubkey(),

View File

@ -7,7 +7,7 @@ use solana_program::{
use spl_governance_addin_api::voter_weight::{VoterWeightAction, VoterWeightRecord}; use spl_governance_addin_api::voter_weight::{VoterWeightAction, VoterWeightRecord};
use spl_governance_tools::account::get_account_data; use spl_governance_tools::account::get_account_data;
use crate::{error::GovernanceError, state::token_owner_record::TokenOwnerRecord}; use crate::{error::GovernanceError, state::token_owner_record::TokenOwnerRecordV2};
/// Asserts the VoterWeightRecord hasn't expired and matches the given action and target if specified /// Asserts the VoterWeightRecord hasn't expired and matches the given action and target if specified
pub fn assert_is_valid_voter_weight( pub fn assert_is_valid_voter_weight(
@ -53,7 +53,7 @@ pub fn get_voter_weight_record_data(
pub fn get_voter_weight_record_data_for_token_owner_record( pub fn get_voter_weight_record_data_for_token_owner_record(
program_id: &Pubkey, program_id: &Pubkey,
voter_weight_record_info: &AccountInfo, voter_weight_record_info: &AccountInfo,
token_owner_record: &TokenOwnerRecord, token_owner_record: &TokenOwnerRecordV2,
) -> Result<VoterWeightRecord, ProgramError> { ) -> Result<VoterWeightRecord, ProgramError> {
let voter_weight_record_data = let voter_weight_record_data =
get_voter_weight_record_data(program_id, voter_weight_record_info)?; get_voter_weight_record_data(program_id, voter_weight_record_info)?;

View File

@ -12,7 +12,7 @@ use spl_governance_tools::account::create_and_serialize_account_signed;
use crate::state::{ use crate::state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
proposal::get_proposal_data, proposal::get_proposal_data,
signatory_record::{get_signatory_record_address_seeds, SignatoryRecord}, signatory_record::{get_signatory_record_address_seeds, SignatoryRecordV2},
token_owner_record::get_token_owner_record_data_for_proposal_owner, token_owner_record::get_token_owner_record_data_for_proposal_owner,
}; };
@ -46,14 +46,15 @@ pub fn process_add_signatory(
token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?; token_owner_record_data.assert_token_owner_or_delegate_is_signer(governance_authority_info)?;
let signatory_record_data = SignatoryRecord { let signatory_record_data = SignatoryRecordV2 {
account_type: GovernanceAccountType::SignatoryRecord, account_type: GovernanceAccountType::SignatoryRecordV2,
proposal: *proposal_info.key, proposal: *proposal_info.key,
signatory, signatory,
signed_off: false, signed_off: false,
reserved_v2: [0; 8],
}; };
create_and_serialize_account_signed::<SignatoryRecord>( create_and_serialize_account_signed::<SignatoryRecordV2>(
payer_info, payer_info,
signatory_record_info, signatory_record_info,
&signatory_record_data, &signatory_record_data,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
clock::Clock, clock::Clock,

View File

@ -26,8 +26,6 @@ use crate::{
}, },
}; };
use borsh::BorshSerialize;
/// Processes CastVote instruction /// Processes CastVote instruction
pub fn process_cast_vote( pub fn process_cast_vote(
program_id: &Pubkey, program_id: &Pubkey,
@ -176,6 +174,8 @@ pub fn process_cast_vote(
governance_data.serialize(&mut *governance_info.data.borrow_mut())?; governance_data.serialize(&mut *governance_info.data.borrow_mut())?;
} }
let governing_token_owner = voter_token_owner_record_data.governing_token_owner;
voter_token_owner_record_data voter_token_owner_record_data
.serialize(&mut *voter_token_owner_record_info.data.borrow_mut())?; .serialize(&mut *voter_token_owner_record_info.data.borrow_mut())?;
@ -185,10 +185,11 @@ pub fn process_cast_vote(
let vote_record_data = VoteRecordV2 { let vote_record_data = VoteRecordV2 {
account_type: GovernanceAccountType::VoteRecordV2, account_type: GovernanceAccountType::VoteRecordV2,
proposal: *proposal_info.key, proposal: *proposal_info.key,
governing_token_owner: voter_token_owner_record_data.governing_token_owner, governing_token_owner,
voter_weight, voter_weight,
vote, vote,
is_relinquished: false, is_relinquished: false,
reserved_v2: [0; 8],
}; };
create_and_serialize_account_signed::<VoteRecordV2>( create_and_serialize_account_signed::<VoteRecordV2>(

View File

@ -3,8 +3,8 @@
use crate::state::{ use crate::state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
governance::{ governance::{
assert_valid_create_governance_args, get_governance_address_seeds, Governance, assert_valid_create_governance_args, get_governance_address_seeds, GovernanceConfig,
GovernanceConfig, GovernanceV2,
}, },
realm::get_realm_data, realm::get_realm_data,
}; };
@ -51,17 +51,18 @@ pub fn process_create_governance(
account_info_iter, // realm_config_info 7, voter_weight_record_info 8 account_info_iter, // realm_config_info 7, voter_weight_record_info 8
)?; )?;
let governance_data = Governance { let governance_data = GovernanceV2 {
account_type: GovernanceAccountType::Governance, account_type: GovernanceAccountType::GovernanceV2,
realm: *realm_info.key, realm: *realm_info.key,
governed_account: *governed_account_info.key, governed_account: *governed_account_info.key,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed::<Governance>( create_and_serialize_account_signed::<GovernanceV2>(
payer_info, payer_info,
governance_info, governance_info,
&governance_data, &governance_data,

View File

@ -4,8 +4,8 @@ use crate::{
state::{ state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
governance::{ governance::{
assert_valid_create_governance_args, get_mint_governance_address_seeds, Governance, assert_valid_create_governance_args, get_mint_governance_address_seeds,
GovernanceConfig, GovernanceConfig, GovernanceV2,
}, },
realm::get_realm_data, realm::get_realm_data,
}, },
@ -63,17 +63,18 @@ pub fn process_create_mint_governance(
account_info_iter, // realm_config_info 9, voter_weight_record_info 10 account_info_iter, // realm_config_info 9, voter_weight_record_info 10
)?; )?;
let mint_governance_data = Governance { let mint_governance_data = GovernanceV2 {
account_type: GovernanceAccountType::MintGovernance, account_type: GovernanceAccountType::MintGovernanceV2,
realm: *realm_info.key, realm: *realm_info.key,
governed_account: *governed_mint_info.key, governed_account: *governed_mint_info.key,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed::<Governance>( create_and_serialize_account_signed::<GovernanceV2>(
payer_info, payer_info,
mint_governance_info, mint_governance_info,
&mint_governance_data, &mint_governance_data,

View File

@ -1,7 +1,7 @@
//! Program state processor //! Program state processor
use crate::{ use crate::{
state::governance::Governance, state::governance::GovernanceV2,
state::{ state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
governance::{ governance::{
@ -63,17 +63,18 @@ pub fn process_create_program_governance(
account_info_iter, // realm_config_info 10, voter_weight_record_info 11 account_info_iter, // realm_config_info 10, voter_weight_record_info 11
)?; )?;
let program_governance_data = Governance { let program_governance_data = GovernanceV2 {
account_type: GovernanceAccountType::ProgramGovernance, account_type: GovernanceAccountType::ProgramGovernanceV2,
realm: *realm_info.key, realm: *realm_info.key,
governed_account: *governed_program_info.key, governed_account: *governed_program_info.key,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed::<Governance>( create_and_serialize_account_signed::<GovernanceV2>(
payer_info, payer_info,
program_governance_info, program_governance_info,
&program_governance_data, &program_governance_data,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
clock::Clock, clock::Clock,

View File

@ -15,7 +15,7 @@ use crate::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
realm::{ realm::{
assert_valid_realm_config_args, get_governing_token_holding_address_seeds, assert_valid_realm_config_args, get_governing_token_holding_address_seeds,
get_realm_address_seeds, Realm, RealmConfig, RealmConfigArgs, get_realm_address_seeds, RealmConfig, RealmConfigArgs, RealmV2,
}, },
realm_config::{get_realm_config_address_seeds, RealmConfigAccount}, realm_config::{get_realm_config_address_seeds, RealmConfigAccount},
}, },
@ -125,8 +125,8 @@ pub fn process_create_realm(
)?; )?;
} }
let realm_data = Realm { let realm_data = RealmV2 {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
community_mint: *governance_token_mint_info.key, community_mint: *governance_token_mint_info.key,
name: name.clone(), name: name.clone(),
@ -143,9 +143,10 @@ pub fn process_create_realm(
use_max_community_voter_weight_addin: config_args.use_max_community_voter_weight_addin, use_max_community_voter_weight_addin: config_args.use_max_community_voter_weight_addin,
}, },
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed::<Realm>( create_and_serialize_account_signed::<RealmV2>(
payer_info, payer_info,
realm_info, realm_info,
&realm_data, &realm_data,

View File

@ -4,8 +4,8 @@ use crate::{
state::{ state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
governance::{ governance::{
assert_valid_create_governance_args, get_token_governance_address_seeds, Governance, assert_valid_create_governance_args, get_token_governance_address_seeds,
GovernanceConfig, GovernanceConfig, GovernanceV2,
}, },
realm::get_realm_data, realm::get_realm_data,
}, },
@ -61,17 +61,18 @@ pub fn process_create_token_governance(
account_info_iter, // realm_config_info 9, voter_weight_record_info 10 account_info_iter, // realm_config_info 9, voter_weight_record_info 10
)?; )?;
let token_governance_data = Governance { let token_governance_data = GovernanceV2 {
account_type: GovernanceAccountType::TokenGovernance, account_type: GovernanceAccountType::TokenGovernanceV2,
realm: *realm_info.key, realm: *realm_info.key,
governed_account: *governed_token_info.key, governed_account: *governed_token_info.key,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed::<Governance>( create_and_serialize_account_signed::<GovernanceV2>(
payer_info, payer_info,
token_governance_info, token_governance_info,
&token_governance_data, &token_governance_data,

View File

@ -14,7 +14,7 @@ use crate::{
state::{ state::{
enums::GovernanceAccountType, enums::GovernanceAccountType,
realm::get_realm_data, realm::get_realm_data,
token_owner_record::{get_token_owner_record_address_seeds, TokenOwnerRecord}, token_owner_record::{get_token_owner_record_address_seeds, TokenOwnerRecordV2},
}, },
}; };
@ -40,8 +40,8 @@ pub fn process_create_token_owner_record(
return Err(GovernanceError::TokenOwnerRecordAlreadyExists.into()); return Err(GovernanceError::TokenOwnerRecordAlreadyExists.into());
} }
let token_owner_record_data = TokenOwnerRecord { let token_owner_record_data = TokenOwnerRecordV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
realm: *realm_info.key, realm: *realm_info.key,
governing_token_owner: *governing_token_owner_info.key, governing_token_owner: *governing_token_owner_info.key,
governing_token_deposit_amount: 0, governing_token_deposit_amount: 0,
@ -51,6 +51,7 @@ pub fn process_create_token_owner_record(
total_votes_count: 0, total_votes_count: 0,
outstanding_proposal_count: 0, outstanding_proposal_count: 0,
reserved: [0; 7], reserved: [0; 7],
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed( create_and_serialize_account_signed(

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult, entrypoint::ProgramResult,
@ -17,7 +16,7 @@ use crate::{
realm::get_realm_data, realm::get_realm_data,
token_owner_record::{ token_owner_record::{
get_token_owner_record_address_seeds, get_token_owner_record_data_for_seeds, get_token_owner_record_address_seeds, get_token_owner_record_data_for_seeds,
TokenOwnerRecord, TokenOwnerRecordV2,
}, },
}, },
tools::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},
@ -79,8 +78,8 @@ pub fn process_deposit_governing_tokens(
return Err(GovernanceError::GoverningTokenOwnerMustSign.into()); return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
} }
let token_owner_record_data = TokenOwnerRecord { let token_owner_record_data = TokenOwnerRecordV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
realm: *realm_info.key, realm: *realm_info.key,
governing_token_owner: *governing_token_owner_info.key, governing_token_owner: *governing_token_owner_info.key,
governing_token_deposit_amount: amount, governing_token_deposit_amount: amount,
@ -90,6 +89,7 @@ pub fn process_deposit_governing_tokens(
total_votes_count: 0, total_votes_count: 0,
outstanding_proposal_count: 0, outstanding_proposal_count: 0,
reserved: [0; 7], reserved: [0; 7],
reserved_v2: [0; 128],
}; };
create_and_serialize_account_signed( create_and_serialize_account_signed(

View File

@ -15,8 +15,6 @@ use crate::state::{
token_owner_record::get_token_owner_record_data_for_proposal_owner, token_owner_record::get_token_owner_record_data_for_proposal_owner,
}; };
use borsh::BorshSerialize;
/// Processes FinalizeVote instruction /// Processes FinalizeVote instruction
pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { pub fn process_finalize_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();

View File

@ -94,6 +94,7 @@ pub fn process_insert_transaction(
executed_at: None, executed_at: None,
execution_status: TransactionExecutionStatus::None, execution_status: TransactionExecutionStatus::None,
proposal: *proposal_info.key, proposal: *proposal_info.key,
reserved_v2: [0; 8],
}; };
create_and_serialize_account_signed::<ProposalTransactionV2>( create_and_serialize_account_signed::<ProposalTransactionV2>(

View File

@ -20,8 +20,6 @@ use crate::{
}, },
}; };
use borsh::BorshSerialize;
/// Processes RelinquishVote instruction /// Processes RelinquishVote instruction
pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult { pub fn process_relinquish_vote(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
let account_info_iter = &mut accounts.iter(); let account_info_iter = &mut accounts.iter();

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult, entrypoint::ProgramResult,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult, entrypoint::ProgramResult,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult, entrypoint::ProgramResult,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
clock::Clock, clock::Clock,

View File

@ -1,6 +1,5 @@
//! Program state processor //! Program state processor
use borsh::BorshSerialize;
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult, entrypoint::ProgramResult,

View File

@ -10,22 +10,22 @@ pub enum GovernanceAccountType {
Uninitialized, Uninitialized,
/// Top level aggregation for governances with Community Token (and optional Council Token) /// Top level aggregation for governances with Community Token (and optional Council Token)
Realm, RealmV1,
/// Token Owner Record for given governing token owner within a Realm /// Token Owner Record for given governing token owner within a Realm
TokenOwnerRecord, TokenOwnerRecordV1,
/// Governance account /// Governance account
Governance, GovernanceV1,
/// Program Governance account /// Program Governance account
ProgramGovernance, ProgramGovernanceV1,
/// Proposal account for Governance account. A single Governance account can have multiple Proposal accounts /// Proposal account for Governance account. A single Governance account can have multiple Proposal accounts
ProposalV1, ProposalV1,
/// Proposal Signatory account /// Proposal Signatory account
SignatoryRecord, SignatoryRecordV1,
/// Vote record account for a given Proposal. Proposal can have 0..n voting records /// Vote record account for a given Proposal. Proposal can have 0..n voting records
VoteRecordV1, VoteRecordV1,
@ -34,12 +34,12 @@ pub enum GovernanceAccountType {
ProposalInstructionV1, ProposalInstructionV1,
/// Mint Governance account /// Mint Governance account
MintGovernance, MintGovernanceV1,
/// Token Governance account /// Token Governance account
TokenGovernance, TokenGovernanceV1,
/// Realm config account /// Realm config account (introduced in V2)
RealmConfig, RealmConfig,
/// Vote record account for a given Proposal. Proposal can have 0..n voting records /// Vote record account for a given Proposal. Proposal can have 0..n voting records
@ -54,8 +54,40 @@ pub enum GovernanceAccountType {
/// V2 adds support for multiple vote options /// V2 adds support for multiple vote options
ProposalV2, ProposalV2,
/// Program metadata account. It stores information about the particular SPL-Governance program instance /// Program metadata account (introduced in V2)
/// It stores information about the particular SPL-Governance program instance
ProgramMetadata, ProgramMetadata,
/// Top level aggregation for governances with Community Token (and optional Council Token)
/// V2 adds the following fields:
/// 1) use_community_voter_weight_addin and use_max_community_voter_weight_addin to RealmConfig
/// 2) voting_proposal_count
/// 3) extra reserved space reserved_v2
RealmV2,
/// Token Owner Record for given governing token owner within a Realm
/// V2 adds extra reserved space reserved_v2
TokenOwnerRecordV2,
/// Governance account
/// V2 adds extra reserved space reserved_v2
GovernanceV2,
/// Program Governance account
/// V2 adds extra reserved space reserved_v2
ProgramGovernanceV2,
/// Mint Governance account
/// V2 adds extra reserved space reserved_v2
MintGovernanceV2,
/// Token Governance account
/// V2 adds extra reserved space reserved_v2
TokenGovernanceV2,
/// Proposal Signatory account
/// V2 adds extra reserved space reserved_v2
SignatoryRecordV2,
} }
impl Default for GovernanceAccountType { impl Default for GovernanceAccountType {

View File

@ -1,19 +1,21 @@
//! Governance Account //! Governance Account
use borsh::maybestd::io::Write;
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::{ state::{
enums::{GovernanceAccountType, VoteThresholdPercentage, VoteTipping}, enums::{GovernanceAccountType, VoteThresholdPercentage, VoteTipping},
legacy::{is_governance_v1_account_type, GovernanceV1},
realm::assert_is_valid_realm, realm::assert_is_valid_realm,
}, },
}; };
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized, account_info::AccountInfo, borsh::try_from_slice_unchecked, program_error::ProgramError,
pubkey::Pubkey, program_pack::IsInitialized, pubkey::Pubkey,
}; };
use spl_governance_tools::{ use spl_governance_tools::{
account::{assert_is_valid_account2, get_account_data, AccountMaxSize}, account::{assert_is_valid_account_of_types, get_account_data, AccountMaxSize},
error::GovernanceToolsError, error::GovernanceToolsError,
}; };
@ -49,7 +51,7 @@ pub struct GovernanceConfig {
/// Governance Account /// Governance Account
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct Governance { pub struct GovernanceV2 {
/// Account type. It can be Uninitialized, Governance, ProgramGovernance, TokenGovernance or MintGovernance /// Account type. It can be Uninitialized, Governance, ProgramGovernance, TokenGovernance or MintGovernance
pub account_type: GovernanceAccountType, pub account_type: GovernanceAccountType,
@ -78,33 +80,43 @@ pub struct Governance {
/// The number of proposals in voting state in the Governance /// The number of proposals in voting state in the Governance
pub voting_proposal_count: u16, pub voting_proposal_count: u16,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 128],
} }
impl AccountMaxSize for Governance {} impl AccountMaxSize for GovernanceV2 {}
impl IsInitialized for Governance { /// Checks if the given account type is one of the Governance account types
pub fn is_governance_v2_account_type(account_type: &GovernanceAccountType) -> bool {
*account_type == GovernanceAccountType::GovernanceV2
|| *account_type == GovernanceAccountType::ProgramGovernanceV2
|| *account_type == GovernanceAccountType::MintGovernanceV2
|| *account_type == GovernanceAccountType::TokenGovernanceV2
}
impl IsInitialized for GovernanceV2 {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::Governance is_governance_v2_account_type(&self.account_type)
|| self.account_type == GovernanceAccountType::ProgramGovernance
|| self.account_type == GovernanceAccountType::MintGovernance
|| self.account_type == GovernanceAccountType::TokenGovernance
} }
} }
impl Governance { impl GovernanceV2 {
/// Returns Governance PDA seeds /// Returns Governance PDA seeds
pub fn get_governance_address_seeds(&self) -> Result<[&[u8]; 3], ProgramError> { pub fn get_governance_address_seeds(&self) -> Result<[&[u8]; 3], ProgramError> {
let seeds = match self.account_type { let seeds = match self.account_type {
GovernanceAccountType::Governance => { GovernanceAccountType::GovernanceV1 | GovernanceAccountType::GovernanceV2 => {
get_governance_address_seeds(&self.realm, &self.governed_account) get_governance_address_seeds(&self.realm, &self.governed_account)
} }
GovernanceAccountType::ProgramGovernance => { GovernanceAccountType::ProgramGovernanceV1
| GovernanceAccountType::ProgramGovernanceV2 => {
get_program_governance_address_seeds(&self.realm, &self.governed_account) get_program_governance_address_seeds(&self.realm, &self.governed_account)
} }
GovernanceAccountType::MintGovernance => { GovernanceAccountType::MintGovernanceV1 | GovernanceAccountType::MintGovernanceV2 => {
get_mint_governance_address_seeds(&self.realm, &self.governed_account) get_mint_governance_address_seeds(&self.realm, &self.governed_account)
} }
GovernanceAccountType::TokenGovernance => { GovernanceAccountType::TokenGovernanceV1 | GovernanceAccountType::TokenGovernanceV2 => {
get_token_governance_address_seeds(&self.realm, &self.governed_account) get_token_governance_address_seeds(&self.realm, &self.governed_account)
} }
_ => return Err(GovernanceToolsError::InvalidAccountType.into()), _ => return Err(GovernanceToolsError::InvalidAccountType.into()),
@ -112,14 +124,67 @@ impl Governance {
Ok(seeds) Ok(seeds)
} }
/// Serializes account into the target buffer
pub fn serialize<W: Write>(self, writer: &mut W) -> Result<(), ProgramError> {
if is_governance_v2_account_type(&self.account_type) {
BorshSerialize::serialize(&self, writer)?
} else if is_governance_v1_account_type(&self.account_type) {
// V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 128] {
panic!("Extended data not supported by GovernanceV1")
}
let governance_data_v1 = GovernanceV1 {
account_type: self.account_type,
realm: self.realm,
governed_account: self.governed_account,
proposals_count: self.proposals_count,
config: self.config,
reserved: self.reserved,
voting_proposal_count: self.voting_proposal_count,
};
BorshSerialize::serialize(&governance_data_v1, writer)?;
}
Ok(())
}
} }
/// Deserializes Governance account and checks owner program /// Deserializes Governance account and checks owner program
pub fn get_governance_data( pub fn get_governance_data(
program_id: &Pubkey, program_id: &Pubkey,
governance_info: &AccountInfo, governance_info: &AccountInfo,
) -> Result<Governance, ProgramError> { ) -> Result<GovernanceV2, ProgramError> {
get_account_data::<Governance>(program_id, governance_info) if governance_info.data_is_empty() {
return Err(GovernanceToolsError::AccountDoesNotExist.into());
}
let account_type: GovernanceAccountType =
try_from_slice_unchecked(&governance_info.data.borrow())?;
// If the account is V1 version then translate to V2
if is_governance_v1_account_type(&account_type) {
let governance_data_v1 = get_account_data::<GovernanceV1>(program_id, governance_info)?;
return Ok(GovernanceV2 {
account_type,
realm: governance_data_v1.realm,
governed_account: governance_data_v1.governed_account,
proposals_count: governance_data_v1.proposals_count,
config: governance_data_v1.config,
reserved: governance_data_v1.reserved,
voting_proposal_count: governance_data_v1.voting_proposal_count,
// Add the extra reserved_v2 padding
reserved_v2: [0; 128],
});
}
get_account_data::<GovernanceV2>(program_id, governance_info)
} }
/// Deserializes Governance account, checks owner program and asserts governance belongs to the given ream /// Deserializes Governance account, checks owner program and asserts governance belongs to the given ream
@ -127,7 +192,7 @@ pub fn get_governance_data_for_realm(
program_id: &Pubkey, program_id: &Pubkey,
governance_info: &AccountInfo, governance_info: &AccountInfo,
realm: &Pubkey, realm: &Pubkey,
) -> Result<Governance, ProgramError> { ) -> Result<GovernanceV2, ProgramError> {
let governance_data = get_governance_data(program_id, governance_info)?; let governance_data = get_governance_data(program_id, governance_info)?;
if governance_data.realm != *realm { if governance_data.realm != *realm {
@ -250,13 +315,17 @@ pub fn assert_is_valid_governance(
program_id: &Pubkey, program_id: &Pubkey,
governance_info: &AccountInfo, governance_info: &AccountInfo,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
assert_is_valid_account2( assert_is_valid_account_of_types(
governance_info, governance_info,
&[ &[
GovernanceAccountType::Governance, GovernanceAccountType::GovernanceV1,
GovernanceAccountType::ProgramGovernance, GovernanceAccountType::GovernanceV2,
GovernanceAccountType::TokenGovernance, GovernanceAccountType::ProgramGovernanceV1,
GovernanceAccountType::MintGovernance, GovernanceAccountType::ProgramGovernanceV2,
GovernanceAccountType::TokenGovernanceV1,
GovernanceAccountType::TokenGovernanceV2,
GovernanceAccountType::MintGovernanceV1,
GovernanceAccountType::MintGovernanceV2,
], ],
program_id, program_id,
) )

View File

@ -2,10 +2,12 @@
use crate::state::{ use crate::state::{
enums::{ enums::{
GovernanceAccountType, InstructionExecutionFlags, MintMaxVoteWeightSource, ProposalState, GovernanceAccountType, InstructionExecutionFlags, ProposalState,
TransactionExecutionStatus, VoteThresholdPercentage, TransactionExecutionStatus, VoteThresholdPercentage,
}, },
governance::GovernanceConfig,
proposal_transaction::InstructionData, proposal_transaction::InstructionData,
realm::RealmConfig,
}; };
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
@ -14,58 +16,6 @@ use solana_program::{
pubkey::Pubkey, pubkey::Pubkey,
}; };
/// Realm Config instruction args
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigArgsV1 {
/// Indicates whether council_mint should be used
/// If yes then council_mint account must also be passed to the instruction
pub use_council_mint: bool,
/// Min number of community tokens required to create a governance
pub min_community_weight_to_create_governance: u64,
/// The source used for community mint max vote weight source
pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
}
/// Instructions supported by the Governance program
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub enum GovernanceInstructionV1 {
/// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
CreateRealm {
#[allow(dead_code)]
/// UTF-8 encoded Governance Realm name
name: String,
#[allow(dead_code)]
/// Realm config args
config_args: RealmConfigArgsV1,
},
/// Deposits governing tokens (Community or Council) to Governance Realm and establishes your voter weight to be used for voting within the Realm
DepositGoverningTokens {
/// The amount to deposit into the realm
#[allow(dead_code)]
amount: u64,
},
}
/// Realm Config defining Realm parameters.
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct RealmConfigV1 {
/// Reserved space for future versions
pub reserved: [u8; 8],
/// Min number of community tokens required to create a governance
pub min_community_weight_to_create_governance: u64,
/// The source used for community mint max vote weight source
pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
/// Optional council mint
pub council_mint: Option<Pubkey>,
}
/// Governance Realm Account /// Governance Realm Account
/// Account PDA seeds" ['governance', name] /// Account PDA seeds" ['governance', name]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
@ -77,10 +27,15 @@ pub struct RealmV1 {
pub community_mint: Pubkey, pub community_mint: Pubkey,
/// Configuration of the Realm /// Configuration of the Realm
pub config: RealmConfigV1, pub config: RealmConfig,
/// Reserved space for future versions /// Reserved space for future versions
pub reserved: [u8; 8], pub reserved: [u8; 6],
/// The number of proposals in voting state in the Realm
/// Note: This is field introduced in V2 but it took space from reserved
/// and we have preserve it for V1 serialization roundtrip
pub voting_proposal_count: u16,
/// Realm authority. The authority must sign transactions which update the realm config /// Realm authority. The authority must sign transactions which update the realm config
/// The authority should be transferred to Realm Governance to make the Realm self governed through proposals /// The authority should be transferred to Realm Governance to make the Realm self governed through proposals
@ -92,7 +47,107 @@ pub struct RealmV1 {
impl IsInitialized for RealmV1 { impl IsInitialized for RealmV1 {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::Realm self.account_type == GovernanceAccountType::RealmV1
}
}
/// Governance Token Owner Record
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct TokenOwnerRecordV1 {
/// Governance account type
pub account_type: GovernanceAccountType,
/// The Realm the TokenOwnerRecord belongs to
pub realm: Pubkey,
/// Governing Token Mint the TokenOwnerRecord holds deposit for
pub governing_token_mint: Pubkey,
/// The owner (either single or multisig) of the deposited governing SPL Tokens
/// This is who can authorize a withdrawal of the tokens
pub governing_token_owner: Pubkey,
/// The amount of governing tokens deposited into the Realm
/// This amount is the voter weight used when voting on proposals
pub governing_token_deposit_amount: u64,
/// The number of votes cast by TokenOwner but not relinquished yet
/// Every time a vote is cast this number is increased and it's always decreased when relinquishing a vote regardless of the vote state
pub unrelinquished_votes_count: u32,
/// The total number of votes cast by the TokenOwner
/// If TokenOwner withdraws vote while voting is still in progress total_votes_count is decreased and the vote doesn't count towards the total
pub total_votes_count: u32,
/// The number of outstanding proposals the TokenOwner currently owns
/// The count is increased when TokenOwner creates a proposal
/// and decreased once it's either voted on (Succeeded or Defeated) or Cancelled
/// By default it's restricted to 1 outstanding Proposal per token owner
pub outstanding_proposal_count: u8,
/// Reserved space for future versions
pub reserved: [u8; 7],
/// A single account that is allowed to operate governance with the deposited governing tokens
/// It can be delegated to by the governing_token_owner or current governance_delegate
pub governance_delegate: Option<Pubkey>,
}
impl IsInitialized for TokenOwnerRecordV1 {
fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::TokenOwnerRecordV1
}
}
/// Governance Account
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct GovernanceV1 {
/// Account type. It can be Uninitialized, Governance, ProgramGovernance, TokenGovernance or MintGovernance
pub account_type: GovernanceAccountType,
/// Governance Realm
pub realm: Pubkey,
/// Account governed by this Governance and/or PDA identity seed
/// It can be Program account, Mint account, Token account or any other account
///
/// Note: The account doesn't have to exist. In that case the field is only a PDA seed
///
/// Note: Setting governed_account doesn't give any authority over the governed account
/// The relevant authorities for specific account types must still be transferred to the Governance PDA
/// Ex: mint_authority/freeze_authority for a Mint account
/// or upgrade_authority for a Program account should be transferred to the Governance PDA
pub governed_account: Pubkey,
/// Running count of proposals
pub proposals_count: u32,
/// Governance config
pub config: GovernanceConfig,
/// Reserved space for future versions
pub reserved: [u8; 6],
/// The number of proposals in voting state in the Governance
/// Note: This is field introduced in V2 but it took space from reserved
/// and we have preserve it for V1 serialization roundtrip
pub voting_proposal_count: u16,
}
/// Checks if the given account type is one of the Governance account types
pub fn is_governance_v1_account_type(account_type: &GovernanceAccountType) -> bool {
*account_type == GovernanceAccountType::GovernanceV1
|| *account_type == GovernanceAccountType::ProgramGovernanceV1
|| *account_type == GovernanceAccountType::MintGovernanceV1
|| *account_type == GovernanceAccountType::TokenGovernanceV1
}
impl IsInitialized for GovernanceV1 {
fn is_initialized(&self) -> bool {
is_governance_v1_account_type(&self.account_type)
} }
} }
@ -186,6 +241,29 @@ impl IsInitialized for ProposalV1 {
} }
} }
/// Account PDA seeds: ['governance', proposal, signatory]
#[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct SignatoryRecordV1 {
/// Governance account type
pub account_type: GovernanceAccountType,
/// Proposal the signatory is assigned for
pub proposal: Pubkey,
/// The account of the signatory who can sign off the proposal
pub signatory: Pubkey,
/// Indicates whether the signatory signed off the proposal
pub signed_off: bool,
}
impl IsInitialized for SignatoryRecordV1 {
fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::SignatoryRecordV1
}
}
/// Proposal instruction V1 /// Proposal instruction V1
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct ProposalInstructionV1 { pub struct ProposalInstructionV1 {

View File

@ -65,7 +65,7 @@ mod test {
#[test] #[test]
fn test_max_size() { fn test_max_size() {
let program_metadata_data = ProgramMetadata { let program_metadata_data = ProgramMetadata {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
updated_at: 10, updated_at: 10,
reserved: [0; 64], reserved: [0; 64],
version: "111.122.155".to_string(), version: "111.122.155".to_string(),

View File

@ -29,7 +29,7 @@ use crate::{
}, },
governance::GovernanceConfig, governance::GovernanceConfig,
proposal_transaction::ProposalTransactionV2, proposal_transaction::ProposalTransactionV2,
realm::Realm, realm::RealmV2,
realm_config::get_realm_config_data_for_realm, realm_config::get_realm_config_data_for_realm,
vote_record::Vote, vote_record::Vote,
}, },
@ -413,7 +413,7 @@ impl ProposalV2 {
/// Calculates max voter weight for given mint supply and realm config /// Calculates max voter weight for given mint supply and realm config
fn get_max_voter_weight_from_mint_supply( fn get_max_voter_weight_from_mint_supply(
&mut self, &mut self,
realm_data: &Realm, realm_data: &RealmV2,
governing_token_mint_supply: u64, governing_token_mint_supply: u64,
) -> Result<u64, ProgramError> { ) -> Result<u64, ProgramError> {
// max vote weight fraction is only used for community mint // max vote weight fraction is only used for community mint
@ -464,7 +464,7 @@ impl ProposalV2 {
governing_token_mint_info: &AccountInfo, governing_token_mint_info: &AccountInfo,
account_info_iter: &mut Iter<AccountInfo>, account_info_iter: &mut Iter<AccountInfo>,
realm: &Pubkey, realm: &Pubkey,
realm_data: &Realm, realm_data: &RealmV2,
) -> Result<u64, ProgramError> { ) -> Result<u64, ProgramError> {
// if the realm uses addin for max community voter weight then use the externally provided max weight // if the realm uses addin for max community voter weight then use the externally provided max weight
if realm_data.config.use_max_community_voter_weight_addin if realm_data.config.use_max_community_voter_weight_addin
@ -774,7 +774,7 @@ impl ProposalV2 {
} }
if self.options.len() != 1 { if self.options.len() != 1 {
panic!("ProposalV1 doesn't multiple options") panic!("ProposalV1 doesn't support multiple options")
} }
let proposal_data_v1 = ProposalV1 { let proposal_data_v1 = ProposalV1 {
@ -1011,7 +1011,7 @@ mod test {
fn create_test_proposal() -> ProposalV2 { fn create_test_proposal() -> ProposalV2 {
ProposalV2 { ProposalV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
governance: Pubkey::new_unique(), governance: Pubkey::new_unique(),
governing_token_mint: Pubkey::new_unique(), governing_token_mint: Pubkey::new_unique(),
max_vote_weight: Some(10), max_vote_weight: Some(10),
@ -1087,9 +1087,9 @@ mod test {
proposal proposal
} }
fn create_test_realm() -> Realm { fn create_test_realm() -> RealmV2 {
Realm { RealmV2 {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
community_mint: Pubkey::new_unique(), community_mint: Pubkey::new_unique(),
reserved: [0; 6], reserved: [0; 6],
@ -1106,6 +1106,7 @@ mod test {
min_community_weight_to_create_governance: 10, min_community_weight_to_create_governance: 10,
}, },
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
} }
} }

View File

@ -114,6 +114,10 @@ pub struct ProposalTransactionV2 {
/// Instruction execution status /// Instruction execution status
pub execution_status: TransactionExecutionStatus, pub execution_status: TransactionExecutionStatus,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 8],
} }
impl AccountMaxSize for ProposalTransactionV2 { impl AccountMaxSize for ProposalTransactionV2 {
@ -125,7 +129,7 @@ impl AccountMaxSize for ProposalTransactionV2 {
.sum::<usize>() .sum::<usize>()
+ 4; + 4;
Some(instructions_size + 90) Some(instructions_size + 98)
} }
} }
@ -146,6 +150,12 @@ impl ProposalTransactionV2 {
}; };
// V1 account can't be resized and we have to translate it back to the original format // V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 8] {
panic!("Extended data not supported by ProposalInstructionV1")
}
let proposal_transaction_data_v1 = ProposalInstructionV1 { let proposal_transaction_data_v1 = ProposalInstructionV1 {
account_type: self.account_type, account_type: self.account_type,
proposal: self.proposal, proposal: self.proposal,
@ -217,6 +227,7 @@ pub fn get_proposal_transaction_data(
instructions: vec![proposal_transaction_data_v1.instruction], instructions: vec![proposal_transaction_data_v1.instruction],
executed_at: proposal_transaction_data_v1.executed_at, executed_at: proposal_transaction_data_v1.executed_at,
execution_status: proposal_transaction_data_v1.execution_status, execution_status: proposal_transaction_data_v1.execution_status,
reserved_v2: [0; 8],
}); });
} }
@ -277,6 +288,7 @@ mod test {
instructions: create_test_instruction_data(), instructions: create_test_instruction_data(),
executed_at: Some(100), executed_at: Some(100),
execution_status: TransactionExecutionStatus::Success, execution_status: TransactionExecutionStatus::Success,
reserved_v2: [0; 8],
} }
} }

View File

@ -1,21 +1,26 @@
//! Realm Account //! Realm Account
use borsh::maybestd::io::Write;
use std::slice::Iter; use std::slice::Iter;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
borsh::try_from_slice_unchecked,
program_error::ProgramError, program_error::ProgramError,
program_pack::IsInitialized, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
}; };
use spl_governance_addin_api::voter_weight::VoterWeightAction; use spl_governance_addin_api::voter_weight::VoterWeightAction;
use spl_governance_tools::account::{assert_is_valid_account, get_account_data, AccountMaxSize}; use spl_governance_tools::account::{
assert_is_valid_account_of_types, get_account_data, AccountMaxSize,
};
use crate::{ use crate::{
error::GovernanceError, error::GovernanceError,
state::{ state::{
enums::{GovernanceAccountType, MintMaxVoteWeightSource}, enums::{GovernanceAccountType, MintMaxVoteWeightSource},
legacy::RealmV1,
token_owner_record::get_token_owner_record_data_for_realm, token_owner_record::get_token_owner_record_data_for_realm,
}, },
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
@ -87,7 +92,7 @@ pub struct RealmConfig {
/// Account PDA seeds" ['governance', name] /// Account PDA seeds" ['governance', name]
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct Realm { pub struct RealmV2 {
/// Governance account type /// Governance account type
pub account_type: GovernanceAccountType, pub account_type: GovernanceAccountType,
@ -109,21 +114,25 @@ pub struct Realm {
/// Governance Realm name /// Governance Realm name
pub name: String, pub name: String,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 128],
} }
impl AccountMaxSize for Realm { impl AccountMaxSize for RealmV2 {
fn get_max_size(&self) -> Option<usize> { fn get_max_size(&self) -> Option<usize> {
Some(self.name.len() + 136) Some(self.name.len() + 264)
} }
} }
impl IsInitialized for Realm { impl IsInitialized for RealmV2 {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::Realm self.account_type == GovernanceAccountType::RealmV2
} }
} }
impl Realm { impl RealmV2 {
/// Asserts the given mint is either Community or Council mint of the Realm /// Asserts the given mint is either Community or Council mint of the Realm
pub fn assert_is_valid_governing_token_mint( pub fn assert_is_valid_governing_token_mint(
&self, &self,
@ -215,6 +224,34 @@ impl Realm {
Ok(()) Ok(())
} }
/// Serializes account into the target buffer
pub fn serialize<W: Write>(self, writer: &mut W) -> Result<(), ProgramError> {
if self.account_type == GovernanceAccountType::RealmV2 {
BorshSerialize::serialize(&self, writer)?
} else if self.account_type == GovernanceAccountType::RealmV1 {
// V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 128] {
panic!("Extended data not supported by RealmV1")
}
let realm_data_v1 = RealmV1 {
account_type: self.account_type,
community_mint: self.community_mint,
config: self.config,
reserved: self.reserved,
voting_proposal_count: self.voting_proposal_count,
authority: self.authority,
name: self.name,
};
BorshSerialize::serialize(&realm_data_v1, writer)?;
}
Ok(())
}
} }
/// Checks whether realm account exists, is initialized and owned by Governance program /// Checks whether realm account exists, is initialized and owned by Governance program
@ -222,15 +259,41 @@ pub fn assert_is_valid_realm(
program_id: &Pubkey, program_id: &Pubkey,
realm_info: &AccountInfo, realm_info: &AccountInfo,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
assert_is_valid_account(realm_info, GovernanceAccountType::Realm, program_id) assert_is_valid_account_of_types(
realm_info,
&[
GovernanceAccountType::RealmV1,
GovernanceAccountType::RealmV2,
],
program_id,
)
} }
/// Deserializes account and checks owner program /// Deserializes account and checks owner program
pub fn get_realm_data( pub fn get_realm_data(
program_id: &Pubkey, program_id: &Pubkey,
realm_info: &AccountInfo, realm_info: &AccountInfo,
) -> Result<Realm, ProgramError> { ) -> Result<RealmV2, ProgramError> {
get_account_data::<Realm>(program_id, realm_info) let account_type: GovernanceAccountType = try_from_slice_unchecked(&realm_info.data.borrow())?;
// If the account is V1 version then translate to V2
if account_type == GovernanceAccountType::ProposalV1 {
let realm_data_v1 = get_account_data::<RealmV1>(program_id, realm_info)?;
return Ok(RealmV2 {
account_type,
community_mint: realm_data_v1.community_mint,
config: realm_data_v1.config,
reserved: realm_data_v1.reserved,
voting_proposal_count: realm_data_v1.voting_proposal_count,
authority: realm_data_v1.authority,
name: realm_data_v1.name,
// Add the extra reserved_v2 padding
reserved_v2: [0; 128],
});
}
get_account_data::<RealmV2>(program_id, realm_info)
} }
/// Deserializes account and checks the given authority is Realm's authority /// Deserializes account and checks the given authority is Realm's authority
@ -238,8 +301,8 @@ pub fn get_realm_data_for_authority(
program_id: &Pubkey, program_id: &Pubkey,
realm_info: &AccountInfo, realm_info: &AccountInfo,
realm_authority: &Pubkey, realm_authority: &Pubkey,
) -> Result<Realm, ProgramError> { ) -> Result<RealmV2, ProgramError> {
let realm_data = get_account_data::<Realm>(program_id, realm_info)?; let realm_data = get_account_data::<RealmV2>(program_id, realm_info)?;
if realm_data.authority.is_none() { if realm_data.authority.is_none() {
return Err(GovernanceError::RealmHasNoAuthority.into()); return Err(GovernanceError::RealmHasNoAuthority.into());
@ -257,7 +320,7 @@ pub fn get_realm_data_for_governing_token_mint(
program_id: &Pubkey, program_id: &Pubkey,
realm_info: &AccountInfo, realm_info: &AccountInfo,
governing_token_mint: &Pubkey, governing_token_mint: &Pubkey,
) -> Result<Realm, ProgramError> { ) -> Result<RealmV2, ProgramError> {
let realm_data = get_realm_data(program_id, realm_info)?; let realm_data = get_realm_data(program_id, realm_info)?;
realm_data.assert_is_valid_governing_token_mint(governing_token_mint)?; realm_data.assert_is_valid_governing_token_mint(governing_token_mint)?;
@ -319,18 +382,15 @@ pub fn assert_valid_realm_config_args(config_args: &RealmConfigArgs) -> Result<(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::{ use crate::instruction::GovernanceInstruction;
instruction::GovernanceInstruction,
state::legacy::{GovernanceInstructionV1, RealmConfigV1, RealmV1},
};
use solana_program::borsh::try_from_slice_unchecked; use solana_program::borsh::try_from_slice_unchecked;
use super::*; use super::*;
#[test] #[test]
fn test_max_size() { fn test_max_size() {
let realm = Realm { let realm = RealmV2 {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
community_mint: Pubkey::new_unique(), community_mint: Pubkey::new_unique(),
reserved: [0; 6], reserved: [0; 6],
@ -346,6 +406,7 @@ mod test {
}, },
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let size = realm.try_to_vec().unwrap().len(); let size = realm.try_to_vec().unwrap().len();
@ -353,36 +414,40 @@ mod test {
assert_eq!(realm.get_max_size(), Some(size)); assert_eq!(realm.get_max_size(), Some(size));
} }
#[test] /// Realm Config instruction args
fn test_deserialize_v2_realm_account_from_v1() { #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
// Arrange pub struct RealmConfigArgsV1 {
let realm_v1 = RealmV1 { /// Indicates whether council_mint should be used
account_type: GovernanceAccountType::Realm, /// If yes then council_mint account must also be passed to the instruction
community_mint: Pubkey::new_unique(), pub use_council_mint: bool,
config: RealmConfigV1 {
council_mint: Some(Pubkey::new_unique()),
reserved: [0; 8],
community_mint_max_vote_weight_source: MintMaxVoteWeightSource::Absolute(100),
min_community_weight_to_create_governance: 10,
},
reserved: [0; 8],
authority: Some(Pubkey::new_unique()),
name: "test-realm-v1".to_string(),
};
let mut realm_v1_data = vec![]; /// Min number of community tokens required to create a governance
realm_v1.serialize(&mut realm_v1_data).unwrap(); pub min_community_weight_to_create_governance: u64,
// Act /// The source used for community mint max vote weight source
let realm_v2: Realm = try_from_slice_unchecked(&realm_v1_data).unwrap(); pub community_mint_max_vote_weight_source: MintMaxVoteWeightSource,
}
// Assert /// Instructions supported by the Governance program
assert!(!realm_v2.config.use_community_voter_weight_addin); #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
assert_eq!(realm_v2.account_type, GovernanceAccountType::Realm); pub enum GovernanceInstructionV1 {
assert_eq!( /// Creates Governance Realm account which aggregates governances for given Community Mint and optional Council Mint
realm_v2.config.min_community_weight_to_create_governance, CreateRealm {
realm_v1.config.min_community_weight_to_create_governance, #[allow(dead_code)]
); /// UTF-8 encoded Governance Realm name
name: String,
#[allow(dead_code)]
/// Realm config args
config_args: RealmConfigArgsV1,
},
/// Deposits governing tokens (Community or Council) to Governance Realm and establishes your voter weight to be used for voting within the Realm
DepositGoverningTokens {
/// The amount to deposit into the realm
#[allow(dead_code)]
amount: u64,
},
} }
#[test] #[test]

View File

@ -92,7 +92,7 @@ mod test {
#[test] #[test]
fn test_max_size() { fn test_max_size() {
let realm_config = RealmConfigAccount { let realm_config = RealmConfigAccount {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
realm: Pubkey::new_unique(), realm: Pubkey::new_unique(),
community_voter_weight_addin: Some(Pubkey::new_unique()), community_voter_weight_addin: Some(Pubkey::new_unique()),
max_community_voter_weight_addin: Some(Pubkey::new_unique()), max_community_voter_weight_addin: Some(Pubkey::new_unique()),

View File

@ -1,6 +1,9 @@
//! Signatory Record //! Signatory Record
use borsh::maybestd::io::Write;
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::borsh::try_from_slice_unchecked;
use solana_program::{ use solana_program::{
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized, account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
@ -11,10 +14,12 @@ use crate::{error::GovernanceError, PROGRAM_AUTHORITY_SEED};
use crate::state::enums::GovernanceAccountType; use crate::state::enums::GovernanceAccountType;
use super::legacy::SignatoryRecordV1;
/// Account PDA seeds: ['governance', proposal, signatory] /// Account PDA seeds: ['governance', proposal, signatory]
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct SignatoryRecord { pub struct SignatoryRecordV2 {
/// Governance account type /// Governance account type
pub account_type: GovernanceAccountType, pub account_type: GovernanceAccountType,
@ -26,17 +31,21 @@ pub struct SignatoryRecord {
/// Indicates whether the signatory signed off the proposal /// Indicates whether the signatory signed off the proposal
pub signed_off: bool, pub signed_off: bool,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 8],
} }
impl AccountMaxSize for SignatoryRecord {} impl AccountMaxSize for SignatoryRecordV2 {}
impl IsInitialized for SignatoryRecord { impl IsInitialized for SignatoryRecordV2 {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::SignatoryRecord self.account_type == GovernanceAccountType::SignatoryRecordV2
} }
} }
impl SignatoryRecord { impl SignatoryRecordV2 {
/// Checks signatory hasn't signed off yet and is transaction signer /// Checks signatory hasn't signed off yet and is transaction signer
pub fn assert_can_sign_off(&self, signatory_info: &AccountInfo) -> Result<(), ProgramError> { pub fn assert_can_sign_off(&self, signatory_info: &AccountInfo) -> Result<(), ProgramError> {
if self.signed_off { if self.signed_off {
@ -58,6 +67,31 @@ impl SignatoryRecord {
Ok(()) Ok(())
} }
/// Serializes account into the target buffer
pub fn serialize<W: Write>(self, writer: &mut W) -> Result<(), ProgramError> {
if self.account_type == GovernanceAccountType::SignatoryRecordV2 {
BorshSerialize::serialize(&self, writer)?
} else if self.account_type == GovernanceAccountType::SignatoryRecordV1 {
// V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 8] {
panic!("Extended data not supported by SignatoryRecordV1")
}
let signatory_record_data_v1 = SignatoryRecordV1 {
account_type: self.account_type,
proposal: self.proposal,
signatory: self.signatory,
signed_off: self.signed_off,
};
BorshSerialize::serialize(&signatory_record_data_v1, writer)?;
}
Ok(())
}
} }
/// Returns SignatoryRecord PDA seeds /// Returns SignatoryRecord PDA seeds
@ -89,8 +123,28 @@ pub fn get_signatory_record_address<'a>(
pub fn get_signatory_record_data( pub fn get_signatory_record_data(
program_id: &Pubkey, program_id: &Pubkey,
signatory_record_info: &AccountInfo, signatory_record_info: &AccountInfo,
) -> Result<SignatoryRecord, ProgramError> { ) -> Result<SignatoryRecordV2, ProgramError> {
get_account_data::<SignatoryRecord>(program_id, signatory_record_info) let account_type: GovernanceAccountType =
try_from_slice_unchecked(&signatory_record_info.data.borrow())?;
// If the account is V1 version then translate to V2
if account_type == GovernanceAccountType::SignatoryRecordV1 {
let signatory_record_data_v1 =
get_account_data::<SignatoryRecordV1>(program_id, signatory_record_info)?;
return Ok(SignatoryRecordV2 {
account_type,
proposal: signatory_record_data_v1.proposal,
signatory: signatory_record_data_v1.signatory,
signed_off: signatory_record_data_v1.signed_off,
// Add the extra reserved_v2 padding
reserved_v2: [0; 8],
});
}
get_account_data::<SignatoryRecordV2>(program_id, signatory_record_info)
} }
/// Deserializes SignatoryRecord and validates its PDA /// Deserializes SignatoryRecord and validates its PDA
@ -99,7 +153,7 @@ pub fn get_signatory_record_data_for_seeds(
signatory_record_info: &AccountInfo, signatory_record_info: &AccountInfo,
proposal: &Pubkey, proposal: &Pubkey,
signatory: &Pubkey, signatory: &Pubkey,
) -> Result<SignatoryRecord, ProgramError> { ) -> Result<SignatoryRecordV2, ProgramError> {
let (signatory_record_address, _) = Pubkey::find_program_address( let (signatory_record_address, _) = Pubkey::find_program_address(
&get_signatory_record_address_seeds(proposal, signatory), &get_signatory_record_address_seeds(proposal, signatory),
program_id, program_id,

View File

@ -1,5 +1,6 @@
//! Token Owner Record Account //! Token Owner Record Account
use borsh::maybestd::io::Write;
use std::slice::Iter; use std::slice::Iter;
use crate::{ use crate::{
@ -8,8 +9,8 @@ use crate::{
}, },
error::GovernanceError, error::GovernanceError,
state::{ state::{
enums::GovernanceAccountType, governance::GovernanceConfig, realm::Realm, enums::GovernanceAccountType, governance::GovernanceConfig, legacy::TokenOwnerRecordV1,
realm_config::get_realm_config_data_for_realm, realm::RealmV2, realm_config::get_realm_config_data_for_realm,
}, },
PROGRAM_AUTHORITY_SEED, PROGRAM_AUTHORITY_SEED,
}; };
@ -17,6 +18,7 @@ use crate::{
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
use solana_program::{ use solana_program::{
account_info::{next_account_info, AccountInfo}, account_info::{next_account_info, AccountInfo},
borsh::try_from_slice_unchecked,
program_error::ProgramError, program_error::ProgramError,
program_pack::IsInitialized, program_pack::IsInitialized,
pubkey::Pubkey, pubkey::Pubkey,
@ -28,7 +30,7 @@ use spl_governance_tools::account::{get_account_data, AccountMaxSize};
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ] /// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)] #[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct TokenOwnerRecord { pub struct TokenOwnerRecordV2 {
/// Governance account type /// Governance account type
pub account_type: GovernanceAccountType, pub account_type: GovernanceAccountType,
@ -66,21 +68,25 @@ pub struct TokenOwnerRecord {
/// A single account that is allowed to operate governance with the deposited governing tokens /// A single account that is allowed to operate governance with the deposited governing tokens
/// It can be delegated to by the governing_token_owner or current governance_delegate /// It can be delegated to by the governing_token_owner or current governance_delegate
pub governance_delegate: Option<Pubkey>, pub governance_delegate: Option<Pubkey>,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 128],
} }
impl AccountMaxSize for TokenOwnerRecord { impl AccountMaxSize for TokenOwnerRecordV2 {
fn get_max_size(&self) -> Option<usize> { fn get_max_size(&self) -> Option<usize> {
Some(154) Some(282)
} }
} }
impl IsInitialized for TokenOwnerRecord { impl IsInitialized for TokenOwnerRecordV2 {
fn is_initialized(&self) -> bool { fn is_initialized(&self) -> bool {
self.account_type == GovernanceAccountType::TokenOwnerRecord self.account_type == GovernanceAccountType::TokenOwnerRecordV2
} }
} }
impl TokenOwnerRecord { impl TokenOwnerRecordV2 {
/// Checks whether the provided Governance Authority signed transaction /// Checks whether the provided Governance Authority signed transaction
pub fn assert_token_owner_or_delegate_is_signer( pub fn assert_token_owner_or_delegate_is_signer(
&self, &self,
@ -104,7 +110,7 @@ impl TokenOwnerRecord {
/// Asserts TokenOwner has enough tokens to be allowed to create proposal and doesn't have any outstanding proposals /// Asserts TokenOwner has enough tokens to be allowed to create proposal and doesn't have any outstanding proposals
pub fn assert_can_create_proposal( pub fn assert_can_create_proposal(
&self, &self,
realm_data: &Realm, realm_data: &RealmV2,
config: &GovernanceConfig, config: &GovernanceConfig,
voter_weight: u64, voter_weight: u64,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
@ -133,7 +139,7 @@ impl TokenOwnerRecord {
/// Asserts TokenOwner has enough tokens to be allowed to create governance /// Asserts TokenOwner has enough tokens to be allowed to create governance
pub fn assert_can_create_governance( pub fn assert_can_create_governance(
&self, &self,
realm_data: &Realm, realm_data: &RealmV2,
voter_weight: u64, voter_weight: u64,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
let min_weight_to_create_governance = let min_weight_to_create_governance =
@ -188,7 +194,7 @@ impl TokenOwnerRecord {
realm_config_info: &AccountInfo, realm_config_info: &AccountInfo,
account_info_iter: &mut Iter<AccountInfo>, account_info_iter: &mut Iter<AccountInfo>,
realm: &Pubkey, realm: &Pubkey,
realm_data: &Realm, realm_data: &RealmV2,
weight_action: VoterWeightAction, weight_action: VoterWeightAction,
weight_action_target: &Pubkey, weight_action_target: &Pubkey,
) -> Result<u64, ProgramError> { ) -> Result<u64, ProgramError> {
@ -218,6 +224,37 @@ impl TokenOwnerRecord {
Ok(self.governing_token_deposit_amount) Ok(self.governing_token_deposit_amount)
} }
} }
/// Serializes account into the target buffer
pub fn serialize<W: Write>(self, writer: &mut W) -> Result<(), ProgramError> {
if self.account_type == GovernanceAccountType::TokenOwnerRecordV2 {
BorshSerialize::serialize(&self, writer)?
} else if self.account_type == GovernanceAccountType::TokenOwnerRecordV1 {
// V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 128] {
panic!("Extended data not supported by TokenOwnerRecordV1")
}
let token_owner_record_data_v1 = TokenOwnerRecordV1 {
account_type: self.account_type,
realm: self.realm,
governing_token_mint: self.governing_token_mint,
governing_token_owner: self.governing_token_owner,
governing_token_deposit_amount: self.governing_token_deposit_amount,
unrelinquished_votes_count: self.unrelinquished_votes_count,
total_votes_count: self.total_votes_count,
outstanding_proposal_count: self.outstanding_proposal_count,
reserved: self.reserved,
governance_delegate: self.governance_delegate,
};
BorshSerialize::serialize(&token_owner_record_data_v1, writer)?;
}
Ok(())
}
} }
/// Returns TokenOwnerRecord PDA address /// Returns TokenOwnerRecord PDA address
@ -252,8 +289,35 @@ pub fn get_token_owner_record_address_seeds<'a>(
pub fn get_token_owner_record_data( pub fn get_token_owner_record_data(
program_id: &Pubkey, program_id: &Pubkey,
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
) -> Result<TokenOwnerRecord, ProgramError> { ) -> Result<TokenOwnerRecordV2, ProgramError> {
get_account_data::<TokenOwnerRecord>(program_id, token_owner_record_info) let account_type: GovernanceAccountType =
try_from_slice_unchecked(&token_owner_record_info.data.borrow())?;
// If the account is V1 version then translate to V2
if account_type == GovernanceAccountType::TokenOwnerRecordV1 {
let token_owner_record_data_v1 =
get_account_data::<TokenOwnerRecordV1>(program_id, token_owner_record_info)?;
return Ok(TokenOwnerRecordV2 {
account_type,
realm: token_owner_record_data_v1.realm,
governing_token_mint: token_owner_record_data_v1.governing_token_mint,
governing_token_owner: token_owner_record_data_v1.governing_token_owner,
governing_token_deposit_amount: token_owner_record_data_v1
.governing_token_deposit_amount,
unrelinquished_votes_count: token_owner_record_data_v1.unrelinquished_votes_count,
total_votes_count: token_owner_record_data_v1.total_votes_count,
outstanding_proposal_count: token_owner_record_data_v1.outstanding_proposal_count,
reserved: token_owner_record_data_v1.reserved,
governance_delegate: token_owner_record_data_v1.governance_delegate,
// Add the extra reserved_v2 padding
reserved_v2: [0; 128],
});
}
get_account_data::<TokenOwnerRecordV2>(program_id, token_owner_record_info)
} }
/// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds /// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds
@ -261,7 +325,7 @@ pub fn get_token_owner_record_data_for_seeds(
program_id: &Pubkey, program_id: &Pubkey,
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
token_owner_record_seeds: &[&[u8]], token_owner_record_seeds: &[&[u8]],
) -> Result<TokenOwnerRecord, ProgramError> { ) -> Result<TokenOwnerRecordV2, ProgramError> {
let (token_owner_record_address, _) = let (token_owner_record_address, _) =
Pubkey::find_program_address(token_owner_record_seeds, program_id); Pubkey::find_program_address(token_owner_record_seeds, program_id);
@ -277,7 +341,7 @@ pub fn get_token_owner_record_data_for_realm(
program_id: &Pubkey, program_id: &Pubkey,
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
realm: &Pubkey, realm: &Pubkey,
) -> Result<TokenOwnerRecord, ProgramError> { ) -> Result<TokenOwnerRecordV2, ProgramError> {
let token_owner_record_data = get_token_owner_record_data(program_id, token_owner_record_info)?; let token_owner_record_data = get_token_owner_record_data(program_id, token_owner_record_info)?;
if token_owner_record_data.realm != *realm { if token_owner_record_data.realm != *realm {
@ -293,7 +357,7 @@ pub fn get_token_owner_record_data_for_realm_and_governing_mint(
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
realm: &Pubkey, realm: &Pubkey,
governing_token_mint: &Pubkey, governing_token_mint: &Pubkey,
) -> Result<TokenOwnerRecord, ProgramError> { ) -> Result<TokenOwnerRecordV2, ProgramError> {
let token_owner_record_data = let token_owner_record_data =
get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm)?; get_token_owner_record_data_for_realm(program_id, token_owner_record_info, realm)?;
@ -309,7 +373,7 @@ pub fn get_token_owner_record_data_for_proposal_owner(
program_id: &Pubkey, program_id: &Pubkey,
token_owner_record_info: &AccountInfo, token_owner_record_info: &AccountInfo,
proposal_owner: &Pubkey, proposal_owner: &Pubkey,
) -> Result<TokenOwnerRecord, ProgramError> { ) -> Result<TokenOwnerRecordV2, ProgramError> {
if token_owner_record_info.key != proposal_owner { if token_owner_record_info.key != proposal_owner {
return Err(GovernanceError::InvalidProposalOwnerAccount.into()); return Err(GovernanceError::InvalidProposalOwnerAccount.into());
} }
@ -319,14 +383,14 @@ pub fn get_token_owner_record_data_for_proposal_owner(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use solana_program::borsh::{get_packed_len, try_from_slice_unchecked}; use solana_program::borsh::get_packed_len;
use super::*; use super::*;
#[test] #[test]
fn test_max_size() { fn test_max_size() {
let token_owner_record = TokenOwnerRecord { let token_owner_record = TokenOwnerRecordV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
realm: Pubkey::new_unique(), realm: Pubkey::new_unique(),
governing_token_mint: Pubkey::new_unique(), governing_token_mint: Pubkey::new_unique(),
governing_token_owner: Pubkey::new_unique(), governing_token_owner: Pubkey::new_unique(),
@ -336,66 +400,11 @@ mod test {
total_votes_count: 1, total_votes_count: 1,
outstanding_proposal_count: 1, outstanding_proposal_count: 1,
reserved: [0; 7], reserved: [0; 7],
reserved_v2: [0; 128],
}; };
let size = get_packed_len::<TokenOwnerRecord>(); let size = get_packed_len::<TokenOwnerRecordV2>();
assert_eq!(token_owner_record.get_max_size(), Some(size)); assert_eq!(token_owner_record.get_max_size(), Some(size));
} }
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
pub struct TokenOwnerRecordV1 {
pub account_type: GovernanceAccountType,
pub realm: Pubkey,
pub governing_token_mint: Pubkey,
pub governing_token_owner: Pubkey,
pub governing_token_deposit_amount: u64,
pub unrelinquished_votes_count: u32,
pub total_votes_count: u32,
pub reserved: [u8; 8],
pub governance_delegate: Option<Pubkey>,
}
#[test]
fn test_deserialize_v1_0_account() {
let token_owner_record_v1_0 = TokenOwnerRecordV1 {
account_type: GovernanceAccountType::TokenOwnerRecord,
realm: Pubkey::new_unique(),
governing_token_mint: Pubkey::new_unique(),
governing_token_owner: Pubkey::new_unique(),
governing_token_deposit_amount: 10,
unrelinquished_votes_count: 2,
total_votes_count: 5,
reserved: [0; 8],
governance_delegate: Some(Pubkey::new_unique()),
};
let mut token_owner_record_v1_0_data = vec![];
token_owner_record_v1_0
.serialize(&mut token_owner_record_v1_0_data)
.unwrap();
let token_owner_record_v1_1_data: TokenOwnerRecord =
try_from_slice_unchecked(&token_owner_record_v1_0_data).unwrap();
assert_eq!(
token_owner_record_v1_0.account_type,
token_owner_record_v1_1_data.account_type
);
assert_eq!(0, token_owner_record_v1_1_data.outstanding_proposal_count);
assert_eq!(
token_owner_record_v1_0.governance_delegate,
token_owner_record_v1_1_data.governance_delegate
);
}
} }

View File

@ -82,6 +82,10 @@ pub struct VoteRecordV2 {
/// Voter's vote /// Voter's vote
pub vote: Vote, pub vote: Vote,
/// Reserved space for versions v2 and onwards
/// Note: This space won't be available to v1 accounts until runtime supports resizing
pub reserved_v2: [u8; 8],
} }
impl AccountMaxSize for VoteRecordV2 {} impl AccountMaxSize for VoteRecordV2 {}
@ -107,6 +111,12 @@ impl VoteRecordV2 {
BorshSerialize::serialize(&self, writer)? BorshSerialize::serialize(&self, writer)?
} else if self.account_type == GovernanceAccountType::VoteRecordV1 { } else if self.account_type == GovernanceAccountType::VoteRecordV1 {
// V1 account can't be resized and we have to translate it back to the original format // V1 account can't be resized and we have to translate it back to the original format
// If reserved_v2 is used it must be individually asses for v1 backward compatibility impact
if self.reserved_v2 != [0; 8] {
panic!("Extended data not supported by VoteRecordV1")
}
let vote_weight = match &self.vote { let vote_weight = match &self.vote {
Vote::Approve(_options) => VoteWeightV1::Yes(self.voter_weight), Vote::Approve(_options) => VoteWeightV1::Yes(self.voter_weight),
Vote::Deny => VoteWeightV1::No(self.voter_weight), Vote::Deny => VoteWeightV1::No(self.voter_weight),
@ -160,6 +170,7 @@ pub fn get_vote_record_data(
is_relinquished: vote_record_data_v1.is_relinquished, is_relinquished: vote_record_data_v1.is_relinquished,
voter_weight, voter_weight,
vote, vote,
reserved_v2: [0; 8],
}); });
} }

View File

@ -1,10 +1,10 @@
use solana_program::{instruction::Instruction, pubkey::Pubkey}; use solana_program::{instruction::Instruction, pubkey::Pubkey};
use solana_sdk::signature::Keypair; use solana_sdk::signature::Keypair;
use spl_governance::state::{ use spl_governance::state::{
governance::Governance, native_treasury::NativeTreasury, program_metadata::ProgramMetadata, governance::GovernanceV2, native_treasury::NativeTreasury, program_metadata::ProgramMetadata,
proposal::ProposalV2, proposal_transaction::ProposalTransactionV2, realm::Realm, proposal::ProposalV2, proposal_transaction::ProposalTransactionV2, realm::RealmV2,
realm_config::RealmConfigAccount, signatory_record::SignatoryRecord, realm_config::RealmConfigAccount, signatory_record::SignatoryRecordV2,
token_owner_record::TokenOwnerRecord, vote_record::VoteRecordV2, token_owner_record::TokenOwnerRecordV2, vote_record::VoteRecordV2,
}; };
use spl_governance_addin_api::{ use spl_governance_addin_api::{
@ -20,7 +20,7 @@ pub trait AccountCookie {
pub struct RealmCookie { pub struct RealmCookie {
pub address: Pubkey, pub address: Pubkey,
pub account: Realm, pub account: RealmV2,
pub community_mint_authority: Keypair, pub community_mint_authority: Keypair,
@ -45,7 +45,7 @@ pub struct RealmConfigCookie {
pub struct TokenOwnerRecordCookie { pub struct TokenOwnerRecordCookie {
pub address: Pubkey, pub address: Pubkey,
pub account: TokenOwnerRecord, pub account: TokenOwnerRecordV2,
pub token_source: Pubkey, pub token_source: Pubkey,
@ -131,7 +131,7 @@ impl AccountCookie for GovernedAccountCookie {
#[derive(Debug)] #[derive(Debug)]
pub struct GovernanceCookie { pub struct GovernanceCookie {
pub address: Pubkey, pub address: Pubkey,
pub account: Governance, pub account: GovernanceV2,
pub next_proposal_index: u32, pub next_proposal_index: u32,
} }
@ -147,7 +147,7 @@ pub struct ProposalCookie {
#[derive(Debug)] #[derive(Debug)]
pub struct SignatoryRecordCookie { pub struct SignatoryRecordCookie {
pub address: Pubkey, pub address: Pubkey,
pub account: SignatoryRecord, pub account: SignatoryRecordV2,
pub signatory: Keypair, pub signatory: Keypair,
} }

View File

@ -32,7 +32,7 @@ use spl_governance::{
}, },
governance::{ governance::{
get_governance_address, get_mint_governance_address, get_program_governance_address, get_governance_address, get_mint_governance_address, get_program_governance_address,
get_token_governance_address, Governance, GovernanceConfig, get_token_governance_address, GovernanceConfig, GovernanceV2,
}, },
native_treasury::{get_native_treasury_address, NativeTreasury}, native_treasury::{get_native_treasury_address, NativeTreasury},
program_metadata::{get_program_metadata_address, ProgramMetadata}, program_metadata::{get_program_metadata_address, ProgramMetadata},
@ -41,12 +41,12 @@ use spl_governance::{
get_proposal_transaction_address, InstructionData, ProposalTransactionV2, get_proposal_transaction_address, InstructionData, ProposalTransactionV2,
}, },
realm::{ realm::{
get_governing_token_holding_address, get_realm_address, Realm, RealmConfig, get_governing_token_holding_address, get_realm_address, RealmConfig, RealmConfigArgs,
RealmConfigArgs, SetRealmAuthorityAction, RealmV2, SetRealmAuthorityAction,
}, },
realm_config::{get_realm_config_address, RealmConfigAccount}, realm_config::{get_realm_config_address, RealmConfigAccount},
signatory_record::{get_signatory_record_address, SignatoryRecord}, signatory_record::{get_signatory_record_address, SignatoryRecordV2},
token_owner_record::{get_token_owner_record_address, TokenOwnerRecord}, token_owner_record::{get_token_owner_record_address, TokenOwnerRecordV2},
vote_record::{get_vote_record_address, Vote, VoteChoice, VoteRecordV2}, vote_record::{get_vote_record_address, Vote, VoteChoice, VoteRecordV2},
}, },
tools::bpf_loader_upgradeable::get_program_data_address, tools::bpf_loader_upgradeable::get_program_data_address,
@ -298,8 +298,8 @@ impl GovernanceProgramTest {
.await .await
.unwrap(); .unwrap();
let account = Realm { let account = RealmV2 {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
community_mint: community_token_mint_keypair.pubkey(), community_mint: community_token_mint_keypair.pubkey(),
name, name,
@ -320,6 +320,7 @@ impl GovernanceProgramTest {
use_max_community_voter_weight_addin: false, use_max_community_voter_weight_addin: false,
}, },
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let realm_config_cookie = if set_realm_config_args.community_voter_weight_addin.is_some() let realm_config_cookie = if set_realm_config_args.community_voter_weight_addin.is_some()
@ -390,8 +391,8 @@ impl GovernanceProgramTest {
.await .await
.unwrap(); .unwrap();
let account = Realm { let account = RealmV2 {
account_type: GovernanceAccountType::Realm, account_type: GovernanceAccountType::RealmV2,
community_mint: realm_cookie.account.community_mint, community_mint: realm_cookie.account.community_mint,
name, name,
@ -408,6 +409,7 @@ impl GovernanceProgramTest {
use_max_community_voter_weight_addin: false, use_max_community_voter_weight_addin: false,
}, },
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let community_token_holding_address = get_governing_token_holding_address( let community_token_holding_address = get_governing_token_holding_address(
@ -470,8 +472,8 @@ impl GovernanceProgramTest {
.await .await
.unwrap(); .unwrap();
let account = TokenOwnerRecord { let account = TokenOwnerRecordV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
realm: realm_cookie.address, realm: realm_cookie.address,
governing_token_mint: realm_cookie.account.community_mint, governing_token_mint: realm_cookie.account.community_mint,
governing_token_owner: token_owner.pubkey(), governing_token_owner: token_owner.pubkey(),
@ -481,6 +483,7 @@ impl GovernanceProgramTest {
total_votes_count: 0, total_votes_count: 0,
outstanding_proposal_count: 0, outstanding_proposal_count: 0,
reserved: [0; 7], reserved: [0; 7],
reserved_v2: [0; 128],
}; };
let token_owner_record_address = get_token_owner_record_address( let token_owner_record_address = get_token_owner_record_address(
@ -686,8 +689,8 @@ impl GovernanceProgramTest {
&token_owner.pubkey(), &token_owner.pubkey(),
); );
let account = TokenOwnerRecord { let account = TokenOwnerRecordV2 {
account_type: GovernanceAccountType::TokenOwnerRecord, account_type: GovernanceAccountType::TokenOwnerRecordV2,
realm: *realm_address, realm: *realm_address,
governing_token_mint: *governing_mint, governing_token_mint: *governing_mint,
governing_token_owner: token_owner.pubkey(), governing_token_owner: token_owner.pubkey(),
@ -697,6 +700,7 @@ impl GovernanceProgramTest {
total_votes_count: 0, total_votes_count: 0,
outstanding_proposal_count: 0, outstanding_proposal_count: 0,
reserved: [0; 7], reserved: [0; 7],
reserved_v2: [0; 128],
}; };
let governance_delegate = Keypair::from_base58_string(&token_owner.to_base58_string()); let governance_delegate = Keypair::from_base58_string(&token_owner.to_base58_string());
@ -1236,14 +1240,15 @@ impl GovernanceProgramTest {
governance_config.clone(), governance_config.clone(),
); );
let account = Governance { let account = GovernanceV2 {
account_type: GovernanceAccountType::Governance, account_type: GovernanceAccountType::GovernanceV2,
realm: realm_cookie.address, realm: realm_cookie.address,
governed_account: governed_account_cookie.address, governed_account: governed_account_cookie.address,
config: governance_config.clone(), config: governance_config.clone(),
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let default_signers = &[create_authority]; let default_signers = &[create_authority];
@ -1404,14 +1409,15 @@ impl GovernanceProgramTest {
.process_transaction(&[create_program_governance_ix], Some(signers)) .process_transaction(&[create_program_governance_ix], Some(signers))
.await?; .await?;
let account = Governance { let account = GovernanceV2 {
account_type: GovernanceAccountType::ProgramGovernance, account_type: GovernanceAccountType::ProgramGovernanceV2,
realm: realm_cookie.address, realm: realm_cookie.address,
governed_account: governed_program_cookie.address, governed_account: governed_program_cookie.address,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let program_governance_address = get_program_governance_address( let program_governance_address = get_program_governance_address(
@ -1527,14 +1533,15 @@ impl GovernanceProgramTest {
.process_transaction(&[create_mint_governance_ix], Some(signers)) .process_transaction(&[create_mint_governance_ix], Some(signers))
.await?; .await?;
let account = Governance { let account = GovernanceV2 {
account_type: GovernanceAccountType::MintGovernance, account_type: GovernanceAccountType::MintGovernanceV2,
realm: realm_cookie.address, realm: realm_cookie.address,
governed_account: governed_mint_cookie.address, governed_account: governed_mint_cookie.address,
config: governance_config.clone(), config: governance_config.clone(),
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let mint_governance_address = get_mint_governance_address( let mint_governance_address = get_mint_governance_address(
@ -1610,14 +1617,15 @@ impl GovernanceProgramTest {
.process_transaction(&[create_token_governance_ix], Some(signers)) .process_transaction(&[create_token_governance_ix], Some(signers))
.await?; .await?;
let account = Governance { let account = GovernanceV2 {
account_type: GovernanceAccountType::TokenGovernance, account_type: GovernanceAccountType::TokenGovernanceV2,
realm: realm_cookie.address, realm: realm_cookie.address,
governed_account: governed_token_cookie.address, governed_account: governed_token_cookie.address,
config, config,
proposals_count: 0, proposals_count: 0,
reserved: [0; 6], reserved: [0; 6],
voting_proposal_count: 0, voting_proposal_count: 0,
reserved_v2: [0; 128],
}; };
let token_governance_address = get_token_governance_address( let token_governance_address = get_token_governance_address(
@ -1857,11 +1865,12 @@ impl GovernanceProgramTest {
&signatory.pubkey(), &signatory.pubkey(),
); );
let signatory_record_data = SignatoryRecord { let signatory_record_data = SignatoryRecordV2 {
account_type: GovernanceAccountType::SignatoryRecord, account_type: GovernanceAccountType::SignatoryRecordV2,
proposal: proposal_cookie.address, proposal: proposal_cookie.address,
signatory: signatory.pubkey(), signatory: signatory.pubkey(),
signed_off: false, signed_off: false,
reserved_v2: [0; 8],
}; };
let signatory_record_cookie = SignatoryRecordCookie { let signatory_record_cookie = SignatoryRecordCookie {
@ -2154,6 +2163,7 @@ impl GovernanceProgramTest {
vote, vote,
voter_weight: vote_amount, voter_weight: vote_amount,
is_relinquished: false, is_relinquished: false,
reserved_v2: [0; 8],
}; };
let vote_record_cookie = VoteRecordCookie { let vote_record_cookie = VoteRecordCookie {
@ -2441,6 +2451,7 @@ impl GovernanceProgramTest {
executed_at: None, executed_at: None,
execution_status: TransactionExecutionStatus::None, execution_status: TransactionExecutionStatus::None,
proposal: proposal_cookie.address, proposal: proposal_cookie.address,
reserved_v2: [0; 8],
}; };
instruction.accounts = instruction instruction.accounts = instruction
@ -2531,9 +2542,9 @@ impl GovernanceProgramTest {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub async fn get_token_owner_record_account(&mut self, address: &Pubkey) -> TokenOwnerRecord { pub async fn get_token_owner_record_account(&mut self, address: &Pubkey) -> TokenOwnerRecordV2 {
self.bench self.bench
.get_borsh_account::<TokenOwnerRecord>(address) .get_borsh_account::<TokenOwnerRecordV2>(address)
.await .await
} }
@ -2552,8 +2563,8 @@ impl GovernanceProgramTest {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub async fn get_realm_account(&mut self, realm_address: &Pubkey) -> Realm { pub async fn get_realm_account(&mut self, realm_address: &Pubkey) -> RealmV2 {
self.bench.get_borsh_account::<Realm>(realm_address).await self.bench.get_borsh_account::<RealmV2>(realm_address).await
} }
#[allow(dead_code)] #[allow(dead_code)]
@ -2567,9 +2578,9 @@ impl GovernanceProgramTest {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub async fn get_governance_account(&mut self, governance_address: &Pubkey) -> Governance { pub async fn get_governance_account(&mut self, governance_address: &Pubkey) -> GovernanceV2 {
self.bench self.bench
.get_borsh_account::<Governance>(governance_address) .get_borsh_account::<GovernanceV2>(governance_address)
.await .await
} }
@ -2601,9 +2612,9 @@ impl GovernanceProgramTest {
pub async fn get_signatory_record_account( pub async fn get_signatory_record_account(
&mut self, &mut self,
proposal_address: &Pubkey, proposal_address: &Pubkey,
) -> SignatoryRecord { ) -> SignatoryRecordV2 {
self.bench self.bench
.get_borsh_account::<SignatoryRecord>(proposal_address) .get_borsh_account::<SignatoryRecordV2>(proposal_address)
.await .await
} }

View File

@ -224,16 +224,16 @@ pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
/// Asserts the given account is not empty, owned by the given program and of the expected type /// Asserts the given account is not empty, owned by the given program and of the expected type
/// Note: The function assumes the account type T is stored as the first element in the account data /// Note: The function assumes the account type T is stored as the first element in the account data
pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>( pub fn assert_is_valid_account_of_type<T: BorshDeserialize + PartialEq>(
account_info: &AccountInfo, account_info: &AccountInfo,
expected_account_type: T, expected_account_type: T,
owner_program_id: &Pubkey, owner_program_id: &Pubkey,
) -> Result<(), ProgramError> { ) -> Result<(), ProgramError> {
assert_is_valid_account2(account_info, &[expected_account_type], owner_program_id) assert_is_valid_account_of_types(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 /// 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 /// 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>( pub fn assert_is_valid_account_of_types<T: BorshDeserialize + PartialEq>(
account_info: &AccountInfo, account_info: &AccountInfo,
expected_account_types: &[T], expected_account_types: &[T],
owner_program_id: &Pubkey, owner_program_id: &Pubkey,