// Copyright (C) Parity Technologies (UK) Ltd. // This file is part of Pezkuwi. // Pezkuwi is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Pezkuwi is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Pezkuwi. If not, see . //! Adapters to work with `pezframe_support::traits::Currency` through XCM. #![allow(deprecated)] use super::MintLocation; use core::{fmt::Debug, marker::PhantomData, result}; use pezframe_support::traits::{ExistenceRequirement::AllowDeath, Get, WithdrawReasons}; use pezsp_runtime::traits::CheckedSub; use xcm::latest::{Asset, Error as XcmError, Location, Result, XcmContext}; use xcm_executor::{ traits::{ConvertLocation, MatchesFungible, TransactAsset}, AssetsInHolding, }; /// Asset transaction errors. enum Error { /// The given asset is not handled. (According to [`XcmError::AssetNotFound`]) AssetNotHandled, /// `Location` to `AccountId` conversion failed. AccountIdConversionFailed, } impl From for XcmError { fn from(e: Error) -> Self { use XcmError::FailedToTransactAsset; match e { Error::AssetNotHandled => XcmError::AssetNotFound, Error::AccountIdConversionFailed => FailedToTransactAsset("AccountIdConversionFailed"), } } } /// Simple adapter to use a currency as asset transactor. This type can be used as `type /// AssetTransactor` in `xcm::Config`. /// /// # Example /// ``` /// use codec::Decode; /// use pezframe_support::{parameter_types, PalletId}; /// use pezsp_runtime::traits::{AccountIdConversion, TrailingZeroInput}; /// use xcm::latest::prelude::*; /// use pezstaging_xcm_builder::{ParentIsPreset, CurrencyAdapter, IsConcrete}; /// /// /// Our chain's account id. /// type AccountId = pezsp_runtime::AccountId32; /// /// /// Our relay chain's location. /// parameter_types! { /// pub RelayChain: Location = Parent.into(); /// pub CheckingAccount: AccountId = PalletId(*b"checking").into_account_truncating(); /// } /// /// /// Some items that implement `ConvertLocation`. Can be more, but for now we just assume we accept /// /// messages from the parent (relay chain). /// pub type LocationConverter = (ParentIsPreset); /// /// /// Just a dummy implementation of `Currency`. Normally this would be `Balances`. /// pub type CurrencyImpl = (); /// /// /// Final currency adapter. This can be used in `xcm::Config` to specify how asset related transactions happen. /// pub type AssetTransactor = CurrencyAdapter< /// // Use this `Currency` impl instance: /// CurrencyImpl, /// // The matcher: use the currency when the asset is a concrete asset in our relay chain. /// IsConcrete, /// // The local converter: default account of the parent relay chain. /// LocationConverter, /// // Our chain's account ID type. /// AccountId, /// // The checking account. Can be any deterministic inaccessible account. /// CheckingAccount, /// >; /// ``` #[deprecated = "Use `FungibleAdapter` instead"] pub struct CurrencyAdapter( PhantomData<(Currency, Matcher, AccountIdConverter, AccountId, CheckedAccount)>, ); impl< Currency: pezframe_support::traits::Currency, Matcher: MatchesFungible, AccountIdConverter: ConvertLocation, AccountId: Clone, // can't get away without it since Currency is generic over it. CheckedAccount: Get>, > CurrencyAdapter { 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(|error| { tracing::debug!(target: "xcm::currency_adapter", ?error, "Failed to ensure can withdraw"); XcmError::NotWithdrawable }) } fn accrue_checked(checked_account: AccountId, amount: Currency::Balance) { let _ = 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 { pezframe_support::defensive!( "`can_check_in` must have returned `true` immediately prior; qed" ); } } } impl< Currency: pezframe_support::traits::Currency, Matcher: MatchesFungible, AccountIdConverter: ConvertLocation, AccountId: Clone + Debug, // can't get away without it since Currency is generic over it. CheckedAccount: Get>, > TransactAsset for CurrencyAdapter { fn can_check_in(origin: &Location, what: &Asset, _context: &XcmContext) -> Result { tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "can_check_in origin"); // Check we handle this asset. 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: &Location, what: &Asset, _context: &XcmContext) { tracing::trace!(target: "xcm::currency_adapter", ?origin, ?what, "check_in origin"); 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: &Location, what: &Asset, _context: &XcmContext) -> Result { tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "can_check_out"); 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: &Location, what: &Asset, _context: &XcmContext) { tracing::trace!(target: "xcm::currency_adapter", ?dest, ?what, "check_out"); 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: &Asset, who: &Location, _context: Option<&XcmContext>) -> Result { tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "deposit_asset"); // Check we handle this asset. 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: &Asset, who: &Location, _maybe_context: Option<&XcmContext>, ) -> result::Result { tracing::trace!(target: "xcm::currency_adapter", ?what, ?who, "withdraw_asset"); // Check we handle this asset. let amount = Matcher::matches_fungible(what).ok_or(Error::AssetNotHandled)?; let who = AccountIdConverter::convert_location(who).ok_or(Error::AccountIdConversionFailed)?; let _ = Currency::withdraw(&who, amount, WithdrawReasons::TRANSFER, AllowDeath).map_err( |error| { tracing::debug!(target: "xcm::currency_adapter", ?error, ?who, ?amount, "Failed to withdraw asset"); XcmError::FailedToTransactAsset(error.into()) }, )?; Ok(what.clone().into()) } fn internal_transfer_asset( asset: &Asset, from: &Location, to: &Location, _context: &XcmContext, ) -> result::Result { tracing::trace!(target: "xcm::currency_adapter", ?asset, ?from, ?to, "internal_transfer_asset"); 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(|error| { tracing::debug!(target: "xcm::currency_adapter", ?error, ?from, ?to, ?amount, "Failed to transfer asset"); XcmError::FailedToTransactAsset(error.into()) })?; Ok(asset.clone().into()) } }