1
// Copyright 2019-2025 PureStake Inc.
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonbeam is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
use crate::{eip2612::Eip2612, mock::*, *};
18
use frame_support::assert_ok;
19
use hex_literal::hex;
20
use libsecp256k1::{sign, Message, SecretKey};
21
use precompile_utils::testing::*;
22
use sha3::{Digest, Keccak256};
23
use sp_core::H256;
24
use std::str::from_utf8;
25

            
26
64
fn precompiles() -> Precompiles<Runtime> {
27
64
	PrecompilesValue::get()
28
64
}
29

            
30
#[test]
31
1
fn selector_less_than_four_bytes() {
32
1
	ExtBuilder::default().build().execute_with(|| {
33
1
		assert_ok!(ForeignAssets::force_create(
34
1
			RuntimeOrigin::root(),
35
1
			0u128,
36
1
			CryptoAlith.into(),
37
1
			true,
38
1
			1
39
1
		));
40
		// This selector is only three bytes long when four are required.
41
1
		precompiles()
42
1
			.prepare_test(CryptoAlith, ForeignAssetId(0u128), vec![1u8, 2u8, 3u8])
43
1
			.execute_reverts(|output| output == b"Tried to read selector out of bounds");
44
1
	});
45
1
}
46

            
47
#[test]
48
1
fn no_selector_exists_but_length_is_right() {
49
1
	ExtBuilder::default().build().execute_with(|| {
50
1
		assert_ok!(ForeignAssets::force_create(
51
1
			RuntimeOrigin::root(),
52
1
			0u128,
53
1
			CryptoAlith.into(),
54
1
			true,
55
1
			1
56
1
		));
57

            
58
1
		precompiles()
59
1
			.prepare_test(CryptoAlith, ForeignAssetId(0u128), vec![1u8, 2u8, 3u8, 4u8])
60
1
			.execute_reverts(|output| output == b"Unknown selector");
61
1
	});
62
1
}
63

            
64
#[test]
65
1
fn selectors() {
66
1
	assert!(ForeignPCall::balance_of_selectors().contains(&0x70a08231));
67
1
	assert!(ForeignPCall::total_supply_selectors().contains(&0x18160ddd));
68
1
	assert!(ForeignPCall::approve_selectors().contains(&0x095ea7b3));
69
1
	assert!(ForeignPCall::allowance_selectors().contains(&0xdd62ed3e));
70
1
	assert!(ForeignPCall::freezer_selectors().contains(&0x92716054));
71
1
	assert!(ForeignPCall::owner_selectors().contains(&0x8da5cb5b));
72
1
	assert!(ForeignPCall::issuer_selectors().contains(&0x1d143848));
73
1
	assert!(ForeignPCall::admin_selectors().contains(&0xf851a440));
74
1
	assert!(ForeignPCall::transfer_selectors().contains(&0xa9059cbb));
75
1
	assert!(ForeignPCall::transfer_from_selectors().contains(&0x23b872dd));
76
1
	assert!(ForeignPCall::name_selectors().contains(&0x06fdde03));
77
1
	assert!(ForeignPCall::symbol_selectors().contains(&0x95d89b41));
78
1
	assert!(ForeignPCall::decimals_selectors().contains(&0x313ce567));
79
1
	assert!(ForeignPCall::eip2612_nonces_selectors().contains(&0x7ecebe00));
80
1
	assert!(ForeignPCall::eip2612_permit_selectors().contains(&0xd505accf));
81
1
	assert!(ForeignPCall::eip2612_domain_separator_selectors().contains(&0x3644e515));
82

            
83
1
	assert_eq!(
84
1
		crate::SELECTOR_LOG_TRANSFER,
85
1
		&Keccak256::digest(b"Transfer(address,address,uint256)")[..]
86
1
	);
87

            
88
1
	assert_eq!(
89
1
		crate::SELECTOR_LOG_APPROVAL,
90
1
		&Keccak256::digest(b"Approval(address,address,uint256)")[..]
91
1
	);
92
1
}
93

            
94
#[test]
95
1
fn modifiers() {
96
1
	ExtBuilder::default()
97
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
98
1
		.build()
99
1
		.execute_with(|| {
100
1
			assert_ok!(ForeignAssets::force_create(
101
1
				RuntimeOrigin::root(),
102
1
				0u128,
103
1
				CryptoAlith.into(),
104
1
				true,
105
1
				1
106
1
			));
107
1
			let mut tester =
108
1
				PrecompilesModifierTester::new(precompiles(), CryptoAlith, ForeignAssetId(0u128));
109
1

            
110
1
			tester.test_view_modifier(ForeignPCall::balance_of_selectors());
111
1
			tester.test_view_modifier(ForeignPCall::total_supply_selectors());
112
1
			tester.test_default_modifier(ForeignPCall::approve_selectors());
113
1
			tester.test_view_modifier(ForeignPCall::allowance_selectors());
114
1
			tester.test_default_modifier(ForeignPCall::transfer_selectors());
115
1
			tester.test_default_modifier(ForeignPCall::transfer_from_selectors());
116
1
			tester.test_view_modifier(ForeignPCall::name_selectors());
117
1
			tester.test_view_modifier(ForeignPCall::symbol_selectors());
118
1
			tester.test_view_modifier(ForeignPCall::decimals_selectors());
119
1
			tester.test_view_modifier(ForeignPCall::eip2612_nonces_selectors());
120
1
			tester.test_default_modifier(ForeignPCall::eip2612_permit_selectors());
121
1
			tester.test_view_modifier(ForeignPCall::eip2612_domain_separator_selectors());
122
1
		});
123
1
}
124

            
125
#[test]
126
1
fn get_total_supply() {
127
1
	ExtBuilder::default()
128
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
129
1
		.build()
130
1
		.execute_with(|| {
131
1
			assert_ok!(ForeignAssets::force_create(
132
1
				RuntimeOrigin::root(),
133
1
				0u128,
134
1
				CryptoAlith.into(),
135
1
				true,
136
1
				1
137
1
			));
138
1
			assert_ok!(ForeignAssets::mint(
139
1
				RuntimeOrigin::signed(CryptoAlith.into()),
140
1
				0u128,
141
1
				CryptoAlith.into(),
142
1
				1000
143
1
			));
144

            
145
1
			precompiles()
146
1
				.prepare_test(
147
1
					CryptoAlith,
148
1
					ForeignAssetId(0u128),
149
1
					ForeignPCall::total_supply {},
150
1
				)
151
1
				.expect_cost(0) // TODO: Test db read/write costs
152
1
				.expect_no_logs()
153
1
				.execute_returns(U256::from(1000u64));
154
1
		});
155
1
}
156

            
157
#[test]
158
1
fn get_balances_known_user() {
159
1
	ExtBuilder::default()
160
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
161
1
		.build()
162
1
		.execute_with(|| {
163
1
			assert_ok!(ForeignAssets::force_create(
164
1
				RuntimeOrigin::root(),
165
1
				0u128,
166
1
				CryptoAlith.into(),
167
1
				true,
168
1
				1
169
1
			));
170
1
			assert_ok!(ForeignAssets::mint(
171
1
				RuntimeOrigin::signed(CryptoAlith.into()),
172
1
				0u128,
173
1
				CryptoAlith.into(),
174
1
				1000
175
1
			));
176

            
177
1
			precompiles()
178
1
				.prepare_test(
179
1
					CryptoAlith,
180
1
					ForeignAssetId(0u128),
181
1
					ForeignPCall::balance_of {
182
1
						who: Address(CryptoAlith.into()),
183
1
					},
184
1
				)
185
1
				.expect_cost(0) // TODO: Test db read/write costs
186
1
				.expect_no_logs()
187
1
				.execute_returns(U256::from(1000u64));
188
1
		});
189
1
}
190

            
191
#[test]
192
1
fn get_balances_unknown_user() {
193
1
	ExtBuilder::default()
194
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
195
1
		.build()
196
1
		.execute_with(|| {
197
1
			assert_ok!(ForeignAssets::force_create(
198
1
				RuntimeOrigin::root(),
199
1
				0u128,
200
1
				CryptoAlith.into(),
201
1
				true,
202
1
				1
203
1
			));
204

            
205
1
			precompiles()
206
1
				.prepare_test(
207
1
					CryptoAlith,
208
1
					ForeignAssetId(0u128),
209
1
					ForeignPCall::balance_of {
210
1
						who: Address(Bob.into()),
211
1
					},
212
1
				)
213
1
				.expect_cost(0) // TODO: Test db read/write costs
214
1
				.expect_no_logs()
215
1
				.execute_returns(U256::from(0u64));
216
1
		});
217
1
}
218

            
219
#[test]
220
1
fn approve() {
221
1
	ExtBuilder::default()
222
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
223
1
		.build()
224
1
		.execute_with(|| {
225
1
			assert_ok!(ForeignAssets::force_create(
226
1
				RuntimeOrigin::root(),
227
1
				0u128,
228
1
				CryptoAlith.into(),
229
1
				true,
230
1
				1
231
1
			));
232
1
			assert_ok!(ForeignAssets::mint(
233
1
				RuntimeOrigin::signed(CryptoAlith.into()),
234
1
				0u128,
235
1
				CryptoAlith.into(),
236
1
				1000
237
1
			));
238

            
239
1
			precompiles()
240
1
				.prepare_test(
241
1
					CryptoAlith,
242
1
					ForeignAssetId(0u128),
243
1
					ForeignPCall::approve {
244
1
						spender: Address(Bob.into()),
245
1
						value: 500.into(),
246
1
					},
247
1
				)
248
1
				.expect_cost(37024756)
249
1
				.expect_log(log3(
250
1
					ForeignAssetId(0u128),
251
1
					SELECTOR_LOG_APPROVAL,
252
1
					CryptoAlith,
253
1
					Bob,
254
1
					solidity::encode_event_data(U256::from(500)),
255
1
				))
256
1
				.execute_returns(true);
257
1
		});
258
1
}
259

            
260
#[test]
261
1
fn approve_saturating() {
262
1
	ExtBuilder::default()
263
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
264
1
		.build()
265
1
		.execute_with(|| {
266
1
			assert_ok!(ForeignAssets::force_create(
267
1
				RuntimeOrigin::root(),
268
1
				0u128,
269
1
				CryptoAlith.into(),
270
1
				true,
271
1
				1
272
1
			));
273
1
			assert_ok!(ForeignAssets::mint(
274
1
				RuntimeOrigin::signed(CryptoAlith.into()),
275
1
				0u128,
276
1
				CryptoAlith.into(),
277
1
				1000
278
1
			));
279

            
280
1
			precompiles()
281
1
				.prepare_test(
282
1
					CryptoAlith,
283
1
					ForeignAssetId(0u128),
284
1
					ForeignPCall::approve {
285
1
						spender: Address(Bob.into()),
286
1
						value: U256::MAX,
287
1
					},
288
1
				)
289
1
				.expect_cost(37024756)
290
1
				.expect_log(log3(
291
1
					ForeignAssetId(0u128),
292
1
					SELECTOR_LOG_APPROVAL,
293
1
					CryptoAlith,
294
1
					Bob,
295
1
					solidity::encode_event_data(U256::MAX),
296
1
				))
297
1
				.execute_returns(true);
298
1

            
299
1
			precompiles()
300
1
				.prepare_test(
301
1
					CryptoAlith,
302
1
					ForeignAssetId(0u128),
303
1
					ForeignPCall::allowance {
304
1
						owner: Address(CryptoAlith.into()),
305
1
						spender: Address(Bob.into()),
306
1
					},
307
1
				)
308
1
				.expect_cost(0u64)
309
1
				.expect_no_logs()
310
1
				.execute_returns(U256::from(u128::MAX));
311
1
		});
312
1
}
313

            
314
#[test]
315
1
fn check_allowance_existing() {
316
1
	ExtBuilder::default()
317
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
318
1
		.build()
319
1
		.execute_with(|| {
320
1
			assert_ok!(ForeignAssets::force_create(
321
1
				RuntimeOrigin::root(),
322
1
				0u128,
323
1
				CryptoAlith.into(),
324
1
				true,
325
1
				1
326
1
			));
327
1
			assert_ok!(ForeignAssets::mint(
328
1
				RuntimeOrigin::signed(CryptoAlith.into()),
329
1
				0u128,
330
1
				CryptoAlith.into(),
331
1
				1000
332
1
			));
333

            
334
1
			precompiles()
335
1
				.prepare_test(
336
1
					CryptoAlith,
337
1
					ForeignAssetId(0u128),
338
1
					ForeignPCall::approve {
339
1
						spender: Address(Bob.into()),
340
1
						value: 500.into(),
341
1
					},
342
1
				)
343
1
				.execute_some();
344
1

            
345
1
			precompiles()
346
1
				.prepare_test(
347
1
					CryptoAlith,
348
1
					ForeignAssetId(0u128),
349
1
					ForeignPCall::allowance {
350
1
						owner: Address(CryptoAlith.into()),
351
1
						spender: Address(Bob.into()),
352
1
					},
353
1
				)
354
1
				.expect_cost(0) // TODO: Test db read/write costs
355
1
				.expect_no_logs()
356
1
				.execute_returns(U256::from(500u64));
357
1
		});
358
1
}
359

            
360
#[test]
361
1
fn check_allowance_not_existing() {
362
1
	ExtBuilder::default()
363
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
364
1
		.build()
365
1
		.execute_with(|| {
366
1
			assert_ok!(ForeignAssets::force_create(
367
1
				RuntimeOrigin::root(),
368
1
				0u128,
369
1
				CryptoAlith.into(),
370
1
				true,
371
1
				1
372
1
			));
373

            
374
1
			precompiles()
375
1
				.prepare_test(
376
1
					CryptoAlith,
377
1
					ForeignAssetId(0u128),
378
1
					ForeignPCall::allowance {
379
1
						owner: Address(CryptoAlith.into()),
380
1
						spender: Address(Bob.into()),
381
1
					},
382
1
				)
383
1
				.expect_cost(0) // TODO: Test db read/write costs
384
1
				.expect_no_logs()
385
1
				.execute_returns(U256::from(0u64));
386
1
		});
387
1
}
388

            
389
#[test]
390
1
fn transfer() {
391
1
	ExtBuilder::default()
392
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
393
1
		.build()
394
1
		.execute_with(|| {
395
1
			assert_ok!(ForeignAssets::force_create(
396
1
				RuntimeOrigin::root(),
397
1
				0u128,
398
1
				CryptoAlith.into(),
399
1
				true,
400
1
				1
401
1
			));
402
1
			assert_ok!(ForeignAssets::mint(
403
1
				RuntimeOrigin::signed(CryptoAlith.into()),
404
1
				0u128,
405
1
				CryptoAlith.into(),
406
1
				1000
407
1
			));
408

            
409
1
			precompiles()
410
1
				.prepare_test(
411
1
					CryptoAlith,
412
1
					ForeignAssetId(0u128),
413
1
					ForeignPCall::transfer {
414
1
						to: Address(Bob.into()),
415
1
						value: 400.into(),
416
1
					},
417
1
				)
418
1
				.expect_cost(50509756) // 1 weight => 1 gas in mock
419
1
				.expect_log(log3(
420
1
					ForeignAssetId(0u128),
421
1
					SELECTOR_LOG_TRANSFER,
422
1
					CryptoAlith,
423
1
					Bob,
424
1
					solidity::encode_event_data(U256::from(400)),
425
1
				))
426
1
				.execute_returns(true);
427
1

            
428
1
			precompiles()
429
1
				.prepare_test(
430
1
					Bob,
431
1
					ForeignAssetId(0u128),
432
1
					ForeignPCall::balance_of {
433
1
						who: Address(Bob.into()),
434
1
					},
435
1
				)
436
1
				.expect_cost(0) // TODO: Test db read/write costs
437
1
				.expect_no_logs()
438
1
				.execute_returns(U256::from(400));
439
1

            
440
1
			precompiles()
441
1
				.prepare_test(
442
1
					CryptoAlith,
443
1
					ForeignAssetId(0u128),
444
1
					ForeignPCall::balance_of {
445
1
						who: Address(CryptoAlith.into()),
446
1
					},
447
1
				)
448
1
				.expect_cost(0) // TODO: Test db read/write costs
449
1
				.expect_no_logs()
450
1
				.execute_returns(U256::from(600));
451
1
		});
452
1
}
453

            
454
#[test]
455
1
fn transfer_not_enough_founds() {
456
1
	ExtBuilder::default()
457
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
458
1
		.build()
459
1
		.execute_with(|| {
460
1
			assert_ok!(ForeignAssets::force_create(
461
1
				RuntimeOrigin::root(),
462
1
				0u128,
463
1
				CryptoAlith.into(),
464
1
				true,
465
1
				1
466
1
			));
467
1
			assert_ok!(ForeignAssets::mint(
468
1
				RuntimeOrigin::signed(CryptoAlith.into()),
469
1
				0u128,
470
1
				CryptoAlith.into(),
471
1
				1
472
1
			));
473

            
474
1
			precompiles()
475
1
				.prepare_test(
476
1
					CryptoAlith,
477
1
					ForeignAssetId(0u128),
478
1
					ForeignPCall::transfer {
479
1
						to: Address(Charlie.into()),
480
1
						value: 50.into(),
481
1
					},
482
1
				)
483
1
				.execute_reverts(|output| {
484
1
					from_utf8(&output)
485
1
						.unwrap()
486
1
						.contains("Dispatched call failed with error: ")
487
1
						&& from_utf8(&output).unwrap().contains("BalanceLow")
488
1
				});
489
1
		});
490
1
}
491

            
492
#[test]
493
1
fn transfer_from() {
494
1
	ExtBuilder::default()
495
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
496
1
		.build()
497
1
		.execute_with(|| {
498
1
			assert_ok!(ForeignAssets::force_create(
499
1
				RuntimeOrigin::root(),
500
1
				0u128,
501
1
				CryptoAlith.into(),
502
1
				true,
503
1
				1
504
1
			));
505
1
			assert_ok!(ForeignAssets::mint(
506
1
				RuntimeOrigin::signed(CryptoAlith.into()),
507
1
				0u128,
508
1
				CryptoAlith.into(),
509
1
				1000
510
1
			));
511

            
512
1
			precompiles()
513
1
				.prepare_test(
514
1
					CryptoAlith,
515
1
					ForeignAssetId(0u128),
516
1
					ForeignPCall::approve {
517
1
						spender: Address(Bob.into()),
518
1
						value: 500.into(),
519
1
					},
520
1
				)
521
1
				.execute_some();
522
1

            
523
1
			// TODO: Duplicate approve (noop)?
524
1
			precompiles()
525
1
				.prepare_test(
526
1
					CryptoAlith,
527
1
					ForeignAssetId(0u128),
528
1
					ForeignPCall::approve {
529
1
						spender: Address(Bob.into()),
530
1
						value: 500.into(),
531
1
					},
532
1
				)
533
1
				.execute_some();
534
1

            
535
1
			precompiles()
536
1
				.prepare_test(
537
1
					Bob, // Bob is the one sending transferFrom!
538
1
					ForeignAssetId(0u128),
539
1
					ForeignPCall::transfer_from {
540
1
						from: Address(CryptoAlith.into()),
541
1
						to: Address(Charlie.into()),
542
1
						value: 400.into(),
543
1
					},
544
1
				)
545
1
				.expect_cost(70172756) // 1 weight => 1 gas in mock
546
1
				.expect_log(log3(
547
1
					ForeignAssetId(0u128),
548
1
					SELECTOR_LOG_TRANSFER,
549
1
					CryptoAlith,
550
1
					Charlie,
551
1
					solidity::encode_event_data(U256::from(400)),
552
1
				))
553
1
				.execute_returns(true);
554
1

            
555
1
			precompiles()
556
1
				.prepare_test(
557
1
					CryptoAlith,
558
1
					ForeignAssetId(0u128),
559
1
					ForeignPCall::balance_of {
560
1
						who: Address(CryptoAlith.into()),
561
1
					},
562
1
				)
563
1
				.expect_cost(0) // TODO: Test db read/write costs
564
1
				.expect_no_logs()
565
1
				.execute_returns(U256::from(600));
566
1

            
567
1
			precompiles()
568
1
				.prepare_test(
569
1
					Bob,
570
1
					ForeignAssetId(0u128),
571
1
					ForeignPCall::balance_of {
572
1
						who: Address(Bob.into()),
573
1
					},
574
1
				)
575
1
				.expect_cost(0) // TODO: Test db read/write costs
576
1
				.expect_no_logs()
577
1
				.execute_returns(U256::from(0));
578
1

            
579
1
			precompiles()
580
1
				.prepare_test(
581
1
					Charlie,
582
1
					ForeignAssetId(0u128),
583
1
					ForeignPCall::balance_of {
584
1
						who: Address(Charlie.into()),
585
1
					},
586
1
				)
587
1
				.expect_cost(0) // TODO: Test db read/write costs
588
1
				.expect_no_logs()
589
1
				.execute_returns(U256::from(400));
590
1
		});
591
1
}
592

            
593
#[test]
594
1
fn transfer_from_non_incremental_approval() {
595
1
	ExtBuilder::default()
596
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
597
1
		.build()
598
1
		.execute_with(|| {
599
1
			assert_ok!(ForeignAssets::force_create(
600
1
				RuntimeOrigin::root(),
601
1
				0u128,
602
1
				CryptoAlith.into(),
603
1
				true,
604
1
				1
605
1
			));
606
1
			assert_ok!(ForeignAssets::mint(
607
1
				RuntimeOrigin::signed(CryptoAlith.into()),
608
1
				0u128,
609
1
				CryptoAlith.into(),
610
1
				1000
611
1
			));
612

            
613
			// We first approve 500
614
1
			precompiles()
615
1
				.prepare_test(
616
1
					CryptoAlith,
617
1
					ForeignAssetId(0u128),
618
1
					ForeignPCall::approve {
619
1
						spender: Address(Bob.into()),
620
1
						value: 500.into(),
621
1
					},
622
1
				)
623
1
				.expect_cost(37024756)
624
1
				.expect_log(log3(
625
1
					ForeignAssetId(0u128),
626
1
					SELECTOR_LOG_APPROVAL,
627
1
					CryptoAlith,
628
1
					Bob,
629
1
					solidity::encode_event_data(U256::from(500)),
630
1
				))
631
1
				.execute_returns(true);
632
1

            
633
1
			// We then approve 300. Non-incremental, so this is
634
1
			// the approved new value
635
1
			// Additionally, the gas used in this approval is higher because we
636
1
			// need to clear the previous one
637
1
			precompiles()
638
1
				.prepare_test(
639
1
					CryptoAlith,
640
1
					ForeignAssetId(0u128),
641
1
					ForeignPCall::approve {
642
1
						spender: Address(Bob.into()),
643
1
						value: 300.into(),
644
1
					},
645
1
				)
646
1
				.expect_cost(76042756)
647
1
				.expect_log(log3(
648
1
					ForeignAssetId(0u128),
649
1
					SELECTOR_LOG_APPROVAL,
650
1
					CryptoAlith,
651
1
					Bob,
652
1
					solidity::encode_event_data(U256::from(300)),
653
1
				))
654
1
				.execute_returns(true);
655
1

            
656
1
			// This should fail, as now the new approved quantity is 300
657
1
			precompiles()
658
1
				.prepare_test(
659
1
					Bob, // Bob is the one sending transferFrom!
660
1
					ForeignAssetId(0u128),
661
1
					ForeignPCall::transfer_from {
662
1
						from: Address(CryptoAlith.into()),
663
1
						to: Address(Bob.into()),
664
1
						value: 500.into(),
665
1
					},
666
1
				)
667
1
				.execute_reverts(|output| {
668
1
					output
669
1
						== b"Dispatched call failed with error: Module(ModuleError { index: 2, error: [10, 0, 0, 0], \
670
1
					message: Some(\"Unapproved\") })"
671
1
				});
672
1
		});
673
1
}
674

            
675
#[test]
676
1
fn transfer_from_above_allowance() {
677
1
	ExtBuilder::default()
678
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
679
1
		.build()
680
1
		.execute_with(|| {
681
1
			assert_ok!(ForeignAssets::force_create(
682
1
				RuntimeOrigin::root(),
683
1
				0u128,
684
1
				CryptoAlith.into(),
685
1
				true,
686
1
				1
687
1
			));
688
1
			assert_ok!(ForeignAssets::mint(
689
1
				RuntimeOrigin::signed(CryptoAlith.into()),
690
1
				0u128,
691
1
				CryptoAlith.into(),
692
1
				1000
693
1
			));
694

            
695
1
			precompiles()
696
1
				.prepare_test(
697
1
					CryptoAlith,
698
1
					ForeignAssetId(0u128),
699
1
					ForeignPCall::approve {
700
1
						spender: Address(Bob.into()),
701
1
						value: 300.into(),
702
1
					},
703
1
				)
704
1
				.execute_some();
705
1

            
706
1
			precompiles()
707
1
				.prepare_test(
708
1
					Bob, // Bob is the one sending transferFrom!
709
1
					ForeignAssetId(0u128),
710
1
					ForeignPCall::transfer_from {
711
1
						from: Address(CryptoAlith.into()),
712
1
						to: Address(Bob.into()),
713
1
						value: 400.into(),
714
1
					},
715
1
				)
716
1
				.execute_reverts(|output| {
717
1
					output
718
1
						== b"Dispatched call failed with error: Module(ModuleError { index: 2, error: [10, 0, 0, 0], \
719
1
					message: Some(\"Unapproved\") })"
720
1
				});
721
1
		});
722
1
}
723

            
724
#[test]
725
1
fn transfer_from_self() {
726
1
	ExtBuilder::default()
727
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
728
1
		.build()
729
1
		.execute_with(|| {
730
1
			assert_ok!(ForeignAssets::force_create(
731
1
				RuntimeOrigin::root(),
732
1
				0u128,
733
1
				CryptoAlith.into(),
734
1
				true,
735
1
				1
736
1
			));
737
1
			assert_ok!(ForeignAssets::mint(
738
1
				RuntimeOrigin::signed(CryptoAlith.into()),
739
1
				0u128,
740
1
				CryptoAlith.into(),
741
1
				1000
742
1
			));
743

            
744
1
			precompiles()
745
1
				.prepare_test(
746
1
					CryptoAlith, // CryptoAlith sending transferFrom herself, no need for allowance.
747
1
					ForeignAssetId(0u128),
748
1
					ForeignPCall::transfer_from {
749
1
						from: Address(CryptoAlith.into()),
750
1
						to: Address(Bob.into()),
751
1
						value: 400.into(),
752
1
					},
753
1
				)
754
1
				.expect_cost(50509756) // 1 weight => 1 gas in mock
755
1
				.expect_log(log3(
756
1
					ForeignAssetId(0u128),
757
1
					SELECTOR_LOG_TRANSFER,
758
1
					CryptoAlith,
759
1
					Bob,
760
1
					solidity::encode_event_data(U256::from(400)),
761
1
				))
762
1
				.execute_returns(true);
763
1

            
764
1
			precompiles()
765
1
				.prepare_test(
766
1
					CryptoAlith,
767
1
					ForeignAssetId(0u128),
768
1
					ForeignPCall::balance_of {
769
1
						who: Address(CryptoAlith.into()),
770
1
					},
771
1
				)
772
1
				.expect_cost(0) // TODO: Test db read/write costs
773
1
				.expect_no_logs()
774
1
				.execute_returns(U256::from(600));
775
1

            
776
1
			precompiles()
777
1
				.prepare_test(
778
1
					CryptoAlith,
779
1
					ForeignAssetId(0u128),
780
1
					ForeignPCall::balance_of {
781
1
						who: Address(Bob.into()),
782
1
					},
783
1
				)
784
1
				.expect_cost(0) // TODO: Test db read/write costs
785
1
				.expect_no_logs()
786
1
				.execute_returns(U256::from(400));
787
1
		});
788
1
}
789

            
790
#[test]
791
1
fn get_metadata() {
792
1
	ExtBuilder::default()
793
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
794
1
		.build()
795
1
		.execute_with(|| {
796
1
			assert_ok!(ForeignAssets::force_create(
797
1
				RuntimeOrigin::root(),
798
1
				0u128,
799
1
				CryptoAlith.into(),
800
1
				true,
801
1
				1
802
1
			));
803
1
			assert_ok!(ForeignAssets::force_set_metadata(
804
1
				RuntimeOrigin::root(),
805
1
				0u128,
806
1
				b"TestToken".to_vec(),
807
1
				b"Test".to_vec(),
808
1
				12,
809
1
				false
810
1
			));
811

            
812
1
			precompiles()
813
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::name {})
814
1
				.expect_cost(0) // TODO: Test db read/write costs
815
1
				.expect_no_logs()
816
1
				.execute_returns(UnboundedBytes::from("TestToken"));
817
1

            
818
1
			precompiles()
819
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::symbol {})
820
1
				.expect_cost(0) // TODO: Test db read/write costs
821
1
				.expect_no_logs()
822
1
				.execute_returns(UnboundedBytes::from("Test"));
823
1

            
824
1
			precompiles()
825
1
				.prepare_test(
826
1
					CryptoAlith,
827
1
					ForeignAssetId(0u128),
828
1
					ForeignPCall::decimals {},
829
1
				)
830
1
				.expect_cost(0) // TODO: Test db read/write costs
831
1
				.expect_no_logs()
832
1
				.execute_returns(12u8);
833
1
		});
834
1
}
835

            
836
#[test]
837
1
fn permit_valid() {
838
1
	ExtBuilder::default()
839
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
840
1
		.build()
841
1
		.execute_with(|| {
842
1
			assert_ok!(ForeignAssets::force_create(
843
1
				RuntimeOrigin::root(),
844
1
				0u128,
845
1
				CryptoAlith.into(),
846
1
				true,
847
1
				1
848
1
			));
849
1
			assert_ok!(ForeignAssets::mint(
850
1
				RuntimeOrigin::signed(CryptoAlith.into()),
851
1
				0u128,
852
1
				CryptoAlith.into(),
853
1
				1000
854
1
			));
855

            
856
1
			let owner: H160 = CryptoAlith.into();
857
1
			let spender: H160 = Bob.into();
858
1
			let value: U256 = 500u16.into();
859
1
			let deadline: U256 = 0u8.into(); // todo: proper timestamp
860
1

            
861
1
			let permit = Eip2612::<Runtime, pallet_assets::Instance1>::generate_permit(
862
1
				ForeignAssetId(0u128).into(),
863
1
				0u128,
864
1
				owner,
865
1
				spender,
866
1
				value,
867
1
				0u8.into(), // nonce
868
1
				deadline,
869
1
			);
870
1

            
871
1
			let secret_key = SecretKey::parse(&alith_secret_key()).unwrap();
872
1
			let message = Message::parse(&permit);
873
1
			let (rs, v) = sign(&message, &secret_key);
874
1

            
875
1
			precompiles()
876
1
				.prepare_test(
877
1
					CryptoAlith,
878
1
					ForeignAssetId(0u128),
879
1
					ForeignPCall::eip2612_nonces {
880
1
						owner: Address(CryptoAlith.into()),
881
1
					},
882
1
				)
883
1
				.expect_cost(0) // TODO: Test db read/write costs
884
1
				.expect_no_logs()
885
1
				.execute_returns(U256::from(0u8));
886
1

            
887
1
			precompiles()
888
1
				.prepare_test(
889
1
					Charlie,
890
1
					ForeignAssetId(0u128),
891
1
					ForeignPCall::eip2612_permit {
892
1
						owner: Address(owner),
893
1
						spender: Address(spender),
894
1
						value,
895
1
						deadline,
896
1
						v: v.serialize(),
897
1
						r: H256::from(rs.r.b32()),
898
1
						s: H256::from(rs.s.b32()),
899
1
					},
900
1
				)
901
1
				.expect_cost(37023000)
902
1
				.expect_log(log3(
903
1
					ForeignAssetId(0u128),
904
1
					SELECTOR_LOG_APPROVAL,
905
1
					CryptoAlith,
906
1
					Bob,
907
1
					solidity::encode_event_data(U256::from(500)),
908
1
				))
909
1
				.execute_returns(());
910
1

            
911
1
			precompiles()
912
1
				.prepare_test(
913
1
					CryptoAlith,
914
1
					ForeignAssetId(0u128),
915
1
					ForeignPCall::allowance {
916
1
						owner: Address(CryptoAlith.into()),
917
1
						spender: Address(Bob.into()),
918
1
					},
919
1
				)
920
1
				.expect_cost(0) // TODO: Test db read/write costs
921
1
				.expect_no_logs()
922
1
				.execute_returns(U256::from(500u16));
923
1

            
924
1
			precompiles()
925
1
				.prepare_test(
926
1
					CryptoAlith,
927
1
					ForeignAssetId(0u128),
928
1
					ForeignPCall::eip2612_nonces {
929
1
						owner: Address(CryptoAlith.into()),
930
1
					},
931
1
				)
932
1
				.expect_cost(0) // TODO: Test db read/write costs
933
1
				.expect_no_logs()
934
1
				.execute_returns(U256::from(1u8));
935
1
		});
936
1
}
937

            
938
#[test]
939
1
fn permit_valid_named_asset() {
940
1
	ExtBuilder::default()
941
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
942
1
		.build()
943
1
		.execute_with(|| {
944
1
			assert_ok!(ForeignAssets::force_create(
945
1
				RuntimeOrigin::root(),
946
1
				0u128,
947
1
				CryptoAlith.into(),
948
1
				true,
949
1
				1
950
1
			));
951
1
			assert_ok!(ForeignAssets::mint(
952
1
				RuntimeOrigin::signed(CryptoAlith.into()),
953
1
				0u128,
954
1
				CryptoAlith.into(),
955
1
				1000
956
1
			));
957
1
			assert_ok!(ForeignAssets::set_metadata(
958
1
				RuntimeOrigin::signed(CryptoAlith.into()),
959
1
				0u128,
960
1
				b"Test token".to_vec(),
961
1
				b"TEST".to_vec(),
962
1
				18
963
1
			));
964

            
965
1
			let owner: H160 = CryptoAlith.into();
966
1
			let spender: H160 = Bob.into();
967
1
			let value: U256 = 500u16.into();
968
1
			let deadline: U256 = 0u8.into(); // todo: proper timestamp
969
1

            
970
1
			let permit = Eip2612::<Runtime, pallet_assets::Instance1>::generate_permit(
971
1
				ForeignAssetId(0u128).into(),
972
1
				0u128,
973
1
				owner,
974
1
				spender,
975
1
				value,
976
1
				0u8.into(), // nonce
977
1
				deadline,
978
1
			);
979
1

            
980
1
			let secret_key = SecretKey::parse(&alith_secret_key()).unwrap();
981
1
			let message = Message::parse(&permit);
982
1
			let (rs, v) = sign(&message, &secret_key);
983
1

            
984
1
			precompiles()
985
1
				.prepare_test(
986
1
					CryptoAlith,
987
1
					ForeignAssetId(0u128),
988
1
					ForeignPCall::eip2612_nonces {
989
1
						owner: Address(CryptoAlith.into()),
990
1
					},
991
1
				)
992
1
				.expect_cost(0) // TODO: Test db read/write costs
993
1
				.expect_no_logs()
994
1
				.execute_returns(U256::from(0u8));
995
1

            
996
1
			precompiles()
997
1
				.prepare_test(
998
1
					Charlie,
999
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v.serialize(),
1
						r: H256::from(rs.r.b32()),
1
						s: H256::from(rs.s.b32()),
1
					},
1
				)
1
				.expect_cost(37023000)
1
				.expect_log(log3(
1
					ForeignAssetId(0u128),
1
					SELECTOR_LOG_APPROVAL,
1
					CryptoAlith,
1
					Bob,
1
					solidity::encode_event_data(U256::from(500)),
1
				))
1
				.execute_returns(());
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(500u16));
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(1u8));
1
		});
