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::{
22    resolve_transfer_type, split_location_into_chain_part_and_beneficiary, ASSET_HUB_PARA_ID,
23    XCM_SIZE_LIMIT,
24};
25use fp_evm::PrecompileHandle;
26use frame_support::{
27    dispatch::{GetDispatchInfo, PostDispatchInfo},
28    pallet_prelude::Weight,
29    traits::{ConstU32, Get},
30};
31use sp_runtime::traits::{Dispatchable, MaybeEquivalence};
32
33use pallet_evm::{AddressMapping, GasWeightMapping};
34use sp_core::{H160, H256, U256};
35
36use sp_std::marker::PhantomData;
37use sp_std::prelude::*;
38
39use xcm::{latest::prelude::*, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm};
40use xcm_executor::traits::TransferType;
41
42use pallet_evm_precompile_assets_erc20::AddressToAssetId;
43use pallet_xcm::WeightInfo as PalletXcmWeightInfo;
44use precompile_utils::prelude::*;
45#[cfg(test)]
46mod mock;
47#[cfg(test)]
48mod tests;
49
50/// Dummy H160 address representing native currency (e.g. ASTR or SDN)
51const NATIVE_ADDRESS: H160 = H160::zero();
52
53/// Bound for the SCALE-encoded XCM blob accepted by the (deprecated) `send_xcm`.
54type GetXcmSizeLimit = ConstU32<XCM_SIZE_LIMIT>;
55
56/// Max number of assets a single cross-chain transfer may carry.
57pub const MAX_ASSETS_FOR_TRANSFER: u32 = 2;
58
59/// Bound for the `BoundedVec` arguments of the asset-list based methods.
60pub type GetMaxAssets = ConstU32<MAX_ASSETS_FOR_TRANSFER>;
61
62/// Default proof_size of 256KB
63const DEFAULT_PROOF_SIZE: u64 = 1024 * 256;
64
65/// Bound for the `Transact` call blob `remote_transact` forwards.
66pub const REMOTE_CALL_SIZE_LIMIT: u32 = 64 * 1024;
67
68/// Bound for the `remote_call` argument of `remote_transact`.
69pub type GetRemoteCallSizeLimit = ConstU32<REMOTE_CALL_SIZE_LIMIT>;
70
71/// Revert reason for `send_xcm`.
72const SEND_XCM_UNSUPPORTED: &str =
73    "send_xcm is not supported: sending an arbitrary XCM requires Root. \
74     Use remote_transact(uint256,bool,address,uint256,bytes,uint64) for a sibling Transact.";
75
76/// A precompile that expose XCM related functions.
77pub struct XcmPrecompile<Runtime, C>(PhantomData<(Runtime, C)>);
78
79#[precompile_utils::precompile]
80#[precompile::test_concrete_types(mock::Runtime, mock::AssetIdConverter<mock::AssetId>)]
81impl<Runtime, C> XcmPrecompile<Runtime, C>
82where
83    Runtime: pallet_evm::Config
84        + pallet_xcm::Config
85        + pallet_assets::Config
86        + AddressToAssetId<<Runtime as pallet_assets::Config>::AssetId>,
87    <<Runtime as frame_system::Config>::RuntimeCall as Dispatchable>::RuntimeOrigin:
88        From<Option<Runtime::AccountId>>,
89    <Runtime as frame_system::Config>::RuntimeCall: From<pallet_xcm::Call<Runtime>>
90        + Dispatchable<PostInfo = PostDispatchInfo>
91        + GetDispatchInfo,
92    C: MaybeEquivalence<Location, <Runtime as pallet_assets::Config>::AssetId>,
93    <Runtime as pallet_evm::Config>::AddressMapping: AddressMapping<Runtime::AccountId>,
94    Runtime::AccountId: Into<[u8; 32]>,
95{
96    // ------------------------------------------------------------------------------------------
97    // Asset transfers. Every selector below funnels into `do_transfer`, which dispatches
98    // `pallet_xcm::transfer_assets_using_type_and_then` - the reserve is derived per asset rather
99    // than chosen by the caller.
100    // ------------------------------------------------------------------------------------------
101
102    /// Transfer XC20 assets to an `AccountId32` beneficiary on the relay chain or a sibling.
103    #[precompile::public("assets_withdraw(address[],uint256[],bytes32,bool,uint256,uint256)")]
104    fn assets_withdraw_native_v1(
105        handle: &mut impl PrecompileHandle,
106        assets: BoundedVec<Address, GetMaxAssets>,
107        amounts: BoundedVec<U256, GetMaxAssets>,
108        recipient_account_id: H256,
109        is_relay: bool,
110        parachain_id: U256,
111        fee_index: U256,
112    ) -> EvmResult<bool> {
113        Self::assets_transfer_v1(
114            handle,
115            assets,
116            amounts,
117            Self::beneficiary_32(recipient_account_id),
118            is_relay,
119            parachain_id,
120            fee_index,
121            false,
122        )
123    }
124
125    /// As above, with an `AccountKey20` beneficiary. Substrate-native destinations generally
126    /// cannot resolve one - prefer the `bytes32` overload unless the destination accepts it.
127    #[precompile::public("assets_withdraw(address[],uint256[],address,bool,uint256,uint256)")]
128    fn assets_withdraw_evm_v1(
129        handle: &mut impl PrecompileHandle,
130        assets: BoundedVec<Address, GetMaxAssets>,
131        amounts: BoundedVec<U256, GetMaxAssets>,
132        recipient_account_id: Address,
133        is_relay: bool,
134        parachain_id: U256,
135        fee_index: U256,
136    ) -> EvmResult<bool> {
137        Self::assets_transfer_v1(
138            handle,
139            assets,
140            amounts,
141            Self::beneficiary_key_20(recipient_account_id),
142            is_relay,
143            parachain_id,
144            fee_index,
145            false,
146        )
147    }
148
149    /// As `assets_withdraw`, except the zero address is read as the native token.
150    ///
151    /// That is the only difference between the two names, and it has always been so: widening
152    /// `assets_withdraw` instead would turn a caller's uninitialised address from a revert into a
153    /// native-balance transfer.
154    #[precompile::public(
155        "assets_reserve_transfer(address[],uint256[],bytes32,bool,uint256,uint256)"
156    )]
157    fn assets_reserve_transfer_native_v1(
158        handle: &mut impl PrecompileHandle,
159        assets: BoundedVec<Address, GetMaxAssets>,
160        amounts: BoundedVec<U256, GetMaxAssets>,
161        recipient_account_id: H256,
162        is_relay: bool,
163        parachain_id: U256,
164        fee_index: U256,
165    ) -> EvmResult<bool> {
166        Self::assets_transfer_v1(
167            handle,
168            assets,
169            amounts,
170            Self::beneficiary_32(recipient_account_id),
171            is_relay,
172            parachain_id,
173            fee_index,
174            true,
175        )
176    }
177
178    /// As `assets_reserve_transfer` above, with an `AccountKey20` beneficiary.
179    #[precompile::public(
180        "assets_reserve_transfer(address[],uint256[],address,bool,uint256,uint256)"
181    )]
182    fn assets_reserve_transfer_evm_v1(
183        handle: &mut impl PrecompileHandle,
184        assets: BoundedVec<Address, GetMaxAssets>,
185        amounts: BoundedVec<U256, GetMaxAssets>,
186        recipient_account_id: Address,
187        is_relay: bool,
188        parachain_id: U256,
189        fee_index: U256,
190    ) -> EvmResult<bool> {
191        Self::assets_transfer_v1(
192            handle,
193            assets,
194            amounts,
195            Self::beneficiary_key_20(recipient_account_id),
196            is_relay,
197            parachain_id,
198            fee_index,
199            true,
200        )
201    }
202
203    /// Transfer a single token - native currency (zero address) or an XC20 - to a combined
204    /// destination location that embeds the beneficiary.
205    #[precompile::public("transfer(address,uint256,(uint8,bytes[]),(uint64,uint64))")]
206    fn transfer(
207        handle: &mut impl PrecompileHandle,
208        currency_address: Address,
209        amount_of_tokens: U256,
210        destination: Location,
211        weight: WeightV2,
212    ) -> EvmResult<bool> {
213        let currency_address: H160 = currency_address.into();
214        // Special case where zero address maps to native token by convention.
215        let asset_location = if currency_address == NATIVE_ADDRESS {
216            Location::here()
217        } else {
218            let asset_id = Runtime::address_to_asset_id(currency_address)
219                .ok_or(revert("Failed to resolve asset id from address"))?;
220            C::convert_back(&asset_id).ok_or(revert(
221                "Failed to resolve asset multilocation from local id",
222            ))?
223        };
224        let asset: Asset = (asset_location, Self::amount(amount_of_tokens)?).into();
225
226        Self::transfer_to_combined_destination(handle, asset.into(), 0, destination, weight)
227    }
228
229    /// As `transfer`, with the asset named by its location instead of its XC20 address.
230    #[precompile::public(
231        "transfer_multiasset((uint8,bytes[]),uint256,(uint8,bytes[]),(uint64,uint64))"
232    )]
233    fn transfer_multiasset(
234        handle: &mut impl PrecompileHandle,
235        asset_location: Location,
236        amount_of_tokens: U256,
237        destination: Location,
238        weight: WeightV2,
239    ) -> EvmResult<bool> {
240        let asset: Asset = (asset_location, Self::amount(amount_of_tokens)?).into();
241
242        Self::transfer_to_combined_destination(handle, asset.into(), 0, destination, weight)
243    }
244
245    /// Transfer several XC20 assets to a combined destination location, `fee_item` naming the one
246    /// that pays for execution there.
247    #[precompile::public(
248        "transfer_multi_currencies((address,uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
249    )]
250    fn transfer_multi_currencies(
251        handle: &mut impl PrecompileHandle,
252        currencies: BoundedVec<Currency, GetMaxAssets>,
253        fee_item: u32,
254        destination: Location,
255        weight: WeightV2,
256    ) -> EvmResult<bool> {
257        let currencies: Vec<Currency> = currencies.into();
258        let unsorted = currencies
259            .into_iter()
260            .map(|currency| {
261                Ok((
262                    Self::asset_location(currency.get_address().into(), false)
263                        .ok_or(revert("can't convert into currency id"))?,
264                    Self::amount(currency.get_amount())?,
265                )
266                    .into())
267            })
268            .collect::<EvmResult<Vec<Asset>>>()?;
269
270        let assets: Assets = unsorted.clone().into();
271        let fee_item = Self::fee_index_after_sort(&unsorted, &assets, fee_item)?;
272
273        Self::transfer_to_combined_destination(handle, assets, fee_item, destination, weight)
274    }
275
276    /// As `transfer_multi_currencies`, with the assets named by their locations.
277    ///
278    /// The list must already be sorted and deduplicated, since `fee_item` indexes it.
279    #[precompile::public(
280        "transfer_multi_assets(((uint8,bytes[]),uint256)[],uint32,(uint8,bytes[]),(uint64,uint64))"
281    )]
282    fn transfer_multi_assets(
283        handle: &mut impl PrecompileHandle,
284        assets: BoundedVec<EvmMultiAsset, GetMaxAssets>,
285        fee_item: u32,
286        destination: Location,
287        weight: WeightV2,
288    ) -> EvmResult<bool> {
289        let assets: Vec<EvmMultiAsset> = assets.into();
290        let assets = assets
291            .into_iter()
292            .map(|asset| Ok((asset.get_location(), Self::amount(asset.get_amount())?).into()))
293            .collect::<EvmResult<Vec<Asset>>>()?;
294
295        let assets = Assets::from_sorted_and_deduplicated(assets).map_err(|_| {
296            revert("In field Assets, Provided assets either not sorted nor deduplicated")
297        })?;
298
299        Self::transfer_to_combined_destination(handle, assets, fee_item, destination, weight)
300    }
301
302    /// As `transfer`, with the destination's fee named separately.
303    #[precompile::public(
304        "transfer_with_fee(address,uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
305    )]
306    fn transfer_with_fee(
307        handle: &mut impl PrecompileHandle,
308        currency_address: Address,
309        amount_of_tokens: U256,
310        fee: U256,
311        destination: Location,
312        weight: WeightV2,
313    ) -> EvmResult<bool> {
314        Self::transfer(
315            handle,
316            currency_address,
317            Self::total_with_fee(amount_of_tokens, fee)?,
318            destination,
319            weight,
320        )
321    }
322
323    /// As `transfer_with_fee`, with the asset named by its location.
324    #[precompile::public(
325        "transfer_multiasset_with_fee((uint8,bytes[]),uint256,uint256,(uint8,bytes[]),(uint64,uint64))"
326    )]
327    fn transfer_multiasset_with_fee(
328        handle: &mut impl PrecompileHandle,
329        asset_location: Location,
330        amount_of_tokens: U256,
331        fee: U256,
332        destination: Location,
333        weight: WeightV2,
334    ) -> EvmResult<bool> {
335        Self::transfer_multiasset(
336            handle,
337            asset_location,
338            Self::total_with_fee(amount_of_tokens, fee)?,
339            destination,
340            weight,
341        )
342    }
343
344    // ------------------------------------------------------------------------------------------
345    // Remote execution.
346    // ------------------------------------------------------------------------------------------
347
348    /// Send a `Transact` to a sibling parachain, buying its execution with `fee_asset_addr`.
349    ///
350    /// `pallet_xcm::send` is `Root`-only, so the message goes to the router directly with the
351    /// caller descended into the origin - byte for byte what the extrinsic built for a signed
352    /// origin, so the account the destination derives does not move.
353    #[precompile::public("remote_transact(uint256,bool,address,uint256,bytes,uint64)")]
354    fn remote_transact_v1(
355        handle: &mut impl PrecompileHandle,
356        para_id: U256,
357        is_relay: bool,
358        fee_asset_addr: Address,
359        fee_amount: U256,
360        remote_call: BoundedBytes<GetRemoteCallSizeLimit>,
361        transact_weight: u64,
362    ) -> EvmResult<bool> {
363        if is_relay {
364            return Err(revert(
365                "remote_transact to the relay chain is not supported, use a sibling parachain",
366            ));
367        }
368
369        let dest = Self::chain_part(false, para_id)?;
370        let remote_call: Vec<u8> = remote_call.into();
371        let remote_call_len = remote_call.len() as u64;
372
373        let fee_asset_addr: H160 = fee_asset_addr.into();
374        // Special case where zero address maps to native token by convention.
375        let fee_asset = if fee_asset_addr == NATIVE_ADDRESS {
376            Location::here()
377        } else {
378            let fee_asset_id = Runtime::address_to_asset_id(fee_asset_addr)
379                .ok_or(revert("Failed to resolve fee asset id from address"))?;
380            C::convert_back(&fee_asset_id).ok_or(revert(
381                "Failed to resolve fee asset multilocation from local id",
382            ))?
383        };
384        let fee: Asset = (fee_asset, Self::amount(fee_amount)?).into();
385
386        let context = <Runtime as pallet_xcm::Config>::UniversalLocation::get();
387        let fee = fee
388            .reanchored(&dest, &context)
389            .map_err(|_| revert("Failed to reanchor fee asset"))?;
390
391        let message = Xcm(vec![
392            WithdrawAsset(fee.clone().into()),
393            BuyExecution {
394                fees: fee,
395                weight_limit: WeightLimit::Unlimited,
396            },
397            Transact {
398                origin_kind: OriginKind::SovereignAccount,
399                fallback_max_weight: Some(Weight::from_parts(transact_weight, DEFAULT_PROOF_SIZE)),
400                call: remote_call.into(),
401            },
402        ]);
403
404        // The interior `pallet_xcm::send` derived for a signed origin: `SignedToAccountId32` with
405        // the network this chain lives in, which `UniversalLocation` already carries. Refuse to
406        // guess it - `network: None` is a different location to the destination, so it would
407        // silently move every caller's derived sovereign account there.
408        let network = context.global_consensus().map_err(|_| {
409            revert(
410                "UniversalLocation carries no global consensus: cannot derive the caller's origin",
411            )
412        })?;
413        let interior = Junction::AccountId32 {
414            network: Some(network),
415            id: Runtime::AddressMapping::into_account_id(handle.context().caller).into(),
416        };
417
418        log::trace!(target: "xcm-precompile:remote_transact", "dest: {:?}, interior: {:?}, message: {:?}", dest, interior, message);
419
420        let weight = <Runtime as pallet_xcm::Config>::WeightInfo::send()
421            .saturating_add(Weight::from_parts(0, remote_call_len));
422        RuntimeHelper::<Runtime>::record_external_cost(handle, weight, 0)?;
423        handle.record_cost(
424            <Runtime as pallet_evm::Config>::GasWeightMapping::weight_to_gas(weight),
425        )?;
426
427        pallet_xcm::Pallet::<Runtime>::send_xcm(interior, dest, message).map_err(|error| {
428            log::trace!(target: "xcm-precompile:remote_transact", "send_xcm failed: {:?}", error);
429            revert("Failed to send xcm")
430        })?;
431
432        Ok(true)
433    }
434
435    // ------------------------------------------------------------------------------------------
436    // Deprecated selectors.
437    // ------------------------------------------------------------------------------------------
438
439    /// An arbitrary XCM to an arbitrary destination from a signed origin is what
440    /// `SendXcmOrigin` was locked down to `Root` to prevent.
441    #[precompile::public("send_xcm((uint8,bytes[]),bytes)")]
442    fn send_xcm(
443        handle: &mut impl PrecompileHandle,
444        dest: Location,
445        xcm_call: BoundedBytes<GetXcmSizeLimit>,
446    ) -> EvmResult<bool> {
447        let _ = (handle, dest, xcm_call);
448        Err(revert(SEND_XCM_UNSUPPORTED))
449    }
450
451    // ------------------------------------------------------------------------------------------
452    // Internals.
453    // ------------------------------------------------------------------------------------------
454
455    /// Shared body of the four `assets_*` selectors, which name the destination chain with the
456    /// `is_relay` / `parachain_id` pair and carry the beneficiary separately.
457    ///
458    /// `native_address` is the one behavioural difference between the two names: only
459    /// `assets_reserve_transfer` has ever read the zero address as the native token.
460    fn assets_transfer_v1(
461        handle: &mut impl PrecompileHandle,
462        assets: BoundedVec<Address, GetMaxAssets>,
463        amounts: BoundedVec<U256, GetMaxAssets>,
464        beneficiary: Location,
465        is_relay: bool,
466        parachain_id: U256,
467        fee_index: U256,
468        native_address: bool,
469    ) -> EvmResult<bool> {
470        let addresses: Vec<Address> = assets.into();
471        let locations = addresses
472            .into_iter()
473            .filter_map(|address| Self::asset_location(address.into(), native_address))
474            .collect::<Vec<Location>>();
475
476        let amounts: Vec<U256> = amounts.into();
477        let amounts = amounts
478            .into_iter()
479            .map(Self::amount)
480            .collect::<EvmResult<Vec<u128>>>()?;
481
482        // Check that assets list is valid:
483        // * all assets resolved to multi-location
484        // * all assets has corresponded amount
485        if locations.len() != amounts.len() || locations.is_empty() {
486            return Err(revert("Assets resolution failure."));
487        }
488
489        let assets = locations
490            .into_iter()
491            .zip(amounts)
492            .map(Into::into)
493            .collect::<Vec<Asset>>();
494
495        let fee_index: u32 = fee_index
496            .try_into()
497            .map_err(|_| revert("error converting fee_index, maybe value too large"))?;
498
499        Self::do_transfer(
500            handle,
501            assets.into(),
502            fee_index,
503            Self::chain_part(is_relay, parachain_id)?,
504            beneficiary,
505            WeightLimit::Unlimited,
506        )
507    }
508
509    /// Shared body of the selectors that take one location holding both the destination chain and
510    /// the beneficiary.
511    fn transfer_to_combined_destination(
512        handle: &mut impl PrecompileHandle,
513        assets: Assets,
514        fee_index: u32,
515        destination: Location,
516        weight: WeightV2,
517    ) -> EvmResult<bool> {
518        let (dest, beneficiary) = split_location_into_chain_part_and_beneficiary(destination)
519            .ok_or(revert(
520                "error splitting destination into chain and beneficiary",
521            ))?;
522
523        // Without one the destination deposits to itself and the assets are trapped there.
524        if beneficiary == Location::here() {
525            return Err(revert(
526                "destination carries no beneficiary: append the recipient junction to it",
527            ));
528        }
529
530        Self::do_transfer(
531            handle,
532            assets,
533            fee_index,
534            dest,
535            beneficiary,
536            Self::weight_limit(&weight)?,
537        )
538    }
539
540    /// Resolve the reserves and hand the transfer to pallet-xcm.
541    fn do_transfer(
542        handle: &mut impl PrecompileHandle,
543        assets: Assets,
544        fee_index: u32,
545        dest: Location,
546        beneficiary: Location,
547        weight_limit: WeightLimit,
548    ) -> EvmResult<bool> {
549        if assets.len() == 0 {
550            return Err(revert("Assets resolution failure."));
551        }
552
553        let dest = Self::redirect_relay_to_asset_hub(&assets, dest);
554        Self::ensure_dot_transfer_policy(assets.inner(), &dest)?;
555
556        let (assets_transfer_type, fees_transfer_type, fee_asset_id) =
557            Self::resolve_transfer_types(&assets, fee_index, &dest)?;
558
559        log::trace!(target: "xcm-precompile:transfer", "assets: {:?}, dest: {:?}, beneficiary: {:?}, transfer types: {:?}/{:?}", assets, dest, beneficiary, assets_transfer_type, fees_transfer_type);
560
561        let call = pallet_xcm::Call::<Runtime>::transfer_assets_using_type_and_then {
562            dest: Box::new(VersionedLocation::V5(dest)),
563            assets: Box::new(VersionedAssets::V5(assets.clone())),
564            assets_transfer_type: Box::new(assets_transfer_type),
565            remote_fees_id: Box::new(VersionedAssetId::V5(fee_asset_id)),
566            fees_transfer_type: Box::new(fees_transfer_type),
567            custom_xcm_on_dest: Box::new(VersionedXcm::V5(Self::deposit_to_beneficiary(
568                assets.len() as u32,
569                beneficiary,
570            ))),
571            weight_limit,
572        };
573
574        let origin = Some(Runtime::AddressMapping::into_account_id(
575            handle.context().caller,
576        ))
577        .into();
578
579        RuntimeHelper::<Runtime>::try_dispatch(handle, origin, call, 0)?;
580
581        Ok(true)
582    }
583
584    /// The relay chain holds no reserve for this chain's own token - Asset Hub does. Substitute
585    /// the destination rather than depositing assets on a chain that cannot account for them
586    fn redirect_relay_to_asset_hub(assets: &Assets, dest: Location) -> Location {
587        let local_asset_present = assets
588            .inner()
589            .iter()
590            .any(|asset| asset.id.0 == Location::here());
591
592        if dest == Location::parent() && local_asset_present {
593            Location::new(1, [Junction::Parachain(ASSET_HUB_PARA_ID)])
594        } else {
595            dest
596        }
597    }
598
599    /// The destination chain named by the legacy `is_relay` / `parachain_id` pair.
600    fn chain_part(is_relay: bool, parachain_id: U256) -> EvmResult<Location> {
601        if is_relay {
602            return Ok(Location::parent());
603        }
604
605        let parachain_id: u32 = parachain_id
606            .try_into()
607            .map_err(|_| revert("error converting parachain_id, maybe value too large"))?;
608
609        Ok(Junctions::from(Junction::Parachain(parachain_id)).into_exterior(1))
610    }
611
612    /// XC20 address to asset location. `native_address` allows the zero address to stand for the
613    /// native token, which only some selectors have ever accepted.
614    ///
615    /// Returns `None` rather than reverting: each selector keeps the wording it has always used.
616    fn asset_location(address: H160, native_address: bool) -> Option<Location> {
617        if native_address && address == NATIVE_ADDRESS {
618            return Some(Location::here());
619        }
620
621        Runtime::address_to_asset_id(address).and_then(|id| C::convert_back(&id))
622    }
623
624    fn amount(amount: U256) -> EvmResult<u128> {
625        let amount: u128 = amount
626            .try_into()
627            .map_err(|_| revert("error converting amount, maybe value too large"))?;
628
629        if amount == 0 {
630            return Err(revert("amount must be greater than zero"));
631        }
632
633        Ok(amount)
634    }
635
636    fn total_with_fee(amount_of_tokens: U256, fee: U256) -> EvmResult<U256> {
637        amount_of_tokens
638            .checked_add(fee)
639            .ok_or(revert("error adding fee to amount, maybe value too large"))
640    }
641
642    /// The `WeightLimit` a caller's `(ref_time, proof_size)` pair asks for.
643    ///
644    /// `(0, 0)` is the documented spelling of `Unlimited`. Neither mixed form is reinterpreted:
645    /// `(0, n)` would silently drop the caller's proof-size limit, and `(n, 0)` is weighed as
646    /// overweight by every destination, which strands the assets there instead of here.
647    fn weight_limit(weight: &WeightV2) -> EvmResult<WeightLimit> {
648        match (weight.ref_time, weight.proof_size) {
649            (0, 0) => Ok(WeightLimit::Unlimited),
650            (0, _) => Err(revert(
651                "weight.ref_time is zero but weight.proof_size is not: pass (0, 0) for an \
652                 unlimited weight limit",
653            )),
654            (_, 0) => Err(revert(
655                "weight.proof_size is zero: every destination weighs a message with a non-zero \
656                 proof size, so the transfer would be rejected there as overweight",
657            )),
658            (ref_time, proof_size) => Ok(WeightLimit::Limited(Weight::from_parts(
659                ref_time, proof_size,
660            ))),
661        }
662    }
663
664    /// `fee_item` indexes the list in the order the caller wrote it, but `Assets` sorts and merges
665    /// what it is built from. Map the caller's index onto the sorted list rather than letting it
666    /// slide onto a neighbouring asset.
667    fn fee_index_after_sort(unsorted: &[Asset], sorted: &Assets, fee_item: u32) -> EvmResult<u32> {
668        let fee_asset_id = unsorted
669            .get(fee_item as usize)
670            .ok_or(revert("fee_index is out of bounds of the assets list"))?
671            .id
672            .clone();
673
674        sorted
675            .inner()
676            .iter()
677            .position(|asset| asset.id == fee_asset_id)
678            .map(|index| index as u32)
679            .ok_or(revert("fee_index is out of bounds of the assets list"))
680    }
681
682    /// `AccountId32` beneficiary, as the `bytes32` overloads take it.
683    fn beneficiary_32(recipient_account_id: H256) -> Location {
684        Junction::AccountId32 {
685            network: None,
686            id: recipient_account_id.into(),
687        }
688        .into()
689    }
690
691    /// `AccountKey20` beneficiary, as the `address` overloads take it.
692    fn beneficiary_key_20(recipient_account_id: Address) -> Location {
693        Junction::AccountKey20 {
694            network: None,
695            key: recipient_account_id.0.to_fixed_bytes(),
696        }
697        .into()
698    }
699
700    /// Picks the reserve model for `assets` and, separately, for the fee asset at
701    /// `fee_asset_item`.
702    fn resolve_transfer_types(
703        assets: &Assets,
704        fee_asset_item: u32,
705        dest: &Location,
706    ) -> EvmResult<(TransferType, TransferType, AssetId)> {
707        let assets = assets.inner();
708        let fee_asset = assets
709            .get(fee_asset_item as usize)
710            .ok_or(revert("fee_index is out of bounds of the assets list"))?;
711
712        let resolve = |asset: &Asset| {
713            resolve_transfer_type::<<Runtime as pallet_xcm::Config>::XcmExecutor>(asset, dest)
714                .ok_or(revert("cannot determine the reserve location for asset"))
715        };
716
717        let fees_transfer_type = resolve(fee_asset)?;
718
719        let mut assets_transfer_type = None;
720        for (idx, asset) in assets.iter().enumerate() {
721            if idx == fee_asset_item as usize {
722                continue;
723            }
724            let transfer_type = resolve(asset)?;
725            match &assets_transfer_type {
726                Some(existing) if existing != &transfer_type => {
727                    return Err(revert("all non-fee assets must share the same reserve"))
728                }
729                Some(_) => {}
730                None => assets_transfer_type = Some(transfer_type),
731            }
732        }
733
734        // A lone asset also acts as the fee asset.
735        let assets_transfer_type =
736            assets_transfer_type.unwrap_or_else(|| fees_transfer_type.clone());
737
738        Ok((
739            assets_transfer_type,
740            fees_transfer_type,
741            fee_asset.id.clone(),
742        ))
743    }
744
745    /// The XCM executed on the destination chain: hand everything that survived the transfer to the
746    /// beneficiary.
747    fn deposit_to_beneficiary(assets_count: u32, beneficiary: Location) -> Xcm<()> {
748        Xcm(vec![DepositAsset {
749            assets: Wild(AllCounted(assets_count)),
750            beneficiary,
751        }])
752    }
753
754    /// Enforces DOT transfer routing policy.
755    ///
756    /// Currently prevents direct DOT transfers to the relay chain,
757    /// requiring routing through AssetHub (parachain 1000).
758    ///
759    /// `dest_chain` is the destination *chain* location - it must not carry the beneficiary.
760    fn ensure_dot_transfer_policy(assets: &[Asset], dest_chain: &Location) -> EvmResult<()> {
761        if dest_chain != &Location::parent() {
762            return Ok(());
763        }
764
765        let deprecated_dot_location = Location::new(1, Junctions::Here);
766
767        for asset in assets {
768            let AssetId(location) = &asset.id;
769            if location == &deprecated_dot_location {
770                return Err(revert(
771                    "DOT cannot be sent directly to the relay. \
772                 Route via AssetHub (parachain 1000).",
773                ));
774            }
775        }
776
777        Ok(())
778    }
779}
780
781#[derive(Debug, Clone, solidity::Codec)]
782pub struct WeightV2 {
783    ref_time: u64,
784    proof_size: u64,
785}
786
787impl WeightV2 {
788    pub fn from(ref_time: u64, proof_size: u64) -> Self {
789        WeightV2 {
790            ref_time,
791            proof_size,
792        }
793    }
794
795    pub fn get_weight(&self) -> Weight {
796        Weight::from_parts(self.ref_time, self.proof_size)
797    }
798}
799
800#[derive(Debug, Clone, solidity::Codec)]
801pub struct Currency {
802    address: Address,
803    amount: U256,
804}
805
806impl Currency {
807    pub fn get_address(&self) -> Address {
808        self.address
809    }
810
811    pub fn get_amount(&self) -> U256 {
812        self.amount
813    }
814}
815
816impl From<(Address, U256)> for Currency {
817    fn from(tuple: (Address, U256)) -> Self {
818        Currency {
819            address: tuple.0,
820            amount: tuple.1,
821        }
822    }
823}
824
825#[derive(Debug, Clone, solidity::Codec)]
826pub struct EvmMultiAsset {
827    location: Location,
828    amount: U256,
829}
830
831impl From<(Location, U256)> for EvmMultiAsset {
832    fn from(tuple: (Location, U256)) -> Self {
833        EvmMultiAsset {
834            location: tuple.0,
835            amount: tuple.1,
836        }
837    }
838}
839
840impl EvmMultiAsset {
841    pub fn get_location(&self) -> Location {
842        self.location.clone()
843    }
844
845    pub fn get_amount(&self) -> U256 {
846        self.amount
847    }
848}