-
Notifications
You must be signed in to change notification settings - Fork 1.1k
pallet-xcm: add new extrinsic for asset transfers using explicit XCM transfer types #3695
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
pallet-xcm: add new extrinsic for asset transfers using explicit XCM transfer types #3695
Conversation
Add `transfer_assets_using_reserve()` for transferring assets from local chain to destination chain using an explicit reserve location (typically local Asset Hub). By default, an asset's reserve is its origin chain. But sometimes we may want to explicitly use another chain as reserve (as long as allowed by runtime `IsReserve` filter). This is very helpful for transferring assets with multiple configured reserves (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used reserve. E.g. For transferring Foreign Assets over a bridge, Asset Hub must be used as the reserve location.
…ge using local asset hub as reserve
|
Just to double check. This two reserves issue happens because a token is teleported between two locations, right? It's not because a token is reserved transferred somewhere and then reserved transferred again from that main reserve into some sort of secondary reserve. |
It is the former. Teleporting assets between chains requires the same level of trust as allowing those chains to act as reserves for said asset. With teleports, it is conceptually the same asset on both chains, unlike reserve-based transfers where the real asset stays put within its borders while outside the borders (i.e. destination chain) a derivative asset is used to represent it. This PR adds utility function for reserve-based transfers (not teleports) of a given asset, but using one of the assets secondary reserve locations (any other chain where it can be teleported to/from). I added some example scenarios to the PR description for more clarity. |
…sfer-using-explicit-reserve
|
Hey @acatangiu, one scenario that is currently not covered by the Moonbeam only has the relay chain as DOT reserve, and we can't have AH because then we would have 2 reserve chains for 1 asset, which could create many new problems with SA's balances. So, ideally, we could have a way in which we do something like: The main problem with the approach above is the Mainly because we have new assets being created in AH, and they are not sufficient assets, meaning that to send them back, users need to pay with either USDC/USDT. XTokens allowed for such an approach in which 1 XCM is sent to the Relay Chain to send DOT to Parachains SA account in AH while another XCM was sent to AH to withdraw DOT to buy execution to transfer the token. However, this approach relied on the first XCM message executing successfully, so teams would usually have a DOT buffer in AH SA. |
|
That is correct, and I don't believe
This is the same problem HydraDX and other parachains too are probably facing. The problem is not limited to DOT. The problem ultimately comes from having both reserve-based transfers as well as teleports allowed in our ecosystem. Once you allow a teleport between two chains, then both of those should be able to act as reserves, otherwise the teleport doesn't make sense. E.g.: parachain assets registered as ForeignAssets on AH and teleported there. The "owner" parachain will want to allow AH to act as a reserve for them, and expose their asset to all the other chains/bridges/CEXs/etc that are already integrated with AH.
Exactly! Ultimately, parachains need "some buffer" in AH SA anyway. I am hoping that with some smarter way of keeping SAs balanced, this problem will go away and workarounds like sending multiple XCMs and waiting for them to happen in a certain sequence will not be needed anymore. The silver lining here is that the problem is very generic and asset/chain agnostic, and we can decide on some mechanism to auto-balance these SAs, that every chain can use. |
The main issue is SA management. It would then require users to go through undesirable routes to get their assets where they want to (for example, SA in Polkadot has 100 DOT, and in AH, it has 20 DOT, and a user wants to get 40 DOT in Polkadot). This creates bad UX. What about creating a call in which you can route XCM and assets through another chain? I guess the application of this is too specific. And could we do something similar to what xTokens is doing, but just from a UI point of view? |
In practice, liquidity should be much larger, and with some periodic automated balancing mechanism this should never happen (unless the user is some whale moving a lot of liquidity at once). A somewhat smarter rebalancer: component be deployed on target chains that tracks SA balance and requests liquidity from owner chain if it falls below a threshold, thus triggering rebalancing on-demand.
Could be done, but don't know of a way to do it generically, chain/asset agnostic - and without a generic mechanism I strongly recommend against it as it will not scale. We will fragment the ecosystem even further with tokens and chains having custom workarounds and special gotchas. There is also an efficiency disadvantage (not really important, but worth mentioning): at the end of the day this is also an automated SA rebalancing mechanism, it just happens on every transfer regardless of need (with the user paying for two extra XCMs each time: to 1. signal the main reserve to 2. send liquidity to secondary reserve). |
|
This pull request has been mentioned on Polkadot Forum. There might be relevant details there: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/1 |
|
I created a forum topic to get more visibility and ideas on the problem: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538 |
|
I raised this issue a year ago paritytech/cumulus#2398 |
Apparently it takes more than raising an issue to solve an ecosystem-wide problem :) Please join the discussion on the forum, help me engage all the parachains, and collectively decide on a solution. I can commit to implement whatever we, the ecosystem, decide is necessary, if help is required from Parity. |
…estination (#4260) Change `transfer_assets_using_type()` to not assume `DepositAssets` as the intended use of the assets on the destination. Instead provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. Note: this is a follow-up on #3695, bringing in an API change for `transfer_assets_using_type()`. This is ok as the previous version has not been yet released. Thus, its first release will include the new API proposed by this PR. This allows usecases such as: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/4 BTW: all this pallet-xcm asset transfers code will be massively reduced once we have polkadot-fellows/xcm-format#54 --------- Signed-off-by: Adrian Catangiu <[email protected]>
|
This pull request has been mentioned on Polkadot Forum. There might be relevant details there: https://forum.polkadot.network/t/xcm-user-and-developer-experience-improvements/4511/21 |
…h#3695) Add `transfer_assets_using_type_and_then()` for transferring assets from local chain to destination chain using explicit XCM transfer types: - `TransferType::LocalReserve`: transfer assets to sovereign account of destination chain and forward a notification XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. - `TransferType::DestinationReserve`: burn local assets and forward a notification to `dest` chain to withdraw the reserve assets from this chain's sovereign account and deposit them to `beneficiary`. - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` chain to move reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically the remote `reserve` is Asset Hub. - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to mint/teleport assets and deposit them to `beneficiary`. By default, an asset's reserve is its origin chain. But sometimes we may want to explicitly use another chain as reserve (as long as allowed by runtime `IsReserve` filter). This is very helpful for transferring assets with multiple configured reserves (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used reserve. E.g. For transferring Foreign Assets over a bridge, Asset Hub must be used as the reserve location. ERC20-tokenX is registered on AssetHub as a ForeignAsset by the Polkadot<>Ethereum bridge (Snowbridge). Its asset_id is something like `(parents:2, (GlobalConsensus(Ethereum), Address(tokenX_contract)))`. Its _original_ reserve is Ethereum (only we can't use Ethereum as a reserve in local transfers); but, since tokenX is also registered on AssetHub as a ForeignAsset, we can use AssetHub as a reserve. With this PR we can transfer tokenX from ParaA to ParaB while using AssetHub as a reserve. AssetA created on ParaA but also registered as foreign asset on Asset Hub. Can use AssetHub as a reserve. And all of the above can be done while still controlling transfer type for `fees` so mixing assets in same transfer is supported. Provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. This allows usecases such as: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/4 Signed-off-by: Adrian Catangiu <[email protected]>
…h#3695) Add `transfer_assets_using_type_and_then()` for transferring assets from local chain to destination chain using explicit XCM transfer types: - `TransferType::LocalReserve`: transfer assets to sovereign account of destination chain and forward a notification XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. - `TransferType::DestinationReserve`: burn local assets and forward a notification to `dest` chain to withdraw the reserve assets from this chain's sovereign account and deposit them to `beneficiary`. - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` chain to move reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically the remote `reserve` is Asset Hub. - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to mint/teleport assets and deposit them to `beneficiary`. By default, an asset's reserve is its origin chain. But sometimes we may want to explicitly use another chain as reserve (as long as allowed by runtime `IsReserve` filter). This is very helpful for transferring assets with multiple configured reserves (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used reserve. E.g. For transferring Foreign Assets over a bridge, Asset Hub must be used as the reserve location. ERC20-tokenX is registered on AssetHub as a ForeignAsset by the Polkadot<>Ethereum bridge (Snowbridge). Its asset_id is something like `(parents:2, (GlobalConsensus(Ethereum), Address(tokenX_contract)))`. Its _original_ reserve is Ethereum (only we can't use Ethereum as a reserve in local transfers); but, since tokenX is also registered on AssetHub as a ForeignAsset, we can use AssetHub as a reserve. With this PR we can transfer tokenX from ParaA to ParaB while using AssetHub as a reserve. AssetA created on ParaA but also registered as foreign asset on Asset Hub. Can use AssetHub as a reserve. And all of the above can be done while still controlling transfer type for `fees` so mixing assets in same transfer is supported. Provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. This allows usecases such as: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/4 Signed-off-by: Adrian Catangiu <[email protected]>
…4462) Add `transfer_assets_using_type_and_then()` for transferring assets from local chain to destination chain using explicit XCM transfer types: - `TransferType::LocalReserve`: transfer assets to sovereign account of destination chain and forward a notification XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. - `TransferType::DestinationReserve`: burn local assets and forward a notification to `dest` chain to withdraw the reserve assets from this chain's sovereign account and deposit them to `beneficiary`. - `TransferType::RemoteReserve(reserve)`: burn local assets, forward XCM to `reserve` chain to move reserves from this chain's SA to `dest` chain's SA, and forward another XCM to `dest` to mint and deposit reserve-based assets to `beneficiary`. Typically the remote `reserve` is Asset Hub. - `TransferType::Teleport`: burn local assets and forward XCM to `dest` chain to mint/teleport assets and deposit them to `beneficiary`. By default, an asset's reserve is its origin chain. But sometimes we may want to explicitly use another chain as reserve (as long as allowed by runtime `IsReserve` filter). This is very helpful for transferring assets with multiple configured reserves (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used reserve. E.g. For transferring Foreign Assets over a bridge, Asset Hub must be used as the reserve location. ERC20-tokenX is registered on AssetHub as a ForeignAsset by the Polkadot<>Ethereum bridge (Snowbridge). Its asset_id is something like `(parents:2, (GlobalConsensus(Ethereum), Address(tokenX_contract)))`. Its _original_ reserve is Ethereum (only we can't use Ethereum as a reserve in local transfers); but, since tokenX is also registered on AssetHub as a ForeignAsset, we can use AssetHub as a reserve. With this PR we can transfer tokenX from ParaA to ParaB while using AssetHub as a reserve. AssetA created on ParaA but also registered as foreign asset on Asset Hub. Can use AssetHub as a reserve. And all of the above can be done while still controlling transfer type for `fees` so mixing assets in same transfer is supported. Provides the caller with the ability to specify custom XCM that be executed on `dest` chain as the last step of the transfer, thus allowing custom usecases for the transferred assets. E.g. some are used/swapped/etc there, while some are sent further to yet another chain. This allows usecases such as: https://forum.polkadot.network/t/managing-sas-on-multiple-reserve-chains-for-same-asset/7538/4 Signed-off-by: Adrian Catangiu <[email protected]>
|
This pull request has been mentioned on Polkadot Forum. There might be relevant details there: https://forum.polkadot.network/t/rfc-xcm-asset-transfer-program-builder/8528/1 |
* chore: ⬆️ upgrade dependencies to 2506 * refactor: 🚨 use workspace dependencies in pallet crowdloan rewards * chore: 📌 depend on crates.io ethereum (similarly to frontier) * fix: 🐛 remove PassByCodec PassByCodec was replaced by PassFatPointerAndDecode in paritytech/polkadot-sdk@1cb22ca * fix: 🐛 use Weight::MAX instead of Weight::max_value() * fix: 🐛 add TrieCacheContext parameter to state_at * fix: 🐛 add missing relay_parent_descendants and collator_peer_id fields to ParachainInherentData * fix: 🐛 remove deprecated AssetHubMigrationStarted config * fix: 🐛 update ExecuteXcm::prepare() to accept weight_limit parameter * fix: 🐛 add generic parameters to Sha3FIPS256<Runtime, ()> * fix: 🐛 add metrics field to BuildNetworkParams * chore: 📌 upgrade pins * fix: 🐛 add OnNewHead to bridge parachain configs This adds the required OnNewHead configuration parameter introduced in paritytech/polkadot-sdk#8531 * fix: 🐛 add relay chain slot duration to FixedVelocityConsensusHook * fix: 🐛 add SlotDuration trait items Upstream PR: Moonsong-Labs/moonkit#83 - "Use PostInherents to validate timestamp" - Author: Rodrigo Quelhas (RomarQ) - Merged: September 30, 2025 Details: This trait item is used in the PostInherents validation to ensure that the configured block time in the node and runtime is compatible with Nimbus's consensus mechanism. The value is set to MILLISECS_PER_BLOCK (12000ms for Moonbeam), matching the parachain's block production time. This change prepares for Polkadot SDK updates that deprecated the CheckInherents parameter in favor of PostInherents validation. * fix: 🐛 add RelayParentOffset trait item Upstream PR: paritytech/polkadot-sdk#8299 - "Allow building on older relay parents" - Author: Sebastian Kunert (skunert) - Merged: May 29, 2025 Details: With an offset of 0, blocks are built on the current relay chain tip. Non-zero values allow building on older relay parents (e.g., offset of 2 means building 2 blocks behind the tip) to avoid building on short-lived relay chain forks. The runtime enforces this offset by requiring multiple relay parent descendants to be present in the set_validation_data inherent. * fix: 🐛 refactor build_relay_chain_interface to return only required tuple elements * fix: 🐛 fixed RuntimeOrigin trait bounds by removing From<Option<AccountId>> bound Fixed RuntimeOrigin trait bounds - Removed From<Option<AccountId>> bound and updated all precompiles to use frame_system::RawOrigin::Signed().into() * fix: 🐛 update cumulus_pallet_xcmp_queue WeightInfo methods * fix: 🐛 fix AccountId ambiguity Added proper type qualifications and EncodeLike trait bound in crowdloan-rewards precompile * fix: 🐛 use QueueFootprintQuery trait for message queue footprint method paritytech/polkadot-sdk#8021 * fix: 🐛 remove deprecated RuntimeEvent * fix: 🐛 update the runtime interface to use explicit marshalling strategies PR #7375 - Host/Runtime Interface Refactoring 🔗 paritytech/polkadot-sdk#7375 * fix: 🐛 do not enable SharedTrieCache * fix: 🐛 implement BenchmarkHelper * fix: 🐛 remove PalletTransactionPaymentBenchmark * fix: 🐛 add missing crate in Cargo feature * fix: 🐛 remove RuntimeEvent associated type Polkadot SDK changes: paritytech/polkadot-sdk#7229 * fix: 🐛 remove unused imports * fix: 🐛 remove RuntimeEvent associated type * fix: 🐛 remove AssetHubMigrationStarted associated type * fix: 🐛 add RelayParentOffset associated type * fix: 🐛 add metrics_registry: None to database configuration * fix: 🐛 add warm_up_trie_cache: None to service configuration * fix: 🐛 fix reference to deprecated RuntimeEvent associated type * fix: 🐛 update WeightBounds trait API * fix: 🐛 add missing fields to ParachainInherentData * fix: 🐛 add missing associated types to Runtime * fix: 🐛 replace `Fail(Option<T::Hash>, XcmError)` with `Fail(Option<T::Hash>, InstructionError)` * style: 🎨 format code * fix: 🐛 fix `XcmExecutor::prepare` args * fix: 🐛 impl create_bare from CreateInherent trait * fix: 🐛 mock BenchmarkHelper * fix: ⬆️ upgrade frame-metadata * chore: ⬆️ upgrade polkadot-sdk * fix: 🐛 silence warning * style: 🚨 mark args as unused * fix: 🐛 fix deprecation warning * style: 🎨 format code * test: 🐛 update snapshot * chore: 📌 update pins * refactor: ♻️ move lint directive to root Cargo.toml * chore: ⚡ run benchmarks * chore: ⚡ run benchmarks * test: ✅ assert specific error type * test: ✅ use proper assert_noop! * test: ✅ use new extrinsic for asset transfers using explicit XCM transfer types paritytech/polkadot-sdk#3695 * fix: 🐛 pin udeps Rust nightly version * Revert "fix: 🐛 pin udeps Rust nightly version" This reverts commit 09e6d03. * fix: 🐛 fix some Cargo.toml features * revert: 🔥 remove AssetHubMigrationStarted storage flag * refactor: 🔥 remove unused import * fix: 🐛 add missing feature flags for pallet-assets dependency * refactor: 🚨 remove duplicated feature flags * revert: 🔥 delete file committed by mistake * fix: 📌 properly pin polkadot-sdk version * test: ✅ initialize ParachainSystem HostConfiguration in runtime tests The ParentAsUmp XCM router now validates upward messages by checking HostConfiguration, which was previously uninitialized in test environments, causing ErrorValidating and SendFailure errors. * update polkadot-sdk pin * update polkadot-sdk pin * fix unit tests * update polkadot-sdk pin * fix check-unused-dependencies * fix test * refactor: ♻️ remove IdentityBenchmarkHelper * Revert "refactor: ♻️ remove IdentityBenchmarkHelper" This reverts commit b872ee4. * revert: ⏪ restore pallet_transaction_payment * test: ✅ update tests call_pallet_xcm_with_fee * refactor: ♻️ add mock_abridged_host_config function * fix pallet-identity benchmarks * fix: 🐛 add helper function to set up XCM router for benchmarks * fix: 🐛 add custom delivery helper for Moonbeam * chore: 🔧 update weights * style: 🎨 keep original name TestDeliveryHelper * fix pallet-randomness benchmarks * Revert "chore: 🔧 update weights" This reverts commit ea91959. * test: ✅ update snapshots --------- Co-authored-by: Rodrigo Quelhas <[email protected]> Co-authored-by: Rodrigo Quelhas <[email protected]>
Description
Add
transfer_assets_using()for transferring assets from local chain to destination chain using explicit XCM transfer types such as:TransferType::LocalReserve: transfer assets to sovereign account of destination chain and forward a notification XCM todestto mint and deposit reserve-based assets tobeneficiary.TransferType::DestinationReserve: burn local assets and forward a notification todestchain to withdraw the reserve assets from this chain's sovereign account and deposit them tobeneficiary.TransferType::RemoteReserve(reserve): burn local assets, forward XCM toreservechain to move reserves from this chain's SA todestchain's SA, and forward another XCM todestto mint and deposit reserve-based assets tobeneficiary. Typically the remotereserveis Asset Hub.TransferType::Teleport: burn local assets and forward XCM todestchain to mint/teleport assets and deposit them tobeneficiary.By default, an asset's reserve is its origin chain. But sometimes we may want to explicitly use another chain as reserve (as long as allowed by runtime
IsReservefilter).This is very helpful for transferring assets with multiple configured reserves (such as Asset Hub ForeignAssets), when the transfer strictly depends on the used reserve.
E.g. For transferring Foreign Assets over a bridge, Asset Hub must be used as the reserve location.
Example usage scenarios
Transfer bridged ethereum ERC20-tokenX between ecosystem parachains.
ERC20-tokenX is registered on AssetHub as a ForeignAsset by the Polkadot<>Ethereum bridge (Snowbridge). Its asset_id is something like
(parents:2, (GlobalConsensus(Ethereum), Address(tokenX_contract))). Its original reserve is Ethereum (only we can't use Ethereum as a reserve in local transfers); but, since tokenX is also registered on AssetHub as a ForeignAsset, we can use AssetHub as a reserve.With this PR we can transfer tokenX from ParaA to ParaB while using AssetHub as a reserve.
Transfer AssetHub ForeignAssets between parachains
AssetA created on ParaA but also registered as foreign asset on Asset Hub. Can use AssetHub as a reserve.
And all of the above can be done while still controlling transfer type for
feesso mixing assets in same transfer is supported.Tests
Added integration tests for showcasing:
Later Edit: NOTE:
This PR has a followup PR that slightly changes the name and API: #4260