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::mock::{
18
	balance, Batch, ExtBuilder, PCall, Precompiles, PrecompilesValue, Revert, Runtime, RuntimeCall,
19
	RuntimeOrigin,
20
};
21
use crate::{
22
	log_subcall_failed, log_subcall_succeeded, Mode, LOG_SUBCALL_FAILED, LOG_SUBCALL_SUCCEEDED,
23
};
24
use fp_evm::ExitError;
25
use frame_support::assert_ok;
26
use pallet_evm::Call as EvmCall;
27
use precompile_utils::solidity::revert::revert_as_bytes;
28
use precompile_utils::{evm::costs::call_cost, prelude::*, testing::*};
29
use sp_core::{H160, H256, U256};
30
use sp_runtime::DispatchError;
31
use sp_runtime::{traits::Dispatchable, DispatchErrorWithPostInfo, ModuleError};
32

            
33
22
fn precompiles() -> Precompiles<Runtime> {
34
22
	PrecompilesValue::get()
35
22
}
36

            
37
12
fn evm_call(from: impl Into<H160>, input: Vec<u8>) -> EvmCall<Runtime> {
38
12
	EvmCall::call {
39
12
		source: from.into(),
40
12
		target: Batch.into(),
41
12
		input,
42
12
		value: U256::zero(), // No value sent in EVM
43
12
		gas_limit: u64::max_value(),
44
12
		max_fee_per_gas: 0.into(),
45
12
		max_priority_fee_per_gas: Some(U256::zero()),
46
12
		nonce: None, // Use the next nonce
47
12
		access_list: Vec::new(),
48
12
		authorization_list: Vec::new(),
49
12
	}
50
12
}
51

            
52
21
fn costs() -> (u64, u64) {
53
21
	let return_log_cost = log_subcall_failed(Batch, 0).compute_cost().unwrap();
54
21
	let call_cost =
55
21
		return_log_cost + call_cost(U256::one(), <Runtime as pallet_evm::Config>::config());
56
21
	(return_log_cost, call_cost)
57
21
}
58

            
59
#[test]
60
1
fn selectors() {
61
1
	assert!(PCall::batch_some_selectors().contains(&0x79df4b9c));
62
1
	assert!(PCall::batch_some_until_failure_selectors().contains(&0xcf0491c7));
63
1
	assert!(PCall::batch_all_selectors().contains(&0x96e292b8));
64
1
	assert_eq!(
65
1
		LOG_SUBCALL_FAILED,
66
1
		hex_literal::hex!("dbc5d06f4f877f959b1ff12d2161cdd693fa8e442ee53f1790b2804b24881f05")
67
1
	);
68
1
	assert_eq!(
69
1
		LOG_SUBCALL_SUCCEEDED,
70
1
		hex_literal::hex!("bf855484633929c3d6688eb3caf8eff910fb4bef030a8d7dbc9390d26759714d")
71
1
	);
72
1
}
73

            
74
#[test]
75
1
fn modifiers() {
76
1
	ExtBuilder::default()
77
1
		.with_balances(vec![(Alice.into(), 1000)])
78
1
		.build()
79
1
		.execute_with(|| {
80
1
			let mut tester = PrecompilesModifierTester::new(precompiles(), Alice, Batch);
81
1

            
82
1
			tester.test_default_modifier(PCall::batch_some_selectors());
83
1
			tester.test_default_modifier(PCall::batch_some_until_failure_selectors());
84
1
			tester.test_default_modifier(PCall::batch_all_selectors());
85
1
		});
86
1
}
87

            
88
#[test]
89
1
fn batch_some_empty() {
90
1
	ExtBuilder::default().build().execute_with(|| {
91
1
		precompiles()
92
1
			.prepare_test(
93
1
				Alice,
94
1
				Batch,
95
1
				PCall::batch_some {
96
1
					to: vec![].into(),
97
1
					value: vec![].into(),
98
1
					call_data: vec![].into(),
99
1
					gas_limit: vec![].into(),
100
1
				},
101
1
			)
102
1
			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
103
1
			.execute_returns(())
104
1
	})
105
1
}
106

            
107
#[test]
108
1
fn batch_some_until_failure_empty() {
109
1
	ExtBuilder::default().build().execute_with(|| {
110
1
		precompiles()
111
1
			.prepare_test(
112
1
				Alice,
113
1
				Batch,
114
1
				PCall::batch_some_until_failure {
115
1
					to: vec![].into(),
116
1
					value: vec![].into(),
117
1
					call_data: vec![].into(),
118
1
					gas_limit: vec![].into(),
119
1
				},
120
1
			)
121
1
			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
122
1
			.execute_returns(())
123
1
	})
124
1
}
125

            
126
#[test]
127
1
fn batch_all_empty() {
128
1
	ExtBuilder::default().build().execute_with(|| {
129
1
		precompiles()
130
1
			.prepare_test(
131
1
				Alice,
132
1
				Batch,
133
1
				PCall::batch_all {
134
1
					to: vec![].into(),
135
1
					value: vec![].into(),
136
1
					call_data: vec![].into(),
137
1
					gas_limit: vec![].into(),
138
1
				},
139
1
			)
140
1
			.with_subcall_handle(|Subcall { .. }| panic!("there should be no subcall"))
141
1
			.execute_returns(())
142
1
	})
143
1
}
144

            
145
3
fn batch_returns(
146
3
	precompiles: &Precompiles<Runtime>,
147
3
	mode: Mode,
148
3
) -> PrecompilesTester<Precompiles<Runtime>> {
149
3
	let mut counter = 0;
150
3

            
151
3
	let (_, total_call_cost) = costs();
152
3

            
153
3
	precompiles
154
3
		.prepare_test(
155
3
			Alice,
156
3
			Batch,
157
3
			PCall::batch_from_mode(
158
3
				mode,
159
3
				vec![Address(Bob.into()), Address(Charlie.into())],
160
3
				vec![U256::from(1u8), U256::from(2u8)],
161
3
				vec![b"one".to_vec(), b"two".to_vec()],
162
3
				vec![],
163
3
			),
164
3
		)
165
3
		.with_target_gas(Some(100_000))
166
6
		.with_subcall_handle(move |subcall| {
167
6
			let Subcall {
168
6
				address,
169
6
				transfer,
170
6
				input,
171
6
				target_gas,
172
6
				is_static,
173
6
				context,
174
6
			} = subcall;
175
6

            
176
6
			// Called from the precompile caller.
177
6
			assert_eq!(context.caller, Alice.into());
178
6
			assert_eq!(is_static, false);
179

            
180
6
			match address {
181
6
				a if a == Bob.into() => {
182
3
					assert_eq!(counter, 0, "this is the first call");
183
3
					counter += 1;
184
3

            
185
3
					assert_eq!(
186
3
						target_gas,
187
3
						Some(100_000 - total_call_cost),
188
						"batch forward all gas"
189
					);
190
3
					let transfer = transfer.expect("there is a transfer");
191
3
					assert_eq!(transfer.source, Alice.into());
192
3
					assert_eq!(transfer.target, Bob.into());
193
3
					assert_eq!(transfer.value, 1u8.into());
194

            
195
3
					assert_eq!(context.address, Bob.into());
196
3
					assert_eq!(context.apparent_value, 1u8.into());
197

            
198
3
					assert_eq!(&input, b"one");
199

            
200
3
					SubcallOutput {
201
3
						cost: 13,
202
3
						logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])],
203
3
						..SubcallOutput::succeed()
204
3
					}
205
				}
206
3
				a if a == Charlie.into() => {
207
3
					assert_eq!(counter, 1, "this is the second call");
208
3
					counter += 1;
209
3

            
210
3
					assert_eq!(
211
3
						target_gas,
212
3
						Some(100_000 - 13 - total_call_cost * 2),
213
						"batch forward all gas"
214
					);
215
3
					let transfer = transfer.expect("there is a transfer");
216
3
					assert_eq!(transfer.source, Alice.into());
217
3
					assert_eq!(transfer.target, Charlie.into());
218
3
					assert_eq!(transfer.value, 2u8.into());
219

            
220
3
					assert_eq!(context.address, Charlie.into());
221
3
					assert_eq!(context.apparent_value, 2u8.into());
222

            
223
3
					assert_eq!(&input, b"two");
224

            
225
3
					SubcallOutput {
226
3
						cost: 17,
227
3
						logs: vec![log1(Charlie, H256::repeat_byte(0x22), vec![])],
228
3
						..SubcallOutput::succeed()
229
3
					}
230
				}
231
				_ => panic!("unexpected subcall"),
232
			}
