From 864ca666351d000abe56263a13097d604067e883 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 11 Nov 2021 23:20:13 +0100 Subject: [PATCH] feat: add proxy manager pallet --- Cargo.lock | 55 +++++ pallets/remote-asset-manager/src/lib.rs | 2 +- pallets/remote-proxy-manager/Cargo.toml | 122 ++++++++++ pallets/remote-proxy-manager/README.md | 1 + pallets/remote-proxy-manager/src/lib.rs | 284 ++++++++++++++++++++++++ 5 files changed, 463 insertions(+), 1 deletion(-) create mode 100644 pallets/remote-proxy-manager/Cargo.toml create mode 100644 pallets/remote-proxy-manager/README.md create mode 100644 pallets/remote-proxy-manager/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 05c22d9f09..de7f945dcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5832,6 +5832,61 @@ dependencies = [ "xcm-simulator", ] +[[package]] +name = "pallet-remote-proxy-manager" +version = "0.0.1" +dependencies = [ + "cumulus-pallet-dmp-queue", + "cumulus-pallet-parachain-system", + "cumulus-pallet-xcm", + "cumulus-pallet-xcmp-queue", + "cumulus-primitives-core", + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "kusama-runtime", + "log", + "orml-currencies", + "orml-tokens", + "orml-traits", + "orml-unknown-tokens", + "orml-xcm-support", + "orml-xtokens", + "pallet-asset-index", + "pallet-assets", + "pallet-balances", + "pallet-collective", + "pallet-price-feed", + "pallet-proxy", + "pallet-saft-registry", + "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", + "pallet-xcm", + "parachain-info", + "parity-scale-codec", + "polkadot-core-primitives", + "polkadot-parachain", + "polkadot-primitives", + "polkadot-runtime-parachains", + "primitives", + "scale-info", + "serde", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "xcm", + "xcm-builder", + "xcm-calls", + "xcm-executor", + "xcm-simulator", +] + [[package]] name = "pallet-remote-treasury" version = "0.0.1" diff --git a/pallets/remote-asset-manager/src/lib.rs b/pallets/remote-asset-manager/src/lib.rs index 72d23788f6..7f51a6cde0 100644 --- a/pallets/remote-asset-manager/src/lib.rs +++ b/pallets/remote-asset-manager/src/lib.rs @@ -1005,7 +1005,7 @@ pub mod pallet { } } - /// Trait for the asset-index pallet extrinsic weights. + /// Trait for the pallet extrinsic weights. pub trait WeightInfo { fn transfer() -> Weight; fn freeze() -> Weight; diff --git a/pallets/remote-proxy-manager/Cargo.toml b/pallets/remote-proxy-manager/Cargo.toml new file mode 100644 index 0000000000..ac158875b7 --- /dev/null +++ b/pallets/remote-proxy-manager/Cargo.toml @@ -0,0 +1,122 @@ +[package] +authors = ['ChainSafe Systems'] +description = 'pallet to manage proxy settings on remote locations' +edition = '2018' +license = 'LGPL-3.0-only' +name = 'pallet-remote-proxy-manager' +readme = 'README.md' +repository = 'https://github.com/ChainSafe/PINT/' +version = '0.0.1' + +[dependencies] +log = { version = "0.4.14", default-features = false } +serde = { version = "1.0.130", features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "2.3.1", default-features = false } +scale-info = { version = "1.0", default-features = false, features = ["derive"] } + +# Substrate Dependencies +frame-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12', default-features = false } +frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12', default-features = false } +frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12', default-features = false, optional = true } +pallet-staking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12', default-features = false } + +# Polkadot Dependencies +xcm = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.12', default-features = false } +xcm-executor = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.12', default-features = false } + +# Cumulus dependencies +cumulus-pallet-xcm = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.12', default-features = false } +cumulus-primitives-core = { git = 'https://github.com/paritytech/cumulus', branch = 'polkadot-v0.9.12', default-features = false } + +# PINT dependencies +xcm-calls = {path = "../../primitives/xcm-calls", default-features = false } +primitives = { path = "../../primitives/primitives", default-features = false } + +# orml Dependencies +orml-traits = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master', default-features = false } +orml-xtokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master', default-features = false } + +[dev-dependencies] +serde = { version = "1.0.130", features = ["derive"] } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" } +sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.12" } +frame-system = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +frame-benchmarking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } + +sp-runtime = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +sp-std = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +sp-staking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +frame-election-provider-support = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } + + +## Substrate Pallet Dependencies +pallet-assets = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-staking = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-proxy = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-balances = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12'} +pallet-staking-reward-curve = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-timestamp = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-session = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } +pallet-collective = { git = 'https://github.com/paritytech/substrate', branch = 'polkadot-v0.9.12' } + +# cumulus +cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +cumulus-pallet-dmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +cumulus-pallet-xcmp-queue = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +cumulus-pallet-xcm = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } +parachain-info = { git = "https://github.com/paritytech/cumulus", branch = "polkadot-v0.9.12" } + +# polkadot +xcm-builder = { git = 'https://github.com/paritytech/polkadot', branch = 'release-v0.9.12' } +polkadot-runtime-parachains = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +polkadot-parachain = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +kusama-runtime = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +pallet-xcm = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +xcm-simulator = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } +polkadot-core-primitives = { git = "https://github.com/paritytech/polkadot", branch = "release-v0.9.12" } + +# PINT +pallet-asset-index = {path = "../asset-index" } +pallet-saft-registry = {path = "../saft-registry" } +pallet-price-feed = { path = "../price-feed" } + +# ORML +orml-currencies = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master' } +orml-tokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master' } +orml-unknown-tokens = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master' } +orml-xcm-support = { git = 'https://github.com/open-web3-stack/open-runtime-module-library', branch = 'master' } + +[features] +default = ['std'] +std = [ + 'serde', + 'codec/std', + 'log/std', + 'frame-support/std', + 'frame-system/std', + 'pallet-staking/std', + 'xcm/std', + + 'xcm-calls/std', + 'primitives/std', + + 'xcm-executor/std', + 'cumulus-pallet-xcm/std', + 'cumulus-primitives-core/std', + + 'orml-traits/std', + 'orml-xtokens/std', +] +# this feature is only for compilation now +runtime-benchmarks = [ + 'frame-benchmarking', + 'frame-support/runtime-benchmarks', + 'frame-system/runtime-benchmarks', + 'primitives/runtime-benchmarks', +] + +[package.metadata.docs.rs] +targets = ['x86_64-unknown-linux-gnu'] diff --git a/pallets/remote-proxy-manager/README.md b/pallets/remote-proxy-manager/README.md new file mode 100644 index 0000000000..e850d9c23f --- /dev/null +++ b/pallets/remote-proxy-manager/README.md @@ -0,0 +1 @@ +License: LGPL-3.0-only \ No newline at end of file diff --git a/pallets/remote-proxy-manager/src/lib.rs b/pallets/remote-proxy-manager/src/lib.rs new file mode 100644 index 0000000000..e7d3bef628 --- /dev/null +++ b/pallets/remote-proxy-manager/src/lib.rs @@ -0,0 +1,284 @@ +// Copyright 2021 ChainSafe Systems +// SPDX-License-Identifier: LGPL-3.0-only + +//! # Remote Proxy Manager Pallet +//! +//! The Remote Proxy Manager pallet handles proxies on remote locations + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +// this is requires as the #[pallet::event] proc macro generates code that violates this lint +#[allow(clippy::unused_unit)] +#[frame_support::pallet] +pub mod pallet { + use cumulus_primitives_core::ParaId; + use frame_support::{ + dispatch::DispatchResultWithPostInfo, + pallet_prelude::*, + sp_runtime::traits::{Convert, Zero}, + sp_std::{self, mem, prelude::*}, + traits::Get, + transactional, + }; + use frame_system::pallet_prelude::*; + use xcm::latest::prelude::*; + + use primitives::traits::MaybeAssetIdConvert; + use xcm_calls::{proxy::*, PalletCall, PalletCallEncoder}; + + type AccountIdFor = ::AccountId; + + // A `pallet_proxy` dispatchable on another chain + // expects a `ProxyType` of u8 and blocknumber of u32 + type PalletProxyCall = ProxyCall, ProxyType, ::BlockNumber>; + + #[pallet::config] + pub trait Config: frame_system::Config + MaybeAssetIdConvert { + /// Asset Id that is used to identify different kinds of assets. + type AssetId: Parameter + Member + Copy + MaybeSerializeDeserialize; + + /// Convert a `T::AssetId` to its relative `MultiLocation` identifier. + type AssetIdConvert: Convert>; + + /// The encoder to use for encoding when transacting a `pallet_proxy` + /// Call + type PalletProxyCallEncoder: ProxyCallEncoder< + Self::AccountId, + ProxyType, + Self::BlockNumber, + Context = Self::AssetId, + >; + + /// The location of the chain itself + #[pallet::constant] + type SelfLocation: Get; + + /// Returns the parachain ID we are running with. + #[pallet::constant] + type SelfParaId: Get; + + /// Origin that is allowed to send cross chain messages on behalf of the + /// PINT chain + type AdminOrigin: EnsureOrigin; + + /// How to send an onward XCM message. + type XcmSender: SendXcm; + + type Event: From> + IsType<::Event>; + + /// The weight for this pallet's extrinsics. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + pub struct Pallet(_); + + /// The config of `pallet_proxy` in the runtime of the parachain. + #[pallet::storage] + #[pallet::getter(fn proxy_config)] + pub type PalletProxyConfig = + StorageMap<_, Twox64Concat, ::AssetId, ProxyConfig, OptionQuery>; + + /// Denotes the current state of proxies on a parachain for the PINT chain's + /// account with the delegates being the second key in this map + /// + /// `location identifier` -> `delegate` -> `proxies` + #[pallet::storage] + #[pallet::getter(fn proxies)] + pub type Proxies = + StorageDoubleMap<_, Blake2_128Concat, T::AssetId, Twox64Concat, AccountIdFor, ProxyState, ValueQuery>; + + /// The extra weight for cross-chain XCM transfers. + /// xcm_dest_weight: value: Weight + #[pallet::storage] + #[pallet::getter(fn xcm_dest_weight)] + pub type XcmDestWeight = StorageValue<_, Weight, ValueQuery>; + + #[pallet::genesis_config] + #[allow(clippy::type_complexity)] + pub struct GenesisConfig { + /// key-value pairs for the `PalletProxyConfig` storage map + pub proxy_configs: Vec<(T::AssetId, ProxyConfig)>, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { proxy_configs: Default::default() } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + self.proxy_configs.iter().for_each(|(id, config)| PalletProxyConfig::::insert(id, config)); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub (super) fn deposit_event)] + pub enum Event { + /// Successfully sent a cross chain message to add a proxy. \[asset, + /// delegate, proxy type\] + SentAddProxy(T::AssetId, AccountIdFor, ProxyType), + /// Successfully sent a cross chain message to remove a proxy. \[asset, + /// delegate, proxy type\] + SentRemoveProxy(T::AssetId, AccountIdFor, ProxyType), + /// Updated the proxy weights of an asset. \[asset, old weights, new + /// weights\] + UpdatedProxyCallWeights(T::AssetId, ProxyWeights, ProxyWeights), + /// A new weight for XCM transfers has been set.\[new_weight\] + XcmDestWeightSet(Weight), + } + + #[pallet::error] + pub enum Error { + /// Thrown when the proxy type was already set. + AlreadyProxy, + /// Thrown when the requested proxy type to removed was not added + /// before. + NoProxyFound, + /// Thrown when the requested cross-chain call could not be encoded for + /// the given location. + NotEncodableForLocation, + /// Thrown when sending a Xcm `pallet_proxy::add_proxy` failed + FailedToSendAddProxyXcm, + /// Thrown when sending a Xcm `pallet_proxy::remove_proxy` failed + FailedToSendRemoveProxyXcm, + /// Thrown when no config was found for the requested location + NoPalletConfigFound, + /// Thrown if liquid asset has invalid chain location + InvalidAssetChainLocation, + } + + #[pallet::call] + impl Pallet { + /// Transacts a `pallet_proxy::Call::add_proxy` call to add a proxy on + /// behalf of the PINT parachain's account on the target chain. + /// + /// Limited to the council origin + #[pallet::weight(10_000)] // TODO: Set weights + pub fn send_add_proxy( + origin: OriginFor, + asset: T::AssetId, + proxy_type: ProxyType, + delegate: AccountIdFor, + ) -> DispatchResultWithPostInfo { + T::AdminOrigin::ensure_origin(origin)?; + let dest = Self::asset_destination(asset)?; + + log::info!(target: "pint_xcm", "Attempting add_proxy {:?} on: {:?} with delegate {:?}", proxy_type, dest, delegate); + + // ensures that the call is encodable for the destination + ensure!(T::PalletProxyCallEncoder::can_encode(&asset), Error::::NotEncodableForLocation); + + let mut proxies = Proxies::::get(&asset, &delegate); + ensure!(!proxies.contains(&proxy_type), Error::::AlreadyProxy); + + let config = PalletProxyConfig::::get(&asset).ok_or(Error::::NoPalletConfigFound)?; + + let call = PalletProxyCall::::AddProxy(ProxyParams { + delegate: delegate.clone(), + proxy_type, + delay: T::BlockNumber::zero(), + }); + let encoder = call.encoder::(&asset); + + let xcm = Self::wrap_call_into_xcm( + encoder.encode_runtime_call(config.pallet_index).encode(), + config.weights.add_proxy, + Self::xcm_dest_weight().into(), + ); + + let result = T::XcmSender::send_xcm(dest, xcm); + log::info!(target: "pint_xcm", "sent pallet_proxy::add_proxy xcm: {:?} ",result); + ensure!(result.is_ok(), Error::::FailedToSendAddProxyXcm); + + // update the proxy for this delegate + proxies.add(proxy_type); + Proxies::::insert(&asset, delegate.clone(), proxies); + + Self::deposit_event(Event::SentAddProxy(asset, delegate, proxy_type)); + Ok(().into()) + } + + /// Updates the configured proxy weights for the given asset. + /// + /// Callable by the admin origin + #[pallet::weight(10_000)] // TODO: Set weights + pub fn update_proxy_weights(origin: OriginFor, asset: T::AssetId, weights: ProxyWeights) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + let old_weights = PalletProxyConfig::::try_mutate( + &asset, + |maybe_config| -> sp_std::result::Result<_, DispatchError> { + let config = maybe_config.as_mut().ok_or(Error::::NoPalletConfigFound)?; + let old = mem::replace(&mut config.weights, weights.clone()); + Ok(old) + }, + )?; + + Self::deposit_event(Event::UpdatedProxyCallWeights(asset, old_weights, weights)); + + Ok(()) + } + + /// Sets the `xcm_dest_weight` for XCM transfers. + /// + /// Callable by the admin origin + /// + /// Parameters: + /// - `xcm_dest_weight`: The new weight for XCM transfers. + #[pallet::weight(< T as Config >::WeightInfo::set_xcm_dest_weight())] + #[transactional] + pub fn set_xcm_dest_weight(origin: OriginFor, #[pallet::compact] xcm_dest_weight: Weight) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + + XcmDestWeight::::put(xcm_dest_weight); + Self::deposit_event(Event::::XcmDestWeightSet(xcm_dest_weight)); + Ok(()) + } + } + + impl Pallet { + /// The destination address of the asset's native location + fn asset_destination(asset: T::AssetId) -> Result { + let dest = T::AssetIdConvert::convert(asset).ok_or(Error::::InvalidAssetChainLocation)?; + Ok(dest) + } + + /// Wrap the call into a Xcm instance. + /// params: + /// - call: The encoded call to be executed + /// - fee: fee (in remote currency) used to buy the `weight` and `debt`. + /// - require_weight_at_most: the weight limit used for the xcm transacted call. + fn wrap_call_into_xcm(call: Vec, require_weight_at_most: Weight, fee: u128) -> Xcm<()> { + let asset = MultiAsset { id: Concrete(MultiLocation::here()), fun: Fungibility::Fungible(fee) }; + Xcm(vec![ + WithdrawAsset(asset.clone().into()), + BuyExecution { fees: asset, weight_limit: Unlimited }, + Transact { origin_type: OriginKind::SovereignAccount, require_weight_at_most, call: call.into() }, + DepositAsset { + assets: All.into(), + max_assets: u32::MAX, + beneficiary: MultiLocation { parents: 1, interior: X1(Parachain(T::SelfParaId::get().into())) }, + }, + ]) + } + } + + /// Trait for the pallet extrinsic weights. + pub trait WeightInfo { + fn set_xcm_dest_weight() -> Weight; + } + + /// For backwards compatibility and tests + impl WeightInfo for () { + fn set_xcm_dest_weight() -> Weight { + Default::default() + } + } +}