Governance: Create Proposals and Sign Off workflow (#1767)
Co-authored-by: Jon Cinque <jon.cinque@gmail.com>
This commit is contained in:
parent
ecc25f7d26
commit
94350d0e8e
|
@ -3819,6 +3819,7 @@ dependencies = [
|
||||||
"borsh 0.8.2",
|
"borsh 0.8.2",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"proptest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"solana-program",
|
"solana-program",
|
||||||
|
|
|
@ -25,6 +25,7 @@ thiserror = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
assert_matches = "1.5.0"
|
assert_matches = "1.5.0"
|
||||||
|
proptest = "0.10"
|
||||||
solana-program-test = "1.6.7"
|
solana-program-test = "1.6.7"
|
||||||
solana-sdk = "1.6.7"
|
solana-sdk = "1.6.7"
|
||||||
|
|
||||||
|
|
|
@ -31,22 +31,66 @@ pub enum GovernanceError {
|
||||||
#[error("Governing Token Owner must sign transaction")]
|
#[error("Governing Token Owner must sign transaction")]
|
||||||
GoverningTokenOwnerMustSign,
|
GoverningTokenOwnerMustSign,
|
||||||
|
|
||||||
/// Governing Token Owner or Vote Authority must sign transaction
|
/// Governing Token Owner or Delegate must sign transaction
|
||||||
#[error("Governing Token Owner or Vote Authority must sign transaction")]
|
#[error("Governing Token Owner or Delegate must sign transaction")]
|
||||||
GoverningTokenOwnerOrVoteAuthrotiyMustSign,
|
GoverningTokenOwnerOrDelegateMustSign,
|
||||||
|
|
||||||
/// All active votes must be relinquished to withdraw governing tokens
|
/// All active votes must be relinquished to withdraw governing tokens
|
||||||
#[error("All active votes must be relinquished to withdraw governing tokens")]
|
#[error("All active votes must be relinquished to withdraw governing tokens")]
|
||||||
CannotWithdrawGoverningTokensWhenActiveVotesExist,
|
CannotWithdrawGoverningTokensWhenActiveVotesExist,
|
||||||
|
|
||||||
/// Invalid Voter account address
|
/// Invalid Token Owner Record account address
|
||||||
#[error("Invalid Voter account address")]
|
#[error("Invalid Token Owner Record account address")]
|
||||||
InvalidVoterAccountAddress,
|
InvalidTokenOwnerRecordAccountAddress,
|
||||||
|
|
||||||
|
/// Invalid Token Owner Record Governing mint
|
||||||
|
#[error("Invalid Token Owner Record Governing mint")]
|
||||||
|
InvalidTokenOwnerRecordGoverningMint,
|
||||||
|
|
||||||
|
/// Invalid Token Owner Record Realm
|
||||||
|
#[error("Invalid Token Owner Record Realm")]
|
||||||
|
InvalidTokenOwnerRecordRealm,
|
||||||
|
|
||||||
|
/// Invalid Signatory account address
|
||||||
|
#[error("Invalid Signatory account address")]
|
||||||
|
InvalidSignatoryAddress,
|
||||||
|
|
||||||
|
/// Signatory already signed off
|
||||||
|
#[error("Signatory already signed off")]
|
||||||
|
SignatoryAlreadySignedOff,
|
||||||
|
|
||||||
|
/// Signatory must sign
|
||||||
|
#[error("Signatory must sign")]
|
||||||
|
SignatoryMustSign,
|
||||||
|
|
||||||
|
/// Invalid Proposal Owner
|
||||||
|
#[error("Invalid Proposal Owner")]
|
||||||
|
InvalidProposalOwnerAccount,
|
||||||
|
|
||||||
/// Invalid Governance config
|
/// Invalid Governance config
|
||||||
#[error("Invalid Governance config")]
|
#[error("Invalid Governance config")]
|
||||||
InvalidGovernanceConfig,
|
InvalidGovernanceConfig,
|
||||||
|
|
||||||
|
/// Proposal for the given Governance, Governing Token Mint and index already exists
|
||||||
|
#[error("Proposal for the given Governance, Governing Token Mint and index already exists")]
|
||||||
|
ProposalAlreadyExists,
|
||||||
|
|
||||||
|
/// Owner doesn't have enough governing tokens to create Proposal
|
||||||
|
#[error("Owner doesn't have enough governing tokens to create Proposal")]
|
||||||
|
NotEnoughTokensToCreateProposal,
|
||||||
|
|
||||||
|
/// Invalid State: Can't edit Signatories
|
||||||
|
#[error("Invalid State: Can't edit Signatories")]
|
||||||
|
InvalidStateCannotEditSignatories,
|
||||||
|
|
||||||
|
/// Invalid State: Can't sign off
|
||||||
|
#[error("Invalid State: Can't sign off")]
|
||||||
|
InvalidStateCannotSignOff,
|
||||||
|
|
||||||
|
/// Invalid Signatory Mint
|
||||||
|
#[error("Invalid Signatory Mint")]
|
||||||
|
InvalidSignatoryMint,
|
||||||
|
|
||||||
/// ---- Account Tools Errors ----
|
/// ---- Account Tools Errors ----
|
||||||
|
|
||||||
/// Invalid account owner
|
/// Invalid account owner
|
||||||
|
|
|
@ -3,13 +3,14 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
id,
|
id,
|
||||||
state::{
|
state::{
|
||||||
enums::GoverningTokenType,
|
|
||||||
governance::{
|
governance::{
|
||||||
get_account_governance_address, get_program_governance_address, GovernanceConfig,
|
get_account_governance_address, get_program_governance_address, GovernanceConfig,
|
||||||
},
|
},
|
||||||
|
proposal::get_proposal_address,
|
||||||
realm::{get_governing_token_holding_address, get_realm_address},
|
realm::{get_governing_token_holding_address, get_realm_address},
|
||||||
|
signatory_record::get_signatory_record_address,
|
||||||
single_signer_instruction::InstructionData,
|
single_signer_instruction::InstructionData,
|
||||||
voter_record::get_voter_record_address,
|
token_owner_record::get_token_owner_record_address,
|
||||||
},
|
},
|
||||||
tools::bpf_loader_upgradeable::get_program_data_address,
|
tools::bpf_loader_upgradeable::get_program_data_address,
|
||||||
};
|
};
|
||||||
|
@ -64,7 +65,7 @@ pub enum GovernanceInstruction {
|
||||||
/// 2. `[writable]` Governing Token Source account. All tokens from the account will be transferred to the Holding account
|
/// 2. `[writable]` Governing Token Source account. All tokens from the account will be transferred to the Holding account
|
||||||
/// 3. `[signer]` Governing Token Owner account
|
/// 3. `[signer]` Governing Token Owner account
|
||||||
/// 4. `[signer]` Governing Token Transfer authority
|
/// 4. `[signer]` Governing Token Transfer authority
|
||||||
/// 5. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
/// 5. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
/// 6. `[signer]` Payer
|
/// 6. `[signer]` Payer
|
||||||
/// 7. `[]` System
|
/// 7. `[]` System
|
||||||
/// 8. `[]` SPL Token
|
/// 8. `[]` SPL Token
|
||||||
|
@ -79,40 +80,30 @@ pub enum GovernanceInstruction {
|
||||||
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
/// 1. `[writable]` Governing Token Holding account. PDA seeds: ['governance',realm, governing_token_mint]
|
||||||
/// 2. `[writable]` Governing Token Destination account. All tokens will be transferred to this account
|
/// 2. `[writable]` Governing Token Destination account. All tokens will be transferred to this account
|
||||||
/// 3. `[signer]` Governing Token Owner account
|
/// 3. `[signer]` Governing Token Owner account
|
||||||
/// 4. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
/// 4. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
/// 5. `[]` SPL Token
|
/// 5. `[]` SPL Token
|
||||||
WithdrawGoverningTokens {},
|
WithdrawGoverningTokens {},
|
||||||
|
|
||||||
/// Sets vote authority for the given Realm and Governing Token Mint (Community or Council)
|
/// Sets Governance Delegate for the given Realm and Governing Token Mint (Community or Council)
|
||||||
/// The vote authority would have voting rights and could vote on behalf of the Governing Token Owner
|
/// The Delegate would have voting rights and could vote on behalf of the Governing Token Owner
|
||||||
/// Note: This doesn't take voting rights from the Token Owner who still can vote and change vote_authority
|
/// The Delegate would also be able to create Proposals on behalf of the Governing Token Owner
|
||||||
|
/// Note: This doesn't take voting rights from the Token Owner who still can vote and change governance_delegate
|
||||||
///
|
///
|
||||||
/// 0. `[signer]` Current Vote authority or Governing Token owner
|
/// 0. `[signer]` Current Governance Delegate or Governing Token owner
|
||||||
/// 1. `[writable]` Voter Record
|
/// 1. `[writable]` Token Owner Record
|
||||||
SetVoteAuthority {
|
SetGovernanceDelegate {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// Governance Realm the new vote authority is set for
|
/// New Governance Delegate
|
||||||
realm: Pubkey,
|
new_governance_delegate: Option<Pubkey>,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
/// Governing Token Mint the vote authority is granted over
|
|
||||||
governing_token_mint: Pubkey,
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
/// Governing Token Owner the vote authority is set for
|
|
||||||
governing_token_owner: Pubkey,
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
/// New vote authority
|
|
||||||
new_vote_authority: Option<Pubkey>,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Creates Account Governance account which can be used to govern an arbitrary account
|
/// Creates Account Governance account which can be used to govern an arbitrary account
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Account Governance account. PDA seeds: ['account-governance', realm, governed_account]
|
/// 0. `[]` Realm account the created Governance belongs to
|
||||||
/// 1. `[signer]` Payer
|
/// 1. `[writable]` Account Governance account. PDA seeds: ['account-governance', realm, governed_account]
|
||||||
/// 2. `[]` System program
|
/// 2. `[signer]` Payer
|
||||||
/// 3. `[]` Sysvar Rent
|
/// 3. `[]` System program
|
||||||
|
/// 4. `[]` Sysvar Rent
|
||||||
CreateAccountGovernance {
|
CreateAccountGovernance {
|
||||||
/// Governance config
|
/// Governance config
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -121,13 +112,14 @@ pub enum GovernanceInstruction {
|
||||||
|
|
||||||
/// Creates Program Governance account which governs an upgradable program
|
/// Creates Program Governance account which governs an upgradable program
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Program Governance account. PDA seeds: ['program-governance', realm, governed_program]
|
/// 0. `[]` Realm account the created Governance belongs to
|
||||||
/// 1. `[writable]` Program Data account of the Program governed by this Governance account
|
/// 1. `[writable]` Program Governance account. PDA seeds: ['program-governance', realm, governed_program]
|
||||||
/// 2. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
|
/// 2. `[writable]` Program Data account of the Program governed by this Governance account
|
||||||
/// 3. `[signer]` Payer
|
/// 3. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
|
||||||
/// 4. `[]` bpf_upgradeable_loader program
|
/// 4. `[signer]` Payer
|
||||||
/// 5. `[]` System program
|
/// 5. `[]` bpf_upgradeable_loader program
|
||||||
/// 6. `[]` Sysvar Rent
|
/// 6. `[]` System program
|
||||||
|
/// 7. `[]` Sysvar Rent
|
||||||
CreateProgramGovernance {
|
CreateProgramGovernance {
|
||||||
/// Governance config
|
/// Governance config
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -140,58 +132,64 @@ pub enum GovernanceInstruction {
|
||||||
transfer_upgrade_authority: bool,
|
transfer_upgrade_authority: bool,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Create Proposal account for Instructions that will be executed at various slots in the future
|
/// Creates Proposal account for Instructions that will be executed at various slots in the future
|
||||||
/// The instruction also grants Admin and Signatory token to the provided account
|
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Uninitialized Proposal account
|
/// 0. `[writable]` Proposal account. PDA seeds ['governance',governance, governing_token_mint, proposal_index]
|
||||||
/// 1. `[writable]` Initialized Governance account
|
/// 1. `[writable]` Governance account
|
||||||
/// 2. `[writable]` Initialized Signatory Mint account
|
/// 2. `[]` Token Owner Record account
|
||||||
/// 3. `[writable]` Initialized Admin Mint account
|
/// 3. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
/// 4. `[writable]` Initialized Admin account for the issued admin token
|
/// 4. `[signer]` Payer
|
||||||
/// 5. `[writable]` Initialized Signatory account for the issued signatory token
|
/// 5. `[]` System program
|
||||||
/// 6. '[]` Token program account
|
/// 6. `[]` Rent sysvar
|
||||||
/// 7. `[]` Rent sysvar
|
/// 7. `[]` Clock sysvar
|
||||||
CreateProposal {
|
CreateProposal {
|
||||||
#[allow(dead_code)]
|
|
||||||
/// Link to gist explaining proposal
|
|
||||||
description_link: String,
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// UTF-8 encoded name of the proposal
|
/// UTF-8 encoded name of the proposal
|
||||||
name: String,
|
name: String,
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// The Governing token (Community or Council) which will be used for voting on the Proposal
|
/// Link to gist explaining proposal
|
||||||
governing_token_type: GoverningTokenType,
|
description_link: String,
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// Governing Token Mint the Proposal is created for
|
||||||
|
governing_token_mint: Pubkey,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
|
/// Adds a signatory to the Proposal which means this Proposal can't leave Draft state until yet another Signatory signs
|
||||||
/// As a result of this call the new Signatory will receive a Signatory Token which then can be used to Sign proposal
|
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Initialized Signatory account
|
/// 1. `[]` Token Owner Record account
|
||||||
/// 2. `[writable]` Initialized Signatory Mint account
|
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
/// 3. `[signer]` Admin account
|
/// 3. `[writable]` Signatory Record Account
|
||||||
/// 4. '[]` Token program account
|
/// 4. `[signer]` Payer
|
||||||
AddSignatory,
|
/// 5. `[]` System program
|
||||||
|
/// 6. `[]` Rent sysvar
|
||||||
|
AddSignatory {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// Signatory to add to the Proposal
|
||||||
|
signatory: Pubkey,
|
||||||
|
},
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Removes a Signatory from the Proposal
|
/// Removes a Signatory from the Proposal
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Signatory account to remove token from
|
/// 1. `[]` Token Owner Record account
|
||||||
/// 2. `[writable]` Signatory Mint account
|
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
/// 3. `[signer]` Admin account
|
/// 3. `[writable]` Signatory Record Account
|
||||||
/// 4. '[]` Token program account
|
/// 4. `[writable]` Beneficiary Account which would receive lamports from the disposed Signatory Record Account
|
||||||
RemoveSignatory,
|
/// 5. `[]` Clock sysvar
|
||||||
|
RemoveSignatory {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
/// Signatory to remove from the Proposal
|
||||||
|
signatory: Pubkey,
|
||||||
|
},
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Adds an instruction to the Proposal. Max of 5 of any type. More than 5 will throw error
|
/// Adds an instruction to the Proposal. Max of 5 of any type. More than 5 will throw error
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Uninitialized Proposal SingleSignerInstruction account
|
/// 1. `[writable]` Uninitialized Proposal SingleSignerInstruction account
|
||||||
/// 2. `[signer]` Admin account
|
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
AddSingleSignerInstruction {
|
AddSingleSignerInstruction {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// Slot waiting time between vote period ending and this being eligible for execution
|
/// Slot waiting time between vote period ending and this being eligible for execution
|
||||||
|
@ -206,42 +204,37 @@ pub enum GovernanceInstruction {
|
||||||
position: u8,
|
position: u8,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Remove instruction from the Proposal
|
/// Remove instruction from the Proposal
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||||
/// 2. `[signer]` Admin account
|
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
RemoveInstruction,
|
RemoveInstruction,
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Update instruction hold up time in the Proposal
|
/// Update instruction hold up time in the Proposal
|
||||||
///
|
///
|
||||||
/// 0. `[]` Proposal account
|
/// 0. `[]` Proposal account
|
||||||
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
/// 1. `[writable]` Proposal SingleSignerInstruction account
|
||||||
/// 2. `[signer]` Admin account
|
/// 2. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
UpdateInstructionHoldUpTime {
|
UpdateInstructionHoldUpTime {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
/// Minimum waiting time in slots for an instruction to be executed after proposal is voted on
|
||||||
hold_up_time: u64,
|
hold_up_time: u64,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// [Requires Admin token]
|
|
||||||
/// Cancels Proposal and moves it into Canceled
|
/// Cancels Proposal and moves it into Canceled
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Admin account
|
/// 1. `[signer]` Governance Authority (Token Owner or Governance Delegate)
|
||||||
CancelProposal,
|
CancelProposal,
|
||||||
|
|
||||||
/// [Requires Signatory token]
|
/// Signs off Proposal indicating the Signatory approves the Proposal
|
||||||
/// Burns signatory token, indicating you approve and sign off on moving this Proposal from Draft state to Voting state
|
/// When the last Signatory signs the Proposal state moves to Voting state
|
||||||
/// The last Signatory token to be burned moves the state to Voting
|
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Signatory account
|
/// 1. `[writable]` Signatory Record account
|
||||||
/// 2. `[writable]` Signatory Mint account
|
/// 2. `[signer]` Signatory account
|
||||||
/// 3. `[]` Token program account
|
/// 3. `[]` Clock sysvar
|
||||||
/// 4. `[]` Clock sysvar
|
|
||||||
SignOffProposal,
|
SignOffProposal,
|
||||||
|
|
||||||
/// Uses your voter weight (deposited Community or Council tokens) to cast a vote on a Proposal
|
/// Uses your voter weight (deposited Community or Council tokens) to cast a vote on a Proposal
|
||||||
|
@ -249,9 +242,9 @@ pub enum GovernanceInstruction {
|
||||||
/// If you tip the consensus then the instructions can begin to be run after their hold up time
|
/// If you tip the consensus then the instructions can begin to be run after their hold up time
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
/// 1. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||||
/// 3. `[signer]` Vote Authority account
|
/// 3. `[signer]` Governance Authority account
|
||||||
/// 4. `[]` Governance account
|
/// 4. `[]` Governance account
|
||||||
Vote {
|
Vote {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -265,9 +258,9 @@ pub enum GovernanceInstruction {
|
||||||
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
|
/// and only allows voters to prune their outstanding votes in case they wanted to withdraw Governing tokens from the Realm
|
||||||
///
|
///
|
||||||
/// 0. `[writable]` Proposal account
|
/// 0. `[writable]` Proposal account
|
||||||
/// 1. `[writable]` Voter Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
/// 1. `[writable]` Token Owner Record account. PDA seeds: ['governance',realm, governing_token_mint, governing_token_owner]
|
||||||
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
/// 2. `[writable]` Proposal Vote Record account. PDA seeds: ['governance',proposal,governing_token_owner]
|
||||||
/// 3. `[signer]` Vote Authority account
|
/// 3. `[signer]` Governance Authority account
|
||||||
RelinquishVote,
|
RelinquishVote,
|
||||||
|
|
||||||
/// Executes an instruction in the Proposal
|
/// Executes an instruction in the Proposal
|
||||||
|
@ -336,7 +329,7 @@ pub fn deposit_governing_tokens(
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let vote_record_address =
|
let vote_record_address =
|
||||||
get_voter_record_address(realm, governing_token_mint, governing_token_owner);
|
get_token_owner_record_address(realm, governing_token_mint, governing_token_owner);
|
||||||
|
|
||||||
let governing_token_holding_address =
|
let governing_token_holding_address =
|
||||||
get_governing_token_holding_address(realm, governing_token_mint);
|
get_governing_token_holding_address(realm, governing_token_mint);
|
||||||
|
@ -373,7 +366,7 @@ pub fn withdraw_governing_tokens(
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let vote_record_address =
|
let vote_record_address =
|
||||||
get_voter_record_address(realm, governing_token_mint, governing_token_owner);
|
get_token_owner_record_address(realm, governing_token_mint, governing_token_owner);
|
||||||
|
|
||||||
let governing_token_holding_address =
|
let governing_token_holding_address =
|
||||||
get_governing_token_holding_address(realm, governing_token_mint);
|
get_governing_token_holding_address(realm, governing_token_mint);
|
||||||
|
@ -396,29 +389,26 @@ pub fn withdraw_governing_tokens(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates SetVoteAuthority instruction
|
/// Creates SetGovernanceDelegate instruction
|
||||||
pub fn set_vote_authority(
|
pub fn set_governance_delegate(
|
||||||
// Accounts
|
// Accounts
|
||||||
vote_authority: &Pubkey,
|
governance_authority: &Pubkey,
|
||||||
// Args
|
// Args
|
||||||
realm: &Pubkey,
|
realm: &Pubkey,
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
governing_token_owner: &Pubkey,
|
governing_token_owner: &Pubkey,
|
||||||
new_vote_authority: &Option<Pubkey>,
|
new_governance_delegate: &Option<Pubkey>,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let vote_record_address =
|
let vote_record_address =
|
||||||
get_voter_record_address(realm, governing_token_mint, governing_token_owner);
|
get_token_owner_record_address(realm, governing_token_mint, governing_token_owner);
|
||||||
|
|
||||||
let accounts = vec![
|
let accounts = vec![
|
||||||
AccountMeta::new_readonly(*vote_authority, true),
|
AccountMeta::new_readonly(*governance_authority, true),
|
||||||
AccountMeta::new(vote_record_address, false),
|
AccountMeta::new(vote_record_address, false),
|
||||||
];
|
];
|
||||||
|
|
||||||
let instruction = GovernanceInstruction::SetVoteAuthority {
|
let instruction = GovernanceInstruction::SetGovernanceDelegate {
|
||||||
realm: *realm,
|
new_governance_delegate: *new_governance_delegate,
|
||||||
governing_token_mint: *governing_token_mint,
|
|
||||||
governing_token_owner: *governing_token_owner,
|
|
||||||
new_vote_authority: *new_vote_authority,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Instruction {
|
Instruction {
|
||||||
|
@ -490,3 +480,138 @@ pub fn create_program_governance(
|
||||||
data: instruction.try_to_vec().unwrap(),
|
data: instruction.try_to_vec().unwrap(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates CreateProposal instruction
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
pub fn create_proposal(
|
||||||
|
// Accounts
|
||||||
|
governance: &Pubkey,
|
||||||
|
governing_token_owner: &Pubkey,
|
||||||
|
governance_authority: &Pubkey,
|
||||||
|
payer: &Pubkey,
|
||||||
|
// Args
|
||||||
|
realm: &Pubkey,
|
||||||
|
name: String,
|
||||||
|
description_link: String,
|
||||||
|
governing_token_mint: &Pubkey,
|
||||||
|
proposal_index: u16,
|
||||||
|
) -> Instruction {
|
||||||
|
let proposal_address = get_proposal_address(
|
||||||
|
governance,
|
||||||
|
governing_token_mint,
|
||||||
|
&proposal_index.to_le_bytes(),
|
||||||
|
);
|
||||||
|
let token_owner_record_address =
|
||||||
|
get_token_owner_record_address(realm, governing_token_mint, governing_token_owner);
|
||||||
|
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(proposal_address, false),
|
||||||
|
AccountMeta::new(*governance, false),
|
||||||
|
AccountMeta::new_readonly(token_owner_record_address, false),
|
||||||
|
AccountMeta::new_readonly(*governance_authority, true),
|
||||||
|
AccountMeta::new_readonly(*payer, true),
|
||||||
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
|
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let instruction = GovernanceInstruction::CreateProposal {
|
||||||
|
name,
|
||||||
|
description_link,
|
||||||
|
governing_token_mint: *governing_token_mint,
|
||||||
|
};
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts,
|
||||||
|
data: instruction.try_to_vec().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates AddSignatory instruction
|
||||||
|
pub fn add_signatory(
|
||||||
|
// Accounts
|
||||||
|
proposal: &Pubkey,
|
||||||
|
token_owner_record: &Pubkey,
|
||||||
|
governance_authority: &Pubkey,
|
||||||
|
payer: &Pubkey,
|
||||||
|
// Args
|
||||||
|
signatory: &Pubkey,
|
||||||
|
) -> Instruction {
|
||||||
|
let signatory_record_address = get_signatory_record_address(proposal, signatory);
|
||||||
|
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(*proposal, false),
|
||||||
|
AccountMeta::new_readonly(*token_owner_record, false),
|
||||||
|
AccountMeta::new_readonly(*governance_authority, true),
|
||||||
|
AccountMeta::new(signatory_record_address, false),
|
||||||
|
AccountMeta::new_readonly(*payer, true),
|
||||||
|
AccountMeta::new_readonly(system_program::id(), false),
|
||||||
|
AccountMeta::new_readonly(sysvar::rent::id(), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let instruction = GovernanceInstruction::AddSignatory {
|
||||||
|
signatory: *signatory,
|
||||||
|
};
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts,
|
||||||
|
data: instruction.try_to_vec().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates RemoveSignatory instruction
|
||||||
|
pub fn remove_signatory(
|
||||||
|
// Accounts
|
||||||
|
proposal: &Pubkey,
|
||||||
|
token_owner_record: &Pubkey,
|
||||||
|
governance_authority: &Pubkey,
|
||||||
|
signatory: &Pubkey,
|
||||||
|
beneficiary: &Pubkey,
|
||||||
|
) -> Instruction {
|
||||||
|
let signatory_record_address = get_signatory_record_address(proposal, signatory);
|
||||||
|
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(*proposal, false),
|
||||||
|
AccountMeta::new_readonly(*token_owner_record, false),
|
||||||
|
AccountMeta::new_readonly(*governance_authority, true),
|
||||||
|
AccountMeta::new(signatory_record_address, false),
|
||||||
|
AccountMeta::new(*beneficiary, false),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let instruction = GovernanceInstruction::RemoveSignatory {
|
||||||
|
signatory: *signatory,
|
||||||
|
};
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts,
|
||||||
|
data: instruction.try_to_vec().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates SignOffProposal instruction
|
||||||
|
pub fn sign_off_proposal(
|
||||||
|
// Accounts
|
||||||
|
proposal: &Pubkey,
|
||||||
|
signatory: &Pubkey,
|
||||||
|
) -> Instruction {
|
||||||
|
let signatory_record_address = get_signatory_record_address(proposal, signatory);
|
||||||
|
|
||||||
|
let accounts = vec![
|
||||||
|
AccountMeta::new(*proposal, false),
|
||||||
|
AccountMeta::new(signatory_record_address, false),
|
||||||
|
AccountMeta::new_readonly(*signatory, true),
|
||||||
|
AccountMeta::new_readonly(sysvar::clock::id(), false),
|
||||||
|
];
|
||||||
|
|
||||||
|
let instruction = GovernanceInstruction::SignOffProposal;
|
||||||
|
|
||||||
|
Instruction {
|
||||||
|
program_id: id(),
|
||||||
|
accounts,
|
||||||
|
data: instruction.try_to_vec().unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
//! Program processor
|
//! Program processor
|
||||||
|
|
||||||
|
mod process_add_signatory;
|
||||||
mod process_create_account_governance;
|
mod process_create_account_governance;
|
||||||
mod process_create_program_governance;
|
mod process_create_program_governance;
|
||||||
|
mod process_create_proposal;
|
||||||
mod process_create_realm;
|
mod process_create_realm;
|
||||||
mod process_deposit_governing_tokens;
|
mod process_deposit_governing_tokens;
|
||||||
mod process_set_vote_authority;
|
mod process_remove_signatory;
|
||||||
|
mod process_set_governance_delegate;
|
||||||
|
mod process_sign_off_proposal;
|
||||||
mod process_withdraw_governing_tokens;
|
mod process_withdraw_governing_tokens;
|
||||||
|
|
||||||
use crate::instruction::GovernanceInstruction;
|
use crate::instruction::GovernanceInstruction;
|
||||||
use borsh::BorshDeserialize;
|
use borsh::BorshDeserialize;
|
||||||
|
|
||||||
|
use process_add_signatory::*;
|
||||||
use process_create_account_governance::*;
|
use process_create_account_governance::*;
|
||||||
use process_create_program_governance::*;
|
use process_create_program_governance::*;
|
||||||
|
use process_create_proposal::*;
|
||||||
use process_create_realm::*;
|
use process_create_realm::*;
|
||||||
use process_deposit_governing_tokens::*;
|
use process_deposit_governing_tokens::*;
|
||||||
use process_set_vote_authority::*;
|
use process_remove_signatory::*;
|
||||||
|
use process_set_governance_delegate::*;
|
||||||
|
use process_sign_off_proposal::*;
|
||||||
use process_withdraw_governing_tokens::*;
|
use process_withdraw_governing_tokens::*;
|
||||||
|
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
@ -31,7 +39,7 @@ pub fn process_instruction(
|
||||||
let instruction = GovernanceInstruction::try_from_slice(input)
|
let instruction = GovernanceInstruction::try_from_slice(input)
|
||||||
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
.map_err(|_| ProgramError::InvalidInstructionData)?;
|
||||||
|
|
||||||
msg!("Instruction: {:?}", instruction);
|
msg!("GOVERNANCE-INSTRUCTION: {:?}", instruction);
|
||||||
|
|
||||||
match instruction {
|
match instruction {
|
||||||
GovernanceInstruction::CreateRealm { name } => {
|
GovernanceInstruction::CreateRealm { name } => {
|
||||||
|
@ -46,18 +54,9 @@ pub fn process_instruction(
|
||||||
process_withdraw_governing_tokens(program_id, accounts)
|
process_withdraw_governing_tokens(program_id, accounts)
|
||||||
}
|
}
|
||||||
|
|
||||||
GovernanceInstruction::SetVoteAuthority {
|
GovernanceInstruction::SetGovernanceDelegate {
|
||||||
realm,
|
new_governance_delegate,
|
||||||
governing_token_mint,
|
} => process_set_governance_delegate(accounts, &new_governance_delegate),
|
||||||
governing_token_owner,
|
|
||||||
new_vote_authority,
|
|
||||||
} => process_set_vote_authority(
|
|
||||||
accounts,
|
|
||||||
&realm,
|
|
||||||
&governing_token_mint,
|
|
||||||
&governing_token_owner,
|
|
||||||
&new_vote_authority,
|
|
||||||
),
|
|
||||||
GovernanceInstruction::CreateProgramGovernance {
|
GovernanceInstruction::CreateProgramGovernance {
|
||||||
config,
|
config,
|
||||||
transfer_upgrade_authority,
|
transfer_upgrade_authority,
|
||||||
|
@ -70,6 +69,26 @@ pub fn process_instruction(
|
||||||
GovernanceInstruction::CreateAccountGovernance { config } => {
|
GovernanceInstruction::CreateAccountGovernance { config } => {
|
||||||
process_create_account_governance(program_id, accounts, config)
|
process_create_account_governance(program_id, accounts, config)
|
||||||
}
|
}
|
||||||
|
GovernanceInstruction::CreateProposal {
|
||||||
|
name,
|
||||||
|
description_link,
|
||||||
|
governing_token_mint,
|
||||||
|
} => process_create_proposal(
|
||||||
|
program_id,
|
||||||
|
accounts,
|
||||||
|
name,
|
||||||
|
description_link,
|
||||||
|
governing_token_mint,
|
||||||
|
),
|
||||||
|
GovernanceInstruction::AddSignatory { signatory } => {
|
||||||
|
process_add_signatory(program_id, accounts, signatory)
|
||||||
|
}
|
||||||
|
GovernanceInstruction::RemoveSignatory { signatory } => {
|
||||||
|
process_remove_signatory(program_id, accounts, signatory)
|
||||||
|
}
|
||||||
|
GovernanceInstruction::SignOffProposal {} => {
|
||||||
|
process_sign_off_proposal(program_id, accounts)
|
||||||
|
}
|
||||||
_ => todo!("Instruction not implemented yet"),
|
_ => todo!("Instruction not implemented yet"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
state::{
|
||||||
|
enums::GovernanceAccountType,
|
||||||
|
proposal::deserialize_proposal_raw,
|
||||||
|
signatory_record::{get_signatory_record_address_seeds, SignatoryRecord},
|
||||||
|
token_owner_record::deserialize_token_owner_record_for_proposal_owner,
|
||||||
|
},
|
||||||
|
tools::{
|
||||||
|
account::create_and_serialize_account_signed,
|
||||||
|
asserts::assert_token_owner_or_delegate_is_signer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Processes AddSignatory instruction
|
||||||
|
pub fn process_add_signatory(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
signatory: Pubkey,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||||
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||||
|
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||||
|
|
||||||
|
let signatory_record_info = next_account_info(account_info_iter)?; // 3
|
||||||
|
|
||||||
|
let payer_info = next_account_info(account_info_iter)?; // 4
|
||||||
|
let system_info = next_account_info(account_info_iter)?; // 5
|
||||||
|
|
||||||
|
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||||
|
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||||
|
|
||||||
|
let mut proposal_data = deserialize_proposal_raw(proposal_info)?;
|
||||||
|
proposal_data.assert_can_edit_signatories()?;
|
||||||
|
|
||||||
|
let token_owner_record_data = deserialize_token_owner_record_for_proposal_owner(
|
||||||
|
token_owner_record_info,
|
||||||
|
&proposal_data.token_owner_record,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_token_owner_or_delegate_is_signer(&token_owner_record_data, governance_authority_info)?;
|
||||||
|
|
||||||
|
let signatory_record_data = SignatoryRecord {
|
||||||
|
account_type: GovernanceAccountType::SignatoryRecord,
|
||||||
|
proposal: *proposal_info.key,
|
||||||
|
signatory,
|
||||||
|
signed_off: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
create_and_serialize_account_signed::<SignatoryRecord>(
|
||||||
|
payer_info,
|
||||||
|
signatory_record_info,
|
||||||
|
&signatory_record_data,
|
||||||
|
&get_signatory_record_address_seeds(proposal_info.key, &signatory),
|
||||||
|
program_id,
|
||||||
|
system_info,
|
||||||
|
rent,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
proposal_data.signatories_count = proposal_data.signatories_count.checked_add(1).unwrap();
|
||||||
|
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ pub fn process_create_account_governance(
|
||||||
let account_governance_data = Governance {
|
let account_governance_data = Governance {
|
||||||
account_type: GovernanceAccountType::AccountGovernance,
|
account_type: GovernanceAccountType::AccountGovernance,
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
proposal_count: 0,
|
proposals_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_and_serialize_account_signed::<Governance>(
|
create_and_serialize_account_signed::<Governance>(
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub fn process_create_program_governance(
|
||||||
let program_governance_data = Governance {
|
let program_governance_data = Governance {
|
||||||
account_type: GovernanceAccountType::ProgramGovernance,
|
account_type: GovernanceAccountType::ProgramGovernance,
|
||||||
config: config.clone(),
|
config: config.clone(),
|
||||||
proposal_count: 0,
|
proposals_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_and_serialize_account_signed::<Governance>(
|
create_and_serialize_account_signed::<Governance>(
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
clock::Clock,
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
rent::Rent,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::GovernanceError,
|
||||||
|
state::{
|
||||||
|
enums::{GovernanceAccountType, ProposalState},
|
||||||
|
governance::deserialize_governance_raw,
|
||||||
|
proposal::{get_proposal_address_seeds, Proposal},
|
||||||
|
token_owner_record::deserialize_token_owner_record_for_realm_and_governing_mint,
|
||||||
|
},
|
||||||
|
tools::{
|
||||||
|
account::create_and_serialize_account_signed,
|
||||||
|
asserts::assert_token_owner_or_delegate_is_signer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Processes CreateProposal instruction
|
||||||
|
pub fn process_create_proposal(
|
||||||
|
program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
name: String,
|
||||||
|
description_link: String,
|
||||||
|
governing_token_mint: Pubkey,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||||
|
let governance_info = next_account_info(account_info_iter)?; // 1
|
||||||
|
|
||||||
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 2
|
||||||
|
let governance_authority_info = next_account_info(account_info_iter)?; // 3
|
||||||
|
|
||||||
|
let payer_info = next_account_info(account_info_iter)?; // 4
|
||||||
|
let system_info = next_account_info(account_info_iter)?; // 5
|
||||||
|
|
||||||
|
let rent_sysvar_info = next_account_info(account_info_iter)?; // 6
|
||||||
|
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||||
|
|
||||||
|
let clock_info = next_account_info(account_info_iter)?; // 7
|
||||||
|
let clock = Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
if !proposal_info.data_is_empty() {
|
||||||
|
return Err(GovernanceError::ProposalAlreadyExists.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut governance_data = deserialize_governance_raw(governance_info)?;
|
||||||
|
|
||||||
|
let token_owner_record_data = deserialize_token_owner_record_for_realm_and_governing_mint(
|
||||||
|
&token_owner_record_info,
|
||||||
|
&governance_data.config.realm,
|
||||||
|
&governing_token_mint,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// proposal_owner must be either governing token owner or governance_delegate and must sign this transaction
|
||||||
|
assert_token_owner_or_delegate_is_signer(&token_owner_record_data, governance_authority_info)?;
|
||||||
|
|
||||||
|
if token_owner_record_data.governing_token_deposit_amount
|
||||||
|
< governance_data.config.min_tokens_to_create_proposal as u64
|
||||||
|
{
|
||||||
|
return Err(GovernanceError::NotEnoughTokensToCreateProposal.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let proposal_data = Proposal {
|
||||||
|
account_type: GovernanceAccountType::Proposal,
|
||||||
|
governance: *governance_info.key,
|
||||||
|
governing_token_mint,
|
||||||
|
state: ProposalState::Draft,
|
||||||
|
token_owner_record: *token_owner_record_info.key,
|
||||||
|
|
||||||
|
signatories_count: 0,
|
||||||
|
signatories_signed_off_count: 0,
|
||||||
|
|
||||||
|
name,
|
||||||
|
description_link,
|
||||||
|
|
||||||
|
draft_at: clock.slot,
|
||||||
|
signing_off_at: None,
|
||||||
|
voting_at: None,
|
||||||
|
voting_completed_at: None,
|
||||||
|
executing_at: None,
|
||||||
|
closed_at: None,
|
||||||
|
|
||||||
|
number_of_executed_instructions: 0,
|
||||||
|
number_of_instructions: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
create_and_serialize_account_signed::<Proposal>(
|
||||||
|
payer_info,
|
||||||
|
proposal_info,
|
||||||
|
&proposal_data,
|
||||||
|
&get_proposal_address_seeds(
|
||||||
|
governance_info.key,
|
||||||
|
&governing_token_mint,
|
||||||
|
&governance_data.proposals_count.to_le_bytes(),
|
||||||
|
),
|
||||||
|
program_id,
|
||||||
|
system_info,
|
||||||
|
rent,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
governance_data.proposals_count = governance_data.proposals_count.checked_add(1).unwrap();
|
||||||
|
governance_data.serialize(&mut *governance_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -12,9 +12,11 @@ use solana_program::{
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GovernanceError,
|
error::GovernanceError,
|
||||||
state::{
|
state::{
|
||||||
enums::{GovernanceAccountType, GoverningTokenType},
|
enums::GovernanceAccountType,
|
||||||
realm::deserialize_realm,
|
realm::deserialize_realm_raw,
|
||||||
voter_record::{deserialize_voter_record, get_voter_record_address_seeds, VoterRecord},
|
token_owner_record::{
|
||||||
|
deserialize_token_owner_record, get_token_owner_record_address_seeds, TokenOwnerRecord,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tools::{
|
tools::{
|
||||||
account::create_and_serialize_account_signed,
|
account::create_and_serialize_account_signed,
|
||||||
|
@ -37,7 +39,7 @@ pub fn process_deposit_governing_tokens(
|
||||||
let governing_token_source_info = next_account_info(account_info_iter)?; // 2
|
let governing_token_source_info = next_account_info(account_info_iter)?; // 2
|
||||||
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
||||||
let governing_token_transfer_authority_info = next_account_info(account_info_iter)?; // 4
|
let governing_token_transfer_authority_info = next_account_info(account_info_iter)?; // 4
|
||||||
let voter_record_info = next_account_info(account_info_iter)?; // 5
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 5
|
||||||
let payer_info = next_account_info(account_info_iter)?; // 6
|
let payer_info = next_account_info(account_info_iter)?; // 6
|
||||||
let system_info = next_account_info(account_info_iter)?; // 7
|
let system_info = next_account_info(account_info_iter)?; // 7
|
||||||
let spl_token_info = next_account_info(account_info_iter)?; // 8
|
let spl_token_info = next_account_info(account_info_iter)?; // 8
|
||||||
|
@ -45,16 +47,10 @@ pub fn process_deposit_governing_tokens(
|
||||||
let rent_sysvar_info = next_account_info(account_info_iter)?; // 9
|
let rent_sysvar_info = next_account_info(account_info_iter)?; // 9
|
||||||
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
let rent = &Rent::from_account_info(rent_sysvar_info)?;
|
||||||
|
|
||||||
let realm_data = deserialize_realm(realm_info)?;
|
let realm_data = deserialize_realm_raw(realm_info)?;
|
||||||
let governing_token_mint = get_mint_from_token_account(governing_token_holding_info)?;
|
let governing_token_mint = get_mint_from_token_account(governing_token_holding_info)?;
|
||||||
|
|
||||||
let governing_token_type = if governing_token_mint == realm_data.community_mint {
|
realm_data.assert_is_valid_governing_token_mint(&governing_token_mint)?;
|
||||||
GoverningTokenType::Community
|
|
||||||
} else if Some(governing_token_mint) == realm_data.council_mint {
|
|
||||||
GoverningTokenType::Council
|
|
||||||
} else {
|
|
||||||
return Err(GovernanceError::InvalidGoverningTokenMint.into());
|
|
||||||
};
|
|
||||||
|
|
||||||
let amount = get_amount_from_token_account(governing_token_source_info)?;
|
let amount = get_amount_from_token_account(governing_token_source_info)?;
|
||||||
|
|
||||||
|
@ -66,13 +62,13 @@ pub fn process_deposit_governing_tokens(
|
||||||
spl_token_info,
|
spl_token_info,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let voter_record_address_seeds = get_voter_record_address_seeds(
|
let token_owner_record_address_seeds = get_token_owner_record_address_seeds(
|
||||||
realm_info.key,
|
realm_info.key,
|
||||||
&governing_token_mint,
|
&governing_token_mint,
|
||||||
governing_token_owner_info.key,
|
governing_token_owner_info.key,
|
||||||
);
|
);
|
||||||
|
|
||||||
if voter_record_info.data_is_empty() {
|
if token_owner_record_info.data_is_empty() {
|
||||||
// Deposited tokens can only be withdrawn by the owner so let's make sure the owner signed the transaction
|
// Deposited tokens can only be withdrawn by the owner so let's make sure the owner signed the transaction
|
||||||
let governing_token_owner = get_owner_from_token_account(&governing_token_source_info)?;
|
let governing_token_owner = get_owner_from_token_account(&governing_token_source_info)?;
|
||||||
|
|
||||||
|
@ -82,36 +78,38 @@ pub fn process_deposit_governing_tokens(
|
||||||
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let voter_record_data = VoterRecord {
|
let token_owner_record_data = TokenOwnerRecord {
|
||||||
account_type: GovernanceAccountType::VoterRecord,
|
account_type: GovernanceAccountType::TokenOwnerRecord,
|
||||||
realm: *realm_info.key,
|
realm: *realm_info.key,
|
||||||
token_owner: *governing_token_owner_info.key,
|
governing_token_owner: *governing_token_owner_info.key,
|
||||||
token_deposit_amount: amount,
|
governing_token_deposit_amount: amount,
|
||||||
token_type: governing_token_type,
|
governing_token_mint,
|
||||||
vote_authority: None,
|
governance_delegate: None,
|
||||||
active_votes_count: 0,
|
active_votes_count: 0,
|
||||||
total_votes_count: 0,
|
total_votes_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
create_and_serialize_account_signed(
|
create_and_serialize_account_signed(
|
||||||
payer_info,
|
payer_info,
|
||||||
voter_record_info,
|
token_owner_record_info,
|
||||||
&voter_record_data,
|
&token_owner_record_data,
|
||||||
&voter_record_address_seeds,
|
&token_owner_record_address_seeds,
|
||||||
program_id,
|
program_id,
|
||||||
system_info,
|
system_info,
|
||||||
rent,
|
rent,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let mut voter_record_data =
|
let mut token_owner_record_data = deserialize_token_owner_record(
|
||||||
deserialize_voter_record(voter_record_info, &voter_record_address_seeds)?;
|
token_owner_record_info,
|
||||||
|
&token_owner_record_address_seeds,
|
||||||
|
)?;
|
||||||
|
|
||||||
voter_record_data.token_deposit_amount = voter_record_data
|
token_owner_record_data.governing_token_deposit_amount = token_owner_record_data
|
||||||
.token_deposit_amount
|
.governing_token_deposit_amount
|
||||||
.checked_add(amount)
|
.checked_add(amount)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
voter_record_data.serialize(&mut *voter_record_info.data.borrow_mut())?;
|
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
clock::Clock,
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
state::{
|
||||||
|
enums::ProposalState, proposal::deserialize_proposal_raw,
|
||||||
|
signatory_record::deserialize_signatory_record,
|
||||||
|
token_owner_record::deserialize_token_owner_record_for_proposal_owner,
|
||||||
|
},
|
||||||
|
tools::{account::dispose_account, asserts::assert_token_owner_or_delegate_is_signer},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Processes RemoveSignatory instruction
|
||||||
|
pub fn process_remove_signatory(
|
||||||
|
_program_id: &Pubkey,
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
signatory: Pubkey,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||||
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||||
|
let governance_authority_info = next_account_info(account_info_iter)?; // 2
|
||||||
|
|
||||||
|
let signatory_record_info = next_account_info(account_info_iter)?; // 3
|
||||||
|
let beneficiary_info = next_account_info(account_info_iter)?; // 4
|
||||||
|
|
||||||
|
let clock_info = next_account_info(account_info_iter)?; // 5
|
||||||
|
let clock = Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
let mut proposal_data = deserialize_proposal_raw(proposal_info)?;
|
||||||
|
proposal_data.assert_can_edit_signatories()?;
|
||||||
|
|
||||||
|
let token_owner_record_data = deserialize_token_owner_record_for_proposal_owner(
|
||||||
|
token_owner_record_info,
|
||||||
|
&proposal_data.token_owner_record,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert_token_owner_or_delegate_is_signer(&token_owner_record_data, governance_authority_info)?;
|
||||||
|
|
||||||
|
let signatory_record_data =
|
||||||
|
deserialize_signatory_record(signatory_record_info, proposal_info.key, &signatory)?;
|
||||||
|
signatory_record_data.assert_can_remove_signatory()?;
|
||||||
|
|
||||||
|
proposal_data.signatories_count = proposal_data.signatories_count.checked_sub(1).unwrap();
|
||||||
|
|
||||||
|
// If all the remaining signatories signed already then we can start voting
|
||||||
|
if proposal_data.signatories_count > 0
|
||||||
|
&& proposal_data.signatories_signed_off_count == proposal_data.signatories_count
|
||||||
|
{
|
||||||
|
proposal_data.voting_at = Some(clock.slot);
|
||||||
|
proposal_data.state = ProposalState::Voting;
|
||||||
|
}
|
||||||
|
|
||||||
|
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
dispose_account(signatory_record_info, beneficiary_info);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
state::token_owner_record::deserialize_token_owner_record_raw,
|
||||||
|
tools::asserts::assert_token_owner_or_delegate_is_signer,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Processes SetGovernanceDelegate instruction
|
||||||
|
pub fn process_set_governance_delegate(
|
||||||
|
accounts: &[AccountInfo],
|
||||||
|
new_governance_delegate: &Option<Pubkey>,
|
||||||
|
) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let governance_authority_info = next_account_info(account_info_iter)?; // 0
|
||||||
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 1
|
||||||
|
|
||||||
|
let mut token_owner_record_data = deserialize_token_owner_record_raw(token_owner_record_info)?;
|
||||||
|
|
||||||
|
assert_token_owner_or_delegate_is_signer(&token_owner_record_data, &governance_authority_info)?;
|
||||||
|
|
||||||
|
token_owner_record_data.governance_delegate = *new_governance_delegate;
|
||||||
|
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,39 +0,0 @@
|
||||||
//! Program state processor
|
|
||||||
|
|
||||||
use borsh::BorshSerialize;
|
|
||||||
use solana_program::{
|
|
||||||
account_info::{next_account_info, AccountInfo},
|
|
||||||
entrypoint::ProgramResult,
|
|
||||||
pubkey::Pubkey,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
state::voter_record::{deserialize_voter_record, get_voter_record_address_seeds},
|
|
||||||
tools::asserts::assert_is_signed_by_owner_or_vote_authority,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Processes SetVoteAuthority instruction
|
|
||||||
pub fn process_set_vote_authority(
|
|
||||||
accounts: &[AccountInfo],
|
|
||||||
realm: &Pubkey,
|
|
||||||
governing_token_mint: &Pubkey,
|
|
||||||
governing_token_owner: &Pubkey,
|
|
||||||
new_vote_authority: &Option<Pubkey>,
|
|
||||||
) -> ProgramResult {
|
|
||||||
let account_info_iter = &mut accounts.iter();
|
|
||||||
|
|
||||||
let vote_authority_info = next_account_info(account_info_iter)?; // 0
|
|
||||||
let voter_record_info = next_account_info(account_info_iter)?; // 1
|
|
||||||
|
|
||||||
let mut voter_record_data = deserialize_voter_record(
|
|
||||||
voter_record_info,
|
|
||||||
&get_voter_record_address_seeds(realm, &governing_token_mint, governing_token_owner),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
assert_is_signed_by_owner_or_vote_authority(&voter_record_data, &vote_authority_info)?;
|
|
||||||
|
|
||||||
voter_record_data.vote_authority = *new_vote_authority;
|
|
||||||
voter_record_data.serialize(&mut *voter_record_info.data.borrow_mut())?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
//! Program state processor
|
||||||
|
|
||||||
|
use borsh::BorshSerialize;
|
||||||
|
use solana_program::{
|
||||||
|
account_info::{next_account_info, AccountInfo},
|
||||||
|
clock::Clock,
|
||||||
|
entrypoint::ProgramResult,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
sysvar::Sysvar,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::{
|
||||||
|
enums::ProposalState, proposal::deserialize_proposal_raw,
|
||||||
|
signatory_record::deserialize_signatory_record,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Processes SignOffProposal instruction
|
||||||
|
pub fn process_sign_off_proposal(_program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
|
||||||
|
let account_info_iter = &mut accounts.iter();
|
||||||
|
|
||||||
|
let proposal_info = next_account_info(account_info_iter)?; // 0
|
||||||
|
|
||||||
|
let signatory_record_info = next_account_info(account_info_iter)?; // 1
|
||||||
|
let signatory_info = next_account_info(account_info_iter)?; // 2
|
||||||
|
|
||||||
|
let clock_info = next_account_info(account_info_iter)?; // 3
|
||||||
|
let clock = Clock::from_account_info(clock_info)?;
|
||||||
|
|
||||||
|
let mut proposal_data = deserialize_proposal_raw(proposal_info)?;
|
||||||
|
proposal_data.assert_can_sign_off()?;
|
||||||
|
|
||||||
|
let mut signatory_record_data =
|
||||||
|
deserialize_signatory_record(signatory_record_info, proposal_info.key, signatory_info.key)?;
|
||||||
|
signatory_record_data.assert_can_sign_off(signatory_info)?;
|
||||||
|
|
||||||
|
signatory_record_data.signed_off = true;
|
||||||
|
signatory_record_data.serialize(&mut *signatory_record_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
if proposal_data.signatories_signed_off_count == 0 {
|
||||||
|
proposal_data.signing_off_at = Some(clock.slot);
|
||||||
|
proposal_data.state = ProposalState::SigningOff;
|
||||||
|
}
|
||||||
|
|
||||||
|
proposal_data.signatories_signed_off_count = proposal_data
|
||||||
|
.signatories_signed_off_count
|
||||||
|
.checked_add(1)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// If all Signatories signed off we can start voting
|
||||||
|
if proposal_data.signatories_signed_off_count == proposal_data.signatories_count {
|
||||||
|
proposal_data.voting_at = Some(clock.slot);
|
||||||
|
proposal_data.state = ProposalState::Voting;
|
||||||
|
}
|
||||||
|
|
||||||
|
proposal_data.serialize(&mut *proposal_info.data.borrow_mut())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -10,8 +10,10 @@ use solana_program::{
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GovernanceError,
|
error::GovernanceError,
|
||||||
state::{
|
state::{
|
||||||
realm::{deserialize_realm, get_realm_address_seeds},
|
realm::{deserialize_realm_raw, get_realm_address_seeds},
|
||||||
voter_record::{deserialize_voter_record, get_voter_record_address_seeds},
|
token_owner_record::{
|
||||||
|
deserialize_token_owner_record, get_token_owner_record_address_seeds,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
tools::token::{get_mint_from_token_account, transfer_spl_tokens_signed},
|
tools::token::{get_mint_from_token_account, transfer_spl_tokens_signed},
|
||||||
};
|
};
|
||||||
|
@ -27,26 +29,26 @@ pub fn process_withdraw_governing_tokens(
|
||||||
let governing_token_holding_info = next_account_info(account_info_iter)?; // 1
|
let governing_token_holding_info = next_account_info(account_info_iter)?; // 1
|
||||||
let governing_token_destination_info = next_account_info(account_info_iter)?; // 2
|
let governing_token_destination_info = next_account_info(account_info_iter)?; // 2
|
||||||
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
let governing_token_owner_info = next_account_info(account_info_iter)?; // 3
|
||||||
let voter_record_info = next_account_info(account_info_iter)?; // 4
|
let token_owner_record_info = next_account_info(account_info_iter)?; // 4
|
||||||
let spl_token_info = next_account_info(account_info_iter)?; // 5
|
let spl_token_info = next_account_info(account_info_iter)?; // 5
|
||||||
|
|
||||||
if !governing_token_owner_info.is_signer {
|
if !governing_token_owner_info.is_signer {
|
||||||
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
return Err(GovernanceError::GoverningTokenOwnerMustSign.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let realm_data = deserialize_realm(realm_info)?;
|
let realm_data = deserialize_realm_raw(realm_info)?;
|
||||||
let governing_token_mint = get_mint_from_token_account(governing_token_holding_info)?;
|
let governing_token_mint = get_mint_from_token_account(governing_token_holding_info)?;
|
||||||
|
|
||||||
let voter_record_address_seeds = get_voter_record_address_seeds(
|
let token_owner_record_address_seeds = get_token_owner_record_address_seeds(
|
||||||
realm_info.key,
|
realm_info.key,
|
||||||
&governing_token_mint,
|
&governing_token_mint,
|
||||||
governing_token_owner_info.key,
|
governing_token_owner_info.key,
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut voter_record_data =
|
let mut token_owner_record_data =
|
||||||
deserialize_voter_record(voter_record_info, &voter_record_address_seeds)?;
|
deserialize_token_owner_record(token_owner_record_info, &token_owner_record_address_seeds)?;
|
||||||
|
|
||||||
if voter_record_data.active_votes_count > 0 {
|
if token_owner_record_data.active_votes_count > 0 {
|
||||||
return Err(GovernanceError::CannotWithdrawGoverningTokensWhenActiveVotesExist.into());
|
return Err(GovernanceError::CannotWithdrawGoverningTokensWhenActiveVotesExist.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,12 +58,12 @@ pub fn process_withdraw_governing_tokens(
|
||||||
&realm_info,
|
&realm_info,
|
||||||
&get_realm_address_seeds(&realm_data.name),
|
&get_realm_address_seeds(&realm_data.name),
|
||||||
program_id,
|
program_id,
|
||||||
voter_record_data.token_deposit_amount,
|
token_owner_record_data.governing_token_deposit_amount,
|
||||||
spl_token_info,
|
spl_token_info,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
voter_record_data.token_deposit_amount = 0;
|
token_owner_record_data.governing_token_deposit_amount = 0;
|
||||||
voter_record_data.serialize(&mut *voter_record_info.data.borrow_mut())?;
|
token_owner_record_data.serialize(&mut *token_owner_record_info.data.borrow_mut())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ pub enum GovernanceAccountType {
|
||||||
/// 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,
|
Realm,
|
||||||
|
|
||||||
/// Voter record for each voter and given governing token type within a Realm
|
/// Token Owner Record for given governing token owner within a Realm
|
||||||
VoterRecord,
|
TokenOwnerRecord,
|
||||||
|
|
||||||
/// Generic Account Governance account
|
/// Generic Account Governance account
|
||||||
AccountGovernance,
|
AccountGovernance,
|
||||||
|
@ -24,6 +24,9 @@ pub enum GovernanceAccountType {
|
||||||
/// 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
|
||||||
Proposal,
|
Proposal,
|
||||||
|
|
||||||
|
/// Proposal Signatory account
|
||||||
|
SignatoryRecord,
|
||||||
|
|
||||||
/// 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
|
||||||
ProposalVoteRecord,
|
ProposalVoteRecord,
|
||||||
|
|
||||||
|
@ -48,16 +51,6 @@ pub enum VoteWeight {
|
||||||
No(u64),
|
No(u64),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Governing Token type
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
|
||||||
pub enum GoverningTokenType {
|
|
||||||
/// Community token
|
|
||||||
Community,
|
|
||||||
/// Council token
|
|
||||||
Council,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What state a Proposal is in
|
/// What state a Proposal is in
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
|
@ -65,8 +58,9 @@ pub enum ProposalState {
|
||||||
/// Draft - Proposal enters Draft state when it's created
|
/// Draft - Proposal enters Draft state when it's created
|
||||||
Draft,
|
Draft,
|
||||||
|
|
||||||
/// Signing - The Proposal is being signed by Signatories. Proposal enters the state when first Signatory Sings and leaves it when last Signatory signs
|
/// SigningOff - The Proposal is being signed off by Signatories
|
||||||
Signing,
|
/// Proposal enters the state when first Signatory Sings and leaves it when last Signatory signs
|
||||||
|
SigningOff,
|
||||||
|
|
||||||
/// Taking votes
|
/// Taking votes
|
||||||
Voting,
|
Voting,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
//! Governance Account
|
//! Governance Account
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
error::GovernanceError, id, state::enums::GovernanceAccountType, tools::account::AccountMaxSize,
|
error::GovernanceError, id, state::enums::GovernanceAccountType,
|
||||||
|
tools::account::deserialize_account, tools::account::AccountMaxSize,
|
||||||
};
|
};
|
||||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
use solana_program::{
|
use solana_program::{
|
||||||
|
@ -46,7 +47,7 @@ pub struct Governance {
|
||||||
pub config: GovernanceConfig,
|
pub config: GovernanceConfig,
|
||||||
|
|
||||||
/// Running count of proposals
|
/// Running count of proposals
|
||||||
pub proposal_count: u32,
|
pub proposals_count: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountMaxSize for Governance {}
|
impl AccountMaxSize for Governance {}
|
||||||
|
@ -58,6 +59,13 @@ impl IsInitialized for Governance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Deserializes account and checks owner program
|
||||||
|
pub fn deserialize_governance_raw(
|
||||||
|
governance_info: &AccountInfo,
|
||||||
|
) -> Result<Governance, ProgramError> {
|
||||||
|
deserialize_account::<Governance>(governance_info, &id())
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns ProgramGovernance PDA seeds
|
/// Returns ProgramGovernance PDA seeds
|
||||||
pub fn get_program_governance_address_seeds<'a>(
|
pub fn get_program_governance_address_seeds<'a>(
|
||||||
realm: &'a Pubkey,
|
realm: &'a Pubkey,
|
||||||
|
|
|
@ -5,5 +5,6 @@ pub mod governance;
|
||||||
pub mod proposal;
|
pub mod proposal;
|
||||||
pub mod proposal_vote_record;
|
pub mod proposal_vote_record;
|
||||||
pub mod realm;
|
pub mod realm;
|
||||||
|
pub mod signatory_record;
|
||||||
pub mod single_signer_instruction;
|
pub mod single_signer_instruction;
|
||||||
pub mod voter_record;
|
pub mod token_owner_record;
|
||||||
|
|
|
@ -1,8 +1,18 @@
|
||||||
//! Proposal Account
|
//! Proposal Account
|
||||||
|
|
||||||
use solana_program::{epoch_schedule::Slot, pubkey::Pubkey};
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, epoch_schedule::Slot, program_error::ProgramError,
|
||||||
|
program_pack::IsInitialized, pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
use super::enums::{GovernanceAccountType, GoverningTokenType, ProposalState};
|
use crate::{
|
||||||
|
error::GovernanceError,
|
||||||
|
id,
|
||||||
|
tools::account::{deserialize_account, AccountMaxSize},
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::enums::{GovernanceAccountType, ProposalState};
|
||||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
|
|
||||||
/// Governance Proposal
|
/// Governance Proposal
|
||||||
|
@ -15,23 +25,21 @@ pub struct Proposal {
|
||||||
/// Governance account the Proposal belongs to
|
/// Governance account the Proposal belongs to
|
||||||
pub governance: Pubkey,
|
pub governance: Pubkey,
|
||||||
|
|
||||||
/// Mint that creates signatory tokens of this Proposal
|
|
||||||
/// If there are outstanding signatory tokens, then cannot leave draft state. Signatories must burn tokens (ie agree
|
|
||||||
/// to move instruction to voting state) and bring mint to net 0 tokens outstanding. Each signatory gets 1 (serves as flag)
|
|
||||||
pub signatory_mint: Pubkey,
|
|
||||||
|
|
||||||
/// Admin ownership mint. One token is minted, can be used to grant admin status to a new person
|
|
||||||
pub admin_mint: Pubkey,
|
|
||||||
|
|
||||||
/// Indicates which Governing Token is used to vote on the Proposal
|
/// Indicates which Governing Token is used to vote on the Proposal
|
||||||
/// Whether the general Community token owners or the Council tokens owners vote on this Proposal
|
/// Whether the general Community token owners or the Council tokens owners vote on this Proposal
|
||||||
pub voting_token_type: GoverningTokenType,
|
pub governing_token_mint: Pubkey,
|
||||||
|
|
||||||
/// Current state of the proposal
|
/// Current proposal state
|
||||||
pub state: ProposalState,
|
pub state: ProposalState,
|
||||||
|
|
||||||
/// Total signatory tokens minted, for use comparing to supply remaining during draft period
|
/// The TokenOwnerRecord representing the user who created and owns this Proposal
|
||||||
pub total_signatory_tokens_minted: u64,
|
pub token_owner_record: Pubkey,
|
||||||
|
|
||||||
|
/// The number of signatories assigned to the Proposal
|
||||||
|
pub signatories_count: u8,
|
||||||
|
|
||||||
|
/// The number of signatories who already signed
|
||||||
|
pub signatories_signed_off_count: u8,
|
||||||
|
|
||||||
/// Link to proposal's description
|
/// Link to proposal's description
|
||||||
pub description_link: String,
|
pub description_link: String,
|
||||||
|
@ -39,27 +47,208 @@ pub struct Proposal {
|
||||||
/// Proposal name
|
/// Proposal name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
/// When the Proposal ended voting - this will also be when the set was defeated or began executing naturally
|
/// When the Proposal was created and entered Draft state
|
||||||
pub voting_ended_at: Option<Slot>,
|
pub draft_at: Slot,
|
||||||
|
|
||||||
|
/// When Signatories started signing off the Proposal
|
||||||
|
pub signing_off_at: Option<Slot>,
|
||||||
|
|
||||||
/// When the Proposal began voting
|
/// When the Proposal began voting
|
||||||
pub voting_began_at: Option<Slot>,
|
pub voting_at: Option<Slot>,
|
||||||
|
|
||||||
/// when the Proposal entered draft state
|
/// When the Proposal ended voting and entered either Succeeded or Defeated
|
||||||
pub created_at: Option<Slot>,
|
pub voting_completed_at: Option<Slot>,
|
||||||
|
|
||||||
/// when the Proposal entered completed state, also when execution ended naturally.
|
/// When the Proposal entered Executing state
|
||||||
pub completed_at: Option<Slot>,
|
pub executing_at: Option<Slot>,
|
||||||
|
|
||||||
/// when the Proposal entered deleted state
|
/// When the Proposal entered final state Completed or Cancelled and was closed
|
||||||
pub deleted_at: Option<Slot>,
|
pub closed_at: Option<Slot>,
|
||||||
|
|
||||||
/// The number of the instructions already executed
|
/// The number of the instructions already executed
|
||||||
pub number_of_executed_instructions: u8,
|
pub number_of_executed_instructions: u8,
|
||||||
|
|
||||||
/// The number of instructions included in the proposal
|
/// The number of instructions included in the proposal
|
||||||
pub number_of_instructions: u8,
|
pub number_of_instructions: u8,
|
||||||
|
}
|
||||||
/// Array of pubkeys pointing at Proposal instructions, up to 5
|
|
||||||
pub instruction: Vec<Pubkey>,
|
impl AccountMaxSize for Proposal {
|
||||||
|
fn get_max_size(&self) -> Option<usize> {
|
||||||
|
Some(self.name.len() + self.description_link.len() + 163)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsInitialized for Proposal {
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.account_type == GovernanceAccountType::Proposal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Proposal {
|
||||||
|
/// Checks if Signatories can be edited (added or removed) for the Proposal in the given state
|
||||||
|
pub fn assert_can_edit_signatories(&self) -> Result<(), ProgramError> {
|
||||||
|
if !(self.state == ProposalState::Draft || self.state == ProposalState::SigningOff) {
|
||||||
|
return Err(GovernanceError::InvalidStateCannotEditSignatories.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if Proposal can be singed off
|
||||||
|
pub fn assert_can_sign_off(&self) -> Result<(), ProgramError> {
|
||||||
|
if !(self.state == ProposalState::Draft || self.state == ProposalState::SigningOff) {
|
||||||
|
return Err(GovernanceError::InvalidStateCannotSignOff.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes Proposal account and checks owner program
|
||||||
|
pub fn deserialize_proposal_raw(proposal_info: &AccountInfo) -> Result<Proposal, ProgramError> {
|
||||||
|
deserialize_account::<Proposal>(proposal_info, &id())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns Proposal PDA seeds
|
||||||
|
pub fn get_proposal_address_seeds<'a>(
|
||||||
|
governance: &'a Pubkey,
|
||||||
|
governing_token_mint: &'a Pubkey,
|
||||||
|
proposal_index_le_bytes: &'a [u8],
|
||||||
|
) -> [&'a [u8]; 4] {
|
||||||
|
[
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
governance.as_ref(),
|
||||||
|
governing_token_mint.as_ref(),
|
||||||
|
&proposal_index_le_bytes,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns Proposal PDA address
|
||||||
|
pub fn get_proposal_address<'a>(
|
||||||
|
governance: &'a Pubkey,
|
||||||
|
governing_token_mint: &'a Pubkey,
|
||||||
|
proposal_index_bytes: &'a [u8],
|
||||||
|
) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&get_proposal_address_seeds(governance, governing_token_mint, &proposal_index_bytes),
|
||||||
|
&id(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use {super::*, proptest::prelude::*};
|
||||||
|
|
||||||
|
fn create_test_proposal() -> Proposal {
|
||||||
|
Proposal {
|
||||||
|
account_type: GovernanceAccountType::TokenOwnerRecord,
|
||||||
|
governance: Pubkey::new_unique(),
|
||||||
|
governing_token_mint: Pubkey::new_unique(),
|
||||||
|
state: ProposalState::Draft,
|
||||||
|
token_owner_record: Pubkey::new_unique(),
|
||||||
|
signatories_count: 10,
|
||||||
|
signatories_signed_off_count: 5,
|
||||||
|
description_link: "This is my description".to_string(),
|
||||||
|
name: "This is my name".to_string(),
|
||||||
|
draft_at: 10,
|
||||||
|
signing_off_at: Some(10),
|
||||||
|
voting_at: Some(10),
|
||||||
|
voting_completed_at: Some(10),
|
||||||
|
executing_at: Some(10),
|
||||||
|
closed_at: Some(10),
|
||||||
|
number_of_executed_instructions: 10,
|
||||||
|
number_of_instructions: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_size() {
|
||||||
|
let proposal = create_test_proposal();
|
||||||
|
let size = proposal.try_to_vec().unwrap().len();
|
||||||
|
|
||||||
|
assert_eq!(proposal.get_max_size(), Some(size));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn editable_signatory_states() -> impl Strategy<Value = ProposalState> {
|
||||||
|
prop_oneof![Just(ProposalState::Draft), Just(ProposalState::SigningOff),]
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_assert_can_edit_signatories(state in editable_signatory_states()) {
|
||||||
|
|
||||||
|
let mut proposal = create_test_proposal();
|
||||||
|
proposal.state = state;
|
||||||
|
proposal.assert_can_edit_signatories().unwrap();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none_editable_signatory_states() -> impl Strategy<Value = ProposalState> {
|
||||||
|
prop_oneof![
|
||||||
|
Just(ProposalState::Voting),
|
||||||
|
Just(ProposalState::Succeeded),
|
||||||
|
Just(ProposalState::Executing),
|
||||||
|
Just(ProposalState::Completed),
|
||||||
|
Just(ProposalState::Cancelled),
|
||||||
|
Just(ProposalState::Defeated),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_assert_can_edit_signatories_with_invalid_state_error(state in none_editable_signatory_states()) {
|
||||||
|
// Arrange
|
||||||
|
let mut proposal = create_test_proposal();
|
||||||
|
proposal.state = state;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = proposal.assert_can_edit_signatories().err().unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(err, GovernanceError::InvalidStateCannotEditSignatories.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sign_off_states() -> impl Strategy<Value = ProposalState> {
|
||||||
|
prop_oneof![Just(ProposalState::SigningOff), Just(ProposalState::Draft),]
|
||||||
|
}
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_assert_can_sign_off(state in sign_off_states()) {
|
||||||
|
let mut proposal = create_test_proposal();
|
||||||
|
proposal.state = state;
|
||||||
|
proposal.assert_can_sign_off().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn none_sign_off_states() -> impl Strategy<Value = ProposalState> {
|
||||||
|
prop_oneof![
|
||||||
|
Just(ProposalState::Voting),
|
||||||
|
Just(ProposalState::Succeeded),
|
||||||
|
Just(ProposalState::Executing),
|
||||||
|
Just(ProposalState::Completed),
|
||||||
|
Just(ProposalState::Cancelled),
|
||||||
|
Just(ProposalState::Defeated),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
proptest! {
|
||||||
|
#[test]
|
||||||
|
fn test_assert_can_sign_off_with_state_error(state in none_sign_off_states()) {
|
||||||
|
// Arrange
|
||||||
|
let mut proposal = create_test_proposal();
|
||||||
|
proposal.state = state;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = proposal.assert_can_sign_off().err().unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(err, GovernanceError::InvalidStateCannotSignOff.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
use solana_program::pubkey::Pubkey;
|
use solana_program::pubkey::Pubkey;
|
||||||
|
|
||||||
use super::enums::{GovernanceAccountType, VoteWeight};
|
use crate::state::enums::{GovernanceAccountType, VoteWeight};
|
||||||
|
|
||||||
/// Proposal Vote Record
|
/// Proposal Vote Record
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
|
@ -7,12 +7,13 @@ use solana_program::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::GovernanceError,
|
||||||
id,
|
id,
|
||||||
tools::account::{assert_is_valid_account, deserialize_account, AccountMaxSize},
|
tools::account::{assert_is_valid_account, deserialize_account, AccountMaxSize},
|
||||||
PROGRAM_AUTHORITY_SEED,
|
PROGRAM_AUTHORITY_SEED,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::enums::GovernanceAccountType;
|
use crate::state::enums::GovernanceAccountType;
|
||||||
|
|
||||||
/// Governance Realm Account
|
/// Governance Realm Account
|
||||||
/// Account PDA seeds" ['governance', name]
|
/// Account PDA seeds" ['governance', name]
|
||||||
|
@ -40,13 +41,31 @@ impl IsInitialized for Realm {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Realm {
|
||||||
|
/// Asserts the given mint is either Community or Council mint of the Realm
|
||||||
|
pub fn assert_is_valid_governing_token_mint(
|
||||||
|
&self,
|
||||||
|
governing_token_mint: &Pubkey,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
if self.community_mint == *governing_token_mint {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.council_mint == Some(*governing_token_mint) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(GovernanceError::InvalidGoverningTokenMint.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks whether realm account exists, is initialized and owned by Governance program
|
/// Checks whether realm account exists, is initialized and owned by Governance program
|
||||||
pub fn assert_is_valid_realm(realm_info: &AccountInfo) -> Result<(), ProgramError> {
|
pub fn assert_is_valid_realm(realm_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||||
assert_is_valid_account(realm_info, GovernanceAccountType::Realm, &id())
|
assert_is_valid_account(realm_info, GovernanceAccountType::Realm, &id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deserializes account and checks owner program
|
/// Deserializes account and checks owner program
|
||||||
pub fn deserialize_realm(realm_info: &AccountInfo) -> Result<Realm, ProgramError> {
|
pub fn deserialize_realm_raw(realm_info: &AccountInfo) -> Result<Realm, ProgramError> {
|
||||||
deserialize_account::<Realm>(realm_info, &id())
|
deserialize_account::<Realm>(realm_info, &id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
//! Signatory Record
|
||||||
|
|
||||||
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::GovernanceError,
|
||||||
|
id,
|
||||||
|
tools::account::{deserialize_account, AccountMaxSize},
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::enums::GovernanceAccountType;
|
||||||
|
|
||||||
|
/// Account PDA seeds: ['governance', proposal, signatory]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
|
pub struct SignatoryRecord {
|
||||||
|
/// 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 AccountMaxSize for SignatoryRecord {}
|
||||||
|
|
||||||
|
impl IsInitialized for SignatoryRecord {
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.account_type == GovernanceAccountType::SignatoryRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignatoryRecord {
|
||||||
|
/// Checks signatory hasn't signed off yet and is transaction signer
|
||||||
|
pub fn assert_can_sign_off(&self, signatory_info: &AccountInfo) -> Result<(), ProgramError> {
|
||||||
|
if self.signed_off {
|
||||||
|
return Err(GovernanceError::SignatoryAlreadySignedOff.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if !signatory_info.is_signer {
|
||||||
|
return Err(GovernanceError::SignatoryMustSign.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks signatory can be removed from Proposal
|
||||||
|
pub fn assert_can_remove_signatory(&self) -> Result<(), ProgramError> {
|
||||||
|
if self.signed_off {
|
||||||
|
return Err(GovernanceError::SignatoryAlreadySignedOff.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns SignatoryRecord PDA seeds
|
||||||
|
pub fn get_signatory_record_address_seeds<'a>(
|
||||||
|
proposal: &'a Pubkey,
|
||||||
|
signatory: &'a Pubkey,
|
||||||
|
) -> [&'a [u8]; 3] {
|
||||||
|
[
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
proposal.as_ref(),
|
||||||
|
signatory.as_ref(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns SignatoryRecord PDA address
|
||||||
|
pub fn get_signatory_record_address<'a>(proposal: &'a Pubkey, signatory: &'a Pubkey) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&get_signatory_record_address_seeds(proposal, signatory),
|
||||||
|
&id(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes SignatoryRecord account and checks owner program
|
||||||
|
pub fn deserialize_signatory_record_raw(
|
||||||
|
signatory_record_info: &AccountInfo,
|
||||||
|
) -> Result<SignatoryRecord, ProgramError> {
|
||||||
|
deserialize_account::<SignatoryRecord>(signatory_record_info, &id())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes SignatoryRecord and validates its PDA
|
||||||
|
pub fn deserialize_signatory_record(
|
||||||
|
signatory_record_info: &AccountInfo,
|
||||||
|
proposal: &Pubkey,
|
||||||
|
signatory: &Pubkey,
|
||||||
|
) -> Result<SignatoryRecord, ProgramError> {
|
||||||
|
let (signatory_record_address, _) = Pubkey::find_program_address(
|
||||||
|
&get_signatory_record_address_seeds(proposal, signatory),
|
||||||
|
&id(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if signatory_record_address != *signatory_record_info.key {
|
||||||
|
return Err(GovernanceError::InvalidSignatoryAddress.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize_signatory_record_raw(signatory_record_info)
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
//! SingleSignerInstruction Account
|
//! SingleSignerInstruction Account
|
||||||
|
|
||||||
use super::enums::GovernanceAccountType;
|
use crate::state::enums::GovernanceAccountType;
|
||||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
|
|
||||||
/// Account for an instruction to be executed for Proposal
|
/// Account for an instruction to be executed for Proposal
|
||||||
|
|
|
@ -0,0 +1,166 @@
|
||||||
|
//! Token Owner Record Account
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error::GovernanceError,
|
||||||
|
id,
|
||||||
|
tools::account::{deserialize_account, AccountMaxSize},
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::state::enums::GovernanceAccountType;
|
||||||
|
|
||||||
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
|
use solana_program::{
|
||||||
|
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Governance Token Owner Record
|
||||||
|
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
|
pub struct TokenOwnerRecord {
|
||||||
|
/// 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
|
||||||
|
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,
|
||||||
|
|
||||||
|
/// A single account that is allowed to operate governance with the deposited governing tokens
|
||||||
|
/// It's delegated to by the governing token owner or current governance_delegate
|
||||||
|
pub governance_delegate: Option<Pubkey>,
|
||||||
|
|
||||||
|
/// The number of active votes cast by TokenOwner
|
||||||
|
pub active_votes_count: u16,
|
||||||
|
|
||||||
|
/// The total number of votes cast by the TokenOwner
|
||||||
|
pub total_votes_count: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountMaxSize for TokenOwnerRecord {
|
||||||
|
fn get_max_size(&self) -> Option<usize> {
|
||||||
|
Some(142)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IsInitialized for TokenOwnerRecord {
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.account_type == GovernanceAccountType::TokenOwnerRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns TokenOwnerRecord PDA address
|
||||||
|
pub fn get_token_owner_record_address(
|
||||||
|
realm: &Pubkey,
|
||||||
|
governing_token_mint: &Pubkey,
|
||||||
|
governing_token_owner: &Pubkey,
|
||||||
|
) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&get_token_owner_record_address_seeds(realm, governing_token_mint, governing_token_owner),
|
||||||
|
&id(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns TokenOwnerRecord PDA seeds
|
||||||
|
pub fn get_token_owner_record_address_seeds<'a>(
|
||||||
|
realm: &'a Pubkey,
|
||||||
|
governing_token_mint: &'a Pubkey,
|
||||||
|
governing_token_owner: &'a Pubkey,
|
||||||
|
) -> [&'a [u8]; 4] {
|
||||||
|
[
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
realm.as_ref(),
|
||||||
|
governing_token_mint.as_ref(),
|
||||||
|
governing_token_owner.as_ref(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes TokenOwnerRecord account and checks owner program
|
||||||
|
pub fn deserialize_token_owner_record_raw(
|
||||||
|
token_owner_record_info: &AccountInfo,
|
||||||
|
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||||
|
deserialize_account::<TokenOwnerRecord>(token_owner_record_info, &id())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes TokenOwnerRecord account and checks its PDA against the provided seeds
|
||||||
|
pub fn deserialize_token_owner_record(
|
||||||
|
token_owner_record_info: &AccountInfo,
|
||||||
|
token_owner_record_seeds: &[&[u8]],
|
||||||
|
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||||
|
let (token_owner_record_address, _) =
|
||||||
|
Pubkey::find_program_address(token_owner_record_seeds, &id());
|
||||||
|
|
||||||
|
if token_owner_record_address != *token_owner_record_info.key {
|
||||||
|
return Err(GovernanceError::InvalidTokenOwnerRecordAccountAddress.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize_token_owner_record_raw(token_owner_record_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes TokenOwnerRecord account and checks that its PDA matches the given realm and governing mint
|
||||||
|
pub fn deserialize_token_owner_record_for_realm_and_governing_mint(
|
||||||
|
token_owner_record_info: &AccountInfo,
|
||||||
|
realm: &Pubkey,
|
||||||
|
governing_token_mint: &Pubkey,
|
||||||
|
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||||
|
let token_owner_record_data = deserialize_token_owner_record_raw(token_owner_record_info)?;
|
||||||
|
|
||||||
|
if token_owner_record_data.governing_token_mint != *governing_token_mint {
|
||||||
|
return Err(GovernanceError::InvalidTokenOwnerRecordGoverningMint.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if token_owner_record_data.realm != *realm {
|
||||||
|
return Err(GovernanceError::InvalidTokenOwnerRecordRealm.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(token_owner_record_data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deserializes TokenOwnerRecord account and checks its address is the give proposal_owner
|
||||||
|
pub fn deserialize_token_owner_record_for_proposal_owner(
|
||||||
|
token_owner_record_info: &AccountInfo,
|
||||||
|
proposal_owner: &Pubkey,
|
||||||
|
) -> Result<TokenOwnerRecord, ProgramError> {
|
||||||
|
if token_owner_record_info.key != proposal_owner {
|
||||||
|
return Err(GovernanceError::InvalidProposalOwnerAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
deserialize_token_owner_record_raw(token_owner_record_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use solana_program::borsh::get_packed_len;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_max_size() {
|
||||||
|
let token_owner_record = TokenOwnerRecord {
|
||||||
|
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,
|
||||||
|
governance_delegate: Some(Pubkey::new_unique()),
|
||||||
|
active_votes_count: 1,
|
||||||
|
total_votes_count: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let size = get_packed_len::<TokenOwnerRecord>();
|
||||||
|
|
||||||
|
assert_eq!(token_owner_record.get_max_size(), Some(size));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
//! Proposal Vote Record Account
|
||||||
|
|
||||||
|
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
||||||
|
use solana_program::{program_pack::IsInitialized, pubkey::Pubkey};
|
||||||
|
|
||||||
|
use crate::{id, tools::account::AccountMaxSize, PROGRAM_AUTHORITY_SEED};
|
||||||
|
|
||||||
|
use crate::state::enums::{GovernanceAccountType, VoteWeight};
|
||||||
|
|
||||||
|
/// Proposal VoteRecord
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
||||||
|
pub struct VoteRecord {
|
||||||
|
/// Governance account type
|
||||||
|
pub account_type: GovernanceAccountType,
|
||||||
|
|
||||||
|
/// Proposal account
|
||||||
|
pub proposal: Pubkey,
|
||||||
|
|
||||||
|
/// The user who casted this vote
|
||||||
|
/// This is the Governing Token Owner who deposited governing tokens into the Realm
|
||||||
|
pub governing_token_owner: Pubkey,
|
||||||
|
|
||||||
|
/// Voter's vote: Yes/No and amount
|
||||||
|
pub vote_weight: VoteWeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountMaxSize for VoteRecord {}
|
||||||
|
|
||||||
|
impl IsInitialized for VoteRecord {
|
||||||
|
fn is_initialized(&self) -> bool {
|
||||||
|
self.account_type == GovernanceAccountType::VoteRecord
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns VoteRecord PDA seeds
|
||||||
|
pub fn get_vote_record_address_seeds<'a>(
|
||||||
|
proposal: &'a Pubkey,
|
||||||
|
token_owner_record: &'a Pubkey,
|
||||||
|
) -> [&'a [u8]; 3] {
|
||||||
|
[
|
||||||
|
PROGRAM_AUTHORITY_SEED,
|
||||||
|
proposal.as_ref(),
|
||||||
|
token_owner_record.as_ref(),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns VoteRecord PDA address
|
||||||
|
pub fn get_vote_record_address<'a>(proposal: &'a Pubkey, token_owner_record: &'a Pubkey) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&get_vote_record_address_seeds(proposal, token_owner_record),
|
||||||
|
&id(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
|
@ -1,126 +0,0 @@
|
||||||
//! Voter Record Account
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
error::GovernanceError,
|
|
||||||
id,
|
|
||||||
tools::account::{deserialize_account, AccountMaxSize},
|
|
||||||
PROGRAM_AUTHORITY_SEED,
|
|
||||||
};
|
|
||||||
|
|
||||||
use super::enums::{GovernanceAccountType, GoverningTokenType};
|
|
||||||
use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
|
|
||||||
use solana_program::{
|
|
||||||
account_info::AccountInfo, program_error::ProgramError, program_pack::IsInitialized,
|
|
||||||
pubkey::Pubkey,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Governance Voter Record
|
|
||||||
/// Account PDA seeds: ['governance', realm, token_mint, token_owner ]
|
|
||||||
#[repr(C)]
|
|
||||||
#[derive(Clone, Debug, PartialEq, BorshDeserialize, BorshSerialize, BorshSchema)]
|
|
||||||
pub struct VoterRecord {
|
|
||||||
/// Governance account type
|
|
||||||
pub account_type: GovernanceAccountType,
|
|
||||||
|
|
||||||
/// The Realm the VoterRecord belongs to
|
|
||||||
pub realm: Pubkey,
|
|
||||||
|
|
||||||
/// The type of the Governing Token the VoteRecord is for
|
|
||||||
pub token_type: GoverningTokenType,
|
|
||||||
|
|
||||||
/// The owner (either single or multisig) of the deposited governing SPL Tokens
|
|
||||||
/// This is who can authorize a withdrawal
|
|
||||||
pub token_owner: Pubkey,
|
|
||||||
|
|
||||||
/// The amount of governing tokens deposited into the Realm
|
|
||||||
/// This amount is the voter weight used when voting on proposals
|
|
||||||
pub token_deposit_amount: u64,
|
|
||||||
|
|
||||||
/// A single account that is allowed to operate governance with the deposited governing tokens
|
|
||||||
/// It's delegated to by the governing token owner or current vote_authority
|
|
||||||
pub vote_authority: Option<Pubkey>,
|
|
||||||
|
|
||||||
/// The number of active votes cast by voter
|
|
||||||
pub active_votes_count: u8,
|
|
||||||
|
|
||||||
/// The total number of votes cast by the voter
|
|
||||||
pub total_votes_count: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AccountMaxSize for VoterRecord {
|
|
||||||
fn get_max_size(&self) -> Option<usize> {
|
|
||||||
Some(109)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IsInitialized for VoterRecord {
|
|
||||||
fn is_initialized(&self) -> bool {
|
|
||||||
self.account_type == GovernanceAccountType::VoterRecord
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns VoteRecord PDA address
|
|
||||||
pub fn get_voter_record_address(
|
|
||||||
realm: &Pubkey,
|
|
||||||
governing_token_mint: &Pubkey,
|
|
||||||
governing_token_owner: &Pubkey,
|
|
||||||
) -> Pubkey {
|
|
||||||
Pubkey::find_program_address(
|
|
||||||
&get_voter_record_address_seeds(realm, governing_token_mint, governing_token_owner),
|
|
||||||
&id(),
|
|
||||||
)
|
|
||||||
.0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns VoterRecord PDA seeds
|
|
||||||
pub fn get_voter_record_address_seeds<'a>(
|
|
||||||
realm: &'a Pubkey,
|
|
||||||
governing_token_mint: &'a Pubkey,
|
|
||||||
governing_token_owner: &'a Pubkey,
|
|
||||||
) -> [&'a [u8]; 4] {
|
|
||||||
[
|
|
||||||
PROGRAM_AUTHORITY_SEED,
|
|
||||||
realm.as_ref(),
|
|
||||||
governing_token_mint.as_ref(),
|
|
||||||
governing_token_owner.as_ref(),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deserializes VoterRecord and checks account PDA and owner program
|
|
||||||
pub fn deserialize_voter_record(
|
|
||||||
voter_record_info: &AccountInfo,
|
|
||||||
voter_record_seeds: &[&[u8]],
|
|
||||||
) -> Result<VoterRecord, ProgramError> {
|
|
||||||
let (voter_record_address, _) = Pubkey::find_program_address(voter_record_seeds, &id());
|
|
||||||
|
|
||||||
if voter_record_address != *voter_record_info.key {
|
|
||||||
return Err(GovernanceError::InvalidVoterAccountAddress.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
deserialize_account::<VoterRecord>(voter_record_info, &id())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use solana_program::borsh::get_packed_len;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_max_size() {
|
|
||||||
let vote_record = VoterRecord {
|
|
||||||
account_type: GovernanceAccountType::VoterRecord,
|
|
||||||
realm: Pubkey::new_unique(),
|
|
||||||
token_type: GoverningTokenType::Community,
|
|
||||||
token_owner: Pubkey::new_unique(),
|
|
||||||
token_deposit_amount: 10,
|
|
||||||
vote_authority: Some(Pubkey::new_unique()),
|
|
||||||
active_votes_count: 1,
|
|
||||||
total_votes_count: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
let size = get_packed_len::<VoterRecord>();
|
|
||||||
|
|
||||||
assert_eq!(vote_record.get_max_size(), Some(size));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -88,13 +88,13 @@ pub fn deserialize_account<T: BorshDeserialize + IsInitialized>(
|
||||||
account_info: &AccountInfo,
|
account_info: &AccountInfo,
|
||||||
owner_program_id: &Pubkey,
|
owner_program_id: &Pubkey,
|
||||||
) -> Result<T, ProgramError> {
|
) -> Result<T, ProgramError> {
|
||||||
|
if account_info.data_is_empty() {
|
||||||
|
return Err(ProgramError::UninitializedAccount);
|
||||||
|
}
|
||||||
if account_info.owner != owner_program_id {
|
if account_info.owner != owner_program_id {
|
||||||
return Err(GovernanceError::InvalidAccountOwner.into());
|
return Err(GovernanceError::InvalidAccountOwner.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if account_info.data_is_empty() {
|
|
||||||
return Err(ProgramError::UninitializedAccount);
|
|
||||||
}
|
|
||||||
let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
|
let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
|
||||||
if !account.is_initialized() {
|
if !account.is_initialized() {
|
||||||
Err(ProgramError::UninitializedAccount)
|
Err(ProgramError::UninitializedAccount)
|
||||||
|
@ -125,3 +125,19 @@ pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disposes account by transferring its lamports to the beneficiary account and zeros its data
|
||||||
|
// After transaction completes the runtime would remove the account with no lamports
|
||||||
|
pub fn dispose_account(account_info: &AccountInfo, beneficiary_account: &AccountInfo) {
|
||||||
|
let account_lamports = account_info.lamports();
|
||||||
|
**account_info.lamports.borrow_mut() = 0;
|
||||||
|
|
||||||
|
**beneficiary_account.lamports.borrow_mut() = beneficiary_account
|
||||||
|
.lamports()
|
||||||
|
.checked_add(account_lamports)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut account_data = account_info.data.borrow_mut();
|
||||||
|
|
||||||
|
account_data.fill(0);
|
||||||
|
}
|
||||||
|
|
|
@ -2,24 +2,24 @@
|
||||||
|
|
||||||
use solana_program::{account_info::AccountInfo, program_error::ProgramError};
|
use solana_program::{account_info::AccountInfo, program_error::ProgramError};
|
||||||
|
|
||||||
use crate::{error::GovernanceError, state::voter_record::VoterRecord};
|
use crate::{error::GovernanceError, state::token_owner_record::TokenOwnerRecord};
|
||||||
|
|
||||||
/// Checks whether the provided vote authority can set new vote authority
|
/// Checks whether the provided Governance Authority signed transaction
|
||||||
pub fn assert_is_signed_by_owner_or_vote_authority(
|
pub fn assert_token_owner_or_delegate_is_signer(
|
||||||
voter_record: &VoterRecord,
|
token_owner_record: &TokenOwnerRecord,
|
||||||
vote_authority_info: &AccountInfo,
|
governance_authority_info: &AccountInfo,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
if vote_authority_info.is_signer {
|
if governance_authority_info.is_signer {
|
||||||
if &voter_record.token_owner == vote_authority_info.key {
|
if &token_owner_record.governing_token_owner == governance_authority_info.key {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(vote_authority) = voter_record.vote_authority {
|
if let Some(governance_delegate) = token_owner_record.governance_delegate {
|
||||||
if &vote_authority == vote_authority_info.key {
|
if &governance_delegate == governance_authority_info.key {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(GovernanceError::GoverningTokenOwnerOrVoteAuthrotiyMustSign.into())
|
Err(GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into())
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,26 +72,24 @@ pub fn assert_program_upgrade_authority_is_signer(
|
||||||
return Err(GovernanceError::InvalidProgramDataAccountAddress.into());
|
return Err(GovernanceError::InvalidProgramDataAccountAddress.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let upgrade_authority = match deserialize(&program_data_info.data.borrow())
|
let upgrade_authority = if let UpgradeableLoaderState::ProgramData {
|
||||||
|
slot: _,
|
||||||
|
upgrade_authority_address,
|
||||||
|
} = deserialize(&program_data_info.data.borrow())
|
||||||
.map_err(|_| GovernanceError::InvalidProgramDataAccountData)?
|
.map_err(|_| GovernanceError::InvalidProgramDataAccountData)?
|
||||||
{
|
{
|
||||||
UpgradeableLoaderState::ProgramData {
|
upgrade_authority_address
|
||||||
slot: _,
|
} else {
|
||||||
upgrade_authority_address,
|
None
|
||||||
} => upgrade_authority_address,
|
|
||||||
_ => None,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
match upgrade_authority {
|
let upgrade_authority = upgrade_authority.ok_or(GovernanceError::ProgramNotUpgradable)?;
|
||||||
Some(upgrade_authority) => {
|
|
||||||
if upgrade_authority != *program_upgrade_authority_info.key {
|
if upgrade_authority != *program_upgrade_authority_info.key {
|
||||||
return Err(GovernanceError::InvalidUpgradeAuthority.into());
|
return Err(GovernanceError::InvalidUpgradeAuthority.into());
|
||||||
}
|
}
|
||||||
if !program_upgrade_authority_info.is_signer {
|
if !program_upgrade_authority_info.is_signer {
|
||||||
return Err(GovernanceError::UpgradeAuthorityMustSign.into());
|
return Err(GovernanceError::UpgradeAuthorityMustSign.into());
|
||||||
}
|
|
||||||
}
|
|
||||||
None => return Err(GovernanceError::ProgramNotUpgradable.into()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -190,7 +190,7 @@ pub fn get_mint_from_token_account(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokeAccount layout: mint(32), owner(32), amount(8), ...
|
// TokeAccount layout: mint(32), owner(32), amount(8), ...
|
||||||
let data = token_account_info.try_borrow_data().unwrap();
|
let data = token_account_info.try_borrow_data()?;
|
||||||
let mint_data = array_ref![data, 0, 32];
|
let mint_data = array_ref![data, 0, 32];
|
||||||
Ok(Pubkey::new_from_array(*mint_data))
|
Ok(Pubkey::new_from_array(*mint_data))
|
||||||
}
|
}
|
||||||
|
@ -205,7 +205,7 @@ pub fn get_owner_from_token_account(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokeAccount layout: mint(32), owner(32), amount(8)
|
// TokeAccount layout: mint(32), owner(32), amount(8)
|
||||||
let data = token_account_info.try_borrow_data().unwrap();
|
let data = token_account_info.try_borrow_data()?;
|
||||||
let owner_data = array_ref![data, 32, 32];
|
let owner_data = array_ref![data, 32, 32];
|
||||||
Ok(Pubkey::new_from_array(*owner_data))
|
Ok(Pubkey::new_from_array(*owner_data))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod program_test;
|
||||||
|
|
||||||
|
use solana_program_test::tokio;
|
||||||
|
|
||||||
|
use program_test::*;
|
||||||
|
|
||||||
|
use spl_governance::error::GovernanceError;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_add_signatory() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let signatory_record_cookie = governance_test
|
||||||
|
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let signatory_record_account = governance_test
|
||||||
|
.get_signatory_record_account(&signatory_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(signatory_record_cookie.account, signatory_record_account);
|
||||||
|
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(1, proposal_account.signatories_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_add_signatory_with_owner_or_delegate_must_sign_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let other_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.token_owner = other_token_owner_record_cookie.token_owner;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_add_signatory_with_invalid_proposal_owner_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let other_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.address = other_token_owner_record_cookie.address;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(err, GovernanceError::InvalidProposalOwnerAccount.into());
|
||||||
|
}
|
|
@ -0,0 +1,255 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
use solana_program::instruction::AccountMeta;
|
||||||
|
use solana_program_test::*;
|
||||||
|
|
||||||
|
mod program_test;
|
||||||
|
|
||||||
|
use program_test::*;
|
||||||
|
use solana_sdk::signature::Keypair;
|
||||||
|
use spl_governance::error::GovernanceError;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_community_proposal_created() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(proposal_cookie.account, proposal_account);
|
||||||
|
|
||||||
|
let account_governance_account = governance_test
|
||||||
|
.get_governance_account(&account_governance_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(1, account_governance_account.proposals_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_multiple_proposals_created() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let community_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let council_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let community_proposal_cookie = governance_test
|
||||||
|
.with_proposal(
|
||||||
|
&community_token_owner_record_cookie,
|
||||||
|
&mut account_governance_cookie,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let council_proposal_cookie = governance_test
|
||||||
|
.with_proposal(
|
||||||
|
&council_token_owner_record_cookie,
|
||||||
|
&mut account_governance_cookie,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let community_proposal_account = governance_test
|
||||||
|
.get_proposal_account(&community_proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
community_proposal_cookie.account,
|
||||||
|
community_proposal_account
|
||||||
|
);
|
||||||
|
|
||||||
|
let council_proposal_account = governance_test
|
||||||
|
.get_proposal_account(&council_proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(council_proposal_cookie.account, council_proposal_account);
|
||||||
|
|
||||||
|
let account_governance_account = governance_test
|
||||||
|
.get_governance_account(&account_governance_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(2, account_governance_account.proposals_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_proposal_with_not_authorized_governance_authority_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.governance_authority = Some(Keypair::new());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_proposal_with_governance_delegate_signer() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
governance_test
|
||||||
|
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.governance_authority =
|
||||||
|
Some(token_owner_record_cookie.clone_governance_delegate());
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(proposal_cookie.account, proposal_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_proposal_with_not_enough_tokens_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_amount = account_governance_cookie
|
||||||
|
.account
|
||||||
|
.config
|
||||||
|
.min_tokens_to_create_proposal as u64
|
||||||
|
- 1;
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit_amount(&realm_cookie, token_amount)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(err, GovernanceError::NotEnoughTokensToCreateProposal.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_create_proposal_with_invalid_token_owner_record_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let council_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.with_proposal_instruction(
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&mut account_governance_cookie,
|
||||||
|
|i| {
|
||||||
|
// Set token_owner_record_address for different (Council) mint
|
||||||
|
i.accounts[2] =
|
||||||
|
AccountMeta::new_readonly(council_token_owner_record_cookie.address, false);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::InvalidTokenOwnerRecordGoverningMint.into()
|
||||||
|
);
|
||||||
|
}
|
|
@ -16,24 +16,27 @@ async fn test_deposit_initial_community_tokens() {
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(voter_record_cookie.account, voter_record);
|
assert_eq!(token_owner_record_cookie.account, token_owner_record);
|
||||||
|
|
||||||
let source_account = governance_test
|
let source_account = governance_test
|
||||||
.get_token_account(&voter_record_cookie.token_source)
|
.get_token_account(&token_owner_record_cookie.token_source)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
voter_record_cookie.token_source_amount - voter_record_cookie.account.token_deposit_amount,
|
token_owner_record_cookie.token_source_amount
|
||||||
|
- token_owner_record_cookie
|
||||||
|
.account
|
||||||
|
.governing_token_deposit_amount,
|
||||||
source_account.amount
|
source_account.amount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -41,7 +44,10 @@ async fn test_deposit_initial_community_tokens() {
|
||||||
.get_token_account(&realm_cookie.community_token_holding_account)
|
.get_token_account(&realm_cookie.community_token_holding_account)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(voter_record.token_deposit_amount, holding_account.amount);
|
assert_eq!(
|
||||||
|
token_owner_record.governing_token_deposit_amount,
|
||||||
|
holding_account.amount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -53,23 +59,26 @@ async fn test_deposit_initial_council_tokens() {
|
||||||
let council_token_holding_account = realm_cookie.council_token_holding_account.unwrap();
|
let council_token_holding_account = realm_cookie.council_token_holding_account.unwrap();
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_council_token_deposit(&realm_cookie)
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(voter_record_cookie.account, voter_record);
|
assert_eq!(token_owner_record_cookie.account, token_owner_record);
|
||||||
|
|
||||||
let source_account = governance_test
|
let source_account = governance_test
|
||||||
.get_token_account(&voter_record_cookie.token_source)
|
.get_token_account(&token_owner_record_cookie.token_source)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
voter_record_cookie.token_source_amount - voter_record_cookie.account.token_deposit_amount,
|
token_owner_record_cookie.token_source_amount
|
||||||
|
- token_owner_record_cookie
|
||||||
|
.account
|
||||||
|
.governing_token_deposit_amount,
|
||||||
source_account.amount
|
source_account.amount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -77,7 +86,10 @@ async fn test_deposit_initial_council_tokens() {
|
||||||
.get_token_account(&council_token_holding_account)
|
.get_token_account(&council_token_holding_account)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(voter_record.token_deposit_amount, holding_account.amount);
|
assert_eq!(
|
||||||
|
token_owner_record.governing_token_deposit_amount,
|
||||||
|
holding_account.amount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -86,24 +98,30 @@ async fn test_deposit_subsequent_community_tokens() {
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_amount = 5;
|
let deposit_amount = 5;
|
||||||
let total_deposit_amount = voter_record_cookie.account.token_deposit_amount + deposit_amount;
|
let total_deposit_amount = token_owner_record_cookie
|
||||||
|
.account
|
||||||
|
.governing_token_deposit_amount
|
||||||
|
+ deposit_amount;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
governance_test
|
governance_test
|
||||||
.with_community_token_deposit(&realm_cookie, &voter_record_cookie, deposit_amount)
|
.with_community_token_deposit(&realm_cookie, &token_owner_record_cookie, deposit_amount)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(total_deposit_amount, voter_record.token_deposit_amount);
|
assert_eq!(
|
||||||
|
total_deposit_amount,
|
||||||
|
token_owner_record.governing_token_deposit_amount
|
||||||
|
);
|
||||||
|
|
||||||
let holding_account = governance_test
|
let holding_account = governance_test
|
||||||
.get_token_account(&realm_cookie.community_token_holding_account)
|
.get_token_account(&realm_cookie.community_token_holding_account)
|
||||||
|
@ -120,24 +138,30 @@ async fn test_deposit_subsequent_council_tokens() {
|
||||||
|
|
||||||
let council_token_holding_account = realm_cookie.council_token_holding_account.unwrap();
|
let council_token_holding_account = realm_cookie.council_token_holding_account.unwrap();
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_council_token_deposit(&realm_cookie)
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_amount = 5;
|
let deposit_amount = 5;
|
||||||
let total_deposit_amount = voter_record_cookie.account.token_deposit_amount + deposit_amount;
|
let total_deposit_amount = token_owner_record_cookie
|
||||||
|
.account
|
||||||
|
.governing_token_deposit_amount
|
||||||
|
+ deposit_amount;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
governance_test
|
governance_test
|
||||||
.with_council_token_deposit(&realm_cookie, &voter_record_cookie, deposit_amount)
|
.with_council_token_deposit(&realm_cookie, &token_owner_record_cookie, deposit_amount)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(total_deposit_amount, voter_record.token_deposit_amount);
|
assert_eq!(
|
||||||
|
total_deposit_amount,
|
||||||
|
token_owner_record.governing_token_deposit_amount
|
||||||
|
);
|
||||||
|
|
||||||
let holding_account = governance_test
|
let holding_account = governance_test
|
||||||
.get_token_account(&council_token_holding_account)
|
.get_token_account(&council_token_holding_account)
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod program_test;
|
||||||
|
|
||||||
|
use solana_program_test::tokio;
|
||||||
|
|
||||||
|
use program_test::*;
|
||||||
|
|
||||||
|
use spl_governance::{error::GovernanceError, state::enums::ProposalState};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_remove_signatory() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let 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();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.remove_signatory(
|
||||||
|
&proposal_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&signatory_record_cookie,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(0, proposal_account.signatories_count);
|
||||||
|
assert_eq!(ProposalState::Draft, proposal_account.state);
|
||||||
|
|
||||||
|
let signatory_account = governance_test
|
||||||
|
.banks_client
|
||||||
|
.get_account(signatory_record_cookie.address)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(None, signatory_account);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_remove_signatory_with_owner_or_delegate_must_sign_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let 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 other_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.token_owner = other_token_owner_record_cookie.token_owner;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.remove_signatory(
|
||||||
|
&proposal_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&signatory_record_cookie,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_remove_signatory_with_invalid_proposal_owner_error() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let 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 other_token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
token_owner_record_cookie.address = other_token_owner_record_cookie.address;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.remove_signatory(
|
||||||
|
&proposal_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&signatory_record_cookie,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(err, GovernanceError::InvalidProposalOwnerAccount.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_remove_signatory_when_all_remaining_signed() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let proposal_cookie = governance_test
|
||||||
|
.with_proposal(&token_owner_record_cookie, &mut account_governance_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let signatory_record_cookie1 = governance_test
|
||||||
|
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let signatory_record_cookie2 = governance_test
|
||||||
|
.with_signatory(&proposal_cookie, &token_owner_record_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
governance_test
|
||||||
|
.sign_off_proposal(&proposal_cookie, &signatory_record_cookie1)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.remove_signatory(
|
||||||
|
&proposal_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&signatory_record_cookie2,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(1, proposal_account.signatories_count);
|
||||||
|
assert_eq!(1, proposal_account.signatories_signed_off_count);
|
||||||
|
assert_eq!(ProposalState::Voting, proposal_account.state);
|
||||||
|
}
|
|
@ -0,0 +1,165 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
use solana_program::instruction::AccountMeta;
|
||||||
|
use solana_program_test::*;
|
||||||
|
|
||||||
|
mod program_test;
|
||||||
|
|
||||||
|
use program_test::*;
|
||||||
|
use solana_sdk::signature::{Keypair, Signer};
|
||||||
|
use spl_governance::{error::GovernanceError, instruction::set_governance_delegate};
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_community_governance_delegate() {
|
||||||
|
// Arrange
|
||||||
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let token_owner_record = governance_test
|
||||||
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(token_owner_record_cookie.governance_delegate.pubkey()),
|
||||||
|
token_owner_record.governance_delegate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_governance_delegate_to_none() {
|
||||||
|
// Arrange
|
||||||
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
governance_test
|
||||||
|
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.set_governance_delegate(
|
||||||
|
&realm_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&token_owner_record_cookie.token_owner,
|
||||||
|
&realm_cookie.account.community_mint,
|
||||||
|
&None,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let token_owner_record = governance_test
|
||||||
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(None, token_owner_record.governance_delegate);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_council_governance_delegate() {
|
||||||
|
// Arrange
|
||||||
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.with_council_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let token_owner_record = governance_test
|
||||||
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(token_owner_record_cookie.governance_delegate.pubkey()),
|
||||||
|
token_owner_record.governance_delegate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_community_governance_delegate_with_owner_must_sign_error() {
|
||||||
|
// Arrange
|
||||||
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let hacker_governance_delegate = Keypair::new();
|
||||||
|
|
||||||
|
let mut instruction = set_governance_delegate(
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
&realm_cookie.address,
|
||||||
|
&realm_cookie.account.community_mint,
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
&Some(hacker_governance_delegate.pubkey()),
|
||||||
|
);
|
||||||
|
|
||||||
|
instruction.accounts[0] =
|
||||||
|
AccountMeta::new_readonly(token_owner_record_cookie.token_owner.pubkey(), false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
let err = governance_test
|
||||||
|
.process_transaction(&[instruction], None)
|
||||||
|
.await
|
||||||
|
.err()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::GoverningTokenOwnerOrDelegateMustSign.into()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_set_community_governance_delegate_signed_by_governance_delegate() {
|
||||||
|
// Arrange
|
||||||
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
let mut token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
governance_test
|
||||||
|
.with_community_governance_delegate(&realm_cookie, &mut token_owner_record_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let new_governance_delegate = Keypair::new();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.set_governance_delegate(
|
||||||
|
&realm_cookie,
|
||||||
|
&token_owner_record_cookie,
|
||||||
|
&token_owner_record_cookie.governance_delegate,
|
||||||
|
&realm_cookie.account.community_mint,
|
||||||
|
&Some(new_governance_delegate.pubkey()),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let token_owner_record = governance_test
|
||||||
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Some(new_governance_delegate.pubkey()),
|
||||||
|
token_owner_record.governance_delegate
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,165 +0,0 @@
|
||||||
#![cfg(feature = "test-bpf")]
|
|
||||||
|
|
||||||
use solana_program::instruction::AccountMeta;
|
|
||||||
use solana_program_test::*;
|
|
||||||
|
|
||||||
mod program_test;
|
|
||||||
|
|
||||||
use program_test::*;
|
|
||||||
use solana_sdk::signature::{Keypair, Signer};
|
|
||||||
use spl_governance::{error::GovernanceError, instruction::set_vote_authority};
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_set_community_vote_authority() {
|
|
||||||
// Arrange
|
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
|
||||||
let mut voter_record_cookie = governance_test
|
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
governance_test
|
|
||||||
.with_community_vote_authority(&realm_cookie, &mut voter_record_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
let voter_record = governance_test
|
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(voter_record_cookie.vote_authority.pubkey()),
|
|
||||||
voter_record.vote_authority
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_set_vote_authority_to_none() {
|
|
||||||
// Arrange
|
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
|
||||||
let mut voter_record_cookie = governance_test
|
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
governance_test
|
|
||||||
.with_community_vote_authority(&realm_cookie, &mut voter_record_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
governance_test
|
|
||||||
.set_vote_authority(
|
|
||||||
&realm_cookie,
|
|
||||||
&voter_record_cookie,
|
|
||||||
&voter_record_cookie.token_owner,
|
|
||||||
&realm_cookie.account.community_mint,
|
|
||||||
&None,
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
let voter_record = governance_test
|
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert_eq!(None, voter_record.vote_authority);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_set_council_vote_authority() {
|
|
||||||
// Arrange
|
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
|
||||||
let mut voter_record_cookie = governance_test
|
|
||||||
.with_initial_council_token_deposit(&realm_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Act
|
|
||||||
governance_test
|
|
||||||
.with_council_vote_authority(&realm_cookie, &mut voter_record_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
let voter_record = governance_test
|
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(voter_record_cookie.vote_authority.pubkey()),
|
|
||||||
voter_record.vote_authority
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_set_community_vote_authority_with_owner_must_sign_error() {
|
|
||||||
// Arrange
|
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
|
||||||
let voter_record_cookie = governance_test
|
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let hacker_vote_authority = Keypair::new();
|
|
||||||
|
|
||||||
let mut instruction = set_vote_authority(
|
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
|
||||||
&realm_cookie.address,
|
|
||||||
&realm_cookie.account.community_mint,
|
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
|
||||||
&Some(hacker_vote_authority.pubkey()),
|
|
||||||
);
|
|
||||||
|
|
||||||
instruction.accounts[0] =
|
|
||||||
AccountMeta::new_readonly(voter_record_cookie.token_owner.pubkey(), false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
let err = governance_test
|
|
||||||
.process_transaction(&[instruction], None)
|
|
||||||
.await
|
|
||||||
.err()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
assert_eq!(
|
|
||||||
err,
|
|
||||||
GovernanceError::GoverningTokenOwnerOrVoteAuthrotiyMustSign.into()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_set_community_vote_authority_signed_by_vote_authority() {
|
|
||||||
// Arrange
|
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
|
||||||
let mut voter_record_cookie = governance_test
|
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
governance_test
|
|
||||||
.with_community_vote_authority(&realm_cookie, &mut voter_record_cookie)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let new_vote_authority = Keypair::new();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
governance_test
|
|
||||||
.set_vote_authority(
|
|
||||||
&realm_cookie,
|
|
||||||
&voter_record_cookie,
|
|
||||||
&voter_record_cookie.vote_authority,
|
|
||||||
&realm_cookie.account.community_mint,
|
|
||||||
&Some(new_vote_authority.pubkey()),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
let voter_record = governance_test
|
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(new_vote_authority.pubkey()),
|
|
||||||
voter_record.vote_authority
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
#![cfg(feature = "test-bpf")]
|
||||||
|
|
||||||
|
mod program_test;
|
||||||
|
|
||||||
|
use solana_program_test::tokio;
|
||||||
|
|
||||||
|
use program_test::*;
|
||||||
|
use spl_governance::state::enums::ProposalState;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_sign_off_proposal() {
|
||||||
|
// 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 mut account_governance_cookie = governance_test
|
||||||
|
.with_account_governance(&realm_cookie, &governed_account_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let token_owner_record_cookie = governance_test
|
||||||
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let 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();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
governance_test
|
||||||
|
.sign_off_proposal(&proposal_cookie, &signatory_record_cookie)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
let proposal_account = governance_test
|
||||||
|
.get_proposal_account(&proposal_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(1, proposal_account.signatories_count);
|
||||||
|
assert_eq!(1, proposal_account.signatories_signed_off_count);
|
||||||
|
assert_eq!(ProposalState::Voting, proposal_account.state);
|
||||||
|
assert_eq!(Some(1), proposal_account.signing_off_at);
|
||||||
|
assert_eq!(Some(1), proposal_account.voting_at);
|
||||||
|
|
||||||
|
let signatory_record_account = governance_test
|
||||||
|
.get_signatory_record_account(&signatory_record_cookie.address)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert_eq!(true, signatory_record_account.signed_off);
|
||||||
|
}
|
|
@ -10,7 +10,7 @@ use solana_sdk::signature::Signer;
|
||||||
|
|
||||||
use spl_governance::{
|
use spl_governance::{
|
||||||
error::GovernanceError, instruction::withdraw_governing_tokens,
|
error::GovernanceError, instruction::withdraw_governing_tokens,
|
||||||
state::voter_record::get_voter_record_address,
|
state::token_owner_record::get_token_owner_record_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
@ -19,22 +19,22 @@ async fn test_withdraw_community_tokens() {
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
governance_test
|
governance_test
|
||||||
.withdraw_community_tokens(&realm_cookie, &voter_record_cookie)
|
.withdraw_community_tokens(&realm_cookie, &token_owner_record_cookie)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(0, voter_record.token_deposit_amount);
|
assert_eq!(0, token_owner_record.governing_token_deposit_amount);
|
||||||
|
|
||||||
let holding_account = governance_test
|
let holding_account = governance_test
|
||||||
.get_token_account(&realm_cookie.community_token_holding_account)
|
.get_token_account(&realm_cookie.community_token_holding_account)
|
||||||
|
@ -43,11 +43,11 @@ async fn test_withdraw_community_tokens() {
|
||||||
assert_eq!(0, holding_account.amount);
|
assert_eq!(0, holding_account.amount);
|
||||||
|
|
||||||
let source_account = governance_test
|
let source_account = governance_test
|
||||||
.get_token_account(&voter_record_cookie.token_source)
|
.get_token_account(&token_owner_record_cookie.token_source)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
voter_record_cookie.token_source_amount,
|
token_owner_record_cookie.token_source_amount,
|
||||||
source_account.amount
|
source_account.amount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -58,22 +58,22 @@ async fn test_withdraw_council_tokens() {
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_council_token_deposit(&realm_cookie)
|
.with_initial_council_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
governance_test
|
governance_test
|
||||||
.withdraw_council_tokens(&realm_cookie, &voter_record_cookie)
|
.withdraw_council_tokens(&realm_cookie, &token_owner_record_cookie)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
let voter_record = governance_test
|
let token_owner_record = governance_test
|
||||||
.get_voter_record_account(&voter_record_cookie.address)
|
.get_token_owner_record_account(&token_owner_record_cookie.address)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(0, voter_record.token_deposit_amount);
|
assert_eq!(0, token_owner_record.governing_token_deposit_amount);
|
||||||
|
|
||||||
let holding_account = governance_test
|
let holding_account = governance_test
|
||||||
.get_token_account(&realm_cookie.council_token_holding_account.unwrap())
|
.get_token_account(&realm_cookie.council_token_holding_account.unwrap())
|
||||||
|
@ -82,11 +82,11 @@ async fn test_withdraw_council_tokens() {
|
||||||
assert_eq!(0, holding_account.amount);
|
assert_eq!(0, holding_account.amount);
|
||||||
|
|
||||||
let source_account = governance_test
|
let source_account = governance_test
|
||||||
.get_token_account(&voter_record_cookie.token_source)
|
.get_token_account(&token_owner_record_cookie.token_source)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
voter_record_cookie.token_source_amount,
|
token_owner_record_cookie.token_source_amount,
|
||||||
source_account.amount
|
source_account.amount
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ async fn test_withdraw_community_tokens_with_owner_must_sign_error() {
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
@ -106,12 +106,12 @@ async fn test_withdraw_community_tokens_with_owner_must_sign_error() {
|
||||||
let mut instruction = withdraw_governing_tokens(
|
let mut instruction = withdraw_governing_tokens(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
&hacker_token_destination,
|
&hacker_token_destination,
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
);
|
);
|
||||||
|
|
||||||
instruction.accounts[3] =
|
instruction.accounts[3] =
|
||||||
AccountMeta::new_readonly(voter_record_cookie.token_owner.pubkey(), false);
|
AccountMeta::new_readonly(token_owner_record_cookie.token_owner.pubkey(), false);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
let err = governance_test
|
let err = governance_test
|
||||||
|
@ -126,19 +126,19 @@ async fn test_withdraw_community_tokens_with_owner_must_sign_error() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_withdraw_community_tokens_with_voter_record_address_mismatch_error() {
|
async fn test_withdraw_community_tokens_with_token_owner_record_address_mismatch_error() {
|
||||||
// Arrange
|
// Arrange
|
||||||
let mut governance_test = GovernanceProgramTest::start_new().await;
|
let mut governance_test = GovernanceProgramTest::start_new().await;
|
||||||
let realm_cookie = governance_test.with_realm().await;
|
let realm_cookie = governance_test.with_realm().await;
|
||||||
|
|
||||||
let voter_record_cookie = governance_test
|
let token_owner_record_cookie = governance_test
|
||||||
.with_initial_community_token_deposit(&realm_cookie)
|
.with_initial_community_token_deposit(&realm_cookie)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let vote_record_address = get_voter_record_address(
|
let vote_record_address = get_token_owner_record_address(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let hacker_record_cookie = governance_test
|
let hacker_record_cookie = governance_test
|
||||||
|
@ -163,5 +163,8 @@ async fn test_withdraw_community_tokens_with_voter_record_address_mismatch_error
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
||||||
assert_eq!(err, GovernanceError::InvalidVoterAccountAddress.into());
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
GovernanceError::InvalidTokenOwnerRecordAccountAddress.into()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use solana_program::pubkey::Pubkey;
|
use solana_program::pubkey::Pubkey;
|
||||||
use solana_sdk::signature::Keypair;
|
use solana_sdk::signature::Keypair;
|
||||||
use spl_governance::state::{governance::Governance, realm::Realm, voter_record::VoterRecord};
|
use spl_governance::state::{
|
||||||
|
governance::Governance, realm::Realm, token_owner_record::TokenOwnerRecord,
|
||||||
|
};
|
||||||
|
use spl_governance::state::{proposal::Proposal, signatory_record::SignatoryRecord};
|
||||||
|
|
||||||
|
use super::tools::clone_keypair;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct RealmCookie {
|
pub struct RealmCookie {
|
||||||
|
@ -18,10 +23,10 @@ pub struct RealmCookie {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VoterRecordCookie {
|
pub struct TokeOwnerRecordCookie {
|
||||||
pub address: Pubkey,
|
pub address: Pubkey,
|
||||||
|
|
||||||
pub account: VoterRecord,
|
pub account: TokenOwnerRecord,
|
||||||
|
|
||||||
pub token_source: Pubkey,
|
pub token_source: Pubkey,
|
||||||
|
|
||||||
|
@ -29,7 +34,24 @@ pub struct VoterRecordCookie {
|
||||||
|
|
||||||
pub token_owner: Keypair,
|
pub token_owner: Keypair,
|
||||||
|
|
||||||
pub vote_authority: Keypair,
|
pub governance_authority: Option<Keypair>,
|
||||||
|
|
||||||
|
pub governance_delegate: Keypair,
|
||||||
|
|
||||||
|
pub governing_token_mint: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TokeOwnerRecordCookie {
|
||||||
|
pub fn get_governance_authority(&self) -> &Keypair {
|
||||||
|
self.governance_authority
|
||||||
|
.as_ref()
|
||||||
|
.unwrap_or(&self.token_owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn clone_governance_delegate(&self) -> Keypair {
|
||||||
|
clone_keypair(&self.governance_delegate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -49,4 +71,20 @@ pub struct GovernedAccountCookie {
|
||||||
pub struct GovernanceCookie {
|
pub struct GovernanceCookie {
|
||||||
pub address: Pubkey,
|
pub address: Pubkey,
|
||||||
pub account: Governance,
|
pub account: Governance,
|
||||||
|
pub next_proposal_index: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProposalCookie {
|
||||||
|
pub address: Pubkey,
|
||||||
|
pub account: Proposal,
|
||||||
|
|
||||||
|
pub proposal_owner: Pubkey,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SignatoryRecordCookie {
|
||||||
|
pub address: Pubkey,
|
||||||
|
pub account: SignatoryRecord,
|
||||||
|
pub signatory: Keypair,
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,25 +23,34 @@ use solana_sdk::{
|
||||||
};
|
};
|
||||||
use spl_governance::{
|
use spl_governance::{
|
||||||
instruction::{
|
instruction::{
|
||||||
create_account_governance, create_program_governance, create_realm,
|
add_signatory, create_account_governance, create_program_governance, create_proposal,
|
||||||
deposit_governing_tokens, set_vote_authority, withdraw_governing_tokens,
|
create_realm, deposit_governing_tokens, remove_signatory, set_governance_delegate,
|
||||||
|
sign_off_proposal, withdraw_governing_tokens,
|
||||||
},
|
},
|
||||||
processor::process_instruction,
|
processor::process_instruction,
|
||||||
state::{
|
state::{
|
||||||
enums::{GovernanceAccountType, GoverningTokenType},
|
enums::{GovernanceAccountType, ProposalState},
|
||||||
governance::{
|
governance::{
|
||||||
get_account_governance_address, get_program_governance_address, Governance,
|
get_account_governance_address, get_program_governance_address, Governance,
|
||||||
GovernanceConfig,
|
GovernanceConfig,
|
||||||
},
|
},
|
||||||
|
proposal::{get_proposal_address, Proposal},
|
||||||
realm::{get_governing_token_holding_address, get_realm_address, Realm},
|
realm::{get_governing_token_holding_address, get_realm_address, Realm},
|
||||||
voter_record::{get_voter_record_address, VoterRecord},
|
signatory_record::{get_signatory_record_address, SignatoryRecord},
|
||||||
|
token_owner_record::{get_token_owner_record_address, TokenOwnerRecord},
|
||||||
},
|
},
|
||||||
tools::bpf_loader_upgradeable::get_program_data_address,
|
tools::bpf_loader_upgradeable::get_program_data_address,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod cookies;
|
pub mod cookies;
|
||||||
use self::cookies::{
|
use crate::program_test::cookies::SignatoryRecordCookie;
|
||||||
GovernanceCookie, GovernedAccountCookie, GovernedProgramCookie, RealmCookie, VoterRecordCookie,
|
|
||||||
|
use self::{
|
||||||
|
cookies::{
|
||||||
|
GovernanceCookie, GovernedAccountCookie, GovernedProgramCookie, ProposalCookie,
|
||||||
|
RealmCookie, TokeOwnerRecordCookie,
|
||||||
|
},
|
||||||
|
tools::NopOverride,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod tools;
|
pub mod tools;
|
||||||
|
@ -166,12 +175,27 @@ impl GovernanceProgramTest {
|
||||||
pub async fn with_initial_community_token_deposit(
|
pub async fn with_initial_community_token_deposit(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
) -> VoterRecordCookie {
|
) -> TokeOwnerRecordCookie {
|
||||||
self.with_initial_governing_token_deposit(
|
self.with_initial_governing_token_deposit(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
GoverningTokenType::Community,
|
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
&realm_cookie.community_mint_authority,
|
&realm_cookie.community_mint_authority,
|
||||||
|
100,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn with_initial_community_token_deposit_amount(
|
||||||
|
&mut self,
|
||||||
|
realm_cookie: &RealmCookie,
|
||||||
|
amount: u64,
|
||||||
|
) -> TokeOwnerRecordCookie {
|
||||||
|
self.with_initial_governing_token_deposit(
|
||||||
|
&realm_cookie.address,
|
||||||
|
&realm_cookie.account.community_mint,
|
||||||
|
&realm_cookie.community_mint_authority,
|
||||||
|
amount,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -180,14 +204,14 @@ impl GovernanceProgramTest {
|
||||||
pub async fn with_community_token_deposit(
|
pub async fn with_community_token_deposit(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
) {
|
) {
|
||||||
self.with_governing_token_deposit(
|
self.with_governing_token_deposit(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
&realm_cookie.community_mint_authority,
|
&realm_cookie.community_mint_authority,
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
amount,
|
amount,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -197,14 +221,14 @@ impl GovernanceProgramTest {
|
||||||
pub async fn with_council_token_deposit(
|
pub async fn with_council_token_deposit(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
) {
|
) {
|
||||||
self.with_governing_token_deposit(
|
self.with_governing_token_deposit(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
&realm_cookie.account.council_mint.unwrap(),
|
&realm_cookie.account.council_mint.unwrap(),
|
||||||
&realm_cookie.council_mint_authority.as_ref().unwrap(),
|
&realm_cookie.council_mint_authority.as_ref().unwrap(),
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
amount,
|
amount,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
@ -214,12 +238,12 @@ impl GovernanceProgramTest {
|
||||||
pub async fn with_initial_council_token_deposit(
|
pub async fn with_initial_council_token_deposit(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
) -> VoterRecordCookie {
|
) -> TokeOwnerRecordCookie {
|
||||||
self.with_initial_governing_token_deposit(
|
self.with_initial_governing_token_deposit(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
GoverningTokenType::Council,
|
|
||||||
&realm_cookie.account.council_mint.unwrap(),
|
&realm_cookie.account.council_mint.unwrap(),
|
||||||
&realm_cookie.council_mint_authority.as_ref().unwrap(),
|
&realm_cookie.council_mint_authority.as_ref().unwrap(),
|
||||||
|
100,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -228,21 +252,20 @@ impl GovernanceProgramTest {
|
||||||
pub async fn with_initial_governing_token_deposit(
|
pub async fn with_initial_governing_token_deposit(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_address: &Pubkey,
|
realm_address: &Pubkey,
|
||||||
governing_token_type: GoverningTokenType,
|
|
||||||
governing_mint: &Pubkey,
|
governing_mint: &Pubkey,
|
||||||
governing_mint_authority: &Keypair,
|
governing_mint_authority: &Keypair,
|
||||||
) -> VoterRecordCookie {
|
amount: u64,
|
||||||
|
) -> TokeOwnerRecordCookie {
|
||||||
let token_owner = Keypair::new();
|
let token_owner = Keypair::new();
|
||||||
let token_source = Keypair::new();
|
let token_source = Keypair::new();
|
||||||
|
|
||||||
let source_amount = 100;
|
|
||||||
let transfer_authority = Keypair::new();
|
let transfer_authority = Keypair::new();
|
||||||
|
|
||||||
self.create_token_account_with_transfer_authority(
|
self.create_token_account_with_transfer_authority(
|
||||||
&token_source,
|
&token_source,
|
||||||
governing_mint,
|
governing_mint,
|
||||||
governing_mint_authority,
|
governing_mint_authority,
|
||||||
source_amount,
|
amount,
|
||||||
&token_owner,
|
&token_owner,
|
||||||
&transfer_authority.pubkey(),
|
&transfer_authority.pubkey(),
|
||||||
)
|
)
|
||||||
|
@ -264,30 +287,32 @@ impl GovernanceProgramTest {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let voter_record_address =
|
let token_owner_record_address =
|
||||||
get_voter_record_address(realm_address, &governing_mint, &token_owner.pubkey());
|
get_token_owner_record_address(realm_address, &governing_mint, &token_owner.pubkey());
|
||||||
|
|
||||||
let account = VoterRecord {
|
let account = TokenOwnerRecord {
|
||||||
account_type: GovernanceAccountType::VoterRecord,
|
account_type: GovernanceAccountType::TokenOwnerRecord,
|
||||||
realm: *realm_address,
|
realm: *realm_address,
|
||||||
token_type: governing_token_type,
|
governing_token_mint: *governing_mint,
|
||||||
token_owner: token_owner.pubkey(),
|
governing_token_owner: token_owner.pubkey(),
|
||||||
token_deposit_amount: source_amount,
|
governing_token_deposit_amount: amount,
|
||||||
vote_authority: None,
|
governance_delegate: None,
|
||||||
active_votes_count: 0,
|
active_votes_count: 0,
|
||||||
total_votes_count: 0,
|
total_votes_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let vote_authority = Keypair::from_base58_string(&token_owner.to_base58_string());
|
let governance_delegate = Keypair::from_base58_string(&token_owner.to_base58_string());
|
||||||
|
|
||||||
VoterRecordCookie {
|
TokeOwnerRecordCookie {
|
||||||
address: voter_record_address,
|
address: token_owner_record_address,
|
||||||
account,
|
account,
|
||||||
|
|
||||||
token_source_amount: source_amount,
|
token_source_amount: amount,
|
||||||
token_source: token_source.pubkey(),
|
token_source: token_source.pubkey(),
|
||||||
token_owner,
|
token_owner,
|
||||||
vote_authority,
|
governance_authority: None,
|
||||||
|
governance_delegate: governance_delegate,
|
||||||
|
governing_token_mint: *governing_mint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,103 +322,103 @@ impl GovernanceProgramTest {
|
||||||
realm: &Pubkey,
|
realm: &Pubkey,
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
governing_token_mint_authority: &Keypair,
|
governing_token_mint_authority: &Keypair,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
) {
|
) {
|
||||||
self.mint_tokens(
|
self.mint_tokens(
|
||||||
governing_token_mint,
|
governing_token_mint,
|
||||||
governing_token_mint_authority,
|
governing_token_mint_authority,
|
||||||
&voter_record_cookie.token_source,
|
&token_owner_record_cookie.token_source,
|
||||||
amount,
|
amount,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let deposit_governing_tokens_instruction = deposit_governing_tokens(
|
let deposit_governing_tokens_instruction = deposit_governing_tokens(
|
||||||
realm,
|
realm,
|
||||||
&voter_record_cookie.token_source,
|
&token_owner_record_cookie.token_source,
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
&self.payer.pubkey(),
|
&self.payer.pubkey(),
|
||||||
governing_token_mint,
|
governing_token_mint,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.process_transaction(
|
self.process_transaction(
|
||||||
&[deposit_governing_tokens_instruction],
|
&[deposit_governing_tokens_instruction],
|
||||||
Some(&[&voter_record_cookie.token_owner]),
|
Some(&[&token_owner_record_cookie.token_owner]),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn with_community_vote_authority(
|
pub async fn with_community_governance_delegate(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &mut VoterRecordCookie,
|
token_owner_record_cookie: &mut TokeOwnerRecordCookie,
|
||||||
) {
|
) {
|
||||||
self.with_governing_token_vote_authority(
|
self.with_governing_token_governance_delegate(
|
||||||
&realm_cookie,
|
&realm_cookie,
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn with_council_vote_authority(
|
pub async fn with_council_governance_delegate(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &mut VoterRecordCookie,
|
token_owner_record_cookie: &mut TokeOwnerRecordCookie,
|
||||||
) {
|
) {
|
||||||
self.with_governing_token_vote_authority(
|
self.with_governing_token_governance_delegate(
|
||||||
&realm_cookie,
|
&realm_cookie,
|
||||||
&realm_cookie.account.council_mint.unwrap(),
|
&realm_cookie.account.council_mint.unwrap(),
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn with_governing_token_vote_authority(
|
pub async fn with_governing_token_governance_delegate(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
voter_record_cookie: &mut VoterRecordCookie,
|
token_owner_record_cookie: &mut TokeOwnerRecordCookie,
|
||||||
) {
|
) {
|
||||||
let new_vote_authority = Keypair::new();
|
let new_governance_delegate = Keypair::new();
|
||||||
|
|
||||||
self.set_vote_authority(
|
self.set_governance_delegate(
|
||||||
realm_cookie,
|
realm_cookie,
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
&voter_record_cookie.token_owner,
|
&token_owner_record_cookie.token_owner,
|
||||||
governing_token_mint,
|
governing_token_mint,
|
||||||
&Some(new_vote_authority.pubkey()),
|
&Some(new_governance_delegate.pubkey()),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
voter_record_cookie.vote_authority = new_vote_authority;
|
token_owner_record_cookie.governance_delegate = new_governance_delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn set_vote_authority(
|
pub async fn set_governance_delegate(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
signing_vote_authority: &Keypair,
|
signing_governance_authority: &Keypair,
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
new_vote_authority: &Option<Pubkey>,
|
new_governance_delegate: &Option<Pubkey>,
|
||||||
) {
|
) {
|
||||||
let set_vote_authority_instruction = set_vote_authority(
|
let set_governance_delegate_instruction = set_governance_delegate(
|
||||||
&signing_vote_authority.pubkey(),
|
&signing_governance_authority.pubkey(),
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
governing_token_mint,
|
governing_token_mint,
|
||||||
&voter_record_cookie.token_owner.pubkey(),
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
new_vote_authority,
|
new_governance_delegate,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.process_transaction(
|
self.process_transaction(
|
||||||
&[set_vote_authority_instruction],
|
&[set_governance_delegate_instruction],
|
||||||
Some(&[&signing_vote_authority]),
|
Some(&[&signing_governance_authority]),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -403,13 +428,13 @@ impl GovernanceProgramTest {
|
||||||
pub async fn withdraw_community_tokens(
|
pub async fn withdraw_community_tokens(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
self.withdraw_governing_tokens(
|
self.withdraw_governing_tokens(
|
||||||
realm_cookie,
|
realm_cookie,
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
&realm_cookie.account.community_mint,
|
&realm_cookie.account.community_mint,
|
||||||
&voter_record_cookie.token_owner,
|
&token_owner_record_cookie.token_owner,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -418,13 +443,13 @@ impl GovernanceProgramTest {
|
||||||
pub async fn withdraw_council_tokens(
|
pub async fn withdraw_council_tokens(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
self.withdraw_governing_tokens(
|
self.withdraw_governing_tokens(
|
||||||
realm_cookie,
|
realm_cookie,
|
||||||
voter_record_cookie,
|
token_owner_record_cookie,
|
||||||
&realm_cookie.account.council_mint.unwrap(),
|
&realm_cookie.account.council_mint.unwrap(),
|
||||||
&voter_record_cookie.token_owner,
|
&token_owner_record_cookie.token_owner,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
@ -433,14 +458,14 @@ impl GovernanceProgramTest {
|
||||||
async fn withdraw_governing_tokens(
|
async fn withdraw_governing_tokens(
|
||||||
&mut self,
|
&mut self,
|
||||||
realm_cookie: &RealmCookie,
|
realm_cookie: &RealmCookie,
|
||||||
voter_record_cookie: &VoterRecordCookie,
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
governing_token_mint: &Pubkey,
|
governing_token_mint: &Pubkey,
|
||||||
|
|
||||||
governing_token_owner: &Keypair,
|
governing_token_owner: &Keypair,
|
||||||
) -> Result<(), ProgramError> {
|
) -> Result<(), ProgramError> {
|
||||||
let deposit_governing_tokens_instruction = withdraw_governing_tokens(
|
let deposit_governing_tokens_instruction = withdraw_governing_tokens(
|
||||||
&realm_cookie.address,
|
&realm_cookie.address,
|
||||||
&voter_record_cookie.token_source,
|
&token_owner_record_cookie.token_source,
|
||||||
&governing_token_owner.pubkey(),
|
&governing_token_owner.pubkey(),
|
||||||
governing_token_mint,
|
governing_token_mint,
|
||||||
);
|
);
|
||||||
|
@ -491,7 +516,7 @@ impl GovernanceProgramTest {
|
||||||
let account = Governance {
|
let account = Governance {
|
||||||
account_type: GovernanceAccountType::AccountGovernance,
|
account_type: GovernanceAccountType::AccountGovernance,
|
||||||
config: governance_config,
|
config: governance_config,
|
||||||
proposal_count: 0,
|
proposals_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.process_transaction(&[create_account_governance_instruction], None)
|
self.process_transaction(&[create_account_governance_instruction], None)
|
||||||
|
@ -503,6 +528,7 @@ impl GovernanceProgramTest {
|
||||||
Ok(GovernanceCookie {
|
Ok(GovernanceCookie {
|
||||||
address: account_governance_address,
|
address: account_governance_address,
|
||||||
account,
|
account,
|
||||||
|
next_proposal_index: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,7 +612,7 @@ impl GovernanceProgramTest {
|
||||||
self.with_program_governance_instruction(
|
self.with_program_governance_instruction(
|
||||||
realm_cookie,
|
realm_cookie,
|
||||||
governed_program_cookie,
|
governed_program_cookie,
|
||||||
|_| {},
|
NopOverride,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -603,10 +629,10 @@ impl GovernanceProgramTest {
|
||||||
let config = GovernanceConfig {
|
let config = GovernanceConfig {
|
||||||
realm: realm_cookie.address,
|
realm: realm_cookie.address,
|
||||||
governed_account: governed_program_cookie.address,
|
governed_account: governed_program_cookie.address,
|
||||||
vote_threshold_percentage: 60,
|
|
||||||
min_tokens_to_create_proposal: 5,
|
min_tokens_to_create_proposal: 5,
|
||||||
min_instruction_hold_up_time: 10,
|
min_instruction_hold_up_time: 10,
|
||||||
max_voting_time: 100,
|
max_voting_time: 100,
|
||||||
|
vote_threshold_percentage: 60,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut create_program_governance_instruction = create_program_governance(
|
let mut create_program_governance_instruction = create_program_governance(
|
||||||
|
@ -627,7 +653,7 @@ impl GovernanceProgramTest {
|
||||||
let account = Governance {
|
let account = Governance {
|
||||||
account_type: GovernanceAccountType::ProgramGovernance,
|
account_type: GovernanceAccountType::ProgramGovernance,
|
||||||
config,
|
config,
|
||||||
proposal_count: 0,
|
proposals_count: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let program_governance_address =
|
let program_governance_address =
|
||||||
|
@ -636,12 +662,178 @@ impl GovernanceProgramTest {
|
||||||
Ok(GovernanceCookie {
|
Ok(GovernanceCookie {
|
||||||
address: program_governance_address,
|
address: program_governance_address,
|
||||||
account,
|
account,
|
||||||
|
next_proposal_index: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn get_voter_record_account(&mut self, address: &Pubkey) -> VoterRecord {
|
pub async fn with_proposal(
|
||||||
self.get_borsh_account::<VoterRecord>(address).await
|
&mut self,
|
||||||
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
|
governance_cookie: &mut GovernanceCookie,
|
||||||
|
) -> Result<ProposalCookie, ProgramError> {
|
||||||
|
self.with_proposal_instruction(token_owner_record_cookie, governance_cookie, |_| {})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn with_proposal_instruction<F: Fn(&mut Instruction)>(
|
||||||
|
&mut self,
|
||||||
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
|
governance_cookie: &mut GovernanceCookie,
|
||||||
|
instruction_override: F,
|
||||||
|
) -> Result<ProposalCookie, ProgramError> {
|
||||||
|
let proposal_index = governance_cookie.next_proposal_index;
|
||||||
|
governance_cookie.next_proposal_index = governance_cookie.next_proposal_index + 1;
|
||||||
|
|
||||||
|
let name = format!("Proposal #{}", proposal_index);
|
||||||
|
|
||||||
|
let description_link = "Proposal Description".to_string();
|
||||||
|
|
||||||
|
let governance_authority = token_owner_record_cookie.get_governance_authority();
|
||||||
|
|
||||||
|
let mut create_proposal_instruction = create_proposal(
|
||||||
|
&governance_cookie.address,
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
&governance_authority.pubkey(),
|
||||||
|
&self.payer.pubkey(),
|
||||||
|
&governance_cookie.account.config.realm,
|
||||||
|
name.clone(),
|
||||||
|
description_link.clone(),
|
||||||
|
&token_owner_record_cookie.governing_token_mint,
|
||||||
|
proposal_index,
|
||||||
|
);
|
||||||
|
|
||||||
|
instruction_override(&mut create_proposal_instruction);
|
||||||
|
|
||||||
|
self.process_transaction(
|
||||||
|
&[create_proposal_instruction],
|
||||||
|
Some(&[&governance_authority]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let account = Proposal {
|
||||||
|
account_type: GovernanceAccountType::Proposal,
|
||||||
|
description_link,
|
||||||
|
name: name.clone(),
|
||||||
|
governance: governance_cookie.address,
|
||||||
|
governing_token_mint: token_owner_record_cookie.governing_token_mint,
|
||||||
|
state: ProposalState::Draft,
|
||||||
|
signatories_count: 0,
|
||||||
|
// Clock always returns 1 when running under the test
|
||||||
|
draft_at: 1,
|
||||||
|
signing_off_at: None,
|
||||||
|
voting_at: None,
|
||||||
|
voting_completed_at: None,
|
||||||
|
executing_at: None,
|
||||||
|
closed_at: None,
|
||||||
|
number_of_executed_instructions: 0,
|
||||||
|
number_of_instructions: 0,
|
||||||
|
token_owner_record: token_owner_record_cookie.address,
|
||||||
|
signatories_signed_off_count: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let proposal_address = get_proposal_address(
|
||||||
|
&governance_cookie.address,
|
||||||
|
&token_owner_record_cookie.governing_token_mint,
|
||||||
|
&proposal_index.to_le_bytes(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(ProposalCookie {
|
||||||
|
address: proposal_address,
|
||||||
|
account,
|
||||||
|
proposal_owner: governance_authority.pubkey(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn with_signatory(
|
||||||
|
&mut self,
|
||||||
|
proposal_cookie: &ProposalCookie,
|
||||||
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
|
) -> Result<SignatoryRecordCookie, ProgramError> {
|
||||||
|
let signatory = Keypair::new();
|
||||||
|
|
||||||
|
let add_signatory_instruction = add_signatory(
|
||||||
|
&proposal_cookie.address,
|
||||||
|
&token_owner_record_cookie.address,
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
&self.payer.pubkey(),
|
||||||
|
&signatory.pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.process_transaction(
|
||||||
|
&[add_signatory_instruction],
|
||||||
|
Some(&[&token_owner_record_cookie.token_owner]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let signatory_record_address =
|
||||||
|
get_signatory_record_address(&proposal_cookie.address, &signatory.pubkey());
|
||||||
|
|
||||||
|
let signatory_record_data = SignatoryRecord {
|
||||||
|
account_type: GovernanceAccountType::SignatoryRecord,
|
||||||
|
proposal: proposal_cookie.address,
|
||||||
|
signatory: signatory.pubkey(),
|
||||||
|
signed_off: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let signatory_record_cookie = SignatoryRecordCookie {
|
||||||
|
address: signatory_record_address,
|
||||||
|
account: signatory_record_data,
|
||||||
|
signatory: signatory,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(signatory_record_cookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn remove_signatory(
|
||||||
|
&mut self,
|
||||||
|
proposal_cookie: &ProposalCookie,
|
||||||
|
token_owner_record_cookie: &TokeOwnerRecordCookie,
|
||||||
|
signatory_record_cookie: &SignatoryRecordCookie,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
let remove_signatory_instruction = remove_signatory(
|
||||||
|
&proposal_cookie.address,
|
||||||
|
&token_owner_record_cookie.address,
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
&signatory_record_cookie.account.signatory,
|
||||||
|
&token_owner_record_cookie.token_owner.pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.process_transaction(
|
||||||
|
&[remove_signatory_instruction],
|
||||||
|
Some(&[&token_owner_record_cookie.token_owner]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn sign_off_proposal(
|
||||||
|
&mut self,
|
||||||
|
proposal_cookie: &ProposalCookie,
|
||||||
|
signatory_record_cookie: &SignatoryRecordCookie,
|
||||||
|
) -> Result<(), ProgramError> {
|
||||||
|
let sign_off_proposal_instruction = sign_off_proposal(
|
||||||
|
&proposal_cookie.address,
|
||||||
|
&signatory_record_cookie.signatory.pubkey(),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.process_transaction(
|
||||||
|
&[sign_off_proposal_instruction],
|
||||||
|
Some(&[&signatory_record_cookie.signatory]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get_token_owner_record_account(&mut self, address: &Pubkey) -> TokenOwnerRecord {
|
||||||
|
self.get_borsh_account::<TokenOwnerRecord>(address).await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -659,6 +851,20 @@ impl GovernanceProgramTest {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get_proposal_account(&mut self, proposal_address: &Pubkey) -> Proposal {
|
||||||
|
self.get_borsh_account::<Proposal>(proposal_address).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub async fn get_signatory_record_account(
|
||||||
|
&mut self,
|
||||||
|
proposal_address: &Pubkey,
|
||||||
|
) -> SignatoryRecord {
|
||||||
|
self.get_borsh_account::<SignatoryRecord>(proposal_address)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
async fn get_packed_account<T: Pack + IsInitialized>(&mut self, address: &Pubkey) -> T {
|
async fn get_packed_account<T: Pack + IsInitialized>(&mut self, address: &Pubkey) -> T {
|
||||||
self.banks_client
|
self.banks_client
|
||||||
|
@ -695,7 +901,7 @@ impl GovernanceProgramTest {
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.map(|a| try_from_slice_unchecked(&a.data).unwrap())
|
.map(|a| try_from_slice_unchecked(&a.data).unwrap())
|
||||||
.expect(format!("GET-TEST-ACCOUNT-ERROR: Account {}", address).as_str())
|
.expect(format!("GET-TEST-ACCOUNT-ERROR: Account {} not found", address).as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -729,54 +935,6 @@ impl GovernanceProgramTest {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub async fn create_token_account(
|
|
||||||
&mut self,
|
|
||||||
token_account_keypair: &Keypair,
|
|
||||||
token_mint: &Pubkey,
|
|
||||||
token_mint_authority: &Keypair,
|
|
||||||
amount: u64,
|
|
||||||
owner: &Pubkey,
|
|
||||||
) {
|
|
||||||
let create_account_instruction = system_instruction::create_account(
|
|
||||||
&self.payer.pubkey(),
|
|
||||||
&token_account_keypair.pubkey(),
|
|
||||||
self.rent
|
|
||||||
.minimum_balance(spl_token::state::Account::get_packed_len()),
|
|
||||||
spl_token::state::Account::get_packed_len() as u64,
|
|
||||||
&spl_token::id(),
|
|
||||||
);
|
|
||||||
|
|
||||||
let initialize_account_instruction = spl_token::instruction::initialize_account(
|
|
||||||
&spl_token::id(),
|
|
||||||
&token_account_keypair.pubkey(),
|
|
||||||
token_mint,
|
|
||||||
&owner,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let mint_instruction = spl_token::instruction::mint_to(
|
|
||||||
&spl_token::id(),
|
|
||||||
token_mint,
|
|
||||||
&token_account_keypair.pubkey(),
|
|
||||||
&token_mint_authority.pubkey(),
|
|
||||||
&[],
|
|
||||||
amount,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
self.process_transaction(
|
|
||||||
&[
|
|
||||||
create_account_instruction,
|
|
||||||
initialize_account_instruction,
|
|
||||||
mint_instruction,
|
|
||||||
],
|
|
||||||
Some(&[&token_account_keypair, &token_mint_authority]),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub async fn create_token_account_with_transfer_authority(
|
pub async fn create_token_account_with_transfer_authority(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use solana_program::{instruction::InstructionError, program_error::ProgramError};
|
use solana_program::{instruction::InstructionError, program_error::ProgramError};
|
||||||
use solana_sdk::{transaction::TransactionError, transport::TransportError};
|
use solana_sdk::{signature::Keypair, transaction::TransactionError, transport::TransportError};
|
||||||
|
|
||||||
/// TODO: Add to SDK
|
/// TODO: Add to SDK
|
||||||
/// Instruction errors not mapped in the sdk
|
/// Instruction errors not mapped in the sdk
|
||||||
|
@ -35,3 +35,11 @@ pub fn map_transaction_error(transport_error: TransportError) -> ProgramError {
|
||||||
_ => panic!("TEST-TRANSPORT-ERROR: {:?}", transport_error),
|
_ => panic!("TEST-TRANSPORT-ERROR: {:?}", transport_error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clone_keypair(source: &Keypair) -> Keypair {
|
||||||
|
Keypair::from_bytes(&source.to_bytes()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOP (No Operation) Override function
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn NopOverride<T>(_: &mut T) {}
|
||||||
|
|
Loading…
Reference in New Issue