233
6
		})
234
3
		.expect_cost(13 + 17 + total_call_cost * 2)
235
3
}
236

            
237
#[test]
238
1
fn batch_some_returns() {
239
1
	ExtBuilder::default().build().execute_with(|| {
240
1
		batch_returns(&precompiles(), Mode::BatchSome)
241
1
			.expect_log(log1(Bob, H256::repeat_byte(0x11), vec![]))
242
1
			.expect_log(log_subcall_succeeded(Batch, 0))
243
1
			.expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![]))
244
1
			.expect_log(log_subcall_succeeded(Batch, 1))
245
1
			.execute_returns(())
246
1
	})
247
1
}
248

            
249
#[test]
250
1
fn batch_some_until_failure_returns() {
251
1
	ExtBuilder::default().build().execute_with(|| {
252
1
		batch_returns(&precompiles(), Mode::BatchSomeUntilFailure)
253
1
			.expect_log(log1(Bob, H256::repeat_byte(0x11), vec![]))
254
1
			.expect_log(log_subcall_succeeded(Batch, 0))
255
1
			.expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![]))
256
1
			.expect_log(log_subcall_succeeded(Batch, 1))
257
1
			.execute_returns(())
258
1
	})
259
1
}
260

            
261
#[test]
262
1
fn batch_all_returns() {
263
1
	ExtBuilder::default().build().execute_with(|| {
264
1
		batch_returns(&precompiles(), Mode::BatchAll)
265
1
			.expect_log(log1(Bob, H256::repeat_byte(0x11), vec![]))
266
1
			.expect_log(log_subcall_succeeded(Batch, 0))
267
1
			.expect_log(log1(Charlie, H256::repeat_byte(0x22), vec![]))
268
1
			.expect_log(log_subcall_succeeded(Batch, 1))
269
1
			.execute_returns(())
270
1
	})
271
1
}
272

            
273
3
fn batch_out_of_gas(
274
3
	precompiles: &Precompiles<Runtime>,
275
3
	mode: Mode,
276
3
) -> PrecompilesTester<Precompiles<Runtime>> {
277
3
	let (_, total_call_cost) = costs();
278
3

            
279
3
	precompiles
280
3
		.prepare_test(
281
3
			Alice,
282
3
			Batch,
283
3
			PCall::batch_from_mode(
284
3
				mode,
285
3
				vec![Address(Bob.into())],
286
3
				vec![U256::from(1u8)],
287
3
				vec![b"one".to_vec()],
288
3
				vec![],
289
3
			),
290
3
		)
291
3
		.with_target_gas(Some(50_000))
292
3
		.with_subcall_handle(move |subcall| {
293
3
			let Subcall {
294
3
				address,
295
3
				transfer,
296
3
				input,
297
3
				target_gas,
298
3
				is_static,
299
3
				context,
300
3
			} = subcall;
301
3

            
302
3
			// Called from the precompile caller.
303
3
			assert_eq!(context.caller, Alice.into());
304
3
			assert_eq!(is_static, false);
305

            
306
3
			match address {
307
3
				a if a == Bob.into() => {
308
3
					assert_eq!(
309
3
						target_gas,
310
3
						Some(50_000 - total_call_cost),
311
						"batch forward all gas"
312
					);
313
3
					let transfer = transfer.expect("there is a transfer");
314
3
					assert_eq!(transfer.source, Alice.into());
315
3
					assert_eq!(transfer.target, Bob.into());
316
3
					assert_eq!(transfer.value, 1u8.into());
317

            
318
3
					assert_eq!(context.address, Bob.into());
319
3
					assert_eq!(context.apparent_value, 1u8.into());
320

            
321
3
					assert_eq!(&input, b"one");
322

            
323
3
					SubcallOutput {
324
3
						cost: 11_000,
325
3
						..SubcallOutput::out_of_gas()
326
3
					}
327
				}
328
				_ => panic!("unexpected subcall"),
329
			}
330
3
		})
331
3
}
332

            
333
#[test]
334
1
fn batch_some_out_of_gas() {
335
1
	ExtBuilder::default().build().execute_with(|| {
336
1
		batch_out_of_gas(&precompiles(), Mode::BatchSome)
337
1
			.expect_log(log_subcall_failed(Batch, 0))
338
1
			.execute_returns(())
339
1
	})
340
1
}
341

            
342
#[test]
343
1
fn batch_some_until_failure_out_of_gas() {
344
1
	ExtBuilder::default().build().execute_with(|| {
345
1
		batch_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure)
346
1
			.expect_log(log_subcall_failed(Batch, 0))
347
1
			.execute_returns(())
348
1
	})
