1
// Copyright 2019-2022 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
use crate::{mock::*, Error, RawOrigin};
17
use ethereum_types::{H160, U256};
18
use frame_support::{
19
	assert_noop, assert_ok,
20
	dispatch::{Pays, PostDispatchInfo},
21
	traits::{ConstU32, Get},
22
	weights::Weight,
23
	BoundedVec,
24
};
25
use sp_runtime::{DispatchError, DispatchErrorWithPostInfo};
26
use xcm_primitives::{EthereumXcmTransaction, EthereumXcmTransactionV2};
27

            
28
// 	pragma solidity ^0.6.6;
29
// 	contract Test {
30
// 		function foo() external pure returns (bool) {
31
// 			return true;
32
// 		}
33
// 		function bar() external pure {
34
// 			require(false, "error_msg");
35
// 		}
36
// 	}
37
const CONTRACT: &str = "608060405234801561001057600080fd5b50610113806100206000396000f3fe6080604052\
38
						348015600f57600080fd5b506004361060325760003560e01c8063c2985578146037578063\
39
						febb0f7e146057575b600080fd5b603d605f565b6040518082151515158152602001915050\
40
						60405180910390f35b605d6068565b005b60006001905090565b600060db576040517f08c3\
41
						79a00000000000000000000000000000000000000000000000000000000081526004018080\
42
						602001828103825260098152602001807f6572726f725f6d73670000000000000000000000\
43
						00000000000000000000000081525060200191505060405180910390fd5b56fea264697066\
44
						7358221220fde68a3968e0e99b16fabf9b2997a78218b32214031f8e07e2c502daf603a69e\
45
						64736f6c63430006060033";
46

            
47
12
fn xcm_evm_transfer_eip_1559_transaction(destination: H160, value: U256) -> EthereumXcmTransaction {
48
12
	EthereumXcmTransaction::V2(EthereumXcmTransactionV2 {
49
12
		gas_limit: U256::from(0x5208),
50
12
		action: ethereum::TransactionAction::Call(destination),
51
12
		value,
52
12
		input:
53
12
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
54
12
				vec![],
55
12
			)
56
12
			.unwrap(),
57
12
		access_list: None,
58
12
	})
59
12
}
60

            
61
2
fn xcm_evm_call_eip_1559_transaction(destination: H160, input: Vec<u8>) -> EthereumXcmTransaction {
62
2
	EthereumXcmTransaction::V2(EthereumXcmTransactionV2 {
63
2
		gas_limit: U256::from(0x100000),
64
2
		action: ethereum::TransactionAction::Call(destination),
65
2
		value: U256::zero(),
66
2
		input:
67
2
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
68
2
				input,
69
2
			)
70
2
			.unwrap(),
71
2
		access_list: None,
72
2
	})
73
2
}
74

            
75
1
fn xcm_erc20_creation_eip_1559_transaction() -> EthereumXcmTransaction {
76
1
	EthereumXcmTransaction::V2(EthereumXcmTransactionV2 {
77
1
		gas_limit: U256::from(0x100000),
78
1
		action: ethereum::TransactionAction::Create,
79
1
		value: U256::zero(),
80
1
		input:
81
1
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
82
1
				hex::decode(CONTRACT).unwrap(),
83
1
			)
84
1
			.unwrap(),
85
1
		access_list: None,
86
1
	})
