@@ -54,7 +54,7 @@ pub struct ReleaseInbound<'info> {
54
54
55
55
#[ derive( AnchorDeserialize , AnchorSerialize ) ]
56
56
pub struct ReleaseInboundArgs {
57
- pub revert_on_delay : bool ,
57
+ pub revert_when_not_ready : bool ,
58
58
}
59
59
60
60
// Burn/mint
@@ -65,11 +65,18 @@ pub struct ReleaseInboundMint<'info> {
65
65
constraint = common. config. mode == Mode :: Burning @ NTTError :: InvalidMode ,
66
66
) ]
67
67
common : ReleaseInbound < ' info > ,
68
+
69
+ #[ account(
70
+ constraint = multisig_token_authority. m == 1
71
+ && multisig_token_authority. signers. contains( & common. token_authority. key( ) )
72
+ @ NTTError :: InvalidMultisig ,
73
+ ) ]
74
+ pub multisig_token_authority : Option < InterfaceAccount < ' info , SplMultisig > > ,
68
75
}
69
76
70
77
/// Release an inbound transfer and mint the tokens to the recipient.
71
- /// When `revert_on_error ` is true, the transaction will revert if the
72
- /// release timestamp has not been reached. When `revert_on_error ` is false, the
78
+ /// When `revert_when_not_ready ` is true, the transaction will revert if the
79
+ /// release timestamp has not been reached. When `revert_when_not_ready ` is false, the
73
80
/// transaction succeeds, but the minting is not performed.
74
81
/// Setting this flag to `false` is useful when bundling this instruction
75
82
/// together with [`crate::instructions::redeem`] in a transaction, so that the minting
@@ -78,7 +85,10 @@ pub fn release_inbound_mint<'info>(
78
85
ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMint < ' info > > ,
79
86
args : ReleaseInboundArgs ,
80
87
) -> Result < ( ) > {
81
- let inbox_item = release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay ) ?;
88
+ let inbox_item = release_inbox_item (
89
+ & mut ctx. accounts . common . inbox_item ,
90
+ args. revert_when_not_ready ,
91
+ ) ?;
82
92
if inbox_item. is_none ( ) {
83
93
return Ok ( ( ) ) ;
84
94
}
@@ -106,18 +116,25 @@ pub fn release_inbound_mint<'info>(
106
116
] ] ;
107
117
108
118
// Step 1: mint tokens to the custody account
109
- token_interface :: mint_to (
110
- CpiContext :: new_with_signer (
119
+ match & ctx . accounts . multisig_token_authority {
120
+ Some ( multisig_token_authority ) => mint_to_custody_from_multisig_token_authority (
111
121
ctx. accounts . common . token_program . to_account_info ( ) ,
112
- token_interface:: MintTo {
113
- mint : ctx. accounts . common . mint . to_account_info ( ) ,
114
- to : ctx. accounts . common . custody . to_account_info ( ) ,
115
- authority : ctx. accounts . common . token_authority . to_account_info ( ) ,
116
- } ,
122
+ ctx. accounts . common . mint . to_account_info ( ) ,
123
+ ctx. accounts . common . custody . to_account_info ( ) ,
124
+ multisig_token_authority. to_account_info ( ) ,
125
+ ctx. accounts . common . token_authority . to_account_info ( ) ,
117
126
token_authority_sig,
118
- ) ,
119
- inbox_item. amount ,
120
- ) ?;
127
+ inbox_item. amount ,
128
+ ) ?,
129
+ None => mint_to_custody_from_token_authority (
130
+ ctx. accounts . common . token_program . to_account_info ( ) ,
131
+ ctx. accounts . common . mint . to_account_info ( ) ,
132
+ ctx. accounts . common . custody . to_account_info ( ) ,
133
+ ctx. accounts . common . token_authority . to_account_info ( ) ,
134
+ token_authority_sig,
135
+ inbox_item. amount ,
136
+ ) ?,
137
+ } ;
121
138
122
139
// Step 2: transfer the tokens from the custody account to the recipient
123
140
onchain:: invoke_transfer_checked (
@@ -134,82 +151,49 @@ pub fn release_inbound_mint<'info>(
134
151
Ok ( ( ) )
135
152
}
136
153
137
- #[ derive( Accounts ) ]
138
- pub struct ReleaseInboundMintMultisig < ' info > {
139
- #[ account(
140
- constraint = common. config. mode == Mode :: Burning @ NTTError :: InvalidMode ,
141
- ) ]
142
- common : ReleaseInbound < ' info > ,
143
-
144
- #[ account(
145
- constraint =
146
- multisig. m == 1 && multisig. signers. contains( & common. token_authority. key( ) )
147
- @ NTTError :: InvalidMultisig ,
148
- ) ]
149
- pub multisig : InterfaceAccount < ' info , SplMultisig > ,
154
+ fn mint_to_custody_from_token_authority < ' info > (
155
+ token_program : AccountInfo < ' info > ,
156
+ mint : AccountInfo < ' info > ,
157
+ custody : AccountInfo < ' info > ,
158
+ token_authority : AccountInfo < ' info > ,
159
+ token_authority_signer_seeds : & [ & [ & [ u8 ] ] ] ,
160
+ amount : u64 ,
161
+ ) -> Result < ( ) > {
162
+ token_interface:: mint_to (
163
+ CpiContext :: new_with_signer (
164
+ token_program,
165
+ token_interface:: MintTo {
166
+ mint,
167
+ to : custody,
168
+ authority : token_authority,
169
+ } ,
170
+ token_authority_signer_seeds,
171
+ ) ,
172
+ amount,
173
+ ) ?;
174
+ Ok ( ( ) )
150
175
}
151
176
152
- pub fn release_inbound_mint_multisig < ' info > (
153
- ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMintMultisig < ' info > > ,
154
- args : ReleaseInboundArgs ,
177
+ fn mint_to_custody_from_multisig_token_authority < ' info > (
178
+ token_program : AccountInfo < ' info > ,
179
+ mint : AccountInfo < ' info > ,
180
+ custody : AccountInfo < ' info > ,
181
+ multisig_token_authority : AccountInfo < ' info > ,
182
+ token_authority : AccountInfo < ' info > ,
183
+ token_authority_signer_seeds : & [ & [ & [ u8 ] ] ] ,
184
+ amount : u64 ,
155
185
) -> Result < ( ) > {
156
- let inbox_item = release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay ) ?;
157
- if inbox_item. is_none ( ) {
158
- return Ok ( ( ) ) ;
159
- }
160
- let inbox_item = inbox_item. unwrap ( ) ;
161
- assert ! ( inbox_item. release_status == ReleaseStatus :: Released ) ;
162
-
163
- // NOTE: minting tokens is a two-step process:
164
- // 1. Mint tokens to the custody account
165
- // 2. Transfer the tokens from the custody account to the recipient
166
- //
167
- // This is done to ensure that if the token has a transfer hook defined, it
168
- // will be called after the tokens are minted.
169
- // Unfortunately the Token2022 program doesn't trigger transfer hooks when
170
- // minting tokens, so we have to do it "manually" via a transfer.
171
- //
172
- // If we didn't do this, transfer hooks could be bypassed by transferring
173
- // the tokens out through NTT first, then back in to the intended recipient.
174
- //
175
- // The [`transfer_burn`] function operates in a similar way
176
- // (transfer to custody from sender, *then* burn).
177
-
178
- let token_authority_sig: & [ & [ & [ u8 ] ] ] = & [ & [
179
- crate :: TOKEN_AUTHORITY_SEED ,
180
- & [ ctx. bumps . common . token_authority ] ,
181
- ] ] ;
182
-
183
- // Step 1: mint tokens to the custody account
184
186
solana_program:: program:: invoke_signed (
185
187
& spl_token_2022:: instruction:: mint_to (
186
- & ctx . accounts . common . token_program . key ( ) ,
187
- & ctx . accounts . common . mint . key ( ) ,
188
- & ctx . accounts . common . custody . key ( ) ,
189
- & ctx . accounts . multisig . key ( ) ,
190
- & [ & ctx . accounts . common . token_authority . key ( ) ] ,
191
- inbox_item . amount ,
188
+ & token_program. key ( ) ,
189
+ & mint. key ( ) ,
190
+ & custody. key ( ) ,
191
+ & multisig_token_authority . key ( ) ,
192
+ & [ & token_authority. key ( ) ] ,
193
+ amount,
192
194
) ?,
193
- & [
194
- ctx. accounts . common . custody . to_account_info ( ) ,
195
- ctx. accounts . common . mint . to_account_info ( ) ,
196
- ctx. accounts . common . token_authority . to_account_info ( ) ,
197
- ctx. accounts . multisig . to_account_info ( ) ,
198
- ] ,
199
- token_authority_sig,
200
- ) ?;
201
-
202
- // Step 2: transfer the tokens from the custody account to the recipient
203
- onchain:: invoke_transfer_checked (
204
- & ctx. accounts . common . token_program . key ( ) ,
205
- ctx. accounts . common . custody . to_account_info ( ) ,
206
- ctx. accounts . common . mint . to_account_info ( ) ,
207
- ctx. accounts . common . recipient . to_account_info ( ) ,
208
- ctx. accounts . common . token_authority . to_account_info ( ) ,
209
- ctx. remaining_accounts ,
210
- inbox_item. amount ,
211
- ctx. accounts . common . mint . decimals ,
212
- token_authority_sig,
195
+ & [ custody, mint, token_authority, multisig_token_authority] ,
196
+ token_authority_signer_seeds,
213
197
) ?;
214
198
Ok ( ( ) )
215
199
}
@@ -225,8 +209,8 @@ pub struct ReleaseInboundUnlock<'info> {
225
209
}
226
210
227
211
/// Release an inbound transfer and unlock the tokens to the recipient.
228
- /// When `revert_on_error ` is true, the transaction will revert if the
229
- /// release timestamp has not been reached. When `revert_on_error ` is false, the
212
+ /// When `revert_when_not_ready ` is true, the transaction will revert if the
213
+ /// release timestamp has not been reached. When `revert_when_not_ready ` is false, the
230
214
/// transaction succeeds, but the unlocking is not performed.
231
215
/// Setting this flag to `false` is useful when bundling this instruction
232
216
/// together with [`crate::instructions::redeem`], so that the unlocking
@@ -235,7 +219,10 @@ pub fn release_inbound_unlock<'info>(
235
219
ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundUnlock < ' info > > ,
236
220
args : ReleaseInboundArgs ,
237
221
) -> Result < ( ) > {
238
- let inbox_item = release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay ) ?;
222
+ let inbox_item = release_inbox_item (
223
+ & mut ctx. accounts . common . inbox_item ,
224
+ args. revert_when_not_ready ,
225
+ ) ?;
239
226
if inbox_item. is_none ( ) {
240
227
return Ok ( ( ) ) ;
241
228
}
@@ -258,14 +245,21 @@ pub fn release_inbound_unlock<'info>(
258
245
) ?;
259
246
Ok ( ( ) )
260
247
}
248
+
261
249
fn release_inbox_item (
262
250
inbox_item : & mut InboxItem ,
263
- revert_on_delay : bool ,
251
+ revert_when_not_ready : bool ,
264
252
) -> Result < Option < & mut InboxItem > > {
265
253
if inbox_item. try_release ( ) ? {
266
254
Ok ( Some ( inbox_item) )
267
- } else if revert_on_delay {
268
- Err ( NTTError :: CantReleaseYet . into ( ) )
255
+ } else if revert_when_not_ready {
256
+ match inbox_item. release_status {
257
+ ReleaseStatus :: NotApproved => Err ( NTTError :: TransferNotApproved . into ( ) ) ,
258
+ ReleaseStatus :: ReleaseAfter ( _) => Err ( NTTError :: CantReleaseYet . into ( ) ) ,
259
+ // Unreachable: if released, [`InboxItem::try_release`] will return an Error immediately
260
+ // rather than Ok(bool).
261
+ ReleaseStatus :: Released => Err ( NTTError :: TransferAlreadyRedeemed . into ( ) ) ,
262
+ }
269
263
} else {
270
264
Ok ( None )
271
265
}
0 commit comments