349
1
}
350

            
351
#[test]
352
1
fn batch_all_out_of_gas() {
353
1
	ExtBuilder::default().build().execute_with(|| {
354
1
		batch_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas)
355
1
	})
356
1
}
357

            
358
3
fn batch_incomplete(
359
3
	precompiles: &Precompiles<Runtime>,
360
3
	mode: Mode,
361
3
) -> PrecompilesTester<Precompiles<Runtime>> {
362
3
	let mut counter = 0;
363
3

            
364
3
	let (_, total_call_cost) = costs();
365
3

            
366
3
	precompiles
367
3
		.prepare_test(
368
3
			Alice,
369
3
			Batch,
370
3
			PCall::batch_from_mode(
371
3
				mode,
372
3
				vec![
373
3
					Address(Bob.into()),
374
3
					Address(Charlie.into()),
375
3
					Address(Alice.into()),
376
3
				],
377
3
				vec![U256::from(1u8), U256::from(2u8), U256::from(3u8)],
378
3
				vec![b"one".to_vec()],
379
3
				vec![],
380
3
			),
381
3
		)
382
3
		.with_target_gas(Some(300_000))
383
7
		.with_subcall_handle(move |subcall| {
384
7
			let Subcall {
385
7
				address,
386
7
				transfer,
387
7
				input,
388
7
				target_gas,
389
7
				is_static,
390
7
				context,
391
7
			} = subcall;
392
7

            
393
7
			// Called from the precompile caller.
394
7
			assert_eq!(context.caller, Alice.into());
395
7
			assert_eq!(is_static, false);
396

            
397
7
			match address {
398
7
				a if a == Bob.into() => {
399
3
					assert_eq!(counter, 0, "this is the first call");
400
3
					counter += 1;
401
3

            
402
3
					assert_eq!(
403
3
						target_gas,
404
3
						Some(300_000 - total_call_cost),
405
						"batch forward all gas"
406
					);
407
3
					let transfer = transfer.expect("there is a transfer");
408
3
					assert_eq!(transfer.source, Alice.into());
409
3
					assert_eq!(transfer.target, Bob.into());
410
3
					assert_eq!(transfer.value, 1u8.into());
411

            
412
3
					assert_eq!(context.address, Bob.into());
413
3
					assert_eq!(context.apparent_value, 1u8.into());
414

            
415
3
					assert_eq!(&input, b"one");
416

            
417
3
					SubcallOutput {
418
3
						cost: 13,
419
3
						logs: vec![log1(Bob, H256::repeat_byte(0x11), vec![])],
420
3
						..SubcallOutput::succeed()
421
3
					}
422
				}
423
4
				a if a == Charlie.into() => {
424
3
					assert_eq!(counter, 1, "this is the second call");
425
3
					counter += 1;
426
3

            
427
3
					assert_eq!(
428
3
						target_gas,
429
3
						Some(300_000 - 13 - total_call_cost * 2),
430
						"batch forward all gas"
431
					);
432
3
					let transfer = transfer.expect("there is a transfer");
433
3
					assert_eq!(transfer.source, Alice.into());
434
3
					assert_eq!(transfer.target, Charlie.into());
435
3
					assert_eq!(transfer.value, 2u8.into());
436

            
437
3
					assert_eq!(context.address, Charlie.into());
438
3
					assert_eq!(context.apparent_value, 2u8.into());
439

            
440
3
					assert_eq!(&input, b"");
441

            
442
3
					SubcallOutput {
443
3
						output: revert_as_bytes("Revert message"),
444
3
						cost: 17,
445
3
						..SubcallOutput::revert()
446
3
					}
447
				}
448
1
				a if a == Alice.into() => {
449
1
					assert_eq!(counter, 2, "this is the third call");
450
1
					counter += 1;
451
1

            
452
1
					assert_eq!(
453
1
						target_gas,
454
1
						Some(300_000 - 13 - 17 - total_call_cost * 3),
455
						"batch forward all gas"
456
					);
457
1
					let transfer = transfer.expect("there is a transfer");
458
1
					assert_eq!(transfer.source, Alice.into());
459
1
					assert_eq!(transfer.target, Alice.into());
460
1
					assert_eq!(transfer.value, 3u8.into());
461

            
462
1
					assert_eq!(context.address, Alice.into());
463
1
					assert_eq!(context.apparent_value, 3u8.into());
464

            
465
1
					assert_eq!(&input, b"");
466

            
467
1
					SubcallOutput {
468
1
						cost: 19,
469
1
						logs: vec![log1(Alice, H256::repeat_byte(0x33), vec![])],
470
1
						..SubcallOutput::succeed()
471
1
					}
472
				}
473
				_ => panic!("unexpected subcall"),
474
			}
475
7
		})
476
3
}
477

            
478
#[test]
479
1
fn batch_some_incomplete() {
480
1
	ExtBuilder::default().build().execute_with(|| {
481
1
		let (_, total_call_cost) = costs();
482
1

            
483
1
		batch_incomplete(&precompiles(), Mode::BatchSome)
484
1
			.expect_log(log1(Bob, H256::repeat_byte(0x11), vec![]))
485
1
			.expect_log(log_subcall_succeeded(Batch, 0))
486
1
			.expect_log(log_subcall_failed(Batch, 1))
487
1
			.expect_log(log1(Alice, H256::repeat_byte(0x33), vec![]))
488
1
			.expect_log(log_subcall_succeeded(Batch, 2))
489
1
			.expect_cost(13 + 17 + 19 + total_call_cost * 3)
490
1
			.execute_returns(())
491
1
	})
492
1
}
493

            
494
#[test]
495
1
fn batch_some_until_failure_incomplete() {
496
1
	ExtBuilder::default().build().execute_with(|| {
497
1
		let (_, total_call_cost) = costs();
498
1

            
499
1
		batch_incomplete(&precompiles(), Mode::BatchSomeUntilFailure)
500
1
			.expect_log(log1(Bob, H256::repeat_byte(0x11), vec![]))
501
1
			.expect_log(log_subcall_succeeded(Batch, 0))
502
1
			.expect_log(log_subcall_failed(Batch, 1))
503
1
			.expect_cost(13 + 17 + total_call_cost * 2)
504
1
			.execute_returns(())
505
1
	})
506
1
}
507

            
508
#[test]
509
1
fn batch_all_incomplete() {
510
1
	ExtBuilder::default().build().execute_with(|| {
511
1
		batch_incomplete(&precompiles(), Mode::BatchAll)
512
1
			.execute_reverts(|output| output == b"Revert message")
513
1
	})
514
1
}
515

            
516
3
fn batch_log_out_of_gas(
517
3
	precompiles: &Precompiles<Runtime>,
518
3
	mode: Mode,
519
3
) -> PrecompilesTester<Precompiles<Runtime>> {
520
3
	let (log_cost, _) = costs();
521
3

            
522
3
	precompiles
523
3
		.prepare_test(
524
3
			Alice,
525
3
			Batch,
526
3
			PCall::batch_from_mode(
527
3
				mode,
528
3
				vec![Address(Bob.into())],
529
3
				vec![U256::from(1u8)],
530
3
				vec![b"one".to_vec()],
531
3
				vec![],
532
3
			),
533
3
		)
534
3
		.with_target_gas(Some(log_cost - 1))
535
3
		.with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls"))
536
3
}
537

            
538
#[test]
539
1
fn batch_all_log_out_of_gas() {
540
1
	ExtBuilder::default().build().execute_with(|| {
541
1
		batch_log_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas);
542
1
	})
