Skip to content

Commit 4af03a7

Browse files
authored
Add operation to transfer mint authority (#983)
1 parent 68f5fb6 commit 4af03a7

File tree

11 files changed

+573
-25
lines changed

11 files changed

+573
-25
lines changed

chains/solana/contracts/programs/base-token-pool/src/common.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,13 @@ pub struct OwnershipTransferred {
420420
pub mint: Pubkey,
421421
}
422422

423+
#[event]
424+
pub struct MintAuthorityTransferred {
425+
pub mint: Pubkey,
426+
pub old_mint_authority: Pubkey,
427+
pub new_mint_authority: Pubkey,
428+
}
429+
423430
#[error_code]
424431
pub enum CcipTokenPoolError {
425432
#[msg("Pool authority does not match token mint owner")]

chains/solana/contracts/programs/burnmint-token-pool/src/context.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,31 @@ pub struct InitializeTokenPool<'info> {
7878
pub config: Account<'info, PoolConfig>, // Global Config PDA of the Token Pool
7979
}
8080

81+
#[derive(Accounts)]
82+
pub struct TransferMintAuthority<'info> {
83+
#[account(
84+
mut,
85+
seeds = [POOL_STATE_SEED, mint.key().as_ref()],
86+
bump,
87+
constraint = valid_version(state.version, MAX_POOL_STATE_V) @ CcipTokenPoolError::InvalidVersion,
88+
)]
89+
pub state: Account<'info, State>,
90+
#[account(mut)]
91+
pub mint: InterfaceAccount<'info, Mint>, // underlying token that the pool wraps
92+
#[account(address = *mint.to_account_info().owner)]
93+
/// CHECK: CPI to token program
94+
pub token_program: AccountInfo<'info>,
95+
#[account(
96+
seeds = [POOL_SIGNER_SEED, mint.key().as_ref()],
97+
bump,
98+
address = state.config.pool_signer,
99+
)]
100+
/// CHECK: unchecked CPI signer
101+
pub pool_signer: UncheckedAccount<'info>,
102+
#[account(mut, address = state.config.owner @ CcipTokenPoolError::Unauthorized)]
103+
pub authority: Signer<'info>,
104+
}
105+
81106
#[derive(Accounts)]
82107
#[instruction(mint: Pubkey)]
83108
pub struct InitializeStateVersion<'info> {

chains/solana/contracts/programs/burnmint-token-pool/src/lib.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,66 @@ pub mod burnmint_token_pool {
5656
Ok(())
5757
}
5858

59+
// This method transfers the mint authority of the mint, so it does a CPI to the Token Program.
60+
// It is only defined in the burn and mint program as the mint authority is only used for minting tokens.
61+
pub fn transfer_mint_authority<'info>(
62+
ctx: Context<'_, '_, 'info, 'info, TransferMintAuthority<'info>>,
63+
new_mint_authority: Pubkey,
64+
) -> Result<()> {
65+
let old_mint_authority = ctx
66+
.accounts
67+
.mint
68+
.mint_authority
69+
.unwrap_or(ctx.accounts.pool_signer.key());
70+
71+
// Transfer the mint authority to the new mint authority using the corresponding Token Program. It can be token 22 or token spl
72+
let mut ix = spl_token_2022::instruction::set_authority(
73+
&spl_token_2022::ID, // use spl-token-2022 to compile instruction - change program later
74+
&ctx.accounts.mint.key(),
75+
Some(&new_mint_authority),
76+
spl_token_2022::instruction::AuthorityType::MintTokens,
77+
&old_mint_authority,
78+
&[ctx.accounts.pool_signer.key],
79+
)?;
80+
ix.program_id = ctx.accounts.state.config.token_program.key(); // set to user specified program
81+
82+
let seeds = &[
83+
POOL_SIGNER_SEED,
84+
&ctx.accounts.mint.key().to_bytes(),
85+
&[ctx.bumps.pool_signer],
86+
];
87+
88+
let account_infos: Vec<AccountInfo> =
89+
if old_mint_authority != ctx.accounts.pool_signer.key() {
90+
// If the old mint authority is not the pool signer, we need to include the multisig account in the instruction
91+
let multisig_account = ctx
92+
.remaining_accounts
93+
.iter()
94+
.find(|acc| acc.key() == old_mint_authority)
95+
.ok_or(CcipBnMTokenPoolError::InvalidMultisig)?;
96+
vec![
97+
ctx.accounts.mint.to_account_info().clone(),
98+
multisig_account.clone(),
99+
ctx.accounts.pool_signer.to_account_info().clone(),
100+
]
101+
} else {
102+
vec![
103+
ctx.accounts.mint.to_account_info().clone(),
104+
ctx.accounts.pool_signer.to_account_info().clone(),
105+
]
106+
};
107+
108+
invoke_signed(&ix, &account_infos, &[&seeds[..]])?;
109+
110+
emit!(MintAuthorityTransferred {
111+
mint: ctx.accounts.mint.key(),
112+
old_mint_authority,
113+
new_mint_authority,
114+
});
115+
116+
Ok(())
117+
}
118+
59119
/// Returns the program type (name) and version.
60120
/// Used by offchain code to easily determine which program & version is being interacted with.
61121
///

