Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

support asset transfer from/to sibling chain #806

Closed
wants to merge 1 commit into from

Conversation

yrong
Copy link

@yrong yrong commented Nov 29, 2021

This PR adds the AsPrefixedGeneralIndexFromLocalOrRemote conversion type to allow accepting assets that were moved to another chain via reserve based transfers back onto Statemint (and friends).

@yrong
Copy link
Author

yrong commented Nov 29, 2021

@apopiak @KiChjang

not sure it is the proper way, AFAIK the current asset<->multilocation convert in statemine runtime only allows to transfer asset from local and this pr is for from remote parachain with reserve location as X2(parachain:1000,GeneralIndex:asset_id)

@KiChjang
Copy link
Contributor

Thanks for your PR! Unfortunately, this is not the right place to add a new item to configure XCM. I believe the right place would be to add it in Polkadot's repository, under xcm/xcm-builder.

Aside from that, I'm not quite sure what this PR is trying to solve. Can you please elaborate the background of your problem and why your solution would solve it? I don't understand anything about this need for a GeneralIndex for example.

@yrong
Copy link
Author

yrong commented Nov 30, 2021

@KiChjang The requirement is simple, we would like to transfer asset issued in statemine (e.g:usdt) to our chain and then transfer it back. With this pr and some adaptor logic in our chain I can achive that.
cross-out-statemine

The GeneralIndex is to encode asset_id as multiLocation now adopted in statemine runtime.

Btw, with limitedReserveTransferAssets to transfer to our chain
image

and wrap xcm as following to transfer asset back
https://github.com/bifrost-finance/bifrost/blob/c7eb1bef7abfdeb6562aeda29ab76805308b77f5/pallets/salp/src/lib.rs?_pjax=%23js-repo-pjax-container%2C%20div%5Bitemtype%3D%22http%3A%2F%2Fschema.org%2FSoftwareSourceCode%22%5D%20main%2C%20%5Bdata-pjax-container%5D#L1014-L1031

@KiChjang
Copy link
Contributor

So if I'm hearing you correctly, you're saying that the GeneralIndex is used to specify which one of the many assets in statemine that you want to transfer to the destination chain via reserve transfers? In other words, your flow is exactly the same as InititateReserveWithdraw, but with the ability to specify an asset that is not KSM, correct?

If so, then there may need to be a bit more thought into how the MultiLocation of a reserve chain asset should be represented. Right now you have Parachain(para_id)/GeneralIndex(index), which is rather imprecise -- a GeneralIndex can be interpreted in many different ways. To reduce the ambiguity, I propose inserting a PalletIndex(assets_pallet_index) in between, so that the whole path becomes Parachain(para_id)/PalletIndex(assets_pallet_index)/GeneralIndex(index), so that there's even less of a chance to misinterpret what a GeneralIndex means.

@yrong
Copy link
Author

yrong commented Nov 30, 2021

So if I'm hearing you correctly, you're saying that the GeneralIndex is used to specify which one of the many assets in statemine that you want to transfer to the destination chain via reserve transfers? In other words, your flow is exactly the same as InititateReserveWithdraw, but with the ability to specify an asset that is not KSM, correct?

Exactly

If so, then there may need to be a bit more thought into how the MultiLocation of a reserve chain asset should be represented. Right now you have Parachain(para_id)/GeneralIndex(index), which is rather imprecise -- a GeneralIndex can be interpreted in many different ways. To reduce the ambiguity, I propose inserting a PalletIndex(assets_pallet_index) in between, so that the whole path becomes Parachain(para_id)/PalletIndex(assets_pallet_index)/GeneralIndex(index), so that there's even less of a chance to misinterpret what a GeneralIndex means.

seems you want to add assets_pallet_index in between, I'am not sure fully understand your concern here. AFAIK statemine is a common good chain focus in asset management and GeneralIndex is the only place we use in statemine to reprensent asset. IMHO we may not need another level for pallet index here.

I believe the right place would be to add it in Polkadot's repository, under xcm/xcm-builder.

Agree

@KiChjang
Copy link
Contributor

KiChjang commented Nov 30, 2021

