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

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

            
77
1
fn xcm_erc20_creation_eip_1559_transaction() -> EthereumXcmTransaction {
78
1
	EthereumXcmTransaction::V1(EthereumXcmTransactionV1 {
79
1
		fee_payment: EthereumXcmFee::Auto,
80
1

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

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

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

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

            
114
#[test]
115
1
fn test_transact_xcm_create() {
116
1
	let (pairs, mut ext) = new_test_ext(1);
117
1
	let alice = &pairs[0];
118
1

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

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

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

            
155
1
		let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
156
1
		let foo = hex::decode("c2985578").unwrap();
157
1
		let bar = hex::decode("febb0f7e").unwrap();
158
1

            
159
1
		let _ = EthereumXcm::transact(
160
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
161
1
			xcm_evm_call_eip_1559_transaction(H160::from_slice(&contract_address), foo),
162
1
		)
163
1
		.expect("Failed to call `foo`");
164
1

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

            
172
1
		assert!(pallet_ethereum::Pending::<Test>::count() == 2);
173

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

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

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

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

            
234
#[test]
235
1
fn test_ensure_transact_xcm_trough_no_proxy_error() {
236
1
	let (pairs, mut ext) = new_test_ext(2);
237
1
	let alice = &pairs[0];
238
1
	let bob = &pairs[1];
239
1

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

            
254
#[test]
255
1
fn test_ensure_transact_xcm_trough_proxy_error() {
256
1
	let (pairs, mut ext) = new_test_ext(2);
257
1
	let alice = &pairs[0];
258
1
	let bob = &pairs[1];
259
1

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

            
280
#[test]
281
1
fn test_ensure_transact_xcm_trough_proxy_ok() {
282
1
	let (pairs, mut ext) = new_test_ext(3);
283
1
	let alice = &pairs[0];
284
1
	let bob = &pairs[1];
285
1
	let charlie = &pairs[2];
286
1

            
287
1
	let allowed_proxies = vec![ProxyType::Any];
288

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

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

            
304
1
			let alice_after = System::account(&alice.account_id);
305
1
			let bob_after = System::account(&bob.account_id);
306
1
			let charlie_after = System::account(&charlie.account_id);
307
1

            
308
1
			// Alice remains unchanged
309
1
			assert_eq!(alice_before, alice_after);
310

            
311
			// Bob nonce was increased
312
1
			assert_eq!(bob_after.nonce, bob_before.nonce + 1);
313

            
314
			// Bob sent some funds without paying any fees
315
1
			assert_eq!(bob_after.data.free, bob_before.data.free - 100);
316

            
317
			// Charlie receive some funds
318
1
			assert_eq!(charlie_after.data.free, charlie_before.data.free + 100);
319

            
320
			// Clear proxy
321
			let _ =
322
1
				Proxy::remove_proxy_delegate(&bob.account_id, alice.account_id.clone(), proxy, 0);
323
1
		});
324
1
	}
325
1
}
326

            
327
#[test]
328
1
fn test_global_nonce_incr() {
329
1
	let (pairs, mut ext) = new_test_ext(3);
330
1
	let alice = &pairs[0];
331
1
	let bob = &pairs[1];
332
1
	let charlie = &pairs[2];
333
1

            
334
1
	ext.execute_with(|| {
335
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
336

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

            
343
1
		assert_eq!(EthereumXcm::nonce(), U256::one());
344

            
345
1
		EthereumXcm::transact(
346
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
347
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
348
1
		)
349
1
		.expect("Failed to execute transaction from Bob to Charlie");
350
1

            
351
1
		assert_eq!(EthereumXcm::nonce(), U256::from(2));
352
1
	});
353
1
}
354

            
355
#[test]
356
1
fn test_global_nonce_not_incr() {
357
1
	let (pairs, mut ext) = new_test_ext(2);
358
1
	let alice = &pairs[0];
359
1
	let bob = &pairs[1];
360
1

            
361
1
	ext.execute_with(|| {
362
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
363

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

            
380
1
		EthereumXcm::transact(
381
1
			RawOrigin::XcmEthereumTransaction(alice.address).into(),
382
1
			invalid_transaction_cost,
383
1
		)
384
1
		.expect_err("Failed to execute transaction from Alice to Bob");
385
1

            
386
1
		assert_eq!(EthereumXcm::nonce(), U256::zero());
387
1
	});
388
1
}
389

            
390
#[test]
391
1
fn test_transaction_hash_collision() {
392
1
	let (pairs, mut ext) = new_test_ext(3);
393
1
	let alice = &pairs[0];
394
1
	let bob = &pairs[1];
395
1
	let charlie = &pairs[2];
396
1

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

            
404
1
		EthereumXcm::transact(
405
1
			RawOrigin::XcmEthereumTransaction(bob.address).into(),
406
1
			xcm_evm_transfer_eip_1559_transaction(charlie.address, U256::one()),
407
1
		)
408
1
		.expect("Failed to execute transaction from Bob to Charlie");
409
1

            
410
1
		let mut hashes = pallet_ethereum::Pending::<Test>::iter_values()
411
2
			.map(|(tx, _, _)| tx.hash())
412
1
			.collect::<Vec<ethereum_types::H256>>();
413
1

            
414
1
		// Holds two transactions hashes
415
1
		assert_eq!(hashes.len(), 2);
416

            
417
1
		hashes.dedup();
418
1

            
419
1
		// Still holds two transactions hashes after removing potential consecutive repeated values.
420
1
		assert_eq!(hashes.len(), 2);
421
1
	});
422
1
}