Governance: Transfer freeze and close authorities (#2799)

* fix: transfer mint freeze authority for mint governance

* chore: update transfer_mint_authorities comments

* feat: transfer close_authority when creating token account governance

* chore: update spl-token function names

* chore: remove redundant set_spl_token_mint_authority
This commit is contained in:
Sebastian Bor 2022-01-25 11:02:43 +00:00 committed by GitHub
parent 6d62ea6cc7
commit 56953b2286
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 231 additions and 69 deletions

View File

@ -107,6 +107,7 @@ impl GovernanceChatProgramTest {
.create_mint(
&governing_token_mint_keypair,
&governing_token_mint_authority.pubkey(),
None,
)
.await;

View File

@ -337,7 +337,7 @@ pub enum GovernanceInstruction {
/// 0. `[]` Realm account the created Governance belongs to
/// 1. `[writable]` Mint Governance account. PDA seeds: ['mint-governance', realm, governed_mint]
/// 2. `[writable]` Mint governed by this Governance account
/// 3. `[signer]` Current Mint Authority
/// 3. `[signer]` Current Mint authority (MintTokens and optionally FreezeAccount)
/// 4. `[]` Governing TokenOwnerRecord account
/// 5. `[signer]` Payer
/// 6. `[]` SPL Token program
@ -352,10 +352,10 @@ pub enum GovernanceInstruction {
config: GovernanceConfig,
#[allow(dead_code)]
/// Indicates whether Mint's authority should be transferred to the Governance PDA
/// Indicates whether Mint's authorities (MintTokens, FreezeAccount) should be transferred to the Governance PDA
/// If it's set to false then it can be done at a later time
/// However the instruction would validate the current mint authority signed the transaction nonetheless
transfer_mint_authority: bool,
transfer_mint_authorities: bool,
},
/// Creates Token Governance account which governs a token account
@ -363,7 +363,7 @@ pub enum GovernanceInstruction {
/// 0. `[]` Realm account the created Governance belongs to
/// 1. `[writable]` Token Governance account. PDA seeds: ['token-governance', realm, governed_token]
/// 2. `[writable]` Token account governed by this Governance account
/// 3. `[signer]` Current Token account
/// 3. `[signer]` Current token account authority (AccountOwner and optionally CloseAccount)
/// 4. `[]` Governing TokenOwnerRecord account
/// 5. `[signer]` Payer
/// 6. `[]` SPL Token program
@ -378,10 +378,10 @@ pub enum GovernanceInstruction {
config: GovernanceConfig,
#[allow(dead_code)]
/// Indicates whether token owner should be transferred to the Governance PDA
/// Indicates whether the token account authorities (AccountOwner and optionally CloseAccount) should be transferred to the Governance PDA
/// If it's set to false then it can be done at a later time
/// However the instruction would validate the current token owner signed the transaction nonetheless
transfer_token_owner: bool,
transfer_account_authorities: bool,
},
/// Sets GovernanceConfig for a Governance
@ -756,7 +756,7 @@ pub fn create_mint_governance(
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
transfer_mint_authority: bool,
transfer_mint_authorities: bool,
) -> Instruction {
let mint_governance_address = get_mint_governance_address(program_id, realm, governed_mint);
@ -777,7 +777,7 @@ pub fn create_mint_governance(
let instruction = GovernanceInstruction::CreateMintGovernance {
config,
transfer_mint_authority,
transfer_mint_authorities,
};
Instruction {
@ -801,7 +801,7 @@ pub fn create_token_governance(
voter_weight_record: Option<Pubkey>,
// Args
config: GovernanceConfig,
transfer_token_owner: bool,
transfer_account_authorities: bool,
) -> Instruction {
let token_governance_address = get_token_governance_address(program_id, realm, governed_token);
@ -822,7 +822,7 @@ pub fn create_token_governance(
let instruction = GovernanceInstruction::CreateTokenGovernance {
config,
transfer_token_owner,
transfer_account_authorities,
};
Instruction {

View File

@ -119,13 +119,20 @@ pub fn process_instruction(
GovernanceInstruction::CreateMintGovernance {
config,
transfer_mint_authority,
} => process_create_mint_governance(program_id, accounts, config, transfer_mint_authority),
transfer_mint_authorities,
} => {
process_create_mint_governance(program_id, accounts, config, transfer_mint_authorities)
}
GovernanceInstruction::CreateTokenGovernance {
config,
transfer_token_owner,
} => process_create_token_governance(program_id, accounts, config, transfer_token_owner),
transfer_account_authorities,
} => process_create_token_governance(
program_id,
accounts,
config,
transfer_account_authorities,
),
GovernanceInstruction::CreateAccountGovernance { config } => {
process_create_account_governance(program_id, accounts, config)

View File

@ -10,23 +10,27 @@ use crate::{
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::spl_token::{assert_spl_token_mint_authority_is_signer, set_spl_token_mint_authority},
tools::spl_token::{
assert_spl_token_mint_authority_is_signer, set_spl_token_account_authority,
},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_pack::Pack,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use spl_token::{instruction::AuthorityType, state::Mint};
/// Processes CreateMintGovernance instruction
pub fn process_create_mint_governance(
program_id: &Pubkey,
accounts: &[AccountInfo],
config: GovernanceConfig,
transfer_mint_authority: bool,
transfer_mint_authorities: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -84,13 +88,28 @@ pub fn process_create_mint_governance(
rent,
)?;
if transfer_mint_authority {
set_spl_token_mint_authority(
if transfer_mint_authorities {
set_spl_token_account_authority(
governed_mint_info,
governed_mint_authority_info,
mint_governance_info.key,
AuthorityType::MintTokens,
spl_token_info,
)?;
// If the mint has freeze_authority then transfer it as well
let mint_data = Mint::unpack(&governed_mint_info.data.borrow())?;
// Note: The code assumes mint_authority==freeze_authority
// If this is not the case then the caller should set freeze_authority accordingly before making the transfer
if mint_data.freeze_authority.is_some() {
set_spl_token_account_authority(
governed_mint_info,
governed_mint_authority_info,
mint_governance_info.key,
AuthorityType::FreezeAccount,
spl_token_info,
)?;
}
} else {
assert_spl_token_mint_authority_is_signer(
governed_mint_info,

View File

@ -10,23 +10,25 @@ use crate::{
realm::get_realm_data,
token_owner_record::get_token_owner_record_data_for_realm,
},
tools::spl_token::{assert_spl_token_owner_is_signer, set_spl_token_owner},
tools::spl_token::{assert_spl_token_owner_is_signer, set_spl_token_account_authority},
};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
program_pack::Pack,
pubkey::Pubkey,
rent::Rent,
sysvar::Sysvar,
};
use spl_governance_tools::account::create_and_serialize_account_signed;
use spl_token::{instruction::AuthorityType, state::Account};
/// Processes CreateTokenGovernance instruction
pub fn process_create_token_governance(
program_id: &Pubkey,
accounts: &[AccountInfo],
config: GovernanceConfig,
transfer_token_owner: bool,
transfer_account_authorities: bool,
) -> ProgramResult {
let account_info_iter = &mut accounts.iter();
@ -84,13 +86,28 @@ pub fn process_create_token_governance(
rent,
)?;
if transfer_token_owner {
set_spl_token_owner(
if transfer_account_authorities {
set_spl_token_account_authority(
governed_token_info,
governed_token_owner_info,
token_governance_info.key,
AuthorityType::AccountOwner,
spl_token_info,
)?;
// If the token account has close_authority then transfer it as well
let token_account_data = Account::unpack(&governed_token_info.data.borrow())?;
// Note: The code assumes owner==close_authority
// If this is not the case then the caller should set close_authority accordingly before making the transfer
if token_account_data.close_authority.is_some() {
set_spl_token_account_authority(
governed_token_info,
governed_token_owner_info,
token_governance_info.key,
AuthorityType::CloseAccount,
spl_token_info,
)?;
}
} else {
assert_spl_token_owner_is_signer(governed_token_info, governed_token_owner_info)?;
}

View File

@ -14,7 +14,7 @@ use solana_program::{
system_instruction,
};
use spl_token::{
instruction::set_authority,
instruction::{set_authority, AuthorityType},
state::{Account, Mint},
};
@ -287,34 +287,6 @@ pub fn assert_spl_token_mint_authority_is_signer(
Ok(())
}
/// Sets new mint authority
pub fn set_spl_token_mint_authority<'a>(
mint_info: &AccountInfo<'a>,
mint_authority: &AccountInfo<'a>,
new_mint_authority: &Pubkey,
spl_token_info: &AccountInfo<'a>,
) -> Result<(), ProgramError> {
let set_authority_ix = set_authority(
&spl_token::id(),
mint_info.key,
Some(new_mint_authority),
spl_token::instruction::AuthorityType::MintTokens,
mint_authority.key,
&[],
)?;
invoke(
&set_authority_ix,
&[
mint_info.clone(),
mint_authority.clone(),
spl_token_info.clone(),
],
)?;
Ok(())
}
/// Asserts current token owner matches the given owner and it's signer of the transaction
pub fn assert_spl_token_owner_is_signer(
token_info: &AccountInfo,
@ -333,27 +305,28 @@ pub fn assert_spl_token_owner_is_signer(
Ok(())
}
/// Sets new token account owner
pub fn set_spl_token_owner<'a>(
token_info: &AccountInfo<'a>,
token_owner: &AccountInfo<'a>,
new_token_owner: &Pubkey,
/// Sets spl-token account (Mint or TokenAccount) authority
pub fn set_spl_token_account_authority<'a>(
account_info: &AccountInfo<'a>,
account_authority: &AccountInfo<'a>,
new_account_authority: &Pubkey,
authority_type: AuthorityType,
spl_token_info: &AccountInfo<'a>,
) -> Result<(), ProgramError> {
let set_authority_ix = set_authority(
&spl_token::id(),
token_info.key,
Some(new_token_owner),
spl_token::instruction::AuthorityType::AccountOwner,
token_owner.key,
account_info.key,
Some(new_account_authority),
authority_type,
account_authority.key,
&[],
)?;
invoke(
&set_authority_ix,
&[
token_info.clone(),
token_owner.clone(),
account_info.clone(),
account_authority.clone(),
spl_token_info.clone(),
],
)?;

View File

@ -226,3 +226,48 @@ async fn test_create_mint_governance_with_invalid_realm_error() {
// Assert
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}
#[tokio::test]
async fn test_create_mint_governance_with_freeze_authority_transfer() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_mint_cookie = governance_test.with_freezable_governed_mint().await;
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
// Act
let mint_governance_cookie = governance_test
.with_mint_governance(
&realm_cookie,
&governed_mint_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// // Assert
let mint_governance_account = governance_test
.get_governance_account(&mint_governance_cookie.address)
.await;
assert_eq!(mint_governance_cookie.account, mint_governance_account);
let mint_account = governance_test
.get_mint_account(&governed_mint_cookie.address)
.await;
assert_eq!(
mint_governance_cookie.address,
mint_account.mint_authority.unwrap()
);
assert_eq!(
mint_governance_cookie.address,
mint_account.freeze_authority.unwrap()
);
}

View File

@ -8,7 +8,7 @@ use solana_sdk::{signature::Keypair, signer::Signer};
use spl_governance::error::GovernanceError;
use spl_governance_tools::error::GovernanceToolsError;
use spl_token::error::TokenError;
use spl_token::{error::TokenError, instruction::AuthorityType};
#[tokio::test]
async fn test_create_token_governance() {
@ -225,3 +225,54 @@ async fn test_create_token_governance_with_invalid_realm_error() {
// Assert
assert_eq!(err, GovernanceToolsError::InvalidAccountType.into());
}
#[tokio::test]
async fn test_create_token_governance_with_close_authority_transfer() {
// Arrange
let mut governance_test = GovernanceProgramTest::start_new().await;
let realm_cookie = governance_test.with_realm().await;
let governed_token_cookie = governance_test.with_governed_token().await;
governance_test
.bench
.set_spl_token_account_authority(
&governed_token_cookie.address,
&governed_token_cookie.token_owner,
Some(&governed_token_cookie.token_owner.pubkey()),
AuthorityType::CloseAccount,
)
.await;
let token_owner_record_cookie = governance_test
.with_community_token_deposit(&realm_cookie)
.await
.unwrap();
// Act
let token_governance_cookie = governance_test
.with_token_governance(
&realm_cookie,
&governed_token_cookie,
&token_owner_record_cookie,
)
.await
.unwrap();
// Assert
let token_governance_account = governance_test
.get_governance_account(&token_governance_cookie.address)
.await;
assert_eq!(token_governance_cookie.account, token_governance_account);
let token_account = governance_test
.get_token_account(&governed_token_cookie.address)
.await;
assert_eq!(token_governance_cookie.address, token_account.owner);
assert_eq!(
token_governance_cookie.address,
token_account.close_authority.unwrap()
);
}

View File

@ -176,6 +176,7 @@ impl GovernanceProgramTest {
.create_mint(
&community_token_mint_keypair,
&community_token_mint_authority.pubkey(),
None,
)
.await;
@ -197,6 +198,7 @@ impl GovernanceProgramTest {
.create_mint(
&council_token_mint_keypair,
&council_token_mint_authority.pubkey(),
None,
)
.await;
@ -962,16 +964,34 @@ impl GovernanceProgramTest {
#[allow(dead_code)]
pub async fn with_governed_mint(&mut self) -> GovernedMintCookie {
let mint_keypair = Keypair::new();
let mint_authority = Keypair::new();
self.with_governed_mint_impl(&mint_authority, None).await
}
#[allow(dead_code)]
pub async fn with_freezable_governed_mint(&mut self) -> GovernedMintCookie {
let mint_authority = Keypair::new();
self.with_governed_mint_impl(&mint_authority, Some(&mint_authority.pubkey()))
.await
}
#[allow(dead_code)]
pub async fn with_governed_mint_impl(
&mut self,
mint_authority: &Keypair,
freeze_authority: Option<&Pubkey>,
) -> GovernedMintCookie {
let mint_keypair = Keypair::new();
self.bench
.create_mint(&mint_keypair, &mint_authority.pubkey())
.create_mint(&mint_keypair, &mint_authority.pubkey(), freeze_authority)
.await;
GovernedMintCookie {
address: mint_keypair.pubkey(),
mint_authority,
mint_authority: clone_keypair(mint_authority),
transfer_mint_authority: true,
}
}
@ -982,7 +1002,7 @@ impl GovernanceProgramTest {
let mint_authority = Keypair::new();
self.bench
.create_mint(&mint_keypair, &mint_authority.pubkey())
.create_mint(&mint_keypair, &mint_authority.pubkey(), None)
.await;
let token_keypair = Keypair::new();

View File

@ -12,6 +12,7 @@ use solana_sdk::{account::Account, signature::Keypair, signer::Signer, transacti
use bincode::deserialize;
use spl_token::instruction::{set_authority, AuthorityType};
use tools::clone_keypair;
use crate::tools::map_transaction_error;
@ -113,7 +114,12 @@ impl ProgramTestBench {
}
}
pub async fn create_mint(&mut self, mint_keypair: &Keypair, mint_authority: &Pubkey) {
pub async fn create_mint(
&mut self,
mint_keypair: &Keypair,
mint_authority: &Pubkey,
freeze_authority: Option<&Pubkey>,
) {
let mint_rent = self.rent.minimum_balance(spl_token::state::Mint::LEN);
let instructions = [
@ -128,7 +134,7 @@ impl ProgramTestBench {
&spl_token::id(),
&mint_keypair.pubkey(),
mint_authority,
None,
freeze_authority,
0,
)
.unwrap(),
@ -139,6 +145,29 @@ impl ProgramTestBench {
.unwrap();
}
/// Sets spl-token program account (Mint or TokenAccount) authority
pub async fn set_spl_token_account_authority(
&mut self,
account: &Pubkey,
account_authority: &Keypair,
new_authority: Option<&Pubkey>,
authority_type: AuthorityType,
) {
let set_authority_ix = set_authority(
&spl_token::id(),
account,
new_authority,
authority_type,
&account_authority.pubkey(),
&[],
)
.unwrap();
self.process_transaction(&[set_authority_ix], Some(&[account_authority]))
.await
.unwrap();
}
#[allow(dead_code)]
pub async fn create_empty_token_account(
&mut self,