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 libsecp256k1::{sign, Message, SecretKey};
22
use precompile_utils::testing::*;
23
use sha3::{Digest, Keccak256};
24
use sp_core::{H256, U256};
25

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
992
#[test]
993
1
fn permit_invalid_signature() {
994
1
	ExtBuilder::default()
995
1
		.with_balances(vec![(CryptoAlith.into(), 1000)])
996
1
		.build()
997
1
		.execute_with(|| {
998
1
			let owner: H160 = CryptoAlith.into();
999
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
					Precompile1,
1
					PCall::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, // 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
				)
1
				.execute_reverts(|output| output == b"Invalid permit");
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::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
					Precompile1,
1
					PCall::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
			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, NativeErc20Metadata>::generate_permit(
1
				Precompile1.into(),
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
					Precompile1,
1
					PCall::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, // 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
				)
1
				.execute_reverts(|output| output == b"Permit expired");
1

            
1
			precompiles()
1
				.prepare_test(
1
					CryptoAlith,
1
					Precompile1,
1
					PCall::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
					Precompile1,
1
					PCall::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 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

            
1
			let rsv = hex_literal::hex!(
1
				"612960858951e133d05483804be5456a030be4ce6c000a855d865c0be75a8fc11d89ca96d5a153e8c
1
				7155ab1147f0f6d3326388b8d866c2406ce34567b7501a01b"
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, // 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
				)
1
				.expect_cost(0) // TODO: Test db read/write costs
1
				.expect_log(log3(
1
					Precompile1,
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 test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
1
	check_precompile_implements_solidity_interfaces(
1
		&["ERC20.sol", "Permit.sol"],
1
		PCall::supports_selector,
1
	)
1
}