1
}
#[test]
1
fn permit_invalid_nonce() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			let owner: H160 = CryptoAlith.into();
1
			let spender: H160 = Bob.into();
1
			let value: U256 = 500u16.into();
1
			let deadline: U256 = 0u8.into();
1

            
1
			let permit = Eip2612::<Runtime, pallet_assets::Instance1>::generate_permit(
1
				ForeignAssetId(0u128).into(),
1
				0u128,
1
				owner,
1
				spender,
1
				value,
1
				1u8.into(), // nonce
1
				deadline,
1
			);
1

            
1
			let secret_key = SecretKey::parse(&alith_secret_key()).unwrap();
1
			let message = Message::parse(&permit);
1
			let (rs, v) = sign(&message, &secret_key);
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1

            
1
			precompiles()
1
				.prepare_test(
1
					Charlie,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v.serialize(),
1
						r: H256::from(rs.r.b32()),
1
						s: H256::from(rs.s.b32()),
1
					},
1
				)
1
				.execute_reverts(|output| output == b"Invalid permit");
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1
		});
1
}
#[test]
1
fn permit_invalid_signature() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			let owner: H160 = CryptoAlith.into();
1
			let spender: H160 = Bob.into();
1
			let value: U256 = 500u16.into();
1
			let deadline: U256 = 0u8.into();
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1

            
1
			precompiles()
