Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Extend pallet-xcm to pay teleport with non teleported asset (#266)
Browse files Browse the repository at this point in the history
Co-authored-by: José Molina <[email protected]>
Co-authored-by: Hector Bulgarini <[email protected]>
Co-authored-by: command-bot <>
Co-authored-by: Steve Degosserie <[email protected]>
Co-authored-by: joe petrowski <[email protected]>
  • Loading branch information
5 people authored Sep 15, 2023
1 parent 95947dc commit 479ffb2
Show file tree
Hide file tree
Showing 9 changed files with 322 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ jobs:

- name: Run Trappist tests
run: |
cargo test --workspace --exclude stout-runtime --no-default-features --features trappist/trappist-runtime,std,runtime-benchmarks --locked --jobs 1
cargo test --workspace --exclude stout-runtime --no-default-features --features trappist/trappist-runtime,std,runtime-benchmarks --locked --jobs 1
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ pallet-dex-rpc-runtime-api = { version = "0.0.1", git = "https://github.com/pari
pallet-asset-registry = { default-features = false, path = "pallets/asset-registry" }
trappist-runtime-benchmarks = { default-features = false, path = "pallets/benchmarks" }
pallet-lockdown-mode = { version = "0.1.0", default-features = false, path = "pallets/lockdown-mode" }
pallet-withdraw-teleport = { version = "0.1.0", default-features = false, path = "pallets/withdraw-teleport" }

# Substrate std
try-runtime-cli = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v1.0.0" }
Expand Down
42 changes: 42 additions & 0 deletions pallets/withdraw-teleport/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[package]
name = "pallet-withdraw-teleport"
version = "0.1.0"
description = "Pallet for allowing to teleport funds by paying with a fee asset on destination."
authors = { workspace = true }
license = { workspace = true }
homepage = { workspace = true }
repository = { workspace = true }
edition = { workspace = true }

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
parity-scale-codec = { workspace = true, features = [ "derive" ] }
scale-info = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
frame-benchmarking = { workspace = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-xcm = { workspace = true }
xcm = { workspace = true }
xcm-executor = { workspace = true }
sp-io = { workspace = true }


[dev-dependencies]
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }

[features]
default = ["std"]
std = [
"parity-scale-codec/std",
"frame-support/std",
"frame-system/std",
"scale-info/std",
]
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"]
try-runtime = ["frame-support/try-runtime"]
240 changes: 240 additions & 0 deletions pallets/withdraw-teleport/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// This file is part of Trappist.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Disclaimer:
// This is an experimental implementation of a pallet-xcm extension.
// This module is not audited and should not be used in production.

#![cfg_attr(not(feature = "std"), no_std)]

type BaseXcm<T> = pallet_xcm::Pallet<T>;
use frame_support::{
dispatch::DispatchResult,
ensure,
traits::{Contains, EnsureOrigin, Get},
};
use frame_system::pallet_prelude::OriginFor;
pub use pallet::*;
use parity_scale_codec::Encode;
use sp_std::{boxed::Box, vec};
pub use xcm::{
latest::prelude::*, VersionedMultiAssets, VersionedMultiLocation, VersionedResponse,
VersionedXcm,
};
use xcm_executor::traits::WeightBounds;

// #[cfg(test)]
// mod mock;

// #[cfg(test)]
// mod tests;

// #[cfg(feature = "runtime-benchmarks")]
// mod benchmarking;
// pub mod weights;
// pub use weights::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
#[pallet::pallet]
pub struct Pallet<T>(_);

#[pallet::config]
pub trait Config: frame_system::Config + pallet_xcm::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}

#[pallet::error]
pub enum Error<T> {
/// An error ocured during send
SendError,
/// Failed to execute
FailedToExecute,
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Execution of an XCM message was attempted.
Attempted { outcome: xcm::latest::Outcome },
/// A XCM message was sent.
Sent {
origin: MultiLocation,
destination: MultiLocation,
message: Xcm<()>,
message_id: XcmHash,
},
}

/// Teleport native asset from a parachain to another.
/// This function is called by the parachain that wants to teleport native assets to another
/// parachain but needs to buy execution on the destination parachain with an asset that is not
/// being teleported. We call this asset the fee asset.
/// The parachain that wants to teleport native assets to another parachain with this method
/// need to fund its Sovereign Account with the fee asset on the destination parachain.
/// If multiple fee assets are included in the message, only the first one is used to buy
/// execution. Fee assets are trapped on the destination parachain.
/// Parameters:
/// - `origin`: The origin of the call.
/// - `dest`: The destination chain of the teleport.
/// - `beneficiary`: The beneficiary of the teleport from the perspective of the destination
/// chain.
/// - `native_asset_amount`: The amount of native asset to teleport.
/// - `fee_asset`: The fee asset to buy execution on the destination chain.

#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(<pallet_xcm::TestWeightInfo as pallet_xcm::WeightInfo>::teleport_assets())]
pub fn withdraw_and_teleport(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
native_asset_amount: u128,
fee_asset: Box<VersionedMultiAssets>,
) -> DispatchResult {
Self::do_withdraw_and_teleport(
origin,
dest,
beneficiary,
native_asset_amount,
fee_asset,
)
}
}
}