543
1
}
544

            
545
#[test]
546
1
fn batch_some_log_out_of_gas() {
547
1
	ExtBuilder::default().build().execute_with(|| {
548
1
		batch_log_out_of_gas(&precompiles(), Mode::BatchSome)
549
1
			.expect_no_logs()
550
1
			.execute_returns(());
551
1
	})
552
1
}
553

            
554
#[test]
555
1
fn batch_some_until_failure_log_out_of_gas() {
556
1
	ExtBuilder::default().build().execute_with(|| {
557
1
		batch_log_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure)
558
1
			.expect_no_logs()
559
1
			.execute_returns(());
560
1
	})
561
1
}
562

            
563
3
fn batch_call_out_of_gas(
564
3
	precompiles: &Precompiles<Runtime>,
565
3
	mode: Mode,
566
3
) -> PrecompilesTester<Precompiles<Runtime>> {
567
3
	let (_, total_call_cost) = costs();
568
3

            
569
3
	precompiles
570
3
		.prepare_test(
571
3
			Alice,
572
3
			Batch,
573
3
			PCall::batch_from_mode(
574
3
				mode,
575
3
				vec![Address(Bob.into())],
576
3
				vec![U256::from(1u8)],
577
3
				vec![b"one".to_vec()],
578
3
				vec![],
579
3
			),
580
3
		)
581
3
		.with_target_gas(Some(total_call_cost - 1))
582
3
		.with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls"))
583
3
}
584

            
585
#[test]
586
1
fn batch_all_call_out_of_gas() {
587
1
	ExtBuilder::default().build().execute_with(|| {
588
1
		batch_call_out_of_gas(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas);
589
1
	})
590
1
}
591

            
592
#[test]
593
1
fn batch_some_call_out_of_gas() {
594
1
	ExtBuilder::default().build().execute_with(|| {
595
1
		batch_call_out_of_gas(&precompiles(), Mode::BatchSome)
596
1
			.expect_log(log_subcall_failed(Batch, 0))
597
1
			.execute_returns(());
598
1
	})
599
1
}
600

            
601
#[test]
602
1
fn batch_some_until_failure_call_out_of_gas() {
603
1
	ExtBuilder::default().build().execute_with(|| {
604
1
		batch_call_out_of_gas(&precompiles(), Mode::BatchSomeUntilFailure)
605
1
			.expect_log(log_subcall_failed(Batch, 0))
606
1
			.execute_returns(());
607
1
	})
608
1
}
609

            
610
3
fn batch_gas_limit(
611
3
	precompiles: &Precompiles<Runtime>,
612
3
	mode: Mode,
613
3
) -> PrecompilesTester<Precompiles<Runtime>> {
614
3
	let (_, total_call_cost) = costs();
615
3

            
616
3
	precompiles
617
3
		.prepare_test(
618
3
			Alice,
619
3
			Batch,
620
3
			PCall::batch_from_mode(
621
3
				mode,
622
3
				vec![Address(Bob.into())],
623
3
				vec![U256::from(1u8)],
624
3
				vec![b"one".to_vec()],
625
3
				vec![50_000 - total_call_cost + 1],
626
3
			),
627
3
		)
628
3
		.with_target_gas(Some(50_000))
629
3
		.with_subcall_handle(move |_subcall| panic!("there shouldn't be any subcalls"))
630
3
}
631

            
632
#[test]
633
1
fn batch_all_gas_limit() {
634
1
	ExtBuilder::default().build().execute_with(|| {
635
1
		batch_gas_limit(&precompiles(), Mode::BatchAll).execute_error(ExitError::OutOfGas);
636
1
	})
637
1
}
638

            
639
#[test]
640
1
fn batch_some_gas_limit() {
641
1
	ExtBuilder::default().build().execute_with(|| {
642
1
		let (return_log_cost, _) = costs();
643
1

            
644
1
		batch_gas_limit(&precompiles(), Mode::BatchSome)
645
1
			.expect_log(log_subcall_failed(Batch, 0))
646
1
			.expect_cost(return_log_cost)
647
1
			.execute_returns(());
648
1
	})
649
1
}
650

            
651
#[test]
652
1
fn batch_some_until_failure_gas_limit() {
653
1
	ExtBuilder::default().build().execute_with(|| {
654
1
		batch_gas_limit(&precompiles(), Mode::BatchSomeUntilFailure)
655
1
			.expect_log(log_subcall_failed(Batch, 0))
656
1
			.execute_returns(());
657
1
	})
658
1
}
659

            
660
#[test]
661
1
fn evm_batch_some_transfers_enough() {
662
1
	ExtBuilder::default()
663
1
		.with_balances(vec![(Alice.into(), 10_000)])
664
1
		.build()
665
1
		.execute_with(|| {
666
1
			assert_ok!(RuntimeCall::Evm(evm_call(
667
1
				Alice,
668
1
				PCall::batch_some {
669
1
					to: vec![Address(Bob.into()), Address(Charlie.into())].into(),
670
1
					value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(),
671
1
					call_data: vec![].into(),
672
1
					gas_limit: vec![].into(),
673
1
				}
674
1
				.into()
675
1
			))
676
1
			.dispatch(RuntimeOrigin::root()));
677
1
		})
678
1
}
679

            
680
#[test]
681
1
fn evm_batch_some_until_failure_transfers_enough() {
682
1
	ExtBuilder::default()
683
1
		.with_balances(vec![(Alice.into(), 10_000)])
684
1
		.build()
685
1
		.execute_with(|| {
686
1
			assert_ok!(RuntimeCall::Evm(evm_call(
687
1
				Alice,
688
1
				PCall::batch_some_until_failure {
689
1
					to: vec![Address(Bob.into()), Address(Charlie.into())].into(),
690
1
					value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(),
691
1
					call_data: vec![].into(),
692
1
					gas_limit: vec![].into(),
693
1
				}
694
1
				.into()
695
1
			))
696
1
			.dispatch(RuntimeOrigin::root()));
697
1
		})
698
1
}
699

            
700
#[test]
701
1
fn evm_batch_all_transfers_enough() {
702
1
	ExtBuilder::default()
703
1
		.with_balances(vec![(Alice.into(), 10_000)])
704
1
		.build()
705
1
		.execute_with(|| {
706
1
			assert_ok!(RuntimeCall::Evm(evm_call(
707
1
				Alice,
708
1
				PCall::batch_all {
709
1
					to: vec![Address(Bob.into()), Address(Charlie.into())].into(),
710
1
					value: vec![U256::from(1_000u16), U256::from(2_000u16)].into(),
711
1
					call_data: vec![].into(),
712
1
					gas_limit: vec![].into(),
713
1
				}
714
1
				.into()
715
1
			))
716
1
			.dispatch(RuntimeOrigin::root()));
717

            
718
1
			assert_eq!(balance(Bob), 1_000);
719
1
			assert_eq!(balance(Charlie), 2_000);
720
1
		})
721
1
}
722

            
723
#[test]
724
1
fn evm_batch_some_transfers_too_much() {
725
1
	ExtBuilder::default()
726
1
		.with_balances(vec![(Alice.into(), 10_000)])
727
1
		.build()
728
1
		.execute_with(|| {
729
1
			assert_ok!(RuntimeCall::Evm(evm_call(
730
1
				Alice,
731
1
				PCall::batch_some {
732
1
					to: vec![
733
1
						Address(Bob.into()),
734
1
						Address(Charlie.into()),
735
1
						Address(David.into()),
736
1
					]
737
1
					.into(),
738
1
					value: vec![
739
1
						U256::from(9_000u16),
740
1
						U256::from(2_000u16),
741
1
						U256::from(500u16)
742
1
					]
743
1
					.into(),
744
1
					call_data: vec![].into(),
745
1
					gas_limit: vec![].into()
746
1
				}
747
1
				.into()
748
1
			))
749
1
			.dispatch(RuntimeOrigin::root()));
750

            
751
1
			assert_eq!(balance(Alice), 500); // gasprice = 0
752
1
			assert_eq!(balance(Bob), 9_000);
753
1
			assert_eq!(balance(Charlie), 0);
754
1
			assert_eq!(balance(David), 500);
755
1
		})
756
1
}
757

            
758
#[test]
759
1
fn evm_batch_some_until_failure_transfers_too_much() {
760
1
	ExtBuilder::default()
761
1
		.with_balances(vec![(Alice.into(), 10_000)])
762
1
		.build()
763
1
		.execute_with(|| {
764
1
			assert_ok!(RuntimeCall::Evm(evm_call(
765
1
				Alice,
766
1
				PCall::batch_some_until_failure {
767
1
					to: vec![
768
1
						Address(Bob.into()),
769
1
						Address(Charlie.into()),
770
1
						Address(David.into()),
771
1
					]
772
1
					.into(),
773
1
					value: vec![
774
1
						U256::from(9_000u16),
775
1
						U256::from(2_000u16),
776
1
						U256::from(500u16)
777
1
					]
778
1
					.into(),
779
1
					call_data: vec![].into(),
780
1
					gas_limit: vec![].into()
781
1
				}
782
1
				.into()
783
1
			))
784
1
			.dispatch(RuntimeOrigin::root()));
785

            
786
1
			assert_eq!(balance(Alice), 1_000); // gasprice = 0
787
1
			assert_eq!(balance(Bob), 9_000);
788
1
			assert_eq!(balance(Charlie), 0);
789
1
			assert_eq!(balance(David), 0);
790
1
		})
791
1
}
792

            
793
#[test]
794
1
fn evm_batch_all_transfers_too_much() {
795
1
	ExtBuilder::default()
796
1
		.with_balances(vec![(Alice.into(), 10_000)])
797
1
		.build()
798
1
		.execute_with(|| {
799
1
			assert_ok!(RuntimeCall::Evm(evm_call(
800
1
				Alice,
801
1
				PCall::batch_all {
802
1
					to: vec![
803
1
						Address(Bob.into()),
804
1
						Address(Charlie.into()),
805
1
						Address(David.into()),
806
1
					]
807
1
					.into(),
808
1
					value: vec![
809
1
						U256::from(9_000u16),
810
1
						U256::from(2_000u16),
811
1
						U256::from(500u16)
812
1
					]
813
1
					.into(),
814
1
					call_data: vec![].into(),
815
1
					gas_limit: vec![].into()
816
1
				}
817
1
				.into()
818
1
			))
819
1
			.dispatch(RuntimeOrigin::root()));
820

            
821
1
			assert_eq!(balance(Alice), 10_000); // gasprice = 0
822
1
			assert_eq!(balance(Bob), 0);
823
1
			assert_eq!(balance(Charlie), 0);
824
1
			assert_eq!(balance(David), 0);
825
1
		})
826
1
}
827

            
828
#[test]
829
1
fn evm_batch_some_contract_revert() {
830
1
	ExtBuilder::default()
831
1
		.with_balances(vec![(Alice.into(), 10_000)])
832
1
		.build()
833
1
		.execute_with(|| {
834
1
			assert_ok!(RuntimeCall::Evm(evm_call(
835
1
				Alice,
836
1
				PCall::batch_some {
837
1
					to: vec![
838
1
						Address(Bob.into()),
839
1
						Address(Revert.into()),
840
1
						Address(David.into()),
841
1
					]
842
1
					.into(),
843
1
					value: vec![
844
1
						U256::from(1_000u16),
845
1
						U256::from(2_000),
846
1
						U256::from(3_000u16)
847
1
					]
848
1
					.into(),
849
1
					call_data: vec![].into(),
850
1
					gas_limit: vec![].into()
851
1
				}
852
1
				.into()
853
1
			))
854
1
			.dispatch(RuntimeOrigin::root()));
855

            
856
1
			assert_eq!(balance(Alice), 6_000); // gasprice = 0
857
1
			assert_eq!(balance(Bob), 1_000);
858
1
			assert_eq!(balance(Revert), 0);
859
1
			assert_eq!(balance(David), 3_000);
860
1
		})
861
1
}
862

            
863
#[test]
864
1
fn evm_batch_some_until_failure_contract_revert() {
865
1
	ExtBuilder::default()
866
1
		.with_balances(vec![(Alice.into(), 10_000)])
867
1
		.build()
868
1
		.execute_with(|| {
869
1
			assert_ok!(RuntimeCall::Evm(evm_call(
870
1
				Alice,
871
1
				PCall::batch_some_until_failure {
872
1
					to: vec![
873
1
						Address(Bob.into()),
874
1
						Address(Revert.into()),
875
1
						Address(David.into()),
876
1
					]
877
1
					.into(),
878
1
					value: vec![
879
1
						U256::from(1_000u16),
880
1
						U256::from(2_000),
881
1
						U256::from(3_000u16)
882
1
					]
883
1
					.into(),
884
1
					call_data: vec![].into(),
885
1
					gas_limit: vec![].into()
886
1
				}
887
1
				.into()
888
1
			))
889
1
			.dispatch(RuntimeOrigin::root()));
890

            
891
1
			assert_eq!(balance(Alice), 9_000); // gasprice = 0
892
1
			assert_eq!(balance(Bob), 1_000);
893
1
			assert_eq!(balance(Revert), 0);
894
1
			assert_eq!(balance(David), 0);
895
1
		})
896
1
}
897

            
898
#[test]
899
1
fn evm_batch_all_contract_revert() {
900
1
	ExtBuilder::default()
901
1
		.with_balances(vec![(Alice.into(), 10_000)])
902
1
		.build()
903
1
		.execute_with(|| {
904
1
			assert_ok!(RuntimeCall::Evm(evm_call(
905
1
				Alice,
906
1
				PCall::batch_all {
907
1
					to: vec![
908
1
						Address(Bob.into()),
909
1
						Address(Revert.into()),
910
1
						Address(David.into()),
911
1
					]
912
1
					.into(),
913
1
					value: vec![
914
1
						U256::from(1_000u16),
915
1
						U256::from(2_000),
916
1
						U256::from(3_000u16)
917
1
					]
918
1
					.into(),
919
1
					call_data: vec![].into(),
920
1
					gas_limit: vec![].into()
921
1
				}
922
1
				.into()
923
1
			))
924
1
			.dispatch(RuntimeOrigin::root()));
925

            
926
1
			assert_eq!(balance(Alice), 10_000); // gasprice = 0
927
1
			assert_eq!(balance(Bob), 0);
928
1
			assert_eq!(balance(Revert), 0);
929
1
			assert_eq!(balance(David), 0);
930
1
		})
931
1
}
932

            
933
#[test]
934
1
fn evm_batch_recursion_under_limit() {
935
1
	ExtBuilder::default()
936
1
		.with_balances(vec![(Alice.into(), 10_000)])
937
1
		.build()
938
1
		.execute_with(|| {
939
1
			// Mock sets the recursion limit to 2, and we 2 nested batch.
940
1
			// Thus it succeeds.
941
1

            
942
1
			let input = PCall::batch_all {
943
1
				to: vec![Address(Batch.into())].into(),
944
1
				value: vec![].into(),
945
1
				gas_limit: vec![].into(),
946
1
				call_data: vec![PCall::batch_all {
947
1
					to: vec![Address(Bob.into())].into(),
948
1
					value: vec![1000_u32.into()].into(),
949
1
					gas_limit: vec![].into(),
950
1
					call_data: vec![].into(),
951
1
				}
952
1
				.encode()
953
1
				.into()]
954
1
				.into(),
955
1
			}
956
1
			.into();
957
1

            
958
1
			assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()));
959

            
960
1
			assert_eq!(balance(Alice), 9_000); // gasprice = 0
961
1
			assert_eq!(balance(Bob), 1_000);
962
1
		})
