@@ -3,14 +3,18 @@ import {
3
3
RATE_LIMIT_AMOUNT ,
4
4
RATE_LIMIT_DURATION ,
5
5
} from '@fuel-bridge/solidity-contracts/protocol/constants' ;
6
- import type { Token } from '@fuel-bridge/solidity-contracts/typechain' ;
6
+ import type {
7
+ Token ,
8
+ MockPermitToken ,
9
+ } from '@fuel-bridge/solidity-contracts/typechain' ;
7
10
import type { TestEnvironment } from '@fuel-bridge/test-utils' ;
8
11
import {
9
12
setupEnvironment ,
10
13
relayCommonMessage ,
11
14
waitForMessage ,
12
15
createRelayMessageParams ,
13
16
getOrDeployECR20Contract ,
17
+ getOrDeployERC20PermitContract ,
14
18
getOrDeployL2Bridge ,
15
19
FUEL_TX_PARAMS ,
16
20
getMessageOutReceipt ,
@@ -42,12 +46,15 @@ describe('Bridging ERC20 tokens', async function () {
42
46
43
47
let env : TestEnvironment ;
44
48
let eth_testToken : Token ;
49
+ let eth_permitTestToken : MockPermitToken ;
45
50
let eth_testTokenAddress : string ;
51
+ let eth_permitTestTokenAddress : string ;
46
52
let eth_erc20GatewayAddress : string ;
47
53
let fuel_bridge : BridgeFungibleToken ;
48
54
let fuel_bridgeImpl : BridgeFungibleToken ;
49
55
let fuel_bridgeContractId : string ;
50
56
let fuel_testAssetId : string ;
57
+ let fuel_test_permit_token_AssetId : string ;
51
58
52
59
// override the default test timeout from 2000ms
53
60
this . timeout ( DEFAULT_TIMEOUT_MS ) ;
@@ -114,7 +121,7 @@ describe('Bridging ERC20 tokens', async function () {
114
121
) ;
115
122
}
116
123
117
- async function relayMessage (
124
+ async function relayMessageFromFuel (
118
125
env : TestEnvironment ,
119
126
withdrawMessageProof : MessageProof
120
127
) {
@@ -134,14 +141,108 @@ describe('Bridging ERC20 tokens', async function () {
134
141
) ;
135
142
}
136
143
144
+ async function relayMessageFromEthereum (
145
+ env : TestEnvironment ,
146
+ fuelTokenMessageReceiver : AbstractAddress ,
147
+ fuelTokenMessageNonce : BN ,
148
+ fuel_AssetId : string ,
149
+ amount : bigint
150
+ ) {
151
+ // relay the message ourselves
152
+ const message = await waitForMessage (
153
+ env . fuel . provider ,
154
+ fuelTokenMessageReceiver ,
155
+ fuelTokenMessageNonce ,
156
+ FUEL_MESSAGE_TIMEOUT_MS
157
+ ) ;
158
+ expect ( message ) . to . not . be . null ;
159
+
160
+ const tx = await relayCommonMessage ( env . fuel . deployer , message , {
161
+ maturity : undefined ,
162
+ contractIds : [ fuel_bridgeImpl . id . toHexString ( ) ] ,
163
+ } ) ;
164
+
165
+ const txResult = await tx . waitForResult ( ) ;
166
+
167
+ expect ( txResult . status ) . to . equal ( 'success' ) ;
168
+ expect ( txResult . mintedAssets . length ) . to . equal ( 1 ) ;
169
+
170
+ const [ mintedAsset ] = txResult . mintedAssets ;
171
+
172
+ expect ( mintedAsset . assetId ) . to . equal ( fuel_AssetId ) ;
173
+ expect ( mintedAsset . amount . toString ( ) ) . to . equal (
174
+ ( amount / DECIMAL_DIFF ) . toString ( )
175
+ ) ;
176
+ }
177
+
178
+ async function buildPermitParams (
179
+ name : string ,
180
+ tokenAddress : string ,
181
+ gatewayAddress : string ,
182
+ amount : bigint ,
183
+ nonce : bigint ,
184
+ deadline : number ,
185
+ deployer : Signer
186
+ ) {
187
+ const domain : any = {
188
+ name : name ,
189
+ version : '1' ,
190
+ chainId : env . eth . provider . _network . chainId ,
191
+ verifyingContract : tokenAddress ,
192
+ } ;
193
+
194
+ const types : any = {
195
+ Permit : [
196
+ { name : 'owner' , type : 'address' } ,
197
+ { name : 'spender' , type : 'address' } ,
198
+ { name : 'value' , type : 'uint256' } ,
199
+ { name : 'nonce' , type : 'uint256' } ,
200
+ { name : 'deadline' , type : 'uint256' } ,
201
+ ] ,
202
+ } ;
203
+
204
+ const values : any = {
205
+ owner : await deployer . getAddress ( ) ,
206
+ spender : gatewayAddress ,
207
+ value : amount . toString ( ) ,
208
+ nonce : nonce . toString ( ) ,
209
+ deadline : deadline . toString ( ) ,
210
+ } ;
211
+
212
+ return { domain, types, values } ;
213
+ }
214
+
215
+ function parseSignature ( signature : string ) {
216
+ signature = signature . startsWith ( '0x' ) ? signature . slice ( 2 ) : signature ;
217
+
218
+ // Ensure the signature length is correct
219
+ if ( signature . length !== 130 ) {
220
+ throw new Error ( 'Invalid signature length!' ) ;
221
+ }
222
+ // Extract R, S, V
223
+ const r = '0x' + signature . slice ( 0 , 64 ) ;
224
+ const s = '0x' + signature . slice ( 64 , 128 ) ;
225
+ const v = parseInt ( signature . slice ( 128 , 130 ) , 16 ) ;
226
+ // Return formatted values
227
+ return {
228
+ r,
229
+ s,
230
+ v,
231
+ } ;
232
+ }
233
+
137
234
before ( async ( ) => {
138
235
env = await setupEnvironment ( { } ) ;
139
236
eth_erc20GatewayAddress = (
140
237
await env . eth . fuelERC20Gateway . getAddress ( )
141
238
) . toLowerCase ( ) ;
142
239
143
240
eth_testToken = await getOrDeployECR20Contract ( env ) ;
241
+ eth_permitTestToken = await getOrDeployERC20PermitContract ( env ) ;
144
242
eth_testTokenAddress = ( await eth_testToken . getAddress ( ) ) . toLowerCase ( ) ;
243
+ eth_permitTestTokenAddress = (
244
+ await eth_permitTestToken . getAddress ( )
245
+ ) . toLowerCase ( ) ;
145
246
146
247
const { contract, implementation } = await getOrDeployL2Bridge (
147
248
env ,
@@ -156,6 +257,11 @@ describe('Bridging ERC20 tokens', async function () {
156
257
await env . eth . fuelERC20Gateway . setAssetIssuerId ( fuel_bridgeContractId ) ;
157
258
fuel_testAssetId = getTokenId ( fuel_bridge , eth_testTokenAddress ) ;
158
259
260
+ fuel_test_permit_token_AssetId = getTokenId (
261
+ fuel_bridge ,
262
+ eth_permitTestTokenAddress
263
+ ) ;
264
+
159
265
// initializing rate limit params for the token
160
266
await env . eth . fuelERC20Gateway
161
267
. connect ( env . eth . deployer )
@@ -200,13 +306,17 @@ describe('Bridging ERC20 tokens', async function () {
200
306
201
307
describe ( 'Bridge ERC20 to Fuel' , async ( ) => {
202
308
const NUM_TOKENS = 100000000000000000000n ;
309
+ const DEADLINE = Math . floor ( Date . now ( ) / 1000 ) + 600 ; // 10 mins from current timestamp
203
310
let ethereumTokenSender : Signer ;
204
311
let ethereumTokenSenderAddress : string ;
205
312
let ethereumTokenSenderBalance : bigint ;
313
+ let ethereumPermitTokenSenderBalance : bigint ;
206
314
let fuelTokenReceiver : FuelWallet ;
207
315
let fuelTokenReceiverAddress : string ;
208
316
let fuelTokenReceiverBalance : BN ;
317
+ let fuelPermitTokenReceiverBalance : BN ;
209
318
let fuelTokenMessageNonce : BN ;
319
+ let fuelTokenMessageNonceForPermitToken : BN ;
210
320
let fuelTokenMessageReceiver : AbstractAddress ;
211
321
212
322
before ( async ( ) => {
@@ -217,14 +327,88 @@ describe('Bridging ERC20 tokens', async function () {
217
327
. mint ( ethereumTokenSenderAddress , NUM_TOKENS )
218
328
. then ( ( tx ) => tx . wait ( ) ) ;
219
329
330
+ await eth_permitTestToken
331
+ . mint ( ethereumTokenSenderAddress , NUM_TOKENS )
332
+ . then ( ( tx ) => tx . wait ( ) ) ;
333
+
220
334
ethereumTokenSenderBalance = await eth_testToken . balanceOf (
221
335
ethereumTokenSenderAddress
222
336
) ;
337
+ ethereumPermitTokenSenderBalance = await eth_permitTestToken . balanceOf (
338
+ ethereumTokenSenderAddress
339
+ ) ;
223
340
fuelTokenReceiver = env . fuel . signers [ 0 ] ;
224
341
fuelTokenReceiverAddress = fuelTokenReceiver . address . toHexString ( ) ;
225
342
fuelTokenReceiverBalance = await fuelTokenReceiver . getBalance (
226
343
fuel_testAssetId
227
344
) ;
345
+ fuelPermitTokenReceiverBalance = await fuelTokenReceiver . getBalance (
346
+ fuel_test_permit_token_AssetId
347
+ ) ;
348
+ } ) ;
349
+
350
+ it ( 'Bridge ERC20 token with permit via FuelERC20Gateway' , async ( ) => {
351
+ const tokenName = await eth_permitTestToken . name ( ) ;
352
+ const tokenAddress = await eth_permitTestToken . getAddress ( ) ;
353
+ const gatewayAddress = await env . eth . fuelERC20Gateway . getAddress ( ) ;
354
+ const deployerNonce = await eth_permitTestToken . nonces (
355
+ ethereumTokenSender
356
+ ) ;
357
+
358
+ const signatureParams = await buildPermitParams (
359
+ tokenName ,
360
+ tokenAddress ,
361
+ gatewayAddress ,
362
+ NUM_TOKENS ,
363
+ deployerNonce ,
364
+ DEADLINE ,
365
+ ethereumTokenSender
366
+ ) ;
367
+
368
+ const signature = await ethereumTokenSender . signTypedData (
369
+ signatureParams . domain ,
370
+ signatureParams . types ,
371
+ signatureParams . values
372
+ ) ;
373
+
374
+ const { r, s, v } = parseSignature ( signature ) ;
375
+ const permitData = {
376
+ deadline : DEADLINE ,
377
+ v,
378
+ r,
379
+ s,
380
+ } ;
381
+
382
+ // use the FuelERC20Gateway to deposit test tokens and receive equivalent tokens on Fuel
383
+ const receipt = await env . eth . fuelERC20Gateway
384
+ . connect ( ethereumTokenSender )
385
+ . depositWithPermit (
386
+ fuelTokenReceiverAddress ,
387
+ eth_permitTestTokenAddress ,
388
+ NUM_TOKENS ,
389
+ permitData
390
+ )
391
+ . then ( ( tx ) => tx . wait ( ) ) ;
392
+
393
+ expect ( receipt . status ) . to . equal ( 1 ) ;
394
+
395
+ // parse events from logs
396
+ const [ event , ...restOfEvents ] =
397
+ await env . eth . fuelMessagePortal . queryFilter (
398
+ env . eth . fuelMessagePortal . filters . MessageSent ,
399
+ receipt . blockNumber ,
400
+ receipt . blockNumber
401
+ ) ;
402
+ expect ( restOfEvents . length ) . to . be . eq ( 0 ) ; // Should be only 1 event
403
+
404
+ fuelTokenMessageNonceForPermitToken = new BN ( event . args . nonce . toString ( ) ) ;
405
+
406
+ // check that the sender balance has decreased by the expected amount
407
+ const newSenderBalance = await eth_permitTestToken . balanceOf (
408
+ ethereumTokenSenderAddress
409
+ ) ;
410
+ expect ( newSenderBalance === ethereumPermitTokenSenderBalance - NUM_TOKENS )
411
+ . to . be . true ;
228
412
} ) ;
229
413
230
414
it ( 'Bridge ERC20 via FuelERC20Gateway' , async ( ) => {
@@ -241,7 +425,6 @@ describe('Bridging ERC20 tokens', async function () {
241
425
. then ( ( tx ) => tx . wait ( ) ) ;
242
426
243
427
expect ( receipt . status ) . to . equal ( 1 ) ;
244
-
245
428
// parse events from logs
246
429
const [ event , ...restOfEvents ] =
247
430
await env . eth . fuelMessagePortal . queryFilter (
@@ -262,34 +445,27 @@ describe('Bridging ERC20 tokens', async function () {
262
445
. true ;
263
446
} ) ;
264
447
265
- it ( 'Relay message from Ethereum on Fuel' , async ( ) => {
448
+ it ( 'Relay messages from Ethereum on Fuel' , async ( ) => {
266
449
// override the default test timeout from 2000ms
267
450
this . timeout ( FUEL_MESSAGE_TIMEOUT_MS ) ;
268
-
269
- // relay the message ourselves
270
- const message = await waitForMessage (
271
- env . fuel . provider ,
451
+ // relay the standard erc20 deposit
452
+ await relayMessageFromEthereum (
453
+ env ,
272
454
fuelTokenMessageReceiver ,
273
455
fuelTokenMessageNonce ,
274
- FUEL_MESSAGE_TIMEOUT_MS
456
+ fuel_testAssetId ,
457
+ NUM_TOKENS
275
458
) ;
276
- expect ( message ) . to . not . be . null ;
277
459
278
- const tx = await relayCommonMessage ( env . fuel . deployer , message , {
279
- maturity : undefined ,
280
- contractIds : [ fuel_bridgeImpl . id . toHexString ( ) ] ,
281
- } ) ;
282
-
283
- const txResult = await tx . waitForResult ( ) ;
284
-
285
- expect ( txResult . status ) . to . equal ( 'success' ) ;
286
- expect ( txResult . mintedAssets . length ) . to . equal ( 1 ) ;
287
-
288
- const [ mintedAsset ] = txResult . mintedAssets ;
289
-
290
- expect ( mintedAsset . assetId ) . to . equal ( fuel_testAssetId ) ;
291
- expect ( mintedAsset . amount . toString ( ) ) . to . equal (
292
- ( NUM_TOKENS / DECIMAL_DIFF ) . toString ( )
460
+ // override the default test timeout from 2000ms
461
+ this . timeout ( FUEL_MESSAGE_TIMEOUT_MS ) ;
462
+ // relay the erc20 permit token deposit
463
+ await relayMessageFromEthereum (
464
+ env ,
465
+ fuelTokenMessageReceiver ,
466
+ fuelTokenMessageNonceForPermitToken ,
467
+ fuel_test_permit_token_AssetId ,
468
+ NUM_TOKENS
293
469
) ;
294
470
} ) ;
295
471
@@ -320,6 +496,19 @@ describe('Bridging ERC20 tokens', async function () {
320
496
) . to . be . true ;
321
497
} ) ;
322
498
499
+ it ( 'Check ERC20 permit token arrived on Fuel' , async ( ) => {
500
+ // check that the recipient balance has increased by the expected amount
501
+ const newReceiverPermitBalance = await fuelTokenReceiver . getBalance (
502
+ fuel_test_permit_token_AssetId
503
+ ) ;
504
+
505
+ expect (
506
+ newReceiverPermitBalance . eq (
507
+ fuelPermitTokenReceiverBalance . add ( toBeHex ( NUM_TOKENS / DECIMAL_DIFF ) )
508
+ )
509
+ ) . to . be . true ;
510
+ } ) ;
511
+
323
512
it ( 'Bridge metadata' , async ( ) => {
324
513
// use the FuelERC20Gateway to deposit test tokens and receive equivalent tokens on Fuel
325
514
const receipt = await env . eth . fuelERC20Gateway
@@ -416,7 +605,7 @@ describe('Bridging ERC20 tokens', async function () {
416
605
await env . eth . fuelERC20Gateway . currentPeriodEnd ( eth_testTokenAddress ) ;
417
606
418
607
// relay message
419
- await relayMessage ( env , withdrawMessageProof ) ;
608
+ await relayMessageFromFuel ( env , withdrawMessageProof ) ;
420
609
421
610
// check rate limit params
422
611
const withdrawnAmountAfterRelay =
@@ -541,7 +730,7 @@ describe('Bridging ERC20 tokens', async function () {
541
730
) ;
542
731
543
732
// relay message
544
- await relayMessage ( env , withdrawMessageProof ) ;
733
+ await relayMessageFromFuel ( env , withdrawMessageProof ) ;
545
734
546
735
const currentPeriodEndAfterRelay =
547
736
await env . eth . fuelERC20Gateway . currentPeriodEnd ( eth_testTokenAddress ) ;
0 commit comments