@yrong If for whatever reason, 1000 is suddenly not the parachain ID for statemine, due to it being reorganized or that you're in fact on a relay chain that has a different architecture (or maybe simply a typo), and Parachain(1000) also happens to interpret GeneralIndex(index) in a way that's different from what you'd expect, then you're in trouble because now you don't actually know what the XCM is doing.

Moreover, it's very not obvious what a GeneralIndex does on a parachain. With a prefixed path that contains a PalletIndex, at least you have an idea that the GeneralIndex is meant to be interpreted by the pallet for an operation that's specific to the pallet (the pallet is also versioned, which helps a lot). With a GeneralIndex that follows a Parachain, what sort of useful information can you gather from it? That basically every GeneralIndex should only be interpreted according to which Parachain it was sent to? So when the parachain decides to change its "API" for this MultiLocation, it can just do so without notifying its consumers?

@apopiak apopiak self-requested a review November 30, 2021 10:00
@apopiak
Copy link
Contributor

apopiak commented Nov 30, 2021

When I set up the asset XCM integration I figured that we need a unique asset identifier within Statemint, so I used the GeneralIndex. When receiving an asset identified by a GeneralIndex Statemint will interpret it as a local asset that it has sovereignty/control over.
Now, I agree that we might want to allow the transaction of other assets in the future, so we will need to define how to identify them and then a PalletIndex or originating Parachain id might come in handy.

@yrong My question to you is: What keeps you from just sending a GeneralIndex as the asset's id? Left a corresponding comment here: https://github.com/bifrost-finance/bifrost/pull/420/files#r759127339

Edit: Context is that the AsPrefixedGeneralIndex is already live in Statemine so I would want a good argument why it needs to be changed.

@yrong
Copy link
Author

yrong commented Nov 30, 2021

When I set up the asset XCM integration I figured that we need a unique asset identifier within Statemint, so I used the GeneralIndex. When receiving an asset identified by a GeneralIndex Statemint will interpret it as a local asset that it has sovereignty/control over. Now, I agree that we might want to allow the transaction of other assets in the future, so we will need to define how to identify them and then a PalletIndex or originating Parachain id might come in handy.

@yrong My question to you is: What keeps you from just sending a GeneralIndex as the asset's id? Left a corresponding comment here: https://github.com/bifrost-finance/bifrost/pull/420/files#r759127339

Edit: Context is that the AsPrefixedGeneralIndex is already live in Statemine so I would want a good argument why it needs to be changed.

@apopiak some clarification here bifrost-io/bifrost#420 (comment)

@apopiak
Copy link
Contributor

apopiak commented Dec 1, 2021

Alright, seems like the reanchoring logic in the executor means we should merge (something like) this.

Copy link
Contributor

@apopiak apopiak left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution from my side as well, btw. 🙏

Here are the adjustments I'd like to see:

@yrong yrong force-pushed the asset-transfer branch 2 times, most recently from 80f010b to 82eae51 Compare December 2, 2021 14:20
@yrong
Copy link
Author

yrong commented Dec 2, 2021

@apopiak some refact per your request list. For the xcm-builder upstream refact I would like to do later and we're looking forward this pr merged into polkadot-v0.9.13 release

@yrong yrong changed the title support asset transfer from other parachain support asset transfer from/to sibling chain Dec 2, 2021
@apopiak
Copy link
Contributor

apopiak commented Dec 2, 2021

Added a basic description. Feel free to expand it.

@apopiak
Copy link
Contributor

apopiak commented Dec 2, 2021

I'd like someone else to also have a look before merging this.

@apopiak apopiak added T6-XCM This PR/Issue is related to XCM. T7-system_parachains This PR/Issue is related to System Parachains. labels Dec 2, 2021
@gavofyork
Copy link
Member

Why is this against 0.9.13? Surely it should be going into master?

@@ -113,6 +114,44 @@ impl<T: Get<MultiLocation>> FilterAssetLocation for AssetsFrom<T> {
}
}

/// Converter struct with two main usage scenarios:
Copy link
Member

Choose a reason for hiding this comment

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

Documentation needs improvement. This is incomprehensible at present.

