-
Notifications
You must be signed in to change notification settings - Fork 47
feat: better XCM fee management #528
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
Changes from 10 commits
8e17acc
ef1e657
31c3015
de7f378
a32fdc4
7611efa
e3c81ac
7c06647
b69dba2
d1943ae
8f4dbd0
d683a3f
8ba834e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,262 @@ | ||
| // KILT Blockchain – https://botlabs.org | ||
| // Copyright (C) 2019-2023 BOTLabs GmbH | ||
|
|
||
| // The KILT Blockchain is free software: you can redistribute it and/or modify | ||
| // it under the terms of the GNU General Public License as published by | ||
| // the Free Software Foundation, either version 3 of the License, or | ||
| // (at your option) any later version. | ||
|
|
||
| // The KILT Blockchain is distributed in the hope that it will be useful, | ||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| // GNU General Public License for more details. | ||
|
|
||
| // You should have received a copy of the GNU General Public License | ||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| // If you feel like getting in touch with us, you can do so at [email protected] | ||
|
|
||
| use dip_support::IdentityDetailsAction; | ||
| use pallet_dip_provider::traits::{IdentityProofDispatcher, TxBuilder}; | ||
| use parity_scale_codec::Encode; | ||
| use sp_core::Get; | ||
| use sp_std::marker::PhantomData; | ||
| use xcm::v3::{ | ||
| Instruction::{BuyExecution, DepositAsset, DescendOrigin, RefundSurplus, Transact, WithdrawAsset}, | ||
| InteriorMultiLocation, | ||
| Junction::AccountId32, | ||
| Junctions::{Here, X1}, | ||
| MultiAsset, | ||
| MultiAssetFilter::Wild, | ||
| MultiAssets, MultiLocation, OriginKind, SendError, SendXcm, Weight, | ||
| WeightLimit::Limited, | ||
| WildMultiAsset::All, | ||
| Xcm, | ||
| }; | ||
|
|
||
| // Dispatcher using a type implementing the `SendXcm` trait. | ||
| // It properly encodes the `Transact` operation, then delegates everything else | ||
| // to the sender, similarly to what the XCM pallet's `send` extrinsic does. | ||
| pub struct XcmRouterIdentityDispatcher<Router, UniversalLocationProvider>( | ||
| PhantomData<(Router, UniversalLocationProvider)>, | ||
| ); | ||
|
|
||
| impl<Router, UniversalLocationProvider, Identifier, ProofOutput, AccountId, Details> | ||
| IdentityProofDispatcher<Identifier, ProofOutput, AccountId, Details> | ||
| for XcmRouterIdentityDispatcher<Router, UniversalLocationProvider> | ||
| where | ||
| Router: SendXcm, | ||
| UniversalLocationProvider: Get<InteriorMultiLocation>, | ||
| Identifier: Encode, | ||
| ProofOutput: Encode, | ||
| AccountId: Into<[u8; 32]> + Clone, | ||
| { | ||
| type PreDispatchOutput = Router::Ticket; | ||
| type Error = SendError; | ||
|
|
||
| fn pre_dispatch<Builder: TxBuilder<Identifier, ProofOutput, Details>>( | ||
| action: IdentityDetailsAction<Identifier, ProofOutput, Details>, | ||
| source: AccountId, | ||
| asset: MultiAsset, | ||
| weight: Weight, | ||
| destination: MultiLocation, | ||
| ) -> Result<(Self::PreDispatchOutput, MultiAssets), Self::Error> { | ||
| // TODO: Replace with proper error handling | ||
| let dest_tx = Builder::build(destination, action) | ||
| .map_err(|_| ()) | ||
| .expect("Failed to build call"); | ||
|
|
||
| // TODO: Set an error handler and an appendix to refund any leftover funds to | ||
| // the provider parachain sovereign account. | ||
| let operation = [[ | ||
| DescendOrigin(X1(AccountId32 { | ||
| network: None, | ||
| id: source.clone().into(), | ||
| })), | ||
| WithdrawAsset(asset.clone().into()), | ||
| BuyExecution { | ||
| fees: asset, | ||
| weight_limit: Limited(weight), | ||
| }, | ||
| Transact { | ||
| origin_kind: OriginKind::Native, | ||
| require_weight_at_most: weight, | ||
| call: dest_tx, | ||
| }, | ||
| RefundSurplus, | ||
| DepositAsset { | ||
| assets: Wild(All), | ||
| beneficiary: MultiLocation { | ||
| parents: 1, | ||
| // Re-anchor the same account junction as seen from the destination. | ||
| // TODO: Error handling | ||
| interior: Here | ||
| .into_location() | ||
| .reanchored(&destination, UniversalLocationProvider::get()) | ||
| .unwrap() | ||
| .pushed_with_interior(AccountId32 { | ||
| network: None, | ||
| id: source.into(), | ||
| }) | ||
| .unwrap() | ||
| .interior, | ||
| }, | ||
| }, | ||
| ]] | ||
| .concat(); | ||
| // TODO: Restructure the trait to be able to inject the [Instruction] provider, | ||
| // and unit test that. | ||
| debug_assert!(barriers::instruction_matcher(&operation).is_ok()); | ||
| let op = Xcm(operation); | ||
| Router::validate(&mut Some(destination), &mut Some(op)) | ||
| } | ||
|
|
||
| fn dispatch(pre_output: Self::PreDispatchOutput) -> Result<(), Self::Error> { | ||
| Router::deliver(pre_output).map(|_| ()) | ||
| } | ||
| } | ||
|
|
||
| pub mod barriers { | ||
| use super::*; | ||
|
|
||
| use frame_support::ensure; | ||
| use xcm::v3::{Instruction, Junction::Parachain, ParentThen}; | ||
| use xcm_executor::traits::ShouldExecute; | ||
|
|
||
| pub(crate) fn instruction_matcher<RuntimeCall>(instructions: &[Instruction<RuntimeCall>]) -> Result<(), ()> { | ||
| let mut iter = instructions.iter(); | ||
| match ( | ||
| iter.next(), | ||
| iter.next(), | ||
| iter.next(), | ||
| iter.next(), | ||
| iter.next(), | ||
| iter.next(), | ||
| iter.next(), | ||
| ) { | ||
| ( | ||
| Some(DescendOrigin(X1(AccountId32 { .. }))), | ||
| Some(WithdrawAsset { .. }), | ||
| Some(BuyExecution { .. }), | ||
| Some(Transact { | ||
| origin_kind: OriginKind::Native, | ||
| .. | ||
| }), | ||
| Some(RefundSurplus), | ||
| Some(DepositAsset { .. }), | ||
| None, | ||
| ) => Ok(()), | ||
| _ => Err(()), | ||
| } | ||
| } | ||
|
|
||
| // Allows a parachain to descend to an `X1(AccountId32)` junction, withdraw fees | ||
| // from their balance, and then carry on with a `Transact`. | ||
| // Must be used **ONLY** in conjunction with the `AccountIdJunctionAsParachain` | ||
| // origin converter. | ||
| pub struct AllowParachainProviderAsSubaccount<ProviderParaId>(PhantomData<ProviderParaId>); | ||
|
|
||
| impl<ProviderParaId> ShouldExecute for AllowParachainProviderAsSubaccount<ProviderParaId> | ||
| where | ||
| ProviderParaId: Get<u32>, | ||
| { | ||
| fn should_execute<RuntimeCall>( | ||
| origin: &MultiLocation, | ||
| instructions: &mut [Instruction<RuntimeCall>], | ||
| _max_weight: Weight, | ||
| _weight_credit: &mut Weight, | ||
| ) -> Result<(), ()> { | ||
| // Ensure that the origin is a parachain allowed to act as identity provider. | ||
| ensure!( | ||
| *origin == ParentThen(Parachain(ProviderParaId::get()).into()).into(), | ||
| () | ||
| ); | ||
| instruction_matcher(instructions) | ||
| } | ||
| } | ||
|
|
||
| // Decorate an existing barrier to add one more check in case all the previous | ||
| // barriers fail. | ||
| pub struct OkOrElseCheckForParachainProvider<Barrier, ProviderParaId>(PhantomData<(Barrier, ProviderParaId)>); | ||
|
|
||
| impl<Barrier, ProviderParaId> ShouldExecute for OkOrElseCheckForParachainProvider<Barrier, ProviderParaId> | ||
| where | ||
| Barrier: ShouldExecute, | ||
| ProviderParaId: Get<u32>, | ||
| { | ||
| fn should_execute<RuntimeCall>( | ||
| origin: &MultiLocation, | ||
| instructions: &mut [Instruction<RuntimeCall>], | ||
| max_weight: Weight, | ||
| weight_credit: &mut Weight, | ||
| ) -> Result<(), ()> { | ||
| Barrier::should_execute(origin, instructions, max_weight, weight_credit).or_else(|_| { | ||
| AllowParachainProviderAsSubaccount::<ProviderParaId>::should_execute( | ||
| origin, | ||
| instructions, | ||
| max_weight, | ||
| weight_credit, | ||
| ) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // Decorate an existing barrier to check for the provider parachain origin only | ||
| // in case none of the previous barriers fail. | ||
| pub struct ErrOrElseCheckForParachainProvider<Barrier, ProviderParaId>(PhantomData<(Barrier, ProviderParaId)>); | ||
|
|
||
| impl<Barrier, ProviderParaId> ShouldExecute for ErrOrElseCheckForParachainProvider<Barrier, ProviderParaId> | ||
| where | ||
| Barrier: ShouldExecute, | ||
| ProviderParaId: Get<u32>, | ||
| { | ||
| fn should_execute<RuntimeCall>( | ||
| origin: &MultiLocation, | ||
| instructions: &mut [Instruction<RuntimeCall>], | ||
| max_weight: Weight, | ||
| weight_credit: &mut Weight, | ||
| ) -> Result<(), ()> { | ||
| Barrier::should_execute(origin, instructions, max_weight, weight_credit)?; | ||
| AllowParachainProviderAsSubaccount::<ProviderParaId>::should_execute( | ||
| origin, | ||
| instructions, | ||
| max_weight, | ||
| weight_credit, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub mod origins { | ||
| use super::*; | ||
|
|
||
| use xcm::v3::{Junction::Parachain, Junctions::X2}; | ||
| use xcm_executor::traits::ConvertOrigin; | ||
|
|
||
| pub struct AccountIdJunctionAsParachain<ProviderParaId, ParachainOrigin, RuntimeOrigin>( | ||
| PhantomData<(ProviderParaId, ParachainOrigin, RuntimeOrigin)>, | ||
| ); | ||
|
|
||
| impl<ProviderParaId, ParachainOrigin, RuntimeOrigin> ConvertOrigin<RuntimeOrigin> | ||
| for AccountIdJunctionAsParachain<ProviderParaId, ParachainOrigin, RuntimeOrigin> | ||
| where | ||
| ProviderParaId: Get<u32>, | ||
| ParachainOrigin: From<u32>, | ||
| RuntimeOrigin: From<ParachainOrigin>, | ||
| { | ||
| fn convert_origin(origin: impl Into<MultiLocation>, kind: OriginKind) -> Result<RuntimeOrigin, MultiLocation> { | ||
| let origin = origin.into(); | ||
| let provider_para_id = ProviderParaId::get(); | ||
| match (kind, origin) { | ||
| ( | ||
| OriginKind::Native, | ||
| MultiLocation { | ||
| parents: 1, | ||
| interior: X2(Parachain(para_id), AccountId32 { .. }), | ||
| }, | ||
| ) if para_id == provider_para_id => Ok(ParachainOrigin::from(provider_para_id).into()), | ||
| _ => Err(origin), | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,16 +19,16 @@ | |
| use cumulus_primitives_utility::ParentAsUmp; | ||
| use frame_support::{ | ||
| parameter_types, | ||
| traits::{ConstU32, Contains, Everything, Nothing}, | ||
| traits::{ConstU32, Contains, Nothing}, | ||
| weights::{IdentityFee, Weight}, | ||
| }; | ||
| use frame_system::EnsureRoot; | ||
| use kilt_dip_support::xcm::{barriers::OkOrElseCheckForParachainProvider, origins::AccountIdJunctionAsParachain}; | ||
| use pallet_xcm::TestWeightInfo; | ||
| use polkadot_parachain::primitives::Sibling; | ||
| use xcm::latest::prelude::*; | ||
| use xcm::v3::prelude::*; | ||
| use xcm_builder::{ | ||
| AllowTopLevelPaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, | ||
| SiblingParachainAsNative, SiblingParachainConvertsVia, SignedToAccountId32, UsingComponents, | ||
| Account32Hash, AllowTopLevelPaidExecutionFrom, CurrencyAdapter, EnsureXcmOrigin, FixedWeightBounds, IsConcrete, | ||
| SignedToAccountId32, UsingComponents, | ||
| }; | ||
| use xcm_executor::XcmExecutor; | ||
|
|
||
|
|
@@ -39,12 +39,13 @@ use crate::{ | |
|
|
||
| parameter_types! { | ||
| pub HereLocation: MultiLocation = MultiLocation::here(); | ||
| pub NoneNetworkId: Option<NetworkId> = None; | ||
| pub UnitWeightCost: Weight = Weight::from_ref_time(1_000); | ||
| pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); | ||
| } | ||
|
|
||
| pub type Barrier = AllowTopLevelPaidExecutionFrom<Everything>; | ||
| pub type AssetTransactorLocationConverter = SiblingParachainConvertsVia<Sibling, AccountId>; | ||
| pub type Barrier = OkOrElseCheckForParachainProvider<AllowTopLevelPaidExecutionFrom<Nothing>, ConstU32<2_000>>; | ||
| pub type AssetTransactorLocationConverter = Account32Hash<NoneNetworkId, AccountId>; | ||
| pub type LocalAssetTransactor = | ||
| CurrencyAdapter<Balances, IsConcrete<HereLocation>, AssetTransactorLocationConverter, AccountId, ()>; | ||
| pub type XcmRouter = (ParentAsUmp<ParachainSystem, (), ()>, XcmpQueue); | ||
|
|
@@ -74,7 +75,7 @@ impl xcm_executor::Config for XcmConfig { | |
| type IsTeleporter = (); | ||
| type MaxAssetsIntoHolding = ConstU32<64>; | ||
| type MessageExporter = (); | ||
| type OriginConverter = SiblingParachainAsNative<cumulus_pallet_xcm::Origin, RuntimeOrigin>; | ||
| type OriginConverter = AccountIdJunctionAsParachain<ConstU32<2_000>, cumulus_pallet_xcm::Origin, RuntimeOrigin>; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type OriginConverter = SovereignSignedViaLocation<(ForeignChainAliasAccount), RuntimeOrigin>; What are the cons of using this type of configuration above? The consumer can filter out any calls and only allow the call function to be executed via XCM.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The pallet expects a parachain origin, hence using |
||
| type PalletInstancesInfo = AllPalletsWithSystem; | ||
| type ResponseHandler = (); | ||
| type RuntimeCall = RuntimeCall; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be accomplished with this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we discussed, the design at the time of this comment was flawed. I have now added a
ExpectOrigininstruction early on (d683a3f). For now, this is a sufficiently good approach, which means that theWithComputedOriginbarrier would not let the call through, which is not what we want. Also theAllowTopLevelPaidExecutionFromwould fail. There is no way around using theOkOrElseCheckForParachainProviderbarrier provided here. If some barrier must explicitly fail, theErrOrElseCheckForParachainProvidershould be used.