paritytech / polkadot-sdk

The Parity Polkadot Blockchain SDK
https://polkadot.com/
1.92k stars 707 forks source link

RFC: Treasury API for Cross-Chain Transfers #4715

Open muharem opened 5 months ago

muharem commented 5 months ago

Summary:

This document proposes an API for a call that dispatches the XCM asset transfer from chain X, withdrawing the asset on chain Y and depositing it on chain Z. Chains X, Y, and Z can be different, the same, or any combination thereof. We will illustrate this with the example of the treasury's spend call.

Motivation:

Currently, the treasury spend call located on chain X (e.g., the Relay Chain) can transfer its assets to any chain Y (e.g., the Asset Hub), where Y can be equal to X or different. However, the beneficiary of this transfer must be on chain Y. This setup misses the use case where stakeholders want to move treasury assets from the Relay Chain account to the Asset Hub account. To avoid this limitation, we currently use the root track to dispatch a call as a treasurer's sovereign account and teleport via the xcm pallet to the desired destination (e.g. the Asset Hub). It seems reasonable to provide a solution that utilizes the treasury referendum tracks and possibly uses a single spend call for this purpose. Additionally, in recent PRs concerning the locatable asset type, reviewers have questioned the need for such a type. Before introducing a similar type to solve the problem described above, I want to share more on the reasoning for such types.

Proposed Solution:

First, let's review existing production APIs that facilitate different type of cross-chain transfers

X = Y = Z

This is a simple transfer or the treasury’s spend_local. It only requires the from account, to account, and the asset id.

X ≠ Y, Y = Z

Implemented by the treasury pallet setup of the Polkadot Relay Chain:

// Dispatched on the X chain.
// Implies `source` = `treasury_account` on withdrawal location, Y chain.
fn treasury_spend (
  ...
  asset_kind: LocatableAssetId {
    // Location of the Y chain.
    location: xcm::Location, 
    // Asset id on the Y chain.
    asset_id: xcm::AssetId,
  }
  // `AccountId` on chain Z or some entity's location (pallet instance on some chain)
  // used to derive it's sovereign account on the Z chain.
  // Z must be equal to Y.
  beneficiary: xcm::Location,
  ... 
);

It is worth mentioning that a beneficiary location presented as the xcm::Location type or an asset id presented as the xcm::AssetId (which is a wrapped xcm::Location) type cannot carry the information about the chain on which these location are meant to be interpreted. A single location can represent a beneficiary (e.g., the Fellowship Treasury pallet instance on the Collectives) or an asset id (e.g., GLMR as the Moonbeam location) without an information where they are located.

X = Y, Y ≠ Z

Implemented by the xcm pallet and its teleport call:

// Dispatched on the X chain.
fn xcm_teleport (
  // Implies the `source` = `sovereign_of(origin)` on the X chain.
  origin: Origin,
  // Location of the Z chain.
  dest: xcm::Location,
  // Sovereign account of the given location on Z chain.
  beneficiary: xcm::Location,
  // Location of the assets on Y chain, which must be equal to X.
  assets: Vec<xcm::Asset>,
  …
);

The last example is what we need for the treasury use case to transfer assets from the Relay Chain to the Asset Hub but requires the root origin. If we use a similar API in the treasury pallet, we would need to introduce a new call like treasury.withdraw_local_spend_remote, but we can do better.

X ≠ Y ≠ Z, and all other possible variations

Drawing from the existing examples, we can propose the following solution:

// Dispatched on the X chain.
// Implies `source` = `treasury_account` on withdrawal location, Y chain.
fn treasury_spend (
  ...
  asset_kind: LocatableAssetId {
    // Location of the Y chain.
    location: xcm::Location, 
    // Asset id on the Y chain.
    asset_id: xcm::AssetId,
  }
  beneficiary: LocatableBeneficiary {
    // Location of the Z chain, deposit location.
    location: xcm::Location,
    // Sovereign account of the given location on Z chain.
    account: xcm::Location,
  }
  …
);

The API can support all desired use cases. The present Pay trait will be implemented over the locatable asset and beneficiary types for different scenarios and can be chained to find an applicable one.

acatangiu commented 5 months ago

Why the need for new type LocatableBeneficiary?

-  beneficiary: LocatableBeneficiary {
-    // Location of the Z chain, deposit location.
-    location: xcm::Location,
-    // Sovereign account of the given location on Z chain.
-    account: xcm::Location,
-  }
+  // Beneficiary's location relative to the current context (chain X),
+  // equivalent location on chain Z can be derived through re-anchoring.
+  beneficiary: xcm::Location,

L.E.: I realized that you also need to specify the final chain, where the deposit should happen (chain Z - there is no other argument specifying Z). So my suggestion above does not work.

I would’ve opted for adding another argument like “deposit_chain”/“beneficiary_chain” to avoid adding yet another concept. But your suggestion of wrapping both the beneficiary chain location and the beneficiary account in the context of that chain in this new “LocatableBeneficiary” is also good.


LocatableAssetId could also be broken down to equivalent components without the need for a new type:

-  asset_kind: LocatableAssetId {
-    // Location of the Y chain.
-    location: xcm::Location, 
-    // Asset id on the Y chain.
-    asset_id: xcm::AssetId,
-  }
+  // The location where the spend is originated (alternative name: `withdraw_context`).
+  spend_context: xcm::Location,
+  // Asset identifier in the context of the `spend_context` location,
+  // equivalent location on chain Z can be derived through re-anchoring.
+  asset_id: xcm::AssetId,

(unless you need to carry the location of asset_id in many places and it makes more sense to keep them wrapped in LocatableAssetId


P.S.: I would also rename the issue title

- RFC: API for Cross-Chain Transfers
+ RFC: Treasury API for Cross-Chain Transfers

since it's very specific to treasury pallet

muharem commented 5 months ago

@acatangiu renamed (=

But my point is that for cross chain transfers where a transfer commanded dispatched from X chain, the asset withdraws from Y chain, and deposited on Z chain, where X ≠ Y ≠ Z, we need, the location of asset_id as xcm::Location, the asset_id as xcm::AssetId, the beneficiary location as xcm::Location and beneficiary address as xcm::Location. And this is not specific for the treasury pallet. This is what was questioned in PRs, why we even need that additional location next to asset id.

I do not understand if you agree with above.

For the treasury the spend call looks like spend(origin, asset_kind: T::AssetKind, beneficiary: T::Beneficiary, ...), similar signature has the frame's Pay trait. As a client you can setup it to work only locally, or only from local to chain Y. I wish to not leak the xcm details to the frame's treasury pallet.

Also since Pay trait will be implemented over these locatable types within xcm builder, it does concern not only the treasury pallet.

acatangiu commented 5 months ago

we need, the location of asset_id as xcm::Location, the asset_id as xcm::AssetId, the beneficiary location as xcm::Location and beneficiary address as xcm::Location

Conceptually this can also be modeled as: the xcm::Location where the spend should occur (chain Y), the xcm::AssetId of the asset to be spent, the xcm::Location destination of the spend (chain Z), and the beneficiary as xcm::Location (relative to chain Z).

For the treasury the spend call looks like spend(origin, asset_kind: T::AssetKind, beneficiary: T::Beneficiary, ...), similar signature has the frame's Pay trait.

But yes, your modelling directly fits existing spend call API and Pay trait 👍

I do not understand if you agree with above.

I agree, yes, both with the concept and the impl proposal 😄