Skip to content

Commit 4e93386

Browse files
committed
solana[WIP]: Refactor to reduce code duplication
1 parent efe3daa commit 4e93386

File tree

4 files changed

+196
-48
lines changed

4 files changed

+196
-48
lines changed

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

+66-17
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,7 @@ pub struct Initialize<'info> {
3737
)]
3838
pub config: Box<Account<'info, crate::config::Config>>,
3939

40-
#[account(
41-
constraint =
42-
args.mode == Mode::Locking
43-
|| mint.mint_authority.unwrap() == token_authority.key()
44-
@ NTTError::InvalidMintAuthority,
45-
)]
40+
#[account()]
4641
pub mint: Box<InterfaceAccount<'info, token_interface::Mint>>,
4742

4843
#[account(
@@ -95,25 +90,79 @@ pub struct InitializeArgs {
9590
pub mode: ntt_messages::mode::Mode,
9691
}
9792

98-
pub fn initialize(ctx: Context<Initialize>, args: InitializeArgs) -> Result<()> {
99-
ctx.accounts.config.set_inner(crate::config::Config {
100-
bump: ctx.bumps.config,
101-
mint: ctx.accounts.mint.key(),
102-
token_program: ctx.accounts.token_program.key(),
103-
mode: args.mode,
104-
chain_id: ChainId { id: args.chain_id },
105-
owner: ctx.accounts.deployer.key(),
93+
#[derive(Accounts)]
94+
#[instruction(args: InitializeArgs)]
95+
pub struct InitializeDefault<'info> {
96+
#[account(
97+
constraint =
98+
args.mode == Mode::Locking
99+
|| common.mint.mint_authority.unwrap() == common.token_authority.key()
100+
@ NTTError::InvalidMintAuthority,
101+
)]
102+
pub common: Initialize<'info>,
103+
}
104+
105+
pub fn initialize(ctx: Context<InitializeDefault>, args: InitializeArgs) -> Result<()> {
106+
initialize_config_and_rate_limit(
107+
&mut ctx.accounts.common,
108+
ctx.bumps.common.config,
109+
args.chain_id,
110+
args.limit,
111+
args.mode,
112+
)
113+
}
114+
115+
#[derive(Accounts)]
116+
#[instruction(args: InitializeArgs)]
117+
pub struct InitializeMultisig<'info> {
118+
#[account(
119+
constraint =
120+
args.mode == Mode::Locking
121+
|| common.mint.mint_authority.unwrap() == multisig.key()
122+
@ NTTError::InvalidMintAuthority,
123+
)]
124+
pub common: Initialize<'info>,
125+
126+
#[account()]
127+
/// CHECK: multisig is mint authority
128+
pub multisig: UncheckedAccount<'info>,
129+
}
130+
131+
pub fn initialize_multisig(ctx: Context<InitializeMultisig>, args: InitializeArgs) -> Result<()> {
132+
initialize_config_and_rate_limit(
133+
&mut ctx.accounts.common,
134+
ctx.bumps.common.config,
135+
args.chain_id,
136+
args.limit,
137+
args.mode,
138+
)
139+
}
140+
141+
fn initialize_config_and_rate_limit(
142+
common: &mut Initialize<'_>,
143+
config_bump: u8,
144+
chain_id: u16,
145+
limit: u64,
146+
mode: ntt_messages::mode::Mode,
147+
) -> Result<()> {
148+
common.config.set_inner(crate::config::Config {
149+
bump: config_bump,
150+
mint: common.mint.key(),
151+
token_program: common.token_program.key(),
152+
mode,
153+
chain_id: ChainId { id: chain_id },
154+
owner: common.deployer.key(),
106155
pending_owner: None,
107156
paused: false,
108157
next_transceiver_id: 0,
109158
// NOTE: can't be changed for now
110159
threshold: 1,
111160
enabled_transceivers: Bitmap::new(),
112-
custody: ctx.accounts.custody.key(),
161+
custody: common.custody.key(),
113162
});
114163

115-
ctx.accounts.rate_limit.set_inner(OutboxRateLimit {
116-
rate_limit: RateLimitState::new(args.limit),
164+
common.rate_limit.set_inner(OutboxRateLimit {
165+
rate_limit: RateLimitState::new(limit),
117166
});
118167

119168
Ok(())
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,15 @@
11
pub mod admin;
22
pub mod initialize;
3-
pub mod initialize_multisig;
43
pub mod luts;
54
pub mod mark_outbox_item_as_released;
65
pub mod redeem;
76
pub mod release_inbound;
8-
pub mod release_inbound_multisig;
97
pub mod transfer;
108

119
pub use admin::*;
1210
pub use initialize::*;
13-
pub use initialize_multisig::*;
1411
pub use luts::*;
1512
pub use mark_outbox_item_as_released::*;
1613
pub use redeem::*;
1714
pub use release_inbound::*;
18-
pub use release_inbound_multisig::*;
1915
pub use transfer::*;

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

+120-17
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::ops::{Deref, DerefMut};
2+
13
use anchor_lang::prelude::*;
24
use anchor_spl::token_interface;
35
use ntt_messages::mode::Mode;
@@ -66,6 +68,34 @@ pub struct ReleaseInboundMint<'info> {
6668
common: ReleaseInbound<'info>,
6769
}
6870

71+
impl<'info> Deref for ReleaseInboundMint<'info> {
72+
type Target = ReleaseInbound<'info>;
73+
74+
fn deref(&self) -> &Self::Target {
75+
&self.common
76+
}
77+
}
78+
79+
impl Deref for ReleaseInboundMintBumps {
80+
type Target = ReleaseInboundBumps;
81+
82+
fn deref(&self) -> &Self::Target {
83+
&self.common
84+
}
85+
}
86+
87+
impl<'info> DerefMut for ReleaseInboundMint<'info> {
88+
fn deref_mut(&mut self) -> &mut Self::Target {
89+
&mut self.common
90+
}
91+
}
92+
93+
#[derive(Accounts)]
94+
pub struct ReleaseInboundMintDefault<'info> {
95+
#[account(constraint = common.mint.mint_authority.unwrap() == common.token_authority.key())]
96+
common: ReleaseInboundMint<'info>,
97+
}
98+
6999
/// Release an inbound transfer and mint the tokens to the recipient.
70100
/// When `revert_on_error` is true, the transaction will revert if the
71101
/// release timestamp has not been reached. When `revert_on_error` is false, the
@@ -74,23 +104,9 @@ pub struct ReleaseInboundMint<'info> {
74104
/// together with [`crate::instructions::redeem`] in a transaction, so that the minting
75105
/// is attempted optimistically.
76106
pub fn release_inbound_mint<'info>(
77-
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMint<'info>>,
107+
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintDefault<'info>>,
78108
args: ReleaseInboundArgs,
79109
) -> Result<()> {
80-
let inbox_item = &mut ctx.accounts.common.inbox_item;
81-
82-
let released = inbox_item.try_release()?;
83-
84-
if !released {
85-
if args.revert_on_delay {
86-
return Err(NTTError::CantReleaseYet.into());
87-
} else {
88-
return Ok(());
89-
}
90-
}
91-
92-
assert!(inbox_item.release_status == ReleaseStatus::Released);
93-
94110
// NOTE: minting tokens is a two-step process:
95111
// 1. Mint tokens to the custody account
96112
// 2. Transfer the tokens from the custody account to the recipient
@@ -120,7 +136,7 @@ pub fn release_inbound_mint<'info>(
120136
&[ctx.bumps.common.token_authority],
121137
]],
122138
),
123-
inbox_item.amount,
139+
ctx.accounts.common.inbox_item.amount,
124140
)?;
125141

