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

Extend pallet-xcm to pay teleport with non teleported asset #266

Merged
merged 30 commits into from
Sep 15, 2023
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
85bde1c
feat: migrate to polkadot-v1.0.0
Aug 22, 2023
35e301e
feat: add missing migrations for polkadot-v1.0.0
Aug 24, 2023
7f13ca7
Fix Benchmkaring
hbulgarini Aug 24, 2023
e484833
".git/.scripts/commands/bench-all/bench-all.sh" --runtime=trappist --…
Aug 24, 2023
900877f
Fix compilation
hbulgarini Aug 24, 2023
923b9f7
add pallet for teleport foreign assets into ah
metricaez Aug 24, 2023
74db417
clean duplicated errors
metricaez Aug 25, 2023
08a93cf
cargo fmt
metricaez Aug 25, 2023
38918dd
comments
metricaez Aug 25, 2023
ac306c0
fix typo
metricaez Aug 28, 2023
92e5940
foreign asset by reanchoring
metricaez Aug 28, 2023
fb6d26b
Burn withdrawn assets on origin
metricaez Aug 28, 2023
935f233
Merge branch 'main' into emi-proxy-teleport-pallet
metricaez Aug 28, 2023
f280b8e
add comments and remove fee_asset_item
metricaez Aug 29, 2023
575e655
import through prelude
metricaez Sep 1, 2023
fa7b919
add foreign locations for LocationToAccountId
metricaez Sep 1, 2023
42f9527
Merge branch 'main' into emi-proxy-teleport-pallet
metricaez Sep 1, 2023
3d28e3c
minor fixes and add execution check
metricaez Sep 4, 2023
ab8bfbc
add obnoxious comments to avoid misusage
metricaez Sep 4, 2023
41aea01
cargo fmt
metricaez Sep 4, 2023
69fb3fe
prelude wildcard
metricaez Sep 5, 2023
55fb0fe
cargo fmt
metricaez Sep 5, 2023
7541ba1
dismiss "proxy" wording
metricaez Sep 5, 2023
a5c8dd4
typo
metricaez Sep 6, 2023
baefbba
Withdraw derivative on origin (#275)
metricaez Sep 14, 2023
4477bb8
Merge branch 'main' into emi-proxy-teleport-pallet
metricaez Sep 14, 2023
6aca06b
add hash of foreign location
metricaez Sep 14, 2023
3aeab64
typo and remove job limit on git workflow
metricaez Sep 15, 2023
8d26c7d
use teleport fallback weight
metricaez Sep 15, 2023
d2bac9e
add jobs 1 flag to check.yml
metricaez Sep 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 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
41 changes: 41 additions & 0 deletions pallets/withdraw-teleport/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
[package]
name = "pallet-withdraw-teleport"
version = "0.1.0"
description = "Pallet for allowing to teleport funds by paying with a fee asset on destiny."
stiiifff marked this conversation as resolved.
Show resolved Hide resolved
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"]
238 changes: 238 additions & 0 deletions pallets/withdraw-teleport/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
// 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},
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::{
latest::prelude::*, 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,
/// 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(10_000)]
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)?;
joepetrowski marked this conversation as resolved.
Show resolved Hide resolved
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 foreing_assets = MultiAssets::from(vec![native_as_foreign]);
joepetrowski marked this conversation as resolved.
Show resolved Hide resolved

// 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)?;

// TODO: Define if Withdrawn fee 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)?;

// 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 message: Xcm<<T as frame_system::Config>::RuntimeCall> =
Xcm(vec![WithdrawAsset(assets.clone()), BurnAsset(assets)]);

// Build the message to send to be executed.
// Set WeightLimit
// TODO: Implement weight_limit calculation with final instructions.
let weight_limit: WeightLimit = Unlimited;
let xcm_to_send: Xcm<()> = Xcm(vec![
// There are currently no limitations on the amount of fee assets to withdraw.
// Since funds are withdrawn from the Sovereign Account of the origin, chains must be
// aware of this and implement a mechanism to prevent draining.
WithdrawAsset(fee_asset),
BuyExecution { fees, weight_limit },
ReceiveTeleportedAsset(foreing_assets.clone()),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ReceiveTeleportedAsset(foreing_assets.clone()),
ReceiveTeleportedAsset(foreing_assets.clone()),
RefundSurplus,

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree but in this case I didn't initially intend to refund used ROCs to avoid this exploit.

TL:DR since users would be calling this extrinsic from Trappist and the execution in Asset Hub is bought with ROC coming from Trappist's AH SovereignAccount, refunding the ROC surplus could be used to drain the SovAcc.

A solution that is based on Cisco and Iker's input is presented here but implies users reserve transferring ROC into their Trappist's account.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I see, but like 275 shows can't you burn the ROC here and then transfer the amount out (partly for fee payment and partly to beneficiary) on AH?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, so I think that #275 is establishing as the way to go.

// Intentionally trap ROC to avoid exploit of draining Sovereing Account
Copy link
Contributor

@joepetrowski joepetrowski Sep 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't look trapped?
IMO this is hacky. Just debit the user of ROC here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would need some more explanation here as I do not fully understand where are you suggesting to debit the ROC from. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean here on Trappist. You have reserve-backed ROC here, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No initially, but yes after #275 , so shall we move forward and merge 275 which solves this ?

// by depositing withdrawn ROC on beneficiary.
DepositAsset { assets: MultiAssetFilter::Definite(foreing_assets), beneficiary },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just AllOf whatever is left?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this is depositing the Trappist token, but the comment says (and I believe is correct) that it should deposit ROC.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is what you want.

Suggested change
DepositAsset { assets: MultiAssetFilter::Definite(foreing_assets), beneficiary },
DepositAsset { assets: AllCounted(2).into(), beneficiary }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and initially did it that way but changed to depositing only teleported foreign_assets because of this.

]);

// 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);
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 @@ -654,6 +654,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 @@ -717,6 +721,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,
UsingComponents, WeightInfoBounds,
DenyReserveTransferToRelayChain, DenyThenTry, DescribeAllTerminal, DescribeFamily,
EnsureXcmOrigin, FixedRateOfFungible, FungiblesAdapter, HashedDescription, IsConcrete,
MintLocation, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, UsingComponents,
WeightInfoBounds,
};
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