1
				.prepare_test(
1
					Charlie,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: 0,
1
						r: H256::random(),
1
						s: H256::random(),
1
					},
1
				)
1
				.execute_reverts(|output| output == b"Invalid permit");
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1
		});
1
}
#[test]
1
fn permit_invalid_deadline() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			pallet_timestamp::Pallet::<Runtime>::set_timestamp(10_000);
1

            
1
			let owner: H160 = CryptoAlith.into();
1
			let spender: H160 = Bob.into();
1
			let value: U256 = 500u16.into();
1
			let deadline: U256 = 5u8.into(); // deadline < timestamp => expired
1

            
1
			let permit = Eip2612::<Runtime, pallet_assets::Instance1>::generate_permit(
1
				ForeignAssetId(0u128).into(),
1
				0u128,
1
				owner,
1
				spender,
1
				value,
1
				0u8.into(), // nonce
1
				deadline,
1
			);
1

            
1
			let secret_key = SecretKey::parse(&alith_secret_key()).unwrap();
1
			let message = Message::parse(&permit);
1
			let (rs, v) = sign(&message, &secret_key);
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1

            
1
			precompiles()
1
				.prepare_test(
1
					Charlie,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v.serialize(),
1
						r: H256::from(rs.r.b32()),
1
						s: H256::from(rs.s.b32()),
1
					},
