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        log::trace!(target: "xcm-precompile:assets_withdraw", "Processed arguments: assets {:?}, destination: {:?}", assets, destination);
200
201        // Build call with origin.
202        let origin = Some(Runtime::AddressMapping::into_account_id(
203            handle.context().caller,
204        ))
205        .into();
206
207        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
208            assets: Box::new(VersionedAssets::V5(assets.into())),
209            fee_item,
210            dest: Box::new(VersionedLocation::V5(destination)),
211            dest_weight_limit: WeightLimit::Unlimited,
212        };
213
214        // Dispatch a call.
215        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
216        Ok(true)
217    }
218
219    #[precompile::public("remote_transact(uint256,bool,address,uint256,bytes,uint64)")]
220    fn remote_transact_v1(
221        handle: &mut impl PrecompileHandle,
222        para_id: U256,
223        is_relay: bool,
224        fee_asset_addr: Address,
225        fee_amount: U256,
226        remote_call: UnboundedBytes,
227        transact_weight: u64,
228    ) -> EvmResult<bool> {
229        // Raw call arguments
230        let para_id: u32 = para_id
231            .try_into()
232            .map_err(|_| revert("error converting para_id, maybe value too large"))?;
233
234        let fee_amount: u128 = fee_amount
235            .try_into()
236            .map_err(|_| revert("error converting fee_amount, maybe value too large"))?;
237
238        let remote_call: Vec<u8> = remote_call.into();
239
240        log::trace!(target: "xcm-precompile:remote_transact", "Raw arguments: para_id: {}, is_relay: {}, fee_asset_addr: {:?}, \
241         fee_amount: {:?}, remote_call: {:?}, transact_weight: {}",
242         para_id, is_relay, fee_asset_addr, fee_amount, remote_call, transact_weight);
243
244        // Process arguments
245        let dest = if is_relay {
246            Location::parent()
247        } else {
248            Junctions::from(Junction::Parachain(para_id)).into_exterior(1)
249        };
250
251        let fee_asset = {
252            let address: H160 = fee_asset_addr.into();
253
254            // Special case where zero address maps to native token by convention.
255            if address == NATIVE_ADDRESS {
256                Here.into()
257            } else {
258                let fee_asset_id = Runtime::address_to_asset_id(address)
259                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
260                C::convert_back(&fee_asset_id).ok_or(revert(
261                    "Failed to resolve fee asset multilocation from local id",
262                ))?
263            }
264        };
265
266        let context = <Runtime as pallet_xcm::Config>::UniversalLocation::get();
267        let fee_multilocation: Asset = (fee_asset, fee_amount).into();
268        let fee_multilocation = fee_multilocation
269            .reanchored(&dest, &context)
270            .map_err(|_| revert("Failed to reanchor fee asset"))?;
271
272        // Prepare XCM
273        let xcm = Xcm(vec![
274            WithdrawAsset(fee_multilocation.clone().into()),
275            BuyExecution {
276                fees: fee_multilocation.clone().into(),
277                weight_limit: WeightLimit::Unlimited,
278            },
279            Transact {
280                origin_kind: OriginKind::SovereignAccount,
281                fallback_max_weight: Some(Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE)),
282                call: remote_call.into(),
283            },
284        ]);
285
286        log::trace!(target: "xcm-precompile:remote_transact", "Processed arguments: dest: {:?}, fee asset: {:?}, XCM: {:?}", dest, fee_multilocation, xcm);
287
288        // Build call with origin.
289        let origin = Some(Runtime::AddressMapping::into_account_id(
290            handle.context().caller,
291        ))
292        .into();
293        let call = pallet_xcm::Call::<Runtime>::send {
294            dest: Box::new(dest.into()),
295            message: Box::new(xcm::VersionedXcm::V5(xcm)),
296        };
297
298        // Dispatch a call.
299        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
300
301        Ok(true)
302    }
303
304    fn assets_reserve_transfer_v1_internal(
305        handle: &mut impl PrecompileHandle,
306        assets: Vec<Address>,
307        amounts: Vec<U256>,
308        beneficiary: Junction,
309        is_relay: bool,
310        parachain_id: U256,
311        fee_item: U256,
312    ) -> EvmResult<bool> {
313        let assets: Vec<Location> = assets
314            .iter()
315            .cloned()
316            .filter_map(|address| {
317                let address: H160 = address.into();
318                // Special case where zero address maps to native token by convention.
319                if address == NATIVE_ADDRESS {
320                    Some(Here.into())
321                } else {
322                    Runtime::address_to_asset_id(address).and_then(|x| C::convert_back(&x))
323                }
324            })
325            .collect();
326
327        let amounts: Vec<u128> = amounts
328            .into_iter()
329            .map(|x| x.try_into())
330            .collect::<Result<Vec<u128>, _>>()
331            .map_err(|_| revert("error converting amounts, maybe value too large"))?;
332
333        // Check that assets list is valid:
334        // * all assets resolved to multi-location
335        // * all assets has corresponded amount
336        if assets.len() != amounts.len() || assets.is_empty() {
337            return Err(revert("Assets resolution failure."));
338        }
339
340        let parachain_id: u32 = parachain_id
341            .try_into()
342            .map_err(|_| revert("error converting parachain_id, maybe value too large"))?;
343
344        let fee_item: u32 = fee_item
345            .try_into()
346            .map_err(|_| revert("error converting fee_index, maybe value too large"))?;
347
348        // Prepare pallet-xcm call arguments
349        let mut destination = if is_relay {
350            Location::parent()
351        } else {
352            Junctions::from(Junction::Parachain(parachain_id)).into_exterior(1)
353        };
354
355        destination
356            .push_interior(beneficiary)
357            .map_err(|_| revert("error building destination multilocation"))?;
358
359        let assets = assets
360            .iter()
361            .cloned()
362            .zip(amounts.iter().cloned())
363            .map(Into::into)
364            .collect::<Vec<Asset>>();
365
366        log::trace!(target: "xcm-precompile:assets_reserve_transfer", "Processed arguments: assets {:?}, destination: {:?}", assets, destination);
367
368        // Build call with origin.
369        let origin = Some(Runtime::AddressMapping::into_account_id(
370            handle.context().caller,
371        ))
372        .into();
373
374        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
375            assets: Box::new(VersionedAssets::V5(assets.into())),
376            fee_item,
377            dest: Box::new(VersionedLocation::V5(destination)),
378            dest_weight_limit: WeightLimit::Unlimited,
379        };
380
381        // Dispatch a call.
382        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
383
384        Ok(true)
385    }
386
387    #[precompile::public(
388        "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)"
389    )]
390    fn assets_reserve_transfer_native_v1(
391        handle: &mut impl PrecompileHandle,
392        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
393        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
394        recipient_account_id: H256,
395        is_relay: bool,
396        parachain_id: U256,
397        fee_index: U256,
398    ) -> EvmResult<bool> {
399        let beneficiary: Junction = Junction::AccountId32 {
400            network: None,
401            id: recipient_account_id.into(),
402        }
403        .into();
404        Self::assets_reserve_transfer_v1_internal(
405            handle,
406            assets.into(),
407            amounts.into(),
408            beneficiary,
409            is_relay,
410            parachain_id,
411            fee_index,
412        )
413    }
414
415    #[precompile::public(
416        "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)"
417    )]
418    fn assets_reserve_transfer_evm_v1(
419        handle: &mut impl PrecompileHandle,
420        assets: BoundedVec<Address, GetMaxAssets<Runtime>>,
421        amounts: BoundedVec<U256, GetMaxAssets<Runtime>>,
422        recipient_account_id: Address,
423        is_relay: bool,
424        parachain_id: U256,
425        fee_index: U256,
426    ) -> EvmResult<bool> {
427        let beneficiary: Junction = Junction::AccountKey20 {
428            network: None,
429            key: recipient_account_id.0.to_fixed_bytes(),
430        }
431        .into();
432        Self::assets_reserve_transfer_v1_internal(
433            handle,
434            assets.into(),
435            amounts.into(),
436            beneficiary,
437            is_relay,
438            parachain_id,
439            fee_index,
440        )
441    }
442
443    #[precompile::public("send_xcm((uint8,bytes[]),bytes)")]
444    fn send_xcm(
445        handle: &mut impl PrecompileHandle,
446        dest: Location,
447        xcm_call: BoundedBytes<GetXcmSizeLimit>,
448    ) -> EvmResult<bool> {
449        // Raw call arguments
450        let dest: Location = dest.into();
451        let xcm_call: Vec<u8> = xcm_call.into();
452
453        log::trace!(target:"xcm-precompile::send_xcm", "Raw arguments: dest: {:?}, xcm_call: {:?}", dest, xcm_call);
454
455        let xcm = xcm::VersionedXcm::<()>::decode_all_with_depth_limit(
456            xcm::MAX_XCM_DECODE_DEPTH,
457            &mut xcm_call.as_slice(),
458        )
459        .map_err(|_| revert("Failed to decode xcm instructions"))?;
460
461        // Build call with origin.
462        let origin = Some(Runtime::AddressMapping::into_account_id(
463            handle.context().caller,
464        ))
465        .into();
466        let call = pallet_xcm::Call::<Runtime>::send {
467            dest: Box::new(dest.into()),
468            message: Box::new(xcm),
469        };
470        log::trace!(target: "xcm-send_xcm", "Processed arguments:  XCM call: {:?}", call);
471        // Dispatch a call.
472        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
473
474        Ok(true)
475    }
476
477    #[precompile::public("transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))")]
478    fn transfer(
479        handle: &mut impl PrecompileHandle,
480        currency_address: Address,
481        amount_of_tokens: U256,
482        destination: Location,
483        weight: WeightV2,
484    ) -> EvmResult<bool> {
485        // Read call arguments
486        let amount_of_tokens: u128 = amount_of_tokens
487            .try_into()
488            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
489
490        let dest_weight_limit = if weight.is_zero() {
491            WeightLimit::Unlimited
492        } else {
493            WeightLimit::Limited(weight.get_weight())
494        };
495
496        let call = {
497            if currency_address == Address::from(NATIVE_ADDRESS) {
498                log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?} (this is native token), amount_of_tokens: {:?}, destination: {:?}, \
499                weight: {:?}",
500                currency_address, amount_of_tokens, destination, weight );
501
502                orml_xtokens::Call::<Runtime>::transfer_multiasset {
503                    asset: Box::new(VersionedAsset::V5(
504                        (Location::here(), amount_of_tokens).into(),
505                    )),
506                    dest: Box::new(VersionedLocation::V5(destination)),
507                    dest_weight_limit,
508                }
509            } else {
510                let asset_id = Runtime::address_to_asset_id(currency_address.into())
511                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
512
513                log::trace!(target: "xcm-precompile::transfer", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
514                weight: {:?}, calculated asset_id: {:?}",
515                currency_address, amount_of_tokens, destination, weight, asset_id);
516
517                orml_xtokens::Call::<Runtime>::transfer {
518                    currency_id: asset_id.into(),
519                    amount: amount_of_tokens.into(),
520                    dest: Box::new(VersionedLocation::V5(destination)),
521                    dest_weight_limit,
522                }
523            }
524        };
525
526        let origin = Some(Runtime::AddressMapping::into_account_id(
527            handle.context().caller,
528        ))
529        .into();
530
531        // Dispatch a call.
532        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
533
534        Ok(true)
535    }
536
537    #[precompile::public(
538        "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
539    )]
540    fn transfer_with_fee(
541        handle: &mut impl PrecompileHandle,
542        currency_address: Address,
543        amount_of_tokens: U256,
544        fee: U256,
545        destination: Location,
546        weight: WeightV2,
547    ) -> EvmResult<bool> {
548        // Read call arguments
549        let amount_of_tokens: u128 = amount_of_tokens
550            .try_into()
551            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
552        let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?;
553
554        let dest_weight_limit = if weight.is_zero() {
555            WeightLimit::Unlimited
556        } else {
557            WeightLimit::Limited(weight.get_weight())
558        };
559
560        let call = {
561            if currency_address == Address::from(NATIVE_ADDRESS) {
562                log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?} (this is native token), amount_of_tokens: {:?}, destination: {:?}, \
563                weight: {:?}, fee {:?}",
564                currency_address, amount_of_tokens, destination, weight, fee );
565
566                orml_xtokens::Call::<Runtime>::transfer_multiasset_with_fee {
567                    asset: Box::new(VersionedAsset::V5(
568                        (Location::here(), amount_of_tokens).into(),
569                    )),
570                    fee: Box::new(VersionedAsset::V5((Location::here(), fee).into())),
571                    dest: Box::new(VersionedLocation::V5(destination)),
572                    dest_weight_limit,
573                }
574            } else {
575                let asset_id = Runtime::address_to_asset_id(currency_address.into())
576                    .ok_or(revert("Failed to resolve fee asset id from address"))?;
577
578                log::trace!(target: "xcm-precompile::transfer_with_fee", "Raw arguments: currency_address: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
579                weight: {:?}, calculated asset_id: {:?}, fee: {:?}",
580                currency_address, amount_of_tokens, destination, weight, asset_id, fee);
581
582                orml_xtokens::Call::<Runtime>::transfer_with_fee {
583                    currency_id: asset_id.into(),
584                    amount: amount_of_tokens.into(),
585                    fee: fee.into(),
586                    dest: Box::new(VersionedLocation::V5(destination)),
587                    dest_weight_limit,
588                }
589            }
590        };
591
592        let origin = Some(Runtime::AddressMapping::into_account_id(
593            handle.context().caller,
594        ))
595        .into();
596
597        // Dispatch a call.
598        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
599
600        Ok(true)
601    }
602
603    #[precompile::public(
604        "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))"
605    )]
606    fn transfer_multiasset(
607        handle: &mut impl PrecompileHandle,
608        asset_location: Location,
609        amount_of_tokens: U256,
610        destination: Location,
611        weight: WeightV2,
612    ) -> EvmResult<bool> {
613        // Read call arguments
614        let amount_of_tokens: u128 = amount_of_tokens
615            .try_into()
616            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
617
618        let dest_weight_limit = if weight.is_zero() {
619            WeightLimit::Unlimited
620        } else {
621            WeightLimit::Limited(weight.get_weight())
622        };
623
624        log::trace!(target: "xcm-precompile::transfer_multiasset", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, destination: {:?}, \
625        weight: {:?}",
626        asset_location, amount_of_tokens, destination, weight);
627
628        let call = orml_xtokens::Call::<Runtime>::transfer_multiasset {
629            asset: Box::new(VersionedAsset::V5(
630                (asset_location, amount_of_tokens).into(),
631            )),
632            dest: Box::new(VersionedLocation::V5(destination)),
633            dest_weight_limit,
634        };
635
636        let origin = Some(Runtime::AddressMapping::into_account_id(
637            handle.context().caller,
638        ))
639        .into();
640
641        // Dispatch a call.
642        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
643
644        Ok(true)
645    }
646
647    #[precompile::public(
648        "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
649    )]
650    fn transfer_multiasset_with_fee(
651        handle: &mut impl PrecompileHandle,
652        asset_location: Location,
653        amount_of_tokens: U256,
654        fee: U256,
655        destination: Location,
656        weight: WeightV2,
657    ) -> EvmResult<bool> {
658        // Read call arguments
659        let amount_of_tokens: u128 = amount_of_tokens
660            .try_into()
661            .map_err(|_| revert("error converting amount_of_tokens, maybe value too large"))?;
662        let fee: u128 = fee.try_into().map_err(|_| revert("can't convert fee"))?;
663
664        let dest_weight_limit = if weight.is_zero() {
665            WeightLimit::Unlimited
666        } else {
667            WeightLimit::Limited(weight.get_weight())
668        };
669
670        log::trace!(target: "xcm-precompile::transfer_multiasset_with_fee", "Raw arguments: asset_location: {:?}, amount_of_tokens: {:?}, fee{:?}, destination: {:?}, \
671        weight: {:?}",
672        asset_location, amount_of_tokens, fee, destination, weight);
673
674        let call = orml_xtokens::Call::<Runtime>::transfer_multiasset_with_fee {
675            asset: Box::new(VersionedAsset::V5(
676                (asset_location.clone(), amount_of_tokens).into(),
677            )),
678            fee: Box::new(VersionedAsset::V5((asset_location, fee).into())),
679            dest: Box::new(VersionedLocation::V5(destination)),
680            dest_weight_limit,
681        };
682
683        let origin = Some(Runtime::AddressMapping::into_account_id(
684            handle.context().caller,
685        ))
686        .into();
687
688        // Dispatch a call.
689        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
690
691        Ok(true)
692    }
693
694    #[precompile::public(
695        "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
696    )]
697    fn transfer_multi_currencies(
698        handle: &mut impl PrecompileHandle,
699        currencies: BoundedVec<Currency, GetMaxAssets<Runtime>>,
700        fee_item: u32,
701        destination: Location,
702        weight: WeightV2,
703    ) -> EvmResult<bool> {
704        let currencies: Vec<_> = currencies.into();
705        let currencies = currencies
706            .into_iter()
707            .map(|currency| {
708                let currency_address: H160 = currency.get_address().into();
709                let amount = currency
710                    .get_amount()
711                    .try_into()
712                    .map_err(|_| revert("value too large: in currency"))?;
713
714                Ok((
715                    Runtime::address_to_asset_id(currency_address.into())
716                        .ok_or(revert("can't convert into currency id"))?
717                        .into(),
718                    amount,
719                ))
720            })
721            .collect::<EvmResult<_>>()?;
722        let dest_weight_limit = if weight.is_zero() {
723            WeightLimit::Unlimited
724        } else {
725            WeightLimit::Limited(weight.get_weight())
726        };
727
728        log::trace!(target: "xcm-precompile::transfer_multi_currencies", "Raw arguments: currencies: {:?}, fee_item{:?}, destination: {:?}, \
729        weight: {:?}",
730        currencies, fee_item, destination, weight);
731
732        let call = orml_xtokens::Call::<Runtime>::transfer_multicurrencies {
733            currencies,
734            fee_item,
735            dest: Box::new(VersionedLocation::V5(destination)),
736            dest_weight_limit,
737        };
738
739        let origin = Some(Runtime::AddressMapping::into_account_id(
740            handle.context().caller,
741        ))
742        .into();
743
744        // Dispatch a call.
745        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
746
747        Ok(true)
748    }
749
750    #[precompile::public(
751        "transfet_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
752    )]
753    fn transfer_multi_assets(
754        handle: &mut impl PrecompileHandle,
755        assets: BoundedVec<EvmMultiAsset, GetMaxAssets<Runtime>>,
756        fee_item: u32,
757        destination: Location,
758        weight: WeightV2,
759    ) -> EvmResult<bool> {
760        let assets: Vec<_> = assets.into();
761
762        let dest_weight_limit = if weight.is_zero() {
763            WeightLimit::Unlimited
764        } else {
765            WeightLimit::Limited(weight.get_weight())
766        };
767
768        log::trace!(target: "xcm-precompile::transfer_multi_assets", "Raw arguments: assets: {:?}, fee_item{:?}, destination: {:?}, \
769        weight: {:?}",
770        assets, fee_item, destination, weight);
771
772        let multiasset_vec: EvmResult<Vec<Asset>> = assets
773            .into_iter()
774            .map(|evm_multiasset| {
775                let to_balance: u128 = evm_multiasset
776                    .get_amount()
777                    .try_into()
778                    .map_err(|_| revert("value too large in assets"))?;
779                Ok((evm_multiasset.get_location(), to_balance).into())
780            })
781            .collect();
782
783        // Since multiassets sorts them, we need to check whether the index is still correct,
784        // and error otherwise as there is not much we can do other than that
785        let multiassets = Assets::from_sorted_and_deduplicated(multiasset_vec?).map_err(|_| {
786            revert("In field Assets, Provided assets either not sorted nor deduplicated")
787        })?;
788
789        let call = orml_xtokens::Call::<Runtime>::transfer_multiassets {
790            assets: Box::new(VersionedAssets::V5(multiassets)),
791            fee_item,
792            dest: Box::new(VersionedLocation::V5(destination)),
793            dest_weight_limit,
794        };
795
796        let origin = Some(Runtime::AddressMapping::into_account_id(
797            handle.context().caller,
798        ))
799        .into();
800
801        // Dispatch a call.
802        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
803
804        Ok(true)
805    }
806}
807
808#[derive(Debug, Clone, solidity::Codec)]
809pub struct WeightV2 {
810    ref_time: u64,
811    proof_size: u64,
812}
813
814impl WeightV2 {
815    pub fn from(ref_time: u64, proof_size: u64) -> Self {
816        WeightV2 {
817            ref_time,
818            proof_size,
819        }
820    }
821
822    pub fn get_weight(&self) -> Weight {
823        Weight::from_parts(self.ref_time, self.proof_size)
824    }
825
826    pub fn is_zero(&self) -> bool {
827        self.ref_time == 0u64
828    }
829}
830
831#[derive(Debug, Clone, solidity::Codec)]
832pub struct Currency {
833    address: Address,
834    amount: U256,
835}
836
837impl Currency {
838    pub fn get_address(&self) -> Address {
839        self.address
840    }
841
842    pub fn get_amount(&self) -> U256 {
843        self.amount
844    }
845}
846
847impl From<(Address, U256)> for Currency {
848    fn from(tuple: (Address, U256)) -> Self {
849        Currency {
850            address: tuple.0,
851            amount: tuple.1,
852        }
853    }
854}
855
856#[derive(Debug, Clone, solidity::Codec)]
857pub struct EvmMultiAsset {
858    location: Location,
859    amount: U256,
860}
861
862impl From<(Location, U256)> for EvmMultiAsset {
863    fn from(tuple: (Location, U256)) -> Self {
864        EvmMultiAsset {
865            location: tuple.0,
866            amount: tuple.1,
867        }
868    }
869}
870
871impl EvmMultiAsset {
872    pub fn get_location(&self) -> Location {
873        self.location.clone()
874    }
875
876    pub fn get_amount(&self) -> U256 {
877        self.amount
878    }
879}