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 super::*;
18
use frame_support::{
19
	assert_noop,
20
	dispatch::{Pays, PostDispatchInfo},
21
	traits::ConstU32,
22
	weights::Weight,
23
	BoundedVec,
24
};
25
use sp_runtime::{DispatchError, DispatchErrorWithPostInfo};
26
use xcm_primitives::{
27
	EthereumXcmFee, EthereumXcmTransaction, EthereumXcmTransactionV1, ManualEthereumXcmFee,
28
};
29

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

            
49
8
fn xcm_evm_transfer_eip_2930_transaction(destination: H160, value: U256) -> EthereumXcmTransaction {
50
8
	let access_list = Some(vec![(H160::default(), vec![H256::default()])]);
51
8

            
52
8
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
53
8
		fee_payment: EthereumXcmFee::Auto,
54
8
		gas_limit: U256::from(0x100000),
55
8
		action: ethereum::TransactionAction::Call(destination),
56
8
		value,
57
8
		input:
58
8
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
59
8
				vec![],
60
8
			)
61
8
			.unwrap(),
62
8
		access_list,
63
8
	})
64
8
}
65

            
66
2
fn xcm_evm_call_eip_2930_transaction(destination: H160, input: Vec<u8>) -> EthereumXcmTransaction {
67
2
	let access_list = Some(vec![(H160::default(), vec![H256::default()])]);
68
2

            
69
2
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
70
2
		fee_payment: EthereumXcmFee::Manual(ManualEthereumXcmFee {
71
2
			gas_price: Some(U256::from(1)),
72
2
			max_fee_per_gas: None,
73
2
		}),
74
2
		gas_limit: U256::from(0x100000),
75
2
		action: ethereum::TransactionAction::Call(destination),
76
2
		value: U256::zero(),
77
2
		input:
78
2
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
79
2
				input,
80
2
			)
81
2
			.unwrap(),
82
2
		access_list,
83
2
	})
84
2
}
85

            
86
1
fn xcm_erc20_creation_eip_2930_transaction() -> EthereumXcmTransaction {
87
1
	let access_list = Some(vec![(H160::default(), vec![H256::default()])]);
88
1

            
89
1
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
90
1
		fee_payment: EthereumXcmFee::Manual(ManualEthereumXcmFee {
91
1
			gas_price: Some(U256::from(1)),
92
1
			max_fee_per_gas: None,
93
1
		}),
94
1
		gas_limit: U256::from(0x100000),
95
1
		action: ethereum::TransactionAction::Create,
96
1
		value: U256::zero(),
97
1
		input:
98
1
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
99
1
				hex::decode(CONTRACT).unwrap(),
100
1
			)
101
1
			.unwrap(),
102
1
		access_list,
103
1
	})
