diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d0d1b745..855fb14f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -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 \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7aaa4f03..ad0bec08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6991,6 +6991,24 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-withdraw-teleport" +version = "0.1.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-xcm", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "xcm", + "xcm-executor", +] + [[package]] name = "pallet-xcm" version = "1.0.0" @@ -12948,6 +12966,7 @@ dependencies = [ "pallet-treasury", "pallet-uniques", "pallet-utility", + "pallet-withdraw-teleport", "pallet-xcm", "pallet-xcm-benchmarks", "parachain-info", diff --git a/Cargo.toml b/Cargo.toml index 61a1923e..5b563ae9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/pallets/withdraw-teleport/Cargo.toml b/pallets/withdraw-teleport/Cargo.toml new file mode 100644 index 00000000..e2152a9d --- /dev/null +++ b/pallets/withdraw-teleport/Cargo.toml @@ -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"] \ No newline at end of file diff --git a/pallets/withdraw-teleport/src/lib.rs b/pallets/withdraw-teleport/src/lib.rs new file mode 100644 index 00000000..09b75205 --- /dev/null +++ b/pallets/withdraw-teleport/src/lib.rs @@ -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 = pallet_xcm::Pallet; +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(_); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_xcm::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::error] + pub enum Error { + /// An error ocured during send + SendError, + /// Failed to execute + FailedToExecute, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// 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 Pallet { + #[pallet::call_index(0)] + #[pallet::weight(::teleport_assets())] + pub fn withdraw_and_teleport( + origin: OriginFor, + dest: Box, + beneficiary: Box, + native_asset_amount: u128, + fee_asset: Box, + ) -> DispatchResult { + Self::do_withdraw_and_teleport( + origin, + dest, + beneficiary, + native_asset_amount, + fee_asset, + ) + } + } +} + +impl Pallet { + fn do_withdraw_and_teleport( + origin: OriginFor, + dest: Box, + beneficiary: Box, + native_asset_amount: u128, + fee_asset: Box, + ) -> 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::::BadVersion)?; + let beneficiary: MultiLocation = + (*beneficiary).try_into().map_err(|()| pallet_xcm::Error::::BadVersion)?; + //Unbox fee asset + let fee_asset: MultiAssets = + (*fee_asset).try_into().map_err(|()| pallet_xcm::Error::::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::::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::::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::::Empty)? + .clone() + .reanchored(&dest, context) + .map_err(|_| pallet_xcm::Error::::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<::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::::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::::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::::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::::InvalidOrigin)?; + //TODO: Check this Error population + let message_id = BaseXcm::::send_xcm(interior, dest, xcm_to_send.clone()) + .map_err(|_| Error::::SendError)?; + let e = Event::Sent { + origin: origin_location, + destination: dest, + message: xcm_to_send, + message_id, + }; + Self::deposit_event(e); + + // Finish. + Ok(()) + } +} diff --git a/runtime/trappist/Cargo.toml b/runtime/trappist/Cargo.toml index a5554911..fd2d122c 100644 --- a/runtime/trappist/Cargo.toml +++ b/runtime/trappist/Cargo.toml @@ -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"] @@ -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", diff --git a/runtime/trappist/src/lib.rs b/runtime/trappist/src/lib.rs index a54bcf0e..d47fb673 100644 --- a/runtime/trappist/src/lib.rs +++ b/runtime/trappist/src/lib.rs @@ -649,6 +649,10 @@ impl pallet_treasury::Config for Runtime { type SpendOrigin = frame_support::traits::NeverEnsureOrigin; } +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; @@ -712,6 +716,7 @@ construct_runtime!( // Additional pallets Dex: pallet_dex::{Pallet, Call, Storage, Event} = 110, AssetRegistry: pallet_asset_registry::{Pallet, Call, Storage, Event} = 111, + WithdrawTeleport: pallet_withdraw_teleport = 112, } ); diff --git a/runtime/trappist/src/xcm_config.rs b/runtime/trappist/src/xcm_config.rs index 31505cd2..a0a9e064 100644 --- a/runtime/trappist/src/xcm_config.rs +++ b/runtime/trappist/src/xcm_config.rs @@ -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}; @@ -88,6 +89,8 @@ pub type LocationToAccountId = ( SiblingParachainConvertsVia, // Straight up local `AccountId32` origins just alias directly to `AccountId`. AccountId32Aliases, + // Foreign locations alias into accounts according to a hash of their standard description. + HashedDescription>, ); /// `AssetId/Balancer` converter for `TrustBackedAssets` diff --git a/xcm-simulator/Cargo.lock b/xcm-simulator/Cargo.lock index 6e6eae9c..5ad38604 100644 --- a/xcm-simulator/Cargo.lock +++ b/xcm-simulator/Cargo.lock @@ -5529,7 +5529,7 @@ dependencies = [ ] [[package]] -name = "pallet-proxy" +name = "pallet-withdraw" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.37#f38bd6671d460293c93062cc1e4fe9e9e490cb29" dependencies = [ @@ -6958,7 +6958,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-preimage", - "pallet-proxy", + "pallet-withdraw", "pallet-scheduler", "pallet-session", "pallet-staking", @@ -7895,7 +7895,7 @@ dependencies = [ "pallet-nis", "pallet-offences", "pallet-preimage", - "pallet-proxy", + "pallet-withdraw", "pallet-recovery", "pallet-scheduler", "pallet-session", @@ -10415,7 +10415,7 @@ dependencies = [ "pallet-balances", "pallet-collator-selection", "pallet-multisig", - "pallet-proxy", + "pallet-withdraw", "pallet-session", "pallet-state-trie-migration", "pallet-timestamp",