impl<T: Config> Pallet<T> {
fn do_withdraw_and_teleport(
origin: OriginFor<T>,
dest: Box<VersionedMultiLocation>,
beneficiary: Box<VersionedMultiLocation>,
native_asset_amount: u128,
fee_asset: Box<VersionedMultiAssets>,
) -> DispatchResult {
//Unbox origin, destination and beneficiary.
let origin_location = T::ExecuteXcmOrigin::ensure_origin(origin)?;
let dest: MultiLocation =
(*dest).try_into().map_err(|()| pallet_xcm::Error::<T>::BadVersion)?;
let beneficiary: MultiLocation =
(*beneficiary).try_into().map_err(|()| pallet_xcm::Error::<T>::BadVersion)?;
//Unbox fee asset
let fee_asset: MultiAssets =
(*fee_asset).try_into().map_err(|()| pallet_xcm::Error::<T>::BadVersion)?;

//Create assets

// Native from local perspective
let native_asset = MultiAsset {
id: AssetId::Concrete(MultiLocation::here()),
fun: Fungibility::Fungible(native_asset_amount),
};
let assets = MultiAssets::from(vec![native_asset.clone()]);

// Native from foreign perspective
let context = T::UniversalLocation::get();
let native_as_foreign = native_asset
.reanchored(&dest, context)
.map_err(|_| pallet_xcm::Error::<T>::CannotReanchor)?;
let foreign_assets = MultiAssets::from(vec![native_as_foreign]);

// TeleportFilter check
let value = (origin_location, assets.into_inner());
ensure!(T::XcmTeleportFilter::contains(&value), pallet_xcm::Error::<T>::Filtered);
let (origin_location, assets) = value;

// Reanchor the fee asset to the destination chain.
let fee_asset_item: usize = 0;
let fees = fee_asset
.get(fee_asset_item as usize)
.ok_or(pallet_xcm::Error::<T>::Empty)?
.clone()
.reanchored(&dest, context)
.map_err(|_| pallet_xcm::Error::<T>::CannotReanchor)?;

// DISCLAIMER: Splitting the instructions to be executed on origin and destination is
// discouraged. Due to current limitations, we need to generate a message
// to be executed on origin and another message to be sent to be executed on destination in
// two different steps as:
// - We cannot buy execution on Asset Hub with foreign assets.
// - We cannot send arbitrary instructions from a local XCM execution.
// - InitiateTeleport prepends unwanted instructions to the message.
// - Asset Hub does not recognize Sibling chains as trusted teleporters of ROC.

//Build the message to execute on origin.
let assets: MultiAssets = assets.into();
let mut message: Xcm<<T as frame_system::Config>::RuntimeCall> = Xcm(vec![
WithdrawAsset(assets.clone()),
SetFeesMode { jit_withdraw: true },
// Burn the native asset.
BurnAsset(assets),
// Burn the fee asset derivative.
WithdrawAsset(fee_asset.clone()),
BurnAsset(fee_asset.clone()),
]);

// Build the message to send to be executed.
// Set WeightLimit
// TODO: Implement weight_limit calculation with final instructions.
let weight_limit: WeightLimit = Unlimited;
let fee_asset_id: AssetId = fee_asset.get(0).ok_or(pallet_xcm::Error::<T>::Empty)?.id;
let xcm_to_send: Xcm<()> = Xcm(vec![
// User must have the derivative of fee_asset on origin.
WithdrawAsset(fee_asset.clone()),
BuyExecution { fees, weight_limit },
ReceiveTeleportedAsset(foreign_assets.clone()),
// We can deposit funds since they were both withdrawn on origin.
DepositAsset { assets: MultiAssetFilter::Definite(foreign_assets), beneficiary },
RefundSurplus,
DepositAsset {
assets: Wild(AllOf { id: fee_asset_id, fun: WildFungibility::Fungible }),
beneficiary,
},
]);

let weight = T::Weigher::weight(&mut message)
.map_err(|()| pallet_xcm::Error::<T>::UnweighableMessage)?;

// Execute Withdraw for trapping assets on origin.
let hash = message.using_encoded(sp_io::hashing::blake2_256);
let outcome =
T::XcmExecutor::execute_xcm_in_credit(origin_location, message, hash, weight, weight);
outcome.clone().ensure_complete().map_err(|_| Error::<T>::FailedToExecute)?;
Self::deposit_event(Event::Attempted { outcome });

// Use pallet-xcm send for sending message.
// Origin is set to Root so it is interpreted as Sovereign Account.
let root_origin = T::SendXcmOrigin::ensure_origin(frame_system::RawOrigin::Root.into())?;
let interior: Junctions =
root_origin.try_into().map_err(|_| pallet_xcm::Error::<T>::InvalidOrigin)?;
//TODO: Check this Error population
let message_id = BaseXcm::<T>::send_xcm(interior, dest, xcm_to_send.clone())
.map_err(|_| Error::<T>::SendError)?;
let e = Event::Sent {
origin: origin_location,
destination: dest,
message: xcm_to_send,
message_id,
};
Self::deposit_event(e);

// Finish.
Ok(())
}
}
2 changes: 2 additions & 0 deletions runtime/trappist/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pallet-dex-rpc-runtime-api = { workspace = true }
pallet-asset-registry = { workspace = true }
trappist-runtime-benchmarks = { workspace = true }
pallet-lockdown-mode = { workspace = true }
pallet-withdraw-teleport = { workspace = true }