/// - with `reverse_ref` to convert a numeric asset ID (must be `TryFrom/TryInto<u128>`) into a `GeneralIndex` junction
/// 2. Transfer asset back from other para-chain to local
/// - with `convert_ref` to convert multilocation struct `(1,X2(ParaChain(para_id),GeneralIndex(general_index))` to a numeric asset ID
pub struct AsPrefixedGeneralIndexFromLocalOrRemote<
Copy link
Member

Choose a reason for hiding this comment

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

Wrong place for this - it should be in xcm-builder.

Copy link
Member

@gavofyork gavofyork left a comment

Choose a reason for hiding this comment

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

I remain yet to be convinced that this is needed or even a good idea.

@@ -481,7 +483,7 @@ pub type FungiblesTransactor = FungiblesAdapter<
ConvertedConcreteAssetId<
AssetId,
Balance,
AsPrefixedGeneralIndex<Local, AssetId, JustTry>,
AsPrefixedGeneralIndexFromLocalOrRemote<Local, Remote, AssetId, JustTry>,
Copy link
Member

Choose a reason for hiding this comment

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

No alterations to Statemint's XCM right now. Stuff like this is EXTREMELY dangerous to change and must a) be completely understood by me; and b) be in place in Kusama for some time prior.

&remote_asset_location,
)
.unwrap_or(u32::default());
assert_eq!(asset_id, 42);
Copy link
Contributor

@KiChjang KiChjang Dec 3, 2021

Choose a reason for hiding this comment

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

Previously I have tried to summarize your problem as the following:

if I'm hearing you correctly, you're saying that the GeneralIndex is used to specify which one of the many assets in statemine that you want to transfer to the destination chain via reserve transfers? In other words, your flow is exactly the same as InititateReserveWithdraw, but with the ability to specify an asset that is not KSM, correct?

But this unit test seems to indicate that you're doing something more -- you're trying to represent a remote asset containing a GeneralIndex locally as a GeneralIndex as well, e.g. a ../Parachain(1000)/GeneralIndex(42) gets converted and represented as ./GeneralIndex(42). Is this what you are trying to do?

If so, then this is absolutely not the right thing to do. Assets should always have ONE canonical MultiLocation to represent it, and as such, an asset located at ../Parachain(1000)/GeneralIndex(42) MUST be interpreted as a different asset from an asset located at ./GeneralIndex(42). If the intention is to mint a derivative asset of the one located at ../Parachain(1000)/GeneralIndex(42), then it is the AssetTransactor logic of your chain that needs to change, and not the XCM configuration, and definitely not the MultiLocation of the reserve asset.

Copy link
Contributor

@KiChjang KiChjang Dec 3, 2021

Choose a reason for hiding this comment

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

Pondering a bit more into this, I think what you are trying to solve is the reanchoring issue, in which case, this is an operation that should have been done by the XCM sender, and not the recipient, i.e. when sending an asset containing a AssetId of ../Parachain(1000)/GeneralIndex(42) to ../Parachain(1000), the XCM executor should have reanchored this AssetId to ./GeneralIndex(42) on the sender's chain. Did this not happen when you're testing?

Copy link
Author

@yrong yrong Dec 3, 2021

Choose a reason for hiding this comment

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

@KiChjang @apopiak I do not think we can achive that without hack in cumulus, let us put more details here:

  • transfer asset from statemine to our chain with TransferReserveAsset

since Local defined here original asset sent from statemine should be represented as ./GeneralIndex(42) and after the reanchor logic you mentioned above asset will be converted to ../Parachain(1000)/GeneralIndex(42) and in our chain we should add some adaptor logic to handle this

  • transfer asset back from our chain to statemine with InitiateReserveWithdraw

I agree with

Assets should always have ONE canonical MultiLocation to represent

so I think from the perspective of our chain statemine asset should always reprensented as ../Parachain(1000)/GeneralIndex(42) but after the reanchor logic here we can not convert it back to ./GeneralIndex(42) anyway so statemine will not understand without hack

Copy link
Contributor

@KiChjang KiChjang Dec 3, 2021

