-
Notifications
You must be signed in to change notification settings - Fork 14
Extend pallet-xcm to pay teleport with non teleported asset #266
Changes from 14 commits
85bde1c
35e301e
7f13ca7
e484833
900877f
923b9f7
74db417
08a93cf
38918dd
ac306c0
92e5940
fb6d26b
935f233
f280b8e
575e655
fa7b919
42f9527
3d28e3c
ab8bfbc
41aea01
69fb3fe
55fb0fe
7541ba1
a5c8dd4
baefbba
4477bb8
6aca06b
3aeab64
8d26c7d
d2bac9e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| [package] | ||
| name = "pallet-proxy-teleport" | ||
| version = "0.1.0" | ||
| description = "Pallet for allowing to teleport funds by paying with a proxy account." | ||
| 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 } | ||
| 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"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| #![cfg_attr(not(feature = "std"), no_std)] | ||
|
|
||
| type BaseXcm<T> = pallet_xcm::Pallet<T>; | ||
| use frame_support::{ | ||
| dispatch::DispatchResult, | ||
| ensure, | ||
| traits::{Contains, EnsureOrigin, Get}, | ||
| weights::Weight, | ||
| }; | ||
| use frame_system::pallet_prelude::OriginFor; | ||
| pub use pallet::*; | ||
| use parity_scale_codec::Encode; | ||
| use sp_std::{boxed::Box, vec}; | ||
| pub use xcm::{ | ||
| opaque::latest::prelude::{Junction, Junctions, MultiLocation, OriginKind}, | ||
| v3::{ | ||
| AssetId, ExecuteXcm, Fungibility, | ||
| Instruction::{ | ||
| BurnAsset, BuyExecution, DepositAsset, DepositReserveAsset, InitiateReserveWithdraw, | ||
| ReceiveTeleportedAsset, Transact, WithdrawAsset, | ||
| }, | ||
| MultiAsset, MultiAssetFilter, MultiAssets, Parent, SendXcm, WeightLimit, | ||
| WeightLimit::Unlimited, | ||
| WildMultiAsset, Xcm, XcmHash, | ||
| }, | ||
| VersionedMultiAssets, VersionedMultiLocation, VersionedResponse, VersionedXcm, | ||
| }; | ||
|
|
||
| // #[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::*; | ||
| use frame_system::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, | ||
| } | ||
|
|
||
| #[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 proxy asset. | ||
| /// The parachain that wants to teleport native assets to another parachain with this method | ||
| /// need to fund its Sovereign Account with the proxy asset on the destination parachain. | ||
| /// If multiple proxy assets are included in the message, only the first one is used to buy | ||
| /// execution. Proxy 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. | ||
| /// - `proxy_asset`: The proxy asset to buy execution on the destination chain. | ||
|
|
||
| #[pallet::call] | ||
| impl<T: Config> Pallet<T> { | ||
| #[pallet::call_index(0)] | ||
| #[pallet::weight(10_000)] | ||
| pub fn proxy_native_teleport( | ||
| origin: OriginFor<T>, | ||
| dest: Box<VersionedMultiLocation>, | ||
| beneficiary: Box<VersionedMultiLocation>, | ||
| native_asset_amount: u128, | ||
| proxy_asset: Box<VersionedMultiAssets>, | ||
| ) -> DispatchResult { | ||
| Self::do_proxy_teleport_assets( | ||
| origin, | ||
| dest, | ||
| beneficiary, | ||
| native_asset_amount, | ||
| proxy_asset, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<T: Config> Pallet<T> { | ||
| fn do_proxy_teleport_assets( | ||
| origin: OriginFor<T>, | ||
| dest: Box<VersionedMultiLocation>, | ||
| beneficiary: Box<VersionedMultiLocation>, | ||
| native_asset_amount: u128, | ||
| proxy_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)?; | ||
|
|
||
| //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 foreing_assets = MultiAssets::from(vec![native_as_foreign]); | ||
|
|
||
| //Unbox proxy asset | ||
| let proxy_asset: MultiAssets = | ||
| (*proxy_asset).try_into().map_err(|()| pallet_xcm::Error::<T>::BadVersion)?; | ||
|
||
|
|
||
| //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 proxy asset to the destination chain. | ||
| let fee_asset_item: usize = 0; | ||
| let fees = proxy_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)?; | ||
|
|
||
| // TODO: Define if Withdrawn proxy assets are deposited or trapped. | ||
| // Check if there is no vulnerability through RefundSurplus | ||
| //let max_assets = (assets.len() as u32).checked_add(1).ok_or(Error::<T>::TooManyAssets)?; | ||
|
|
||
| //Build the message to execute on origin. | ||
| let assets: MultiAssets = assets.into(); | ||
stiiifff marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| let message: Xcm<<T as frame_system::Config>::RuntimeCall> = Xcm(vec![ | ||
| // Withdraw drops asset so is used as burn mechanism | ||
|
||
| WithdrawAsset(assets.clone()), | ||
| BurnAsset(assets), | ||
|
||
| ]); | ||
|
|
||
| // Build the message to send. | ||
| // Set WeightLimit | ||
| // TODO: Implement weight_limit calculation with final instructions. | ||
| let weight_limit: WeightLimit = Unlimited; | ||
| let xcm_message: Xcm<()> = Xcm(vec![ | ||
franciscoaguirre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| WithdrawAsset(proxy_asset), | ||
| BuyExecution { fees, weight_limit }, | ||
| ReceiveTeleportedAsset(foreing_assets.clone()), | ||
|
||
| // Intentionally trap ROC to avoid exploit | ||
|
||
| DepositAsset { assets: MultiAssetFilter::Definite(foreing_assets), beneficiary }, | ||
| ]); | ||
|
|
||
| // Temporarly hardcode weight. | ||
| // TODO: Replace for Weigher. | ||
| let weight: Weight = Weight::from_parts(1_000_000_000, 100_000); | ||
| // T::Weigher::weight(&mut message).map_err(|()| 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); | ||
| Self::deposit_event(Event::Attempted { outcome }); | ||
|
|
||
| // Use pallet-xcm send for sending message. | ||
| let root_origin = T::SendXcmOrigin::ensure_origin(frame_system::RawOrigin::Root.into())?; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The origin should be the account that has the funds it wants to teleport
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that the one paying for the execution with the proxy asset on destiny chain should be the As a quick recap, proxy asset indicates which asset should be used to buy execution on destiny, but the funding of the account that is going to pay for that execution with said asset must be done previously to this call. For simplicity, let's focus on the use case of: origin chain is Trappist, destiny chain is Asset Hub and the proxy asset is ROC. Funding the SA of Trappist is simple, you can obtain the SA address with this tool and the PARA ID. Then you do a simple However doing so for the SA of the caller origin account from Trappist in Asset Hub, let's call it
Moving forward, I implemented the This allowed me to send ROC on AH to this address and now the call buys execution successfully if we set the origin of the call to Alice instead of Root. However, Using an alias of Alice's SA to Trappist's SA which is currently a TrustedTeleported would be a increment of privileges on Alice that I do not see fit and also AH doesn't accept any There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think the name "proxy asset" is the best to describe what's going on here. The idea of this pallet, and extrinsic, is to teleport one main asset to chain B but withdraw another one on B as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Regarding using It's perfectly fine to On another note, if you only fund Trappist's sovereign account on AH, you're basically giving up power over those assets and are unable to use them again. You should instead use
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed on the wording, as logic evolved the proxy term that I initially used became outdated, will re work.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As for the
As discussed on our following chat regarding this comment, withdrawn ROC derivatives on Trappist should also be burned and surplus of ROCs on Asset Hub could be refunded to avoid trapping unnecessary users funds. From usability perspective, users would no longer need to fund SA through a Will implement and update on this thread.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #275 Shows a first approach. |
||
| 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_message.clone()) | ||
franciscoaguirre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| .map_err(|_| Error::<T>::SendError)?; | ||
|
||
| let e = Event::Sent { | ||
| origin: origin_location, | ||
| destination: dest, | ||
| message: xcm_message, | ||
| message_id, | ||
| }; | ||
| Self::deposit_event(e); | ||
|
|
||
| // Finish. | ||
| Ok(()) | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.