Skip to content

Commit dcc0305

Browse files
authored
solana: Allow transferring mint authority (#570)
* Add one step transfer ix: `set_token_authority_one_step_unchecked` * Add two step version: `set_token_authority` and `claim_token_authority` (and `revert_token_authority` to reset authority back to `token_authority`) * `createSetTokenAuthorityInstruction` helper function in NTT namespace * Update IDL
1 parent 404c0aa commit dcc0305

File tree

7 files changed

+1118
-11
lines changed

7 files changed

+1118
-11
lines changed

solana/programs/example-native-token-transfers/src/error.rs

+6
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ pub enum NTTError {
5353
BitmapIndexOutOfBounds,
5454
#[msg("NoRegisteredTransceivers")]
5555
NoRegisteredTransceivers,
56+
#[msg("NotPaused")]
57+
NotPaused,
58+
#[msg("InvalidPendingTokenAuthority")]
59+
InvalidPendingTokenAuthority,
60+
#[msg("IncorrectRentPayer")]
61+
IncorrectRentPayer,
5662
}
5763

5864
impl From<ScalingError> for NTTError {

solana/programs/example-native-token-transfers/src/instructions/admin.rs

+208-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use anchor_lang::prelude::*;
2+
use anchor_spl::{token_2022::spl_token_2022::instruction::AuthorityType, token_interface};
23
use ntt_messages::chain_id::ChainId;
34
use wormhole_solana_utils::cpi::bpf_loader_upgradeable::{self, BpfLoaderUpgradeable};
45

@@ -9,6 +10,7 @@ use crate::{
910
config::Config,
1011
error::NTTError,
1112
peer::NttManagerPeer,
13+
pending_token_authority::PendingTokenAuthority,
1214
queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit, rate_limit::RateLimitState},
1315
registered_transceiver::RegisteredTransceiver,
1416
};
@@ -154,6 +156,210 @@ pub fn claim_ownership(ctx: Context<ClaimOwnership>) -> Result<()> {
154156
)
155157
}
156158