Choose a reason for hiding this comment

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

A couple of observations:

  1. What you interact on your local chain should always be ./GeneralIndex(42) and not ../Parachain(1000)/GeneralIndex(42), i.e. you interact with the derivative asset and never the reserve asset. Therefore when you do a reserve asset transfer, you will first execute a WithdrawAsset(GeneralIndex(42)) and you will get some GeneralIndex(42) stored in your chain's holding register.
  2. The next step in the process is InitiateReserveWithdraw, which takes the derivative asset in the holding register and sends it back to Statemine. Before that happens though, what should happen on your location chain is that it should burn and convert the derivative asset (i.e. GeneralIndex(42)) to the reserve asset (i.e. ../Parachain(1000)/GeneralIndex(42)).
  3. The receiver should never be responsible for converting between derivative asset IDs and reserve asset IDs. Doing so would mean that all derivative assets should "register" themselves on the reserve chain, which is absolutely not the direction we want to head towards.

I've looked into the code for the XCM executor now and it does seem like there's no way to burn and convert a derivative asset back to its reserve asset during the execution of InitiateReserveWithdraw, and I think that is the crux of the issue here.

Copy link
Author

@yrong yrong Dec 3, 2021

Choose a reason for hiding this comment

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

@KiChjang

What you interact on your local chain should always be ./GeneralIndex(42) and not ../Parachain(1000)/GeneralIndex(42) i.e. you interact with the derivative asset and never the reserve asset.

No, I do think we should use reserve asset rather than derivative asset here. except for statemine we need to talk to other parachains like Acala and as you mentioned before only the GeneralIndex is too abstract. How to differ the same reprensentation ./GeneralIndex(42) in acala and statemine but with different meaning?

Copy link
Author

Choose a reason for hiding this comment

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

That could easily be solved if you prepend an ExchangeAsset instruction before you execute InitiateReserveWithdraw, and in ExchangeAsset, your custom XCM executor will convert your derivative asset back to the reserve asset.

@KiChjang I really do not know this primitive and will try, thanks for your suggestion

Copy link
Author

Choose a reason for hiding this comment

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

@KiChjang
seems ExchangeAsset you mentioned not implemented yet, is it in some feature branch? And I would like to know if there is any other solution or implementation other parachain team already solved as you mentioned?

Copy link
Contributor

@KiChjang KiChjang Dec 4, 2021

Choose a reason for hiding this comment

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

It is purposefully not implemented; as I said, we do not provide any opinionated approaches as to how a chain should manage their bookkeeping of their reserve assets. Any chain that wants to execute ExchangeAsset will have to provide their own implementation. I'm not as familiar as to how other parachain teams in the ecosystem solves this problem, however, but I would think that any parachain that has onboarded to Kusama and has dealt with reserve assets would have come up with a solution dealing with it.

Copy link
Author

@yrong yrong Dec 4, 2021

Choose a reason for hiding this comment

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

Understood. we can implement this to do some asset convert logic from sender side. But I would assume at last we still need InitiateReserveWithdraw primitive to send asset back to statemine,right? From what I've tested after the renchor logic in InitiateReserveWithdraw here asset will still be converted to ../Parachain(1000)/GeneralIndex(42) no matter what it previously is. And this format can not be recognized by statemine without hack which only knows ./GeneralIndex(42).Correct me if I'm wrong

Copy link
Contributor

Choose a reason for hiding this comment

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

Correct, the reanchoring logic is what we're in the middle of investigating as well. However, in the context of this PR, your changes will unfortunately not be the correct fix. We will provide an update in #827 as soon as we have any more details to share.

@gavofyork
Copy link
Member

gavofyork commented Dec 4, 2021

#827 (comment)

As I mention here, the primitives in the XCM builder expect that the derivative asset (on your chain, which represented ownership of the reserve assets on Statemint) should generally be represented in XCM as the same conceptual location as the reserve asset.

So, generally this will mean it is GeneralIndex(42) on Statemint and (Parent, Parachain(1000), GeneralIndex(42)) on your parachain.

Reanchoring should ensure that the former MultiLocation value (used on Statemint) gets changed into the latter MultiLocation value (used on your chain) and back again.