963
1
}
964

            
965
#[test]
966
1
fn evm_batch_recursion_over_limit() {
967
1
	ExtBuilder::default()
968
1
		.with_balances(vec![(Alice.into(), 10_000)])
969
1
		.build()
970
1
		.execute_with(|| {
971
1
			// Mock sets the recursion limit to 2, and we 3 nested batch.
972
1
			// Thus it reverts.
973
1

            
974
1
			let input = PCall::batch_from_mode(
975
1
				Mode::BatchAll,
976
1
				vec![Address(Batch.into())],
977
1
				vec![],
978
1
				vec![PCall::batch_from_mode(
979
1
					Mode::BatchAll,
980
1
					vec![Address(Batch.into())],
981
1
					vec![],
982
1
					vec![PCall::batch_from_mode(
983
1
						Mode::BatchAll,
984
1
						vec![Address(Bob.into())],
985
1
						vec![1000_u32.into()],
986
1
						vec![],
987
1
						vec![].into(),
988
1
					)
989
1
					.into()],
990
1
					vec![].into(),
991
1
				)
992
1
				.into()],
993
1
				vec![],
994
1
			)
995
1
			.into();
996
1

            
997
1
			assert_ok!(RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()));
998

            
999
1
			assert_eq!(balance(Alice), 10_000); // gasprice = 0
1
			assert_eq!(balance(Bob), 0);
1
		})