1
				)
1
				.execute_reverts(|output| output == b"Permit expired");
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1
		});
1
}
// This test checks the validity of a metamask signed message against the permit precompile
// The code used to generate the signature is the following.
// You will need to import CryptoAlith_PRIV_KEY in metamask.
// If you put this code in the developer tools console, it will log the signature
/*
await window.ethereum.enable();
const accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
const value = 1000;
const fromAddress = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac";
const deadline = 1;
const nonce = 0;
const spender = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
const from = accounts[0];
const createPermitMessageData = function () {
	const message = {
	owner: from,
	spender: spender,
	value: value,
	nonce: nonce,
	deadline: deadline,
	};
	const typedData = JSON.stringify({
	types: {
		EIP712Domain: [
		{
			name: "name",
			type: "string",
		},
		{
			name: "version",
			type: "string",
		},
		{
			name: "chainId",
			type: "uint256",
		},
		{
			name: "verifyingContract",
			type: "address",
		},
		],
		Permit: [
		{
			name: "owner",
			type: "address",
		},
		{
			name: "spender",
			type: "address",
		},
		{
			name: "value",
			type: "uint256",
		},
		{
			name: "nonce",
			type: "uint256",
		},
		{
			name: "deadline",
			type: "uint256",
		},
		],
	},
	primaryType: "Permit",
	domain: {
		name: "Unnamed XC20 #1",
		version: "1",
		chainId: 0,
		verifyingContract: "0xffffffff00000000000000000000000000000001",
	},
	message: message,
	});
	return {
		typedData,
		message,
	};
};
const method = "eth_signTypedData_v4"
const messageData = createPermitMessageData();
const params = [from, messageData.typedData];
web3.currentProvider.sendAsync(
	{
		method,
		params,
		from,
	},
	function (err, result) {
		if (err) return console.dir(err);
		if (result.error) {
			alert(result.error.message);
		}
		if (result.error) return console.error('ERROR', result);
		console.log('TYPED SIGNED:' + JSON.stringify(result.result));
		const recovered = sigUtil.recoverTypedSignature_v4({
			data: JSON.parse(msgParams),
			sig: result.result,
		});
		if (
			ethUtil.toChecksumAddress(recovered) === ethUtil.toChecksumAddress(from)
		) {
			alert('Successfully recovered signer as ' + from);
		} else {
			alert(
				'Failed to verify signer when comparing ' + result + ' to ' + from
			);
		}
	}
);
*/
#[test]
1
fn permit_valid_with_metamask_signed_data() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			// assetId 1
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				1u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				1u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			let owner: H160 = CryptoAlith.into();
1
			let spender: H160 = Bob.into();
