Skip to content

Commit ae21768

Browse files
committed
solana: Add 2-step set token authority ixs
1 parent 51d66fd commit ae21768

File tree

6 files changed

+549
-20
lines changed

6 files changed

+549
-20
lines changed

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

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ pub enum NTTError {
5555
NoRegisteredTransceivers,
5656
#[msg("NotPaused")]
5757
NotPaused,
58+
#[msg("InvalidPendingTokenAuthority")]
59+
InvalidPendingTokenAuthority,
60+
#[msg("IncorrectRentPayer")]
61+
IncorrectRentPayer,
5862
}
5963

6064
impl From<ScalingError> for NTTError {

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

+109-4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use crate::{
1010
config::Config,
1111
error::NTTError,
1212
peer::NttManagerPeer,
13+
pending_token_authority::PendingTokenAuthority,
1314
queue::{inbox::InboxRateLimit, outbox::OutboxRateLimit, rate_limit::RateLimitState},
1415
registered_transceiver::RegisteredTransceiver,
1516
};
@@ -174,20 +175,124 @@ pub struct SetTokenAuthority<'info> {
174175
/// CHECK: the mint address matches the config
175176
pub mint: InterfaceAccount<'info, token_interface::Mint>,
176177

178+
#[account(
179+
seeds = [crate::TOKEN_AUTHORITY_SEED],
180+
bump,
181+
constraint = mint.mint_authority.unwrap() == token_authority.key() @ NTTError::InvalidMintAuthority
182+
)]
183+
/// CHECK: The constraints enforce this is valid mint authority
184+
pub token_authority: UncheckedAccount<'info>,
185+
186+
/// CHECK: This account will be the signer in the [claim_token_authority] instruction.
187+
pub new_authority: UncheckedAccount<'info>,
188+
}
189+
190+
#[derive(Accounts)]
191+
pub struct SetTokenAuthorityChecked<'info> {
192+
pub common: SetTokenAuthority<'info>,
193+
194+
#[account(mut)]
195+
pub payer: Signer<'info>,
196+
197+
#[account(
198+
init_if_needed,
199+
space = 8 + PendingTokenAuthority::INIT_SPACE,
200+
payer = payer,
201+
seeds = [PendingTokenAuthority::SEED_PREFIX],
202+
bump
203+
)]
204+
pub pending_token_authority: Account<'info, PendingTokenAuthority>,
205+
206+
pub system_program: Program<'info, System>,
207+
}
208+
209+
pub fn set_token_authority(ctx: Context<SetTokenAuthorityChecked>) -> Result<()> {
210+
ctx.accounts
211+
.pending_token_authority
212+
.set_inner(PendingTokenAuthority {
213+
bump: ctx.bumps.pending_token_authority,
214+
pending_authority: ctx.accounts.common.new_authority.key(),
215+
rent_payer: ctx.accounts.payer.key(),
216+
});
217+
Ok(())
218+
}
219+
220+
#[derive(Accounts)]
221+
pub struct SetTokenAuthorityUnchecked<'info> {
222+
pub common: SetTokenAuthority<'info>,
223+
177224
pub token_program: Interface<'info, token_interface::TokenInterface>,
225+
}
226+
227+
pub fn set_token_authority_one_step_unchecked(
228+
ctx: Context<SetTokenAuthorityUnchecked>,
229+
) -> Result<()> {
230+
token_interface::set_authority(
231+
CpiContext::new_with_signer(
232+
ctx.accounts.token_program.to_account_info(),
233+
token_interface::SetAuthority {
234+
account_or_mint: ctx.accounts.common.mint.to_account_info(),
235+
current_authority: ctx.accounts.common.token_authority.to_account_info(),
236+
},
237+
&[&[
238+
crate::TOKEN_AUTHORITY_SEED,
239+
&[ctx.bumps.common.token_authority],
240+
]],
241+
),
242+
AuthorityType::MintTokens,
243+
Some(ctx.accounts.common.new_authority.key()),
244+
)
245+
}
246+
247+
#[derive(Accounts)]
248+
pub struct ClaimTokenAuthority<'info> {
249+
#[account(
250+
constraint = config.paused @ NTTError::NotPaused,
251+
)]
252+
pub config: Account<'info, Config>,
253+
254+
#[account(
255+
mut,
256+
address = pending_token_authority.rent_payer @ NTTError::IncorrectRentPayer,
257+
)]
258+
pub payer: Signer<'info>,
259+
260+
#[account(
261+
mut,
262+
address = config.mint,
263+
)]
264+
/// CHECK: the mint address matches the config
265+
pub mint: InterfaceAccount<'info, token_interface::Mint>,
178266