1
}
#[test]
1
fn batch_is_not_callable_by_dummy_code() {
1
	ExtBuilder::default()
1
		.with_balances(vec![(Alice.into(), 10_000)])
1
		.build()
1
		.execute_with(|| {
1
			// "deploy" dummy code to alice address
1
			let alice_h160: H160 = Alice.into();
1
			pallet_evm::AccountCodes::<Runtime>::insert(
1
				alice_h160,
1
				[0x60, 0x00, 0x60, 0x00, 0xfd].to_vec(),
1
			);
1

            
1
			// succeeds if called by dummy code, see `evm_batch_recursion_under_limit`
1
			let input = PCall::batch_all {
1
				to: vec![Address(Batch.into())].into(),
1
				value: vec![].into(),
1
				gas_limit: vec![].into(),
1
				call_data: vec![PCall::batch_all {
1
					to: vec![Address(Bob.into())].into(),
1
					value: vec![1000_u32.into()].into(),
1
					gas_limit: vec![].into(),
1
					call_data: vec![].into(),
1
				}
1
				.encode()
1
				.into()]
1
				.into(),
1
			}
1
			.into();
1

            
1
			match RuntimeCall::Evm(evm_call(Alice, input)).dispatch(RuntimeOrigin::root()) {
				Err(DispatchErrorWithPostInfo {
					error:
						DispatchError::Module(ModuleError {
							message: Some(err_msg),
							..
						}),
					..
				}) => println!("MESSAGE {:?}", err_msg),
1
				_ => println!("expected error 'TransactionMustComeFromEOA'"),
			}
1
		})
1
}
#[test]
1
fn test_solidity_interface_has_all_function_selectors_documented_and_implemented() {
1
	check_precompile_implements_solidity_interfaces(&["Batch.sol"], PCall::supports_selector)
1
}