Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 329d7e3

Browse files
Governance: Create mint governance (#1953)
* feat: implement CreateMintGovernance instruction * fix: ensure mint is initialised in assert_is_valid_spl_token_mint * fix: ensure token account is initialised in assert_is_valid_spl_token_account * chore: move account type and instruction to the end to avoid breaking changes * chore: make clippy happy Co-authored-by: Jon Cinque <[email protected]>
1 parent 683d34d commit 329d7e3

File tree

14 files changed

+629
-54
lines changed

14 files changed

+629
-54
lines changed

governance/program/src/error.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ pub enum GovernanceError {
168168
#[error("Invalid account owner")]
169169
InvalidAccountOwner,
170170

171+
/// Account doesn't exist
172+
#[error("Account doesn't exist")]
173+
AccountDoesNotExist,
174+
171175
/// Invalid Account type
172176
#[error("Invalid Account type")]
173177
InvalidAccountType,
@@ -180,6 +184,18 @@ pub enum GovernanceError {
180184
#[error("Proposal does not belong to given Governing Mint")]
181185
InvalidGoverningMintForProposal,
182186

187+
/// Current mint authority must sign transaction
188+
#[error("Current mint authority must sign transaction")]
189+
MintAuthorityMustSign,
190+
191+
/// Invalid mint authority
192+
#[error("Invalid mint authority")]
193+
InvalidMintAuthority,
194+
195+
/// Mint has no authority
196+
#[error("Mint has no authority")]
197+
MintHasNoAuthority,
198+
183199
/// ---- SPL Token Tools Errors ----
184200
185201
/// Invalid Token account owner
@@ -194,6 +210,10 @@ pub enum GovernanceError {
194210
#[error("Token Account is not initialized")]
195211
SplTokenAccountNotInitialized,
196212

213+
/// Token Account doesn't exist
214+
#[error("Token Account doesn't exist")]
215+
SplTokenAccountDoesNotExist,
216+
197217
/// Token account data is invalid
198218
#[error("Token account data is invalid")]
199219
SplTokenInvalidTokenAccountData,
@@ -206,6 +226,10 @@ pub enum GovernanceError {
206226
#[error("Token Mint account is not initialized")]
207227
SplTokenMintNotInitialized,
208228

229+
/// Token Mint account doesn't exist
230+
#[error("Token Mint account doesn't exist")]
231+
SplTokenMintDoesNotExist,
232+
209233
/// ---- Bpf Upgradable Loader Tools Errors ----
210234
211235
/// Invalid ProgramData account Address

governance/program/src/instruction.rs

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
use crate::{
44
state::{
55
governance::{
6-
get_account_governance_address, get_program_governance_address, GovernanceConfig,
6+
get_account_governance_address, get_mint_governance_address,
7+
get_program_governance_address, GovernanceConfig,
78
},
89
proposal::get_proposal_address,
910
proposal_instruction::{get_proposal_instruction_address, InstructionData},
@@ -126,7 +127,7 @@ pub enum GovernanceInstruction {
126127
config: GovernanceConfig,
127128

128129
#[allow(dead_code)]
129-
/// Indicate whether Program's upgrade_authority should be transferred to the Governance PDA
130+
/// Indicates whether Program's upgrade_authority should be transferred to the Governance PDA
130131
/// If it's set to false then it can be done at a later time
131132
/// However the instruction would validate the current upgrade_authority signed the transaction nonetheless
132133
transfer_upgrade_authority: bool,
@@ -290,6 +291,28 @@ pub enum GovernanceInstruction {
290291
/// 2. `[]` Clock sysvar
291292
/// 3+ Any extra accounts that are part of the instruction, in order
292293
ExecuteInstruction,
294+
295+
/// Creates Mint Governance account which governs a mint
296+
///
297+
/// 0. `[]` Realm account the created Governance belongs to
298+
/// 1. `[writable]` Mint Governance account. PDA seeds: ['mint-governance', realm, governed_mint]
299+
/// 2. `[writable]` Mint governed by this Governance account
300+
/// 3. `[signer]` Current Mint Authority
301+
/// 4. `[signer]` Payer
302+
/// 5. `[]` SPL Token program
303+
/// 6. `[]` System program
304+
/// 7. `[]` Sysvar Rent
305+
CreateMintGovernance {
306+
#[allow(dead_code)]
307+
/// Governance config
308+
config: GovernanceConfig,
309+
310+
#[allow(dead_code)]
311+
/// Indicates whether Mint's authority should be transferred to the Governance PDA
312+
/// If it's set to false then it can be done at a later time
313+
/// However the instruction would validate the current mint authority signed the transaction nonetheless
314+
transfer_mint_authority: bool,
315+
},
293316
}
294317

295318
/// Creates CreateRealm instruction
@@ -514,6 +537,42 @@ pub fn create_program_governance(
514537
}
515538
}
516539

540+
/// Creates CreateMintGovernance instruction
541+
pub fn create_mint_governance(
542+
program_id: &Pubkey,
543+
// Accounts
544+
governed_mint_authority: &Pubkey,
545+
payer: &Pubkey,
546+
// Args
547+
config: GovernanceConfig,
548+
transfer_mint_authority: bool,
549+
) -> Instruction {
550+
let mint_governance_address =
551+
get_mint_governance_address(program_id, &config.realm, &config.governed_account);
552+
553+
let accounts = vec![
554+
AccountMeta::new_readonly(config.realm, false),
555+
AccountMeta::new(mint_governance_address, false),
556+
AccountMeta::new(config.governed_account, false),
557+
AccountMeta::new_readonly(*governed_mint_authority, true),
558+
AccountMeta::new_readonly(*payer, true),
559+
AccountMeta::new_readonly(spl_token::id(), false),
560+
AccountMeta::new_readonly(system_program::id(), false),
561+
AccountMeta::new_readonly(sysvar::rent::id(), false),
562+
];
563+
564+
let instruction = GovernanceInstruction::CreateMintGovernance {
565+
config,
566+
transfer_mint_authority,
567+
};
568+
569+
Instruction {
570+
program_id: *program_id,
571+
accounts,
572+
data: instruction.try_to_vec().unwrap(),
573+
}
574+
}
575+
517576
/// Creates CreateProposal instruction
518577
#[allow(clippy::too_many_arguments)]
519578
pub fn create_proposal(

governance/program/src/processor/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ mod process_add_signatory;
44
mod process_cancel_proposal;
55
mod process_cast_vote;
66
mod process_create_account_governance;
7+
mod process_create_mint_governance;
78
mod process_create_program_governance;
89
mod process_create_proposal;
910
mod process_create_realm;
@@ -25,6 +26,7 @@ use process_add_signatory::*;
2526
use process_cancel_proposal::*;
2627
use process_cast_vote::*;
2728
use process_create_account_governance::*;
29+
use process_create_mint_governance::*;
2830
use process_create_program_governance::*;
2931
use process_create_proposal::*;
3032
use process_create_realm::*;
@@ -96,6 +98,11 @@ pub fn process_instruction(
9698
transfer_upgrade_authority,
9799
),
98100

101+
GovernanceInstruction::CreateMintGovernance {
102+
config,
103+
transfer_mint_authority,
104+
} => process_create_mint_governance(program_id, accounts, config, transfer_mint_authority),
105+
99106
GovernanceInstruction::CreateAccountGovernance { config } => {
100107
process_create_account_governance(program_id, accounts, config)
101108
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//! Program state processor
2+
3+
use crate::{
4+
state::{
5+
enums::GovernanceAccountType,
6+
governance::{
7+
assert_is_valid_governance_config, get_mint_governance_address_seeds, Governance,
8+
GovernanceConfig,
9+
},
10+
},
11+
tools::{
12+
account::create_and_serialize_account_signed,
13+
spl_token::{assert_spl_token_mint_authority_is_signer, set_spl_token_mint_authority},
14+
},
15+
};
16+
use solana_program::{
17+
account_info::{next_account_info, AccountInfo},
18+
entrypoint::ProgramResult,
19+
pubkey::Pubkey,
20+
rent::Rent,
21+
sysvar::Sysvar,
22+
};
23+
24+
/// Processes CreateMintGovernance instruction
25+
pub fn process_create_mint_governance(
26+
program_id: &Pubkey,
27+
accounts: &[AccountInfo],
28+
config: GovernanceConfig,
29+
transfer_mint_authority: bool,
30+
) -> ProgramResult {
31+
let account_info_iter = &mut accounts.iter();
32+
33+
let realm_info = next_account_info(account_info_iter)?; // 0
34+
let mint_governance_info = next_account_info(account_info_iter)?; // 1
35+
36+
let governed_mint_info = next_account_info(account_info_iter)?; // 2
37+
let governed_mint_authority_info = next_account_info(account_info_iter)?; // 3
38+
39+
let payer_info = next_account_info(account_info_iter)?; // 4
40+
let spl_token_info = next_account_info(account_info_iter)?; // 5
41+
42+
let system_info = next_account_info(account_info_iter)?; // 6
43+
44+
let rent_sysvar_info = next_account_info(account_info_iter)?; // 7
45+
let rent = &Rent::from_account_info(rent_sysvar_info)?;
46+
47+
assert_is_valid_governance_config(program_id, &config, realm_info)?;
48+
49+
let mint_governance_data = Governance {
50+
account_type: GovernanceAccountType::MintGovernance,
51+
config: config.clone(),
52+
proposals_count: 0,
53+
};
54+
55+
create_and_serialize_account_signed::<Governance>(
56+
payer_info,
57+
mint_governance_info,
58+
&mint_governance_data,
59+
&get_mint_governance_address_seeds(&config.realm, &config.governed_account),
60+
program_id,
61+
system_info,
62+
rent,
63+
)?;
64+
65+
if transfer_mint_authority {
66+
set_spl_token_mint_authority(
67+
governed_mint_info,
68+
governed_mint_authority_info,
69+
mint_governance_info.key,
70+
spl_token_info,
71+
)?;
72+
} else {
73+
assert_spl_token_mint_authority_is_signer(
74+
governed_mint_info,
75+
governed_mint_authority_info,
76+
)?;
77+
}
78+
79+
Ok(())
80+
}

governance/program/src/state/enums.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ pub enum GovernanceAccountType {
3232

3333
/// ProposalInstruction account which holds an instruction to execute for Proposal
3434
ProposalInstruction,
35+
36+
/// Mint Governance account
37+
MintGovernance,
3538
}
3639

3740
impl Default for GovernanceAccountType {

governance/program/src/state/governance.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ impl IsInitialized for Governance {
5858
fn is_initialized(&self) -> bool {
5959
self.account_type == GovernanceAccountType::AccountGovernance
6060
|| self.account_type == GovernanceAccountType::ProgramGovernance
61+
|| self.account_type == GovernanceAccountType::MintGovernance
6162
}
6263
}
6364

@@ -73,6 +74,9 @@ impl Governance {
7374
&self.config.realm,
7475
&self.config.governed_account,
7576
),
77+
GovernanceAccountType::MintGovernance => {
78+
get_mint_governance_address_seeds(&self.config.realm, &self.config.governed_account)
79+
}
7680
_ => return Err(GovernanceError::InvalidAccountType.into()),
7781
};
7882

@@ -115,6 +119,29 @@ pub fn get_program_governance_address<'a>(
115119
.0
116120
}
117121

122+
/// Returns MintGovernance PDA seeds
123+
pub fn get_mint_governance_address_seeds<'a>(
124+
realm: &'a Pubkey,
125+
governed_mint: &'a Pubkey,
126+
) -> [&'a [u8]; 3] {
127+
// 'mint-governance' prefix ensures uniqueness of the PDA
128+
// Note: Only the current mint authority can create an account with this PDA using CreateMintGovernance instruction
129+
[b"mint-governance", realm.as_ref(), governed_mint.as_ref()]
130+
}
131+
132+
/// Returns MintGovernance PDA address
133+
pub fn get_mint_governance_address<'a>(
134+
program_id: &Pubkey,
135+
realm: &'a Pubkey,
136+
governed_mint: &'a Pubkey,
137+
) -> Pubkey {
138+
Pubkey::find_program_address(
139+
&get_mint_governance_address_seeds(realm, governed_mint),
140+
program_id,
141+
)
142+
.0
143+
}
144+
118145
/// Returns AccountGovernance PDA seeds
119146
pub fn get_account_governance_address_seeds<'a>(
120147
realm: &'a Pubkey,

governance/program/src/tools/account.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
8989
owner_program_id: &Pubkey,
9090
) -> Result<T, ProgramError> {
9191
if account_info.data_is_empty() {
92-
return Err(ProgramError::UninitializedAccount);
92+
return Err(GovernanceError::AccountDoesNotExist.into());
9393
}
9494
if account_info.owner != owner_program_id {
9595
return Err(GovernanceError::InvalidAccountOwner.into());
@@ -103,7 +103,8 @@ pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
103103
}
104104
}
105105

106-
/// Asserts the given account is not empty, owned given program and of the expected type
106+
/// Asserts the given account is not empty, owned by the given program and of the expected type
107+
/// Note: The function assumes the account type T is stored as the first element in the account data
107108
pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
108109
account_info: &AccountInfo,
109110
expected_account_type: T,
@@ -114,7 +115,7 @@ pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
114115
}
115116

116117
if account_info.data_is_empty() {
117-
return Err(ProgramError::UninitializedAccount);
118+
return Err(GovernanceError::AccountDoesNotExist.into());
118119
}
119120

120121
let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;

governance/program/src/tools/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ pub mod account;
55
pub mod spl_token;
66

77
pub mod bpf_loader_upgradeable;
8+
9+
pub mod pack;

governance/program/src/tools/pack.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//! General purpose packing utility functions
2+
3+
use arrayref::array_refs;
4+
use solana_program::{program_error::ProgramError, program_option::COption, pubkey::Pubkey};
5+
6+
/// Unpacks COption from a slice
7+
pub fn unpack_coption_pubkey(src: &[u8; 36]) -> Result<COption<Pubkey>, ProgramError> {
8+
let (tag, body) = array_refs![src, 4, 32];
9+
match *tag {
10+
[0, 0, 0, 0] => Ok(COption::None),
11+
[1, 0, 0, 0] => Ok(COption::Some(Pubkey::new_from_array(*body))),
12+
_ => Err(ProgramError::InvalidAccountData),
13+
}
14+
}

0 commit comments

Comments
 (0)