104
1
}
105

            
106
#[test]
107
1
fn test_transact_xcm_evm_transfer() {
108
1
	let (pairs, mut ext) = new_test_ext(2);
109
1
	let alice = &pairs[0];
110
1
	let bob = &pairs[1];
111
1

            
112
1
	ext.execute_with(|| {
113
1
		let balances_before = System::account(&bob.account_id);
114
1
		EthereumXcm::transact(
115
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
116
1
			xcm_evm_transfer_eip_2930_transaction(bob.address, U256::from(100)),
117
1
		)
118
1
		.expect("Failed to execute transaction");
119
1

            
120
1
		assert_eq!(
121
1
			System::account(&bob.account_id).data.free,
122
1
			balances_before.data.free + 100
123
1
		);
124
1
	});
125
1
}
126

            
127
#[test]
128
1
fn test_transact_xcm_create() {
129
1
	let (pairs, mut ext) = new_test_ext(1);
130
1
	let alice = &pairs[0];
131
1

            
132
1
	ext.execute_with(|| {
133
1
		assert_noop!(
134
1
			EthereumXcm::transact(
135
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
136
1
				xcm_erc20_creation_eip_2930_transaction()
137
1
			),
138
1
			DispatchErrorWithPostInfo {
139
1
				post_info: PostDispatchInfo {
140
1
					actual_weight: Some(Weight::zero()),
141
1
					pays_fee: Pays::Yes,
142
1
				},
143
1
				error: DispatchError::Other("Cannot convert xcm payload to known type"),
144
1
			}
145
1
		);
146
1
	});
147
1
}
148

            
149
#[test]
150
1
fn test_transact_xcm_evm_call_works() {
151
1
	let (pairs, mut ext) = new_test_ext(2);
152
1
	let alice = &pairs[0];
153
1
	let bob = &pairs[1];
154
1

            
155
1
	ext.execute_with(|| {
156
1
		let t = EIP2930UnsignedTransaction {
157
1
			nonce: U256::zero(),
158
1
			gas_price: U256::from(1),
159
1
			gas_limit: U256::from(0x100000),
160
1
			action: ethereum::TransactionAction::Create,
161
1
			value: U256::zero(),
162
1
			input: hex::decode(CONTRACT).unwrap(),
163
1
		}
164
1
		.sign(&alice.private_key, None);
165
1
		assert_ok!(Ethereum::execute(alice.address, &t, None, None));
166

            
167
1
		let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
168
1
		let foo = hex::decode("c2985578").unwrap();
169
1
		let bar = hex::decode("febb0f7e").unwrap();
170
1

            
171
1
		let _ = EthereumXcm::transact(
172
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
173
1
			xcm_evm_call_eip_2930_transaction(H160::from_slice(&contract_address), foo),
174
1
		)
175
1
		.expect("Failed to call `foo`");
176
1

            
177
1
		// Evm call failing still succesfully dispatched
178
1
		let _ = EthereumXcm::transact(
179
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
180
1
			xcm_evm_call_eip_2930_transaction(H160::from_slice(&contract_address), bar),
181
1
		)
182
1
		.expect("Failed to call `bar`");
183
1

            
184
1
		assert!(pallet_ethereum::Pending::<Test>::count() == 2);
185

            
186
		// Transaction is in Pending storage, with nonce 0 and status 1 (evm succeed).
187
1
		let (transaction_0, _, receipt_0) = &pallet_ethereum::Pending::<Test>::get(0).unwrap();
188
1
		match (transaction_0, receipt_0) {
189
1
			(&crate::Transaction::EIP2930(ref t), &crate::Receipt::EIP2930(ref r)) => {
190
1
				assert!(t.nonce == U256::from(0u8));
191
1
				assert!(r.status_code == 1u8);
192
			}
193
			_ => unreachable!(),
194
		}
195

            
196
		// Transaction is in Pending storage, with nonce 1 and status 0 (evm failed).
197
1
		let (transaction_1, _, receipt_1) = &pallet_ethereum::Pending::<Test>::get(1).unwrap();
198
1
		match (transaction_1, receipt_1) {
199
1
			(&crate::Transaction::EIP2930(ref t), &crate::Receipt::EIP2930(ref r)) => {
200
1
				assert!(t.nonce == U256::from(1u8));
201
1
				assert!(r.status_code == 0u8);
202
			}
203
			_ => unreachable!(),
204
		}
205
1
	});
206
1
}
207

            
208
#[test]
209
1
fn test_transact_xcm_validation_works() {
210
1
	let (pairs, mut ext) = new_test_ext(2);
211
1
	let alice = &pairs[0];
212
1
	let bob = &pairs[1];
213
1

            
214
1
	ext.execute_with(|| {
215
1
		// Not enough gas limit to cover the transaction cost.
216
1
		assert_noop!(
217
1
			EthereumXcm::transact(
218
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
219
1
				EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
220
1
					fee_payment: EthereumXcmFee::Manual(xcm_primitives::ManualEthereumXcmFee {
221
1
						gas_price: Some(U256::from(0)),
222
1
						max_fee_per_gas: None,
223
1
					}),
224
1
					gas_limit: U256::from(0x5207),
225
1
					action: ethereum::TransactionAction::Call(bob.address),
226
1
					value: U256::from(1),
227
1
					input: BoundedVec::<
228
1
						u8,
229
1
						ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>,
230
1
					>::try_from(vec![])
231
1
					.unwrap(),
232
1
					access_list: Some(vec![(H160::default(), vec![H256::default()])]),
233
1
				}),
234
1
			),
235
1
			DispatchErrorWithPostInfo {
236
1
				post_info: PostDispatchInfo {
237
1
					actual_weight: Some(Weight::zero()),
238
1
					pays_fee: Pays::Yes,
239
1
				},
240
1
				error: DispatchError::Other("Failed to validate ethereum transaction"),
241
1
			}
242
1
		);
243
1
	});