1
			let value: U256 = 1000u16.into();
1
			let deadline: U256 = 1u16.into(); // todo: proper timestamp
1

            
1
			let rsv = hex!(
1
				"3aac886f06729d76067b6b0dbae23978fe48224b10b5648265b8f0e8c4cf25ff7625965d64bf9a6069d
1
				b00ef5771b65fd24dd118531fc6e86b61a238ca76b9a11c"
1
			)
1
			.as_slice();
1
			let (r, sv) = rsv.split_at(32);
1
			let (s, v) = sv.split_at(32);
1
			let v_real = v[0];
1
			let r_real: [u8; 32] = r.try_into().unwrap();
1
			let s_real: [u8; 32] = s.try_into().unwrap();
1

            
1
			precompiles()
1
				.prepare_test(
1
					Charlie,
1
					ForeignAssetId(1u128),
1
					ForeignPCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v_real,
1
						r: H256::from(r_real),
1
						s: H256::from(s_real),
1
					},
1
				)
1
				.expect_cost(37023000)
1
				.expect_log(log3(
1
					ForeignAssetId(1u128),
1
					SELECTOR_LOG_APPROVAL,
1
					CryptoAlith,
1
					Bob,
1
					solidity::encode_event_data(U256::from(1000)),
1
				))
1
				.execute_returns(());
