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 std::str::from_utf8;
18

            
19
use crate::{eip2612::Eip2612, mock::*, *};
20

            
21
use fp_evm::MAX_TRANSACTION_GAS_LIMIT;
22
use libsecp256k1::{sign, Message, SecretKey};
23
use precompile_utils::testing::*;
24
use sha3::{Digest, Keccak256};
25
use sp_core::{H256, U256};
26

            
27
// No test of invalid selectors since we have a fallback behavior (deposit).
28
64
fn precompiles() -> Precompiles<Runtime> {
29
64
	PrecompilesValue::get()
30
64
}
31

            
32
#[test]
33
1
fn selectors() {
34
1
	assert!(PCall::balance_of_selectors().contains(&0x70a08231));
35
1
	assert!(PCall::total_supply_selectors().contains(&0x18160ddd));
36
1
	assert!(PCall::approve_selectors().contains(&0x095ea7b3));
37
1
	assert!(PCall::allowance_selectors().contains(&0xdd62ed3e));
38
1
	assert!(PCall::transfer_selectors().contains(&0xa9059cbb));
39
1
	assert!(PCall::transfer_from_selectors().contains(&0x23b872dd));
40
1
	assert!(PCall::name_selectors().contains(&0x06fdde03));
41
1
	assert!(PCall::symbol_selectors().contains(&0x95d89b41));
42
1
	assert!(PCall::deposit_selectors().contains(&0xd0e30db0));
43
1
	assert!(PCall::withdraw_selectors().contains(&0x2e1a7d4d));
44
1
	assert!(PCall::eip2612_nonces_selectors().contains(&0x7ecebe00));
45
1
	assert!(PCall::eip2612_permit_selectors().contains(&0xd505accf));
46
1
	assert!(PCall::eip2612_domain_separator_selectors().contains(&0x3644e515));
47

            
48
1
	assert_eq!(
49
		crate::SELECTOR_LOG_TRANSFER,
50
1
		&Keccak256::digest(b"Transfer(address,address,uint256)")[..]
51
	);
52

            
53
1
	assert_eq!(
54
		crate::SELECTOR_LOG_APPROVAL,
55
1
		&Keccak256::digest(b"Approval(address,address,uint256)")[..]
56
	);
57

            
58
1
	assert_eq!(
59
		crate::SELECTOR_LOG_DEPOSIT,
60
1
		&Keccak256::digest(b"Deposit(address,uint256)")[..]
61
	);
62

            
63
1
	assert_eq!(
64
		crate::SELECTOR_LOG_WITHDRAWAL,
65
1
		&Keccak256::digest(b"Withdrawal(address,uint256)")[..]
66
	);
67
1
}
68

            
69
#[test]
70
1
fn modifiers() {
71
1
	ExtBuilder::default()
72
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
73
1
		.build()
74
1
		.execute_with(|| {
75
1
			let mut tester =
76
1
				PrecompilesModifierTester::new(precompiles(), CryptoAlith, Precompile1);
77

            
78
1
			tester.test_view_modifier(PCall::balance_of_selectors());
79
1
			tester.test_view_modifier(PCall::total_supply_selectors());
80
1
			tester.test_default_modifier(PCall::approve_selectors());
81
1
			tester.test_view_modifier(PCall::allowance_selectors());
82
1
			tester.test_default_modifier(PCall::transfer_selectors());
83
1
			tester.test_default_modifier(PCall::transfer_from_selectors());
84
1
			tester.test_view_modifier(PCall::name_selectors());
85
1
			tester.test_view_modifier(PCall::symbol_selectors());
86
1
			tester.test_view_modifier(PCall::decimals_selectors());
87
1
			tester.test_payable_modifier(PCall::deposit_selectors());
88
1
			tester.test_default_modifier(PCall::withdraw_selectors());
89
1
			tester.test_view_modifier(PCall::eip2612_nonces_selectors());
90
1
			tester.test_default_modifier(PCall::eip2612_permit_selectors());
91
1
			tester.test_view_modifier(PCall::eip2612_domain_separator_selectors());
92
1
		});
93
1
}
94

            
95
#[test]
96
1
fn get_total_supply() {
97
1
	ExtBuilder::default()
98
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
99
1
		.build()
100
1
		.execute_with(|| {
101
1
			precompiles()
102
1
				.prepare_test(CryptoAlith, Precompile1, PCall::total_supply {})
103
1
				.expect_cost(0) // TODO: Test db read/write costs
104
1
				.expect_no_logs()
105
1
				.execute_returns(U256::from(3500u64));
106
1
		});
107
1
}
108

            
109
#[test]
110
1
fn get_balances_known_user() {
111
1
	ExtBuilder::default()
112
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
113
1
		.build()
114
1
		.execute_with(|| {
115
1
			precompiles()
116
1
				.prepare_test(
117
1
					CryptoAlith,
118
1
					Precompile1,
119
1
					PCall::balance_of {
120
1
						owner: Address(CryptoAlith.into()),
121
1
					},
122
				)
123
1
				.expect_cost(0) // TODO: Test db read/write costs
124
1
				.expect_no_logs()
125
1
				.execute_returns(U256::from(1000u64));
126
1
		});
127
1
}
128

            
129
#[test]
130
1
fn get_balances_unknown_user() {
131
1
	ExtBuilder::default()
132
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
133
1
		.build()
134
1
		.execute_with(|| {
135
1
			precompiles()
136
1
				.prepare_test(
137
1
					CryptoAlith,
138
1
					Precompile1,
139
1
					PCall::balance_of {
140
1
						owner: Address(Bob.into()),
141
1
					},
142
				)
143
1
				.expect_cost(0) // TODO: Test db read/write costs
144
1
				.expect_no_logs()
145
1
				.execute_returns(U256::from(0u64));
146
1
		});
147
1
}
148

            
149
#[test]
150
1
fn approve() {
151
1
	ExtBuilder::default()
152
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
153
1
		.build()
154
1
		.execute_with(|| {
155
1
			precompiles()
156
1
				.prepare_test(
157
1
					CryptoAlith,
158
1
					Precompile1,
159
1
					PCall::approve {
160
1
						spender: Address(Bob.into()),
161
1
						value: 500.into(),
162
1
					},
163
				)
164
1
				.expect_cost(1756)
165
1
				.expect_log(log3(
166
1
					Precompile1,
167
					SELECTOR_LOG_APPROVAL,
168
1
					CryptoAlith,
169
1
					Bob,
170
1
					solidity::encode_event_data(U256::from(500)),
171
				))
172
1
				.execute_returns(true);
173
1
		});
174
1
}
175

            
176
#[test]
177
1
fn approve_saturating() {
178
1
	ExtBuilder::default()
179
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
180
1
		.build()
181
1
		.execute_with(|| {
182
1
			precompiles()
183
1
				.prepare_test(
184
1
					CryptoAlith,
185
1
					Precompile1,
186
1
					PCall::approve {
187
1
						spender: Address(Bob.into()),
188
1
						value: U256::MAX,
189
1
					},
190
				)
191
1
				.expect_cost(1756u64)
192
1
				.expect_log(log3(
193
1
					Precompile1,
194
					SELECTOR_LOG_APPROVAL,
195
1
					CryptoAlith,
196
1
					Bob,
197
1
					solidity::encode_event_data(U256::MAX),
198
				))
199
1
				.execute_returns(true);
200

            
201
1
			precompiles()
202
1
				.prepare_test(
203
1
					CryptoAlith,
204
1
					Precompile1,
205
1
					PCall::allowance {
206
1
						owner: Address(CryptoAlith.into()),
207
1
						spender: Address(Bob.into()),
208
1
					},
209
				)
210
1
				.expect_cost(0)
211
1
				.expect_no_logs()
212
1
				.execute_returns(U256::from(u128::MAX));
213
1
		});
214
1
}
215

            
216
#[test]
217
1
fn check_allowance_existing() {
218
1
	ExtBuilder::default()
219
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
220
1
		.build()
221
1
		.execute_with(|| {
222
1
			precompiles()
223
1
				.prepare_test(
224
1
					CryptoAlith,
225
1
					Precompile1,
226
1
					PCall::approve {
227
1
						spender: Address(Bob.into()),
228
1
						value: 500.into(),
229
1
					},
230
				)
231
1
				.execute_some();
232

            
233
1
			precompiles()
234
1
				.prepare_test(
235
1
					CryptoAlith,
236
1
					Precompile1,
237
1
					PCall::allowance {
238
1
						owner: Address(CryptoAlith.into()),
239
1
						spender: Address(Bob.into()),
240
1
					},
241
				)
242
1
				.expect_cost(0) // TODO: Test db read/write costs
243
1
				.expect_no_logs()
244
1
				.execute_returns(U256::from(500u64));
245
1
		});
246
1
}
247

            
248
#[test]
249
1
fn check_allowance_not_existing() {
250
1
	ExtBuilder::default()
251
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
252
1
		.build()
253
1
		.execute_with(|| {
254
1
			precompiles()
255
1
				.prepare_test(
256
1
					CryptoAlith,
257
1
					Precompile1,
258
1
					PCall::allowance {
259
1
						owner: Address(CryptoAlith.into()),
260
1
						spender: Address(Bob.into()),
261
1
					},
262
				)
263
1
				.expect_cost(0) // TODO: Test db read/write costs
264
1
				.expect_no_logs()
265
1
				.execute_returns(U256::from(0u64));
266
1
		});
267
1
}
268

            
269
#[test]
270
1
fn transfer() {
271
1
	ExtBuilder::default()
272
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
273
1
		.build()
274
1
		.execute_with(|| {
275
1
			precompiles()
276
1
				.prepare_test(
277
1
					CryptoAlith,
278
1
					Precompile1,
279
1
					PCall::transfer {
280
1
						to: Address(Bob.into()),
281
1
						value: 400.into(),
282
1
					},
283
				)
284
1
				.expect_cost(8709)
285
1
				.expect_log(log3(
286
1
					Precompile1,
287
					SELECTOR_LOG_TRANSFER,
288
1
					CryptoAlith,
289
1
					Bob,
290
1
					solidity::encode_event_data(U256::from(400)),
291
				))
292
1
				.execute_returns(true);
293

            
294
1
			precompiles()
295
1
				.prepare_test(
296
1
					CryptoAlith,
297
1
					Precompile1,
298
1
					PCall::balance_of {
299
1
						owner: Address(CryptoAlith.into()),
300
1
					},
301
				)
302
1
				.expect_cost(0) // TODO: Test db read/write costs
303
1
				.expect_no_logs()
304
1
				.execute_returns(U256::from(600));
305

            
306
1
			precompiles()
307
1
				.prepare_test(
308
1
					CryptoAlith,
309
1
					Precompile1,
310
1
					PCall::balance_of {
311
1
						owner: Address(Bob.into()),
312
1
					},
313
				)
314
1
				.expect_cost(0) // TODO: Test db read/write costs
315
1
				.expect_no_logs()
316
1
				.execute_returns(U256::from(400));
317
1
		});
318
1
}
319

            
320
#[test]
321
1
fn transfer_not_enough_funds() {
322
1
	ExtBuilder::default()
323
1
		.with_balances(vec![
324
1
			(CryptoAlith.into(), 1000),
325
1
			(CryptoBaltathar.into(), 1000),
326
1
		])
327
1
		.build()
328
1
		.execute_with(|| {
329
1
			precompiles()
330
1
				.prepare_test(
331
1
					CryptoAlith,
332
1
					Precompile1,
333
1
					PCall::transfer {
334
1
						to: Address(Bob.into()),
335
1
						value: 1400.into(),
336
1
					},
337
				)
338
1
				.execute_reverts(|output| {
339
1
					from_utf8(&output)
340
1
						.unwrap()
341
1
						.contains("Dispatched call failed with error: ")
342
1
						&& from_utf8(&output).unwrap().contains("FundsUnavailable")
343
1
				});
344
1
		});
345
1
}
346

            
347
#[test]
348
1
fn transfer_from() {
349
1
	ExtBuilder::default()
350
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
351
1
		.build()
352
1
		.execute_with(|| {
353
1
			precompiles()
354
1
				.prepare_test(
355
1
					CryptoAlith,
356
1
					Precompile1,
357
1
					PCall::approve {
358
1
						spender: Address(Bob.into()),
359
1
						value: 500.into(),
360
1
					},
361
				)
362
1
				.execute_some();
363

            
364
1
			precompiles()
365
1
				.prepare_test(
366
1
					Bob,
367
1
					Precompile1,
368
1
					PCall::transfer_from {
369
1
						from: Address(CryptoAlith.into()),
370
1
						to: Address(Bob.into()),
371
1
						value: 400.into(),
372
1
					},
373
				)
374
1
				.expect_cost(8709)
375
1
				.expect_log(log3(
376
1
					Precompile1,
377
					SELECTOR_LOG_TRANSFER,
378
1
					CryptoAlith,
379
1
					Bob,
380
1
					solidity::encode_event_data(U256::from(400)),
381
				))
382
1
				.execute_returns(true);
383

            
384
1
			precompiles()
385
1
				.prepare_test(
386
1
					CryptoAlith,
387
1
					Precompile1,
388
1
					PCall::balance_of {
389
1
						owner: Address(CryptoAlith.into()),
390
1
					},
391
				)
392
1
				.expect_cost(0) // TODO: Test db read/write costs
393
1
				.expect_no_logs()
394
1
				.execute_returns(U256::from(600));
395

            
396
1
			precompiles()
397
1
				.prepare_test(
398
1
					CryptoAlith,
399
1
					Precompile1,
400
1
					PCall::balance_of {
401
1
						owner: Address(Bob.into()),
402
1
					},
403
				)
404
1
				.expect_cost(0) // TODO: Test db read/write costs
405
1
				.expect_no_logs()
406
1
				.execute_returns(U256::from(400));
407

            
408
1
			precompiles()
409
1
				.prepare_test(
410
1
					CryptoAlith,
411
1
					Precompile1,
412
1
					PCall::allowance {
413
1
						owner: Address(CryptoAlith.into()),
414
1
						spender: Address(Bob.into()),
415
1
					},
416
				)
417
1
				.expect_cost(0) // TODO: Test db read/write costs
418
1
				.expect_no_logs()
419
1
				.execute_returns(U256::from(100u64));
420
1
		});
421
1
}
422

            
423
#[test]
424
1
fn transfer_from_above_allowance() {
425
1
	ExtBuilder::default()
426
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
427
1
		.build()
428
1
		.execute_with(|| {
429
1
			precompiles()
430
1
				.prepare_test(
431
1
					CryptoAlith,
432
1
					Precompile1,
433
1
					PCall::approve {
434
1
						spender: Address(Bob.into()),
435
1
						value: 300.into(),
436
1
					},
437
				)
438
1
				.execute_some();
439

            
440
1
			precompiles()
441
1
				.prepare_test(
442
1
					Bob, // Bob is the one sending transferFrom!
443
1
					Precompile1,
444
1
					PCall::transfer_from {
445
1
						from: Address(CryptoAlith.into()),
446
1
						to: Address(Bob.into()),
447
1
						value: 400.into(),
448
1
					},
449
				)
450
1
				.execute_reverts(|output| output == b"trying to spend more than allowed");
451
1
		});
452
1
}
453

            
454
#[test]
455
1
fn transfer_from_self() {
456
1
	ExtBuilder::default()
457
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
458
1
		.build()
459
1
		.execute_with(|| {
460
1
			precompiles()
461
1
				.prepare_test(
462
1
					CryptoAlith, // CryptoAlith sending transferFrom herself, no need for allowance.
463
1
					Precompile1,
464
1
					PCall::transfer_from {
465
1
						from: Address(CryptoAlith.into()),
466
1
						to: Address(Bob.into()),
467
1
						value: 400.into(),
468
1
					},
469
				)
470
1
				.expect_cost(8709)
471
1
				.expect_log(log3(
472
1
					Precompile1,
473
					SELECTOR_LOG_TRANSFER,
474
1
					CryptoAlith,
475
1
					Bob,
476
1
					solidity::encode_event_data(U256::from(400)),
477
				))
478
1
				.execute_returns(true);
479

            
480
1
			precompiles()
481
1
				.prepare_test(
482
1
					CryptoAlith,
483
1
					Precompile1,
484
1
					PCall::balance_of {
485
1
						owner: Address(CryptoAlith.into()),
486
1
					},
487
				)
488
1
				.expect_cost(0) // TODO: Test db read/write costs
489
1
				.expect_no_logs()
490
1
				.execute_returns(U256::from(600));
491

            
492
1
			precompiles()
493
1
				.prepare_test(
494
1
					CryptoAlith,
495
1
					Precompile1,
496
1
					PCall::balance_of {
497
1
						owner: Address(Bob.into()),
498
1
					},
499
				)
500
1
				.expect_cost(0) // TODO: Test db read/write costs
501
1
				.expect_no_logs()
502
1
				.execute_returns(U256::from(400));
503
1
		});
504
1
}
505

            
506
#[test]
507
1
fn get_metadata_name() {
508
1
	ExtBuilder::default()
509
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
510
1
		.build()
511
1
		.execute_with(|| {
512
1
			precompiles()
513
1
				.prepare_test(CryptoAlith, Precompile1, PCall::name {})
514
1
				.expect_cost(0) // TODO: Test db read/write costs
515
1
				.expect_no_logs()
516
1
				.execute_returns(UnboundedBytes::from("Mock token"));
517
1
		});
518
1
}
519

            
520
#[test]
521
1
fn get_metadata_symbol() {
522
1
	ExtBuilder::default()
523
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
524
1
		.build()
525
1
		.execute_with(|| {
526
1
			precompiles()
527
1
				.prepare_test(CryptoAlith, Precompile1, PCall::symbol {})
528
1
				.expect_cost(0) // TODO: Test db read/write costs
529
1
				.expect_no_logs()
530
1
				.execute_returns(UnboundedBytes::from("MOCK"));
531
1
		});
532
1
}
533

            
534
#[test]
535
1
fn get_metadata_decimals() {
536
1
	ExtBuilder::default()
537
1
		.with_balances(vec![(CryptoAlith.into(), 1000), (Bob.into(), 2500)])
538
1
		.build()
539
1
		.execute_with(|| {
540
1
			precompiles()
541
1
				.prepare_test(CryptoAlith, Precompile1, PCall::decimals {})
542
1
				.expect_cost(0) // TODO: Test db read/write costs
543
1
				.expect_no_logs()
544
1
				.execute_returns(18u8);
545
1
		});
546
1
}
547

            
548
3
fn deposit(data: Vec<u8>) {
549
3
	ExtBuilder::default()
550
3
		.with_balances(vec![(CryptoAlith.into(), 1000)])
551
3
		.build()
552
3
		.execute_with(|| {
553
			// Check precompile balance is 0.
554
3
			precompiles()
555
3
				.prepare_test(
556
3
					CryptoAlith,
557
3
					Precompile1,
558
3
					PCall::balance_of {
559
3
						owner: Address(Precompile1.into()),
560
3
					},
561
				)
562
3
				.expect_cost(0) // TODO: Test db read/write costs
563
3
				.expect_no_logs()
564
3
				.execute_returns(U256::from(0));
565

            
566
			// Deposit
567
			// We need to call using EVM pallet so we can check the EVM correctly sends the amount
568
			// to the precompile.
569
3
			Evm::call(
570
3
				RuntimeOrigin::root(),
571
3
				CryptoAlith.into(),
572
3
				Precompile1.into(),
573
3
				data,
574
3
				From::from(500),                     // amount sent
575
3
				MAX_TRANSACTION_GAS_LIMIT.low_u64(), // gas limit
576
3
				0u32.into(),                         // gas price
577
3
				None,                                // max priority
578
3
				None,                                // nonce
579
3
				vec![],                              // access list
580
3
				vec![],                              // authorization list
581
			)
582
3
			.expect("it works");
583

            
584
3
			assert_eq!(
585
3
				events(),
586
3
				vec![
587
3
					RuntimeEvent::System(frame_system::Event::NewAccount {
588
3
						account: Precompile1.into()
589
3
					}),
590
3
					RuntimeEvent::Balances(pallet_balances::Event::Endowed {
591
3
						account: Precompile1.into(),
592
3
						free_balance: 500
593
3
					}),
594
					// EVM make a transfer because some value is provided.
595
3
					RuntimeEvent::Balances(pallet_balances::Event::Transfer {
596
3
						from: CryptoAlith.into(),
597
3
						to: Precompile1.into(),
598
3
						amount: 500
599
3
					}),
600
					// Precompile1 send it back since deposit should be a no-op.
601
3
					RuntimeEvent::Balances(pallet_balances::Event::Transfer {
602
3
						from: Precompile1.into(),
603
3
						to: CryptoAlith.into(),
604
3
						amount: 500
605
3
					}),
606
					// Log is correctly emitted.
607
3
					RuntimeEvent::Evm(pallet_evm::Event::Log {
608
3
						log: log2(
609
3
							Precompile1,
610
3
							SELECTOR_LOG_DEPOSIT,
611
3
							CryptoAlith,
612
3
							solidity::encode_event_data(U256::from(500)),
613
3
						)
614
3
					}),
615
3
					RuntimeEvent::Evm(pallet_evm::Event::Executed {
616
3
						address: Precompile1.into()
617
3
					}),
618
				]
619
			);
620

            
621
			// Check precompile balance is still 0.
622
3
			precompiles()
623
3
				.prepare_test(
624
3
					CryptoAlith,
625
3
					Precompile1,
626
3
					PCall::balance_of {
627
3
						owner: Address(Precompile1.into()),
628
3
					},
629
				)
630
3
				.expect_cost(0) // TODO: Test db read/write costs
631
3
				.expect_no_logs()
632
3
				.execute_returns(U256::from(0));
633

            
634
			// Check CryptoAlith balance is still 1000.
635
3
			precompiles()
636
3
				.prepare_test(
637
3
					CryptoAlith,
638
3
					Precompile1,
639
3
					PCall::balance_of {
640
3
						owner: Address(CryptoAlith.into()),
641
3
					},
642
				)
643
3
				.expect_cost(0) // TODO: Test db read/write costs
644
3
				.expect_no_logs()
645
3
				.execute_returns(U256::from(1000));
646
3
		});
647
3
}
648

            
649
#[test]
650
1
fn deposit_function() {
651
1
	deposit(PCall::deposit {}.into())
652
1
}
653

            
654
#[test]
655
1
fn deposit_fallback() {
656
1
	deposit(solidity::encode_with_selector(0x01234567u32, ()))
657
1
}
658

            
659
#[test]
660
1
fn deposit_receive() {
661
1
	deposit(vec![])
662
1
}
663

            
664
#[test]
665
1
fn deposit_zero() {
666
1
	ExtBuilder::default()
667
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
668
1
		.build()
669
1
		.execute_with(|| {
670
			// Check precompile balance is 0.
671
1
			precompiles()
672
1
				.prepare_test(
673
1
					CryptoAlith,
674
1
					Precompile1,
675
1
					PCall::balance_of {
676
1
						owner: Address(Precompile1.into()),
677
1
					},
678
				)
679
1
				.expect_cost(0) // TODO: Test db read/write costs
680
1
				.expect_no_logs()
681
1
				.execute_returns(U256::from(0));
682

            
683
			// Deposit
684
			// We need to call using EVM pallet so we can check the EVM correctly sends the amount
685
			// to the precompile.
686
1
			Evm::call(
687
1
				RuntimeOrigin::root(),
688
1
				CryptoAlith.into(),
689
1
				Precompile1.into(),
690
1
				PCall::deposit {}.into(),
691
1
				From::from(0),                       // amount sent
692
1
				MAX_TRANSACTION_GAS_LIMIT.low_u64(), // gas limit
693
1
				0u32.into(),                         // gas price
694
1
				None,                                // max priority
695
1
				None,                                // nonce
696
1
				vec![],                              // access list
697
1
				vec![],                              // authorization list
698
			)
699
1
			.expect("it works");
700

            
701
1
			assert_eq!(
702
1
				events(),
703
1
				vec![RuntimeEvent::Evm(pallet_evm::Event::ExecutedFailed {
704
1
					address: Precompile1.into()
705
1
				}),]
706
			);
707

            
708
			// Check precompile balance is still 0.
709
1
			precompiles()
710
1
				.prepare_test(
711
1
					CryptoAlith,
712
1
					Precompile1,
713
1
					PCall::balance_of {
714
1
						owner: Address(Precompile1.into()),
715
1
					},
716
				)
717
1
				.expect_cost(0) // TODO: Test db read/write costs
718
1
				.expect_no_logs()
719
1
				.execute_returns(U256::from(0));
720

            
721
			// Check CryptoAlith balance is still 1000.
722
1
			precompiles()
723
1
				.prepare_test(
724
1
					CryptoAlith,
725
1
					Precompile1,
726
1
					PCall::balance_of {
727
1
						owner: Address(CryptoAlith.into()),
728
1
					},
729
				)
730
1
				.expect_cost(0) // TODO: Test db read/write costs
731
1
				.expect_no_logs()
732
1
				.execute_returns(U256::from(1000));
733
1
		});
734
1
}
735

            
736
#[test]
737
1
fn withdraw() {
738
1
	ExtBuilder::default()
739
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
740
1
		.build()
741
1
		.execute_with(|| {
742
			// Check precompile balance is 0.
743
1
			precompiles()
744
1
				.prepare_test(
745
1
					CryptoAlith,
746
1
					Precompile1,
747
1
					PCall::balance_of {
748
1
						owner: Address(Precompile1.into()),
749
1
					},
750
				)
751
1
				.expect_cost(0) // TODO: Test db read/write costs
752
1
				.expect_no_logs()
753
1
				.execute_returns(U256::from(0));
754

            
755
			// Withdraw
756
1
			precompiles()
757
1
				.prepare_test(
758
1
					CryptoAlith,
759
1
					Precompile1,
760
1
					PCall::withdraw { value: 500.into() },
761
				)
762
1
				.expect_cost(1381)
763
1
				.expect_log(log2(
764
1
					Precompile1,
765
					SELECTOR_LOG_WITHDRAWAL,
766
1
					CryptoAlith,
767
1
					solidity::encode_event_data(U256::from(500)),
768
				))
769
1
				.execute_returns(());
770

            
771
			// Check CryptoAlith balance is still 1000.
772
1
			precompiles()
773
1
				.prepare_test(
774
1
					CryptoAlith,
775
1
					Precompile1,
776
1
					PCall::balance_of {
777
1
						owner: Address(CryptoAlith.into()),
778
1
					},
779
				)
780
1
				.expect_cost(0) // TODO: Test db read/write costs
781
1
				.expect_no_logs()
782
1
				.execute_returns(U256::from(1000));
783
1
		});
784
1
}
785

            
786
#[test]
787
1
fn withdraw_more_than_owned() {
788
1
	ExtBuilder::default()
789
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
790
1
		.build()
791
1
		.execute_with(|| {
792
			// Check precompile balance is 0.
793
1
			precompiles()
794
1
				.prepare_test(
795
1
					CryptoAlith,
796
1
					Precompile1,
797
1
					PCall::balance_of {
798
1
						owner: Address(Precompile1.into()),
799
1
					},
800
				)
801
1
				.expect_cost(0) // TODO: Test db read/write costs
802
1
				.expect_no_logs()
803
1
				.execute_returns(U256::from(0));
804

            
805
			// Withdraw
806
1
			precompiles()
807
1
				.prepare_test(
808
1
					CryptoAlith,
809
1
					Precompile1,
810
1
					PCall::withdraw { value: 1001.into() },
811
				)
812
1
				.execute_reverts(|output| output == b"Trying to withdraw more than owned");
813

            
814
			// Check CryptoAlith balance is still 1000.
815
1
			precompiles()
816
1
				.prepare_test(
817
1
					CryptoAlith,
818
1
					Precompile1,
819
1
					PCall::balance_of {
820
1
						owner: Address(CryptoAlith.into()),
821
1
					},
822
				)
823
1
				.expect_cost(0) // TODO: Test db read/write costs
824
1
				.expect_no_logs()
825
1
				.execute_returns(U256::from(1000));
826
1
		});
827
1
}
828

            
829
#[test]
830
1
fn permit_valid() {
831
1
	ExtBuilder::default()
832
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
833
1
		.build()
834
1
		.execute_with(|| {
835
1
			let owner: H160 = CryptoAlith.into();
836
1
			let spender: H160 = Bob.into();
837
1
			let value: U256 = 500u16.into();
838
1
			let deadline: U256 = 0u8.into(); // todo: proper timestamp
839

            
840
1
			let permit = Eip2612::<Runtime, NativeErc20Metadata>::generate_permit(
841
1
				Precompile1.into(),
842
1
				owner,
843
1
				spender,
844
1
				value,
845
1
				0u8.into(), // nonce
846
1
				deadline,
847
			);
848

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

            
853
1
			precompiles()
854
1
				.prepare_test(
855
1
					CryptoAlith,
856
1
					Precompile1,
857
1
					PCall::eip2612_nonces {
858
1
						owner: Address(CryptoAlith.into()),
859
1
					},
860
				)
861
1
				.expect_cost(0) // TODO: Test db read/write costs
862
1
				.expect_no_logs()
863
1
				.execute_returns(U256::from(0u8));
864

            
865
1
			precompiles()
866
1
				.prepare_test(
867
1
					Charlie, // can be anyone
868
1
					Precompile1,
869
1
					PCall::eip2612_permit {
870
1
						owner: Address(owner),
871
1
						spender: Address(spender),
872
1
						value,
873
1
						deadline,
874
1
						v: v.serialize(),
875
1
						r: rs.r.b32().into(),
876
1
						s: rs.s.b32().into(),
877
1
					},
878
				)
879
1
				.expect_cost(0) // TODO: Test db read/write costs
880
1
				.expect_log(log3(
881
1
					Precompile1,
882
					SELECTOR_LOG_APPROVAL,
883
1
					CryptoAlith,
884
1
					Bob,
885
1
					solidity::encode_event_data(U256::from(value)),
886
				))
887
1
				.execute_returns(());
888

            
889
1
			precompiles()
890
1
				.prepare_test(
891
1
					CryptoAlith,
892
1
					Precompile1,
893
1
					PCall::allowance {
894
1
						owner: Address(CryptoAlith.into()),
895
1
						spender: Address(Bob.into()),
896
1
					},
897
				)
898
1
				.expect_cost(0) // TODO: Test db read/write costs
899
1
				.expect_no_logs()
900
1
				.execute_returns(U256::from(500u16));
901

            
902
1
			precompiles()
903
1
				.prepare_test(
904
1
					CryptoAlith,
905
1
					Precompile1,
906
1
					PCall::eip2612_nonces {
907
1
						owner: Address(CryptoAlith.into()),
908
1
					},
909
				)
910
1
				.expect_cost(0) // TODO: Test db read/write costs
911
1
				.expect_no_logs()
912
1
				.execute_returns(U256::from(1u8));
913
1
		});
914
1
}
915

            
916
#[test]
917
1
fn permit_invalid_nonce() {
918
1
	ExtBuilder::default()
919
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
920
1
		.build()
921
1
		.execute_with(|| {
922
1
			let owner: H160 = CryptoAlith.into();
923
1
			let spender: H160 = Bob.into();
924
1
			let value: U256 = 500u16.into();
925
1
			let deadline: U256 = 0u8.into();
926

            
927
1
			let permit = Eip2612::<Runtime, NativeErc20Metadata>::generate_permit(
928
1
				Precompile1.into(),
929
1
				owner,
930
1
				spender,
931
1
				value,
932
1
				1u8.into(), // nonce
933
1
				deadline,
934
			);
935

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

            
940
1
			precompiles()
941
1
				.prepare_test(
942
1
					CryptoAlith,
943
1
					Precompile1,
944
1
					PCall::eip2612_nonces {
945
1
						owner: Address(CryptoAlith.into()),
946
1
					},
947
				)
948
1
				.expect_cost(0) // TODO: Test db read/write costs
949
1
				.expect_no_logs()
950
1
				.execute_returns(U256::from(0u8));
951

            
952
1
			precompiles()
953
1
				.prepare_test(
954
1
					Charlie, // can be anyone
955
1
					Precompile1,
956
1
					PCall::eip2612_permit {
957
1
						owner: Address(owner),
958
1
						spender: Address(spender),
959
1
						value,
960
1
						deadline,
961
1
						v: v.serialize(),
962
1
						r: rs.r.b32().into(),
963
1
						s: rs.s.b32().into(),
964
1
					},
965
				)
966
1
				.execute_reverts(|output| output == b"Invalid permit");
967

            
968
1
			precompiles()
969
1
				.prepare_test(
970
1
					CryptoAlith,
971
1
					Precompile1,
972
1
					PCall::allowance {
973
1
						owner: Address(CryptoAlith.into()),
974
1
						spender: Address(Bob.into()),
975
1
					},
976
				)
977
1
				.expect_cost(0) // TODO: Test db read/write costs
978
1
				.expect_no_logs()
979
1
				.execute_returns(U256::from(0u16));
980

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

            
995
#[test]
996
1
fn permit_invalid_signature() {
997
1
	ExtBuilder::default()
998
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
999
1
		.build()
1
		.execute_with(|| {
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
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1
			precompiles()
1
				.prepare_test(
1
					Charlie, // can be anyone
1
					Precompile1,
1
					PCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: 0,
1
						r: H256::repeat_byte(0x11),
1
						s: H256::repeat_byte(0x11),
1
					},
				)
1
				.execute_reverts(|output| output == b"Invalid permit");
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
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
			pallet_timestamp::Pallet::<Runtime>::set_timestamp(10_000);
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
			let permit = Eip2612::<Runtime, NativeErc20Metadata>::generate_permit(
1
				Precompile1.into(),
1
				owner,
1
				spender,
1
				value,
1
				0u8.into(), // nonce
1
				deadline,
			);
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
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
1
					},
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u8));
1
			precompiles()
1
				.prepare_test(
1
					Charlie, // can be anyone
1
					Precompile1,
1
					PCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v.serialize(),
1
						r: rs.r.b32().into(),
1
						s: rs.s.b32().into(),
1
					},
				)
1
				.execute_reverts(|output| output == b"Permit expired");
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::eip2612_nonces {
1
						owner: Address(CryptoAlith.into()),
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_expired_deadline_milliseconds() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
1
		.build()
1
		.execute_with(|| {
1
			let one_second = 1;
1
			let owner: H160 = CryptoAlith.into();
1
			let spender: H160 = Bob.into();
1
			let value: U256 = 500u16.into();
1
			let deadline: U256 = one_second.into();
1
			let permit = Eip2612::<Runtime, NativeErc20Metadata>::generate_permit(
1
				Precompile1.into(),
1
				owner,
1
				spender,
1
				value,
1
				0u8.into(), // nonce
1
				deadline,
			);
1
			let secret_key = SecretKey::parse(&alith_secret_key()).unwrap();
1
			let message = Message::parse(&permit);
1
			let (rs, v) = sign(&message, &secret_key);
			// (deadline_ms + 1ms) = should revert with "Permit expired"
1
			pallet_timestamp::Pallet::<Runtime>::set_timestamp((one_second * 1000) + 1);
1
			precompiles()
1
				.prepare_test(
1
					Charlie, // can be anyone
1
					Precompile1,
1
					PCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v.serialize(),
1
						r: rs.r.b32().into(),
1
						s: rs.s.b32().into(),
1
					},
				)
1
				.execute_reverts(|output| output == b"Permit expired");
			// Allowance should remain zero
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::allowance {
1
						owner: Address(CryptoAlith.into()),
1
						spender: Address(Bob.into()),
1
					},
				)
1
				.expect_cost(0)
1
				.expect_no_logs()
1
				.execute_returns(U256::from(0u16));
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 ALICE_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: "Mock token",
		version: "1",
		chainId: 0,
		verifyingContract: "0x0000000000000000000000000000000000000001",
	},
	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
			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
			let rsv = hex_literal::hex!(
				"612960858951e133d05483804be5456a030be4ce6c000a855d865c0be75a8fc11d89ca96d5a153e8c
				7155ab1147f0f6d3326388b8d866c2406ce34567b7501a01b"
			)
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
			precompiles()
1
				.prepare_test(
1
					Charlie, // can be anyone,
1
					Precompile1,
1
					PCall::eip2612_permit {
1
						owner: Address(owner),
1
						spender: Address(spender),
1
						value,
1
						deadline,
1
						v: v_real,
1
						r: r_real.into(),
1
						s: s_real.into(),
1
					},
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_log(log3(
1
					Precompile1,
					SELECTOR_LOG_APPROVAL,
1
					CryptoAlith,
1
					Bob,
1
					solidity::encode_event_data(U256::from(1000)),
				))
1
				.execute_returns(());
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"],
		PCall::supports_selector,
	)
1
}