87
1
}
88

            
89
#[test]
90
1
fn test_transact_xcm_evm_transfer() {
91
1
	let (pairs, mut ext) = new_test_ext(2);
92
1
	let alice = &pairs[0];
93
1
	let bob = &pairs[1];
94
1

            
95
1
	ext.execute_with(|| {
96
1
		let balances_before = System::account(&bob.account_id);
97
1
		EthereumXcm::transact(
98
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
99
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
100
1
		)
101
1
		.expect("Failed to execute transaction");
102
1

            
103
1
		assert_eq!(
104
1
			System::account(&bob.account_id).data.free,
105
1
			balances_before.data.free + 100
106
1
		);
107
1
	});
108
1
}
109

            
110
#[test]
111
1
fn test_transact_xcm_create() {
112
1
	let (pairs, mut ext) = new_test_ext(1);
113
1
	let alice = &pairs[0];
114
1

            
115
1
	ext.execute_with(|| {
116
1
		assert_noop!(
117
1
			EthereumXcm::transact(
118
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
119
1
				xcm_erc20_creation_eip_1559_transaction()
120
1
			),
121
1
			DispatchErrorWithPostInfo {
122
1
				post_info: PostDispatchInfo {
123
1
					actual_weight: Some(Weight::zero()),
124
1
					pays_fee: Pays::Yes,
125
1
				},
126
1
				error: DispatchError::Other("Cannot convert xcm payload to known type"),
127
1
			}
128
1
		);
129
1
	});
130
1
}
131

            
132
#[test]
133
1
fn test_transact_xcm_evm_call_works() {
134
1
	let (pairs, mut ext) = new_test_ext(2);
135
1
	let alice = &pairs[0];
136
1
	let bob = &pairs[1];
137
1

            
138
1
	ext.execute_with(|| {
139
1
		let t = EIP1559UnsignedTransaction {
140
1
			nonce: U256::zero(),
141
1
			max_priority_fee_per_gas: U256::one(),
142
1
			max_fee_per_gas: U256::one(),
143
1
			gas_limit: U256::from(0x100000),
144
1
			action: ethereum::TransactionAction::Create,
145
1
			value: U256::zero(),
146
1
			input: hex::decode(CONTRACT).unwrap(),
147
1
		}
148
1
		.sign(&alice.private_key, None);
149
1
		assert_ok!(Ethereum::execute(alice.address, &t, None, None));
150

            
151
1
		let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
152
1
		let foo = hex::decode("c2985578").unwrap();
153
1
		let bar = hex::decode("febb0f7e").unwrap();
154
1

            
155
1
		let _ = EthereumXcm::transact(
156
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
157
1
			xcm_evm_call_eip_1559_transaction(H160::from_slice(&contract_address), foo),
158
1
		)
159
1
		.expect("Failed to call `foo`");
160
1

            
161
1
		// Evm call failing still succesfully dispatched
162
1
		let _ = EthereumXcm::transact(
163
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
164
1
			xcm_evm_call_eip_1559_transaction(H160::from_slice(&contract_address), bar),
165
1
		)
166
1
		.expect("Failed to call `bar`");
167
1

            
168
1
		let pending = pallet_ethereum::Pending::<Test>::get();
169
1
		assert!(pending.len() == 2);
170

            
171
		// Transaction is in Pending storage, with nonce 0 and status 1 (evm succeed).
172
1
		let (transaction_0, _, receipt_0) = &pending[0];
173
1
		match (transaction_0, receipt_0) {
174
1
			(&crate::Transaction::EIP1559(ref t), &crate::Receipt::EIP1559(ref r)) => {
175
1
				assert!(t.nonce == U256::from(0u8));
176
1
				assert!(r.status_code == 1u8);
177
			}
178
			_ => unreachable!(),
179
		}
180

            
181
		// Transaction is in Pending storage, with nonce 1 and status 0 (evm failed).
182
1
		let (transaction_1, _, receipt_1) = &pending[1];
183
1
		match (transaction_1, receipt_1) {
184
1
			(&crate::Transaction::EIP1559(ref t), &crate::Receipt::EIP1559(ref r)) => {
185
1
				assert!(t.nonce == U256::from(1u8));
186
1
				assert!(r.status_code == 0u8);
187
			}
188
			_ => unreachable!(),
189
		}
190
1
	});
191
1
}
192

            
193
#[test]
194
1
fn test_transact_xcm_validation_works() {
195
1
	let (pairs, mut ext) = new_test_ext(2);
196
1
	let alice = &pairs[0];
197
1
	let bob = &pairs[1];
198
1

            
199
1
	ext.execute_with(|| {
200
1
		// Not enough gas limit to cover the transaction cost.
201
1
		assert_noop!(
202
1
			EthereumXcm::transact(
203
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
204
1
				EthereumXcmTransaction::V2(EthereumXcmTransactionV2 {
205
1
					gas_limit: U256::from(0x5207),
206
1
					action: ethereum::TransactionAction::Call(bob.address),
207
1
					value: U256::one(),
208
1
					input: BoundedVec::<
209
1
						u8,
210
1
						ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>,
211
1
					>::try_from(vec![])
212
1
					.unwrap(),
213
1
					access_list: None,
214
1
				}),
215
1
			),
216
1
			DispatchErrorWithPostInfo {
217
1
				post_info: PostDispatchInfo {
218
1
					actual_weight: Some(Weight::zero()),
219
1
					pays_fee: Pays::Yes,
220
1
				},
221
1
				error: DispatchError::Other("Failed to validate ethereum transaction"),
222
1
			}
223
1
		);
224
1
	});
225
1
}
226

            
227
#[test]
228
1
fn test_ensure_transact_xcm_trough_no_proxy_error() {
229
1
	let (pairs, mut ext) = new_test_ext(2);
230
1
	let alice = &pairs[0];
231
1
	let bob = &pairs[1];
232
1

            
233
1
	ext.execute_with(|| {
234
1
		let r = EthereumXcm::transact_through_proxy(
235
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
236
1
			bob.address,
237
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
238
1
		);
239
1
		assert!(r.is_err());
240
1
		assert_eq!(
241
1
			r.unwrap_err().error,
242
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
243
1
		);
244
1
	});
245
1
}
246

            
247
#[test]
248
1
fn test_ensure_transact_xcm_trough_proxy_error() {
249
1
	let (pairs, mut ext) = new_test_ext(2);
250
1
	let alice = &pairs[0];
251
1
	let bob = &pairs[1];
252
1

            
253
1
	ext.execute_with(|| {
254
1
		let _ = Proxy::add_proxy_delegate(
255
1
			&bob.account_id,
256
1
			alice.account_id.clone(),
257
1
			ProxyType::NotAllowed,
258
1
			0,
259
1
		);
260
1
		let r = EthereumXcm::transact_through_proxy(
261
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
262
1
			bob.address,
263
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
264
1
		);
265
1
		assert!(r.is_err());
266
1
		assert_eq!(
267
1
			r.unwrap_err().error,
268
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
269
1
		);
270
1
	});
271
1
}
272

            
273
#[test]
274
1
fn test_ensure_transact_xcm_trough_proxy_ok() {
275
1
	let (pairs, mut ext) = new_test_ext(3);
276
1
	let alice = &pairs[0];
277
1
	let bob = &pairs[1];
278
1
	let charlie = &pairs[2];
279
1

            
280
1
	let allowed_proxies = vec![ProxyType::Any];
281

            
282
1
	for proxy in allowed_proxies.into_iter() {
283
1
		ext.execute_with(|| {
284
1
			let _ = Proxy::add_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
285
1
			let alice_before = System::account(&alice.account_id);
286
1
			let bob_before = System::account(&bob.account_id);
287
1
			let charlie_before = System::account(&charlie.account_id);
288
1

            
289
1
			let r = EthereumXcm::transact_through_proxy(
290
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
291
1
				bob.address,
292
1
				xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::from(100)),
293
1
			);
294
1
			// Transact succeeded
295
1
			assert!(r.is_ok());
296

            
297
1
			let alice_after = System::account(&alice.account_id);
298
1
			let bob_after = System::account(&bob.account_id);
299
1
			let charlie_after = System::account(&charlie.account_id);
300
1

            
301
1
			// Alice remains unchanged
302
1
			assert_eq!(alice_before, alice_after);
303

            
304
			// Bob nonce was increased
305
1
			assert_eq!(bob_after.nonce, bob_before.nonce + 1);
306

            
307
			// Bob sent some funds without paying any fees
308
1
			assert_eq!(bob_after.data.free, bob_before.data.free - 100);
309

            
310
			// Charlie receive some funds
311
1
			assert_eq!(charlie_after.data.free, charlie_before.data.free + 100);
312

            
313
			// Clear proxy
314
			let _ =
315
1
				Proxy::remove_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
316
1
		});
317
1
	}
