// KILT Blockchain – https://botlabs.org
// Copyright (C) 2019-2024 BOTLabs GmbH
// The KILT Blockchain 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.
// The KILT Blockchain 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 this program. If not, see .
// If you feel like getting in touch with us, you can do so at info@botlabs.org
use frame_support::{
ensure,
traits::{fungible::Mutate, tokens::Preservation},
};
use sp_std::marker::PhantomData;
use xcm::v4::{Asset, AssetId, Error, Fungibility, Location, Result, XcmContext};
use xcm_executor::traits::{ConvertLocation, TransactAsset};
use crate::{traits::SwitchHooks, Config, Event, LocalCurrencyBalanceOf, Pallet, SwitchPair};
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
const LOG_TARGET: &str = "xcm::pallet-asset-switch::SwitchPairRemoteAssetTransactor";
/// Type implementing [TransactAsset] that moves from the switch pair pool
/// account, if present, as many local tokens as remote assets received into
/// the specified `Location` if the incoming asset ID matches the remote
/// asset ID as specified in the switch pair and if they are both fungible.
pub struct SwitchPairRemoteAssetTransactor(PhantomData<(AccountIdConverter, T, I)>);
impl TransactAsset for SwitchPairRemoteAssetTransactor
where
AccountIdConverter: ConvertLocation,
T: Config,
I: 'static,
{
fn deposit_asset(what: &Asset, who: &Location, context: Option<&XcmContext>) -> Result {
log::info!(target: LOG_TARGET, "deposit_asset {:?} {:?} {:?}", what, who, context);
// 1. Verify the switch pair exists.
let switch_pair = SwitchPair::::get().ok_or(Error::AssetNotFound)?;
// 2. Verify the asset matches the other side of the switch pair.
let remote_asset_id_v4: AssetId = switch_pair.remote_asset_id.clone().try_into().map_err(|e| {
log::error!(
target: LOG_TARGET,
"Failed to convert stored asset ID {:?} into required version with error {:?}.",
switch_pair.remote_asset_id,
e
);
Error::AssetNotFound
})?;
ensure!(remote_asset_id_v4 == what.id, Error::AssetNotFound);
// 3. Verify the asset being deposited is fungible.
let Fungibility::Fungible(fungible_amount) = what.fun else {
return Err(Error::AssetNotFound);
};
// After this ensure, we know we need to be transacting with this asset, so any
// errors thrown from here onwards is a `FailedToTransactAsset` error.
// 4. Verify the switch pair is running.
ensure!(
switch_pair.is_enabled(),
Error::FailedToTransactAsset("switch pair is not running.",)
);
let beneficiary = AccountIdConverter::convert_location(who).ok_or(Error::FailedToTransactAsset(
"Failed to convert beneficiary to valid account.",
))?;
// 5. Call into the pre-switch hook
T::SwitchHooks::pre_remote_to_local_switch(&beneficiary, fungible_amount).map_err(|e| {
log::error!(
target: LOG_TARGET,
"Hook pre-switch check failed with error code {:?}",
e.into()
);
Error::FailedToTransactAsset("Failed to validate preconditions for remote-to-local switch.")
})?;
// 6. Perform the local transfer
let fungible_amount_as_currency_balance: LocalCurrencyBalanceOf =
fungible_amount.try_into().map_err(|_| {
Error::FailedToTransactAsset("Failed to convert fungible amount to balance of local currency.")
})?;
T::LocalCurrency::transfer(
&switch_pair.pool_account,
&beneficiary,
fungible_amount_as_currency_balance,
Preservation::Preserve,
)
.map_err(|e| {
log::error!(
target: LOG_TARGET,
"Failed to transfer assets from pool account with error {:?}",
e
);
Error::FailedToTransactAsset("Failed to transfer assets from pool account to specified account.")
})?;
// 6. Increase the balance of the remote asset
SwitchPair::::try_mutate(|entry| {
let switch_pair_info = entry
.as_mut()
.ok_or(Error::FailedToTransactAsset("SwitchPair should not be None."))?;
switch_pair_info
.try_process_incoming_switch(fungible_amount)
.map_err(|_| {
Error::FailedToTransactAsset("Failed to apply the transfer outcome to the storage components.")
})?;
Ok::<_, Error>(())
})?;
// 7. Call into the post-switch hook
T::SwitchHooks::post_remote_to_local_switch(&beneficiary, fungible_amount).map_err(|e| {
log::error!(
target: LOG_TARGET,
"Hook post-switch check failed with error code {:?}",
e.into()
);
Error::FailedToTransactAsset("Failed to validate postconditions for remote-to-local switch.")
})?;
Pallet::::deposit_event(Event::::RemoteToLocalSwitchExecuted {
amount: fungible_amount,
to: beneficiary,
});
Ok(())
}
}