diff --git a/Cargo.lock b/Cargo.lock index d033efc860..759e8c19fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2450,6 +2450,7 @@ dependencies = [ "frame-support", "frame-system", "frame-system-rpc-runtime-api", + "kilt-dip-support", "kilt-runtime-api-dip-provider", "pallet-aura", "pallet-authorship", @@ -2502,12 +2503,14 @@ dependencies = [ "did", "dip-consumer-runtime-template", "dip-provider-runtime-template", + "dip-support", "frame-support", "frame-system", "kilt-dip-support", "kilt-support", "pallet-balances", "pallet-did-lookup", + "pallet-dip-provider", "pallet-web3-names", "parachain-info", "parity-scale-codec", @@ -2522,6 +2525,7 @@ dependencies = [ "sp-runtime", "sp-std", "xcm", + "xcm-builder", "xcm-emulator", "xcm-executor", ] @@ -4221,6 +4225,7 @@ name = "kilt-dip-support" version = "1.11.0-dev" dependencies = [ "did", + "dip-support", "frame-support", "frame-system", "pallet-dip-consumer", @@ -4231,6 +4236,8 @@ dependencies = [ "sp-runtime", "sp-std", "sp-trie", + "xcm", + "xcm-executor", ] [[package]] @@ -6281,6 +6288,7 @@ dependencies = [ name = "pallet-dip-provider" version = "1.11.0-dev" dependencies = [ + "did", "dip-support", "frame-support", "frame-system", diff --git a/crates/kilt-dip-support/Cargo.toml b/crates/kilt-dip-support/Cargo.toml index e06e17664e..b417b44cba 100644 --- a/crates/kilt-dip-support/Cargo.toml +++ b/crates/kilt-dip-support/Cargo.toml @@ -13,6 +13,7 @@ version.workspace = true [dependencies] # Internal dependencies did.workspace = true +dip-support.workspace = true pallet-dip-consumer.workspace = true pallet-dip-provider.workspace = true @@ -28,10 +29,15 @@ sp-core.workspace = true sp-trie.workspace = true sp-std.workspace = true +# Polkadot dependencies +xcm.workspace = true +xcm-executor.workspace = true + [features] default = ["std"] std = [ "did/std", + "dip-support/std", "pallet-dip-consumer/std", "pallet-dip-provider/std", "parity-scale-codec/std", @@ -41,6 +47,8 @@ std = [ "sp-runtime/std", "sp-core/std", "sp-trie/std", - "sp-std/std" + "sp-std/std", + "xcm-executor/std", + "xcm/std" ] runtime-benchmarks = [] diff --git a/crates/kilt-dip-support/src/lib.rs b/crates/kilt-dip-support/src/lib.rs index 103a5c504f..f4c1a05e4a 100644 --- a/crates/kilt-dip-support/src/lib.rs +++ b/crates/kilt-dip-support/src/lib.rs @@ -28,6 +28,7 @@ use crate::did::MerkleLeavesAndDidSignature; pub mod did; pub mod merkle; pub mod traits; +pub mod xcm; /// A type that chains a Merkle proof verification with a DID signature /// verification. The required input of this type is a tuple (A, B) where A is diff --git a/crates/kilt-dip-support/src/xcm.rs b/crates/kilt-dip-support/src/xcm.rs new file mode 100644 index 0000000000..8a84459242 --- /dev/null +++ b/crates/kilt-dip-support/src/xcm.rs @@ -0,0 +1,289 @@ +// 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 . + +// If you feel like getting in touch with us, you can do so at info@botlabs.org + +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, ExpectOrigin, 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( + PhantomData<(Router, UniversalLocationProvider)>, +); + +impl + IdentityProofDispatcher + for XcmRouterIdentityDispatcher +where + Router: SendXcm, + UniversalLocationProvider: Get, + Identifier: Encode, + ProofOutput: Encode, + AccountId: Into<[u8; 32]> + Clone, +{ + type PreDispatchOutput = Router::Ticket; + type Error = SendError; + + fn pre_dispatch>( + action: IdentityDetailsAction, + 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 = [[ + ExpectOrigin(Some( + Here.into_location() + .reanchored(&destination, UniversalLocationProvider::get()) + .unwrap(), + )), + 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::check_expected_dip_instruction_order(&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; + + // Must match the order of instructions as produced by the provider's + // implementation of the `IdentityProofDispatcher` trait. + pub(crate) fn check_expected_dip_instruction_order( + instructions: &[Instruction], + ) -> Result<(), ()> { + let mut iter = instructions.iter(); + match ( + iter.next(), + iter.next(), + iter.next(), + iter.next(), + iter.next(), + iter.next(), + iter.next(), + iter.next(), + ) { + ( + // A first instruction different than `DescendOrigin` is needed to distinguish between user-triggered + // and parachain-triggered XCM messages, since also the XCM pallet always preprends user-created XCM + // messages with a `DescendOrigin` instruction. + Some(ExpectOrigin(..)), + // Go down to user level to charge them for the XCM fees. + Some(DescendOrigin(X1(AccountId32 { .. }))), + // Expect the user to first withdraw an asset to pay for the fees. + Some(WithdrawAsset { .. }), + // Buy execution time. + Some(BuyExecution { .. }), + // Although this is irrelevant since `origin_kind` can also be specified by a user, we use + // `OriginKind::Native` here to make clear this is a parachain-dispatched XCM message. + Some(Transact { + origin_kind: OriginKind::Native, + .. + }), + // Any unused weight is refunded. + Some(RefundSurplus), + // Any unused assets are refunded back into the user's account. + Some(DepositAsset { .. }), + // No more instructions are allowed. + 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(PhantomData); + + impl ShouldExecute for AllowParachainProviderAsSubaccount + where + ProviderParaId: Get, + { + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + _max_weight: Weight, + _weight_credit: &mut Weight, + ) -> Result<(), ()> { + #[cfg(feature = "std")] + println!( + "AllowParachainProviderAsSubaccount::should_execute(origin = {:?}, instructions = {:?}", + origin, instructions + ); + // Ensure that the origin is a parachain allowed to act as identity provider. + ensure!( + *origin == ParentThen(Parachain(ProviderParaId::get()).into()).into(), + () + ); + check_expected_dip_instruction_order(instructions) + } + } + + // Decorate an existing barrier to add one more check in case all the previous + // barriers fail. + pub struct OkOrElseCheckForParachainProvider(PhantomData<(Barrier, ProviderParaId)>); + + impl ShouldExecute for OkOrElseCheckForParachainProvider + where + Barrier: ShouldExecute, + ProviderParaId: Get, + { + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + Barrier::should_execute(origin, instructions, max_weight, weight_credit).or_else(|_| { + AllowParachainProviderAsSubaccount::::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(PhantomData<(Barrier, ProviderParaId)>); + + impl ShouldExecute for ErrOrElseCheckForParachainProvider + where + Barrier: ShouldExecute, + ProviderParaId: Get, + { + fn should_execute( + origin: &MultiLocation, + instructions: &mut [Instruction], + max_weight: Weight, + weight_credit: &mut Weight, + ) -> Result<(), ()> { + Barrier::should_execute(origin, instructions, max_weight, weight_credit)?; + AllowParachainProviderAsSubaccount::::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( + PhantomData<(ProviderParaId, ParachainOrigin, RuntimeOrigin)>, + ); + + impl ConvertOrigin + for AccountIdJunctionAsParachain + where + ProviderParaId: Get, + ParachainOrigin: From, + RuntimeOrigin: From, + { + fn convert_origin(origin: impl Into, kind: OriginKind) -> Result { + 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), + } + } + } +} diff --git a/dip-template/runtimes/dip-consumer/src/xcm_config.rs b/dip-template/runtimes/dip-consumer/src/xcm_config.rs index bb7775be4f..83d2918888 100644 --- a/dip-template/runtimes/dip-consumer/src/xcm_config.rs +++ b/dip-template/runtimes/dip-consumer/src/xcm_config.rs @@ -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 = None; pub UnitWeightCost: Weight = Weight::from_ref_time(1_000); pub UniversalLocation: InteriorMultiLocation = Parachain(ParachainInfo::parachain_id().into()).into(); } -pub type Barrier = AllowTopLevelPaidExecutionFrom; -pub type AssetTransactorLocationConverter = SiblingParachainConvertsVia; +pub type Barrier = OkOrElseCheckForParachainProvider, ConstU32<2_000>>; +pub type AssetTransactorLocationConverter = Account32Hash; pub type LocalAssetTransactor = CurrencyAdapter, AssetTransactorLocationConverter, AccountId, ()>; pub type XcmRouter = (ParentAsUmp, XcmpQueue); @@ -74,7 +75,7 @@ impl xcm_executor::Config for XcmConfig { type IsTeleporter = (); type MaxAssetsIntoHolding = ConstU32<64>; type MessageExporter = (); - type OriginConverter = SiblingParachainAsNative; + type OriginConverter = AccountIdJunctionAsParachain, cumulus_pallet_xcm::Origin, RuntimeOrigin>; type PalletInstancesInfo = AllPalletsWithSystem; type ResponseHandler = (); type RuntimeCall = RuntimeCall; diff --git a/dip-template/runtimes/dip-provider/Cargo.toml b/dip-template/runtimes/dip-provider/Cargo.toml index ce1a7582bb..14fdeb6257 100644 --- a/dip-template/runtimes/dip-provider/Cargo.toml +++ b/dip-template/runtimes/dip-provider/Cargo.toml @@ -20,6 +20,7 @@ scale-info = {workspace = true, features = ["derive"]} # DIP did.workspace = true dip-support.workspace = true +kilt-dip-support.workspace = true kilt-runtime-api-dip-provider.workspace = true pallet-did-lookup.workspace = true pallet-dip-provider.workspace = true @@ -78,6 +79,7 @@ std = [ "scale-info/std", "did/std", "dip-support/std", + "kilt-dip-support/std", "kilt-runtime-api-dip-provider/std", "pallet-did-lookup/std", "pallet-dip-provider/std", @@ -123,6 +125,7 @@ std = [ ] runtime-benchmarks = [ "did/runtime-benchmarks", + "kilt-dip-support/runtime-benchmarks", "pallet-did-lookup/runtime-benchmarks", "pallet-dip-provider/runtime-benchmarks", "pallet-web3-names/runtime-benchmarks", diff --git a/dip-template/runtimes/dip-provider/src/dip.rs b/dip-template/runtimes/dip-provider/src/dip.rs index b8559c997c..59b6aedade 100644 --- a/dip-template/runtimes/dip-provider/src/dip.rs +++ b/dip-template/runtimes/dip-provider/src/dip.rs @@ -16,16 +16,15 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org -use did::EnsureDidOrigin; +use did::{DidRawOrigin, EnsureDidOrigin}; use dip_support::IdentityDetailsAction; -use frame_support::traits::EitherOfDiverse; -use frame_system::EnsureRoot; -use pallet_dip_provider::traits::{TxBuilder, XcmRouterDispatcher}; +use kilt_dip_support::xcm::XcmRouterIdentityDispatcher; +use pallet_dip_provider::traits::TxBuilder; use parity_scale_codec::{Decode, Encode}; use runtime_common::dip::{did::LinkedDidInfoProviderOf, merkle::DidMerkleRootGenerator}; use xcm::{latest::MultiLocation, DoubleEncoded}; -use crate::{AccountId, DidIdentifier, Hash, Runtime, RuntimeEvent, XcmRouter}; +use crate::{AccountId, DidIdentifier, Hash, Runtime, RuntimeEvent, UniversalLocation, XcmRouter}; #[derive(Encode, Decode)] enum ConsumerParachainCalls { @@ -56,9 +55,10 @@ impl TxBuilder for ConsumerParachainTxBuilder { } impl pallet_dip_provider::Config for Runtime { - type CommitOrigin = EitherOfDiverse, EnsureDidOrigin>; + type CommitOriginCheck = EnsureDidOrigin; + type CommitOrigin = DidRawOrigin; type Identifier = DidIdentifier; - type IdentityProofDispatcher = XcmRouterDispatcher; + type IdentityProofDispatcher = XcmRouterIdentityDispatcher; type IdentityProofGenerator = DidMerkleRootGenerator; type IdentityProvider = LinkedDidInfoProviderOf; type ProofOutput = Hash; diff --git a/dip-template/runtimes/dip-provider/src/xcm_config.rs b/dip-template/runtimes/dip-provider/src/xcm_config.rs index d73c126189..df62925d53 100644 --- a/dip-template/runtimes/dip-provider/src/xcm_config.rs +++ b/dip-template/runtimes/dip-provider/src/xcm_config.rs @@ -24,7 +24,7 @@ use frame_support::{ }; use frame_system::EnsureRoot; use pallet_xcm::TestWeightInfo; -use xcm::latest::prelude::*; +use xcm::v3::prelude::*; use xcm_builder::{EnsureXcmOrigin, FixedWeightBounds, SignedToAccountId32, UsingComponents}; use xcm_executor::XcmExecutor; diff --git a/dip-template/runtimes/xcm-tests/Cargo.toml b/dip-template/runtimes/xcm-tests/Cargo.toml index ae3b735c77..d922974440 100644 --- a/dip-template/runtimes/xcm-tests/Cargo.toml +++ b/dip-template/runtimes/xcm-tests/Cargo.toml @@ -15,12 +15,14 @@ cumulus-pallet-xcmp-queue = { workspace = true, features = ["std"] } did = { workspace = true, features = ["std"] } dip-consumer-runtime-template = { workspace = true, features = ["std"] } dip-provider-runtime-template = { workspace = true, features = ["std"] } +dip-support = { workspace = true, features = ["std"] } frame-support = { workspace = true, features = ["std"] } frame-system = { workspace = true, features = ["std"] } kilt-dip-support = { workspace = true, features = ["std"] } kilt-support = { workspace = true, features = ["std"] } pallet-balances = { workspace = true, features = ["std"] } pallet-did-lookup = { workspace = true, features = ["std"] } +pallet-dip-provider = { workspace = true, features = ["std"] } pallet-web3-names = { workspace = true, features = ["std"] } parachain-info = { workspace = true, features = ["std"] } parity-scale-codec = {workspace = true, features = ["std", "derive"]} @@ -35,6 +37,7 @@ sp-io = { workspace = true, features = ["std"] } sp-runtime = { workspace = true, features = ["std"] } sp-std = { workspace = true, features = ["std"] } xcm = { workspace = true, features = ["std"] } +xcm-builder = { workspace = true, features = ["std"] } xcm-emulator = { git = "https://github.com/shaunxw/xcm-simulator", branch = "master" } xcm-executor = { workspace = true, features = ["std"] } diff --git a/dip-template/runtimes/xcm-tests/src/para.rs b/dip-template/runtimes/xcm-tests/src/para.rs index c671ae0c83..a24076fe04 100644 --- a/dip-template/runtimes/xcm-tests/src/para.rs +++ b/dip-template/runtimes/xcm-tests/src/para.rs @@ -25,7 +25,7 @@ pub(super) mod provider { pub(crate) use dip_provider_runtime_template::{DidIdentifier, DmpQueue, Runtime, RuntimeOrigin, XcmpQueue}; use did::did_details::{DidDetails, DidEncryptionKey, DidVerificationKey}; - use dip_provider_runtime_template::{AccountId, Balance, BlockNumber, System, Web3Name}; + use dip_provider_runtime_template::{AccountId, Balance, BlockNumber, System, Web3Name, UNIT}; use kilt_support::deposit::Deposit; use pallet_did_lookup::{linkable_account::LinkableAccountId, ConnectionRecord}; use pallet_web3_names::web3_name::Web3NameOwnership; @@ -38,6 +38,8 @@ pub(super) mod provider { use super::*; pub const PARA_ID: u32 = 2_000; + pub const DISPATCHER_ACCOUNT: AccountId = AccountId::new([190u8; 32]); + const INITIAL_BALANCE: Balance = 100_000 * UNIT; pub(crate) fn did_auth_key() -> ed25519::Pair { ed25519::Pair::from_seed(&[200u8; 32]) @@ -81,6 +83,12 @@ pub(super) mod provider { >::assimilate_storage(¶chain_info_config, &mut t) .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(DISPATCHER_ACCOUNT, INITIAL_BALANCE)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext = TestExternalities::new(t); let did: DidIdentifier = did_auth_key().public().into(); let details = generate_did_details(); @@ -137,7 +145,11 @@ pub(super) mod consumer { }; use dip_consumer_runtime_template::System; - use xcm::latest::{Junction::Parachain, Junctions::X1, ParentThen}; + use xcm::v3::{ + Junction::{AccountId32, Parachain}, + Junctions::X2, + ParentThen, + }; use xcm_executor::traits::Convert; use super::*; @@ -146,9 +158,18 @@ pub(super) mod consumer { pub const DISPATCHER_ACCOUNT: AccountId = AccountId::new([90u8; 32]); const INITIAL_BALANCE: Balance = 100_000 * UNIT; - pub(crate) fn provider_parachain_account() -> AccountId { - AssetTransactorLocationConverter::convert(ParentThen(X1(Parachain(provider::PARA_ID))).into()) - .expect("Conversion of account from provider parachain to consumer parachain should not fail.") + pub(crate) fn provider_dispatcher_account_on_consumer() -> AccountId { + AssetTransactorLocationConverter::convert( + ParentThen(X2( + Parachain(provider::PARA_ID), + AccountId32 { + network: None, + id: provider::DISPATCHER_ACCOUNT.into(), + }, + )) + .into(), + ) + .expect("Conversion of account from provider parachain to consumer parachain should not fail.") } pub(crate) fn para_ext() -> TestExternalities { @@ -165,7 +186,7 @@ pub(super) mod consumer { pallet_balances::GenesisConfig:: { balances: vec![ - (provider_parachain_account(), INITIAL_BALANCE), + (provider_dispatcher_account_on_consumer(), INITIAL_BALANCE), (DISPATCHER_ACCOUNT, INITIAL_BALANCE), ], } @@ -178,4 +199,23 @@ pub(super) mod consumer { }); ext } + + #[cfg(test)] + pub(crate) use test_utils::*; + + #[cfg(test)] + mod test_utils { + use super::*; + + use polkadot_parachain::primitives::Sibling; + use xcm::v3::Junctions::X1; + use xcm_builder::SiblingParachainConvertsVia; + + pub(crate) fn provider_parachain_account_on_consumer() -> AccountId { + SiblingParachainConvertsVia::::convert( + ParentThen(X1(Parachain(provider::PARA_ID))).into(), + ) + .expect("Conversion of account from provider parachain to consumer parachain should not fail.") + } + } } diff --git a/dip-template/runtimes/xcm-tests/src/tests.rs b/dip-template/runtimes/xcm-tests/src/tests.rs index 62114f0fee..c44e45dd33 100644 --- a/dip-template/runtimes/xcm-tests/src/tests.rs +++ b/dip-template/runtimes/xcm-tests/src/tests.rs @@ -18,7 +18,8 @@ use super::*; -use did::{Did, DidSignature}; +use did::{Did, DidRawOrigin, DidSignature}; +use dip_support::IdentityDetailsAction; use frame_support::{assert_ok, weights::Weight}; use frame_system::RawOrigin; use kilt_dip_support::{ @@ -26,6 +27,7 @@ use kilt_dip_support::{ merkle::MerkleProof, }; use pallet_did_lookup::{linkable_account::LinkableAccountId, ConnectedAccounts}; +use pallet_dip_provider::traits::TxBuilder; use pallet_web3_names::{Names, Owner}; use parity_scale_codec::Encode; use runtime_common::dip::{ @@ -34,34 +36,59 @@ use runtime_common::dip::{ }; use sp_core::Pair; use sp_runtime::traits::Zero; -use xcm::latest::{ - Junction::Parachain, - Junctions::{Here, X1}, - ParentThen, +use xcm::{ + v3::{ + Instruction::{BuyExecution, DepositAsset, ExpectOrigin, RefundSurplus, Transact, WithdrawAsset}, + Junction::{AccountId32, Parachain}, + Junctions::{Here, X1}, + MultiAsset, + MultiAssetFilter::Wild, + MultiLocation, OriginKind, ParentThen, + WeightLimit::Limited, + WildMultiAsset::All, + Xcm, + }, + VersionedXcm, }; use xcm_emulator::TestExt; use cumulus_pallet_xcmp_queue::Event as XcmpEvent; use dip_consumer_runtime_template::{ - BlockNumber, DidIdentifier, DidLookup, DipConsumer, Runtime as ConsumerRuntime, RuntimeCall as ConsumerRuntimeCall, - RuntimeEvent, System, + Balances, BlockNumber, DidIdentifier, DidLookup, DipConsumer, Runtime as ConsumerRuntime, + RuntimeCall as ConsumerRuntimeCall, RuntimeEvent, System, +}; +use dip_provider_runtime_template::{ + ConsumerParachainTxBuilder, DipProvider, PolkadotXcm as ProviderXcmPallet, Runtime as ProviderRuntime, + UniversalLocation, }; -use dip_provider_runtime_template::{DipProvider, Runtime as ProviderRuntime}; #[test] fn commit_identity() { Network::reset(); let did: DidIdentifier = para::provider::did_auth_key().public().into(); + let consumer_location: MultiLocation = ParentThen(X1(Parachain(para::consumer::PARA_ID))).into(); + let asset: MultiAsset = (Here, 1_000_000_000).into(); + let weight = Weight::from_ref_time(4_000); + let provider_parachain_on_consumer_parachain_balance_before = ConsumerParachain::execute_with(|| { + Balances::free_balance(para::consumer::provider_parachain_account_on_consumer()) + }); + let dispatcher_on_consumer_parachain_balance_before = ConsumerParachain::execute_with(|| { + Balances::free_balance(para::consumer::provider_dispatcher_account_on_consumer()) + }); // 1. Send identity commitment from DIP provider to DIP consumer. ProviderParachain::execute_with(|| { assert_ok!(DipProvider::commit_identity( - RawOrigin::Root.into(), + DidRawOrigin { + id: did.clone(), + submitter: para::provider::DISPATCHER_ACCOUNT + } + .into(), did.clone(), - Box::new(ParentThen(X1(Parachain(para::consumer::PARA_ID))).into()), - Box::new((Here, 1_000_000_000).into()), - Weight::from_ref_time(4_000), + Box::new(consumer_location.into_versioned()), + Box::new(asset.into()), + weight, )); }); // 2. Verify that the commitment has made it to the DIP consumer. @@ -77,6 +104,19 @@ fn commit_identity() { ))); // 2.2 Verify the proof digest was stored correctly. assert!(DipConsumer::identity_proofs(&did).is_some()); + // 2.3 Verify that the provider parachain sovereign account balance has not + // changed. + let provider_parachain_on_consumer_parachain_balance_after = + Balances::free_balance(para::consumer::provider_parachain_account_on_consumer()); + assert_eq!( + provider_parachain_on_consumer_parachain_balance_before, + provider_parachain_on_consumer_parachain_balance_after + ); + // 2.4 Verify that the dispatcher's account balance on the consumer parachain + // has decreased. + let dispatcher_on_consumer_parachain_balance_after = + Balances::free_balance(para::consumer::provider_dispatcher_account_on_consumer()); + assert!(dispatcher_on_consumer_parachain_balance_after < dispatcher_on_consumer_parachain_balance_before); }); // 3. Call an extrinsic on the consumer chain with a valid proof and signature let did_details = ProviderParachain::execute_with(|| { @@ -169,3 +209,77 @@ fn commit_identity() { assert_eq!(details, Some(1u128)); }); } + +#[test] +fn user_generated_commit_identity() { + Network::reset(); + + let did: DidIdentifier = para::provider::did_auth_key().public().into(); + let consumer_location: MultiLocation = ParentThen(X1(Parachain(para::consumer::PARA_ID))).into(); + let asset: MultiAsset = (Here, 1_000_000_000).into(); + let weight = Weight::from_ref_time(4_000); + let dest_tx = ConsumerParachainTxBuilder::build(consumer_location, IdentityDetailsAction::Deleted(did.clone())) + .expect("Provider Tx builder should not fail to create the encoded `Transact` call."); + let message = ProviderParachain::execute_with(|| { + Xcm::<()>(vec![ + ExpectOrigin(Some( + Here.into_location() + .reanchored(&consumer_location, UniversalLocation::get()) + .unwrap(), + )), + 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, + interior: Here + .into_location() + .reanchored(&consumer_location, UniversalLocation::get()) + .unwrap() + .pushed_with_interior(AccountId32 { + network: None, + id: para::provider::DISPATCHER_ACCOUNT.into(), + }) + .unwrap() + .interior, + }, + }, + ]) + }); + // 1. Send identity commitment from DIP provider to DIP consumer via a + // user-dispatched XCM call using the XCM pallet (no parachain origin). + ProviderParachain::execute_with(|| { + assert_ok!(ProviderXcmPallet::send( + RawOrigin::Signed(para::provider::DISPATCHER_ACCOUNT).into(), + Box::new(consumer_location.into()), + Box::new(VersionedXcm::from(message)) + )); + }); + // 2. Verify that the commitment has NOT made it to the DIP consumer and must + // have failed, since this was a user-generated XCM message on the provider + // chain using the XCM pallet. + ConsumerParachain::execute_with(|| { + // 2.1 Verify that there was an XCM error. + println!("{:?}", System::events()); + assert!(System::events().iter().any(|r| matches!( + r.event, + RuntimeEvent::XcmpQueue(XcmpEvent::Fail { + error: _, + message_hash: _, + weight: _ + }) + ))); + // 2.2 Verify there is no storage entry in the consumer pallet. + assert!(DipConsumer::identity_proofs(&did).is_none()); + }); +} diff --git a/pallets/pallet-dip-provider/Cargo.toml b/pallets/pallet-dip-provider/Cargo.toml index 6283ad284a..e87da12329 100644 --- a/pallets/pallet-dip-provider/Cargo.toml +++ b/pallets/pallet-dip-provider/Cargo.toml @@ -14,6 +14,7 @@ version.workspace = true targets = ["x86_64-unknown-linux-gnu"] [dependencies] +did.workspace = true dip-support.workspace = true frame-support.workspace = true frame-system.workspace = true @@ -25,6 +26,7 @@ xcm.workspace = true [features] default = ["std"] std = [ + "did/std", "dip-support/std", "frame-support/std", "frame-system/std", @@ -34,6 +36,7 @@ std = [ "xcm/std", ] runtime-benchmarks = [ + "did/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks" ] diff --git a/pallets/pallet-dip-provider/src/lib.rs b/pallets/pallet-dip-provider/src/lib.rs index cf2983fa2d..34ca55f400 100644 --- a/pallets/pallet-dip-provider/src/lib.rs +++ b/pallets/pallet-dip-provider/src/lib.rs @@ -35,7 +35,7 @@ pub mod pallet { use dip_support::IdentityDetailsAction; - use crate::traits::{IdentityProofDispatcher, IdentityProofGenerator, IdentityProvider, TxBuilder}; + use crate::traits::{IdentityProofDispatcher, IdentityProofGenerator, IdentityProvider, SubmitterInfo, TxBuilder}; pub type IdentityOf = <::IdentityProvider as IdentityProvider<::Identifier>>::Success; pub type IdentityProofActionOf = IdentityDetailsAction<::Identifier, ::ProofOutput>; @@ -44,14 +44,15 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - type CommitOrigin: EnsureOrigin; + type CommitOriginCheck: EnsureOrigin; + type CommitOrigin: SubmitterInfo; type Identifier: Parameter; type IdentityProofGenerator: IdentityProofGenerator< Self::Identifier, IdentityOf, Output = Self::ProofOutput, >; - type IdentityProofDispatcher: IdentityProofDispatcher; + type IdentityProofDispatcher: IdentityProofDispatcher; type IdentityProvider: IdentityProvider; type ProofOutput: Clone + Eq + Debug; type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -90,8 +91,7 @@ pub mod pallet { asset: Box, weight: Weight, ) -> DispatchResult { - // TODO: Charge the dispatcher based on the destination weight configuration - T::CommitOrigin::ensure_origin(origin)?; + let dispatcher = T::CommitOriginCheck::ensure_origin(origin).map(|e| e.submitter())?; let destination: MultiLocation = (*destination).try_into().map_err(|_| Error::::BadVersion)?; let action: IdentityProofActionOf = match T::IdentityProvider::retrieve(&identifier) { @@ -107,12 +107,17 @@ pub mod pallet { let asset: MultiAsset = (*asset).try_into().map_err(|_| Error::::BadVersion)?; - let (ticket, _) = - T::IdentityProofDispatcher::pre_dispatch::(action.clone(), asset, weight, destination) - .map_err(|_| Error::::Predispatch)?; + let (ticket, _) = T::IdentityProofDispatcher::pre_dispatch::( + action.clone(), + dispatcher, + asset, + weight, + destination, + ) + .map_err(|_| Error::::Predispatch)?; // TODO: Use returned asset of `pre_dispatch` to charge the tx submitter for the - // fee, in addition to the cost on the target chain. + // fee. T::IdentityProofDispatcher::dispatch(ticket).map_err(|_| Error::::Dispatch)?; Self::deposit_event(Event::IdentityInfoDispatched(action, Box::new(destination))); diff --git a/pallets/pallet-dip-provider/src/traits.rs b/pallets/pallet-dip-provider/src/traits.rs index 12784472e4..f5a8836d1f 100644 --- a/pallets/pallet-dip-provider/src/traits.rs +++ b/pallets/pallet-dip-provider/src/traits.rs @@ -16,6 +16,7 @@ // If you feel like getting in touch with us, you can do so at info@botlabs.org +use did::DidRawOrigin; use dip_support::IdentityDetailsAction; use xcm::{latest::prelude::*, DoubleEncoded}; @@ -53,15 +54,14 @@ pub mod identity_dispatch { use super::*; use frame_support::weights::Weight; - use parity_scale_codec::Encode; - use sp_std::{marker::PhantomData, vec}; - pub trait IdentityProofDispatcher { + pub trait IdentityProofDispatcher { type PreDispatchOutput; type Error; fn pre_dispatch>( action: IdentityDetailsAction, + source: AccountId, asset: MultiAsset, weight: Weight, destination: MultiLocation, @@ -73,14 +73,15 @@ pub mod identity_dispatch { // Returns `Ok` without doing anything. pub struct NullIdentityProofDispatcher; - impl IdentityProofDispatcher - for NullIdentityProofDispatcher + impl + IdentityProofDispatcher for NullIdentityProofDispatcher { type PreDispatchOutput = (); type Error = (); fn pre_dispatch<_B>( _action: IdentityDetailsAction, + _source: AccountId, _asset: MultiAsset, _weight: Weight, _destination: MultiLocation, @@ -92,59 +93,6 @@ pub mod identity_dispatch { Ok(()) } } - - // 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 XcmRouterDispatcher( - PhantomData<(Router, Identifier, ProofOutput, Details)>, - ); - - impl IdentityProofDispatcher - for XcmRouterDispatcher - where - Router: SendXcm, - Identifier: Encode, - ProofOutput: Encode, - { - type PreDispatchOutput = Router::Ticket; - type Error = SendError; - - fn pre_dispatch>( - action: IdentityDetailsAction, - 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 = [vec![ - WithdrawAsset(asset.clone().into()), - BuyExecution { - fees: asset, - // TODO: Configurable weight limit? - weight_limit: Unlimited, - }, - Transact { - origin_kind: OriginKind::Native, - require_weight_at_most: weight, - call: dest_tx, - }, - ]] - .concat(); - 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 use identity_provision::*; @@ -196,3 +144,28 @@ pub trait TxBuilder { action: IdentityDetailsAction, ) -> Result, Self::Error>; } + +pub trait SubmitterInfo { + type Submitter; + + fn submitter(&self) -> Self::Submitter; +} + +impl SubmitterInfo for frame_support::sp_runtime::AccountId32 { + type Submitter = Self; + + fn submitter(&self) -> Self::Submitter { + self.clone() + } +} + +impl SubmitterInfo for DidRawOrigin +where + AccountId: Clone, +{ + type Submitter = AccountId; + + fn submitter(&self) -> Self::Submitter { + self.submitter.clone() + } +} diff --git a/runtimes/common/src/xcm_config.rs b/runtimes/common/src/xcm_config.rs index 51eed3b5f3..3ac0affa7b 100644 --- a/runtimes/common/src/xcm_config.rs +++ b/runtimes/common/src/xcm_config.rs @@ -19,7 +19,7 @@ use core::marker::PhantomData; use frame_support::{log, match_types, parameter_types, weights::Weight}; use polkadot_parachain::primitives::Sibling; -use xcm::latest::prelude::*; +use xcm::v3::prelude::*; use xcm_builder::{AccountId32Aliases, CurrencyAdapter, IsConcrete, ParentIsPreset, SiblingParachainConvertsVia}; use xcm_executor::traits::ShouldExecute; diff --git a/runtimes/peregrine/src/xcm_config.rs b/runtimes/peregrine/src/xcm_config.rs index a21d4e1049..4691ccedce 100644 --- a/runtimes/peregrine/src/xcm_config.rs +++ b/runtimes/peregrine/src/xcm_config.rs @@ -27,7 +27,7 @@ use frame_support::{ }; use pallet_xcm::XcmPassthrough; use sp_core::ConstU32; -use xcm::latest::prelude::*; +use xcm::v3::prelude::*; use xcm_builder::{ AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, RelayChainAsNative, SiblingParachainAsNative, SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, UsingComponents, WithComputedOrigin, diff --git a/runtimes/spiritnet/src/xcm_config.rs b/runtimes/spiritnet/src/xcm_config.rs index e399457755..840a2e48d0 100644 --- a/runtimes/spiritnet/src/xcm_config.rs +++ b/runtimes/spiritnet/src/xcm_config.rs @@ -27,7 +27,7 @@ use frame_support::{ }; use pallet_xcm::XcmPassthrough; use sp_core::ConstU32; -use xcm::latest::prelude::*; +use xcm::v3::prelude::*; use xcm_builder::{ AllowTopLevelPaidExecutionFrom, EnsureXcmOrigin, FixedWeightBounds, RelayChainAsNative, SiblingParachainAsNative, SignedAccountId32AsNative, SignedToAccountId32, UsingComponents, WithComputedOrigin,