159+
// * Set token authority
160+
161+
#[derive(Accounts)]
162+
pub struct AcceptTokenAuthority<'info> {
163+
#[account(
164+
has_one = mint,
165+
constraint = config.paused @ NTTError::NotPaused,
166+
)]
167+
pub config: Account<'info, Config>,
168+
169+
#[account(mut)]
170+
pub mint: InterfaceAccount<'info, token_interface::Mint>,
171+
172+
#[account(
173+
seeds = [crate::TOKEN_AUTHORITY_SEED],
174+
bump,
175+
)]
176+
/// CHECK: The constraints enforce this is valid mint authority
177+
pub token_authority: UncheckedAccount<'info>,
178+
179+
pub current_authority: Signer<'info>,
180+
181+
pub token_program: Interface<'info, token_interface::TokenInterface>,
182+
}
183+
184+
pub fn accept_token_authority(ctx: Context<AcceptTokenAuthority>) -> Result<()> {
185+
token_interface::set_authority(
186+
CpiContext::new(
187+
ctx.accounts.token_program.to_account_info(),
188+
token_interface::SetAuthority {
189+
account_or_mint: ctx.accounts.mint.to_account_info(),
190+
current_authority: ctx.accounts.current_authority.to_account_info(),
191+
},
192+
),
193+
AuthorityType::MintTokens,
194+
Some(ctx.accounts.token_authority.key()),
195+
)
196+
}
197+
198+
#[derive(Accounts)]
199+
pub struct SetTokenAuthority<'info> {
200+
#[account(
201+
has_one = owner,
202+
has_one = mint,
203+
constraint = config.paused @ NTTError::NotPaused,
204+
)]
205+
pub config: Account<'info, Config>,
206+
207+
pub owner: Signer<'info>,
208+
209+
#[account(mut)]
210+
pub mint: InterfaceAccount<'info, token_interface::Mint>,
211+
212+
#[account(
213+
seeds = [crate::TOKEN_AUTHORITY_SEED],
214+
bump,
215+
)]
216+
/// CHECK: The constraints enforce this is valid mint authority
217+
pub token_authority: UncheckedAccount<'info>,
218+
219+
/// CHECK: This account will be the signer in the [claim_token_authority] instruction.
220+
pub new_authority: UncheckedAccount<'info>,
221+
}
222+
223+
#[derive(Accounts)]
224+
pub struct SetTokenAuthorityChecked<'info> {
225+
#[account(
226+
constraint = common.token_authority.key() == common.mint.mint_authority.unwrap() @ NTTError::InvalidMintAuthority
227+
)]
228+
pub common: SetTokenAuthority<'info>,
229+
230+
#[account(mut)]
231+
pub rent_payer: Signer<'info>,
232+
233+
#[account(
234+
init_if_needed,
235+
space = 8 + PendingTokenAuthority::INIT_SPACE,
236+
payer = rent_payer,
237+
seeds = [PendingTokenAuthority::SEED_PREFIX],
238+
bump
239+
)]
240+
pub pending_token_authority: Account<'info, PendingTokenAuthority>,
241+
242+
pub system_program: Program<'info, System>,
243+
}
244+
245+
pub fn set_token_authority(ctx: Context<SetTokenAuthorityChecked>) -> Result<()> {
246+
ctx.accounts
247+
.pending_token_authority
248+
.set_inner(PendingTokenAuthority {
249+
bump: ctx.bumps.pending_token_authority,
250+
pending_authority: ctx.accounts.common.new_authority.key(),
251+
rent_payer: ctx.accounts.rent_payer.key(),
252+
});
253+
Ok(())
254+
}
255+
256+
#[derive(Accounts)]
257+
pub struct SetTokenAuthorityUnchecked<'info> {
258+
pub common: SetTokenAuthority<'info>,
259+
260+
pub token_program: Interface<'info, token_interface::TokenInterface>,
261+
}
262+
263+
pub fn set_token_authority_one_step_unchecked(
264+
ctx: Context<SetTokenAuthorityUnchecked>,
265+
) -> Result<()> {
266+
token_interface::set_authority(
267+
CpiContext::new_with_signer(
268+
ctx.accounts.token_program.to_account_info(),
269+
token_interface::SetAuthority {
270+
account_or_mint: ctx.accounts.common.mint.to_account_info(),
271+
current_authority: ctx.accounts.common.token_authority.to_account_info(),
272+
},
273+
&[&[
274+
crate::TOKEN_AUTHORITY_SEED,
275+
&[ctx.bumps.common.token_authority],
276+
]],
277+
),
278+
AuthorityType::MintTokens,
279+
Some(ctx.accounts.common.new_authority.key()),
280+
)
281+
}
282+
283+
// * Claim token authority
284+
285+
#[derive(Accounts)]
286+
pub struct ClaimTokenAuthorityBase<'info> {
287+
#[account(
288+
has_one = mint,
289+
constraint = config.paused @ NTTError::NotPaused,
290+
)]
291+
pub config: Account<'info, Config>,
292+
293+
#[account(mut)]
294+
pub mint: InterfaceAccount<'info, token_interface::Mint>,
295+
296+
#[account(
297+
seeds = [crate::TOKEN_AUTHORITY_SEED],
298+
bump,
299+
)]
300+
/// CHECK: The seeds constraint enforces that this is the correct address
301+
pub token_authority: UncheckedAccount<'info>,
302+
303+
#[account(mut)]
304+
/// CHECK: the `pending_token_authority` constraint enforces that this is the correct address
305+
pub rent_payer: UncheckedAccount<'info>,
306+
307+
#[account(
308+
mut,
309+
seeds = [PendingTokenAuthority::SEED_PREFIX],
310+
bump = pending_token_authority.bump,
311+
has_one = rent_payer @ NTTError::IncorrectRentPayer,
312+
close = rent_payer
313+
)]
314+
pub pending_token_authority: Account<'info, PendingTokenAuthority>,
315+
316+
pub token_program: Interface<'info, token_interface::TokenInterface>,
317+
318+
pub system_program: Program<'info, System>,
319+
}
320+
321+
#[derive(Accounts)]
322+
pub struct RevertTokenAuthority<'info> {
323+
pub common: ClaimTokenAuthorityBase<'info>,
324+
325+
#[account(
326+
address = common.config.owner
327+
)]
328+
pub owner: Signer<'info>,
329+
}
330+
331+
pub fn revert_token_authority(_ctx: Context<RevertTokenAuthority>) -> Result<()> {
332+
Ok(())
333+
}
334+
335+
#[derive(Accounts)]
336+
pub struct ClaimTokenAuthority<'info> {
337+
pub common: ClaimTokenAuthorityBase<'info>,
338+
339+
#[account(
340+
address = common.pending_token_authority.pending_authority @ NTTError::InvalidPendingTokenAuthority
341+
)]
342+
pub new_authority: Signer<'info>,
343+
}
344+
345+
pub fn claim_token_authority(ctx: Context<ClaimTokenAuthority>) -> Result<()> {
346+
token_interface::set_authority(
347+
CpiContext::new_with_signer(
348+
ctx.accounts.common.token_program.to_account_info(),
349+
token_interface::SetAuthority {
350+
account_or_mint: ctx.accounts.common.mint.to_account_info(),
351+
current_authority: ctx.accounts.common.token_authority.to_account_info(),
352+
},
353+
&[&[
354+
crate::TOKEN_AUTHORITY_SEED,
355+
&[ctx.bumps.common.token_authority],
356+
]],
357+
),
358+
AuthorityType::MintTokens,
359+
Some(ctx.accounts.new_authority.key()),
360+
)
361+
}
362+
157363
// * Set peers
158364