244
1
}
245

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

            
252
1
	ext.execute_with(|| {
253
1
		let r = EthereumXcm::transact_through_proxy(
254
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
255
1
			bob.address,
256
1
			xcm_evm_transfer_eip_2930_transaction(bob.address, U256::from(100)),
257
1
		);
258
1
		assert!(r.is_err());
259
1
		assert_eq!(
260
1
			r.unwrap_err().error,
261
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
262
1
		);
263
1
	});
264
1
}
265

            
266
#[test]
267
1
fn test_ensure_transact_xcm_trough_proxy_error() {
268
1
	let (pairs, mut ext) = new_test_ext(2);
269
1
	let alice = &pairs[0];
270
1
	let bob = &pairs[1];
271
1

            
272
1
	ext.execute_with(|| {
273
1
		let _ = Proxy::add_proxy_delegate(
274
1
			&bob.account_id,
275
1
			alice.account_id.clone(),
276
1
			ProxyType::NotAllowed,
277
1
			0,
278
1
		);
279
1
		let r = EthereumXcm::transact_through_proxy(
280
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
281
1
			bob.address,
282
1
			xcm_evm_transfer_eip_2930_transaction(bob.address, U256::from(100)),
283
1
		);
284
1
		assert!(r.is_err());
285
1
		assert_eq!(
286
1
			r.unwrap_err().error,
287
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
288
1
		);
289
1
	});
290
1
}
291

            
292
#[test]
293
1
fn test_ensure_transact_xcm_trough_proxy_ok() {
294
1
	let (pairs, mut ext) = new_test_ext(3);
295
1
	let alice = &pairs[0];
296
1
	let bob = &pairs[1];
297
1
	let charlie = &pairs[2];
298
1

            
299
1
	let allowed_proxies = vec![ProxyType::Any];
300

            
301
1
	for proxy in allowed_proxies.into_iter() {
302
1
		ext.execute_with(|| {
303
1
			let _ = Proxy::add_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
304
1
			let alice_before = System::account(&alice.account_id);
305
1
			let bob_before = System::account(&bob.account_id);
306
1
			let charlie_before = System::account(&charlie.account_id);
307
1

            
308
1
			let r = EthereumXcm::transact_through_proxy(
309
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
310
1
				bob.address,
311
1
				xcm_evm_transfer_eip_2930_transaction(charlie.address, U256::from(100)),
312
1
			);
313
1
			// Transact succeeded
314
1
			assert!(r.is_ok());
315

            
316
1
			let alice_after = System::account(&alice.account_id);
317
1
			let bob_after = System::account(&bob.account_id);
318
1
			let charlie_after = System::account(&charlie.account_id);
319
1

            
320
1
			// Alice remains unchanged
321
1
			assert_eq!(alice_before, alice_after);
322

            
323
			// Bob nonce was increased
324
1
			assert_eq!(bob_after.nonce, bob_before.nonce + 1);
325

            
326
			// Bob sent some funds without paying any fees
327
1
			assert_eq!(bob_after.data.free, bob_before.data.free - 100);
328

            
329
			// Charlie receive some funds
330
1
			assert_eq!(charlie_after.data.free, charlie_before.data.free + 100);
331

            
332
			// Clear proxy
333
			let _ =
334
1
				Proxy::remove_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
335
1
		});
336
1
	}
337
1
}
338

            
339
#[test]
340
1
fn test_global_nonce_incr() {
341
1
	let (pairs, mut ext) = new_test_ext(3);
342
1
	let alice = &pairs[0];
343
1
	let bob = &pairs[1];
344
1
	let charlie = &pairs[2];
345
1

            
346
1
	ext.execute_with(|| {
347
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
348

            
349
1
		EthereumXcm::transact(
350
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
351
1
			xcm_evm_transfer_eip_2930_transaction(charlie.address, U256::one()),
352
1
		)
353
1
		.expect("Failed to execute transaction from Alice to Charlie");
354
1

            
355
1
		assert_eq!(EthereumXcm::nonce(), U256::one());
356

            
357
1
		EthereumXcm::transact(
358
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
359
1
			xcm_evm_transfer_eip_2930_transaction(charlie.address, U256::one()),
360
1
		)
361
1
		.expect("Failed to execute transaction from Bob to Charlie");
362
1

            
363
1
		assert_eq!(EthereumXcm::nonce(), U256::from(2));
364
1
	});
365
1
}
366

            
367
#[test]
368
1
fn test_global_nonce_not_incr() {
369
1
	let (pairs, mut ext) = new_test_ext(2);
370
1
	let alice = &pairs[0];
371
1
	let bob = &pairs[1];
372
1

            
373
1
	ext.execute_with(|| {
374
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
375

            
376
1
		let access_list = Some(vec![(H160::default(), vec![H256::default()])]);
377
1

            
378
1
		let invalid_transaction_cost =
379
1
			EthereumXcmTransaction::V1(
380
1
				EthereumXcmTransactionV1 {
381
1
					fee_payment: EthereumXcmFee::Auto,
382
1
					gas_limit: U256::one(),
383
1
					action: ethereum::TransactionAction::Call(bob.address),
384
1
					value: U256::one(),
385
1
					input: BoundedVec::<
386
1
						u8,
387
1
						ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>,
388
1
					>::try_from(vec![])
389
1
					.unwrap(),
390
1
					access_list,
391
1
				},
392
1
			);
393
1

            
394
1
		EthereumXcm::transact(
395
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
396
1
			invalid_transaction_cost,
397
1
		)
398
1
		.expect_err("Failed to execute transaction from Alice to Bob");
399
1

            
400
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
401
1
	});
402
1
}
403

            
404
#[test]
405
1
fn test_transaction_hash_collision() {
406
1
	let (pairs, mut ext) = new_test_ext(3);
407
1
	let alice = &pairs[0];
408
1
	let bob = &pairs[1];
409
1
	let charlie = &pairs[2];
410
1

            
411
1
	ext.execute_with(|| {
412
1
		EthereumXcm::transact(
413
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
414
1
			xcm_evm_transfer_eip_2930_transaction(charlie.address, U256::one()),
415
1
		)
416
1
		.expect("Failed to execute transaction from Alice to Charlie");
417
1

            
418
1
		EthereumXcm::transact(
419
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
420
1
			xcm_evm_transfer_eip_2930_transaction(charlie.address, U256::one()),
421
1
		)
422
1
		.expect("Failed to execute transaction from Bob to Charlie");
423
1

            
424
1
		let mut hashes = pallet_ethereum::Pending::<Test>::iter_values()
425
2
			.map(|(tx, _, _)| tx.hash())
426
1
			.collect::<Vec<ethereum_types::H256>>();
427
1

            
428
1
		// Holds two transactions hashes
429
1
		assert_eq!(hashes.len(), 2);
430

            
431
1
		hashes.dedup();
432
1

            
433
1
		// Still holds two transactions hashes after removing potential consecutive repeated values.
434
1
		assert_eq!(hashes.len(), 2);
435
1
	});
436
1
}