318
1
}
319

            
320
#[test]
321
1
fn test_global_nonce_incr() {
322
1
	let (pairs, mut ext) = new_test_ext(3);
323
1
	let alice = &pairs[0];
324
1
	let bob = &pairs[1];
325
1
	let charlie = &pairs[2];
326
1

            
327
1
	ext.execute_with(|| {
328
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
329

            
330
1
		EthereumXcm::transact(
331
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
332
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
333
1
		)
334
1
		.expect("Failed to execute transaction from Alice to Charlie");
335
1

            
336
1
		assert_eq!(EthereumXcm::nonce(), U256::one());
337

            
338
1
		EthereumXcm::transact(
339
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
340
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
341
1
		)
342
1
		.expect("Failed to execute transaction from Bob to Charlie");
343
1

            
344
1
		assert_eq!(EthereumXcm::nonce(), U256::from(2));
345
1
	});
346
1
}
347

            
348
#[test]
349
1
fn test_global_nonce_not_incr() {
350
1
	let (pairs, mut ext) = new_test_ext(2);
351
1
	let alice = &pairs[0];
352
1
	let bob = &pairs[1];
353
1

            
354
1
	ext.execute_with(|| {
355
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
356

            
357
1
		let invalid_transaction_cost =
358
1
			EthereumXcmTransaction::V2(
359
1
				EthereumXcmTransactionV2 {
360
1
					gas_limit: U256::one(),
361
1
					action: ethereum::TransactionAction::Call(bob.address),
362
1
					value: U256::one(),
363
1
					input: BoundedVec::<
364
1
						u8,
365
1
						ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>,
366
1
					>::try_from(vec![])
367
1
					.unwrap(),
368
1
					access_list: None,
369
1
				},
370
1
			);
371
1

            
372
1
		EthereumXcm::transact(
373
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
374
1
			invalid_transaction_cost,
375
1
		)
376
1
		.expect_err("Failed to execute transaction from Alice to Bob");
377
1

            
378
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
379
1
	});
380
1
}
381

            
382
#[test]
383
1
fn test_transaction_hash_collision() {
384
1
	let (pairs, mut ext) = new_test_ext(3);
385
1
	let alice = &pairs[0];
386
1
	let bob = &pairs[1];
387
1
	let charlie = &pairs[2];
388
1

            
389
1
	ext.execute_with(|| {
390
1
		EthereumXcm::transact(
391
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
392
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
393
1
		)
394
1
		.expect("Failed to execute transaction from Alice to Charlie");
395
1

            
396
1
		EthereumXcm::transact(
397
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
398
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
399
1
		)
400
1
		.expect("Failed to execute transaction from Bob to Charlie");
401
1

            
402
1
		let mut hashes = pallet_ethereum::Pending::<Test>::get()
403
1
			.iter()
404
2
			.map(|(tx, _, _)| tx.hash())
405
1
			.collect::<Vec<ethereum_types::H256>>();
406
1

            
407
1
		// Holds two transactions hashes
408
1
		assert_eq!(hashes.len(), 2);
409

            
410
1
		hashes.dedup();
411
1

            
412
1
		// Still holds two transactions hashes after removing potential consecutive repeated values.
413
1
		assert_eq!(hashes.len(), 2);
414
1
	});
415
1
}
416

            
417
#[test]
418
1
fn check_suspend_ethereum_to_xcm_works() {
419
1
	let (pairs, mut ext) = new_test_ext(2);
420
1
	let alice = &pairs[0];
421
1
	let bob = &pairs[1];
422
1

            
423
1
	let db_weights: frame_support::weights::RuntimeDbWeight =
424
1
		<Test as frame_system::Config>::DbWeight::get();
425
1

            
426
1
	ext.execute_with(|| {
427
1
		assert_ok!(EthereumXcm::suspend_ethereum_xcm_execution(
428
1
			RuntimeOrigin::root(),
429
1
		));
430
1
		assert_noop!(
431
1
			EthereumXcm::transact(
432
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
433
1
				xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
434
1
			),
435
1
			DispatchErrorWithPostInfo {
436
1
				error: Error::<Test>::EthereumXcmExecutionSuspended.into(),
437
1
				post_info: PostDispatchInfo {
438
1
					actual_weight: Some(db_weights.reads(1)),
439
1
					pays_fee: Pays::Yes
440
1
				}
441
1
			}
442
1
		);
443

            
444
1
		assert_noop!(
445
1
			EthereumXcm::transact_through_proxy(
446
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
447
1
				bob.address,
448
1
				xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
449
1
			),
450
1
			DispatchErrorWithPostInfo {
451
1
				error: Error::<Test>::EthereumXcmExecutionSuspended.into(),
452
1
				post_info: PostDispatchInfo {
453
1
					actual_weight: Some(db_weights.reads(1)),
454
1
					pays_fee: Pays::Yes
455
1
				}
456
1
			}
457
1
		);
458
1
	});
459
1
}
460

            
461
#[test]
462
1
fn transact_after_resume_ethereum_to_xcm_works() {
463
1
	let (pairs, mut ext) = new_test_ext(2);
464
1
	let alice = &pairs[0];
465
1
	let bob = &pairs[1];
466
1

            
467
1
	ext.execute_with(|| {
468
1
		let bob_before = System::account(&bob.account_id);
469
1

            
470
1
		assert_ok!(EthereumXcm::suspend_ethereum_xcm_execution(
471
1
			RuntimeOrigin::root()
472
1
		));
473

            
474
1
		assert_ok!(EthereumXcm::resume_ethereum_xcm_execution(
475
1
			RuntimeOrigin::root()
476
1
		));
477
1
		assert_ok!(EthereumXcm::transact(
478
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
479
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
480
1
		));
481
1
		let bob_after = System::account(&bob.account_id);
482
1

            
483
1
		// Bob sent some funds without paying any fees
484
1
		assert_eq!(bob_after.data.free, bob_before.data.free + 100);
485
1
	});
486
1
}
487

            
488
#[test]
489
1
fn transact_through_proxy_after_resume_ethereum_to_xcm_works() {
490
1
	let (pairs, mut ext) = new_test_ext(3);
491
1
	let alice = &pairs[0];
492
1
	let bob = &pairs[1];
493
1
	let charlie = &pairs[2];
494
1

            
495
1
	ext.execute_with(|| {
496
1
		let _ =
497
1
			Proxy::add_proxy_delegate(&bob.account_id, alice.account_id.clone(), ProxyType::Any, 0);
498
1
		let alice_before = System::account(&alice.account_id);
499
1
		let bob_before = System::account(&bob.account_id);
500
1
		let charlie_before = System::account(&charlie.account_id);
501
1

            
502
1
		assert_ok!(EthereumXcm::suspend_ethereum_xcm_execution(
503
1
			RuntimeOrigin::root()
504
1
		));
505

            
506
1
		assert_ok!(EthereumXcm::resume_ethereum_xcm_execution(
507
1
			RuntimeOrigin::root()
508
1
		));
509
1
		assert_ok!(EthereumXcm::transact_through_proxy(
510
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
511
1
			bob.address,
512
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::from(100)),
513
1
		));
514

            
515
1
		let alice_after = System::account(&alice.account_id);
516
1
		let bob_after = System::account(&bob.account_id);
517
1
		let charlie_after = System::account(&charlie.account_id);
518
1

            
519
1
		// Alice remains unchanged
520
1
		assert_eq!(alice_before, alice_after);
521

            
522
		// Bob nonce was increased
523
1
		assert_eq!(bob_after.nonce, bob_before.nonce + 1);
524

            
525
		// Bob sent some funds without paying any fees
526
1
		assert_eq!(bob_after.data.free, bob_before.data.free - 100);
527

            
528
		// Charlie receive some funds
529
1
		assert_eq!(charlie_after.data.free, charlie_before.data.free + 100);
530
1
	});
531
1
}