[features]
default = ["std"]
Expand Down Expand Up @@ -148,6 +149,7 @@ std = [
"pallet-identity/std",
"pallet-lockdown-mode/std",
"pallet-preimage/std",
"pallet-withdraw-teleport/std",
"pallet-multisig/std",
"pallet-insecure-randomness-collective-flip/std",
"pallet-scheduler/std",
Expand Down
5 changes: 5 additions & 0 deletions runtime/trappist/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,10 @@ impl pallet_treasury::Config for Runtime {
type SpendOrigin = frame_support::traits::NeverEnsureOrigin<Balance>;
}

impl pallet_withdraw_teleport::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}

impl pallet_lockdown_mode::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type LockdownModeOrigin = frame_system::EnsureRoot<Self::AccountId>;
Expand Down Expand Up @@ -712,6 +716,7 @@ construct_runtime!(
// Additional pallets
Dex: pallet_dex::{Pallet, Call, Storage, Event<T>} = 110,
AssetRegistry: pallet_asset_registry::{Pallet, Call, Storage, Event<T>} = 111,
WithdrawTeleport: pallet_withdraw_teleport = 112,
}
);

Expand Down
13 changes: 8 additions & 5 deletions runtime/trappist/src/xcm_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ use xcm::latest::{prelude::*, Fungibility::Fungible, MultiAsset, MultiLocation};
use xcm_builder::{
AccountId32Aliases, AllowKnownQueryResponses, AllowSubscriptionsFrom,
AllowTopLevelPaidExecutionFrom, AllowUnpaidExecutionFrom, CurrencyAdapter,
DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FixedRateOfFungible,
FungiblesAdapter, IsConcrete, MintLocation, NativeAsset, NoChecking, ParentAsSuperuser,
ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia,
SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin,
DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily,
EnsureXcmOrigin, FixedRateOfFungible, FungiblesAdapter, HashedDescription, IsConcrete,
MintLocation, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId,
UsingComponents, WeightInfoBounds, WithComputedOrigin,
};
use xcm_executor::{traits::JustTry, XcmExecutor};

Expand Down Expand Up @@ -88,6 +89,8 @@ pub type LocationToAccountId = (
SiblingParachainConvertsVia<Sibling, AccountId>,
// Straight up local `AccountId32` origins just alias directly to `AccountId`.
AccountId32Aliases<RelayNetwork, AccountId>,
// Foreign locations alias into accounts according to a hash of their standard description.
HashedDescription<AccountId, DescribeFamily<DescribeAllTerminal>>,
);

/// `AssetId/Balancer` converter for `TrustBackedAssets`
Expand Down
Loading

0 comments on commit 479ffb2

Please sign in to comment.