126142
// Step 2: transfer the tokens from the custody account to the recipient
@@ -131,13 +147,100 @@ pub fn release_inbound_mint<'info>(
131147
ctx.accounts.common.recipient.to_account_info(),
132148
ctx.accounts.common.token_authority.to_account_info(),
133149
ctx.remaining_accounts,
134-
inbox_item.amount,
150+
ctx.accounts.common.inbox_item.amount,
151+
ctx.accounts.common.mint.decimals,
152+
&[&[
153+
crate::TOKEN_AUTHORITY_SEED,
154+
&[ctx.bumps.common.token_authority],
155+
]],
156+
)?;
157+
158+
release_inbox_item(&mut ctx.accounts.common.inbox_item, args.revert_on_delay)
159+
}
160+
161+
#[derive(Accounts)]
162+
pub struct ReleaseInboundMintMultisig<'info> {
163+
#[account(constraint = common.mint.mint_authority.unwrap() == multisig.key())]
164+
common: ReleaseInboundMint<'info>,
165+
166+
/// CHECK: multisig account should be mint authority
167+
pub multisig: UncheckedAccount<'info>,
168+
}
169+
170+
pub fn release_inbound_mint_multisig<'info>(
171+
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintMultisig<'info>>,
172+
args: ReleaseInboundArgs,
173+
) -> Result<()> {
174+
// NOTE: minting tokens is a two-step process:
175+
// 1. Mint tokens to the custody account
176+
// 2. Transfer the tokens from the custody account to the recipient
177+
//
178+
// This is done to ensure that if the token has a transfer hook defined, it
179+
// will be called after the tokens are minted.
180+
// Unfortunately the Token2022 program doesn't trigger transfer hooks when
181+
// minting tokens, so we have to do it "manually" via a transfer.
182+
//
183+
// If we didn't do this, transfer hooks could be bypassed by transferring
184+
// the tokens out through NTT first, then back in to the intended recipient.
185+
//
186+
// The [`transfer_burn`] function operates in a similar way
187+
// (transfer to custody from sender, *then* burn).
188+
189+
// Step 1: mint tokens to the custody account
190+
let ix = spl_token_2022::instruction::mint_to(
191+
&ctx.accounts.common.token_program.key(),
192+
&ctx.accounts.common.mint.key(),
193+
&ctx.accounts.common.custody.key(),
194+
&ctx.accounts.multisig.key(),
195+
&[&ctx.accounts.common.token_authority.key()],
196+
ctx.accounts.common.inbox_item.amount,
197+
)?;
198+
solana_program::program::invoke_signed(
199+
&ix,
200+
&[
201+
ctx.accounts.common.custody.to_account_info(),
202+
ctx.accounts.common.mint.to_account_info(),
203+
ctx.accounts.common.token_authority.to_account_info(),
204+
ctx.accounts.multisig.to_account_info(),
205+
],
206+
&[&[
207+
crate::TOKEN_AUTHORITY_SEED,
208+
&[ctx.bumps.common.token_authority],
209+
]],
210+
)?;
211+
212+
// Step 2: transfer the tokens from the custody account to the recipient
213+
onchain::invoke_transfer_checked(
214+
&ctx.accounts.common.token_program.key(),
215+
ctx.accounts.common.custody.to_account_info(),
216+
ctx.accounts.common.mint.to_account_info(),
217+
ctx.accounts.common.recipient.to_account_info(),
218+
ctx.accounts.common.token_authority.to_account_info(),
219+
ctx.remaining_accounts,
220+
ctx.accounts.common.inbox_item.amount,
135221
ctx.accounts.common.mint.decimals,
136222
&[&[
137223
crate::TOKEN_AUTHORITY_SEED,
138224
&[ctx.bumps.common.token_authority],
139225
]],
140226
)?;
227+
228+
release_inbox_item(&mut ctx.accounts.common.inbox_item, args.revert_on_delay)
229+
}
230+
231+
fn release_inbox_item(inbox_item: &mut InboxItem, revert_on_delay: bool) -> Result<()> {
232+
let released = inbox_item.try_release()?;
233+
234+
if !released {
235+
if revert_on_delay {
236+
return Err(NTTError::CantReleaseYet.into());
237+
} else {
238+
return Ok(());
239+
}
240+
}
241+
242+
assert!(inbox_item.release_status == ReleaseStatus::Released);
243+
141244
Ok(())
142245
}
143246

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

