Newer
Older
//! # Xtokens Module
//!
//! ## Overview
//!
//! The xtokens module provides cross-chain token transfer functionality, by
//! cross-consensus messages(XCM).
//!
//! The xtokens module provides functions for
//! - Token transfer from parachains to relay chain.
//! - Token transfer between parachains, including relay chain tokens like DOT,
//! KSM, and parachain tokens like ACA, aUSD.
//!
//! ## Interface
//!
//! ### Dispatchable functions
//!
//! - `transfer`: Transfer local assets with given `CurrencyId` and `Amount`.
//! - `transfer_multiasset`: Transfer `MultiAsset` assets.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::from_over_into)]
#![allow(clippy::unused_unit)]
#![allow(clippy::large_enum_variant)]
use frame_support::{pallet_prelude::*, traits::Get, transactional, Parameter};
use frame_system::{ensure_signed, pallet_prelude::*};
use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert, MaybeSerializeDeserialize, Member, Zero},
DispatchError,
};
use sp_std::prelude::*;
use xcm::v0::{
Junction::*,
MultiAsset, MultiLocation, Order,
Order::*,
Xcm::{self, *},
};
use orml_traits::location::{Parse, Reserve};
use orml_xcm_support::XcmHandler;
mod mock;
mod tests;
pub use module::*;
#[frame_support::pallet]
pub mod module {
#[pallet::config]
pub trait Config: frame_system::Config {
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
type Balance: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Into<u128>;
type CurrencyId: Parameter + Member + Clone;
/// Convert `T::CurrencyIn` to `MultiLocation`.
type CurrencyIdConvert: Convert<Self::CurrencyId, Option<MultiLocation>>;
/// Convert `Self::Account` to `AccountId32`
type AccountId32Convert: Convert<Self::AccountId, [u8; 32]>;
/// Self chain location.
#[pallet::constant]
type SelfLocation: Get<MultiLocation>;
/// Xcm handler to execute XCM.
type XcmHandler: XcmHandler<Self::AccountId>;
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
#[pallet::metadata(T::AccountId = "AccountId", T::CurrencyId = "CurrencyId", T::Balance = "Balance")]
/// Transferred. \[sender, currency_id, amount, dest\]
Transferred(T::AccountId, T::CurrencyId, T::Balance, MultiLocation),
/// Transferred `MultiAsset`. \[sender, asset, dest\]
TransferredMultiAsset(T::AccountId, MultiAsset, MultiLocation),
pub enum Error<T> {
/// Asset has no reserve location.
AssetHasNoReserve,
/// Not cross-chain transfer.
NotCrossChainTransfer,
/// Invalid transfer destination.
InvalidDest,
/// Currency is not cross-chain transferable.
NotCrossChainTransferableCurrency,
#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {}
#[pallet::pallet]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(1000)]
pub fn transfer(
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
if amount == Zero::zero() {
return Ok(().into());
}
let id: MultiLocation = T::CurrencyIdConvert::convert(currency_id.clone())
.ok_or(Error::<T>::NotCrossChainTransferableCurrency)?;
let asset = MultiAsset::ConcreteFungible {
amount: amount.into(),
};
Self::do_transfer_multiasset(who.clone(), asset, dest.clone())?;
Self::deposit_event(Event::<T>::Transferred(who, currency_id, amount, dest));
#[pallet::weight(1000)]
pub fn transfer_multiasset(
dest: MultiLocation,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::do_transfer_multiasset(who.clone(), asset.clone(), dest.clone())?;
Self::deposit_event(Event::<T>::TransferredMultiAsset(who, asset, dest));
Ok(().into())
}
}
impl<T: Config> Pallet<T> {
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
/// Transfer `MultiAsset` without depositing event.
fn do_transfer_multiasset(
who: T::AccountId,
asset: MultiAsset,
dest: MultiLocation,
) -> DispatchResultWithPostInfo {
let (dest, recipient) = Self::ensure_valid_dest(dest)?;
let self_location = T::SelfLocation::get();
ensure!(dest != self_location, Error::<T>::NotCrossChainTransfer);
let reserve = asset.reserve().ok_or(Error::<T>::AssetHasNoReserve)?;
let xcm = if reserve == self_location {
Self::transfer_self_reserve_asset(asset, dest, recipient)
} else if reserve == dest {
Self::transfer_to_reserve(asset, dest, recipient)
} else {
Self::transfer_to_non_reserve(asset, reserve, dest, recipient)
};
T::XcmHandler::execute_xcm(who, xcm)?;
Ok(().into())
}
fn transfer_self_reserve_asset(asset: MultiAsset, dest: MultiLocation, recipient: MultiLocation) -> Xcm {
WithdrawAsset {
assets: vec![asset],
effects: vec![DepositReserveAsset {
assets: vec![MultiAsset::All],
dest,
effects: Self::deposit_asset(recipient),
}
}
fn transfer_to_reserve(asset: MultiAsset, reserve: MultiLocation, recipient: MultiLocation) -> Xcm {
WithdrawAsset {
assets: vec![asset],
effects: vec![InitiateReserveWithdraw {
reserve,
effects: Self::deposit_asset(recipient),
fn transfer_to_non_reserve(
asset: MultiAsset,
reserve: MultiLocation,
dest: MultiLocation,
let mut reanchored_dest = dest.clone();
if reserve == Parent.into() {
if let MultiLocation::X2(Parent, Parachain { id }) = dest {
reanchored_dest = Parachain { id }.into();
}
}
WithdrawAsset {
assets: vec![asset],
effects: vec![InitiateReserveWithdraw {
reserve,
effects: vec![DepositReserveAsset {
dest: reanchored_dest,
effects: Self::deposit_asset(recipient),
fn deposit_asset(recipient: MultiLocation) -> Vec<Order> {
vec![DepositAsset {
dest: recipient,
}]
}
fn is_zero_amount(asset: &MultiAsset) -> bool {
if let MultiAsset::ConcreteFungible { id: _, amount } = asset {
if *amount == Zero::zero() {
return true;
if let MultiAsset::AbstractFungible { id: _, amount } = asset {
if *amount == Zero::zero() {
return true;
}
}
false
}
/// Ensure has the `dest` has chain part and recipient part.
fn ensure_valid_dest(
dest: MultiLocation,
) -> sp_std::result::Result<(MultiLocation, MultiLocation), DispatchError> {
if let (Some(dest), Some(recipient)) = (dest.chain_part(), dest.non_chain_part()) {
Ok((dest, recipient))
} else {
Err(Error::<T>::InvalidDest.into())