If you believe there is an issuer with the reanchoring, then I'll just need a failing test to demonstrate this. So far I have not seen one.

You will likely want to have an instance of the Assets pallet which represents all Statemint assets. You might also write your own Convert<MultiLocation, AssetId> to feed into the FungiblesAdapter which would effectively "namespace" the Statemint assets within your general assets pallet; to do this you'd need to ensure that your AssetId type is big enough to contain all Statemint AssetId values as well as any other AssetId values specific to your chain which you might want.

None of this should require alterations to Statemint.

@gavofyork
Copy link
Member

Ok, I believe I figured it out. There is indeed a subtle issue with the reanchoring logic, specifically it gives a correct non-canonical destination value. Basically, it sends a MultiLocation to Statemint of (Parent, Statemint, 42) rather than just (42). From Statemint's point of view, they're equivalent, only the former includes a superfluous (Parent, Statemint) prefix.

I'll put in something to simplify it and stuff should work.

@gavofyork
Copy link
Member

paritytech/polkadot#4470 should fix things

@yrong
Copy link
Author

yrong commented Dec 7, 2021

@gavofyork @KiChjang seems this fix will break current behavior how we bifrost(2001) communicate with acala(2000) and other parachains, breaking test cases as following:

#[test]
fn reanchor_1_works() {
  let mut id: MultiLocation = (Parent, Parachain(2000), GeneralKey(42.encode())).into();
  let ancestry = Parachain(2001).into();
  let target = (Parent, Parachain(2000)).into();
  let expected = (Parent, Parachain(2001), GeneralKey(42.encode())).into();
  id.reanchor(&target, &ancestry).unwrap();
  assert_eq!(id, expected);
}

maybe @xlc can add some comments here also

@yrong yrong closed this Dec 7, 2021
@yrong
Copy link
Author

yrong commented Dec 7, 2021

@KiChjang I also see this pr which seems we decide to add pallet index in between to represent asset in statemine, right? if so we may also revamp adaptor logic from our side accordingly.

@yrong yrong reopened this Dec 7, 2021
@gavofyork
Copy link
Member

gavofyork commented Dec 7, 2021

@gavofyork @KiChjang seems this fix will break current behavior how we bifrost(2001) communicate with acala(2000) and other parachains, breaking test cases as following:

#[test]
fn reanchor_1_works() {
  let mut id: MultiLocation = (Parent, Parachain(2000), GeneralKey(42.encode())).into();
  let ancestry = Parachain(2001).into();
  let target = (Parent, Parachain(2000)).into();
  let expected = (Parent, Parachain(2001), GeneralKey(42.encode())).into();
  id.reanchor(&target, &ancestry).unwrap();
  assert_eq!(id, expected);
}

maybe @xlc can add some comments here also

Yes. This test is very much incorrect. The GeneralKey(42) exists on parachain #2000, not #2001. So expected should be just GeneralKey(42).

From parachain #2001's point of view (where we begin):

  1. ancestry: Parachain(2001)
  2. parachain Polkadot v0.9.32 #2000: (Parent, Parachain(2000))
  3. parachain Bump serde from 1.0.150 to 1.0.151 #2001: Here
  4. the asset: (Parent, Parachain(2000), GeneralKey(42))

From parachain #2000's point of view (where we reanchor to):
5. ancestry: Parachain(2000)
6. parachain #2000: Here
7. parachain #2001: (Parent, Parachain(2001))
8. the asset: GeneralKey(42)

So the reanchoring should take the ancestry of the location where we begin (1), the destination (relative to where we begin) (2) and the asset (also relatively to where we begin) (4).

From these three pieces of information, it is able to tell us how the destination would refer to the same asset (8).

@gavofyork gavofyork closed this Dec 7, 2021
@gavofyork
Copy link
Member

gavofyork commented Dec 7, 2021

Closing since this is definitely not the right fix. Please move any further discussion to paritytech/polkadot#4470 or #833.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
T6-XCM This PR/Issue is related to XCM. T7-system_parachains This PR/Issue is related to System Parachains.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants