Skip to content

Commit

Permalink
[xcm-v5] implement RFC#122: InitiateTransfer can alias XCM original o…
Browse files Browse the repository at this point in the history
…rigin on destination (#5971)

Built on top of #5876

# Description

Currently, all XCM asset transfer instructions ultimately clear the
origin in the remote XCM message by use of the `ClearOrigin`
instruction. This is done for security considerations to ensure that
subsequent (user-controlled) instructions cannot command the authority
of the sending chain.

The problem with this approach is that it limits what can be achieved on
remote chains through XCM. Most XCM operations require having an origin,
and following any asset transfer the origin is lost, meaning not much
can be done other than depositing the transferred assets to some local
account or transferring them onward to another chain.

For example, we cannot transfer some funds for buying execution, then do
a `Transact` (all in the same XCM message).

In the case of XCM programs going from source-chain directly to
dest-chain without an intermediary hop, we can enable scenarios such as
above by using the AliasOrigin instruction instead of the ClearOrigin
instruction.

Instead of clearing the source-chain origin, the destination chain shall
attempt to alias source-chain to "original origin" on the source chain.
Most common such origin aliasing would be X1(Parachain(source-chain)) ->
X2(Parachain(source-chain), AccountId32(origin-account)) for the case of
a single hop transfer where the initiator is a (signed/pure/proxy)
account origin-account on source-chain. This is equivalent to using the
DescendOrigin instruction in this case, but also usable in the multi hop
case.

This allows an actor on chain A to Transact on chain B without having to
prefund its SA account on chain B, instead they can simply transfer the
required fees in the same XCM program as the Transact.

As long as the asset transfer has the same XCM route/hops as the rest of
the program, this pattern of usage can be composed across multiple hops,
to ultimately Transact on the final hop using the original origin on the
source chain, effectively abstracting away any intermediary hops.

### XCM `InitiateAssetsTransfer` instruction changes

A new parameter `preserve_origin` to be added to the
`InitiateAssetsTransfer` XCM instruction that specifies if the original
origin should be preserved or cleared.

```diff
InitiateAssetsTransfer {
	destination: Location,
	assets: Vec<AssetTransferFilter>,
	remote_fees: Option<AssetTransferFilter>,
+	preserve_origin: bool,
	remote_xcm: Xcm<()>,
}
```

This parameter is explicitly necessary because the instruction should be
usable between any two chains regardless of their origin-aliasing trust
relationship. Preserving the origin requires some level of trust, while
clearing it works regardless of that relationship.
Specifying `preserve_origin: false` will always work regardless of the
configured alias filters of the
involved chains.

# Testing

- [x] e2e test: User on PenpalA registers foreign token (transacts) on
PenpalB through XCM, while paying all fees using USDT (meaning XCM has
to go through AssetHub) - AH carries over the original origin,
effectively being a transparent proxy,
- [x] e2e test: User/contract on Ethereum registers foreign token
(transacts) on Polkadot-PenpalA through XCM (over bridge), while paying
all fees using DOT (has to go through AssetHub) - AH carries over the
original origin, effectively being a transparent proxy for Ethereum,

---------

Signed-off-by: Adrian Catangiu <[email protected]>
Co-authored-by: Francisco Aguirre <[email protected]>
Co-authored-by: Branislav Kontur <[email protected]>
  • Loading branch information
3 people authored Oct 29, 2024
1 parent a1b8381 commit 86542d6
Show file tree
Hide file tree
Showing 43 changed files with 1,435 additions and 598 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

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

7 changes: 6 additions & 1 deletion bridges/snowbridge/primitives/router/src/inbound/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -458,8 +458,10 @@ where
{
fn convert_location(location: &Location) -> Option<AccountId> {
match location.unpack() {
(_, [GlobalConsensus(Ethereum { chain_id })]) =>
(2, [GlobalConsensus(Ethereum { chain_id })]) =>
Some(Self::from_chain_id(chain_id).into()),
(2, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) =>
Some(Self::from_chain_id_with_key(chain_id, *key).into()),
_ => None,
}
}
Expand All @@ -469,4 +471,7 @@ impl<AccountId> GlobalConsensusEthereumConvertsFor<AccountId> {
pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
(b"ethereum-chain", chain_id).using_encoded(blake2_256)
}
pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] {
(b"ethereum-chain", chain_id, key).using_encoded(blake2_256)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pallet-asset-tx-payment = { workspace = true }
# Polkadot
polkadot-runtime-common = { workspace = true, default-features = true }
xcm = { workspace = true }
xcm-builder = { workspace = true }
xcm-executor = { workspace = true }
pallet-xcm = { workspace = true }
xcm-runtime-apis = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ mod imports {
};

// Polkadot
pub use xcm::prelude::{AccountId32 as AccountId32Junction, *};
pub use xcm::{
latest::AssetTransferFilter,
prelude::{AccountId32 as AccountId32Junction, *},
};
pub use xcm_executor::traits::TransferType;

// Cumulus
Expand All @@ -42,7 +45,7 @@ mod imports {
xcm_helpers::{
get_amount_from_versioned_assets, non_fee_asset, xcm_transact_paid_execution,
},
ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, XCM_V3,
ASSETS_PALLET_ID, RESERVABLE_ASSET_ID, USDT_ID, XCM_V3,
};
pub use parachains_common::{AccountId, Balance};
pub use westend_system_emulated_network::{
Expand All @@ -68,6 +71,7 @@ mod imports {
CustomizableAssetFromSystemAssetHub as PenpalCustomizableAssetFromSystemAssetHub,
LocalReservableFromAssetHub as PenpalLocalReservableFromAssetHub,
LocalTeleportableToAssetHub as PenpalLocalTeleportableToAssetHub,
UniversalLocation as PenpalUniversalLocation,
UsdtFromAssetHub as PenpalUsdtFromAssetHub,
},
PenpalAParaPallet as PenpalAPallet, PenpalAssetOwner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ use crate::{
imports::*,
tests::teleport::do_bidirectional_teleport_foreign_assets_between_para_and_asset_hub_using_xt,
};
use xcm::latest::AssetTransferFilter;

fn para_to_para_assethub_hop_assertions(t: ParaToParaThroughAHTest) {
type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
Expand Down Expand Up @@ -851,6 +850,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() {
InitiateTransfer {
destination: t.args.dest,
remote_fees: Some(AssetTransferFilter::ReserveWithdraw(fees.into())),
preserve_origin: false,
assets: vec![AssetTransferFilter::Teleport(assets.into())],
remote_xcm: xcm_on_dest,
},
Expand Down Expand Up @@ -886,6 +886,7 @@ fn bidirectional_transfer_multiple_assets_between_penpal_and_asset_hub() {
InitiateTransfer {
destination: t.args.dest,
remote_fees: Some(AssetTransferFilter::ReserveDeposit(fees.into())),
preserve_origin: false,
assets: vec![AssetTransferFilter::Teleport(assets.into())],
remote_xcm: xcm_on_dest,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,82 @@ mod set_asset_claimer;
mod set_xcm_versions;
mod swap;
mod teleport;
mod transact;
mod treasury;
mod xcm_fee_estimation;

#[macro_export]
macro_rules! foreign_balance_on {
( $chain:ident, $id:expr, $who:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type ForeignAssets = <$chain as [<$chain Pallet>]>::ForeignAssets;
<ForeignAssets as Inspect<_>>::balance($id, $who)
})
}
};
}

#[macro_export]
macro_rules! create_pool_with_wnd_on {
( $chain:ident, $asset_id:expr, $is_foreign:expr, $asset_owner:expr ) => {
emulated_integration_tests_common::impls::paste::paste! {
<$chain>::execute_with(|| {
type RuntimeEvent = <$chain as Chain>::RuntimeEvent;
let owner = $asset_owner;
let signed_owner = <$chain as Chain>::RuntimeOrigin::signed(owner.clone());
let wnd_location: Location = Parent.into();
if $is_foreign {
assert_ok!(<$chain as [<$chain Pallet>]>::ForeignAssets::mint(
signed_owner.clone(),
$asset_id.clone().into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
} else {
let asset_id = match $asset_id.interior.last() {
Some(GeneralIndex(id)) => *id as u32,
_ => unreachable!(),
};
assert_ok!(<$chain as [<$chain Pallet>]>::Assets::mint(
signed_owner.clone(),
asset_id.into(),
owner.clone().into(),
10_000_000_000_000, // For it to have more than enough.
));
}

assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::create_pool(
signed_owner.clone(),
Box::new(wnd_location.clone()),
Box::new($asset_id.clone()),
));

assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::PoolCreated { .. }) => {},
]
);

assert_ok!(<$chain as [<$chain Pallet>]>::AssetConversion::add_liquidity(
signed_owner,
Box::new(wnd_location),
Box::new($asset_id),
1_000_000_000_000,
2_000_000_000_000, // $asset_id is worth half of wnd
0,
0,
owner.into()
));

assert_expected_events!(
$chain,
vec![
RuntimeEvent::AssetConversion(pallet_asset_conversion::Event::LiquidityAdded { .. }) => {},
]
);
});
}
};
}
Loading

0 comments on commit 86542d6

Please sign in to comment.