use super::MintLocation;
use frame_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons};
use sp_runtime::traits::CheckedSub;
use sp_std::{marker::PhantomData, result};
use xcm::latest::{Error as XcmError, MultiAsset, MultiLocation, Result, XcmContext};
use xcm_executor::{
traits::{ConvertLocation, MatchesFungible, TransactAsset},
Assets,
};
enum Error {
AssetNotHandled,
AccountIdConversionFailed,
}
impl From<Error> for XcmError {
fn from(e: Error) -> Self {
use XcmError::FailedToTransactAsset;
match e {
Error::AssetNotHandled => XcmError::AssetNotFound,
Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"),
}
}
}
pub struct CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>(
PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>,
);
impl<
Currency: frame_support::traits::Currency<AccountId>,
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
> CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
{
fn can_accrue_checked(_checked_account: AccountId, _amount: Currency::Balance) -> Result {
Ok(())
}
fn can_reduce_checked(checked_account: AccountId, amount: Currency::Balance) -> Result {
let new_balance = Currency::free_balance(&checked_account)
.checked_sub(&amount)
.ok_or(XcmError::NotWithdrawable)?;
Currency::ensure_can_withdraw(
&checked_account,
amount,
WithdrawReasons::TRANSFER,
new_balance,
)
.map_err(|_| XcmError::NotWithdrawable)
}
fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) {
Currency::deposit_creating(&checked_account, amount);
Currency::deactivate(amount);
}
fn reduce_checked(checked_account: AccountId, amount: Currency::Balance) {
let ok =
Currency::withdraw(&checked_account, amount, WithdrawReasons::TRANSFER, AllowDeath)
.is_ok();
if ok {
Currency::reactivate(amount);
} else {
frame_support::defensive!(
"`can_check_in` must have returned `true` immediately prior; qed"
);
}
}
}
impl<
Currency: frame_support::traits::Currency<AccountId>,
Matcher: MatchesFungible<Currency::Balance>,
AccountIdConverter: ConvertLocation<AccountId>,
AccountId: Clone, CheckedAccount: Get<Option<(AccountId, MintLocation)>>,
> TransactAsset
for CurrencyAdapter<Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount>
{
fn can_check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result {
log::trace!(target: "xcm::currency_adapter", "can_check_in origin: {:?}, what: {:?}", _origin, what);
let amount: Currency::Balance =
Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::can_reduce_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::can_accrue_checked(checked_account, amount),
None => Ok(()),
}
}
fn check_in(_origin: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
log::trace!(target: "xcm::currency_adapter", "check_in origin: {:?}, what: {:?}", _origin, what);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::reduce_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::accrue_checked(checked_account, amount),
None => (),
}
}
}
fn can_check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) -> Result {
log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what);
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::can_accrue_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::can_reduce_checked(checked_account, amount),
None => Ok(()),
}
}
fn check_out(_dest: &MultiLocation, what: &MultiAsset, _context: &XcmContext) {
log::trace!(target: "xcm::currency_adapter", "check_out dest: {:?}, what: {:?}", _dest, what);
if let Some(amount) = Matcher::matches_fungible(what) {
match CheckedAccount::get() {
Some((checked_account, MintLocation::Local)) =>
Self::accrue_checked(checked_account, amount),
Some((checked_account, MintLocation::NonLocal)) =>
Self::reduce_checked(checked_account, amount),
None => (),
}
}
}
fn deposit_asset(what: &MultiAsset, who: &MultiLocation, _context: &XcmContext) -> Result {
log::trace!(target: "xcm::currency_adapter", "deposit_asset what: {:?}, who: {:?}", what, who);
let amount = Matcher::matches_fungible(&what).ok_or(Error::AssetNotHandled)?;
let who =
AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
let _imbalance = Currency::deposit_creating(&who, amount);
Ok(())
}
fn withdraw_asset(
what: &MultiAsset,
who: &MultiLocation,
_maybe_context: Option<&XcmContext>,
) -> result::Result<Assets, XcmError> {
log::trace!(target: "xcm::currency_adapter", "withdraw_asset what: {:?}, who: {:?}", what, who);
let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?;
let who =
AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?;
Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(what.clone().into())
}
fn internal_transfer_asset(
asset: &MultiAsset,
from: &MultiLocation,
to: &MultiLocation,
_context: &XcmContext,
) -> result::Result<Assets, XcmError> {
log::trace!(target: "xcm::currency_adapter", "internal_transfer_asset asset: {:?}, from: {:?}, to: {:?}", asset, from, to);
let amount = Matcher::matches_fungible(asset).ok_or(Error::AssetNotHandled)?;
let from =
AccountIdConverter::convert_location(from).ok_or(Error::AccountIdConversionFailed)?;
let to =
AccountIdConverter::convert_location(to).ok_or(Error::AccountIdConversionFailed)?;
Currency::transfer(&from, &to, amount, AllowDeath)
.map_err(|e| XcmError::FailedToTransactAsset(e.into()))?;
Ok(asset.clone().into())
}
}