chains/solana/contracts/target/idl/base_token_pool.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,26 @@
579579
}
580580
]
581581
},
582+
{
583+
"name": "MintAuthorityTransferred",
584+
"fields": [
585+
{
586+
"name": "mint",
587+
"type": "publicKey",
588+
"index": false
589+
},
590+
{
591+
"name": "oldMintAuthority",
592+
"type": "publicKey",
593+
"index": false
594+
},
595+
{
596+
"name": "newMintAuthority",
597+
"type": "publicKey",
598+
"index": false
599+
}
600+
]
601+
},
582602
{
583603
"name": "TokensConsumed",
584604
"fields": [

chains/solana/contracts/target/idl/burnmint_token_pool.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@
109109
}
110110
]
111111
},
112+
{
113+
"name": "transferMintAuthority",
114+
"accounts": [
115+
{
116+
"name": "state",
117+
"isMut": true,
118+
"isSigner": false
119+
},
120+
{
121+
"name": "mint",
122+
"isMut": true,
123+
"isSigner": false
124+
},
125+
{
126+
"name": "tokenProgram",
127+
"isMut": false,
128+
"isSigner": false
129+
},
130+
{
131+
"name": "poolSigner",
132+
"isMut": false,
133+
"isSigner": false
134+
},
135+
{
136+
"name": "authority",
137+
"isMut": true,
138+
"isSigner": true
139+
}
140+
],
141+
"args": [
142+
{
143+
"name": "newMintAuthority",
144+
"type": "publicKey"
145+
}
146+
]
147+
},
112148
{
113149
"name": "typeVersion",
114150
"docs": [

chains/solana/contracts/target/types/base_token_pool.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,26 @@ export type BaseTokenPool = {
579579
}
580580
]
581581
},
582+
{
583+
"name": "MintAuthorityTransferred",
584+
"fields": [
585+
{
586+
"name": "mint",
587+
"type": "publicKey",
588+
"index": false
589+
},
590+
{
591+
"name": "oldMintAuthority",
592+
"type": "publicKey",
593+
"index": false
594+
},
595+
{
596+
"name": "newMintAuthority",
597+
"type": "publicKey",
598+
"index": false
599+
}
600+
]
601+
},
582602
{
583603
"name": "TokensConsumed",
584604
"fields": [
@@ -1287,6 +1307,26 @@ export const IDL: BaseTokenPool = {
12871307
}
12881308
]
12891309
},
1310+
{
1311+
"name": "MintAuthorityTransferred",
1312+
"fields": [
1313+
{
1314+
"name": "mint",
1315+
"type": "publicKey",
1316+
"index": false
1317+
},
1318+
{
1319+
"name": "oldMintAuthority",
1320+
"type": "publicKey",
1321+
"index": false
1322+
},
1323+
{
1324+
"name": "newMintAuthority",
1325+
"type": "publicKey",
1326+
"index": false
1327+
}
1328+
]
1329+
},
12901330
{
12911331
"name": "TokensConsumed",
12921332
"fields": [

chains/solana/contracts/target/types/burnmint_token_pool.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,42 @@ export type BurnmintTokenPool = {
109109
}
110110
]
111111
},
112+
{
113+
"name": "transferMintAuthority",
114+
"accounts": [
115+
{
116+
"name": "state",
117+
"isMut": true,
118+
"isSigner": false
119+
},
120+
{
121+
"name": "mint",
122+
"isMut": true,
123+
"isSigner": false
124+
},
125+
{
126+
"name": "tokenProgram",
127+
"isMut": false,
128+
"isSigner": false
129+
},
130+
{
131+
"name": "poolSigner",
132+
"isMut": false,
133+
"isSigner": false
134+
},
135+
{
136+
"name": "authority",
137+
"isMut": true,
138+
"isSigner": true
139+
}
140+
],
141+
"args": [
142+
{
143+
"name": "newMintAuthority",
144+
"type": "publicKey"
145+
}
146+
]
147+
},
112148
{
113149
"name": "typeVersion",
114150
"docs": [
@@ -806,6 +842,42 @@ export const IDL: BurnmintTokenPool = {
806842
}
807843
]
808844
},
845+
{
846+
"name": "transferMintAuthority",
847+
"accounts": [
848+
{
849+
"name": "state",
850+
"isMut": true,
851+
"isSigner": false
852+
},
853+
{
854+
"name": "mint",
855+
"isMut": true,
856+
"isSigner": false
857+
},
858+
{
859+
"name": "tokenProgram",
860+
"isMut": false,
861+
"isSigner": false
862+
},
863+
{
864+
"name": "poolSigner",
865+
"isMut": false,
866+
"isSigner": false
867+
},
868+
{
869+
"name": "authority",
870+
"isMut": true,
871+
"isSigner": true
872+
}
873+
],
874+
"args": [
875+
{
876+
"name": "newMintAuthority",
877+
"type": "publicKey"
878+
}
879+
]
880+
},
809881
{
810882
"name": "typeVersion",
811883
"docs": [

0 commit comments

Comments
 (0)