pallet_evm_precompile_xcm/
lib.rs

1// This file is part of Astar.
2
3// Copyright (C) Stake Technologies Pte.Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later
5
6// Astar is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// Astar is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with Astar. If not, see <http://www.gnu.org/licenses/>.
18
19#![cfg_attr(not(feature = "std"), no_std)]
20
21use astar_primitives::xcm::XCM_SIZE_LIMIT;
22use fp_evm::PrecompileHandle;
23use frame_support::{
24    dispatch::{GetDispatchInfo, PostDispatchInfo},
25    pallet_prelude::Weight,
26    traits::{ConstU32, Get},
27};
28use sp_runtime::traits::{Dispatchable, MaybeEquivalence};
29type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;
30
31use pallet_evm::AddressMapping;
32use parity_scale_codec::DecodeLimit;
33use sp_core::{H160, H256, U256};
34
35use sp_std::marker::PhantomData;
36use sp_std::prelude::*;
37
38use xcm::{latest::prelude::*, VersionedAsset, VersionedAssets, VersionedLocation};
39
40use pallet_evm_precompile_assets_erc20::AddressToAssetId;
41use precompile_utils::prelude::*;
42#[cfg(test)]
43mod mock;
44#[cfg(test)]
45mod tests;
46
47/// Dummy H160 address representing native currency (e.g. ASTR or SDN)
48const NATIVE_ADDRESS: H160 = H160::zero();
49
50/// Default proof_size of 256KB
51const DEFAULT_PROOF_SIZE: u64 = 1024 * 256;
52
53pub type XBalanceOf<Runtime> = <Runtime as orml_xtokens::Config>::Balance;
54
55pub struct GetMaxAssets<R>(PhantomData<R>);
56
57impl<R> Get<u32> for GetMaxAssets<R>
58where
59    R: orml_xtokens::Config,
60{
61    fn get() -> u32 {
62        <R as orml_xtokens::Config>::MaxAssetsForTransfer::get() as u32
63    }
64}
65
66/// A precompile that expose XCM related functions.
67pub struct XcmPrecompile<Runtime, C>(PhantomData<(Runtime, C)>);
68
69#[precompile_utils::precompile]
70#[precompile::test_concrete_types(mock::Runtime, mock::AssetIdConverter<mock::AssetId>)]
71impl<Runtime, C> XcmPrecompile<Runtime, C>
72where
73    Runtime: pallet_evm::Config
74        + pallet_xcm::Config
75        + orml_xtokens::Config
76        + pallet_assets::Config
77        + AddressToAssetId<<Runtime as pallet_assets::Config>::AssetId>,
78    <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
79        From<Option<Runtime::AccountId>>,
80    <Runtime as frame_system::Config>::AccountId: Into<[u8; 32]>,
81    <Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>
82        + From<orml_xtokens::Call<Runtime>>
83        + Dispatchable<PostInfo = PostDispatchInfo>
84        + GetDispatchInfo,
85    XBalanceOf<Runtime>: TryFrom<U256> + Into<U256> + From<u128>,
86    <Runtime as orml_xtokens::Config>::CurrencyId:
87        From<<Runtime as pallet_assets::Config>::AssetId>,
88    C: MaybeEquivalence<Location, <Runtime as pallet_assets::Config>::AssetId>,
89    <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
90{
91    #[precompile::public("assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)")]
92    fn assets_withdraw_native_v1(
93        handle: &mut impl PrecompileHandle,
94        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
95        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
96        recipient_account_id: H256,
97        is_relay: bool,
98        parachain_id: U256,
99        fee_index: U256,
100    ) -> EvmResult<bool> {
101        let beneficiary: Junction = Junction::AccountId32 {
102            network: None,
103            id: recipient_account_id.into(),
104        }
105        .into();
106        Self::assets_withdraw_v1_internal(
107            handle,
108            assets.into(),
109            amounts.into(),
110            beneficiary,
111            is_relay,
112            parachain_id,
113            fee_index,
114        )
115    }
116
117    #[precompile::public("assets_withdraw(address[],uint256[],address,bool,uint256,uint256)")]
118    fn assets_withdraw_evm_v1(
119        handle: &mut impl PrecompileHandle,
120        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
121        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
122        recipient_account_id: Address,
123        is_relay: bool,
124        parachain_id: U256,
125        fee_index: U256,
126    ) -> EvmResult<bool> {
127        let beneficiary: Junction = Junction::AccountKey20 {
128            network: None,
129            key: recipient_account_id.0.to_fixed_bytes(),
130        }
131        .into();
132        Self::assets_withdraw_v1_internal(
133            handle,
134            assets.into(),
135            amounts.into(),
136            beneficiary,
137            is_relay,
138            parachain_id,
139            fee_index,
140        )
141    }
142
143    fn assets_withdraw_v1_internal(
144        handle: &mut impl PrecompileHandle,
145        assets: Vec<Address>,
146        amounts: Vec<U256>,
147        beneficiary: Junction,
148        is_relay: bool,
149        parachain_id: U256,
150        fee_index: U256,
151    ) -> EvmResult<bool> {
152        // Read arguments and check it
153        let assets = assets
154            .iter()
155            .cloned()
156            .filter_map(|address| {
157                Runtime::address_to_asset_id(address.into()).and_then(|x| C::convert_back(&x))
158            })
159            .collect::<Vec<Location>>();
160
161        let amounts = amounts
162            .into_iter()
163            .map(|x| x.try_into())
164            .collect::<Result<Vec<u128>, _>>()
165            .map_err(|_| revert("error converting amounts, maybe value too large"))?;
166
167        // Check that assets list is valid:
168        // * all assets resolved to multi-location
169        // * all assets has corresponded amount
170        if assets.len() != amounts.len() || assets.is_empty() {
171            return Err(revert("Assets resolution failure."));
172        }
173
174        let parachain_id: u32 = parachain_id
175            .try_into()
176            .map_err(|_| revert("error converting parachain_id, maybe value too large"))?;
177
178        let fee_item: u32 = fee_index
179            .try_into()
180            .map_err(|_| revert("error converting fee_index, maybe value too large"))?;
181
182        let mut destination = if is_relay {
183            Location::parent()
184        } else {
185            Junctions::from(Junction::Parachain(parachain_id)).into_exterior(1)
186        };
187
188        destination
189            .push_interior(beneficiary)
190            .map_err(|_| revert("error building destination multilocation"))?;
191
192        let assets = assets
193            .iter()
194            .cloned()
195            .zip(amounts.iter().cloned())
196            .map(Into::into)
197            .collect::<Vec<Asset>>();
198
199        Self::ensure_dot_transfer_policy(&assets, &destination)?;
200
201        log::trace!(target: "xcm-precompile:assets_withdraw", "Processed arguments: assets {:?}, destination: {:?}", assets, destination);
202
203        // Build call with origin.
204        let origin = Some(Runtime::AddressMapping::into_account_id(
205            handle.context().caller,
206        ))
207        .into();
208
209        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
210            assets: Box::new(VersionedAssets::V5(assets.into())),
211            fee_item,
212            dest: Box::new(VersionedLocation::V5(destination)),
213            dest_weight_limit: WeightLimit::Unlimited,
214        };
215
216        // Dispatch a call.
217        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
218        Ok(true)
219    }
220
221    #[precompile::public("remote_transact(uint256,bool,address,uint256,bytes,uint64)")]
222    fn remote_transact_v1(
223        handle: &mut impl PrecompileHandle,
224        para_id: U256,
225        is_relay: bool,
226        fee_asset_addr: Address,
227        fee_amount: U256,
228        remote_call: UnboundedBytes,
229        transact_weight: u64,
230    ) -> EvmResult<bool> {
231        // Raw call arguments
232        let para_id: u32 = para_id
233            .try_into()
234            .map_err(|_| revert("error converting para_id, maybe value too large"))?;
235
236        let fee_amount: u128 = fee_amount
237            .try_into()
238            .map_err(|_| revert("error converting fee_amount, maybe value too large"))?;
239
240        let remote_call: Vec<u8> = remote_call.into();
241
242        log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \
243         fee_amount: {:?}, remote_call: {:?}, transact_weight: {}",
244         para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight);
245
246        // Process arguments
247        let dest = if is_relay {
248            Location::parent()
249        } else {
250            Junctions::from(Junction::Parachain(para_id)).into_exterior(1)
251        };
252
253        let fee_asset = {
254            let address: H160 = fee_asset_addr.into();
255
256            // Special case where zero address maps to native token by convention.
257            if address == NATIVE_ADDRESS {
258                Here.into()
259            } else {
260                let fee_asset_id = Runtime::address_to_asset_id(address)
261                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
262                C::convert_back(&fee_asset_id).ok_or(revert(
263                    "Failed to resolve fee asset multilocation from local id",
264                ))?
265            }
266        };
267
268        let context = <Runtime as pallet_xcm::Config>::UniversalLocation::get();
269        let fee_multilocation: Asset = (fee_asset, fee_amount).into();
270        let fee_multilocation = fee_multilocation
271            .reanchored(&dest, &context)
272            .map_err(|_| revert("Failed to reanchor fee asset"))?;
273
274        // Prepare XCM
275        let xcm = Xcm(vec![
276            WithdrawAsset(fee_multilocation.clone().into()),
277            BuyExecution {
278                fees: fee_multilocation.clone().into(),
279                weight_limit: WeightLimit::Unlimited,
280            },
281            Transact {
282                origin_kind: OriginKind::SovereignAccount,
283                fallback_max_weight: Some(Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE)),
284                call: remote_call.into(),
285            },
286        ]);
287
288        log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm);
289
290        // Build call with origin.
291        let origin = Some(Runtime::AddressMapping::into_account_id(
292            handle.context().caller,
293        ))
294        .into();
295        let call = pallet_xcm::Call::<Runtime>::send {
296            dest: Box::new(dest.into()),
297            message: Box::new(xcm::VersionedXcm::V5(xcm)),
298        };
299
300        // Dispatch a call.
301        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
302
303        Ok(true)
304    }
305
306    fn assets_reserve_transfer_v1_internal(
307        handle: &mut impl PrecompileHandle,
308        assets: Vec<Address>,
309        amounts: Vec<U256>,
310        beneficiary: Junction,
311        is_relay: bool,
312        parachain_id: U256,
313        fee_item: U256,
314    ) -> EvmResult<bool> {
315        let assets: Vec<Location> = assets
316            .iter()
317            .cloned()
318            .filter_map(|address| {
319                let address: H160 = address.into();
320                // Special case where zero address maps to native token by convention.
321                if address == NATIVE_ADDRESS {
322                    Some(Here.into())
323                } else {
324                    Runtime::address_to_asset_id(address).and_then(|x| C::convert_back(&x))
325                }
326            })
327            .collect();
328
329        let amounts: Vec<u128> = amounts
330            .into_iter()
331            .map(|x| x.try_into())
332            .collect::<Result<Vec<u128>, _>>()
333            .map_err(|_| revert("error converting amounts, maybe value too large"))?;
334
335        // Check that assets list is valid:
336        // * all assets resolved to multi-location
337        // * all assets has corresponded amount
338        if assets.len() != amounts.len() || assets.is_empty() {
339            return Err(revert("Assets resolution failure."));
340        }
341
342        let parachain_id: u32 = parachain_id
343            .try_into()
344            .map_err(|_| revert("error converting parachain_id, maybe value too large"))?;
345
346        let fee_item: u32 = fee_item
347            .try_into()
348            .map_err(|_| revert("error converting fee_index, maybe value too large"))?;
349
350        // Prepare pallet-xcm call arguments
351        let mut destination = if is_relay {
352            Location::parent()
353        } else {
354            Junctions::from(Junction::Parachain(parachain_id)).into_exterior(1)
355        };
356
357        destination
358            .push_interior(beneficiary)
359            .map_err(|_| revert("error building destination multilocation"))?;
360
361        let assets = assets
362            .iter()
363            .cloned()
364            .zip(amounts.iter().cloned())
365            .map(Into::into)
366            .collect::<Vec<Asset>>();
367
368        Self::ensure_dot_transfer_policy(&assets, &destination)?;
369        log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, destination: {:?}", assets, destination);
370
371        // Build call with origin.
372        let origin = Some(Runtime::AddressMapping::into_account_id(
373            handle.context().caller,
374        ))
375        .into();
376
377        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
378            assets: Box::new(VersionedAssets::V5(assets.into())),
379            fee_item,
380            dest: Box::new(VersionedLocation::V5(destination)),
381            dest_weight_limit: WeightLimit::Unlimited,
382        };
383
384        // Dispatch a call.
385        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
386
387        Ok(true)
388    }
389
390    #[precompile::public(
391        "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)"
392    )]
393    fn assets_reserve_transfer_native_v1(
394        handle: &mut impl PrecompileHandle,
395        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
396        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
397        recipient_account_id: H256,
398        is_relay: bool,
399        parachain_id: U256,
400        fee_index: U256,
401    ) -> EvmResult<bool> {
402        let beneficiary: Junction = Junction::AccountId32 {
403            network: None,
404            id: recipient_account_id.into(),
405        }
406        .into();
407        Self::assets_reserve_transfer_v1_internal(
408            handle,
409            assets.into(),
410            amounts.into(),
411            beneficiary,
412            is_relay,
413            parachain_id,
414            fee_index,
415        )
416    }
417
418    #[precompile::public(
419        "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)"
420    )]
421    fn assets_reserve_transfer_evm_v1(
422        handle: &mut impl PrecompileHandle,
423        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
424        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
425        recipient_account_id: Address,
426        is_relay: bool,
427        parachain_id: U256,
428        fee_index: U256,
429    ) -> EvmResult<bool> {
430        let beneficiary: Junction = Junction::AccountKey20 {
431            network: None,
432            key: recipient_account_id.0.to_fixed_bytes(),
433        }
434        .into();
435        Self::assets_reserve_transfer_v1_internal(
436            handle,
437            assets.into(),
438            amounts.into(),
439            beneficiary,
440            is_relay,
441            parachain_id,
442            fee_index,
443        )
444    }
445
446    #[precompile::public("send_xcm((uint8,bytes[]),bytes)")]
447    fn send_xcm(
448        handle: &mut impl PrecompileHandle,
449        dest: Location,
450        xcm_call: BoundedBytes<GetXcmSizeLimit>,
451    ) -> EvmResult<bool> {
452        // Raw call arguments
453        let dest: Location = dest.into();
454        let xcm_call: Vec<u8> = xcm_call.into();
455
456        log::trace!(target:"xcm-precompile::send_xcm", "Raw arguments: dest: {:?}, xcm_call: {:?}", dest, xcm_call);
457
458        let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit(
459            xcm::MAX_XCM_DECODE_DEPTH,
460            &mut xcm_call.as_slice(),
461        )
462        .map_err(|_| revert("Failed to decode xcm instructions"))?;
463
464        // Build call with origin.
465        let origin = Some(Runtime::AddressMapping::into_account_id(
466            handle.context().caller,
467        ))
468        .into();
469        let call = pallet_xcm::Call::<Runtime>::send {
470            dest: Box::new(dest.into()),
471            message: Box::new(xcm),
472        };
473        log::trace!(target: "xcm-send_xcm", "Processed arguments:  XCM call: {:?}", call);
474        // Dispatch a call.
475        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
476
477        Ok(true)
478    }
479
480    #[precompile::public("transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))")]
481    fn transfer(
482        handle: &mut impl PrecompileHandle,
483        currency_address: Address,
484        amount_of_tokens: U256,
485        destination: Location,
486        weight: WeightV2,
487    ) -> EvmResult<bool> {
488        // Read call arguments
489        let amount_of_tokens: u128 = amount_of_tokens
490            .try_into()
491            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
492
493        let dest_weight_limit = if weight.is_zero() {
494            WeightLimit::Unlimited
495        } else {
496            WeightLimit::Limited(weight.get_weight())
497        };
498
499        let call = {
500            if currency_address == Address::from(NATIVE_ADDRESS) {
501                log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?} (this is native token), amount_of_tokens: {:?}, destination: {:?}, \
502                weight: {:?}",
503                currency_address, amount_of_tokens, destination, weight );
504
505                orml_xtokens::Call::<Runtime>::transfer_multiasset {
506                    asset: Box::new(VersionedAsset::V5(
507                        (Location::here(), amount_of_tokens).into(),
508                    )),
509                    dest: Box::new(VersionedLocation::V5(destination)),
510                    dest_weight_limit,
511                }
512            } else {
513                let asset_id = Runtime::address_to_asset_id(currency_address.into())
514                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
515
516                log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
517                weight: {:?}, calculated asset_id: {:?}",
518                currency_address, amount_of_tokens, destination, weight, asset_id);
519
520                orml_xtokens::Call::<Runtime>::transfer {
521                    currency_id: asset_id.into(),
522                    amount: amount_of_tokens.into(),
523                    dest: Box::new(VersionedLocation::V5(destination)),
524                    dest_weight_limit,
525                }
526            }
527        };
528
529        let origin = Some(Runtime::AddressMapping::into_account_id(
530            handle.context().caller,
531        ))
532        .into();
533
534        // Dispatch a call.
535        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
536
537        Ok(true)
538    }
539
540    #[precompile::public(
541        "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
542    )]
543    fn transfer_with_fee(
544        handle: &mut impl PrecompileHandle,
545        currency_address: Address,
546        amount_of_tokens: U256,
547        fee: U256,
548        destination: Location,
549        weight: WeightV2,
550    ) -> EvmResult<bool> {
551        // Read call arguments
552        let amount_of_tokens: u128 = amount_of_tokens
553            .try_into()
554            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
555        let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?;
556
557        let dest_weight_limit = if weight.is_zero() {
558            WeightLimit::Unlimited
559        } else {
560            WeightLimit::Limited(weight.get_weight())
561        };
562
563        let call = {
564            if currency_address == Address::from(NATIVE_ADDRESS) {
565                log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?} (this is native token), amount_of_tokens: {:?}, destination: {:?}, \
566                weight: {:?}, fee {:?}",
567                currency_address, amount_of_tokens, destination, weight, fee );
568
569                orml_xtokens::Call::<Runtime>::transfer_multiasset_with_fee {
570                    asset: Box::new(VersionedAsset::V5(
571                        (Location::here(), amount_of_tokens).into(),
572                    )),
573                    fee: Box::new(VersionedAsset::V5((Location::here(), fee).into())),
574                    dest: Box::new(VersionedLocation::V5(destination)),
575                    dest_weight_limit,
576                }
577            } else {
578                let asset_id = Runtime::address_to_asset_id(currency_address.into())
579                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
580
581                log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
582                weight: {:?}, calculated asset_id: {:?}, fee: {:?}",
583                currency_address, amount_of_tokens, destination, weight, asset_id, fee);
584
585                orml_xtokens::Call::<Runtime>::transfer_with_fee {
586                    currency_id: asset_id.into(),
587                    amount: amount_of_tokens.into(),
588                    fee: fee.into(),
589                    dest: Box::new(VersionedLocation::V5(destination)),
590                    dest_weight_limit,
591                }
592            }
593        };
594
595        let origin = Some(Runtime::AddressMapping::into_account_id(
596            handle.context().caller,
597        ))
598        .into();
599
600        // Dispatch a call.
601        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
602
603        Ok(true)
604    }
605
606    #[precompile::public(
607        "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))"
608    )]
609    fn transfer_multiasset(
610        handle: &mut impl PrecompileHandle,
611        asset_location: Location,
612        amount_of_tokens: U256,
613        destination: Location,
614        weight: WeightV2,
615    ) -> EvmResult<bool> {
616        // Read call arguments
617        let amount_of_tokens: u128 = amount_of_tokens
618            .try_into()
619            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
620
621        let dest_weight_limit = if weight.is_zero() {
622            WeightLimit::Unlimited
623        } else {
624            WeightLimit::Limited(weight.get_weight())
625        };
626
627        log::trace!(target: "xcm-precompile::transfer_multiasset", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
628        weight: {:?}",
629        asset_location, amount_of_tokens, destination, weight);
630
631        let call = orml_xtokens::Call::<Runtime>::transfer_multiasset {
632            asset: Box::new(VersionedAsset::V5(
633                (asset_location, amount_of_tokens).into(),
634            )),
635            dest: Box::new(VersionedLocation::V5(destination)),
636            dest_weight_limit,
637        };
638
639        let origin = Some(Runtime::AddressMapping::into_account_id(
640            handle.context().caller,
641        ))
642        .into();
643
644        // Dispatch a call.
645        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
646
647        Ok(true)
648    }
649
650    #[precompile::public(
651        "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
652    )]
653    fn transfer_multiasset_with_fee(
654        handle: &mut impl PrecompileHandle,
655        asset_location: Location,
656        amount_of_tokens: U256,
657        fee: U256,
658        destination: Location,
659        weight: WeightV2,
660    ) -> EvmResult<bool> {
661        // Read call arguments
662        let amount_of_tokens: u128 = amount_of_tokens
663            .try_into()
664            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
665        let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?;
666
667        let dest_weight_limit = if weight.is_zero() {
668            WeightLimit::Unlimited
669        } else {
670            WeightLimit::Limited(weight.get_weight())
671        };
672
673        log::trace!(target: "xcm-precompile::transfer_multiasset_with_fee", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, fee{:?}, destination: {:?}, \
674        weight: {:?}",
675        asset_location, amount_of_tokens, fee, destination, weight);
676
677        let call = orml_xtokens::Call::<Runtime>::transfer_multiasset_with_fee {
678            asset: Box::new(VersionedAsset::V5(
679                (asset_location.clone(), amount_of_tokens).into(),
680            )),
681            fee: Box::new(VersionedAsset::V5((asset_location, fee).into())),
682            dest: Box::new(VersionedLocation::V5(destination)),
683            dest_weight_limit,
684        };
685
686        let origin = Some(Runtime::AddressMapping::into_account_id(
687            handle.context().caller,
688        ))
689        .into();
690
691        // Dispatch a call.
692        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
693
694        Ok(true)
695    }
696
697    #[precompile::public(
698        "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
699    )]
700    fn transfer_multi_currencies(
701        handle: &mut impl PrecompileHandle,
702        currencies: BoundedVec<Currency, GetMaxAssets<Runtime>>,
703        fee_item: u32,
704        destination: Location,
705        weight: WeightV2,
706    ) -> EvmResult<bool> {
707        let currencies: Vec<_> = currencies.into();
708        let currencies = currencies
709            .into_iter()
710            .map(|currency| {
711                let currency_address: H160 = currency.get_address().into();
712                let amount = currency
713                    .get_amount()
714                    .try_into()
715                    .map_err(|_| revert("value too large: in currency"))?;
716
717                Ok((
718                    Runtime::address_to_asset_id(currency_address.into())
719                        .ok_or(revert("can't convert into currency id"))?
720                        .into(),
721                    amount,
722                ))
723            })
724            .collect::<EvmResult<_>>()?;
725        let dest_weight_limit = if weight.is_zero() {
726            WeightLimit::Unlimited
727        } else {
728            WeightLimit::Limited(weight.get_weight())
729        };
730
731        log::trace!(target: "xcm-precompile::transfer_multi_currencies", "Raw arguments: currencies: {:?}, fee_item{:?}, destination: {:?}, \
732        weight: {:?}",
733        currencies, fee_item, destination, weight);
734
735        let call = orml_xtokens::Call::<Runtime>::transfer_multicurrencies {
736            currencies,
737            fee_item,
738            dest: Box::new(VersionedLocation::V5(destination)),
739            dest_weight_limit,
740        };
741
742        let origin = Some(Runtime::AddressMapping::into_account_id(
743            handle.context().caller,
744        ))
745        .into();
746
747        // Dispatch a call.
748        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
749
750        Ok(true)
751    }
752
753    #[precompile::public(
754        "transfer_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
755    )]
756    fn transfer_multi_assets(
757        handle: &mut impl PrecompileHandle,
758        assets: BoundedVec<EvmMultiAsset, GetMaxAssets<Runtime>>,
759        fee_item: u32,
760        destination: Location,
761        weight: WeightV2,
762    ) -> EvmResult<bool> {
763        let assets: Vec<_> = assets.into();
764
765        let dest_weight_limit = if weight.is_zero() {
766            WeightLimit::Unlimited
767        } else {
768            WeightLimit::Limited(weight.get_weight())
769        };
770
771        log::trace!(target: "xcm-precompile::transfer_multi_assets", "Raw arguments: assets: {:?}, fee_item{:?}, destination: {:?}, \
772        weight: {:?}",
773        assets, fee_item, destination, weight);
774
775        let multiasset_vec: EvmResult<Vec<Asset>> = assets
776            .into_iter()
777            .map(|evm_multiasset| {
778                let to_balance: u128 = evm_multiasset
779                    .get_amount()
780                    .try_into()
781                    .map_err(|_| revert("value too large in assets"))?;
782                Ok((evm_multiasset.get_location(), to_balance).into())
783            })
784            .collect();
785
786        // Since multiassets sorts them, we need to check whether the index is still correct,
787        // and error otherwise as there is not much we can do other than that
788        let multiassets = Assets::from_sorted_and_deduplicated(multiasset_vec?).map_err(|_| {
789            revert("In field Assets, Provided assets either not sorted nor deduplicated")
790        })?;
791
792        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
793            assets: Box::new(VersionedAssets::V5(multiassets)),
794            fee_item,
795            dest: Box::new(VersionedLocation::V5(destination)),
796            dest_weight_limit,
797        };
798
799        let origin = Some(Runtime::AddressMapping::into_account_id(
800            handle.context().caller,
801        ))
802        .into();
803
804        // Dispatch a call.
805        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
806
807        Ok(true)
808    }
809
810    /// Enforces DOT transfer routing policy.
811    ///
812    /// Currently prevents direct DOT transfers to the relay chain,
813    /// requiring routing through AssetHub (parachain 1000).
814    fn ensure_dot_transfer_policy(assets: &[Asset], destination: &Location) -> EvmResult<()> {
815        let dest_chain = {
816            let mut d = destination.clone();
817            // Remove the final interior junction (the beneficiary AccountId32 / AccountKey20)
818            // to get just the chain root, e.g. (1, Here) or (1, Parachain(1000)).
819            d.interior.take_last();
820            d
821        };
822
823        if dest_chain != Location::parent() {
824            return Ok(());
825        }
826
827        let deprecated_dot_location = Location::new(1, Junctions::Here);
828
829        for asset in assets {
830            let AssetId(location) = &asset.id;
831            if location == &deprecated_dot_location {
832                return Err(revert(
833                    "DOT cannot be sent directly to the relay. \
834                 Route via AssetHub (parachain 1000).",
835                ));
836            }
837        }
838
839        Ok(())
840    }
841}
842
843#[derive(Debug, Clone, solidity::Codec)]
844pub struct WeightV2 {
845    ref_time: u64,
846    proof_size: u64,
847}
848
849impl WeightV2 {
850    pub fn from(ref_time: u64, proof_size: u64) -> Self {
851        WeightV2 {
852            ref_time,
853            proof_size,
854        }
855    }
856
857    pub fn get_weight(&self) -> Weight {
858        Weight::from_parts(self.ref_time, self.proof_size)
859    }
860
861    pub fn is_zero(&self) -> bool {
862        self.ref_time == 0u64
863    }
864}
865
866#[derive(Debug, Clone, solidity::Codec)]
867pub struct Currency {
868    address: Address,
869    amount: U256,
870}
871
872impl Currency {
873    pub fn get_address(&self) -> Address {
874        self.address
875    }
876
877    pub fn get_amount(&self) -> U256 {
878        self.amount
879    }
880}
881
882impl From<(Address, U256)> for Currency {
883    fn from(tuple: (Address, U256)) -> Self {
884        Currency {
885            address: tuple.0,
886            amount: tuple.1,
887        }
888    }
889}
890
891#[derive(Debug, Clone, solidity::Codec)]
892pub struct EvmMultiAsset {
893    location: Location,
894    amount: U256,
895}
896
897impl From<(Location, U256)> for EvmMultiAsset {
898    fn from(tuple: (Location, U256)) -> Self {
899        EvmMultiAsset {
900            location: tuple.0,
901            amount: tuple.1,
902        }
903    }
904}
905
906impl EvmMultiAsset {
907    pub fn get_location(&self) -> Location {
908        self.location.clone()
909    }
910
911    pub fn get_amount(&self) -> U256 {
912        self.amount
913    }
914}