1
		});
1
}
#[test]
1
fn transfer_amount_overflow() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::transfer {
1
						to: Address(Bob.into()),
1
						value: U256::from(u128::MAX) + 1,
1
					},
1
				)
1
				.expect_cost(1756u64) // 1 weight => 1 gas in mock
1
				.expect_no_logs()
1
				.execute_reverts(|e| e == b"value: Value is too large for balance type");
1

            
1
			precompiles()
1
				.prepare_test(
1
					Bob,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::balance_of {
1
						who: Address(Bob.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0));
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::balance_of {
1
						who: Address(CryptoAlith.into()),
1
					},
1
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(1000));
1
		});
1
}
#[test]
1
fn transfer_from_overflow() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::mint(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				CryptoAlith.into(),
1
				1000
1
			));
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::approve {
1
						spender: Address(Bob.into()),
1
						value: 500.into(),
1
					},
1
				)
1
				.execute_some();
1

            
1
			// TODO: Duplicate approve of same value (noop?)
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					ForeignAssetId(0u128),
1
					ForeignPCall::approve {
1
						spender: Address(Bob.into()),
1
						value: 500.into(),
1
					},
1
				)
1
				.execute_some();
1

            
1
			precompiles()
1
				.prepare_test(
1
					Bob, // Bob is the one sending transferFrom!
1
					ForeignAssetId(0u128),
1
					ForeignPCall::transfer_from {
1
						from: Address(CryptoAlith.into()),
1
						to: Address(Charlie.into()),
1
						value: U256::from(u128::MAX) + 1,
1
					},
1
				)
