referrerpolicy=no-referrer-when-downgrade

pallet_xcm/
precompiles.rs

1// This file is part of Polkadot.
2
3// Polkadot is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7
8// Polkadot is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU General Public License for more details.
12
13// You should have received a copy of the GNU General Public License
14// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
15
16use crate::{Config, VersionedLocation, VersionedXcm, Weight, WeightInfo};
17use alloc::vec::Vec;
18use codec::{DecodeAll, DecodeLimit};
19use core::{fmt, marker::PhantomData, num::NonZero};
20use pallet_revive::{
21	precompiles::{
22		alloy::{self, sol_types::SolValue},
23		AddressMatcher, Error, Ext, Precompile,
24	},
25	DispatchInfo, Origin,
26};
27use tracing::error;
28use xcm::{v5, IdentifyVersion, MAX_XCM_DECODE_DEPTH};
29use xcm_executor::traits::WeightBounds;
30
31alloy::sol!("src/precompiles/IXcm.sol");
32use IXcm::IXcmCalls;
33
34const LOG_TARGET: &str = "xcm::precompiles";
35
36fn revert(error: &impl fmt::Debug, message: &str) -> Error {
37	error!(target: LOG_TARGET, ?error, "{}", message);
38	Error::Revert(message.into())
39}
40
41// We don't allow XCM versions older than 5.
42fn ensure_xcm_version<V: IdentifyVersion>(input: &V) -> Result<(), Error> {
43	let version = input.identify_version();
44	if version < v5::VERSION {
45		return Err(Error::Revert("Only XCM version 5 and onwards are supported.".into()));
46	}
47	Ok(())
48}
49
50pub struct XcmPrecompile<T>(PhantomData<T>);
51
52impl<Runtime> Precompile for XcmPrecompile<Runtime>
53where
54	Runtime: crate::Config + pallet_revive::Config,
55{
56	type T = Runtime;
57	const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(10).unwrap());
58	const HAS_CONTRACT_INFO: bool = false;
59	type Interface = IXcm::IXcmCalls;
60
61	fn call(
62		_address: &[u8; 20],
63		input: &Self::Interface,
64		env: &mut impl Ext<T = Self::T>,
65	) -> Result<Vec<u8>, Error> {
66		let origin = env.caller();
67		let frame_origin = match origin {
68			Origin::Root => frame_system::RawOrigin::Root.into(),
69			Origin::Signed(account_id) =>
70				frame_system::RawOrigin::Signed(account_id.clone()).into(),
71		};
72
73		match input {
74			IXcmCalls::send(IXcm::sendCall { destination, message }) => {
75				let _ = env.charge(<Runtime as Config>::WeightInfo::send())?;
76
77				let final_destination = VersionedLocation::decode_all(&mut &destination[..])
78					.map_err(|error| {
79						revert(&error, "XCM send failed: Invalid destination format")
80					})?;
81
82				ensure_xcm_version(&final_destination)?;
83
84				let final_message = VersionedXcm::<()>::decode_all_with_depth_limit(
85					MAX_XCM_DECODE_DEPTH,
86					&mut &message[..],
87				)
88				.map_err(|error| revert(&error, "XCM send failed: Invalid message format"))?;
89
90				ensure_xcm_version(&final_message)?;
91
92				crate::Pallet::<Runtime>::send(
93					frame_origin,
94					final_destination.into(),
95					final_message.into(),
96				)
97				.map(|_| Vec::new())
98				.map_err(|error| {
99					revert(
100						&error,
101						"XCM send failed: destination or message format may be incompatible",
102					)
103				})
104			},
105			IXcmCalls::execute(IXcm::executeCall { message, weight }) => {
106				let max_weight = Weight::from_parts(weight.refTime, weight.proofSize);
107				let weight_to_charge =
108					max_weight.saturating_add(<Runtime as Config>::WeightInfo::execute());
109				let charged_amount = env.charge(weight_to_charge)?;
110
111				let final_message = VersionedXcm::decode_all_with_depth_limit(
112					MAX_XCM_DECODE_DEPTH,
113					&mut &message[..],
114				)
115				.map_err(|error| revert(&error, "XCM execute failed: Invalid message format"))?;
116
117				ensure_xcm_version(&final_message)?;
118
119				let result = crate::Pallet::<Runtime>::execute(
120					frame_origin,
121					final_message.into(),
122					max_weight,
123				);
124
125				let pre = DispatchInfo {
126					call_weight: weight_to_charge,
127					extension_weight: Weight::zero(),
128					..Default::default()
129				};
130
131				// Adjust gas using actual weight or fallback to initially charged weight
132				let actual_weight = frame_support::dispatch::extract_actual_weight(&result, &pre);
133				env.adjust_gas(charged_amount, actual_weight);
134
135				result.map(|_| Vec::new()).map_err(|error| {
136					revert(
137							&error,
138							"XCM execute failed: message may be invalid or execution constraints not satisfied"
139						)
140				})
141			},
142			IXcmCalls::weighMessage(IXcm::weighMessageCall { message }) => {
143				let _ = env.charge(<Runtime as Config>::WeightInfo::weigh_message())?;
144
145				let converted_message = VersionedXcm::decode_all_with_depth_limit(
146					MAX_XCM_DECODE_DEPTH,
147					&mut &message[..],
148				)
149				.map_err(|error| revert(&error, "XCM weightMessage: Invalid message format"))?;
150
151				ensure_xcm_version(&converted_message)?;
152
153				let mut final_message = converted_message.try_into().map_err(|error| {
154					revert(&error, "XCM weightMessage: Conversion to Xcm failed")
155				})?;
156
157				let weight = <<Runtime>::Weigher>::weight(&mut final_message, Weight::MAX)
158					.map_err(|error| {
159						revert(&error, "XCM weightMessage: Failed to calculate weight")
160					})?;
161
162				let final_weight =
163					IXcm::Weight { proofSize: weight.proof_size(), refTime: weight.ref_time() };
164
165				Ok(final_weight.abi_encode())
166			},
167		}
168	}
169}
170
171#[cfg(test)]
172mod test {
173	use crate::{
174		mock::*,
175		precompiles::IXcm::{self, weighMessageCall},
176		VersionedLocation, VersionedXcm,
177	};
178	use frame_support::traits::Currency;
179	use pallet_revive::{
180		precompiles::{
181			alloy::{
182				hex,
183				sol_types::{SolInterface, SolValue},
184			},
185			H160,
186		},
187		DepositLimit, U256,
188	};
189	use polkadot_parachain_primitives::primitives::Id as ParaId;
190	use sp_runtime::traits::AccountIdConversion;
191	use xcm::{prelude::*, v3, v4};
192
193	const BOB: AccountId = AccountId::new([1u8; 32]);
194	const CHARLIE: AccountId = AccountId::new([2u8; 32]);
195	const SEND_AMOUNT: u128 = 10;
196	const CUSTOM_INITIAL_BALANCE: u128 = 100_000_000_000u128;
197
198	#[test]
199	fn test_xcm_send_precompile_works() {
200		use codec::Encode;
201
202		let balances = vec![
203			(ALICE, CUSTOM_INITIAL_BALANCE),
204			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
205		];
206		new_test_ext_with_balances(balances).execute_with(|| {
207			let xcm_precompile_addr = H160::from(
208				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
209			);
210
211			let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
212			let message = Xcm(vec![
213				ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
214				ClearOrigin,
215				buy_execution((Parent, SEND_AMOUNT)),
216				DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
217			]);
218
219			let versioned_dest: VersionedLocation = RelayLocation::get().into();
220			let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
221
222			let xcm_send_params = IXcm::sendCall {
223				destination: versioned_dest.encode().into(),
224				message: versioned_message.encode().into(),
225			};
226			let call = IXcm::IXcmCalls::send(xcm_send_params);
227			let encoded_call = call.abi_encode();
228
229			let result = pallet_revive::Pallet::<Test>::bare_call(
230				RuntimeOrigin::signed(ALICE),
231				xcm_precompile_addr,
232				U256::zero(),
233				Weight::MAX,
234				DepositLimit::UnsafeOnlyForDryRun,
235				encoded_call,
236			);
237			assert!(result.result.is_ok());
238			let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
239				.into_iter()
240				.chain(message.0.clone().into_iter())
241				.collect());
242			assert_eq!(sent_xcm(), vec![(Here.into(), sent_message)]);
243		});
244	}
245
246	#[test]
247	fn test_xcm_send_precompile_to_parachain() {
248		use codec::Encode;
249
250		let balances = vec![
251			(ALICE, CUSTOM_INITIAL_BALANCE),
252			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
253		];
254		new_test_ext_with_balances(balances).execute_with(|| {
255			let xcm_precompile_addr = H160::from(
256				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
257			);
258
259			let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
260			let message = Xcm(vec![
261				ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
262				ClearOrigin,
263				buy_execution((Parent, SEND_AMOUNT)),
264				DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
265			]);
266
267			let destination: VersionedLocation = Parachain(OTHER_PARA_ID).into();
268			let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
269
270			let xcm_send_params = IXcm::sendCall {
271				destination: destination.encode().into(),
272				message: versioned_message.encode().into(),
273			};
274			let call = IXcm::IXcmCalls::send(xcm_send_params);
275			let encoded_call = call.abi_encode();
276
277			let result = pallet_revive::Pallet::<Test>::bare_call(
278				RuntimeOrigin::signed(ALICE),
279				xcm_precompile_addr,
280				U256::zero(),
281				Weight::MAX,
282				DepositLimit::UnsafeOnlyForDryRun,
283				encoded_call,
284			);
285
286			assert!(result.result.is_ok());
287			let sent_message = Xcm(Some(DescendOrigin(sender.clone().try_into().unwrap()))
288				.into_iter()
289				.chain(message.0.clone().into_iter())
290				.collect());
291			assert_eq!(sent_xcm(), vec![(Parachain(OTHER_PARA_ID).into(), sent_message)]);
292		});
293	}
294
295	#[test]
296	fn test_xcm_send_precompile_fails() {
297		use codec::Encode;
298
299		let balances = vec![
300			(ALICE, CUSTOM_INITIAL_BALANCE),
301			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
302		];
303		new_test_ext_with_balances(balances).execute_with(|| {
304			let xcm_precompile_addr = H160::from(
305				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
306			);
307
308			let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
309			let message = Xcm(vec![
310				ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
311				buy_execution((Parent, SEND_AMOUNT)),
312				DepositAsset { assets: AllCounted(1).into(), beneficiary: sender },
313			]);
314
315			let destination: VersionedLocation = VersionedLocation::from(Location::ancestor(8));
316			let versioned_message: VersionedXcm<()> = VersionedXcm::from(message.clone());
317
318			let xcm_send_params = IXcm::sendCall {
319				destination: destination.encode().into(),
320				message: versioned_message.encode().into(),
321			};
322			let call = IXcm::IXcmCalls::send(xcm_send_params);
323			let encoded_call = call.abi_encode();
324
325			let result = pallet_revive::Pallet::<Test>::bare_call(
326				RuntimeOrigin::signed(ALICE),
327				xcm_precompile_addr,
328				U256::zero(),
329				Weight::MAX,
330				DepositLimit::UnsafeOnlyForDryRun,
331				encoded_call,
332			);
333			let return_value = match result.result {
334				Ok(value) => value,
335				Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
336			};
337			assert!(return_value.did_revert());
338		});
339	}
340
341	#[test]
342	fn send_fails_on_old_location_version() {
343		use codec::Encode;
344
345		let balances = vec![
346			(ALICE, CUSTOM_INITIAL_BALANCE),
347			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
348		];
349		new_test_ext_with_balances(balances).execute_with(|| {
350			let xcm_precompile_addr = H160::from(
351				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
352			);
353
354			let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
355			let message = Xcm(vec![
356				ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
357				ClearOrigin,
358				buy_execution((Parent, SEND_AMOUNT)),
359				DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
360			]);
361
362			// V4 location is old and will fail.
363			let destination: VersionedLocation =
364				VersionedLocation::V4(v4::Junction::Parachain(OTHER_PARA_ID).into());
365			let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::from(message.clone());
366
367			let xcm_send_params = IXcm::sendCall {
368				destination: destination.encode().into(),
369				message: versioned_message.encode().into(),
370			};
371			let call = IXcm::IXcmCalls::send(xcm_send_params);
372			let encoded_call = call.abi_encode();
373
374			let result = pallet_revive::Pallet::<Test>::bare_call(
375				RuntimeOrigin::signed(ALICE),
376				xcm_precompile_addr,
377				U256::zero(),
378				Weight::MAX,
379				DepositLimit::UnsafeOnlyForDryRun,
380				encoded_call,
381			);
382			let return_value = match result.result {
383				Ok(value) => value,
384				Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
385			};
386			assert!(return_value.did_revert());
387
388			// V3 also fails.
389			let destination: VersionedLocation =
390				VersionedLocation::V3(v3::Junction::Parachain(OTHER_PARA_ID).into());
391			let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::from(message);
392
393			let xcm_send_params = IXcm::sendCall {
394				destination: destination.encode().into(),
395				message: versioned_message.encode().into(),
396			};
397			let call = IXcm::IXcmCalls::send(xcm_send_params);
398			let encoded_call = call.abi_encode();
399
400			let result = pallet_revive::Pallet::<Test>::bare_call(
401				RuntimeOrigin::signed(ALICE),
402				xcm_precompile_addr,
403				U256::zero(),
404				Weight::MAX,
405				DepositLimit::UnsafeOnlyForDryRun,
406				encoded_call,
407			);
408			let return_value = match result.result {
409				Ok(value) => value,
410				Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
411			};
412			assert!(return_value.did_revert());
413		});
414	}
415
416	#[test]
417	fn send_fails_on_old_xcm_version() {
418		use codec::Encode;
419
420		let balances = vec![
421			(ALICE, CUSTOM_INITIAL_BALANCE),
422			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
423		];
424		new_test_ext_with_balances(balances).execute_with(|| {
425			let xcm_precompile_addr = H160::from(
426				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
427			);
428
429			let sender: Location = AccountId32 { network: None, id: ALICE.into() }.into();
430			let message = Xcm(vec![
431				ReserveAssetDeposited((Parent, SEND_AMOUNT).into()),
432				ClearOrigin,
433				buy_execution((Parent, SEND_AMOUNT)),
434				DepositAsset { assets: AllCounted(1).into(), beneficiary: sender.clone() },
435			]);
436			// V4 is old and fails.
437			let v4_message: v4::Xcm<RuntimeCall> = message.try_into().unwrap();
438
439			let destination: VersionedLocation = Parachain(OTHER_PARA_ID).into();
440			let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::V4(v4_message.clone());
441
442			let xcm_send_params = IXcm::sendCall {
443				destination: destination.encode().into(),
444				message: versioned_message.encode().into(),
445			};
446			let call = IXcm::IXcmCalls::send(xcm_send_params);
447			let encoded_call = call.abi_encode();
448
449			let result = pallet_revive::Pallet::<Test>::bare_call(
450				RuntimeOrigin::signed(ALICE),
451				xcm_precompile_addr,
452				U256::zero(),
453				Weight::MAX,
454				DepositLimit::UnsafeOnlyForDryRun,
455				encoded_call,
456			);
457			let return_value = match result.result {
458				Ok(value) => value,
459				Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
460			};
461			assert!(return_value.did_revert());
462
463			// With V3 it also fails.
464			let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
465
466			let destination: VersionedLocation = Parachain(OTHER_PARA_ID).into();
467			let versioned_message: VersionedXcm<RuntimeCall> = VersionedXcm::V3(v3_message);
468
469			let xcm_send_params = IXcm::sendCall {
470				destination: destination.encode().into(),
471				message: versioned_message.encode().into(),
472			};
473			let call = IXcm::IXcmCalls::send(xcm_send_params);
474			let encoded_call = call.abi_encode();
475
476			let result = pallet_revive::Pallet::<Test>::bare_call(
477				RuntimeOrigin::signed(ALICE),
478				xcm_precompile_addr,
479				U256::zero(),
480				Weight::MAX,
481				DepositLimit::UnsafeOnlyForDryRun,
482				encoded_call,
483			);
484			let return_value = match result.result {
485				Ok(value) => value,
486				Err(err) => panic!("XcmSendPrecompile call failed with error: {err:?}"),
487			};
488			assert!(return_value.did_revert());
489		});
490	}
491
492	#[test]
493	fn test_xcm_execute_precompile_works() {
494		use codec::Encode;
495
496		let balances = vec![
497			(ALICE, CUSTOM_INITIAL_BALANCE),
498			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
499		];
500		new_test_ext_with_balances(balances).execute_with(|| {
501			let xcm_precompile_addr = H160::from(
502				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
503			);
504
505			let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
506			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
507
508			let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
509				WithdrawAsset((Here, SEND_AMOUNT).into()),
510				buy_execution((Here, SEND_AMOUNT)),
511				DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
512			]));
513
514			let weight_params = weighMessageCall { message: message.encode().into() };
515			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
516			let encoded_weight_call = weight_call.abi_encode();
517
518			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
519				RuntimeOrigin::signed(ALICE),
520				xcm_precompile_addr,
521				U256::zero(),
522				Weight::MAX,
523				DepositLimit::UnsafeOnlyForDryRun,
524				encoded_weight_call,
525			);
526
527			let weight_result = match xcm_weight_results.result {
528				Ok(value) => value,
529				Err(err) =>
530					panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
531			};
532
533			let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
534				.expect("XcmExecutePrecompile Failed to decode weight");
535
536			let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
537			let call = IXcm::IXcmCalls::execute(xcm_execute_params);
538			let encoded_call = call.abi_encode();
539
540			let result = pallet_revive::Pallet::<Test>::bare_call(
541				RuntimeOrigin::signed(ALICE),
542				xcm_precompile_addr,
543				U256::zero(),
544				Weight::MAX,
545				DepositLimit::UnsafeOnlyForDryRun,
546				encoded_call,
547			);
548
549			assert!(result.result.is_ok());
550			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE - SEND_AMOUNT);
551			assert_eq!(Balances::total_balance(&BOB), SEND_AMOUNT);
552		});
553	}
554
555	#[test]
556	fn test_xcm_execute_precompile_different_beneficiary() {
557		use codec::Encode;
558
559		let balances = vec![(ALICE, CUSTOM_INITIAL_BALANCE), (CHARLIE, CUSTOM_INITIAL_BALANCE)];
560		new_test_ext_with_balances(balances).execute_with(|| {
561			let xcm_precompile_addr = H160::from(
562				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
563			);
564
565			let dest: Location = Junction::AccountId32 { network: None, id: CHARLIE.into() }.into();
566			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
567
568			let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
569				WithdrawAsset((Here, SEND_AMOUNT).into()),
570				buy_execution((Here, SEND_AMOUNT)),
571				DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
572			]));
573
574			let weight_params = weighMessageCall { message: message.encode().into() };
575			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
576			let encoded_weight_call = weight_call.abi_encode();
577
578			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
579				RuntimeOrigin::signed(ALICE),
580				xcm_precompile_addr,
581				U256::zero(),
582				Weight::MAX,
583				DepositLimit::UnsafeOnlyForDryRun,
584				encoded_weight_call,
585			);
586
587			let weight_result = match xcm_weight_results.result {
588				Ok(value) => value,
589				Err(err) =>
590					panic!("XcmExecutePrecompile Failed to decode weight with error: {err:?}"),
591			};
592
593			let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
594				.expect("XcmExecutePrecompile Failed to decode weight");
595
596			let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
597			let call = IXcm::IXcmCalls::execute(xcm_execute_params);
598			let encoded_call = call.abi_encode();
599
600			let result = pallet_revive::Pallet::<Test>::bare_call(
601				RuntimeOrigin::signed(ALICE),
602				xcm_precompile_addr,
603				U256::zero(),
604				Weight::MAX,
605				DepositLimit::UnsafeOnlyForDryRun,
606				encoded_call,
607			);
608
609			let return_value = match result.result {
610				Ok(value) => value,
611				Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
612			};
613
614			assert!(!return_value.did_revert());
615			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE - SEND_AMOUNT);
616			assert_eq!(Balances::total_balance(&CHARLIE), CUSTOM_INITIAL_BALANCE + SEND_AMOUNT);
617		});
618	}
619
620	#[test]
621	fn test_xcm_execute_precompile_fails() {
622		use codec::Encode;
623
624		let balances = vec![(ALICE, CUSTOM_INITIAL_BALANCE), (BOB, CUSTOM_INITIAL_BALANCE)];
625		new_test_ext_with_balances(balances).execute_with(|| {
626			let xcm_precompile_addr = H160::from(
627				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
628			);
629
630			let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
631			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
632			let amount_to_send = CUSTOM_INITIAL_BALANCE - ExistentialDeposit::get();
633			let assets: Assets = (Here, amount_to_send).into();
634
635			let message: VersionedXcm<RuntimeCall> = VersionedXcm::from(Xcm(vec![
636				WithdrawAsset(assets.clone()),
637				buy_execution(assets.inner()[0].clone()),
638				DepositAsset { assets: assets.clone().into(), beneficiary: dest },
639				WithdrawAsset(assets),
640			]));
641
642			let weight_params = weighMessageCall { message: message.encode().into() };
643			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
644			let encoded_weight_call = weight_call.abi_encode();
645
646			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
647				RuntimeOrigin::signed(ALICE),
648				xcm_precompile_addr,
649				U256::zero(),
650				Weight::MAX,
651				DepositLimit::UnsafeOnlyForDryRun,
652				encoded_weight_call,
653			);
654
655			let weight_result = match xcm_weight_results.result {
656				Ok(value) => value,
657				Err(err) =>
658					panic!("XcmExecutePrecompile Failed to decode weight with error: {err:?}"),
659			};
660
661			let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
662				.expect("XcmExecutePrecompile Failed to decode weight");
663
664			let xcm_execute_params = IXcm::executeCall { message: message.encode().into(), weight };
665			let call = IXcm::IXcmCalls::execute(xcm_execute_params);
666			let encoded_call = call.abi_encode();
667
668			let result = pallet_revive::Pallet::<Test>::bare_call(
669				RuntimeOrigin::signed(ALICE),
670				xcm_precompile_addr,
671				U256::zero(),
672				Weight::MAX,
673				DepositLimit::UnsafeOnlyForDryRun,
674				encoded_call,
675			);
676			let return_value = match result.result {
677				Ok(value) => value,
678				Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
679			};
680			assert!(return_value.did_revert());
681			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
682			assert_eq!(Balances::total_balance(&BOB), CUSTOM_INITIAL_BALANCE);
683		});
684	}
685
686	#[test]
687	fn execute_fails_on_old_version() {
688		use codec::Encode;
689
690		let balances = vec![
691			(ALICE, CUSTOM_INITIAL_BALANCE),
692			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
693		];
694		new_test_ext_with_balances(balances).execute_with(|| {
695			let xcm_precompile_addr = H160::from(
696				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
697			);
698
699			let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
700			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
701
702			let message = Xcm(vec![
703				WithdrawAsset((Here, SEND_AMOUNT).into()),
704				buy_execution((Here, SEND_AMOUNT)),
705				DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
706			]);
707			let versioned_message = VersionedXcm::from(message.clone());
708
709			let weight_params = weighMessageCall { message: versioned_message.encode().into() };
710			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
711			let encoded_weight_call = weight_call.abi_encode();
712
713			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
714				RuntimeOrigin::signed(ALICE),
715				xcm_precompile_addr,
716				U256::zero(),
717				Weight::MAX,
718				DepositLimit::UnsafeOnlyForDryRun,
719				encoded_weight_call,
720			);
721
722			let weight_result = match xcm_weight_results.result {
723				Ok(value) => value,
724				Err(err) =>
725					panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
726			};
727
728			let weight: IXcm::Weight = IXcm::Weight::abi_decode(&weight_result.data[..])
729				.expect("XcmExecutePrecompile Failed to decode weight");
730
731			// Using a V4 message to check that it fails.
732			let v4_message: v4::Xcm<RuntimeCall> = message.clone().try_into().unwrap();
733			let versioned_message = VersionedXcm::V4(v4_message.clone());
734
735			let xcm_execute_params = IXcm::executeCall {
736				message: versioned_message.encode().into(),
737				weight: weight.clone(),
738			};
739			let call = IXcm::IXcmCalls::execute(xcm_execute_params);
740			let encoded_call = call.abi_encode();
741
742			let result = pallet_revive::Pallet::<Test>::bare_call(
743				RuntimeOrigin::signed(ALICE),
744				xcm_precompile_addr,
745				U256::zero(),
746				Weight::MAX,
747				DepositLimit::UnsafeOnlyForDryRun,
748				encoded_call,
749			);
750
751			let return_value = match result.result {
752				Ok(value) => value,
753				Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
754			};
755			assert!(return_value.did_revert());
756			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
757			assert_eq!(Balances::total_balance(&BOB), 0);
758
759			// Now using a V3 message.
760			let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
761			let versioned_message = VersionedXcm::V3(v3_message);
762
763			let xcm_execute_params =
764				IXcm::executeCall { message: versioned_message.encode().into(), weight };
765			let call = IXcm::IXcmCalls::execute(xcm_execute_params);
766			let encoded_call = call.abi_encode();
767
768			let result = pallet_revive::Pallet::<Test>::bare_call(
769				RuntimeOrigin::signed(ALICE),
770				xcm_precompile_addr,
771				U256::zero(),
772				Weight::MAX,
773				DepositLimit::UnsafeOnlyForDryRun,
774				encoded_call,
775			);
776
777			let return_value = match result.result {
778				Ok(value) => value,
779				Err(err) => panic!("XcmExecutePrecompile call failed with error: {err:?}"),
780			};
781			assert!(return_value.did_revert());
782			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
783			assert_eq!(Balances::total_balance(&BOB), 0);
784		});
785	}
786
787	#[test]
788	fn weight_fails_on_old_version() {
789		use codec::Encode;
790
791		let balances = vec![
792			(ALICE, CUSTOM_INITIAL_BALANCE),
793			(ParaId::from(OTHER_PARA_ID).into_account_truncating(), CUSTOM_INITIAL_BALANCE),
794		];
795		new_test_ext_with_balances(balances).execute_with(|| {
796			let xcm_precompile_addr = H160::from(
797				hex::const_decode_to_array(b"00000000000000000000000000000000000A0000").unwrap(),
798			);
799
800			let dest: Location = Junction::AccountId32 { network: None, id: BOB.into() }.into();
801			assert_eq!(Balances::total_balance(&ALICE), CUSTOM_INITIAL_BALANCE);
802
803			let message: Xcm<RuntimeCall> = Xcm(vec![
804				WithdrawAsset((Here, SEND_AMOUNT).into()),
805				buy_execution((Here, SEND_AMOUNT)),
806				DepositAsset { assets: AllCounted(1).into(), beneficiary: dest },
807			]);
808			// V4 version is old, fails.
809			let v4_message: v4::Xcm<RuntimeCall> = message.try_into().unwrap();
810			let versioned_message = VersionedXcm::V4(v4_message.clone());
811
812			let weight_params = weighMessageCall { message: versioned_message.encode().into() };
813			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
814			let encoded_weight_call = weight_call.abi_encode();
815
816			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
817				RuntimeOrigin::signed(ALICE),
818				xcm_precompile_addr,
819				U256::zero(),
820				Weight::MAX,
821				DepositLimit::UnsafeOnlyForDryRun,
822				encoded_weight_call,
823			);
824
825			let result = match xcm_weight_results.result {
826				Ok(value) => value,
827				Err(err) =>
828					panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
829			};
830			assert!(result.did_revert());
831
832			// Now we also try V3.
833			let v3_message: v3::Xcm<RuntimeCall> = v4_message.try_into().unwrap();
834			let versioned_message = VersionedXcm::V3(v3_message);
835
836			let weight_params = weighMessageCall { message: versioned_message.encode().into() };
837			let weight_call = IXcm::IXcmCalls::weighMessage(weight_params);
838			let encoded_weight_call = weight_call.abi_encode();
839
840			let xcm_weight_results = pallet_revive::Pallet::<Test>::bare_call(
841				RuntimeOrigin::signed(ALICE),
842				xcm_precompile_addr,
843				U256::zero(),
844				Weight::MAX,
845				DepositLimit::UnsafeOnlyForDryRun,
846				encoded_weight_call,
847			);
848
849			let result = match xcm_weight_results.result {
850				Ok(value) => value,
851				Err(err) =>
852					panic!("XcmExecutePrecompile Failed to decode weight with error {err:?}"),
853			};
854			assert!(result.did_revert());
855		});
856	}
857}