159365
#[derive(Accounts)]
@@ -267,7 +473,7 @@ pub fn register_transceiver(ctx: Context<RegisterTransceiver>) -> Result<()> {
267473
#[derive(Accounts)]
268474
pub struct SetOutboundLimit<'info> {
269475
#[account(
270-
constraint = config.owner == owner.key()
476+
has_one = owner,
271477
)]
272478
pub config: Account<'info, Config>,
273479

@@ -294,7 +500,7 @@ pub fn set_outbound_limit(
294500
#[instruction(args: SetInboundLimitArgs)]
295501
pub struct SetInboundLimit<'info> {
296502
#[account(
297-
constraint = config.owner == owner.key()
503+
has_one = owner,
298504
)]
299505
pub config: Account<'info, Config>,
300506

solana/programs/example-native-token-transfers/src/lib.rs

+23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub mod error;
1818
pub mod instructions;
1919
pub mod messages;
2020
pub mod peer;
21+
pub mod pending_token_authority;
2122
pub mod queue;
2223
pub mod registered_transceiver;
2324
pub mod transceivers;
@@ -126,6 +127,28 @@ pub mod example_native_token_transfers {
126127
instructions::claim_ownership(ctx)
127128
}
128129

130+
pub fn accept_token_authority(ctx: Context<AcceptTokenAuthority>) -> Result<()> {
131+
instructions::accept_token_authority(ctx)
132+
}
133+
134+
pub fn set_token_authority(ctx: Context<SetTokenAuthorityChecked>) -> Result<()> {
135+
instructions::set_token_authority(ctx)
136+
}
137+
138+
pub fn set_token_authority_one_step_unchecked(
139+
ctx: Context<SetTokenAuthorityUnchecked>,
140+
) -> Result<()> {
141+
instructions::set_token_authority_one_step_unchecked(ctx)
142+
}
143+
144+
pub fn revert_token_authority(ctx: Context<RevertTokenAuthority>) -> Result<()> {
145+
instructions::revert_token_authority(ctx)
146+
}
147+
148+
pub fn claim_token_authority(ctx: Context<ClaimTokenAuthority>) -> Result<()> {
149+
instructions::claim_token_authority(ctx)
150+
}
151+
129152
pub fn set_paused(ctx: Context<SetPaused>, pause: bool) -> Result<()> {
130153
instructions::set_paused(ctx, pause)
131154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use anchor_lang::prelude::*;
2+
3+
#[account]
4+
#[derive(InitSpace)]
5+
pub struct PendingTokenAuthority {
6+
pub bump: u8,
7+
pub pending_authority: Pubkey,
8+
pub rent_payer: Pubkey,
9+
}
10+
11+
impl PendingTokenAuthority {
12+
pub const SEED_PREFIX: &'static [u8] = b"pending_token_authority";
13+
}

0 commit comments

Comments
 (0)