1
				.expect_cost(1756u64) // 1 weight => 1 gas in mock
1
				.expect_no_logs()
1
				.execute_reverts(|e| e == b"value: Value is too large for balance type");
1
		});
1
}
#[test]
1
fn get_owner() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::transfer_ownership(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				// owner
1
				Bob.into(),
1
			));
1
			precompiles()
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::owner {})
1
				.expect_cost(0)
1
				.expect_no_logs()
1
				.execute_returns(Address(Bob.into()));
1
		});
1
}
#[test]
1
fn get_issuer() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::set_team(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				// Issuer
1
				Bob.into(),
1
				// admin
1
				CryptoAlith.into(),
1
				// freezer
1
				CryptoAlith.into(),
1
			));
1
			precompiles()
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::issuer {})
1
				.expect_cost(0)
1
				.expect_no_logs()
1
				.execute_returns(Address(Bob.into()));
1
		});
1
}
#[test]
1
fn get_admin() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::set_team(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				// Issuer
1
				CryptoAlith.into(),
1
				// admin
1
				Bob.into(),
1
				// freezer
1
				CryptoAlith.into(),
1
			));
1
			precompiles()
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::admin {})
1
				.expect_cost(0)
1
				.expect_no_logs()
1
				.execute_returns(Address(Bob.into()));
1
		});
1
}
#[test]
1
fn get_freezer() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
1
		.build()
1
		.execute_with(|| {
1
			assert_ok!(ForeignAssets::force_create(
1
				RuntimeOrigin::root(),
1
				0u128,
1
				CryptoAlith.into(),
1
				true,
1
				1
1
			));
1
			assert_ok!(ForeignAssets::set_team(
1
				RuntimeOrigin::signed(CryptoAlith.into()),
1
				0u128,
1
				// Issuer
1
				CryptoAlith.into(),
1
				// admin
1
				CryptoAlith.into(),
1
				// freezer
1
				Bob.into(),
1
			));
1
			precompiles()
1
				.prepare_test(CryptoAlith, ForeignAssetId(0u128), ForeignPCall::freezer {})
1
				.expect_cost(0)
1
				.expect_no_logs()
1
				.execute_returns(Address(Bob.into()));
1
		});
1
}
#[test]
1
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
1
	check_precompile_implements_solidity_interfaces(
1
		&["ERC20.sol", "Permit.sol"],
1
		ForeignPCall::supports_selector,
1
	)
1
}