@@ -7,6 +7,7 @@ use crate::{
7
7
config:: * ,
8
8
error:: NTTError ,
9
9
queue:: inbox:: { InboxItem , ReleaseStatus } ,
10
+ spl_multisig:: SplMultisig ,
10
11
} ;
11
12
12
13
#[ derive( Accounts ) ]
@@ -77,18 +78,11 @@ pub fn release_inbound_mint<'info>(
77
78
ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMint < ' info > > ,
78
79
args : ReleaseInboundArgs ,
79
80
) -> 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
- }
81
+ let inbox_item = release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay ) ?;
82
+ if inbox_item. is_none ( ) {
83
+ return Ok ( ( ) ) ;
90
84
}
91
-
85
+ let inbox_item = inbox_item . unwrap ( ) ;
92
86
assert ! ( inbox_item. release_status == ReleaseStatus :: Released ) ;
93
87
94
88
// NOTE: minting tokens is a two-step process:
@@ -106,6 +100,11 @@ pub fn release_inbound_mint<'info>(
106
100
// The [`transfer_burn`] function operates in a similar way
107
101
// (transfer to custody from sender, *then* burn).
108
102
103
+ let token_authority_sig: & [ & [ & [ u8 ] ] ] = & [ & [
104
+ crate :: TOKEN_AUTHORITY_SEED ,
105
+ & [ ctx. bumps . common . token_authority ] ,
106
+ ] ] ;
107
+
109
108
// Step 1: mint tokens to the custody account
110
109
token_interface:: mint_to (
111
110
CpiContext :: new_with_signer (
@@ -115,10 +114,7 @@ pub fn release_inbound_mint<'info>(
115
114
to : ctx. accounts . common . custody . to_account_info ( ) ,
116
115
authority : ctx. accounts . common . token_authority . to_account_info ( ) ,
117
116
} ,
118
- & [ & [
119
- crate :: TOKEN_AUTHORITY_SEED ,
120
- & [ ctx. bumps . common . token_authority ] ,
121
- ] ] ,
117
+ token_authority_sig,
122
118
) ,
123
119
inbox_item. amount ,
124
120
) ?;
@@ -133,10 +129,87 @@ pub fn release_inbound_mint<'info>(
133
129
ctx. remaining_accounts ,
134
130
inbox_item. amount ,
135
131
ctx. accounts . common . mint . decimals ,
136
- & [ & [
137
- crate :: TOKEN_AUTHORITY_SEED ,
138
- & [ ctx. bumps . common . token_authority ] ,
139
- ] ] ,
132
+ token_authority_sig,
133
+ ) ?;
134
+ Ok ( ( ) )
135
+ }
136
+
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 > ,
150
+ }
151
+
152
+ pub fn release_inbound_mint_multisig < ' info > (
153
+ ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundMintMultisig < ' info > > ,
154
+ args : ReleaseInboundArgs ,
155
+ ) -> 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
+ solana_program:: program:: invoke_signed (
185
+ & 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 ,
192
+ ) ?,
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,
140
213
) ?;
141
214
Ok ( ( ) )
142
215
}
@@ -162,17 +235,12 @@ pub fn release_inbound_unlock<'info>(
162
235
ctx : Context < ' _ , ' _ , ' _ , ' info , ReleaseInboundUnlock < ' info > > ,
163
236
args : ReleaseInboundArgs ,
164
237
) -> Result < ( ) > {
165
- let inbox_item = & mut ctx. accounts . common . inbox_item ;
166
-
167
- let released = inbox_item. try_release ( ) ?;
168
-
169
- if !released {
170
- if args. revert_on_delay {
171
- return Err ( NTTError :: CantReleaseYet . into ( ) ) ;
172
- } else {
173
- return Ok ( ( ) ) ;
174
- }
238
+ let inbox_item = release_inbox_item ( & mut ctx. accounts . common . inbox_item , args. revert_on_delay ) ?;
239
+ if inbox_item. is_none ( ) {
240
+ return Ok ( ( ) ) ;
175
241
}
242
+ let inbox_item = inbox_item. unwrap ( ) ;
243
+ assert ! ( inbox_item. release_status == ReleaseStatus :: Released ) ;
176
244
177
245
onchain:: invoke_transfer_checked (
178
246
& ctx. accounts . common . token_program . key ( ) ,
@@ -190,3 +258,15 @@ pub fn release_inbound_unlock<'info>(
190
258
) ?;
191
259
Ok ( ( ) )
192
260
}
261
+ fn release_inbox_item (
262
+ inbox_item : & mut InboxItem ,
263
+ revert_on_delay : bool ,
264
+ ) -> Result < Option < & mut InboxItem > > {
265
+ if inbox_item. try_release ( ) ? {
266
+ Ok ( Some ( inbox_item) )
267
+ } else if revert_on_delay {
268
+ Err ( NTTError :: CantReleaseYet . into ( ) )
269
+ } else {
270
+ Ok ( None )
271
+ }
272
+ }
0 commit comments