179267
#[account(
180268
seeds = [crate::TOKEN_AUTHORITY_SEED],
181269
bump,
182270
)]
183-
/// CHECK: token_program will ensure this is the valid mint_authority.
271+
/// CHECK: The seeds constraint enforces that this is the correct address
184272
pub token_authority: UncheckedAccount<'info>,
185273

186-
/// CHECK: This is unsafe as is so should be used with caution
187-
pub new_authority: UncheckedAccount<'info>,
274+
#[account(
275+
constraint = (
276+
new_authority.key() == pending_token_authority.pending_authority
277+
|| new_authority.key() == token_authority.key()
278+
) @ NTTError::InvalidPendingTokenAuthority
279+
)]
280+
pub new_authority: Signer<'info>,
281+
282+
#[account(
283+
mut,
284+
seeds = [PendingTokenAuthority::SEED_PREFIX],
285+
bump = pending_token_authority.bump,
286+
close = payer
287+
)]
288+
pub pending_token_authority: Account<'info, PendingTokenAuthority>,
289+
290+
pub token_program: Interface<'info, token_interface::TokenInterface>,
291+
292+
pub system_program: Program<'info, System>,
188293
}
189294

190-
pub fn set_token_authority_one_step_unchecked(ctx: Context<SetTokenAuthority>) -> Result<()> {
295+
pub fn claim_token_authority(ctx: Context<ClaimTokenAuthority>) -> Result<()> {
191296
token_interface::set_authority(
192297
CpiContext::new_with_signer(
193298
ctx.accounts.token_program.to_account_info(),

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

+12-1
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,10 +127,20 @@ pub mod example_native_token_transfers {
126127
instructions::claim_ownership(ctx)
127128
}
128129

129-
pub fn set_token_authority_one_step_unchecked(ctx: Context<SetTokenAuthority>) -> Result<()> {
130+
pub fn set_token_authority(ctx: Context<SetTokenAuthorityChecked>) -> Result<()> {
131+
instructions::set_token_authority(ctx)
132+
}
133+
134+
pub fn set_token_authority_one_step_unchecked(
135+
ctx: Context<SetTokenAuthorityUnchecked>,
136+
) -> Result<()> {
130137
instructions::set_token_authority_one_step_unchecked(ctx)
131138
}
132139

140+
pub fn claim_token_authority(ctx: Context<ClaimTokenAuthority>) -> Result<()> {
141+
instructions::claim_token_authority(ctx)
142+
}
143+
133144
pub fn set_paused(ctx: Context<SetPaused>, pause: bool) -> Result<()> {
134145
instructions::set_paused(ctx, pause)
135146
}
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+
}

solana/ts/idl/3_0_0/json/example_native_token_transfers.json

+137-5
Original file line numberDiff line numberDiff line change
@@ -719,17 +719,109 @@
719719
],
720720
"args": []
721721
},
722+
{
723+
"name": "setTokenAuthority",
724+
"accounts": [
725+
{
726+
"name": "common",
727+
"accounts": [
728+
{
729+
"name": "config",
730+
"isMut": false,
731+
"isSigner": false
732+
},
733+
{
734+
"name": "owner",
735+
"isMut": false,
736+
"isSigner": true
737+
},
738+
{
739+
"name": "mint",
740+
"isMut": true,
741+
"isSigner": false
742+
},
743+
{
744+
"name": "tokenAuthority",
745+
"isMut": false,
746+
"isSigner": false
747+
},
748+
{
749+
"name": "newAuthority",
750+
"isMut": false,
751+
"isSigner": false
752+
}
753+
]
754+
},
755+
{
756+
"name": "payer",
757+
"isMut": true,
758+
"isSigner": true
759+
},
760+
{
761+
"name": "pendingTokenAuthority",
762+
"isMut": true,
763+
"isSigner": false
764+
},
765+
{
766+
"name": "systemProgram",
767+
"isMut": false,
768+
"isSigner": false
769+
}
770+
],
771+
"args": []
772+
},
722773
{
723774
"name": "setTokenAuthorityOneStepUnchecked",
775+
"accounts": [
776+
{
777+
"name": "common",
778+
"accounts": [
779+
{
780+
"name": "config",
781+
"isMut": false,
782+
"isSigner": false
783+
},
784+
{
785+
"name": "owner",
786+
"isMut": false,
787+
"isSigner": true
788+
},
789+
{
790+
"name": "mint",
791+
"isMut": true,
792+
"isSigner": false
793+
},
794+
{
795+
"name": "tokenAuthority",
796+
"isMut": false,
797+
"isSigner": false
798+
},
799+
{
800+
"name": "newAuthority",
801+
"isMut": false,
802+
"isSigner": false
803+
}
804+
]
805+
},
806+
{
807+
"name": "tokenProgram",
808+
"isMut": false,
809+
"isSigner": false
810+
}
811+
],
812+
"args": []
813+
},
814+
{
815+
"name": "claimTokenAuthority",
724816
"accounts": [
725817
{
726818
"name": "config",
727819
"isMut": false,
728820
"isSigner": false
729821
},
730822
{
731-
"name": "owner",
732-
"isMut": false,
823+
"name": "payer",
824+
"isMut": true,
733825
"isSigner": true
734826
},
735827
{
@@ -738,17 +830,27 @@
738830
"isSigner": false
739831
},
740832
{
741-
"name": "tokenProgram",
833+
"name": "tokenAuthority",
742834
"isMut": false,
743835
"isSigner": false
744836
},
745837
{
746-
"name": "tokenAuthority",
838+
"name": "newAuthority",
839+
"isMut": false,
840+
"isSigner": true
841+
},
842+
{
843+
"name": "pendingTokenAuthority",
844+
"isMut": true,
845+
"isSigner": false
846+
},
847+
{
848+
"name": "tokenProgram",
747849
"isMut": false,
748850
"isSigner": false
749851
},
750852
{
751-
"name": "newAuthority",
853+
"name": "systemProgram",
752854
"isMut": false,
753855
"isSigner": false
754856
}
@@ -1412,6 +1514,26 @@
14121514
]
14131515
}
14141516
},
1517+
{
1518+
"name": "PendingTokenAuthority",
1519+
"type": {
1520+
"kind": "struct",
1521+
"fields": [
1522+
{
1523+
"name": "bump",
1524+
"type": "u8"
1525+
},
1526+
{
1527+
"name": "pendingAuthority",
1528+
"type": "publicKey"
1529+
},
1530+
{
1531+
"name": "rentPayer",
1532+
"type": "publicKey"
1533+
}
1534+
]
1535+
}
1536+
},
14151537
{
14161538
"name": "InboxItem",
14171539
"type": {
@@ -2060,6 +2182,16 @@
20602182
"code": 6024,
20612183
"name": "NotPaused",
20622184
"msg": "NotPaused"
2185+
},
2186+
{
2187+
"code": 6025,
2188+
"name": "InvalidPendingTokenAuthority",
2189+
"msg": "InvalidPendingTokenAuthority"
2190+
},
2191+
{
2192+
"code": 6026,
2193+
"name": "IncorrectRentPayer",
2194+
"msg": "IncorrectRentPayer"
20632195
}
20642196
]
20652197
}

0 commit comments

Comments
 (0)