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

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

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

            
65
2
fn xcm_evm_call_eip_1559_transaction(destination: H160, input: Vec<u8>) -> EthereumXcmTransaction {
66
2
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
67
2
		fee_payment: EthereumXcmFee::Auto,
68
2
		gas_limit: U256::from(0x100000),
69
2
		action: ethereum::TransactionAction::Call(destination),
70
2
		value: U256::zero(),
71
2
		input:
72
2
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
73
2
				input,
74
2
			)
75
2
			.unwrap(),
76
2
		access_list: None,
77
2
	})
78
2
}
79

            
80
1
fn xcm_erc20_creation_eip_1559_transaction() -> EthereumXcmTransaction {
81
1
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
82
1
		fee_payment: EthereumXcmFee::Auto,
83
1

            
84
1
		gas_limit: U256::from(0x100000),
85
1
		action: ethereum::TransactionAction::Create,
86
1
		value: U256::zero(),
87
1
		input:
88
1
			BoundedVec::<u8, ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>>::try_from(
89
1
				hex::decode(CONTRACT).unwrap(),
90
1
			)
91
1
			.unwrap(),
92
1
		access_list: None,
93
1
	})
94
1
}
95

            
96
#[test]
97
1
fn test_transact_xcm_evm_transfer() {
98
1
	let (pairs, mut ext) = new_test_ext(2);
99
1
	let alice = &pairs[0];
100
1
	let bob = &pairs[1];
101
1

            
102
1
	ext.execute_with(|| {
103
1
		let balances_before = System::account(&bob.account_id);
104
1
		EthereumXcm::transact(
105
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
106
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
107
1
		)
108
1
		.expect("Failed to execute transaction");
109
1

            
110
1
		assert_eq!(
111
1
			System::account(&bob.account_id).data.free,
112
1
			balances_before.data.free + 100
113
1
		);
114
1
	});
115
1
}
116

            
117
#[test]
118
1
fn test_transact_xcm_create() {
119
1
	let (pairs, mut ext) = new_test_ext(1);
120
1
	let alice = &pairs[0];
121
1

            
122
1
	ext.execute_with(|| {
123
1
		assert_noop!(
124
1
			EthereumXcm::transact(
125
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
126
1
				xcm_erc20_creation_eip_1559_transaction()
127
1
			),
128
1
			DispatchErrorWithPostInfo {
129
1
				post_info: PostDispatchInfo {
130
1
					actual_weight: Some(Weight::zero()),
131
1
					pays_fee: Pays::Yes,
132
1
				},
133
1
				error: DispatchError::Other("Cannot convert xcm payload to known type"),
134
1
			}
135
1
		);
136
1
	});
137
1
}
138

            
139
#[test]
140
1
fn test_transact_xcm_evm_call_works() {
141
1
	let (pairs, mut ext) = new_test_ext(2);
142
1
	let alice = &pairs[0];
143
1
	let bob = &pairs[1];
144
1

            
145
1
	ext.execute_with(|| {
146
1
		let t = EthereumXcmTransactionV1 {
147
1
			gas_limit: U256::from(0x100000),
148
1
			fee_payment: EthereumXcmFee::Auto,
149
1
			action: ethereum::TransactionAction::Create,
150
1
			value: U256::zero(),
151
1
			input: hex::decode(CONTRACT).unwrap().try_into().unwrap(),
152
1
			access_list: None,
153
1
		}
154
1
		.into_transaction(U256::zero(), Default::default(), true)
155
1
		.unwrap();
156
1
		assert!(matches!(t, TransactionV3::EIP1559(_)));
157
1
		assert_ok!(Ethereum::execute(alice.address, &t, None, None));
158

            
159
1
		let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
160
1
		let foo = hex::decode("c2985578").unwrap();
161
1
		let bar = hex::decode("febb0f7e").unwrap();
162
1

            
163
1
		let _ = EthereumXcm::transact(
164
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
165
1
			xcm_evm_call_eip_1559_transaction(H160::from_slice(&contract_address), foo),
166
1
		)
167
1
		.expect("Failed to call `foo`");
168
1

            
169
1
		// Evm call failing still succesfully dispatched
170
1
		let _ = EthereumXcm::transact(
171
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
172
1
			xcm_evm_call_eip_1559_transaction(H160::from_slice(&contract_address), bar),
173
1
		)
174
1
		.expect("Failed to call `bar`");
175
1

            
176
1
		assert!(pallet_ethereum::Pending::<Test>::count() == 2);
177

            
178
		// Transaction is in Pending storage, with nonce 0 and status 1 (evm succeed).
179
1
		let (transaction_0, _, receipt_0) = &pallet_ethereum::Pending::<Test>::get(0).unwrap();
180
1
		match (transaction_0, receipt_0) {
181
1
			(&crate::Transaction::EIP1559(ref t), &crate::Receipt::EIP1559(ref r)) => {
182
1
				assert!(t.nonce == U256::from(0u8));
183
1
				assert!(r.status_code == 1u8);
184
			}
185
			_ => unreachable!(),
186
		}
187

            
188
		// Transaction is in Pending storage, with nonce 1 and status 0 (evm failed).
189
1
		let (transaction_1, _, receipt_1) = &pallet_ethereum::Pending::<Test>::get(1).unwrap();
190
1
		match (transaction_1, receipt_1) {
191
1
			(&crate::Transaction::EIP1559(ref t), &crate::Receipt::EIP1559(ref r)) => {
192
1
				assert!(t.nonce == U256::from(1u8));
193
1
				assert!(r.status_code == 0u8);
194
			}
195
			_ => unreachable!(),
196
		}
197
1
	});
198
1
}
199

            
200
#[test]
201
1
fn test_transact_xcm_validation_works() {
202
1
	let (pairs, mut ext) = new_test_ext(2);
203
1
	let alice = &pairs[0];
204
1
	let bob = &pairs[1];
205
1

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

            
238
#[test]
239
1
fn test_ensure_transact_xcm_trough_no_proxy_error() {
240
1
	let (pairs, mut ext) = new_test_ext(2);
241
1
	let alice = &pairs[0];
242
1
	let bob = &pairs[1];
243
1

            
244
1
	ext.execute_with(|| {
245
1
		let r = EthereumXcm::transact_through_proxy(
246
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
247
1
			bob.address,
248
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
249
1
		);
250
1
		assert!(r.is_err());
251
1
		assert_eq!(
252
1
			r.unwrap_err().error,
253
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
254
1
		);
255
1
	});
256
1
}
257

            
258
#[test]
259
1
fn test_ensure_transact_xcm_trough_proxy_error() {
260
1
	let (pairs, mut ext) = new_test_ext(2);
261
1
	let alice = &pairs[0];
262
1
	let bob = &pairs[1];
263
1

            
264
1
	ext.execute_with(|| {
265
1
		let _ = Proxy::add_proxy_delegate(
266
1
			&bob.account_id,
267
1
			alice.account_id.clone(),
268
1
			ProxyType::NotAllowed,
269
1
			0,
270
1
		);
271
1
		let r = EthereumXcm::transact_through_proxy(
272
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
273
1
			bob.address,
274
1
			xcm_evm_transfer_eip_1559_transaction(bob.address, U256::from(100)),
275
1
		);
276
1
		assert!(r.is_err());
277
1
		assert_eq!(
278
1
			r.unwrap_err().error,
279
1
			sp_runtime::DispatchError::Other("proxy error: expected `ProxyType::Any`"),
280
1
		);
281
1
	});
282
1
}
283

            
284
#[test]
285
1
fn test_ensure_transact_xcm_trough_proxy_ok() {
286
1
	let (pairs, mut ext) = new_test_ext(3);
287
1
	let alice = &pairs[0];
288
1
	let bob = &pairs[1];
289
1
	let charlie = &pairs[2];
290
1

            
291
1
	let allowed_proxies = vec![ProxyType::Any];
292

            
293
1
	for proxy in allowed_proxies.into_iter() {
294
1
		ext.execute_with(|| {
295
1
			let _ = Proxy::add_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
296
1
			let alice_before = System::account(&alice.account_id);
297
1
			let bob_before = System::account(&bob.account_id);
298
1
			let charlie_before = System::account(&charlie.account_id);
299
1

            
300
1
			let r = EthereumXcm::transact_through_proxy(
301
1
				RawOrigin::XcmEthereumTransaction(alice.address).into(),
302
1
				bob.address,
303
1
				xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::from(100)),
304
1
			);
305
1
			// Transact succeeded
306
1
			assert!(r.is_ok());
307

            
308
1
			let alice_after = System::account(&alice.account_id);
309
1
			let bob_after = System::account(&bob.account_id);
310
1
			let charlie_after = System::account(&charlie.account_id);
311
1

            
312
1
			// Alice remains unchanged
313
1
			assert_eq!(alice_before, alice_after);
314

            
315
			// Bob nonce was increased
316
1
			assert_eq!(bob_after.nonce, bob_before.nonce + 1);
317

            
318
			// Bob sent some funds without paying any fees
319
1
			assert_eq!(bob_after.data.free, bob_before.data.free - 100);
320

            
321
			// Charlie receive some funds
322
1
			assert_eq!(charlie_after.data.free, charlie_before.data.free + 100);
323

            
324
			// Clear proxy
325
			let _ =
326
1
				Proxy::remove_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
327
1
		});
328
1
	}
329
1
}
330

            
331
#[test]
332
1
fn test_global_nonce_incr() {
333
1
	let (pairs, mut ext) = new_test_ext(3);
334
1
	let alice = &pairs[0];
335
1
	let bob = &pairs[1];
336
1
	let charlie = &pairs[2];
337
1

            
338
1
	ext.execute_with(|| {
339
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
340

            
341
1
		EthereumXcm::transact(
342
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
343
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
344
1
		)
345
1
		.expect("Failed to execute transaction from Alice to Charlie");
346
1

            
347
1
		assert_eq!(EthereumXcm::nonce(), U256::one());
348

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

            
355
1
		assert_eq!(EthereumXcm::nonce(), U256::from(2));
356
1
	});
357
1
}
358

            
359
#[test]
360
1
fn test_global_nonce_not_incr() {
361
1
	let (pairs, mut ext) = new_test_ext(2);
362
1
	let alice = &pairs[0];
363
1
	let bob = &pairs[1];
364
1

            
365
1
	ext.execute_with(|| {
366
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
367

            
368
1
		let invalid_transaction_cost =
369
1
			EthereumXcmTransaction::V1(
370
1
				EthereumXcmTransactionV1 {
371
1
					fee_payment: EthereumXcmFee::Auto,
372
1
					gas_limit: U256::one(),
373
1
					action: ethereum::TransactionAction::Call(bob.address),
374
1
					value: U256::one(),
375
1
					input: BoundedVec::<
376
1
						u8,
377
1
						ConstU32<{ xcm_primitives::MAX_ETHEREUM_XCM_INPUT_SIZE }>,
378
1
					>::try_from(vec![])
379
1
					.unwrap(),
380
1
					access_list: None,
381
1
				},
382
1
			);
383
1

            
384
1
		EthereumXcm::transact(
385
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
386
1
			invalid_transaction_cost,
387
1
		)
388
1
		.expect_err("Failed to execute transaction from Alice to Bob");
389
1

            
390
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
391
1
	});
392
1
}
393

            
394
#[test]
395
1
fn test_transaction_hash_collision() {
396
1
	let (pairs, mut ext) = new_test_ext(3);
397
1
	let alice = &pairs[0];
398
1
	let bob = &pairs[1];
399
1
	let charlie = &pairs[2];
400
1

            
401
1
	ext.execute_with(|| {
402
1
		EthereumXcm::transact(
403
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
404
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
405
1
		)
406
1
		.expect("Failed to execute transaction from Alice to Charlie");
407
1

            
408
1
		EthereumXcm::transact(
409
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
410
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
411
1
		)
412
1
		.expect("Failed to execute transaction from Bob to Charlie");
413
1

            
414
1
		let mut hashes = pallet_ethereum::Pending::<Test>::iter_values()
415
2
			.map(|(tx, _, _)| tx.hash())
416
1
			.collect::<Vec<ethereum_types::H256>>();
417
1

            
418
1
		// Holds two transactions hashes
419
1
		assert_eq!(hashes.len(), 2);
420

            
421
1
		hashes.dedup();
422
1

            
423
1
		// Still holds two transactions hashes after removing potential consecutive repeated values.
424
1
		assert_eq!(hashes.len(), 2);
425
1
	});
426
1
}