+10-10
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ pub mod example_native_token_transfers {
7070

7171
use super::*;
7272

73-
pub fn initialize(ctx: Context<Initialize>, args: InitializeArgs) -> Result<()> {
73+
pub fn initialize(ctx: Context<InitializeDefault>, args: InitializeArgs) -> Result<()> {
7474
instructions::initialize(ctx, args)
7575
}
7676

7777
pub fn initialize_multisig(
7878
ctx: Context<InitializeMultisig>,
79-
args: InitializeMultisigArgs,
79+
args: InitializeArgs,
8080
) -> Result<()> {
8181
instructions::initialize_multisig(ctx, args)
8282
}
@@ -108,24 +108,24 @@ pub mod example_native_token_transfers {
108108
}
109109

110110
pub fn release_inbound_mint<'info>(
111-
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMint<'info>>,
111+
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintDefault<'info>>,
112112
args: ReleaseInboundArgs,
113113
) -> Result<()> {
114114
instructions::release_inbound_mint(ctx, args)
115115
}
116116

117-
pub fn release_inbound_unlock<'info>(
118-
ctx: Context<'_, '_, '_, 'info, ReleaseInboundUnlock<'info>>,
117+
pub fn release_inbound_mint_multisig<'info>(
118+
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMintMultisig<'info>>,
119119
args: ReleaseInboundArgs,
120120
) -> Result<()> {
121-
instructions::release_inbound_unlock(ctx, args)
121+
instructions::release_inbound_mint_multisig(ctx, args)
122122
}
123123

124-
pub fn release_inbound_multisig_mint<'info>(
125-
ctx: Context<'_, '_, '_, 'info, ReleaseInboundMultisigMint<'info>>,
126-
args: ReleaseInboundMultisigArgs,
124+
pub fn release_inbound_unlock<'info>(
125+
ctx: Context<'_, '_, '_, 'info, ReleaseInboundUnlock<'info>>,
126+
args: ReleaseInboundArgs,
127127
) -> Result<()> {
128-
instructions::release_inbound_multisig_mint(ctx, args)
128+
instructions::release_inbound_unlock(ctx, args)
129129
}
130130

131131
pub fn transfer_ownership(ctx: Context<TransferOwnership>) -> Result<()> {

0 commit comments

Comments
 (0)