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
	}
49
12
}
50

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
998
1
			assert_eq!(balance(Alice), 10_000); // gasprice = 0
999
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
}