From 4dff934f02cf40a5a7ea0c30da730dfbd11e75b2 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Thu, 29 Dec 2022 10:46:45 +0000 Subject: [PATCH] Merge branch 'develop' into feature/sapphire-is-quartz --- --- a/Cargo.lock +++ b/Cargo.lock @@ -4026,7 +4026,7 @@ "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", - "pallet-identity", + "pallet-identity 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36)", "pallet-im-online", "pallet-indices", "pallet-membership", @@ -5307,8 +5307,10 @@ "orml-xtokens", "pallet-app-promotion", "pallet-aura", + "pallet-authorship", "pallet-balances", "pallet-base-fee", + "pallet-collator-selection", "pallet-common", "pallet-configuration", "pallet-ethereum", @@ -5320,6 +5322,7 @@ "pallet-evm-transaction-payment", "pallet-foreign-assets", "pallet-fungible", + "pallet-identity 4.0.0-dev", "pallet-inflation", "pallet-maintenance", "pallet-nonfungible", @@ -5327,6 +5330,7 @@ "pallet-refungible", "pallet-rmrk-core", "pallet-rmrk-equip", + "pallet-session", "pallet-structure", "pallet-sudo", "pallet-template-transaction-payment", @@ -5772,6 +5776,33 @@ ] [[package]] +name = "pallet-collator-selection" +version = "4.0.0" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-aura", + "pallet-authorship", + "pallet-balances", + "pallet-configuration", + "pallet-session", + "pallet-timestamp", + "parity-scale-codec 3.2.1", + "rand 0.8.5", + "scale-info", + "serde", + "sp-consensus-aura", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", +] + +[[package]] name = "pallet-collective" version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36#cb4f2491b00af7d7817f3a54209c26b20faa1f51" @@ -5812,9 +5843,10 @@ [[package]] name = "pallet-configuration" -version = "0.1.2" +version = "0.1.3" dependencies = [ "fp-evm", + "frame-benchmarking", "frame-support", "frame-system", "parity-scale-codec 3.2.1", @@ -6153,6 +6185,23 @@ [[package]] name = "pallet-identity" version = "4.0.0-dev" +dependencies = [ + "enumflags2", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec 3.2.1", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-identity" +version = "4.0.0-dev" source = "git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36#cb4f2491b00af7d7817f3a54209c26b20faa1f51" dependencies = [ "enumflags2", @@ -8106,7 +8155,7 @@ "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", - "pallet-identity", + "pallet-identity 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36)", "pallet-im-online", "pallet-indices", "pallet-membership", @@ -8866,8 +8915,10 @@ "orml-xtokens", "pallet-app-promotion", "pallet-aura", + "pallet-authorship", "pallet-balances", "pallet-base-fee", + "pallet-collator-selection", "pallet-common", "pallet-configuration", "pallet-ethereum", @@ -8879,6 +8930,7 @@ "pallet-evm-transaction-payment", "pallet-foreign-assets", "pallet-fungible", + "pallet-identity 4.0.0-dev", "pallet-inflation", "pallet-maintenance", "pallet-nonfungible", @@ -8886,6 +8938,7 @@ "pallet-refungible", "pallet-rmrk-core", "pallet-rmrk-equip", + "pallet-session", "pallet-structure", "pallet-sudo", "pallet-template-transaction-payment", @@ -9318,7 +9371,7 @@ "pallet-democracy", "pallet-elections-phragmen", "pallet-grandpa", - "pallet-identity", + "pallet-identity 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36)", "pallet-im-online", "pallet-indices", "pallet-membership", @@ -13094,8 +13147,10 @@ "orml-xtokens", "pallet-app-promotion", "pallet-aura", + "pallet-authorship", "pallet-balances", "pallet-base-fee", + "pallet-collator-selection", "pallet-common", "pallet-configuration", "pallet-ethereum", @@ -13107,6 +13162,7 @@ "pallet-evm-transaction-payment", "pallet-foreign-assets", "pallet-fungible", + "pallet-identity 4.0.0-dev", "pallet-inflation", "pallet-maintenance", "pallet-nonfungible", @@ -13114,6 +13170,7 @@ "pallet-refungible", "pallet-rmrk-core", "pallet-rmrk-equip", + "pallet-session", "pallet-structure", "pallet-sudo", "pallet-template-transaction-payment", @@ -13750,7 +13807,7 @@ "pallet-elections-phragmen", "pallet-fast-unstake", "pallet-grandpa", - "pallet-identity", + "pallet-identity 4.0.0-dev (git+https://github.com/paritytech/substrate?branch=polkadot-v0.9.36)", "pallet-im-online", "pallet-indices", "pallet-membership", --- a/Makefile +++ b/Makefile @@ -89,6 +89,10 @@ bench-evm-migration: make _bench PALLET=evm-migration +.PHONY: bench-configuration +bench-configuration: + make _bench PALLET=configuration + .PHONY: bench-common bench-common: make _bench PALLET=common @@ -127,12 +131,20 @@ .PHONY: bench-foreign-assets bench-foreign-assets: - make _bench PALLET=foreign-assets + make _bench PALLET=foreign-assets + +.PHONY: bench-collator-selection +bench-collator-selection: + make _bench PALLET=collator-selection + +.PHONY: bench-identity +bench-identity: + make _bench PALLET=identity .PHONY: bench-app-promotion bench-app-promotion: make _bench PALLET=app-promotion PALLET_DIR=app-promotion .PHONY: bench -# Disabled: bench-scheduler, bench-rmrk-core, bench-rmrk-equip -bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-foreign-assets +# Disabled: bench-scheduler, bench-collator-selection, bench-rmrk-core, bench-rmrk-equip +bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-configuration bench-foreign-assets bench-identity --- a/launch-config.json +++ b/launch-config.json @@ -85,7 +85,7 @@ "parachains": [ { "bin": "../unique-chain/target/release/unique-collator", - "id": "1000", + "id": 1000, "balance": "1000000000000000000000000", "nodes": [ { @@ -111,6 +111,42 @@ "--unsafe-ws-external", "-lxcm=trace" ] + }, + { + "port": 31202, + "wsPort": 9946, + "rpcPort": 9935, + "name": "charlie", + "flags": [ + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lxcm=trace" + ] + }, + { + "port": 31203, + "wsPort": 9947, + "rpcPort": 9936, + "name": "dave", + "flags": [ + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lxcm=trace" + ] + }, + { + "port": 31204, + "wsPort": 9948, + "rpcPort": 9937, + "name": "eve", + "flags": [ + "--rpc-cors=all", + "--unsafe-rpc-external", + "--unsafe-ws-external", + "-lxcm=trace" + ] } ] } --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -143,11 +143,76 @@ AccountPublic::from(get_from_seed::(seed)).into_account() } +#[cfg(feature = "quartz-runtime")] +macro_rules! testnet_genesis { + ( + $runtime:path, + $root_key:expr, + $initial_invulnerables:expr, + $endowed_accounts:expr, + $id:expr + ) => {{ + use $runtime::*; + + GenesisConfig { + system: SystemConfig { + code: WASM_BINARY + .expect("WASM binary was not build, please build it!") + .to_vec(), + }, + balances: BalancesConfig { + balances: $endowed_accounts + .iter() + .cloned() + // 1e13 UNQ + .map(|k| (k, 1 << 100)) + .collect(), + }, + treasury: Default::default(), + tokens: TokensConfig { balances: vec![] }, + sudo: SudoConfig { + key: Some($root_key), + }, + vesting: VestingConfig { vesting: vec![] }, + parachain_info: ParachainInfoConfig { + parachain_id: $id.into(), + }, + parachain_system: Default::default(), + collator_selection: CollatorSelectionConfig { + invulnerables: $initial_invulnerables + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect(), + }, + session: SessionConfig { + keys: $initial_invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura }, // session keys + ) + }) + .collect(), + }, + aura: Default::default(), + aura_ext: Default::default(), + evm: EVMConfig { + accounts: BTreeMap::new(), + }, + ethereum: EthereumConfig {}, + } + }}; +} + +#[cfg(not(feature = "quartz-runtime"))] macro_rules! testnet_genesis { ( $runtime:path, $root_key:expr, - $initial_authorities:expr, + $initial_invulnerables:expr, $endowed_accounts:expr, $id:expr ) => {{ @@ -178,7 +243,10 @@ }, parachain_system: Default::default(), aura: AuraConfig { - authorities: $initial_authorities, + authorities: $initial_invulnerables + .into_iter() + .map(|(_, aura)| aura) + .collect(), }, aura_ext: Default::default(), evm: EVMConfig { @@ -219,8 +287,14 @@ // Sudo account get_account_id_from_seed::("Alice"), vec![ - get_from_seed::("Alice"), - get_from_seed::("Bob"), + ( + get_account_id_from_seed::("Alice"), + get_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_from_seed::("Bob"), + ), ], // Pre-funded accounts vec![ @@ -287,8 +361,14 @@ // Sudo account get_account_id_from_seed::("Alice"), vec![ - get_from_seed::("Alice"), - get_from_seed::("Bob"), + ( + get_account_id_from_seed::("Alice"), + get_from_seed::("Alice"), + ), + ( + get_account_id_from_seed::("Bob"), + get_from_seed::("Bob"), + ), ], // Pre-funded accounts vec![ --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -973,7 +973,7 @@ .import_notification_stream() .map(|_| EngineCommand::SealNewBlock { create_empty: true, - finalize: false, + finalize: false, // todo:collator finalize true parent_hash: None, sender: None, }), @@ -984,7 +984,7 @@ dyn Stream> + Send + Sync + Unpin, > = Box::new(autoseal_interval.map(|_| EngineCommand::SealNewBlock { create_empty: true, - finalize: false, + finalize: false, // todo:collator finalize true parent_hash: None, sender: None, })); --- /dev/null +++ b/pallets/collator-selection/CHANGELOG.md @@ -0,0 +1,25 @@ +# Change Log + +All notable changes to this project will be documented in this file. + + + +## [4.0.0] - 2022-12-29 + +### Added + +- Entire functionality moved over from cumulus/pallet-collator-selection (v3.0.0). Refactored business logic: + - Added an extra step for candidacy, `get_license`, at which payment happens. The number of license holders is unlimited. + - Only licensed accounts can apply for candidacy with `onboard`. No extra deposits are made. + - Active candidates may `offboard`, but they will retain their license. + - Deposit is returned, and candidacy possibly removed with `release_license`. + - License can be forcibly forfeited and candidacy removed with `force_release_license`. + - Failing collators' deposits will now be fully slashed, and funds will be redirected to Treasury. + - Unify `MaxInvulnerables` and `MaxCandidates` into `MaxCollators`. + - Remove `MinCandidates`. + - Minimal amount of invulnerables is now 1. + - Both invulnerables and candidates count against the limits together, however invulnerables ignore `DesiredCollators`. + - `KickThreshold` is made configurable. + - `DesiredCollators`, `LicenseBond`, and `KickThreshold` are moved to and referenced from `pallet-configuration`. + - Naming changes to better reflect the new functionality. + - More minor changes, tests, benchmarks, etc. \ No newline at end of file --- /dev/null +++ b/pallets/collator-selection/Cargo.toml @@ -0,0 +1,64 @@ +[package] +name = "pallet-collator-selection" +version = "4.0.0" +description = "Simple staking pallet with a fixed stake." +authors = ["Parity Technologies ", "Unique Network "] +edition = "2021" +homepage = "https://unique.network" +repository = "https://github.com/UniqueNetwork/unique-chain" +license = "GPLv3" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = { version = "0.4.17", default-features = false } +codec = { default-features = false, features = ["derive"], package = "parity-scale-codec", version = "3.0.0" } +rand = { version = "0.8.5", features = ["std_rng"], default-features = false } +scale-info = { version = "2.2.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.145", default-features = false } + +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-staking = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-authorship = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-session = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-configuration = { default-features = false, path = "../configuration" } + +frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-tracing = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-timestamp = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-consensus-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +pallet-aura = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } + +[features] +default = ["std"] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +std = [ + "codec/std", + "log/std", + "scale-info/std", + "rand/std", + "sp-runtime/std", + "sp-staking/std", + "sp-std/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", + "pallet-authorship/std", + "pallet-session/std", +] + +try-runtime = [ "frame-support/try-runtime" ] --- /dev/null +++ b/pallets/collator-selection/src/benchmarking.rs @@ -0,0 +1,388 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking setup for pallet-collator-selection + +use super::*; + +#[allow(unused)] +use crate::Pallet as CollatorSelection; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller}; +use frame_support::{ + assert_ok, + codec::Decode, + traits::{Currency, EnsureOrigin, Get}, +}; +use frame_system::{EventRecord, RawOrigin}; +use pallet_authorship::EventHandler; +use pallet_session::{self as session, SessionManager}; +use pallet_configuration::{ + self as configuration, BalanceOf, + CollatorSelectionDesiredCollatorsOverride as DesiredCollators, + CollatorSelectionLicenseBondOverride as LicenseBond, +}; +use sp_std::prelude::*; + +const SEED: u32 = 0; + +// TODO: remove if this is given in substrate commit. +macro_rules! whitelist { + ($acc:ident) => { + frame_benchmarking::benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&$acc).into(), + ); + }; +} + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +fn create_funded_user( + string: &'static str, + n: u32, + balance_factor: u32, +) -> T::AccountId { + let user = account(string, n, SEED); + let balance = T::Currency::minimum_balance() * balance_factor.into(); + let _ = T::Currency::make_free_balance_be(&user, balance); + user +} + +fn keys(c: u32) -> ::Keys { + use rand::{RngCore, SeedableRng}; + + let keys = { + let mut keys = [0u8; 128]; + + if c > 0 { + let mut rng = rand::rngs::StdRng::seed_from_u64(c as u64); + rng.fill_bytes(&mut keys); + } + + keys + }; + + Decode::decode(&mut &keys[..]).unwrap() +} + +fn validator(c: u32) -> (T::AccountId, ::Keys) { + (create_funded_user::("candidate", c, 1000), keys::(c)) +} + +fn register_validators(count: u32) -> Vec { + let validators = (0..count).map(|c| validator::(c)).collect::>(); + + for (who, keys) in validators.clone() { + >::set_keys(RawOrigin::Signed(who).into(), keys, Vec::new()).unwrap(); + } + + validators.into_iter().map(|(who, _)| who).collect() +} + +fn register_invulnerables(count: u32) { + let candidates = (0..count) + .map(|c| account("candidate", c, SEED)) + .collect::>(); + + for who in candidates { + >::add_invulnerable(T::UpdateOrigin::successful_origin(), who) + .unwrap(); + } +} + +fn register_candidates(count: u32) { + let candidates = (0..count) + .map(|c| account("candidate", c, SEED)) + .collect::>(); + assert!( + >::get() > 0u32.into(), + "Bond cannot be zero!" + ); + + for who in candidates { + T::Currency::make_free_balance_be(&who, >::get() * 2u32.into()); + >::get_license(RawOrigin::Signed(who.clone()).into()).unwrap(); + >::onboard(RawOrigin::Signed(who).into()).unwrap(); + } +} + +fn get_licenses(count: u32) { + let candidates = (0..count) + .map(|c| account("candidate", c, SEED)) + .collect::>(); + assert!( + >::get() > 0u32.into(), + "Bond cannot be zero!" + ); + + for who in candidates { + T::Currency::make_free_balance_be(&who, >::get() * 2u32.into()); + >::get_license(RawOrigin::Signed(who.clone()).into()).unwrap(); + } +} + +benchmarks! { + where_clause { where T: pallet_authorship::Config + session::Config + configuration::Config } + + // todo:collator this and all the following do not work for some reason, going all the way up to 10 in length + // Both invulnerables and candidates count together against MaxCollators. + // Maybe try putting it in braces? 1 .. (T::MaxCollators::get() - 2) + add_invulnerable { + let b in 1 .. T::MaxCollators::get() - 3; + register_validators::(b); + register_invulnerables::(b); + + // log::info!("{} {}", >::get().len(), b); + + let new_invulnerable: T::AccountId = whitelisted_caller(); + let bond: BalanceOf = T::Currency::minimum_balance() * 2u32.into(); + T::Currency::make_free_balance_be(&new_invulnerable, bond.clone()); + + >::set_keys( + RawOrigin::Signed(new_invulnerable.clone()).into(), + keys::(b + 1), + Vec::new() + ).unwrap(); + + let root_origin = T::UpdateOrigin::successful_origin(); + }: { + assert_ok!( + >::add_invulnerable(root_origin, new_invulnerable.clone()) + ); + } + verify { + assert_last_event::(Event::InvulnerableAdded{invulnerable: new_invulnerable}.into()); + } + + remove_invulnerable { + let b in 1 .. T::MaxCollators::get(); + register_validators::(b); + register_invulnerables::(b); + + let root_origin = T::UpdateOrigin::successful_origin(); + let leaving = >::get().last().unwrap().clone(); + whitelist!(leaving); + }: { + assert_ok!( + >::remove_invulnerable(root_origin, leaving.clone()) + ); + } + verify { + assert_last_event::(Event::InvulnerableRemoved{invulnerable: leaving}.into()); + } + + get_license { + let c in 1 .. T::MaxCollators::get(); + + >::put(T::Currency::minimum_balance()); + + register_validators::(c); + get_licenses::(c); + + let caller: T::AccountId = whitelisted_caller(); + let bond: BalanceOf = T::Currency::minimum_balance() * 2u32.into(); + T::Currency::make_free_balance_be(&caller, bond.clone()); + + >::set_keys( + RawOrigin::Signed(caller.clone()).into(), + keys::(c + 1), + Vec::new() + ).unwrap(); + + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_last_event::(Event::LicenseObtained{account_id: caller, deposit: bond / 2u32.into()}.into()); + } + + // worst case is when we have all the max-candidate slots filled except one, and we fill that + // one. + onboard { + let c in 1 .. 5; + + >::put(T::Currency::minimum_balance()); + >::put(c + 2); + + register_validators::(c); + register_candidates::(c); + + let caller: T::AccountId = whitelisted_caller(); + let bond: BalanceOf = T::Currency::minimum_balance() * 2u32.into(); + T::Currency::make_free_balance_be(&caller, bond.clone()); + + let origin = RawOrigin::Signed(caller.clone()); + + >::set_keys( + origin.clone().into(), + keys::(c + 1), + Vec::new() + ).unwrap(); + + assert_ok!( + >::get_license(origin.clone().into()) + ); + }: _(origin) + verify { + assert_last_event::(Event::CandidateAdded{account_id: caller}.into()); + } + + // worst case is the last candidate leaving. + offboard { + let c in 1 .. T::MaxCollators::get(); + >::put(T::Currency::minimum_balance()); + >::put(c + 2); + + register_validators::(c); + register_candidates::(c); + + let leaving = >::get().last().unwrap().clone(); + whitelist!(leaving); + }: _(RawOrigin::Signed(leaving.clone())) + verify { + assert_last_event::(Event::CandidateRemoved{account_id: leaving}.into()); + } + + // worst case is the last candidate leaving. + release_license { + let c in 1 .. T::MaxCollators::get(); + let bond = T::Currency::minimum_balance(); + >::put(bond); + >::put(c); + + register_validators::(c); + register_candidates::(c); + + let leaving = >::get().last().unwrap().clone(); + whitelist!(leaving); + }: _(RawOrigin::Signed(leaving.clone())) + verify { + assert_last_event::(Event::LicenseReleased{account_id: leaving, deposit_returned: bond}.into()); + } + + // worst case is the last candidate leaving. + force_release_license { + let c in 1 .. T::MaxCollators::get(); + let bond = T::Currency::minimum_balance(); + >::put(bond); + >::put(c); + + register_validators::(c); + register_candidates::(c); + + let leaving = >::get().last().unwrap().clone(); + whitelist!(leaving); + let origin = T::UpdateOrigin::successful_origin(); + }: { + assert_ok!( + >::force_release_license(origin, leaving.clone()) + ); + } + verify { + assert_last_event::(Event::LicenseReleased{account_id: leaving, deposit_returned: bond}.into()); + } + + // worst case is paying a non-existing candidate account. + note_author { + >::put(T::Currency::minimum_balance()); + T::Currency::make_free_balance_be( + &>::account_id(), + T::Currency::minimum_balance() * 4u32.into(), + ); + let author = account("author", 0, SEED); + let new_block: T::BlockNumber = 10u32.into(); + + frame_system::Pallet::::set_block_number(new_block); + assert!(T::Currency::free_balance(&author) == 0u32.into()); + }: { + as EventHandler<_, _>>::note_author(author.clone()) + } verify { + assert!(T::Currency::free_balance(&author) > 0u32.into()); + assert_eq!(frame_system::Pallet::::block_number(), new_block); + } + + // worst case for new session. + new_session { + let r in 1 .. T::MaxCollators::get(); + let c in 1 .. T::MaxCollators::get(); + + >::put(T::Currency::minimum_balance()); + >::put(c); + frame_system::Pallet::::set_block_number(0u32.into()); + + register_validators::(c); + register_candidates::(c); + + let new_block: T::BlockNumber = 1800u32.into(); + let zero_block: T::BlockNumber = 0u32.into(); + let candidates = >::get(); + + let non_removals = c.saturating_sub(r); + + for i in 0..c { + >::insert(candidates[i as usize].clone(), zero_block); + } + + if non_removals > 0 { + for i in 0..non_removals { + >::insert(candidates[i as usize].clone(), new_block); + } + } else { + for i in 0..c { + >::insert(candidates[i as usize].clone(), new_block); + } + } + + let pre_length = >::get().len(); + + frame_system::Pallet::::set_block_number(new_block); + + assert!(>::get().len() == c as usize); + }: { + as SessionManager<_>>::new_session(0) + } verify { + if c > r { + assert!(>::get().len() < pre_length); + } else { + assert!(>::get().len() == pre_length); + } + } +} + +impl_benchmark_test_suite!( + CollatorSelection, + crate::mock::new_test_ext(), + crate::mock::Test, +); --- /dev/null +++ b/pallets/collator-selection/src/lib.rs @@ -0,0 +1,649 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// todo:collator documentation +//! Collator Selection pallet. +//! +//! A pallet to manage collators in a parachain. +//! +//! ## Overview +//! +//! The Collator Selection pallet manages the collators of a parachain. **Collation is _not_ a +//! secure activity** and this pallet does not implement any game-theoretic mechanisms to meet BFT +//! safety assumptions of the chosen set. +//! +//! ## Terminology +//! +//! - Collator: A parachain block producer. +//! - Bond: An amount of `Balance` _reserved_ for candidate registration. +//! - Invulnerable: An account guaranteed to be in the collator set. +//! +//! ## Implementation +//! +//! The final `Collators` are aggregated from two individual lists: +//! +//! 1. [`Invulnerables`]: a set of collators appointed by governance. These accounts will always be +//! collators. +//! 2. [`Candidates`]: these are *candidates to the collation task* and may or may not be elected as +//! a final collator. +//! +//! The current implementation resolves congestion of [`Candidates`] in a first-come-first-serve +//! manner. +//! +//! Candidates will not be allowed to get kicked or leave_intent if the total number of candidates +//! fall below MinCandidates. This is for potential disaster recovery scenarios. +//! +//! ### Rewards +//! +//! The Collator Selection pallet maintains an on-chain account (the "Pot"). In each block, the +//! collator who authored it receives: +//! +//! - Half the value of the Pot. +//! - Half the value of the transaction fees within the block. The other half of the transaction +//! fees are deposited into the Pot. +//! +//! To initiate rewards an ED needs to be transferred to the pot address. +//! +//! Note: Eventually the Pot distribution may be modified as discussed in +//! [this issue](https://github.com/paritytech/statemint/issues/21#issuecomment-810481073). + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +#[frame_support::pallet] +pub mod pallet { + pub use crate::weights::WeightInfo; + use core::ops::Div; + use frame_support::{ + dispatch::{DispatchClass, DispatchResultWithPostInfo}, + inherent::Vec, + pallet_prelude::*, + sp_runtime::traits::{AccountIdConversion, CheckedSub, Saturating, Zero}, + traits::{ + Currency, EnsureOrigin, ExistenceRequirement::KeepAlive, ReservableCurrency, + ValidatorRegistration, + }, + BoundedVec, PalletId, + }; + use frame_system::pallet_prelude::*; + use pallet_session::SessionManager; + use sp_runtime::{Perbill, traits::Convert}; + use pallet_configuration::{ + CollatorSelectionDesiredCollatorsOverride as DesiredCollators, + CollatorSelectionLicenseBondOverride as LicenseBond, + CollatorSelectionKickThresholdOverride as KickThreshold, BalanceOf, + }; + use sp_staking::SessionIndex; + + /// A convertor from collators id. Since this pallet does not have stash/controller, this is + /// just identity. + pub struct IdentityCollator; + impl sp_runtime::traits::Convert> for IdentityCollator { + fn convert(t: T) -> Option { + Some(t) + } + } + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config + pallet_configuration::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Origin that can dictate updating parameters of this pallet. + type UpdateOrigin: EnsureOrigin; + + /// Account Identifier that holds the chain's treasury. + type TreasuryAccountId: Get; + + /// Account Identifier from which the internal Pot is generated. + type PotId: Get; + + /// Maximum number of candidates and invulnerables that we should have. This is enforced in code. + type MaxCollators: Get; + + /// If kicked, how much of the collator's deposit will be slashed and sent to the slash destination. + type SlashRatio: Get; + + /// A stable ID for a validator. + type ValidatorId: Member + Parameter; + + /// A conversion from account ID to validator ID. + /// + /// Its cost must be at most one storage read. + type ValidatorIdOf: Convert>; + + /// Validate a user is registered + type ValidatorRegistration: ValidatorRegistration; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// The invulnerable, fixed collators. + #[pallet::storage] + #[pallet::getter(fn invulnerables)] + pub type Invulnerables = + StorageValue<_, BoundedVec, ValueQuery>; + + /// The (community) collation license holders. + #[pallet::storage] + #[pallet::getter(fn license_deposit_of)] + pub type LicenseDepositOf = + StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf, ValueQuery>; + + /// The (community, limited) collation candidates. + #[pallet::storage] + #[pallet::getter(fn candidates)] + pub type Candidates = + StorageValue<_, BoundedVec, ValueQuery>; + + /// Last block authored by collator. + #[pallet::storage] + #[pallet::getter(fn last_authored_block)] + pub type LastAuthoredBlock = + StorageMap<_, Twox64Concat, T::AccountId, T::BlockNumber, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub invulnerables: Vec, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + fn default() -> Self { + Self { + invulnerables: Default::default(), + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + let duplicate_invulnerables = self + .invulnerables + .iter() + .collect::>(); + assert!( + duplicate_invulnerables.len() == self.invulnerables.len(), + "duplicate invulnerables in genesis." + ); + + let bounded_invulnerables = + BoundedVec::<_, T::MaxCollators>::try_from(self.invulnerables.clone()) + .expect("genesis invulnerables are more than T::MaxCollators"); + + >::put(bounded_invulnerables); + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + InvulnerableAdded { + invulnerable: T::AccountId, + }, + InvulnerableRemoved { + invulnerable: T::AccountId, + }, + LicenseObtained { + account_id: T::AccountId, + deposit: BalanceOf, + }, + LicenseReleased { + account_id: T::AccountId, + deposit_returned: BalanceOf, + }, + CandidateAdded { + account_id: T::AccountId, + }, + CandidateRemoved { + account_id: T::AccountId, + }, + } + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + /// Too many candidates + TooManyCandidates, + /// Unknown error + Unknown, + /// Permission issue + Permission, + /// User already holds license to collate + AlreadyHoldingLicense, + /// User does not hold a license to collate + NoLicense, + /// User is already a candidate + AlreadyCandidate, + /// User is not a candidate + NotCandidate, + /// Too many invulnerables + TooManyInvulnerables, + /// Too few invulnerables + TooFewInvulnerables, + /// User is already an Invulnerable + AlreadyInvulnerable, + /// User is not an Invulnerable + NotInvulnerable, + /// Account has no associated validator ID + NoAssociatedValidatorId, + /// Validator ID is not yet registered + ValidatorNotRegistered, + } + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + /// Add a collator to the list of invulnerable (fixed) collators. + #[pallet::call_index(0)] + #[pallet::weight(::WeightInfo::add_invulnerable(T::MaxCollators::get()))] + pub fn add_invulnerable( + origin: OriginFor, + new: T::AccountId, + ) -> DispatchResultWithPostInfo { + T::UpdateOrigin::ensure_origin(origin)?; + + // check if the new invulnerable has associated validator keys before it is added + let validator_key = T::ValidatorIdOf::convert(new.clone()) + .ok_or(Error::::NoAssociatedValidatorId)?; + ensure!( + T::ValidatorRegistration::is_registered(&validator_key), + Error::::ValidatorNotRegistered + ); + if Self::invulnerables().contains(&new) { + return Ok(().into()); + } + + >::try_append(new.clone()) + .map_err(|_| Error::::TooManyInvulnerables)?; + + // try to offboard the new invulnerable if it was a collator candidate before + let _ = Self::try_remove_candidate(&new); + + Self::deposit_event(Event::InvulnerableAdded { invulnerable: new }); + Ok(().into()) + } + + /// Remove a collator from the list of invulnerable (fixed) collators. + #[pallet::call_index(1)] + #[pallet::weight(::WeightInfo::remove_invulnerable(T::MaxCollators::get()))] + pub fn remove_invulnerable( + origin: OriginFor, + who: T::AccountId, + ) -> DispatchResultWithPostInfo { + T::UpdateOrigin::ensure_origin(origin)?; + + >::try_mutate(|invulnerables| -> DispatchResult { + if invulnerables.len() <= 1 { + return Err(Error::::TooFewInvulnerables.into()); + } + + let index = invulnerables + .into_iter() + .position(|r| *r == who) + .ok_or(Error::::NotInvulnerable)?; + invulnerables.remove(index); + Ok(()) + })?; + Self::deposit_event(Event::InvulnerableRemoved { invulnerable: who }); + Ok(().into()) + } + + /// Purchase a license on block collation for this account. + /// It does not make it a collator candidate, use `onboard` afterward. The account must + /// (a) already have registered session keys and (b) be able to reserve the `LicenseBond`. + /// + /// This call is not available to `Invulnerable` collators. + #[pallet::call_index(2)] + #[pallet::weight(::WeightInfo::get_license(T::MaxCollators::get()))] + pub fn get_license(origin: OriginFor) -> DispatchResultWithPostInfo { + // register_as_candidate + let who = ensure_signed(origin)?; + + if LicenseDepositOf::::contains_key(&who) { + return Err(Error::::AlreadyHoldingLicense.into()); + } + + let validator_key = T::ValidatorIdOf::convert(who.clone()) + .ok_or(Error::::NoAssociatedValidatorId)?; + ensure!( + T::ValidatorRegistration::is_registered(&validator_key), + Error::::ValidatorNotRegistered + ); + + let deposit = >::get(); + + T::Currency::reserve(&who, deposit)?; + LicenseDepositOf::::insert(who.clone(), deposit); + + Self::deposit_event(Event::LicenseObtained { + account_id: who, + deposit, + }); + Ok(().into()) // Some(T::WeightInfo::register_as_candidate(current_count as u32)).into()) + } + + /// Register this account as a candidate for collators for next sessions. + /// The account must already hold a license, and cannot offboard immediately during a session. + /// + /// This call is not available to `Invulnerable` collators. + #[pallet::call_index(3)] + #[pallet::weight(::WeightInfo::onboard(T::MaxCollators::get()))] + pub fn onboard(origin: OriginFor) -> DispatchResultWithPostInfo { + // register_as_candidate + let who = ensure_signed(origin)?; + + // ensure the user obtained the license. + ensure!( + LicenseDepositOf::::contains_key(&who), + Error::::NoLicense + ); + // ensure we are below limit. + let length = >::decode_len().unwrap_or_default() + + >::decode_len().unwrap_or_default(); + ensure!( + (length as u32) < >::get(), + Error::::TooManyCandidates + ); + ensure!( + !Self::invulnerables().contains(&who), + Error::::AlreadyInvulnerable + ); + + let current_count = + >::try_mutate(|candidates| -> Result { + if candidates.iter().any(|candidate| *candidate == who) { + Err(Error::::AlreadyCandidate)? + } else { + candidates + .try_push(who.clone()) + .map_err(|_| Error::::TooManyCandidates)?; + // First authored block is current block plus kick threshold to handle session delay + >::insert( + who.clone(), + frame_system::Pallet::::block_number() + >::get(), + ); + Ok(candidates.len()) + } + })?; + + Self::deposit_event(Event::CandidateAdded { account_id: who }); + Ok(Some(::WeightInfo::onboard(current_count as u32)).into()) + } + + /// Deregister `origin` as a collator candidate. Note that the collator can only leave on + /// session change. The license to `onboard` later at any other time will remain. + #[pallet::call_index(4)] + #[pallet::weight(::WeightInfo::offboard(T::MaxCollators::get()))] + pub fn offboard(origin: OriginFor) -> DispatchResultWithPostInfo { + // leave_intent + let who = ensure_signed(origin)?; + let current_count = Self::try_remove_candidate(&who)?; + + Ok(Some(::WeightInfo::offboard(current_count as u32)).into()) + } + + /// Forfeit `origin`'s own license. The `LicenseBond` will be unreserved immediately. + /// + /// This call is not available to `Invulnerable` collators. + #[pallet::call_index(5)] + #[pallet::weight(::WeightInfo::release_license(T::MaxCollators::get()))] + pub fn release_license(origin: OriginFor) -> DispatchResultWithPostInfo { + // leave_intent + let who = ensure_signed(origin)?; + + let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?; + + Ok(Some(::WeightInfo::release_license( + current_count as u32, + )) + .into()) + } + + /// Force deregister `origin` as a collator candidate as a governing authority, and revoke its license. + /// Note that the collator can only leave on session change. + /// The `LicenseBond` will be unreserved and returned immediately. + /// + /// This call is, of course, not applicable to `Invulnerable` collators. + #[pallet::call_index(6)] + #[pallet::weight(::WeightInfo::force_release_license(T::MaxCollators::get()))] + pub fn force_release_license( + origin: OriginFor, + who: T::AccountId, + ) -> DispatchResultWithPostInfo { + // leave_intent + T::UpdateOrigin::ensure_origin(origin)?; + + let current_count = Self::try_remove_candidate_and_release_license(&who, false, true)?; + + Ok(Some(::WeightInfo::force_release_license( + current_count as u32, + )) + .into()) + } + } + + impl Pallet { + /// Get a unique, inaccessible account id from the `PotId`. + pub fn account_id() -> T::AccountId { + T::PotId::get().into_account_truncating() + } + + /// Removes a candidate and their license, optionally slashed and optionally ignoring, + /// whether or not they actually are a candidate. + fn try_remove_candidate_and_release_license( + who: &T::AccountId, + should_slash: bool, + ignore_if_not_candidate: bool, + ) -> Result { + let current_count = Self::try_remove_candidate(who); + let current_count = if ignore_if_not_candidate + && current_count == Err(Error::::NotCandidate.into()) + { + >::decode_len().unwrap_or_default() + } else { + current_count? + }; + Self::try_release_license(who, should_slash)?; + Ok(current_count) + } + + /// Removes a candidate from the collator pool for the next session if they exist. + fn try_remove_candidate(who: &T::AccountId) -> Result { + let current_count = + >::try_mutate(|candidates| -> Result { + let index = candidates + .iter() + .position(|candidate| *candidate == *who) + .ok_or(Error::::NotCandidate)?; + candidates.remove(index); + >::remove(who.clone()); + Ok(candidates.len()) + })?; + Self::deposit_event(Event::CandidateRemoved { + account_id: who.clone(), + }); + Ok(current_count) + } + + /// Removes a candidate if they exist and sends them back their deposit, optionally slashed. + fn try_release_license(who: &T::AccountId, should_slash: bool) -> DispatchResult { + let mut deposit_returned = BalanceOf::::default(); + LicenseDepositOf::::try_mutate_exists(who, |deposit| -> DispatchResult { + if let Some(deposit) = deposit.take() { + if should_slash { + let slashed = T::SlashRatio::get() * deposit; + let remaining = deposit - slashed; + + let (imbalance, _) = T::Currency::slash_reserved(who, slashed); + //T::Currency::unreserve(who, remaining); + deposit_returned = remaining; + + T::Currency::resolve_creating(&T::TreasuryAccountId::get(), imbalance); + } else { + //T::Currency::unreserve(who, deposit); + deposit_returned = deposit; + } + + T::Currency::unreserve(who, deposit_returned); + Ok(()) + } else { + Err(Error::::NoLicense.into()) + } + })?; + Self::deposit_event(Event::LicenseReleased { + account_id: who.clone(), + deposit_returned, + }); + Ok(()) + } + + /// Assemble the current set of candidates and invulnerables into the next collator set. + /// + /// This is done on the fly, as frequent as we are told to do so, as the session manager. + pub fn assemble_collators( + candidates: BoundedVec, + ) -> Vec { + let mut collators = Self::invulnerables().to_vec(); + collators.extend(candidates); + collators + } + + /// Kicks out candidates that did not produce a block in the kick threshold + /// and **confiscates** their deposits to the treasury. + pub fn kick_stale_candidates( + candidates: BoundedVec, + ) -> BoundedVec { + let now = frame_system::Pallet::::block_number(); + let kick_threshold = >::get(); + candidates + .into_iter() + .filter_map(|c| { + let last_block = >::get(c.clone()); + let since_last = now.saturating_sub(last_block); + if since_last < kick_threshold { + Some(c) + } else { + let outcome = Self::try_remove_candidate_and_release_license(&c, true, false); + if let Err(why) = outcome { + log::warn!("Failed to kick collator and release license {:?}", why); + debug_assert!(false, "failed to kick collator and release license {why:?}"); + } + None + } + }) + .collect::>() + .try_into() + .expect("filter_map operation can't result in a bounded vec larger than its original; qed") + } + } + + /// Keep track of number of authored blocks per authority, uncles are counted as well since + /// they're a valid proof of being online. + impl + pallet_authorship::EventHandler for Pallet + { + fn note_author(author: T::AccountId) { + let pot = Self::account_id(); + // assumes an ED will be sent to pot. + let reward = T::Currency::free_balance(&pot) + .checked_sub(&T::Currency::minimum_balance()) + .unwrap_or_else(Zero::zero) + .div(2u32.into()); + // `reward` is half of pot account minus ED, this should never fail. + let _success = T::Currency::transfer(&pot, &author, reward, KeepAlive); + debug_assert!(_success.is_ok()); + >::insert(author, frame_system::Pallet::::block_number()); + + frame_system::Pallet::::register_extra_weight_unchecked( + ::WeightInfo::note_author(), + DispatchClass::Mandatory, + ); + } + + fn note_uncle(_author: T::AccountId, _age: T::BlockNumber) { + //TODO can we ignore this? + } + } + + /// Play the role of the session manager. + impl SessionManager for Pallet { + fn new_session(index: SessionIndex) -> Option> { + log::info!( + "assembling new collators for new session {} at #{:?}", + index, + >::block_number(), + ); + + let candidates = Self::candidates(); + let candidates_len_before = candidates.len(); + let active_candidates = Self::kick_stale_candidates(candidates); + let removed = candidates_len_before - active_candidates.len(); + let result = Self::assemble_collators(active_candidates); + + frame_system::Pallet::::register_extra_weight_unchecked( + ::WeightInfo::new_session( + candidates_len_before as u32, + removed as u32, + ), + DispatchClass::Mandatory, + ); + Some(result) + } + fn start_session(_: SessionIndex) { + // we don't care. + } + fn end_session(_: SessionIndex) { + // we don't care. + } + } +} --- /dev/null +++ b/pallets/collator-selection/src/mock.rs @@ -0,0 +1,302 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate as collator_selection; +use frame_support::{ + ord_parameter_types, parameter_types, + traits::{FindAuthor, GenesisBuild, ValidatorRegistration}, + PalletId, +}; +use frame_system as system; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + testing::{Header, UintAuthorityId}, + traits::{BlakeTwo256, IdentityLookup, OpaqueKeys}, + Perbill, RuntimeAppPublic, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + Aura: pallet_aura::{Pallet, Storage, Config}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + CollatorSelection: collator_selection::{Pallet, Call, Storage, Event}, + Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, + Configuration: pallet_configuration::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + +impl system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = SS58Prefix; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 5; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; +} + +pub struct Author4; +impl FindAuthor for Author4 { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(4) + } +} + +impl pallet_authorship::Config for Test { + type FindAuthor = Author4; + type UncleGenerations = (); + type FilterUncle = (); + type EventHandler = CollatorSelection; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +impl pallet_aura::Config for Test { + type AuthorityId = sp_consensus_aura::sr25519::AuthorityId; + type MaxAuthorities = MaxAuthorities; + type DisabledValidators = (); +} + +sp_runtime::impl_opaque_keys! { + pub struct MockSessionKeys { + // a key for aura authoring + pub aura: UintAuthorityId, + } +} + +impl From for MockSessionKeys { + fn from(aura: sp_runtime::testing::UintAuthorityId) -> Self { + Self { aura } + } +} + +parameter_types! { + pub static SessionHandlerCollators: Vec = Vec::new(); + pub static SessionChangeBlock: u64 = 0; +} + +pub struct TestSessionHandler; +impl pallet_session::SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[UintAuthorityId::ID]; + fn on_genesis_session(keys: &[(u64, Ks)]) { + SessionHandlerCollators::set(keys.into_iter().map(|(a, _)| *a).collect::>()) + } + fn on_new_session(_: bool, keys: &[(u64, Ks)], _: &[(u64, Ks)]) { + SessionChangeBlock::set(System::block_number()); + dbg!(keys.len()); + SessionHandlerCollators::set(keys.into_iter().map(|(a, _)| *a).collect::>()) + } + fn on_before_session_ending() {} + fn on_disabled(_: u32) {} +} + +parameter_types! { + pub const Offset: u64 = 0; + pub const Period: u64 = 10; +} + +impl pallet_session::Config for Test { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = CollatorSelection; + type SessionHandler = TestSessionHandler; + type Keys = MockSessionKeys; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxCollators: u32 = 5; + pub const LicenseBond: u64 = 10; + pub const KickThreshold: u64 = 10; + // the following values do not matter and are meaningless, etc. + pub const DefaultWeightToFeeCoefficient: u64 = 100_000; + pub const DefaultMinGasPrice: u64 = 100_000; + pub const MaxXcmAllowedLocations: u32 = 16; + pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000); + pub const DayRelayBlocks: u32 = 1; +} + +impl pallet_configuration::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type DefaultCollatorSelectionMaxCollators = MaxCollators; + type DefaultCollatorSelectionKickThreshold = KickThreshold; + type DefaultCollatorSelectionLicenseBond = LicenseBond; + // the following constants we don't care about + type DefaultWeightToFeeCoefficient = DefaultWeightToFeeCoefficient; + type DefaultMinGasPrice = DefaultMinGasPrice; + type MaxXcmAllowedLocations = MaxXcmAllowedLocations; + type AppPromotionDailyRate = AppPromotionDailyRate; + type DayRelayBlocks = DayRelayBlocks; + type WeightInfo = pallet_configuration::weights::SubstrateWeight; +} + +ord_parameter_types! { + pub const RootAccount: u64 = 777; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const MaxAuthorities: u32 = 100_000; + pub const SlashRatio: Perbill = Perbill::one(); +} + +pub struct IsRegistered; +impl ValidatorRegistration for IsRegistered { + fn is_registered(id: &u64) -> bool { + if *id == 7u64 { + false + } else { + true + } + } +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type UpdateOrigin = EnsureSignedBy; + type PotId = PotId; + type MaxCollators = MaxCollators; + type SlashRatio = SlashRatio; + type TreasuryAccountId = (); + type ValidatorId = ::AccountId; + type ValidatorIdOf = IdentityCollator; + type ValidatorRegistration = IsRegistered; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + let invulnerables = vec![1, 2]; + + let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)]; + let keys = balances + .iter() + .map(|&(i, _)| { + ( + i, + i, + MockSessionKeys { + aura: UintAuthorityId(i), + }, + ) + }) + .collect::>(); + let collator_selection = collator_selection::GenesisConfig:: { invulnerables }; + let session = pallet_session::GenesisConfig:: { keys }; + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); + // collator selection must be initialized before session. + collator_selection.assimilate_storage(&mut t).unwrap(); + session.assimilate_storage(&mut t).unwrap(); + + t.into() +} + +pub fn initialize_to_block(n: u64) { + for i in System::block_number() + 1..=n { + System::set_block_number(i); + >::on_initialize(i); + } +} --- /dev/null +++ b/pallets/collator-selection/src/tests.rs @@ -0,0 +1,538 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate as collator_selection; +use crate::{mock::*, Error}; +use frame_support::{ + assert_noop, assert_ok, + traits::{Currency, GenesisBuild, OnInitialize}, +}; +use frame_system::RawOrigin; +use pallet_balances::Error as BalancesError; +use sp_runtime::traits::BadOrigin; +use pallet_configuration::{ + CollatorSelectionDesiredCollatorsOverride as DesiredCollators, + CollatorSelectionKickThresholdOverride as KickThreshold, + CollatorSelectionLicenseBondOverride as LicenseBond, +}; + +fn get_license_and_onboard(account_id: ::AccountId) { + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed( + account_id + ))); + assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed( + account_id + ))); +} + +#[test] +fn basic_setup_works() { + new_test_ext().execute_with(|| { + assert_eq!(>::get(), 5); + assert_eq!(>::get(), 10); + + assert!(CollatorSelection::candidates().is_empty()); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + }); +} + +#[test] +fn it_should_add_invulnerables() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 1 + )); + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 2 + )); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // cannot set with non-root. + assert_noop!( + CollatorSelection::add_invulnerable(RuntimeOrigin::signed(1), 3), + BadOrigin + ); + + // cannot set invulnerables without associated validator keys + assert_noop!( + CollatorSelection::add_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 7), + Error::::ValidatorNotRegistered + ); + }); +} + +#[test] +fn it_should_remove_invulnerables() { + new_test_ext().execute_with(|| { + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 1 + )); + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 2 + )); + + // cannot remove with non-root. + assert_noop!( + CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(1), 3), + BadOrigin + ); + + assert_ok!(CollatorSelection::remove_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 2 + )); + assert_eq!(CollatorSelection::invulnerables(), vec![1]); + + // cannot remove an invulnerable if there would be 0 invulnerables. + assert_noop!( + CollatorSelection::remove_invulnerable(RuntimeOrigin::signed(RootAccount::get()), 1), + Error::::TooFewInvulnerables + ); + }); +} + +#[test] +fn set_desired_collators_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(>::get(), 5); + + // can set + assert_ok!(Configuration::set_collator_selection_desired_collators( + RawOrigin::Root.into(), + Some(7) + )); + assert_eq!(>::get(), 7); + + // rejects bad origin + assert_noop!( + Configuration::set_collator_selection_desired_collators( + RuntimeOrigin::signed(1), + Some(8) + ), + BadOrigin + ); + }); +} + +#[test] +fn set_license_bond() { + new_test_ext().execute_with(|| { + // given + assert_eq!(>::get(), 10); + + // can set + assert_ok!(Configuration::set_collator_selection_license_bond( + RawOrigin::Root.into(), + Some(7) + )); + assert_eq!(>::get(), 7); + + // rejects bad origin. + assert_noop!( + Configuration::set_collator_selection_license_bond(RuntimeOrigin::signed(1), Some(8)), + BadOrigin + ); + }); +} + +#[test] +fn cannot_onboard_candidate_with_no_license() { + new_test_ext().execute_with(|| { + // can't onboard a candidate who did not get a license. + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(3)), + Error::::NoLicense, + ); + + // but give it a license and welcome aboard. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(3))); + }) +} + +#[test] +fn cannot_onboard_candidate_if_too_many() { + new_test_ext().execute_with(|| { + // reset desired candidates + >::put(0); + + // can still get a license. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4))); + + // can't accept anyone anymore. + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(4)), + Error::::TooManyCandidates, + ); + + // reset desired candidates to invulnerables + 1 + >::put(3); + assert_ok!(CollatorSelection::onboard(RuntimeOrigin::signed(4))); + + // but no more. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(5))); + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(5)), + Error::::TooManyCandidates, + ); + }) +} + +#[test] +fn cannot_obtain_license_if_keys_not_registered() { + new_test_ext().execute_with(|| { + // can't 7 because keys not registered. + assert_noop!( + CollatorSelection::get_license(RuntimeOrigin::signed(7)), + Error::::ValidatorNotRegistered + ); + }) +} + +#[test] +fn cannot_obtain_license_if_poor() { + new_test_ext().execute_with(|| { + assert_eq!(Balances::free_balance(&3), 100); + assert_eq!(Balances::free_balance(&33), 0); + + // works + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3))); + + // poor + assert_noop!( + CollatorSelection::get_license(RuntimeOrigin::signed(33)), + BalancesError::::InsufficientBalance, + ); + }); +} + +#[test] +fn cannot_onboard_dupe_candidate() { + new_test_ext().execute_with(|| { + // can add 3 as candidate + get_license_and_onboard(3); + assert_eq!(CollatorSelection::license_deposit_of(3), 10); + assert_eq!(CollatorSelection::candidates(), vec![3]); + assert_eq!(CollatorSelection::last_authored_block(3), 10); + assert_eq!(Balances::free_balance(3), 90); + + // but no more + assert_noop!( + CollatorSelection::get_license(RuntimeOrigin::signed(3)), + Error::::AlreadyHoldingLicense, + ); + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(3)), + Error::::AlreadyCandidate, + ); + }) +} + +#[test] +fn becoming_candidate_works() { + new_test_ext().execute_with(|| { + // given + assert_eq!(>::get(), 5); + assert_eq!(>::get(), 10); + assert_eq!(CollatorSelection::candidates(), Vec::new()); + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // take two endowed, non-invulnerables accounts. + assert_eq!(Balances::free_balance(&3), 100); + assert_eq!(Balances::free_balance(&4), 100); + + get_license_and_onboard(3); + get_license_and_onboard(4); + + assert_eq!(Balances::free_balance(&3), 90); + assert_eq!(Balances::free_balance(&4), 90); + + assert_eq!(CollatorSelection::candidates().len(), 2); + }); +} + +#[test] +fn cannot_become_candidate_if_invulnerable() { + new_test_ext().execute_with(|| { + assert_eq!(CollatorSelection::invulnerables(), vec![1, 2]); + + // can obtain a license even if is invulnerable. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(1))); + // but cannot onboard + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(1)), + Error::::AlreadyInvulnerable, + ); + + // get a license and then become invulnerable. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3))); + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 3 + )); + assert_noop!( + CollatorSelection::onboard(RuntimeOrigin::signed(3)), + Error::::AlreadyInvulnerable, + ); + }) +} + +#[test] +fn can_become_invulnerable_if_candidate() { + new_test_ext().execute_with(|| { + // become a candidate and then become invulnerable. + get_license_and_onboard(3); + assert_eq!(CollatorSelection::candidates(), vec![3]); + + assert_ok!(CollatorSelection::add_invulnerable( + RuntimeOrigin::signed(RootAccount::get()), + 3 + )); + // should exclude from candidates, but not revoke the license + assert_eq!(CollatorSelection::candidates(), vec![]); + assert_eq!(CollatorSelection::license_deposit_of(3), 10); + assert_eq!(Balances::free_balance(3), 90); + }); +} + +#[test] +fn offboard() { + new_test_ext().execute_with(|| { + // register a candidate. + get_license_and_onboard(3); + assert_eq!(Balances::free_balance(3), 90); + + // cannot leave if holds license but not yet candidate. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(4))); + assert_noop!( + CollatorSelection::offboard(RuntimeOrigin::signed(4)), + Error::::NotCandidate + ); + // cannot leave if does not hold license. + assert_noop!( + CollatorSelection::offboard(RuntimeOrigin::signed(5)), + Error::::NotCandidate + ); + + // bond is returned - only after releasing the license + assert_ok!(CollatorSelection::offboard(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 90); + assert_eq!(CollatorSelection::last_authored_block(3), 0); + assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 100); + }); +} + +#[test] +fn release_license() { + new_test_ext().execute_with(|| { + // obtain a license to collate and reserve the bond. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 90); + + // release the license and get the bond back. + assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 100); + + // register a candidate. + get_license_and_onboard(3); + assert_eq!(Balances::free_balance(3), 90); + + // can release license even if onboarded. + assert_ok!(CollatorSelection::release_license(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(CollatorSelection::candidates(), vec![]); + }); +} + +#[test] +fn force_release_license() { + new_test_ext().execute_with(|| { + // obtain a license to collate and reserve the bond. + assert_ok!(CollatorSelection::get_license(RuntimeOrigin::signed(3))); + assert_eq!(Balances::free_balance(3), 90); + + // cannot execute the operation as non-root + assert_noop!( + CollatorSelection::force_release_license(RuntimeOrigin::signed(3), 3), + BadOrigin + ); + + // release the license and get the bond back. + assert_ok!(CollatorSelection::force_release_license( + RuntimeOrigin::signed(RootAccount::get()), + 3 + )); + assert_eq!(Balances::free_balance(3), 100); + + // register a candidate. + get_license_and_onboard(3); + assert_eq!(Balances::free_balance(3), 90); + + // can release license even if onboarded. + assert_ok!(CollatorSelection::force_release_license( + RuntimeOrigin::signed(RootAccount::get()), + 3 + )); + assert_eq!(Balances::free_balance(3), 100); + assert_eq!(CollatorSelection::candidates(), vec![]); + }); +} + +#[test] +fn authorship_event_handler() { + new_test_ext().execute_with(|| { + // put 100 in the pot + 5 for ED + Balances::make_free_balance_be(&CollatorSelection::account_id(), 105); + + // 4 is the default author. + assert_eq!(Balances::free_balance(4), 100); + get_license_and_onboard(4); + // triggers `note_author` + Authorship::on_initialize(1); + + assert_eq!(CollatorSelection::candidates(), vec![4]); + assert_eq!(CollatorSelection::last_authored_block(4), 0); + + // half of the pot goes to the collator who's the author (4 in tests). + assert_eq!(Balances::free_balance(4), 140); + // half + ED stays. + assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 55); + }); +} + +#[test] +fn fees_edgecases() { + new_test_ext().execute_with(|| { + // Nothing panics, no reward when no ED in balance + Authorship::on_initialize(1); + // put some money into the pot at ED + Balances::make_free_balance_be(&CollatorSelection::account_id(), 5); + // 4 is the default author. + assert_eq!(Balances::free_balance(4), 100); + get_license_and_onboard(4); + // triggers `note_author` + Authorship::on_initialize(1); + + assert_eq!(CollatorSelection::candidates(), vec![4]); + assert_eq!(CollatorSelection::last_authored_block(4), 0); + // Nothing received + assert_eq!(Balances::free_balance(4), 90); + // all fee stays + assert_eq!(Balances::free_balance(CollatorSelection::account_id()), 5); + }); +} + +#[test] +fn session_management_works() { + new_test_ext().execute_with(|| { + initialize_to_block(1); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(4); + + assert_eq!(SessionChangeBlock::get(), 0); + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + // add a new collator + get_license_and_onboard(5); + + // session won't see this. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + // but we have a new candidate. + assert_eq!(CollatorSelection::candidates().len(), 1); + + initialize_to_block(10); + assert_eq!(SessionChangeBlock::get(), 10); + // pallet-session has 1 session delay; current validators are the same. + assert_eq!(Session::validators(), vec![1, 2]); + // queued ones are changed, and now we have 3. + assert_eq!(Session::queued_keys().len(), 3); + // session handlers (aura, et. al.) cannot see this yet. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2]); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // changed are now reflected to session handlers. + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 5]); + }); +} + +#[test] +fn kick_mechanism() { + new_test_ext().execute_with(|| { + // add a new collator + get_license_and_onboard(3); + get_license_and_onboard(4); + + initialize_to_block(10); + assert_eq!(CollatorSelection::candidates().len(), 2); + + initialize_to_block(20); + assert_eq!(SessionChangeBlock::get(), 20); + // 4 authored this block, gets to stay 3 was kicked + assert_eq!(CollatorSelection::candidates().len(), 1); + // 3 will be kicked after 1 session delay + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 3, 4]); + + assert_eq!(CollatorSelection::candidates(), vec![4]); + assert_eq!(>::get(), 10); + assert_eq!(CollatorSelection::last_authored_block(4), 20); + + initialize_to_block(30); + // 3 gets kicked after 1 session delay + assert_eq!(SessionHandlerCollators::get(), vec![1, 2, 4]); + // kicked collator gets their funds slashed, the deposit going to treasury + assert_eq!(Balances::free_balance(3), 90); + }); +} + +#[test] +#[should_panic = "duplicate invulnerables in genesis."] +fn cannot_set_genesis_value_twice() { + sp_tracing::try_init_simple(); + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + let invulnerables = vec![1, 1]; + + let collator_selection = collator_selection::GenesisConfig:: { invulnerables }; + // collator selection must be initialized before session. + collator_selection.assimilate_storage(&mut t).unwrap(); +} --- /dev/null +++ b/pallets/collator-selection/src/weights.rs @@ -0,0 +1,190 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +// The weight info trait for `pallet_collator_selection`. +pub trait WeightInfo { + fn add_invulnerable(_b: u32) -> Weight; + fn remove_invulnerable(_b: u32) -> Weight; + fn get_license(_c: u32) -> Weight; + fn onboard(_c: u32) -> Weight; + fn offboard(_c: u32) -> Weight; + fn release_license(_c: u32) -> Weight; + fn force_release_license(_c: u32) -> Weight; + fn note_author() -> Weight; + fn new_session(_c: u32, _r: u32) -> Weight; +} + +/// Weights for pallet_collator_selection using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn add_invulnerable(b: u32) -> Weight { + Weight::from_ref_time(18_563_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn remove_invulnerable(b: u32) -> Weight { + Weight::from_ref_time(18_563_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + fn get_license(c: u32) -> Weight { + Weight::from_ref_time(71_196_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(198_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn onboard(c: u32) -> Weight { + Weight::from_ref_time(71_196_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(198_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(4 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn offboard(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn release_license(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn force_release_license(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + fn note_author() -> Weight { + Weight::from_ref_time(71_461_000 as u64) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(4 as u64)) + } + fn new_session(r: u32, c: u32) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 1_010_000 + .saturating_add(Weight::from_ref_time(109_961_000 as u64).saturating_mul(r as u64)) + // Standard Error: 1_010_000 + .saturating_add(Weight::from_ref_time(151_952_000 as u64).saturating_mul(c as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(r as u64))) + .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(c as u64))) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + fn add_invulnerable(b: u32) -> Weight { + Weight::from_ref_time(18_563_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn remove_invulnerable(b: u32) -> Weight { + Weight::from_ref_time(18_563_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(68_000 as u64).saturating_mul(b as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + fn get_license(c: u32) -> Weight { + Weight::from_ref_time(71_196_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(198_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn onboard(c: u32) -> Weight { + Weight::from_ref_time(71_196_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(198_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(4 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn offboard(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn release_license(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn force_release_license(c: u32) -> Weight { + Weight::from_ref_time(55_336_000 as u64) + // Standard Error: 0 + .saturating_add(Weight::from_ref_time(151_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + fn note_author() -> Weight { + Weight::from_ref_time(71_461_000 as u64) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(4 as u64)) + } + fn new_session(r: u32, c: u32) -> Weight { + Weight::from_ref_time(0 as u64) + // Standard Error: 1_010_000 + .saturating_add(Weight::from_ref_time(109_961_000 as u64).saturating_mul(r as u64)) + // Standard Error: 1_010_000 + .saturating_add(Weight::from_ref_time(151_952_000 as u64).saturating_mul(c as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64))) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(r as u64))) + .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(c as u64))) + } +} --- a/pallets/configuration/CHANGELOG.md +++ b/pallets/configuration/CHANGELOG.md @@ -1,4 +1,15 @@ +# Change Log + +All notable changes to this project will be documented in this file. + + +## [0.1.3] - 2022-12-29 + +### Added + +- The ability to configure pallet `collator-selection` via the `configuration` pallet. + ## [0.1.2] - 2022-12-13 ### Added --- a/pallets/configuration/Cargo.toml +++ b/pallets/configuration/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-configuration" -version = "0.1.2" +version = "0.1.3" edition = "2021" [dependencies] @@ -12,6 +12,7 @@ ] } frame-support = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } frame-system = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } sp-std = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } sp-core = { default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } @@ -22,10 +23,12 @@ [features] default = ["std"] +runtime-benchmarks = ["frame-benchmarking"] std = [ "parity-scale-codec/std", "frame-support/std", "frame-system/std", + "frame-benchmarking/std", "sp-runtime/std", "sp-std/std", "sp-core/std", --- /dev/null +++ b/pallets/configuration/src/benchmarking.rs @@ -0,0 +1,100 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +//! Benchmarking setup for pallet-configuration + +use super::*; +use frame_benchmarking::benchmarks; +use frame_system::{EventRecord, RawOrigin}; +use frame_support::{assert_ok, BoundedVec, traits::Currency}; +use xcm::v1::MultiLocation; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + +benchmarks! { + where_clause { where T: Config } + + set_weight_to_fee_coefficient_override { + let coeff: u64 = 999; + }: { + assert_ok!( + >::set_weight_to_fee_coefficient_override(RawOrigin::Root.into(), Some(coeff)) + ); + } + + set_min_gas_price_override { + let coeff: u64 = 999; + }: { + assert_ok!( + >::set_min_gas_price_override(RawOrigin::Root.into(), Some(coeff)) + ); + } + + set_xcm_allowed_locations { + let locations: BoundedVec = Default::default(); + }: { + assert_ok!( + >::set_xcm_allowed_locations(RawOrigin::Root.into(), Some(locations)) + ); + } + + set_app_promotion_configuration_override { + let configuration: AppPromotionConfiguration = Default::default(); + }: { + assert_ok!( + >::set_app_promotion_configuration_override(RawOrigin::Root.into(), configuration) + ); + } + + set_collator_selection_desired_collators { + let max: u32 = 999; + }: { + assert_ok!( + >::set_collator_selection_desired_collators(RawOrigin::Root.into(), Some(max.clone())) + ); + } + verify { + assert_last_event::(Event::NewDesiredCollators{desired_collators: Some(max)}.into()); + } + + set_collator_selection_license_bond { + let bond_cost: Option> = Some(T::Currency::minimum_balance() * 10u32.into()); + }: { + assert_ok!( + >::set_collator_selection_license_bond(RawOrigin::Root.into(), bond_cost.clone()) + ); + } + verify { + assert_last_event::(Event::NewCollatorLicenseBond{bond_cost}.into()); + } + + set_collator_selection_kick_threshold { + let threshold: Option = Some(900u32.into()); + }: { + assert_ok!( + >::set_collator_selection_kick_threshold(RawOrigin::Root.into(), threshold.clone()) + ); + } + verify { + assert_last_event::(Event::NewCollatorKickThreshold{length_in_blocks: threshold}.into()); + } +} --- a/pallets/configuration/src/lib.rs +++ b/pallets/configuration/src/lib.rs @@ -34,19 +34,33 @@ pub use pallet::*; use sp_core::U256; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + #[pallet] mod pallet { use super::*; use frame_support::{ - traits::Get, - pallet_prelude::{StorageValue, ValueQuery, DispatchResult, OptionQuery}, - BoundedVec, + traits::{Get, ReservableCurrency, Currency}, + pallet_prelude::{StorageValue, ValueQuery, DispatchResult, IsType, OptionQuery}, + BoundedVec, log, }; - use frame_system::{pallet_prelude::OriginFor, ensure_root}; + use frame_system::{pallet_prelude::OriginFor, ensure_root, Config as SystemConfig}; use xcm::v1::MultiLocation; + pub use crate::weights::WeightInfo; + pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + #[pallet::config] pub trait Config: frame_system::Config { + /// Overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The currency mechanism. + type Currency: ReservableCurrency; + #[pallet::constant] type DefaultWeightToFeeCoefficient: Get; #[pallet::constant] @@ -58,6 +72,30 @@ type AppPromotionDailyRate: Get; #[pallet::constant] type DayRelayBlocks: Get; + + #[pallet::constant] + type DefaultCollatorSelectionMaxCollators: Get; + #[pallet::constant] + type DefaultCollatorSelectionLicenseBond: Get>; + #[pallet::constant] + type DefaultCollatorSelectionKickThreshold: Get; + + /// The weight information of this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + NewDesiredCollators { + desired_collators: Option, + }, + NewCollatorLicenseBond { + bond_cost: Option>, + }, + NewCollatorKickThreshold { + length_in_blocks: Option, + }, } #[pallet::error] @@ -86,10 +124,31 @@ pub type AppPromomotionConfigurationOverride = StorageValue, QueryKind = ValueQuery>; + #[pallet::storage] + pub type CollatorSelectionDesiredCollatorsOverride = StorageValue< + Value = u32, + QueryKind = ValueQuery, + OnEmpty = T::DefaultCollatorSelectionMaxCollators, + >; + + #[pallet::storage] + pub type CollatorSelectionLicenseBondOverride = StorageValue< + Value = BalanceOf, + QueryKind = ValueQuery, + OnEmpty = T::DefaultCollatorSelectionLicenseBond, + >; + + #[pallet::storage] + pub type CollatorSelectionKickThresholdOverride = StorageValue< + Value = T::BlockNumber, + QueryKind = ValueQuery, + OnEmpty = T::DefaultCollatorSelectionKickThreshold, + >; + #[pallet::call] impl Pallet { #[pallet::call_index(0)] - #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::weight(T::WeightInfo::set_weight_to_fee_coefficient_override())] pub fn set_weight_to_fee_coefficient_override( origin: OriginFor, coeff: Option, @@ -104,7 +163,7 @@ } #[pallet::call_index(1)] - #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::weight(T::WeightInfo::set_min_gas_price_override())] pub fn set_min_gas_price_override( origin: OriginFor, coeff: Option, @@ -119,7 +178,7 @@ } #[pallet::call_index(2)] - #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::weight(T::WeightInfo::set_xcm_allowed_locations())] pub fn set_xcm_allowed_locations( origin: OriginFor, locations: Option>, @@ -130,7 +189,7 @@ } #[pallet::call_index(3)] - #[pallet::weight(T::DbWeight::get().writes(1))] + #[pallet::weight(T::WeightInfo::set_app_promotion_configuration_override())] pub fn set_app_promotion_configuration_override( origin: OriginFor, mut configuration: AppPromotionConfiguration, @@ -149,6 +208,62 @@ Ok(()) } + + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::set_collator_selection_desired_collators())] + pub fn set_collator_selection_desired_collators( + origin: OriginFor, + max: Option, + ) -> DispatchResult { + ensure_root(origin)?; + if let Some(max) = max { + // we trust origin calls, this is just a for more accurate benchmarking + if max > T::DefaultCollatorSelectionMaxCollators::get() { + log::warn!("max > T::DefaultCollatorSelectionMaxCollators; you might need to run benchmarks again"); + } + >::set(max); + } else { + >::kill(); + } + Self::deposit_event(Event::NewDesiredCollators { + desired_collators: max, + }); + Ok(()) + } + + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::set_collator_selection_license_bond())] + pub fn set_collator_selection_license_bond( + origin: OriginFor, + amount: Option>, + ) -> DispatchResult { + ensure_root(origin)?; + if let Some(amount) = amount { + >::set(amount); + } else { + >::kill(); + } + Self::deposit_event(Event::NewCollatorLicenseBond { bond_cost: amount }); + Ok(()) + } + + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::set_collator_selection_kick_threshold())] + pub fn set_collator_selection_kick_threshold( + origin: OriginFor, + threshold: Option, + ) -> DispatchResult { + ensure_root(origin)?; + if let Some(threshold) = threshold { + >::set(threshold); + } else { + >::kill(); + } + Self::deposit_event(Event::NewCollatorKickThreshold { + length_in_blocks: threshold, + }); + Ok(()) + } } #[pallet::pallet] --- /dev/null +++ b/pallets/configuration/src/weights.rs @@ -0,0 +1,123 @@ +// Template adopted from https://github.com/paritytech/substrate/blob/master/.maintain/frame-weight-template.hbs + +//! Autogenerated weights for pallet_configuration +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-12-28, STEPS: `50`, REPEAT: 80, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// target/release/unique-collator +// benchmark +// pallet +// --pallet +// pallet-configuration +// --wasm-execution +// compiled +// --extrinsic +// * +// --template +// .maintain/frame-weight-template.hbs +// --steps=50 +// --repeat=80 +// --heap-pages=4096 +// --output=./pallets/configuration/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] +#![allow(clippy::unnecessary_cast)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_configuration. +pub trait WeightInfo { + fn set_weight_to_fee_coefficient_override() -> Weight; + fn set_min_gas_price_override() -> Weight; + fn set_xcm_allowed_locations() -> Weight; + fn set_app_promotion_configuration_override() -> Weight; + fn set_collator_selection_desired_collators() -> Weight; + fn set_collator_selection_license_bond() -> Weight; + fn set_collator_selection_kick_threshold() -> Weight; +} + +/// Weights for pallet_configuration using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Configuration WeightToFeeCoefficientOverride (r:0 w:1) + fn set_weight_to_fee_coefficient_override() -> Weight { + Weight::from_ref_time(5_691_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration MinGasPriceOverride (r:0 w:1) + fn set_min_gas_price_override() -> Weight { + Weight::from_ref_time(5_521_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration XcmAllowedLocationsOverride (r:0 w:1) + fn set_xcm_allowed_locations() -> Weight { + Weight::from_ref_time(6_091_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration AppPromomotionConfigurationOverride (r:0 w:1) + fn set_app_promotion_configuration_override() -> Weight { + Weight::from_ref_time(6_241_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionDesiredCollatorsOverride (r:0 w:1) + fn set_collator_selection_desired_collators() -> Weight { + Weight::from_ref_time(25_298_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionLicenseBondOverride (r:0 w:1) + fn set_collator_selection_license_bond() -> Weight { + Weight::from_ref_time(18_675_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionKickThresholdOverride (r:0 w:1) + fn set_collator_selection_kick_threshold() -> Weight { + Weight::from_ref_time(18_044_000 as u64) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Configuration WeightToFeeCoefficientOverride (r:0 w:1) + fn set_weight_to_fee_coefficient_override() -> Weight { + Weight::from_ref_time(5_691_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration MinGasPriceOverride (r:0 w:1) + fn set_min_gas_price_override() -> Weight { + Weight::from_ref_time(5_521_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration XcmAllowedLocationsOverride (r:0 w:1) + fn set_xcm_allowed_locations() -> Weight { + Weight::from_ref_time(6_091_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration AppPromomotionConfigurationOverride (r:0 w:1) + fn set_app_promotion_configuration_override() -> Weight { + Weight::from_ref_time(6_241_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionDesiredCollatorsOverride (r:0 w:1) + fn set_collator_selection_desired_collators() -> Weight { + Weight::from_ref_time(25_298_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionLicenseBondOverride (r:0 w:1) + fn set_collator_selection_license_bond() -> Weight { + Weight::from_ref_time(18_675_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Configuration CollatorSelectionKickThresholdOverride (r:0 w:1) + fn set_collator_selection_kick_threshold() -> Weight { + Weight::from_ref_time(18_044_000 as u64) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} --- /dev/null +++ b/pallets/identity/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-identity" +version = "4.0.0-dev" +authors = ["Parity Technologies ", "Unique Network "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME identity management pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "max-encoded-len"] } +enumflags2 = { version = "0.7.4" } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { default-features = false, optional = true, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-io = { version = "7.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-runtime = { version = "7.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-std = { version = "5.0.0", default-features = false, git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } +sp-core = { version = "7.0.0", git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.36" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] --- /dev/null +++ b/pallets/identity/README.md @@ -0,0 +1,56 @@ +# Identity Module + +- [`identity::Config`](https://docs.rs/pallet-identity/latest/pallet_identity/trait.Config.html) +- [`Call`](https://docs.rs/pallet-identity/latest/pallet_identity/enum.Call.html) + +## Overview + +A federated naming system, allowing for multiple registrars to be added from a specified origin. +Registrars can set a fee to provide identity-verification service. Anyone can put forth a +proposed identity for a fixed deposit and ask for review by any number of registrars (paying +each of their fees). Registrar judgements are given as an `enum`, allowing for sophisticated, +multi-tier opinions. + +Some judgements are identified as *sticky*, which means they cannot be removed except by +complete removal of the identity, or by the registrar. Judgements are allowed to represent a +portion of funds that have been reserved for the registrar. + +A super-user can remove accounts and in doing so, slash the deposit. + +All accounts may also have a limited number of sub-accounts which may be specified by the owner; +by definition, these have equivalent ownership and each has an individual name. + +The number of registrars should be limited, and the deposit made sufficiently large, to ensure +no state-bloat attack is viable. + +## Interface + +### Dispatchable Functions + +#### For general users +* `set_identity` - Set the associated identity of an account; a small deposit is reserved if not + already taken. +* `clear_identity` - Remove an account's associated identity; the deposit is returned. +* `request_judgement` - Request a judgement from a registrar, paying a fee. +* `cancel_request` - Cancel the previous request for a judgement. + +#### For general users with sub-identities +* `set_subs` - Set the sub-accounts of an identity. +* `add_sub` - Add a sub-identity to an identity. +* `remove_sub` - Remove a sub-identity of an identity. +* `rename_sub` - Rename a sub-identity of an identity. +* `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity). + +#### For registrars +* `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar. +* `set_fields` - Set the fields that a registrar cares about in their judgements. +* `provide_judgement` - Provide a judgement to an identity. + +#### For super-users +* `add_registrar` - Add a new registrar to the system. +* `kill_identity` - Forcibly remove the associated identity; the deposit is lost. + +[`Call`]: ./enum.Call.html +[`Config`]: ./trait.Config.html + +License: Apache-2.0 --- /dev/null +++ b/pallets/identity/src/benchmarking.rs @@ -0,0 +1,500 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Identity pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use crate::Pallet as Identity; +use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_support::{ + ensure, assert_ok, + traits::{EnsureOrigin, Get}, +}; +use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; + +const SEED: u32 = 0; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +// Adds `r` registrars to the Identity Pallet. These registrars will have set fees and fields. +fn add_registrars(r: u32) -> Result<(), &'static str> { + for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); + let _ = T::Currency::make_free_balance_be(®istrar, BalanceOf::::max_value()); + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, registrar_lookup)?; + Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), i, 10u32.into())?; + let fields = IdentityFields( + IdentityField::Display + | IdentityField::Legal + | IdentityField::Web + | IdentityField::Riot + | IdentityField::Email + | IdentityField::PgpFingerprint + | IdentityField::Image + | IdentityField::Twitter, + ); + Identity::::set_fields(RawOrigin::Signed(registrar.clone()).into(), i, fields)?; + } + + assert_eq!(Registrars::::get().len(), r as usize); + Ok(()) +} + +// Create `s` sub-accounts for the identity of `who` and return them. +// Each will have 32 bytes of raw data added to it. +fn create_sub_accounts( + who: &T::AccountId, + s: u32, +) -> Result, &'static str> { + let mut subs = Vec::new(); + let who_origin = RawOrigin::Signed(who.clone()); + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + + for i in 0..s { + let sub_account = account("sub", i, SEED); + subs.push((sub_account, data.clone())); + } + + // Set identity so `set_subs` does not fail. + if IdentityOf::::get(who).is_none() { + let _ = T::Currency::make_free_balance_be(who, BalanceOf::::max_value() / 2u32.into()); + let info = create_identity_info::(1); + Identity::::set_identity(who_origin.into(), Box::new(info))?; + } + + Ok(subs) +} + +// Adds `s` sub-accounts to the identity of `who`. Each will have 32 bytes of raw data added to it. +// This additionally returns the vector of sub-accounts so it can be modified if needed. +fn add_sub_accounts( + who: &T::AccountId, + s: u32, +) -> Result, &'static str> { + let who_origin = RawOrigin::Signed(who.clone()); + let subs = create_sub_accounts::(who, s)?; + + Identity::::set_subs(who_origin.into(), subs.clone())?; + + Ok(subs) +} + +// This creates an `IdentityInfo` object with `num_fields` extra fields. +// All data is pre-populated with some arbitrary bytes. +fn create_identity_info(num_fields: u32) -> IdentityInfo { + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + + IdentityInfo { + additional: vec![(data.clone(), data.clone()); num_fields as usize] + .try_into() + .unwrap(), + display: data.clone(), + legal: data.clone(), + web: data.clone(), + riot: data.clone(), + email: data.clone(), + pgp_fingerprint: Some([0; 20]), + image: data.clone(), + twitter: data, + } +} + +benchmarks! { + add_registrar { + let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; + ensure!(Registrars::::get().len() as u32 == r, "Registrars not set up correctly."); + let origin = T::RegistrarOrigin::successful_origin(); + let account = T::Lookup::unlookup(account("registrar", r + 1, SEED)); + }: _(origin, account) + verify { + ensure!(Registrars::::get().len() as u32 == r + 1, "Registrars not added."); + } + + set_identity { + let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; + let x in 0 .. T::MaxAdditionalFields::get(); + let caller = { + // The target user + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let caller_origin: ::RuntimeOrigin = RawOrigin::Signed(caller.clone()).into(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + // Add an initial identity + let initial_info = create_identity_info::(1); + Identity::::set_identity(caller_origin.clone(), Box::new(initial_info.clone()))?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let registrar_lookup = T::Lookup::unlookup(registrar.clone()); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(registrar).into(), + i, + caller_lookup.clone(), + Judgement::Reasonable, + T::Hashing::hash_of(&initial_info), + )?; + } + caller + }; + }: _(RawOrigin::Signed(caller.clone()), Box::new(create_identity_info::(x))) + verify { + assert_last_event::(Event::::IdentitySet { who: caller }.into()); + } + + // We need to split `set_subs` into two benchmarks to accurately isolate the potential + // writes caused by new or old sub accounts. The actual weight should simply be + // the sum of these two weights. + set_subs_new { + let caller: T::AccountId = whitelisted_caller(); + // Create a new subs vec with s sub accounts + let s in 0 .. T::MaxSubAccounts::get() => (); + let subs = create_sub_accounts::(&caller, s)?; + ensure!(SubsOf::::get(&caller).1.len() == 0, "Caller already has subs"); + }: set_subs(RawOrigin::Signed(caller.clone()), subs) + verify { + ensure!(SubsOf::::get(&caller).1.len() as u32 == s, "Subs not added"); + } + + set_subs_old { + let caller: T::AccountId = whitelisted_caller(); + // Give them p many previous sub accounts. + let p in 0 .. T::MaxSubAccounts::get() => { + let _ = add_sub_accounts::(&caller, p)?; + }; + // Remove all subs. + let subs = create_sub_accounts::(&caller, 0)?; + ensure!( + SubsOf::::get(&caller).1.len() as u32 == p, + "Caller does have subs", + ); + }: set_subs(RawOrigin::Signed(caller.clone()), subs) + verify { + ensure!(SubsOf::::get(&caller).1.len() == 0, "Subs not removed"); + } + + clear_identity { + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); + let caller_lookup = ::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; + let s in 0 .. T::MaxSubAccounts::get() => { + // Give them s many sub accounts + let caller: T::AccountId = whitelisted_caller(); + let _ = add_sub_accounts::(&caller, s)?; + }; + let x in 0 .. T::MaxAdditionalFields::get(); + + // Create their main identity with x additional fields + let info = create_identity_info::(x); + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); + Identity::::set_identity(caller_origin.clone(), Box::new(info.clone()))?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + + Identity::::request_judgement(caller_origin.clone(), i, 10u32.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(registrar).into(), + i, + caller_lookup.clone(), + Judgement::Reasonable, + T::Hashing::hash_of(&info), + )?; + } + ensure!(IdentityOf::::contains_key(&caller), "Identity does not exist."); + }: _(RawOrigin::Signed(caller.clone())) + verify { + ensure!(!IdentityOf::::contains_key(&caller), "Identity not cleared."); + } + + request_judgement { + let caller: T::AccountId = whitelisted_caller(); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; + let x in 0 .. T::MaxAdditionalFields::get() => { + // Create their main identity with x additional fields + let info = create_identity_info::(x); + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller)); + Identity::::set_identity(caller_origin, Box::new(info))?; + }; + }: _(RawOrigin::Signed(caller.clone()), r - 1, 10u32.into()) + verify { + assert_last_event::(Event::::JudgementRequested { who: caller, registrar_index: r-1 }.into()); + } + + cancel_request { + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller.clone())); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; + let x in 0 .. T::MaxAdditionalFields::get() => { + // Create their main identity with x additional fields + let info = create_identity_info::(x); + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = ::RuntimeOrigin::from(RawOrigin::Signed(caller)); + Identity::::set_identity(caller_origin, Box::new(info))?; + }; + + Identity::::request_judgement(caller_origin, r - 1, 10u32.into())?; + }: _(RawOrigin::Signed(caller.clone()), r - 1) + verify { + assert_last_event::(Event::::JudgementUnrequested { who: caller, registrar_index: r-1 }.into()); + } + + set_fee { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + + let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; + + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().fee == 0u32.into(), "Fee already set."); + }: _(RawOrigin::Signed(caller), r, 100u32.into()) + verify { + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().fee == 100u32.into(), "Fee not changed."); + } + + set_account_id { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; + + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().account == caller, "id not set."); + let new_account = T::Lookup::unlookup(account("new", 0, SEED)); + }: _(RawOrigin::Signed(caller), r, new_account) + verify { + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().account == account("new", 0, SEED), "id not changed."); + } + + set_fields { + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; + + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; + let fields = IdentityFields( + IdentityField::Display | IdentityField::Legal | IdentityField::Web | IdentityField::Riot + | IdentityField::Email | IdentityField::PgpFingerprint | IdentityField::Image | IdentityField::Twitter + ); + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().fields == Default::default(), "fields already set."); + }: _(RawOrigin::Signed(caller), r, fields) + verify { + let registrars = Registrars::::get(); + ensure!(registrars[r as usize].as_ref().unwrap().fields != Default::default(), "fields not set."); + } + + provide_judgement { + // The user + let user: T::AccountId = account("user", r, SEED); + let user_origin = ::RuntimeOrigin::from(RawOrigin::Signed(user.clone())); + let user_lookup = ::unlookup(user.clone()); + let _ = T::Currency::make_free_balance_be(&user, BalanceOf::::max_value()); + + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + + let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; + let x in 0 .. T::MaxAdditionalFields::get(); + + let info = create_identity_info::(x); + let info_hash = T::Hashing::hash_of(&info); + Identity::::set_identity(user_origin.clone(), Box::new(info))?; + + let registrar_origin = T::RegistrarOrigin::successful_origin(); + Identity::::add_registrar(registrar_origin, caller_lookup)?; + Identity::::request_judgement(user_origin, r, 10u32.into())?; + }: _(RawOrigin::Signed(caller), r, user_lookup, Judgement::Reasonable, info_hash) + verify { + assert_last_event::(Event::::JudgementGiven { target: user, registrar_index: r }.into()) + } + + kill_identity { + let r in 1 .. T::MaxRegistrars::get() => add_registrars::(r)?; + let s in 0 .. T::MaxSubAccounts::get(); + let x in 0 .. T::MaxAdditionalFields::get(); + + let target: T::AccountId = account("target", 0, SEED); + let target_origin: ::RuntimeOrigin = RawOrigin::Signed(target.clone()).into(); + let target_lookup = T::Lookup::unlookup(target.clone()); + let _ = T::Currency::make_free_balance_be(&target, BalanceOf::::max_value()); + + let info = create_identity_info::(x); + Identity::::set_identity(target_origin.clone(), Box::new(info.clone()))?; + let _ = add_sub_accounts::(&target, s)?; + + // User requests judgement from all the registrars, and they approve + for i in 0..r { + let registrar: T::AccountId = account("registrar", i, SEED); + let balance_to_use = T::Currency::minimum_balance() * 10u32.into(); + let _ = T::Currency::make_free_balance_be(®istrar, balance_to_use); + + Identity::::request_judgement(target_origin.clone(), i, 10u32.into())?; + Identity::::provide_judgement( + RawOrigin::Signed(registrar).into(), + i, + target_lookup.clone(), + Judgement::Reasonable, + T::Hashing::hash_of(&info), + )?; + } + ensure!(IdentityOf::::contains_key(&target), "Identity not set"); + let origin = T::ForceOrigin::successful_origin(); + }: _(origin, target_lookup) + verify { + ensure!(!IdentityOf::::contains_key(&target), "Identity not removed"); + } + + force_insert_identities { + let x in 0 .. T::MaxAdditionalFields::get(); + let n in 0..600; + use frame_benchmarking::account; + let identities = (0..n).map(|i| ( + account("caller", i, 0), + Registration::, T::MaxRegistrars, T::MaxAdditionalFields> { + judgements: Default::default(), + deposit: Default::default(), + info: create_identity_info::(x), + }, + )).collect::>(); + let origin = T::ForceOrigin::successful_origin(); + }: _(origin, identities) + + force_remove_identities { + let x in 0 .. T::MaxAdditionalFields::get(); + let n in 0..600; + use frame_benchmarking::account; + let origin = T::ForceOrigin::successful_origin(); + let identities = (0..n).map(|i| ( + account("caller", i, 0), + Registration::, T::MaxRegistrars, T::MaxAdditionalFields> { + judgements: Default::default(), + deposit: Default::default(), + info: create_identity_info::(x), + }, + )).collect::>(); + assert_ok!( + Identity::::force_insert_identities(origin.clone(), identities.clone()), + ); + let identities = identities.into_iter().map(|(acc, _)| acc).collect::>(); + }: _(origin, identities) + + add_sub { + let s in 0 .. T::MaxSubAccounts::get() - 1; + + let caller: T::AccountId = whitelisted_caller(); + let _ = add_sub_accounts::(&caller, s)?; + let sub = account("new_sub", 0, SEED); + let data = Data::Raw(vec![0; 32].try_into().unwrap()); + ensure!(SubsOf::::get(&caller).1.len() as u32 == s, "Subs not set."); + }: _(RawOrigin::Signed(caller.clone()), T::Lookup::unlookup(sub), data) + verify { + ensure!(SubsOf::::get(&caller).1.len() as u32 == s + 1, "Subs not added."); + } + + rename_sub { + let s in 1 .. T::MaxSubAccounts::get(); + + let caller: T::AccountId = whitelisted_caller(); + let (sub, _) = add_sub_accounts::(&caller, s)?.remove(0); + let data = Data::Raw(vec![1; 32].try_into().unwrap()); + ensure!(SuperOf::::get(&sub).unwrap().1 != data, "data already set"); + }: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub.clone()), data.clone()) + verify { + ensure!(SuperOf::::get(&sub).unwrap().1 == data, "data not set"); + } + + remove_sub { + let s in 1 .. T::MaxSubAccounts::get(); + + let caller: T::AccountId = whitelisted_caller(); + let (sub, _) = add_sub_accounts::(&caller, s)?.remove(0); + ensure!(SuperOf::::contains_key(&sub), "Sub doesn't exists"); + }: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub.clone())) + verify { + ensure!(!SuperOf::::contains_key(&sub), "Sub not removed"); + } + + quit_sub { + let s in 0 .. T::MaxSubAccounts::get() - 1; + + let caller: T::AccountId = whitelisted_caller(); + let sup = account("super", 0, SEED); + let _ = add_sub_accounts::(&sup, s)?; + let sup_origin = RawOrigin::Signed(sup).into(); + Identity::::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32].try_into().unwrap()))?; + ensure!(SuperOf::::contains_key(&caller), "Sub doesn't exists"); + }: _(RawOrigin::Signed(caller.clone())) + verify { + ensure!(!SuperOf::::contains_key(&caller), "Sub not removed"); + } + + impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test); +} --- /dev/null +++ b/pallets/identity/src/lib.rs @@ -0,0 +1,1166 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Identity Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! A federated naming system, allowing for multiple registrars to be added from a specified origin. +//! Registrars can set a fee to provide identity-verification service. Anyone can put forth a +//! proposed identity for a fixed deposit and ask for review by any number of registrars (paying +//! each of their fees). Registrar judgements are given as an `enum`, allowing for sophisticated, +//! multi-tier opinions. +//! +//! Some judgements are identified as *sticky*, which means they cannot be removed except by +//! complete removal of the identity, or by the registrar. Judgements are allowed to represent a +//! portion of funds that have been reserved for the registrar. +//! +//! A super-user can remove accounts and in doing so, slash the deposit. +//! +//! All accounts may also have a limited number of sub-accounts which may be specified by the owner; +//! by definition, these have equivalent ownership and each has an individual name. +//! +//! The number of registrars should be limited, and the deposit made sufficiently large, to ensure +//! no state-bloat attack is viable. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! #### For general users +//! * `set_identity` - Set the associated identity of an account; a small deposit is reserved if not +//! already taken. +//! * `clear_identity` - Remove an account's associated identity; the deposit is returned. +//! * `request_judgement` - Request a judgement from a registrar, paying a fee. +//! * `cancel_request` - Cancel the previous request for a judgement. +//! +//! #### For general users with sub-identities +//! * `set_subs` - Set the sub-accounts of an identity. +//! * `add_sub` - Add a sub-identity to an identity. +//! * `remove_sub` - Remove a sub-identity of an identity. +//! * `rename_sub` - Rename a sub-identity of an identity. +//! * `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity). +//! +//! #### For registrars +//! * `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar. +//! * `set_fields` - Set the fields that a registrar cares about in their judgements. +//! * `provide_judgement` - Provide a judgement to an identity. +//! +//! #### For super-users +//! * `add_registrar` - Add a new registrar to the system. +//! * `kill_identity` - Forcibly remove the associated identity; the deposit is lost. +//! +//! [`Call`]: ./enum.Call.html +//! [`Config`]: ./trait.Config.html + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +#[cfg(test)] +mod tests; +mod types; +pub mod weights; + +use frame_support::traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency}; +use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero}; +use sp_std::prelude::*; +pub use weights::WeightInfo; + +pub use pallet::*; +pub use types::{ + Data, IdentityField, IdentityFields, IdentityInfo, Judgement, RegistrarIndex, RegistrarInfo, + Registration, +}; + +pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; +type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, +>>::NegativeImbalance; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The currency trait. + type Currency: ReservableCurrency; + + /// The amount held on deposit for a registered identity + #[pallet::constant] + type BasicDeposit: Get>; + + /// The amount held on deposit per additional field for a registered identity. + #[pallet::constant] + type FieldDeposit: Get>; + + /// The amount held on deposit for a registered subaccount. This should account for the fact + /// that one storage item's value will increase by the size of an account ID, and there will + /// be another trie item whose value is the size of an account ID plus 32 bytes. + #[pallet::constant] + type SubAccountDeposit: Get>; + + /// The maximum number of sub-accounts allowed per identified account. + #[pallet::constant] + type MaxSubAccounts: Get; + + /// Maximum number of additional fields that may be stored in an ID. Needed to bound the I/O + /// required to access an identity, but can be pretty high. + #[pallet::constant] + type MaxAdditionalFields: Get; + + /// Maxmimum number of registrars allowed in the system. Needed to bound the complexity + /// of, e.g., updating judgements. + #[pallet::constant] + type MaxRegistrars: Get; + + /// What to do with slashed funds. + type Slashed: OnUnbalanced>; + + /// The origin which may forcibly set or remove a name. Root can always do this. + type ForceOrigin: EnsureOrigin; + + /// The origin which may add or remove registrars. Root can always do this. + type RegistrarOrigin: EnsureOrigin; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + /// Information that is pertinent to identify the entity behind an account. + /// + /// TWOX-NOTE: OK ― `AccountId` is a secure hash. + #[pallet::storage] + #[pallet::getter(fn identity)] + pub(super) type IdentityOf = StorageMap< + _, + Twox64Concat, + T::AccountId, + Registration, T::MaxRegistrars, T::MaxAdditionalFields>, + OptionQuery, + >; + + /// The super-identity of an alternative "sub" identity together with its name, within that + /// context. If the account is not some other account's sub-identity, then just `None`. + #[pallet::storage] + #[pallet::getter(fn super_of)] + pub(super) type SuperOf = + StorageMap<_, Blake2_128Concat, T::AccountId, (T::AccountId, Data), OptionQuery>; + + /// Alternative "sub" identities of this account. + /// + /// The first item is the deposit, the second is a vector of the accounts. + /// + /// TWOX-NOTE: OK ― `AccountId` is a secure hash. + #[pallet::storage] + #[pallet::getter(fn subs_of)] + pub(super) type SubsOf = StorageMap< + _, + Twox64Concat, + T::AccountId, + (BalanceOf, BoundedVec), + ValueQuery, + >; + + /// The set of registrars. Not expected to get very big as can only be added through a + /// special origin (likely a council motion). + /// + /// The index into this can be cast to `RegistrarIndex` to get a valid value. + #[pallet::storage] + #[pallet::getter(fn registrars)] + pub(super) type Registrars = StorageValue< + _, + BoundedVec, T::AccountId>>, T::MaxRegistrars>, + ValueQuery, + >; + + #[pallet::error] + pub enum Error { + /// Too many subs-accounts. + TooManySubAccounts, + /// Account isn't found. + NotFound, + /// Account isn't named. + NotNamed, + /// Empty index. + EmptyIndex, + /// Fee is changed. + FeeChanged, + /// No identity found. + NoIdentity, + /// Sticky judgement. + StickyJudgement, + /// Judgement given. + JudgementGiven, + /// Invalid judgement. + InvalidJudgement, + /// The index is invalid. + InvalidIndex, + /// The target is invalid. + InvalidTarget, + /// Too many additional fields. + TooManyFields, + /// Maximum amount of registrars reached. Cannot add any more. + TooManyRegistrars, + /// Account ID is already named. + AlreadyClaimed, + /// Sender is not a sub-account. + NotSub, + /// Sub-account isn't owned by sender. + NotOwned, + /// The provided judgement was for a different identity. + JudgementForDifferentIdentity, + /// Error that occurs when there is an issue paying for judgement. + JudgementPaymentFailed, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A name was set or reset (which will remove all judgements). + IdentitySet { who: T::AccountId }, + /// A name was cleared, and the given balance returned. + IdentityCleared { + who: T::AccountId, + deposit: BalanceOf, + }, + /// A name was removed and the given balance slashed. + IdentityKilled { + who: T::AccountId, + deposit: BalanceOf, + }, + /// A number of identities and associated info were forcibly inserted. + IdentitiesInserted { amount: u32 }, + /// A number of identities and all associated info were forcibly removed. + IdentitiesRemoved { amount: u32 }, + /// A judgement was asked from a registrar. + JudgementRequested { + who: T::AccountId, + registrar_index: RegistrarIndex, + }, + /// A judgement request was retracted. + JudgementUnrequested { + who: T::AccountId, + registrar_index: RegistrarIndex, + }, + /// A judgement was given by a registrar. + JudgementGiven { + target: T::AccountId, + registrar_index: RegistrarIndex, + }, + /// A registrar was added. + RegistrarAdded { registrar_index: RegistrarIndex }, + /// A sub-identity was added to an identity and the deposit paid. + SubIdentityAdded { + sub: T::AccountId, + main: T::AccountId, + deposit: BalanceOf, + }, + /// A sub-identity was removed from an identity and the deposit freed. + SubIdentityRemoved { + sub: T::AccountId, + main: T::AccountId, + deposit: BalanceOf, + }, + /// A sub-identity was cleared, and the given deposit repatriated from the + /// main identity account to the sub-identity account. + SubIdentityRevoked { + sub: T::AccountId, + main: T::AccountId, + deposit: BalanceOf, + }, + } + + #[pallet::call] + /// Identity pallet declaration. + impl Pallet { + /// Add a registrar to the system. + /// + /// The dispatch origin for this call must be `T::RegistrarOrigin`. + /// + /// - `account`: the account of the registrar. + /// + /// Emits `RegistrarAdded` if successful. + /// + /// # + /// - `O(R)` where `R` registrar-count (governance-bounded and code-bounded). + /// - One storage mutation (codec `O(R)`). + /// - One event. + /// # + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))] + pub fn add_registrar( + origin: OriginFor, + account: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + T::RegistrarOrigin::ensure_origin(origin)?; + let account = T::Lookup::lookup(account)?; + + let (i, registrar_count) = >::try_mutate( + |registrars| -> Result<(RegistrarIndex, usize), DispatchError> { + registrars + .try_push(Some(RegistrarInfo { + account, + fee: Zero::zero(), + fields: Default::default(), + })) + .map_err(|_| Error::::TooManyRegistrars)?; + Ok(((registrars.len() - 1) as RegistrarIndex, registrars.len())) + }, + )?; + + Self::deposit_event(Event::RegistrarAdded { registrar_index: i }); + + Ok(Some(T::WeightInfo::add_registrar(registrar_count as u32)).into()) + } + + /// Set an account's identity information and reserve the appropriate deposit. + /// + /// If the account already has identity information, the deposit is taken as part payment + /// for the new deposit. + /// + /// The dispatch origin for this call must be _Signed_. + /// + /// - `info`: The identity information. + /// + /// Emits `IdentitySet` if successful. + /// + /// # + /// - `O(X + X' + R)` + /// - where `X` additional-field-count (deposit-bounded and code-bounded) + /// - where `R` judgements-count (registrar-count-bounded) + /// - One balance reserve operation. + /// - One storage mutation (codec-read `O(X' + R)`, codec-write `O(X + R)`). + /// - One event. + /// # + #[pallet::call_index(1)] + #[pallet::weight( T::WeightInfo::set_identity( + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X + ))] + pub fn set_identity( + origin: OriginFor, + info: Box>, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + let extra_fields = info.additional.len() as u32; + ensure!( + extra_fields <= T::MaxAdditionalFields::get(), + Error::::TooManyFields + ); + let fd = >::from(extra_fields) * T::FieldDeposit::get(); + + let mut id = match >::get(&sender) { + Some(mut id) => { + // Only keep non-positive judgements. + id.judgements.retain(|j| j.1.is_sticky()); + id.info = *info; + id + } + None => Registration { + info: *info, + judgements: BoundedVec::default(), + deposit: Zero::zero(), + }, + }; + + let old_deposit = id.deposit; + id.deposit = T::BasicDeposit::get() + fd; + if id.deposit > old_deposit { + T::Currency::reserve(&sender, id.deposit - old_deposit)?; + } + if old_deposit > id.deposit { + let err_amount = T::Currency::unreserve(&sender, old_deposit - id.deposit); + debug_assert!(err_amount.is_zero()); + } + + let judgements = id.judgements.len(); + >::insert(&sender, id); + Self::deposit_event(Event::IdentitySet { who: sender }); + + Ok(Some(T::WeightInfo::set_identity( + judgements as u32, // R + extra_fields, // X + )) + .into()) + } + + /// Set the sub-accounts of the sender. + /// + /// Payment: Any aggregate balance reserved by previous `set_subs` calls will be returned + /// and an amount `SubAccountDeposit` will be reserved for each item in `subs`. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// identity. + /// + /// - `subs`: The identity's (new) sub-accounts. + /// + /// # + /// - `O(P + S)` + /// - where `P` old-subs-count (hard- and deposit-bounded). + /// - where `S` subs-count (hard- and deposit-bounded). + /// - At most one balance operations. + /// - DB: + /// - `P + S` storage mutations (codec complexity `O(1)`) + /// - One storage read (codec complexity `O(P)`). + /// - One storage write (codec complexity `O(S)`). + /// - One storage-exists (`IdentityOf::contains_key`). + /// # + // TODO: This whole extrinsic screams "not optimized". For example we could + // filter any overlap between new and old subs, and avoid reading/writing + // to those values... We could also ideally avoid needing to write to + // N storage items for N sub accounts. Right now the weight on this function + // is a large overestimate due to the fact that it could potentially write + // to 2 x T::MaxSubAccounts::get(). + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get()) // P: Assume max sub accounts removed. + .saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32)) // S: Assume all subs are new. + )] + pub fn set_subs( + origin: OriginFor, + subs: Vec<(T::AccountId, Data)>, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + ensure!(>::contains_key(&sender), Error::::NotFound); + ensure!( + subs.len() <= T::MaxSubAccounts::get() as usize, + Error::::TooManySubAccounts + ); + + let (old_deposit, old_ids) = >::get(&sender); + let new_deposit = T::SubAccountDeposit::get() * >::from(subs.len() as u32); + + let not_other_sub = subs + .iter() + .filter_map(|i| SuperOf::::get(&i.0)) + .all(|i| i.0 == sender); + ensure!(not_other_sub, Error::::AlreadyClaimed); + + if old_deposit < new_deposit { + T::Currency::reserve(&sender, new_deposit - old_deposit)?; + } else if old_deposit > new_deposit { + let err_amount = T::Currency::unreserve(&sender, old_deposit - new_deposit); + debug_assert!(err_amount.is_zero()); + } + // do nothing if they're equal. + + for s in old_ids.iter() { + >::remove(s); + } + let mut ids = BoundedVec::::default(); + for (id, name) in subs { + >::insert(&id, (sender.clone(), name)); + ids.try_push(id) + .expect("subs length is less than T::MaxSubAccounts; qed"); + } + let new_subs = ids.len(); + + if ids.is_empty() { + >::remove(&sender); + } else { + >::insert(&sender, (new_deposit, ids)); + } + + Ok(Some( + T::WeightInfo::set_subs_old(old_ids.len() as u32) // P: Real number of old accounts removed. + // S: New subs added + .saturating_add(T::WeightInfo::set_subs_new(new_subs as u32)), + ) + .into()) + } + + /// Clear an account's identity info and all sub-accounts and return all deposits. + /// + /// Payment: All reserved balances on the account are returned. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// identity. + /// + /// Emits `IdentityCleared` if successful. + /// + /// # + /// - `O(R + S + X)` + /// - where `R` registrar-count (governance-bounded). + /// - where `S` subs-count (hard- and deposit-bounded). + /// - where `X` additional-field-count (deposit-bounded and code-bounded). + /// - One balance-unreserve operation. + /// - `2` storage reads and `S + 2` storage deletions. + /// - One event. + /// # + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::clear_identity( + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X + ))] + pub fn clear_identity(origin: OriginFor) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + + let (subs_deposit, sub_ids) = >::take(&sender); + let id = >::take(&sender).ok_or(Error::::NotNamed)?; + let deposit = id.total_deposit() + subs_deposit; + for sub in sub_ids.iter() { + >::remove(sub); + } + + let err_amount = T::Currency::unreserve(&sender, deposit); + debug_assert!(err_amount.is_zero()); + + Self::deposit_event(Event::IdentityCleared { + who: sender, + deposit, + }); + + Ok(Some(T::WeightInfo::clear_identity( + id.judgements.len() as u32, // R + sub_ids.len() as u32, // S + id.info.additional.len() as u32, // X + )) + .into()) + } + + /// Request a judgement from a registrar. + /// + /// Payment: At most `max_fee` will be reserved for payment to the registrar if judgement + /// given. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a + /// registered identity. + /// + /// - `reg_index`: The index of the registrar whose judgement is requested. + /// - `max_fee`: The maximum fee that may be paid. This should just be auto-populated as: + /// + /// ```nocompile + /// Self::registrars().get(reg_index).unwrap().fee + /// ``` + /// + /// Emits `JudgementRequested` if successful. + /// + /// # + /// - `O(R + X)`. + /// - One balance-reserve operation. + /// - Storage: 1 read `O(R)`, 1 mutate `O(X + R)`. + /// - One event. + /// # + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::request_judgement( + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X + ))] + pub fn request_judgement( + origin: OriginFor, + #[pallet::compact] reg_index: RegistrarIndex, + #[pallet::compact] max_fee: BalanceOf, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + let registrars = >::get(); + let registrar = registrars + .get(reg_index as usize) + .and_then(Option::as_ref) + .ok_or(Error::::EmptyIndex)?; + ensure!(max_fee >= registrar.fee, Error::::FeeChanged); + let mut id = >::get(&sender).ok_or(Error::::NoIdentity)?; + + let item = (reg_index, Judgement::FeePaid(registrar.fee)); + match id.judgements.binary_search_by_key(®_index, |x| x.0) { + Ok(i) => { + if id.judgements[i].1.is_sticky() { + return Err(Error::::StickyJudgement.into()); + } else { + id.judgements[i] = item + } + } + Err(i) => id + .judgements + .try_insert(i, item) + .map_err(|_| Error::::TooManyRegistrars)?, + } + + T::Currency::reserve(&sender, registrar.fee)?; + + let judgements = id.judgements.len(); + let extra_fields = id.info.additional.len(); + >::insert(&sender, id); + + Self::deposit_event(Event::JudgementRequested { + who: sender, + registrar_index: reg_index, + }); + + Ok(Some(T::WeightInfo::request_judgement( + judgements as u32, + extra_fields as u32, + )) + .into()) + } + + /// Cancel a previous request. + /// + /// Payment: A previously reserved deposit is returned on success. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a + /// registered identity. + /// + /// - `reg_index`: The index of the registrar whose judgement is no longer requested. + /// + /// Emits `JudgementUnrequested` if successful. + /// + /// # + /// - `O(R + X)`. + /// - One balance-reserve operation. + /// - One storage mutation `O(R + X)`. + /// - One event + /// # + #[pallet::call_index(5)] + #[pallet::weight(T::WeightInfo::cancel_request( + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X + ))] + pub fn cancel_request( + origin: OriginFor, + reg_index: RegistrarIndex, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + let mut id = >::get(&sender).ok_or(Error::::NoIdentity)?; + + let pos = id + .judgements + .binary_search_by_key(®_index, |x| x.0) + .map_err(|_| Error::::NotFound)?; + let fee = if let Judgement::FeePaid(fee) = id.judgements.remove(pos).1 { + fee + } else { + return Err(Error::::JudgementGiven.into()); + }; + + let err_amount = T::Currency::unreserve(&sender, fee); + debug_assert!(err_amount.is_zero()); + let judgements = id.judgements.len(); + let extra_fields = id.info.additional.len(); + >::insert(&sender, id); + + Self::deposit_event(Event::JudgementUnrequested { + who: sender, + registrar_index: reg_index, + }); + + Ok(Some(T::WeightInfo::cancel_request( + judgements as u32, + extra_fields as u32, + )) + .into()) + } + + /// Set the fee required for a judgement to be requested from a registrar. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must be the account + /// of the registrar whose index is `index`. + /// + /// - `index`: the index of the registrar whose fee is to be set. + /// - `fee`: the new fee. + /// + /// # + /// - `O(R)`. + /// - One storage mutation `O(R)`. + /// - Benchmark: 7.315 + R * 0.329 µs (min squares analysis) + /// # + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R + pub fn set_fee( + origin: OriginFor, + #[pallet::compact] index: RegistrarIndex, + #[pallet::compact] fee: BalanceOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let registrars = >::mutate(|rs| -> Result { + rs.get_mut(index as usize) + .and_then(|x| x.as_mut()) + .and_then(|r| { + if r.account == who { + r.fee = fee; + Some(()) + } else { + None + } + }) + .ok_or_else(|| DispatchError::from(Error::::InvalidIndex))?; + Ok(rs.len()) + })?; + Ok(Some(T::WeightInfo::set_fee(registrars as u32)).into()) // R + } + + /// Change the account associated with a registrar. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must be the account + /// of the registrar whose index is `index`. + /// + /// - `index`: the index of the registrar whose fee is to be set. + /// - `new`: the new account ID. + /// + /// # + /// - `O(R)`. + /// - One storage mutation `O(R)`. + /// - Benchmark: 8.823 + R * 0.32 µs (min squares analysis) + /// # + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R + pub fn set_account_id( + origin: OriginFor, + #[pallet::compact] index: RegistrarIndex, + new: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let new = T::Lookup::lookup(new)?; + + let registrars = >::mutate(|rs| -> Result { + rs.get_mut(index as usize) + .and_then(|x| x.as_mut()) + .and_then(|r| { + if r.account == who { + r.account = new; + Some(()) + } else { + None + } + }) + .ok_or_else(|| DispatchError::from(Error::::InvalidIndex))?; + Ok(rs.len()) + })?; + Ok(Some(T::WeightInfo::set_account_id(registrars as u32)).into()) // R + } + + /// Set the field information for a registrar. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must be the account + /// of the registrar whose index is `index`. + /// + /// - `index`: the index of the registrar whose fee is to be set. + /// - `fields`: the fields that the registrar concerns themselves with. + /// + /// # + /// - `O(R)`. + /// - One storage mutation `O(R)`. + /// - Benchmark: 7.464 + R * 0.325 µs (min squares analysis) + /// # + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R + pub fn set_fields( + origin: OriginFor, + #[pallet::compact] index: RegistrarIndex, + fields: IdentityFields, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let registrars = >::mutate(|rs| -> Result { + rs.get_mut(index as usize) + .and_then(|x| x.as_mut()) + .and_then(|r| { + if r.account == who { + r.fields = fields; + Some(()) + } else { + None + } + }) + .ok_or_else(|| DispatchError::from(Error::::InvalidIndex))?; + Ok(rs.len()) + })?; + Ok(Some(T::WeightInfo::set_fields( + registrars as u32, // R + )) + .into()) + } + + /// Provide a judgement for an account's identity. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must be the account + /// of the registrar whose index is `reg_index`. + /// + /// - `reg_index`: the index of the registrar whose judgement is being made. + /// - `target`: the account whose identity the judgement is upon. This must be an account + /// with a registered identity. + /// - `judgement`: the judgement of the registrar of index `reg_index` about `target`. + /// - `identity`: The hash of the [`IdentityInfo`] for that the judgement is provided. + /// + /// Emits `JudgementGiven` if successful. + /// + /// # + /// - `O(R + X)`. + /// - One balance-transfer operation. + /// - Up to one account-lookup operation. + /// - Storage: 1 read `O(R)`, 1 mutate `O(R + X)`. + /// - One event. + /// # + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::provide_judgement( + T::MaxRegistrars::get(), // R + T::MaxAdditionalFields::get(), // X + ))] + pub fn provide_judgement( + origin: OriginFor, + #[pallet::compact] reg_index: RegistrarIndex, + target: AccountIdLookupOf, + judgement: Judgement>, + identity: T::Hash, + ) -> DispatchResultWithPostInfo { + let sender = ensure_signed(origin)?; + let target = T::Lookup::lookup(target)?; + ensure!(!judgement.has_deposit(), Error::::InvalidJudgement); + >::get() + .get(reg_index as usize) + .and_then(Option::as_ref) + .filter(|r| r.account == sender) + .ok_or(Error::::InvalidIndex)?; + let mut id = >::get(&target).ok_or(Error::::InvalidTarget)?; + + if T::Hashing::hash_of(&id.info) != identity { + return Err(Error::::JudgementForDifferentIdentity.into()); + } + + let item = (reg_index, judgement); + match id.judgements.binary_search_by_key(®_index, |x| x.0) { + Ok(position) => { + if let Judgement::FeePaid(fee) = id.judgements[position].1 { + T::Currency::repatriate_reserved( + &target, + &sender, + fee, + BalanceStatus::Free, + ) + .map_err(|_| Error::::JudgementPaymentFailed)?; + } + id.judgements[position] = item + } + Err(position) => id + .judgements + .try_insert(position, item) + .map_err(|_| Error::::TooManyRegistrars)?, + } + + let judgements = id.judgements.len(); + let extra_fields = id.info.additional.len(); + >::insert(&target, id); + Self::deposit_event(Event::JudgementGiven { + target, + registrar_index: reg_index, + }); + + Ok(Some(T::WeightInfo::provide_judgement( + judgements as u32, + extra_fields as u32, + )) + .into()) + } + + /// Remove an account's identity and sub-account information and slash the deposits. + /// + /// Payment: Reserved balances from `set_subs` and `set_identity` are slashed and handled by + /// `Slash`. Verification request deposits are not returned; they should be cancelled + /// manually using `cancel_request`. + /// + /// The dispatch origin for this call must match `T::ForceOrigin`. + /// + /// - `target`: the account whose identity the judgement is upon. This must be an account + /// with a registered identity. + /// + /// Emits `IdentityKilled` if successful. + /// + /// # + /// - `O(R + S + X)`. + /// - One balance-reserve operation. + /// - `S + 2` storage mutations. + /// - One event. + /// # + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::kill_identity( + T::MaxRegistrars::get(), // R + T::MaxSubAccounts::get(), // S + T::MaxAdditionalFields::get(), // X + ))] + pub fn kill_identity( + origin: OriginFor, + target: AccountIdLookupOf, + ) -> DispatchResultWithPostInfo { + T::ForceOrigin::ensure_origin(origin)?; + + // Figure out who we're meant to be clearing. + let target = T::Lookup::lookup(target)?; + // Grab their deposit (and check that they have one). + let (subs_deposit, sub_ids) = >::take(&target); + let id = >::take(&target).ok_or(Error::::NotNamed)?; + let deposit = id.total_deposit() + subs_deposit; + for sub in sub_ids.iter() { + >::remove(sub); + } + // Slash their deposit from them. + T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0); + + Self::deposit_event(Event::IdentityKilled { + who: target, + deposit, + }); + + Ok(Some(T::WeightInfo::kill_identity( + id.judgements.len() as u32, // R + sub_ids.len() as u32, // S + id.info.additional.len() as u32, // X + )) + .into()) + } + + /// Add the given account to the sender's subs. + /// + /// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated + /// to the sender. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// sub identity of `sub`. + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::add_sub(T::MaxSubAccounts::get()))] + pub fn add_sub( + origin: OriginFor, + sub: AccountIdLookupOf, + data: Data, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let sub = T::Lookup::lookup(sub)?; + ensure!( + IdentityOf::::contains_key(&sender), + Error::::NoIdentity + ); + + // Check if it's already claimed as sub-identity. + ensure!( + !SuperOf::::contains_key(&sub), + Error::::AlreadyClaimed + ); + + SubsOf::::try_mutate(&sender, |(ref mut subs_deposit, ref mut sub_ids)| { + // Ensure there is space and that the deposit is paid. + ensure!( + sub_ids.len() < T::MaxSubAccounts::get() as usize, + Error::::TooManySubAccounts + ); + let deposit = T::SubAccountDeposit::get(); + T::Currency::reserve(&sender, deposit)?; + + SuperOf::::insert(&sub, (sender.clone(), data)); + sub_ids + .try_push(sub.clone()) + .expect("sub ids length checked above; qed"); + *subs_deposit = subs_deposit.saturating_add(deposit); + + Self::deposit_event(Event::SubIdentityAdded { + sub, + main: sender.clone(), + deposit, + }); + Ok(()) + }) + } + + /// Alter the associated name of the given sub-account. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// sub identity of `sub`. + #[pallet::call_index(12)] + #[pallet::weight(T::WeightInfo::rename_sub(T::MaxSubAccounts::get()))] + pub fn rename_sub( + origin: OriginFor, + sub: AccountIdLookupOf, + data: Data, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + let sub = T::Lookup::lookup(sub)?; + ensure!( + IdentityOf::::contains_key(&sender), + Error::::NoIdentity + ); + ensure!( + SuperOf::::get(&sub).map_or(false, |x| x.0 == sender), + Error::::NotOwned + ); + SuperOf::::insert(&sub, (sender, data)); + Ok(()) + } + + /// Remove the given account from the sender's subs. + /// + /// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated + /// to the sender. + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// sub identity of `sub`. + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::remove_sub(T::MaxSubAccounts::get()))] + pub fn remove_sub(origin: OriginFor, sub: AccountIdLookupOf) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!( + IdentityOf::::contains_key(&sender), + Error::::NoIdentity + ); + let sub = T::Lookup::lookup(sub)?; + let (sup, _) = SuperOf::::get(&sub).ok_or(Error::::NotSub)?; + ensure!(sup == sender, Error::::NotOwned); + SuperOf::::remove(&sub); + SubsOf::::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| { + sub_ids.retain(|x| x != &sub); + let deposit = T::SubAccountDeposit::get().min(*subs_deposit); + *subs_deposit -= deposit; + let err_amount = T::Currency::unreserve(&sender, deposit); + debug_assert!(err_amount.is_zero()); + Self::deposit_event(Event::SubIdentityRemoved { + sub, + main: sender, + deposit, + }); + }); + Ok(()) + } + + /// Remove the sender as a sub-account. + /// + /// Payment: Balance reserved by a previous `set_subs` call for one sub will be repatriated + /// to the sender (*not* the original depositor). + /// + /// The dispatch origin for this call must be _Signed_ and the sender must have a registered + /// super-identity. + /// + /// NOTE: This should not normally be used, but is provided in the case that the non- + /// controller of an account is maliciously registered as a sub-account. + #[pallet::call_index(14)] + #[pallet::weight(T::WeightInfo::quit_sub(T::MaxSubAccounts::get()))] + pub fn quit_sub(origin: OriginFor) -> DispatchResult { + let sender = ensure_signed(origin)?; + let (sup, _) = SuperOf::::take(&sender).ok_or(Error::::NotSub)?; + SubsOf::::mutate(&sup, |(ref mut subs_deposit, ref mut sub_ids)| { + sub_ids.retain(|x| x != &sender); + let deposit = T::SubAccountDeposit::get().min(*subs_deposit); + *subs_deposit -= deposit; + let _ = + T::Currency::repatriate_reserved(&sup, &sender, deposit, BalanceStatus::Free); + Self::deposit_event(Event::SubIdentityRevoked { + sub: sender, + main: sup.clone(), + deposit, + }); + }); + Ok(()) + } + + /// Set identities to be associated with the provided accounts as force origin. + /// + /// This is not meant to operate in tandem with the identity pallet as is, + /// and be instead used to keep identities made and verified externally, + /// forbidden from interacting with an ordinary user, since it ignores any safety mechanism. + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::force_insert_identities( + T::MaxAdditionalFields::get(), // X + identities.len() as u32, // N + ))] + pub fn force_insert_identities( + origin: OriginFor, + identities: Vec<( + T::AccountId, + Registration, T::MaxRegistrars, T::MaxAdditionalFields>, + )>, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + for identity in identities.clone() { + IdentityOf::::insert(identity.0, identity.1); + } + Self::deposit_event(Event::IdentitiesInserted { + amount: identities.len() as u32, + }); + Ok(()) + } + + /// Remove identities associated with the provided accounts as force origin. + /// + /// This is not meant to operate in tandem with the identity pallet as is, + /// and be instead used to keep identities made and verified externally, + /// forbidden from interacting with an ordinary user, since it ignores any safety mechanism. + #[pallet::call_index(16)] + #[pallet::weight(T::WeightInfo::force_remove_identities( + T::MaxAdditionalFields::get(), // X + identities.len() as u32, // N + ))] + pub fn force_remove_identities( + origin: OriginFor, + identities: Vec, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + for identity in identities.clone() { + IdentityOf::::set(identity, None); + } + Self::deposit_event(Event::IdentitiesRemoved { + amount: identities.len() as u32, + }); + Ok(()) + } + } +} + +impl Pallet { + /// Get the subs of an account. + pub fn subs(who: &T::AccountId) -> Vec<(T::AccountId, Data)> { + SubsOf::::get(who) + .1 + .into_iter() + .filter_map(|a| SuperOf::::get(&a).map(|x| (a, x.1))) + .collect() + } + + /// Check if the account has corresponding identity information by the identity field. + pub fn has_identity(who: &T::AccountId, fields: u64) -> bool { + IdentityOf::::get(who).map_or(false, |registration| { + (registration.info.fields().0.bits() & fields) == fields + }) + } +} --- /dev/null +++ b/pallets/identity/src/tests.rs @@ -0,0 +1,755 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for Identity Pallet + +use super::*; +use crate as pallet_identity; + +use codec::{Decode, Encode}; +use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, parameter_types, + traits::{ConstU32, ConstU64, EitherOfDiverse}, + BoundedVec, +}; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BadOrigin, BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Identity: pallet_identity::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(frame_support::weights::Weight::from_ref_time(1024)); +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type WeightInfo = (); +} + +parameter_types! { + pub const MaxAdditionalFields: u32 = 2; + pub const MaxRegistrars: u32 = 20; +} + +ord_parameter_types! { + pub const One: u64 = 1; + pub const Two: u64 = 2; +} +type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; +type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; +impl pallet_identity::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type Slashed = (); + type BasicDeposit = ConstU64<10>; + type FieldDeposit = ConstU64<10>; + type SubAccountDeposit = ConstU64<10>; + type MaxSubAccounts = ConstU32<2>; + type MaxAdditionalFields = MaxAdditionalFields; + type MaxRegistrars = MaxRegistrars; + type RegistrarOrigin = EnsureOneOrRoot; + type ForceOrigin = EnsureTwoOrRoot; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10), (2, 10), (3, 10), (10, 100), (20, 100), (30, 100)], + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() +} + +fn ten() -> IdentityInfo { + IdentityInfo { + display: Data::Raw(b"ten".to_vec().try_into().unwrap()), + legal: Data::Raw(b"The Right Ordinal Ten, Esq.".to_vec().try_into().unwrap()), + ..Default::default() + } +} + +fn twenty() -> IdentityInfo { + IdentityInfo { + display: Data::Raw(b"twenty".to_vec().try_into().unwrap()), + legal: Data::Raw( + b"The Right Ordinal Twenty, Esq." + .to_vec() + .try_into() + .unwrap(), + ), + ..Default::default() + } +} + +#[test] +fn editing_subaccounts_should_work() { + new_test_ext().execute_with(|| { + let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); + + assert_noop!( + Identity::add_sub(RuntimeOrigin::signed(10), 20, data(1)), + Error::::NoIdentity + ); + + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + + // first sub account + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); + assert_eq!(SuperOf::::get(1), Some((10, data(1)))); + assert_eq!(Balances::free_balance(10), 80); + + // second sub account + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 2, data(2))); + assert_eq!(SuperOf::::get(1), Some((10, data(1)))); + assert_eq!(SuperOf::::get(2), Some((10, data(2)))); + assert_eq!(Balances::free_balance(10), 70); + + // third sub account is too many + assert_noop!( + Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3)), + Error::::TooManySubAccounts + ); + + // rename first sub account + assert_ok!(Identity::rename_sub(RuntimeOrigin::signed(10), 1, data(11))); + assert_eq!(SuperOf::::get(1), Some((10, data(11)))); + assert_eq!(SuperOf::::get(2), Some((10, data(2)))); + assert_eq!(Balances::free_balance(10), 70); + + // remove first sub account + assert_ok!(Identity::remove_sub(RuntimeOrigin::signed(10), 1)); + assert_eq!(SuperOf::::get(1), None); + assert_eq!(SuperOf::::get(2), Some((10, data(2)))); + assert_eq!(Balances::free_balance(10), 80); + + // add third sub account + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3))); + assert_eq!(SuperOf::::get(1), None); + assert_eq!(SuperOf::::get(2), Some((10, data(2)))); + assert_eq!(SuperOf::::get(3), Some((10, data(3)))); + assert_eq!(Balances::free_balance(10), 70); + }); +} + +#[test] +fn resolving_subaccount_ownership_works() { + new_test_ext().execute_with(|| { + let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); + + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(20), + Box::new(twenty()) + )); + + // 10 claims 1 as a subaccount + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); + assert_eq!(Balances::free_balance(1), 10); + assert_eq!(Balances::free_balance(10), 80); + assert_eq!(Balances::reserved_balance(10), 20); + // 20 cannot claim 1 now + assert_noop!( + Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1)), + Error::::AlreadyClaimed + ); + // 1 wants to be with 20 so it quits from 10 + assert_ok!(Identity::quit_sub(RuntimeOrigin::signed(1))); + // 1 gets the 10 that 10 paid. + assert_eq!(Balances::free_balance(1), 20); + assert_eq!(Balances::free_balance(10), 80); + assert_eq!(Balances::reserved_balance(10), 10); + // 20 can claim 1 now + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1))); + }); +} + +#[test] +fn trailing_zeros_decodes_into_default_data() { + let encoded = Data::Raw(b"Hello".to_vec().try_into().unwrap()).encode(); + assert!(<(Data, Data)>::decode(&mut &encoded[..]).is_err()); + let input = &mut &encoded[..]; + let (a, b) = <(Data, Data)>::decode(&mut AppendZerosInput::new(input)).unwrap(); + assert_eq!(a, Data::Raw(b"Hello".to_vec().try_into().unwrap())); + assert_eq!(b, Data::None); +} + +#[test] +fn adding_registrar_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let fields = IdentityFields(IdentityField::Display | IdentityField::Legal); + assert_ok!(Identity::set_fields(RuntimeOrigin::signed(3), 0, fields)); + assert_eq!( + Identity::registrars(), + vec![Some(RegistrarInfo { + account: 3, + fee: 10, + fields + })] + ); + }); +} + +#[test] +fn amount_of_registrars_is_limited() { + new_test_ext().execute_with(|| { + for i in 1..MaxRegistrars::get() + 1 { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), i as u64)); + } + let last_registrar = MaxRegistrars::get() as u64 + 1; + assert_noop!( + Identity::add_registrar(RuntimeOrigin::signed(1), last_registrar), + Error::::TooManyRegistrars + ); + }); +} + +#[test] +fn registration_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let mut three_fields = ten(); + three_fields + .additional + .try_push(Default::default()) + .unwrap(); + three_fields + .additional + .try_push(Default::default()) + .unwrap(); + assert!(three_fields + .additional + .try_push(Default::default()) + .is_err()); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_eq!(Identity::identity(10).unwrap().info, ten()); + assert_eq!(Balances::free_balance(10), 90); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); + assert_eq!(Balances::free_balance(10), 100); + assert_noop!( + Identity::clear_identity(RuntimeOrigin::signed(10)), + Error::::NotNamed + ); + }); +} + +#[test] +fn uninvited_judgement_should_work() { + new_test_ext().execute_with(|| { + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), + Error::::InvalidIndex + ); + + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), + Error::::InvalidTarget + ); + + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + H256::random() + ), + Error::::JudgementForDifferentIdentity + ); + + let identity_hash = BlakeTwo256::hash_of(&ten()); + + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(10), + 0, + 10, + Judgement::Reasonable, + identity_hash + ), + Error::::InvalidIndex + ); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::FeePaid(1), + identity_hash + ), + Error::::InvalidJudgement + ); + + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + identity_hash + )); + assert_eq!( + Identity::identity(10).unwrap().judgements, + vec![(0, Judgement::Reasonable)] + ); + }); +} + +#[test] +fn clearing_judgement_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + BlakeTwo256::hash_of(&ten()) + )); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); + assert_eq!(Identity::identity(10), None); + }); +} + +#[test] +fn killing_slashing_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_noop!( + Identity::kill_identity(RuntimeOrigin::signed(1), 10), + BadOrigin + ); + assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); + assert_eq!(Identity::identity(10), None); + assert_eq!(Balances::free_balance(10), 90); + assert_noop!( + Identity::kill_identity(RuntimeOrigin::signed(2), 10), + Error::::NotNamed + ); + }); +} + +#[test] +fn setting_subaccounts_should_work() { + new_test_ext().execute_with(|| { + let mut subs = vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))]; + assert_noop!( + Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), + Error::::NotFound + ); + + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); + assert_eq!(Balances::free_balance(10), 80); + assert_eq!(Identity::subs_of(10), (10, vec![20].try_into().unwrap())); + assert_eq!( + Identity::super_of(20), + Some((10, Data::Raw(vec![40; 1].try_into().unwrap()))) + ); + + // push another item and re-set it. + subs.push((30, Data::Raw(vec![50; 1].try_into().unwrap()))); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); + assert_eq!(Balances::free_balance(10), 70); + assert_eq!( + Identity::subs_of(10), + (20, vec![20, 30].try_into().unwrap()) + ); + assert_eq!( + Identity::super_of(20), + Some((10, Data::Raw(vec![40; 1].try_into().unwrap()))) + ); + assert_eq!( + Identity::super_of(30), + Some((10, Data::Raw(vec![50; 1].try_into().unwrap()))) + ); + + // switch out one of the items and re-set. + subs[0] = (40, Data::Raw(vec![60; 1].try_into().unwrap())); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); + assert_eq!(Balances::free_balance(10), 70); // no change in the balance + assert_eq!( + Identity::subs_of(10), + (20, vec![40, 30].try_into().unwrap()) + ); + assert_eq!(Identity::super_of(20), None); + assert_eq!( + Identity::super_of(30), + Some((10, Data::Raw(vec![50; 1].try_into().unwrap()))) + ); + assert_eq!( + Identity::super_of(40), + Some((10, Data::Raw(vec![60; 1].try_into().unwrap()))) + ); + + // clear + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), vec![])); + assert_eq!(Balances::free_balance(10), 90); + assert_eq!(Identity::subs_of(10), (0, BoundedVec::default())); + assert_eq!(Identity::super_of(30), None); + assert_eq!(Identity::super_of(40), None); + + subs.push((20, Data::Raw(vec![40; 1].try_into().unwrap()))); + assert_noop!( + Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), + Error::::TooManySubAccounts + ); + }); +} + +#[test] +fn clearing_account_should_remove_subaccounts_and_refund() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::set_subs( + RuntimeOrigin::signed(10), + vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] + )); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); + assert_eq!(Balances::free_balance(10), 100); + assert!(Identity::super_of(20).is_none()); + }); +} + +#[test] +fn killing_account_should_remove_subaccounts_and_not_refund() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::set_subs( + RuntimeOrigin::signed(10), + vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] + )); + assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); + assert_eq!(Balances::free_balance(10), 80); + assert!(Identity::super_of(20).is_none()); + }); +} + +#[test] +fn cancelling_requested_judgement_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_noop!( + Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Error::::NoIdentity + ); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::request_judgement( + RuntimeOrigin::signed(10), + 0, + 10 + )); + assert_ok!(Identity::cancel_request(RuntimeOrigin::signed(10), 0)); + assert_eq!(Balances::free_balance(10), 90); + assert_noop!( + Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Error::::NotFound + ); + + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Reasonable, + BlakeTwo256::hash_of(&ten()) + )); + assert_noop!( + Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Error::::JudgementGiven + ); + }); +} + +#[test] +fn requesting_judgement_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_noop!( + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 9), + Error::::FeeChanged + ); + assert_ok!(Identity::request_judgement( + RuntimeOrigin::signed(10), + 0, + 10 + )); + // 10 for the judgement request, 10 for the identity. + assert_eq!(Balances::free_balance(10), 80); + + // Re-requesting won't work as we already paid. + assert_noop!( + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), + Error::::StickyJudgement + ); + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Erroneous, + BlakeTwo256::hash_of(&ten()) + )); + // Registrar got their payment now. + assert_eq!(Balances::free_balance(3), 20); + + // Re-requesting still won't work as it's erroneous. + assert_noop!( + Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), + Error::::StickyJudgement + ); + + // Requesting from a second registrar still works. + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 4)); + assert_ok!(Identity::request_judgement( + RuntimeOrigin::signed(10), + 1, + 10 + )); + + // Re-requesting after the judgement has been reduced works. + assert_ok!(Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::OutOfDate, + BlakeTwo256::hash_of(&ten()) + )); + assert_ok!(Identity::request_judgement( + RuntimeOrigin::signed(10), + 0, + 10 + )); + }); +} + +#[test] +fn provide_judgement_should_return_judgement_payment_failed_error() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert_ok!(Identity::request_judgement( + RuntimeOrigin::signed(10), + 0, + 10 + )); + // 10 for the judgement request, 10 for the identity. + assert_eq!(Balances::free_balance(10), 80); + + // This forces judgement payment failed error + Balances::make_free_balance_be(&3, 0); + assert_noop!( + Identity::provide_judgement( + RuntimeOrigin::signed(3), + 0, + 10, + Judgement::Erroneous, + BlakeTwo256::hash_of(&ten()) + ), + Error::::JudgementPaymentFailed + ); + }); +} + +#[test] +fn field_deposit_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(IdentityInfo { + additional: vec![ + ( + Data::Raw(b"number".to_vec().try_into().unwrap()), + Data::Raw(10u32.encode().try_into().unwrap()) + ), + ( + Data::Raw(b"text".to_vec().try_into().unwrap()), + Data::Raw(b"10".to_vec().try_into().unwrap()) + ), + ] + .try_into() + .unwrap(), + ..Default::default() + }) + )); + assert_eq!(Balances::free_balance(10), 70); + }); +} + +#[test] +fn setting_account_id_should_work() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + // account 4 cannot change the first registrar's identity since it's owned by 3. + assert_noop!( + Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3), + Error::::InvalidIndex + ); + // account 3 can, because that's the registrar's current account. + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(3), 0, 4)); + // account 4 can now, because that's their new ID. + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3)); + }); +} + +#[test] +fn test_has_identity() { + new_test_ext().execute_with(|| { + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(10), + Box::new(ten()) + )); + assert!(Identity::has_identity(&10, IdentityField::Display as u64)); + assert!(Identity::has_identity(&10, IdentityField::Legal as u64)); + assert!(Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 + )); + assert!(!Identity::has_identity( + &10, + IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64 + )); + }); +} --- /dev/null +++ b/pallets/identity/src/types.rs @@ -0,0 +1,540 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use codec::{Decode, Encode, MaxEncodedLen}; +use enumflags2::{bitflags, BitFlags}; +use frame_support::{ + traits::{ConstU32, Get}, + BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use scale_info::{ + build::{Fields, Variants}, + meta_type, Path, Type, TypeInfo, TypeParameter, +}; +use sp_runtime::{traits::Zero, RuntimeDebug}; +use sp_std::{fmt::Debug, iter::once, ops::Add, prelude::*}; + +/// Either underlying data blob if it is at most 32 bytes, or a hash of it. If the data is greater +/// than 32-bytes then it will be truncated when encoding. +/// +/// Can also be `None`. +#[derive(Clone, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)] +pub enum Data { + /// No data here. + None, + /// The data is stored directly. + Raw(BoundedVec>), + /// Only the Blake2 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + BlakeTwo256([u8; 32]), + /// Only the SHA2-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + Sha256([u8; 32]), + /// Only the Keccak-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + Keccak256([u8; 32]), + /// Only the SHA3-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + ShaThree256([u8; 32]), +} + +impl Data { + pub fn is_none(&self) -> bool { + self == &Data::None + } +} + +impl Decode for Data { + fn decode(input: &mut I) -> sp_std::result::Result { + let b = input.read_byte()?; + Ok(match b { + 0 => Data::None, + n @ 1..=33 => { + let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + .try_into() + .expect("bound checked in match arm condition; qed"); + input.read(&mut r[..])?; + Data::Raw(r) + } + 34 => Data::BlakeTwo256(<[u8; 32]>::decode(input)?), + 35 => Data::Sha256(<[u8; 32]>::decode(input)?), + 36 => Data::Keccak256(<[u8; 32]>::decode(input)?), + 37 => Data::ShaThree256(<[u8; 32]>::decode(input)?), + _ => return Err(codec::Error::from("invalid leading byte")), + }) + } +} + +impl Encode for Data { + fn encode(&self) -> Vec { + match self { + Data::None => vec![0u8; 1], + Data::Raw(ref x) => { + let l = x.len().min(32); + let mut r = vec![l as u8 + 1; l + 1]; + r[1..].copy_from_slice(&x[..l as usize]); + r + } + Data::BlakeTwo256(ref h) => once(34u8).chain(h.iter().cloned()).collect(), + Data::Sha256(ref h) => once(35u8).chain(h.iter().cloned()).collect(), + Data::Keccak256(ref h) => once(36u8).chain(h.iter().cloned()).collect(), + Data::ShaThree256(ref h) => once(37u8).chain(h.iter().cloned()).collect(), + } + } +} +impl codec::EncodeLike for Data {} + +/// Add a Raw variant with the given index and a fixed sized byte array +macro_rules! data_raw_variants { + ($variants:ident, $(($index:literal, $size:literal)),* ) => { + $variants + $( + .variant(concat!("Raw", stringify!($size)), |v| v + .index($index) + .fields(Fields::unnamed().field(|f| f.ty::<[u8; $size]>())) + ) + )* + } +} + +impl TypeInfo for Data { + type Identity = Self; + + fn type_info() -> Type { + let variants = Variants::new().variant("None", |v| v.index(0)); + + // create a variant for all sizes of Raw data from 0-32 + let variants = data_raw_variants!( + variants, + (1, 0), + (2, 1), + (3, 2), + (4, 3), + (5, 4), + (6, 5), + (7, 6), + (8, 7), + (9, 8), + (10, 9), + (11, 10), + (12, 11), + (13, 12), + (14, 13), + (15, 14), + (16, 15), + (17, 16), + (18, 17), + (19, 18), + (20, 19), + (21, 20), + (22, 21), + (23, 22), + (24, 23), + (25, 24), + (26, 25), + (27, 26), + (28, 27), + (29, 28), + (30, 29), + (31, 30), + (32, 31), + (33, 32) + ); + + let variants = variants + .variant("BlakeTwo256", |v| { + v.index(34) + .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) + }) + .variant("Sha256", |v| { + v.index(35) + .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) + }) + .variant("Keccak256", |v| { + v.index(36) + .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) + }) + .variant("ShaThree256", |v| { + v.index(37) + .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) + }); + + Type::builder() + .path(Path::new("Data", module_path!())) + .variant(variants) + } +} + +impl Default for Data { + fn default() -> Self { + Self::None + } +} + +/// An identifier for a single name registrar/identity verification service. +pub type RegistrarIndex = u32; + +/// An attestation of a registrar over how accurate some `IdentityInfo` is in describing an account. +/// +/// NOTE: Registrars may pay little attention to some fields. Registrars may want to make clear +/// which fields their attestation is relevant for by off-chain means. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub enum Judgement +{ + /// The default value; no opinion is held. + Unknown, + /// No judgement is yet in place, but a deposit is reserved as payment for providing one. + FeePaid(Balance), + /// The data appears to be reasonably acceptable in terms of its accuracy, however no in depth + /// checks (such as in-person meetings or formal KYC) have been conducted. + Reasonable, + /// The target is known directly by the registrar and the registrar can fully attest to the + /// the data's accuracy. + KnownGood, + /// The data was once good but is currently out of date. There is no malicious intent in the + /// inaccuracy. This judgement can be removed through updating the data. + OutOfDate, + /// The data is imprecise or of sufficiently low-quality to be problematic. It is not + /// indicative of malicious intent. This judgement can be removed through updating the data. + LowQuality, + /// The data is erroneous. This may be indicative of malicious intent. This cannot be removed + /// except by the registrar. + Erroneous, +} + +impl + Judgement +{ + /// Returns `true` if this judgement is indicative of a deposit being currently held. This means + /// it should not be cleared or replaced except by an operation which utilizes the deposit. + pub(crate) fn has_deposit(&self) -> bool { + matches!(self, Judgement::FeePaid(_)) + } + + /// Returns `true` if this judgement is one that should not be generally be replaced outside + /// of specialized handlers. Examples include "malicious" judgements and deposit-holding + /// judgements. + pub(crate) fn is_sticky(&self) -> bool { + matches!(self, Judgement::FeePaid(_) | Judgement::Erroneous) + } +} + +/// The fields that we use to identify the owner of an account with. Each corresponds to a field +/// in the `IdentityInfo` struct. +#[bitflags] +#[repr(u64)] +#[derive(Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub enum IdentityField { + Display = 0b0000000000000000000000000000000000000000000000000000000000000001, + Legal = 0b0000000000000000000000000000000000000000000000000000000000000010, + Web = 0b0000000000000000000000000000000000000000000000000000000000000100, + Riot = 0b0000000000000000000000000000000000000000000000000000000000001000, + Email = 0b0000000000000000000000000000000000000000000000000000000000010000, + PgpFingerprint = 0b0000000000000000000000000000000000000000000000000000000000100000, + Image = 0b0000000000000000000000000000000000000000000000000000000001000000, + Twitter = 0b0000000000000000000000000000000000000000000000000000000010000000, +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] +pub struct IdentityFields(pub BitFlags); + +impl MaxEncodedLen for IdentityFields { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +impl Eq for IdentityFields {} +impl Encode for IdentityFields { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for IdentityFields { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self( + >::from_bits(field as u64).map_err(|_| "invalid value")?, + )) + } +} +impl TypeInfo for IdentityFields { + type Identity = Self; + + fn type_info() -> Type { + Type::builder() + .path(Path::new("BitFlags", module_path!())) + .type_params(vec![TypeParameter::new( + "T", + Some(meta_type::()), + )]) + .composite(Fields::unnamed().field(|f| f.ty::().type_name("IdentityField"))) + } +} + +/// Information concerning the identity of the controller of an account. +/// +/// NOTE: This should be stored at the end of the storage item to facilitate the addition of extra +/// fields in a backwards compatible way through a specialized `Decode` impl. +#[derive( + CloneNoBound, Encode, Decode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[codec(mel_bound())] +#[cfg_attr(test, derive(frame_support::DefaultNoBound))] +#[scale_info(skip_type_params(FieldLimit))] +pub struct IdentityInfo> { + /// Additional fields of the identity that are not catered for with the struct's explicit + /// fields. + pub additional: BoundedVec<(Data, Data), FieldLimit>, + + /// A reasonable display name for the controller of the account. This should be whatever it is + /// that it is typically known as and should not be confusable with other entities, given + /// reasonable context. + /// + /// Stored as UTF-8. + pub display: Data, + + /// The full legal name in the local jurisdiction of the entity. This might be a bit + /// long-winded. + /// + /// Stored as UTF-8. + pub legal: Data, + + /// A representative website held by the controller of the account. + /// + /// NOTE: `https://` is automatically prepended. + /// + /// Stored as UTF-8. + pub web: Data, + + /// The Riot/Matrix handle held by the controller of the account. + /// + /// Stored as UTF-8. + pub riot: Data, + + /// The email address of the controller of the account. + /// + /// Stored as UTF-8. + pub email: Data, + + /// The PGP/GPG public key of the controller of the account. + pub pgp_fingerprint: Option<[u8; 20]>, + + /// A graphic image representing the controller of the account. Should be a company, + /// organization or project logo or a headshot in the case of a human. + pub image: Data, + + /// The Twitter identity. The leading `@` character may be elided. + pub twitter: Data, +} + +impl> IdentityInfo { + pub(crate) fn fields(&self) -> IdentityFields { + let mut res = >::empty(); + if !self.display.is_none() { + res.insert(IdentityField::Display); + } + if !self.legal.is_none() { + res.insert(IdentityField::Legal); + } + if !self.web.is_none() { + res.insert(IdentityField::Web); + } + if !self.riot.is_none() { + res.insert(IdentityField::Riot); + } + if !self.email.is_none() { + res.insert(IdentityField::Email); + } + if self.pgp_fingerprint.is_some() { + res.insert(IdentityField::PgpFingerprint); + } + if !self.image.is_none() { + res.insert(IdentityField::Image); + } + if !self.twitter.is_none() { + res.insert(IdentityField::Twitter); + } + IdentityFields(res) + } +} + +/// Information concerning the identity of the controller of an account. +/// +/// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a +/// backwards compatible way through a specialized `Decode` impl. +#[derive( + CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, +)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(MaxJudgements, MaxAdditionalFields))] +pub struct Registration< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, + MaxJudgements: Get, + MaxAdditionalFields: Get, +> { + /// Judgements from the registrars on this identity. Stored ordered by `RegistrarIndex`. There + /// may be only a single judgement from each registrar. + pub judgements: BoundedVec<(RegistrarIndex, Judgement), MaxJudgements>, + + /// Amount held on deposit for this information. + pub deposit: Balance, + + /// Information on the identity. + pub info: IdentityInfo, +} + +impl< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add, + MaxJudgements: Get, + MaxAdditionalFields: Get, + > Registration +{ + pub(crate) fn total_deposit(&self) -> Balance { + self.deposit + + self + .judgements + .iter() + .map(|(_, ref j)| { + if let Judgement::FeePaid(fee) = j { + *fee + } else { + Zero::zero() + } + }) + .fold(Zero::zero(), |a, i| a + i) + } +} + +impl< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, + MaxJudgements: Get, + MaxAdditionalFields: Get, + > Decode for Registration +{ + fn decode(input: &mut I) -> sp_std::result::Result { + let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?; + Ok(Self { + judgements, + deposit, + info, + }) + } +} + +/// Information concerning a registrar. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct RegistrarInfo< + Balance: Encode + Decode + Clone + Debug + Eq + PartialEq, + AccountId: Encode + Decode + Clone + Debug + Eq + PartialEq, +> { + /// The account of the registrar. + pub account: AccountId, + + /// Amount required to be given to the registrar for them to provide judgement. + pub fee: Balance, + + /// Relevant fields for this registrar. Registrar judgements are limited to attestations on + /// these fields. + pub fields: IdentityFields, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn manual_data_type_info() { + let mut registry = scale_info::Registry::new(); + let type_id = registry.register_type(&scale_info::meta_type::()); + let registry: scale_info::PortableRegistry = registry.into(); + let type_info = registry.resolve(type_id.id()).unwrap(); + + let check_type_info = |data: &Data| { + let variant_name = match data { + Data::None => "None".to_string(), + Data::BlakeTwo256(_) => "BlakeTwo256".to_string(), + Data::Sha256(_) => "Sha256".to_string(), + Data::Keccak256(_) => "Keccak256".to_string(), + Data::ShaThree256(_) => "ShaThree256".to_string(), + Data::Raw(bytes) => format!("Raw{}", bytes.len()), + }; + if let scale_info::TypeDef::Variant(variant) = type_info.type_def() { + let variant = variant + .variants() + .iter() + .find(|v| v.name() == &variant_name) + .expect(&format!("Expected to find variant {}", variant_name)); + + let field_arr_len = variant + .fields() + .first() + .and_then(|f| registry.resolve(f.ty().id())) + .map(|ty| { + if let scale_info::TypeDef::Array(arr) = ty.type_def() { + arr.len() + } else { + panic!("Should be an array type") + } + }) + .unwrap_or(0); + + let encoded = data.encode(); + assert_eq!(encoded[0], variant.index()); + assert_eq!(encoded.len() as u32 - 1, field_arr_len); + } else { + panic!("Should be a variant type") + }; + }; + + let mut data = vec![ + Data::None, + Data::BlakeTwo256(Default::default()), + Data::Sha256(Default::default()), + Data::Keccak256(Default::default()), + Data::ShaThree256(Default::default()), + ]; + + // A Raw instance for all possible sizes of the Raw data + for n in 0..32 { + data.push(Data::Raw(vec![0u8; n as usize].try_into().unwrap())) + } + + for d in data.iter() { + check_type_info(d); + } + } +} --- /dev/null +++ b/pallets/identity/src/weights.rs @@ -0,0 +1,558 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +// Original license: +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_identity +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_identity +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/identity/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_identity. +pub trait WeightInfo { + fn add_registrar(r: u32, ) -> Weight; + fn set_identity(r: u32, x: u32, ) -> Weight; + fn set_subs_new(s: u32, ) -> Weight; + fn set_subs_old(p: u32, ) -> Weight; + fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight; + fn request_judgement(r: u32, x: u32, ) -> Weight; + fn cancel_request(r: u32, x: u32, ) -> Weight; + fn set_fee(r: u32, ) -> Weight; + fn set_account_id(r: u32, ) -> Weight; + fn set_fields(r: u32, ) -> Weight; + fn provide_judgement(r: u32, x: u32, ) -> Weight; + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight; + fn force_insert_identities(x: u32, n: u32, ) -> Weight; + fn force_remove_identities(x: u32, n: u32, ) -> Weight; + fn add_sub(s: u32, ) -> Weight; + fn rename_sub(s: u32, ) -> Weight; + fn remove_sub(s: u32, ) -> Weight; + fn quit_sub(s: u32, ) -> Weight; +} + +/// Weights for pallet_identity using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn add_registrar(r: u32, ) -> Weight { + // Minimum execution time: 20_269 nanoseconds. + Weight::from_ref_time(21_910_543 as u64) + // Standard Error: 4_604 + .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn set_identity(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. + fn set_subs_new(s: u32, ) -> Weight { + // Minimum execution time: 12_024 nanoseconds. + Weight::from_ref_time(32_550_819 as u64) + // Standard Error: 5_057 + .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. + fn set_subs_old(p: u32, ) -> Weight { + // Minimum execution time: 12_232 nanoseconds. + Weight::from_ref_time(34_009_761 as u64) + // Standard Error: 5_047 + .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity IdentityOf (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 57_144 nanoseconds. + Weight::from_ref_time(41_559_247 as u64) + // Standard Error: 9_996 + .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity Registrars (r:1 w:0) + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn request_judgement(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 44_726 nanoseconds. + Weight::from_ref_time(41_637_308 as u64) + // Standard Error: 1_907 + .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) + // Standard Error: 372 + .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn cancel_request(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 39_719 nanoseconds. + Weight::from_ref_time(38_008_751 as u64) + // Standard Error: 2_394 + .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) + // Standard Error: 467 + .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_fee(r: u32, ) -> Weight { + // Minimum execution time: 10_634 nanoseconds. + Weight::from_ref_time(11_383_704 as u64) + // Standard Error: 2_250 + .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_account_id(r: u32, ) -> Weight { + // Minimum execution time: 10_840 nanoseconds. + Weight::from_ref_time(11_638_740 as u64) + // Standard Error: 1_985 + .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_fields(r: u32, ) -> Weight { + // Minimum execution time: 10_748 nanoseconds. + Weight::from_ref_time(11_346_901 as u64) + // Standard Error: 2_132 + .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:0) + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[0, 100]`. + fn provide_judgement(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 33_682 nanoseconds. + Weight::from_ref_time(31_336_603 as u64) + // Standard Error: 3_056 + .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity IdentityOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 68_794 nanoseconds. + Weight::from_ref_time(52_114_486 as u64) + // Standard Error: 4_808 + .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(3 as u64)) + .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `x` is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_insert_identities(x: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `x` is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_remove_identities(x: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. + fn add_sub(s: u32, ) -> Weight { + // Minimum execution time: 37_914 nanoseconds. + Weight::from_ref_time(43_488_083 as u64) + // Standard Error: 1_631 + .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. + fn rename_sub(s: u32, ) -> Weight { + // Minimum execution time: 16_124 nanoseconds. + Weight::from_ref_time(18_580_462 as u64) + // Standard Error: 688 + .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. + fn remove_sub(s: u32, ) -> Weight { + // Minimum execution time: 41_517 nanoseconds. + Weight::from_ref_time(45_123_530 as u64) + // Standard Error: 1_530 + .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(3 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. + fn quit_sub(s: u32, ) -> Weight { + // Minimum execution time: 30_171 nanoseconds. + Weight::from_ref_time(33_355_514 as u64) + // Standard Error: 1_286 + .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn add_registrar(r: u32, ) -> Weight { + // Minimum execution time: 20_269 nanoseconds. + Weight::from_ref_time(21_910_543 as u64) + // Standard Error: 4_604 + .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn set_identity(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity SuperOf (r:2 w:2) + /// The range of component `s` is `[0, 100]`. + fn set_subs_new(s: u32, ) -> Weight { + // Minimum execution time: 12_024 nanoseconds. + Weight::from_ref_time(32_550_819 as u64) + // Standard Error: 5_057 + .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(s as u64))) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:2) + /// The range of component `p` is `[0, 100]`. + fn set_subs_old(p: u32, ) -> Weight { + // Minimum execution time: 12_232 nanoseconds. + Weight::from_ref_time(34_009_761 as u64) + // Standard Error: 5_047 + .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + } + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity IdentityOf (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 57_144 nanoseconds. + Weight::from_ref_time(41_559_247 as u64) + // Standard Error: 9_996 + .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) + // Standard Error: 1_952 + .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity Registrars (r:1 w:0) + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn request_judgement(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 44_726 nanoseconds. + Weight::from_ref_time(41_637_308 as u64) + // Standard Error: 1_907 + .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) + // Standard Error: 372 + .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 20]`. + /// The range of component `x` is `[0, 100]`. + fn cancel_request(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 39_719 nanoseconds. + Weight::from_ref_time(38_008_751 as u64) + // Standard Error: 2_394 + .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) + // Standard Error: 467 + .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_fee(r: u32, ) -> Weight { + // Minimum execution time: 10_634 nanoseconds. + Weight::from_ref_time(11_383_704 as u64) + // Standard Error: 2_250 + .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_account_id(r: u32, ) -> Weight { + // Minimum execution time: 10_840 nanoseconds. + Weight::from_ref_time(11_638_740 as u64) + // Standard Error: 1_985 + .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + fn set_fields(r: u32, ) -> Weight { + // Minimum execution time: 10_748 nanoseconds. + Weight::from_ref_time(11_346_901 as u64) + // Standard Error: 2_132 + .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity Registrars (r:1 w:0) + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `r` is `[1, 19]`. + /// The range of component `x` is `[0, 100]`. + fn provide_judgement(r: u32, x: u32, ) -> Weight { + // Minimum execution time: 33_682 nanoseconds. + Weight::from_ref_time(31_336_603 as u64) + // Standard Error: 3_056 + .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) + // Standard Error: 565 + .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity SubsOf (r:1 w:1) + // Storage: Identity IdentityOf (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Identity SuperOf (r:0 w:100) + /// The range of component `r` is `[1, 20]`. + /// The range of component `s` is `[0, 100]`. + /// The range of component `x` is `[0, 100]`. + fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { + // Minimum execution time: 68_794 nanoseconds. + Weight::from_ref_time(52_114_486 as u64) + // Standard Error: 4_808 + .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) + // Standard Error: 939 + .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(3 as u64)) + .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `x` is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_insert_identities(x: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } + // Storage: Identity IdentityOf (r:1 w:1) + /// The range of component `x` is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_remove_identities(x: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. + fn add_sub(s: u32, ) -> Weight { + // Minimum execution time: 37_914 nanoseconds. + Weight::from_ref_time(43_488_083 as u64) + // Standard Error: 1_631 + .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. + fn rename_sub(s: u32, ) -> Weight { + // Minimum execution time: 16_124 nanoseconds. + Weight::from_ref_time(18_580_462 as u64) + // Standard Error: 688 + .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: Identity IdentityOf (r:1 w:0) + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[1, 100]`. + fn remove_sub(s: u32, ) -> Weight { + // Minimum execution time: 41_517 nanoseconds. + Weight::from_ref_time(45_123_530 as u64) + // Standard Error: 1_530 + .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(3 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } + // Storage: Identity SuperOf (r:1 w:1) + // Storage: Identity SubsOf (r:1 w:1) + /// The range of component `s` is `[0, 99]`. + fn quit_sub(s: u32, ) -> Weight { + // Minimum execution time: 30_171 nanoseconds. + Weight::from_ref_time(33_355_514 as u64) + // Standard Error: 1_286 + .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +} --- a/primitives/common/src/constants.rs +++ b/primitives/common/src/constants.rs @@ -42,6 +42,15 @@ pub const CENTIUNIQUE: Balance = 10 * MILLIUNIQUE; pub const UNIQUE: Balance = 100 * CENTIUNIQUE; +/// Minimum balance required to create or keep an account open. +pub const EXISTENTIAL_DEPOSIT: u128 = 0; +/// Amount of Balance reserved for candidate registration. +pub const GENESIS_LICENSE_BOND: u128 = 1_000_000_000_000 * UNIQUE; +/// Amount of maximum collators for Collator Selection. +pub const MAX_COLLATORS: u32 = 10; +/// How long a periodic session lasts in blocks. +pub const SESSION_LENGTH: BlockNumber = HOURS; + // Targeting 0.1 UNQ per transfer pub const WEIGHT_TO_FEE_COEFF: u64 = /**/77_071_069_464_131_590/**/; --- /dev/null +++ b/runtime/common/config/pallets/collator_selection.rs @@ -0,0 +1,98 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +use frame_support::{parameter_types, PalletId}; +use frame_system::EnsureRoot; +use crate::{ + AccountId, Balance, Balances, BlockNumber, Runtime, RuntimeEvent, Aura, Session, SessionKeys, + CollatorSelection, Treasury, + config::pallets::{MaxCollators, SessionPeriod, TreasuryAccountId}, +}; +use sp_runtime::Perbill; +use up_common::constants::{UNIQUE, MILLIUNIQUE}; + +parameter_types! { + pub const SessionOffset: BlockNumber = 0; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = CollatorSelection; + // Essentially just Aura, but lets be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = pallet_session::weights::SubstrateWeight; // (); +} + +parameter_types! { + pub const UncleGenerations: u32 = 0; +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = CollatorSelection; +} + +parameter_types! { + // These do not matter as we forbid non-sudo operations with the identity pallet + pub const BasicDeposit: Balance = 10 * UNIQUE; + pub const FieldDeposit: Balance = 25 * MILLIUNIQUE; + pub const SubAccountDeposit: Balance = 2 * UNIQUE; + pub const MaxSubAccounts: u32 = 100; + pub const MaxAdditionalFields: u32 = 100; + pub const MaxRegistrars: u32 = 20; +} + +impl pallet_identity::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type BasicDeposit = BasicDeposit; + type FieldDeposit = FieldDeposit; + type MaxAdditionalFields = MaxAdditionalFields; + type MaxRegistrars = MaxRegistrars; + type MaxSubAccounts = MaxSubAccounts; + type SubAccountDeposit = SubAccountDeposit; + type RegistrarOrigin = EnsureRoot<::AccountId>; + type ForceOrigin = EnsureRoot<::AccountId>; + type Slashed = Treasury; + type WeightInfo = pallet_identity::weights::SubstrateWeight; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SlashRatio: Perbill = Perbill::from_percent(100); +} + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + // We allow root only to execute privileged collator selection operations. + type UpdateOrigin = EnsureRoot; + type TreasuryAccountId = TreasuryAccountId; + type PotId = PotId; + type MaxCollators = MaxCollators; + type SlashRatio = SlashRatio; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = pallet_collator_selection::weights::SubstrateWeight; +} --- a/runtime/common/config/pallets/mod.rs +++ b/runtime/common/config/pallets/mod.rs @@ -47,6 +47,9 @@ #[cfg(feature = "app-promotion")] pub mod app_promotion; +#[cfg(feature = "collator-selection")] +pub mod collator_selection; + parameter_types! { pub TreasuryAccountId: AccountId = TreasuryModuleId::get().into_account_truncating(); pub const CollectionCreationPrice: Balance = 2 * UNIQUE; @@ -101,14 +104,24 @@ parameter_types! { pub AppPromotionDailyRate: Perbill = Perbill::from_rational(5u32, 10_000); + pub const MaxCollators: u32 = MAX_COLLATORS; + pub const LicenseBond: Balance = GENESIS_LICENSE_BOND; + pub const SessionPeriod: BlockNumber = SESSION_LENGTH; pub const DayRelayBlocks: BlockNumber = RELAY_DAYS; } + impl pallet_configuration::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; type DefaultWeightToFeeCoefficient = ConstU64<{ up_common::constants::WEIGHT_TO_FEE_COEFF }>; type DefaultMinGasPrice = ConstU64<{ up_common::constants::MIN_GAS_PRICE }>; + type DefaultCollatorSelectionMaxCollators = MaxCollators; + type DefaultCollatorSelectionKickThreshold = SessionPeriod; + type DefaultCollatorSelectionLicenseBond = LicenseBond; type MaxXcmAllowedLocations = ConstU32<16>; type AppPromotionDailyRate = AppPromotionDailyRate; type DayRelayBlocks = DayRelayBlocks; + type WeightInfo = pallet_configuration::weights::SubstrateWeight; } impl pallet_maintenance::Config for Runtime { --- a/runtime/common/config/substrate.rs +++ b/runtime/common/config/substrate.rs @@ -131,7 +131,7 @@ parameter_types! { // pub const ExistentialDeposit: u128 = 500; - pub const ExistentialDeposit: u128 = 0; + pub const ExistentialDeposit: u128 = EXISTENTIAL_DEPOSIT; pub const MaxLocks: u32 = 50; pub const MaxReserves: u32 = 50; } --- a/runtime/common/construct_runtime/mod.rs +++ b/runtime/common/construct_runtime/mod.rs @@ -32,8 +32,17 @@ ParachainSystem: cumulus_pallet_parachain_system::{Pallet, Call, Config, Storage, Inherent, Event, ValidateUnsigned} = 20, ParachainInfo: parachain_info::{Pallet, Storage, Config} = 21, - Aura: pallet_aura::{Pallet, Config} = 22, - AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 23, + #[runtimes(quartz)] + Authorship: pallet_authorship::{Pallet, Call, Storage} = 22, + + #[runtimes(quartz)] + CollatorSelection: pallet_collator_selection::{Pallet, Call, Storage, Event, Config} = 23, + + #[runtimes(quartz)] + Session: pallet_session::{Pallet, Call, Storage, Event, Config} = 24, + + Aura: pallet_aura::{Pallet, Config} = 25, + AuraExt: cumulus_pallet_aura_ext::{Pallet, Config} = 26, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event} = 30, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage} = 31, @@ -47,6 +56,9 @@ Tokens: orml_tokens = 39, // Contracts: pallet_contracts::{Pallet, Call, Storage, Event} = 38, + #[runtimes(quartz)] + Identity: pallet_identity::{Pallet, Call, Storage, Event} = 40, + // XCM helpers. XcmpQueue: cumulus_pallet_xcmp_queue::{Pallet, Call, Storage, Event} = 50, PolkadotXcm: pallet_xcm::{Pallet, Call, Event, Origin} = 51, @@ -60,7 +72,7 @@ // #[runtimes(opal)] // Scheduler: pallet_unique_scheduler_v2::{Pallet, Call, Storage, Event} = 62, - Configuration: pallet_configuration::{Pallet, Call, Storage} = 63, + Configuration: pallet_configuration::{Pallet, Call, Storage, Event} = 63, Charging: pallet_charge_transaction::{Pallet, Call, Storage } = 64, // ContractHelpers: pallet_contract_helpers::{Pallet, Call, Storage} = 65, --- /dev/null +++ b/runtime/common/identity.rs @@ -0,0 +1,67 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +use scale_info::TypeInfo; +use codec::{Encode, Decode}; +use up_common::types::AccountId; +use crate::RuntimeCall; + +use sp_runtime::{ + traits::{DispatchInfoOf, SignedExtension}, + transaction_validity::{ + TransactionValidity, ValidTransaction, InvalidTransaction, TransactionValidityError, + }, +}; + +#[derive(Debug, Encode, Decode, PartialEq, Eq, Clone, TypeInfo)] +pub struct DisableIdentityCalls; + +impl SignedExtension for DisableIdentityCalls { + type AccountId = AccountId; + type Call = RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + + const IDENTIFIER: &'static str = "DisableIdentityCalls"; + + fn additional_signed(&self) -> Result { + Ok(()) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(|_| ()) + } + + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + match call { + #[cfg(feature = "collator-selection")] + RuntimeCall::Identity(_) => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + _ => Ok(ValidTransaction::default()), + } + } +} --- a/runtime/common/maintenance.rs +++ b/runtime/common/maintenance.rs @@ -85,6 +85,12 @@ Err(TransactionValidityError::Invalid(InvalidTransaction::Call)) } + #[cfg(feature = "collator-selection")] + RuntimeCall::CollatorSelection(_) + | RuntimeCall::Authorship(_) + | RuntimeCall::Session(_) + | RuntimeCall::Identity(_) => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + #[cfg(feature = "pallet-test-utils")] RuntimeCall::TestUtils(_) => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), --- a/runtime/common/mod.rs +++ b/runtime/common/mod.rs @@ -18,6 +18,7 @@ pub mod construct_runtime; pub mod dispatch; pub mod ethereum; +pub mod identity; pub mod instance; pub mod maintenance; pub mod runtime_apis; @@ -33,7 +34,10 @@ pub mod tests; use sp_core::H160; -use frame_support::traits::{Currency, OnUnbalanced, Imbalance}; +use frame_support::{ + traits::{Currency, OnUnbalanced, Imbalance}, + weights::Weight, +}; use sp_runtime::{ generic, traits::{BlakeTwo256, BlockNumberProvider}, @@ -93,6 +97,7 @@ frame_system::CheckNonce, frame_system::CheckWeight, maintenance::CheckMaintenance, + identity::DisableIdentityCalls, ChargeTransactionPayment, //pallet_contract_helpers::ContractHelpersExtension, pallet_ethereum::FakeTransactionFinalizer, @@ -113,6 +118,7 @@ frame_system::ChainContext, Runtime, AllPalletsWithSystem, + AuraToCollatorSelection, >; type NegativeImbalance = >::NegativeImbalance; @@ -179,3 +185,164 @@ /// Transfer tokens to the given account from the Parachain account. TransferToken(XAccountId, XBalance), } + +pub struct AuraToCollatorSelection; +impl frame_support::traits::OnRuntimeUpgrade for AuraToCollatorSelection { + fn on_runtime_upgrade() -> Weight { + #[cfg(feature = "collator-selection")] + { + use frame_support::{BoundedVec, storage::migration}; + use sp_runtime::{traits::OpaqueKeys, RuntimeAppPublic}; + use pallet_session::SessionManager; + use crate::config::pallets::MaxCollators; + + let mut weight = ::DbWeight::get().reads(1); + + let version = migration::get_storage_value::<()>( + b"AuraToCollatorSelection", + b"StorageVersion", + &[], + ); + + let should_upgrade = match version { + None => true, + Some(_) => false, + }; + + if should_upgrade { + log::info!( + target: "runtime::aura_to_collator_selection", + "Running migration of Aura authorities to Collator Selection invulnerables" + ); + + let invulnerables = pallet_aura::Pallet::::authorities() + .iter() + .cloned() + .filter_map(|authority_id| { + weight.saturating_accrue(::DbWeight::get().reads_writes(1, 1)); + let vec = authority_id.clone().to_raw_vec(); + let slice = vec.as_slice(); + let array: Option<[u8; 32]> = match slice.try_into() { + Ok(a) => Some(a), + Err(_) => { + log::error!("Failed to convert an Aura authority to a Collator Selection invulnerable: {:?}", authority_id); + None + }, + }; + array.map(|a| (AccountId::from(a), authority_id)) + }) + .collect::>(); + + let bounded_invulnerables = BoundedVec::<_, MaxCollators>::try_from( + invulnerables + .iter() + .cloned() + .map(|(acc, _)| acc) + .collect::>(), + ) + .expect("Existing collators/invulnerables are more than MaxCollators"); + + >::put(bounded_invulnerables); + + let keys = invulnerables + .into_iter() + .map(|(acc, aura)| { + ( + acc.clone(), // account id + acc, // validator id + SessionKeys { aura: aura.clone() }, // session keys + ) + }) + .collect::>(); + + for (account, val, keys) in keys.iter().cloned() { + for id in ::Keys::key_ids() { + >::insert((*id, keys.get_raw(*id)), &val) + } + >::insert(&val, &keys); + // todo exercise caution, the following is taken from genesis + if frame_system::Pallet::::inc_consumers_without_limit(&account) + .is_err() + { + log::warn!( + "We have entered an error with incrementing consumers without limit during the migration" + ); + // This will leak a provider reference, however it only happens once (at + // genesis) so it's really not a big deal and we assume that the user wants to + // do this since it's the only way a non-endowed account can contain a session + // key. + frame_system::Pallet::::inc_providers(&account); + } + } + + let initial_validators_0 = + ::SessionManager::new_session(0) + .unwrap_or_else(|| { + frame_support::print( + "No initial validator provided by `SessionManager`, use \ + session config keys to generate initial validator set.", + ); + keys.iter().map(|x| x.1.clone()).collect() + }); + /*assert!( + !initial_validators_0.is_empty(), + "Empty validator set for session 0 in (pseudo) genesis block!" + );*/ + + let initial_validators_1 = + ::SessionManager::new_session(1) + .unwrap_or_else(|| initial_validators_0.clone()); + /*assert!( + !initial_validators_1.is_empty(), + "Empty validator set for session 1 in (pseudo) genesis block!" + );*/ + + let queued_keys: Vec<_> = initial_validators_1 + .iter() + .cloned() + .map(|v| { + ( + v.clone(), + >::get(&v) + .expect("Validator in session 1 missing keys!"), + ) + }) + .collect(); + + // Tell everyone about the genesis session keys -- Aura must've already initialized it + //::SessionHandler::on_genesis_session::<::Keys>(&queued_keys); + + >::put(initial_validators_0); + >::put(queued_keys); + + ::SessionManager::start_session(0); + + log::info!( + target: "runtime::aura_to_collator_selection", + "Migration of Aura authorities to Collator Selection invulnerables is complete." + ); + + migration::put_storage_value::<()>( + b"AuraToCollatorSelection", + b"StorageVersion", + &[], + (), + ); + + weight += ::DbWeight::get().writes(1) + } else { + log::info!( + target: "runtime::aura_to_collator_selection", + "The storage migration has already been flagged as complete. No migration needs to be done.", + ); + } + + weight + } + + #[cfg(not(feature = "collator-selection"))] + { + Weight::zero() + } + } +} --- a/runtime/common/runtime_apis.rs +++ b/runtime/common/runtime_apis.rs @@ -686,6 +686,7 @@ list_benchmark!(list, extra, pallet_unique, Unique); list_benchmark!(list, extra, pallet_structure, Structure); list_benchmark!(list, extra, pallet_inflation, Inflation); + list_benchmark!(list, extra, pallet_configuration, Configuration); #[cfg(feature = "app-promotion")] list_benchmark!(list, extra, pallet_app_promotion, AppPromotion); @@ -705,6 +706,12 @@ #[cfg(feature = "rmrk")] list_benchmark!(list, extra, pallet_proxy_rmrk_equip, RmrkEquip); + #[cfg(feature = "collator-selection")] + list_benchmark!(list, extra, pallet_collator_selection, CollatorSelection); + + #[cfg(feature = "collator-selection")] + list_benchmark!(list, extra, pallet_identity, Identity); + #[cfg(feature = "foreign-assets")] list_benchmark!(list, extra, pallet_foreign_assets, ForeignAssets); @@ -749,6 +756,7 @@ add_benchmark!(params, batches, pallet_unique, Unique); add_benchmark!(params, batches, pallet_structure, Structure); add_benchmark!(params, batches, pallet_inflation, Inflation); + add_benchmark!(params, batches, pallet_configuration, Configuration); #[cfg(feature = "app-promotion")] add_benchmark!(params, batches, pallet_app_promotion, AppPromotion); @@ -768,6 +776,12 @@ #[cfg(feature = "rmrk")] add_benchmark!(params, batches, pallet_proxy_rmrk_equip, RmrkEquip); + #[cfg(feature = "collator-selection")] + add_benchmark!(params, batches, pallet_collator_selection, CollatorSelection); + + #[cfg(feature = "collator-selection")] + add_benchmark!(params, batches, pallet_identity, Identity); + #[cfg(feature = "foreign-assets")] add_benchmark!(params, batches, pallet_foreign_assets, ForeignAssets); --- a/runtime/common/tests/mod.rs +++ b/runtime/common/tests/mod.rs @@ -18,7 +18,7 @@ use sp_core::{Public, Pair}; use sp_std::vec; use up_common::types::AuraId; -use crate::{GenesisConfig, ParachainInfoConfig, AuraConfig}; +use crate::{GenesisConfig, ParachainInfoConfig}; pub mod xcm; @@ -28,7 +28,56 @@ .public() } +#[cfg(feature = "collator-selection")] fn new_test_ext(para_id: u32) -> sp_io::TestExternalities { + use sp_core::{sr25519}; + use sp_runtime::traits::{IdentifyAccount, Verify}; + use crate::{AccountId, Signature, SessionKeys, CollatorSelectionConfig, SessionConfig}; + + type AccountPublic = ::Signer; + + fn get_account_id_from_seed(seed: &str) -> AccountId + where + AccountPublic: From<::Public>, + { + AccountPublic::from(get_from_seed::(seed)).into_account() + } + + let accounts = vec!["Alice", "Bob"]; + let keys = accounts + .iter() + .map(|&acc| { + let account_id = get_account_id_from_seed::(acc); + ( + account_id.clone(), + account_id, + SessionKeys { + aura: get_from_seed::(acc), + }, + ) + }) + .collect::>(); + let invulnerables = accounts + .iter() + .map(|acc| get_account_id_from_seed::(acc)) + .collect::>(); + + let cfg = GenesisConfig { + collator_selection: CollatorSelectionConfig { invulnerables }, + session: SessionConfig { keys }, + parachain_info: ParachainInfoConfig { + parachain_id: para_id.into(), + }, + ..GenesisConfig::default() + }; + + cfg.build_storage().unwrap().into() +} + +#[cfg(not(feature = "collator-selection"))] +fn new_test_ext(para_id: u32) -> sp_io::TestExternalities { + use crate::AuraConfig; + let cfg = GenesisConfig { aura: AuraConfig { authorities: vec![ --- a/runtime/opal/Cargo.toml +++ b/runtime/opal/Cargo.toml @@ -28,6 +28,7 @@ 'pallet-evm-coder-substrate/runtime-benchmarks', 'pallet-balances/runtime-benchmarks', 'pallet-timestamp/runtime-benchmarks', + 'pallet-configuration/runtime-benchmarks', 'pallet-common/runtime-benchmarks', 'pallet-structure/runtime-benchmarks', 'pallet-fungible/runtime-benchmarks', @@ -39,6 +40,8 @@ 'pallet-unique/runtime-benchmarks', 'pallet-inflation/runtime-benchmarks', 'pallet-app-promotion/runtime-benchmarks', + 'pallet-collator-selection/runtime-benchmarks', + 'pallet-identity/runtime-benchmarks', 'pallet-unique-scheduler-v2/runtime-benchmarks', 'pallet-xcm/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', @@ -69,6 +72,10 @@ 'pallet-xcm/try-runtime', 'cumulus-pallet-xcm/try-runtime', 'cumulus-pallet-dmp-queue/try-runtime', + "pallet-authorship/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-session/try-runtime", + "pallet-identity/try-runtime", 'pallet-inflation/try-runtime', 'pallet-unique/try-runtime', 'pallet-configuration/try-runtime', @@ -113,6 +120,10 @@ # 'pallet-contracts-primitives/std', # 'pallet-contracts-rpc-runtime-api/std', # 'pallet-contract-helpers/std', + "pallet-authorship/std", + 'pallet-collator-selection/std', + "pallet-session/std", + 'pallet-identity/std', 'pallet-randomness-collective-flip/std', 'pallet-sudo/std', 'pallet-timestamp/std', @@ -192,6 +203,7 @@ rmrk = [] foreign-assets = [] app-promotion = [] +collator-selection = [] ################################################################################ # Substrate Dependencies @@ -255,6 +267,16 @@ git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.36" +[dependencies.pallet-authorship] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + +[dependencies.pallet-session] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + [dependencies.pallet-balances] default-features = false git = "https://github.com/paritytech/substrate" @@ -478,6 +500,8 @@ pallet-inflation = { path = '../../pallets/inflation', default-features = false } pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false } up-data-structs = { path = '../../primitives/data-structs', default-features = false } +pallet-collator-selection = { default-features = false, path = "../../pallets/collator-selection" } +pallet-identity = { default-features = false, path = "../../pallets/identity" } pallet-configuration = { default-features = false, path = "../../pallets/configuration" } pallet-common = { default-features = false, path = "../../pallets/common" } pallet-structure = { default-features = false, path = "../../pallets/structure" } --- a/runtime/quartz/Cargo.toml +++ b/runtime/quartz/Cargo.toml @@ -28,6 +28,7 @@ 'pallet-evm-coder-substrate/runtime-benchmarks', 'pallet-balances/runtime-benchmarks', 'pallet-timestamp/runtime-benchmarks', + 'pallet-configuration/runtime-benchmarks', 'pallet-common/runtime-benchmarks', 'pallet-structure/runtime-benchmarks', 'pallet-fungible/runtime-benchmarks', @@ -38,6 +39,8 @@ 'pallet-unique/runtime-benchmarks', 'pallet-foreign-assets/runtime-benchmarks', 'pallet-inflation/runtime-benchmarks', + 'pallet-collator-selection/runtime-benchmarks', + 'pallet-identity/runtime-benchmarks', 'pallet-app-promotion/runtime-benchmarks', 'pallet-xcm/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', @@ -67,6 +70,10 @@ 'pallet-xcm/try-runtime', 'cumulus-pallet-xcm/try-runtime', 'cumulus-pallet-dmp-queue/try-runtime', + "pallet-authorship/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-session/try-runtime", + "pallet-identity/try-runtime", 'pallet-inflation/try-runtime', 'pallet-unique/try-runtime', 'pallet-configuration/try-runtime', @@ -108,6 +115,10 @@ # 'pallet-contracts-primitives/std', # 'pallet-contracts-rpc-runtime-api/std', # 'pallet-contract-helpers/std', + "pallet-authorship/std", + 'pallet-collator-selection/std', + "pallet-session/std", + "pallet-identity/std", 'pallet-randomness-collective-flip/std', 'pallet-sudo/std', 'pallet-timestamp/std', @@ -170,7 +181,7 @@ "pallet-maintenance/std", ] limit-testing = ['pallet-unique/limit-testing', 'up-data-structs/limit-testing'] -quartz-runtime = ['refungible', 'app-promotion', 'foreign-assets'] +quartz-runtime = ['refungible', 'app-promotion', 'collator-selection', 'foreign-assets'] become-sapphire = [] pov-estimate = [] @@ -178,6 +189,7 @@ scheduler = [] rmrk = [] foreign-assets = [] +collator-selection = [] app-promotion = [] ################################################################################ @@ -242,6 +254,16 @@ git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.36" +[dependencies.pallet-authorship] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + +[dependencies.pallet-session] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + [dependencies.pallet-balances] default-features = false git = "https://github.com/paritytech/substrate" @@ -472,6 +494,8 @@ pallet-inflation = { path = '../../pallets/inflation', default-features = false } pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false } up-data-structs = { path = '../../primitives/data-structs', default-features = false } +pallet-collator-selection = { default-features = false, path = "../../pallets/collator-selection" } +pallet-identity = { default-features = false, path = "../../pallets/identity" } pallet-configuration = { default-features = false, path = "../../pallets/configuration" } pallet-common = { default-features = false, path = "../../pallets/common" } pallet-structure = { default-features = false, path = "../../pallets/structure" } --- a/runtime/unique/Cargo.toml +++ b/runtime/unique/Cargo.toml @@ -28,6 +28,7 @@ 'pallet-evm-coder-substrate/runtime-benchmarks', 'pallet-balances/runtime-benchmarks', 'pallet-timestamp/runtime-benchmarks', + 'pallet-configuration/runtime-benchmarks', 'pallet-common/runtime-benchmarks', 'pallet-structure/runtime-benchmarks', 'pallet-fungible/runtime-benchmarks', @@ -38,6 +39,8 @@ 'pallet-unique/runtime-benchmarks', 'pallet-foreign-assets/runtime-benchmarks', 'pallet-inflation/runtime-benchmarks', + 'pallet-collator-selection/runtime-benchmarks', + 'pallet-identity/runtime-benchmarks', 'pallet-app-promotion/runtime-benchmarks', 'pallet-xcm/runtime-benchmarks', 'sp-runtime/runtime-benchmarks', @@ -67,6 +70,10 @@ 'pallet-xcm/try-runtime', 'cumulus-pallet-xcm/try-runtime', 'cumulus-pallet-dmp-queue/try-runtime', + "pallet-authorship/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-session/try-runtime", + "pallet-identity/try-runtime", 'pallet-inflation/try-runtime', 'pallet-unique/try-runtime', 'pallet-configuration/try-runtime', @@ -108,6 +115,10 @@ # 'pallet-contracts-primitives/std', # 'pallet-contracts-rpc-runtime-api/std', # 'pallet-contract-helpers/std', + "pallet-authorship/std", + 'pallet-collator-selection/std', + "pallet-session/std", + "pallet-identity/std", 'pallet-randomness-collective-flip/std', 'pallet-sudo/std', 'pallet-timestamp/std', @@ -178,6 +189,7 @@ scheduler = [] rmrk = [] foreign-assets = [] +collator-selection = [] ################################################################################ # Substrate Dependencies @@ -241,6 +253,16 @@ git = "https://github.com/paritytech/substrate" branch = "polkadot-v0.9.36" +[dependencies.pallet-authorship] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + +[dependencies.pallet-session] +default-features = false +git = 'https://github.com/paritytech/substrate' +branch = 'polkadot-v0.9.36' + [dependencies.pallet-balances] default-features = false git = "https://github.com/paritytech/substrate" @@ -462,6 +484,8 @@ pallet-inflation = { path = '../../pallets/inflation', default-features = false } pallet-app-promotion = { path = '../../pallets/app-promotion', default-features = false } up-data-structs = { path = '../../primitives/data-structs', default-features = false } +pallet-collator-selection = { default-features = false, path = "../../pallets/collator-selection" } +pallet-identity = { default-features = false, path = "../../pallets/identity" } pallet-configuration = { default-features = false, path = "../../pallets/configuration" } pallet-common = { default-features = false, path = "../../pallets/common" } pallet-structure = { default-features = false, path = "../../pallets/structure" } --- a/tests/package.json +++ b/tests/package.json @@ -31,6 +31,7 @@ "lint": "eslint --ext .ts,.js src/", "fix": "eslint --ext .ts,.js src/ --fix", "setup": "ts-node ./src/util/globalSetup.ts", + "setIdentities": "ts-node ./src/util/identitySetter.ts", "test": "yarn setup && mocha --timeout 9999999 -r ts-node/register './src/**/*.*test.ts'", "testParallelFull": "yarn testParallel && yarn testSequential", "testParallel": "yarn setup && mocha --parallel --timeout 9999999 -r ts-node/register './src/**/*.test.ts'", @@ -87,6 +88,8 @@ "testSchedulingEVM": "mocha --timeout 9999999 -r ts-node/register ./**/eth/scheduling.test.ts", "testPalletPresence": "mocha --timeout 9999999 -r ts-node/register ./**/pallet-presence.test.ts", "testBlockProduction": "mocha --timeout 9999999 -r ts-node/register ./**/block-production.test.ts", + "testCollatorSelection": "mocha --timeout 9999999 -r ts-node/register ./**/collatorSelection.*test.ts", + "testIdentity": "mocha --timeout 9999999 -r ts-node/register ./**/identity.*test.ts", "testEnableDisableTransfers": "mocha --timeout 9999999 -r ts-node/register ./**/enableDisableTransfer.test.ts", "testLimits": "mocha --timeout 9999999 -r ts-node/register ./**/limits.test.ts", "testEthCreateNFTCollection": "mocha --timeout 9999999 -r ts-node/register ./**/eth/createNFTCollection.test.ts", --- a/tests/src/.outdated/substrate/substrate-api.ts +++ b/tests/src/.outdated/substrate/substrate-api.ts @@ -41,6 +41,10 @@ extrinsic: {}, payload: {}, }, + DisableIdentityCalls: { + extrinsic: {}, + payload: {}, + }, FakeTransactionFinalizer: { extrinsic: {}, payload: {}, --- /dev/null +++ b/tests/src/collatorSelection.seqtest.ts @@ -0,0 +1,465 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util'; + +async function resetInvulnerables() { + await usingPlaygrounds(async (helper, privateKey) => { + const superuser = await privateKey('//Alice'); + const alice = await privateKey('//Alice'); + const bob = await privateKey('//Bob'); + const invulnerables = await helper.collatorSelection.getInvulnerables(); + if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) { + console.warn('Alice and Bob are not the invulnerables! Reinstating them back. ' + + 'Current invulnerables\' size: ' + invulnerables.length); + + let nonce = await helper.chain.getNonce(alice.address); + // In case there are too many invulnerables already, remove some of them, leaving space for Alice and Bob. + if (invulnerables.length + 2 >= helper.collatorSelection.maxCollators()) { + await Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerables.pop()], true, {nonce: nonce++}), + ]); + } + + nonce = await helper.chain.getNonce(alice.address); + await Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}), + ]); + + nonce = await helper.chain.getNonce(alice.address); + await Promise.all(invulnerables.map((invulnerable: any) => { + if (invulnerable == alice.address || invulnerable == bob.address) return new Promise(res => res()); + return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++}); + })); + } + }); +} + +// todo:collator Most preferable to launch this test in parallel somehow -- or change the session period (1 hr). +describe('Integration Test: Collator Selection', () => { + let superuser: IKeyringPair; + let previousLicenseBond = 0n; + let licenseBond = 0n; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.CollatorSelection]); + superuser = await privateKey('//Alice'); + + previousLicenseBond = await helper.collatorSelection.getLicenseBond(); + licenseBond = 10n * helper.balance.getOneTokenNominal(); + await helper.getSudo().collatorSelection.setLicenseBond(superuser, licenseBond); + }); + }); + + describe('Dynamic shuffling of collators', () => { + // These two are the default invulnerables, and should return to be invulnerables after this suite. + let alice: IKeyringPair; + let bob: IKeyringPair; + + let charlie: IKeyringPair; + let dave: IKeyringPair; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + // todo:collator see again if blocks start to be finalized in dev mode + // Skip the collator block production in dev mode, since the blocks are sealed automatically. + if (await helper.arrange.isDevNode()) this.skip(); + + alice = await privateKey('//Alice'); + bob = await privateKey('//Bob'); + charlie = await privateKey('//Charlie'); + dave = await privateKey('//Dave'); + + expect((await helper.session.setOwnKeysFromAddress(charlie)) + .status.toLowerCase()).to.be.equal('success'); + expect((await helper.session.setOwnKeysFromAddress(dave)) + .status.toLowerCase()).to.be.equal('success'); + + const invulnerables = await helper.collatorSelection.getInvulnerables(); + if (!invulnerables.includes(alice.address) || !invulnerables.includes(bob.address) || invulnerables.length != 2) { + console.warn('Alice and Bob are not the invulnerables! Reinstating them back. ' + + 'Current invulnerables\' size: ' + invulnerables.length); + + let nonce = await helper.chain.getNonce(superuser.address); + await Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}), + ]); + + nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(invulnerables.map((invulnerable: any) => { + if (invulnerable == alice.address || invulnerable == bob.address) return new Promise((res) => res); + return helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [invulnerable], true, {nonce: nonce++}); + })); + } + }); + }); + + itSub('Change invulnerables and make sure they start producing blocks', async ({helper}) => { + let nonce = await helper.chain.getNonce(superuser.address); + await expect(Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [charlie.address], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [dave.address], true, {nonce: nonce++}), + ])).to.be.fulfilled; + + nonce = await helper.chain.getNonce(superuser.address); + await expect(Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [alice.address], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [bob.address], true, {nonce: nonce++}), + ])).to.be.fulfilled; + + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + expect(newInvulnerables).to.contain(charlie.address).and.contain(dave.address).and.be.length(2); + + await helper.wait.newSessions(2); + + const newValidators = await helper.callRpc('api.query.session.validators'); + expect(newValidators).to.contain(charlie.address).and.contain(dave.address).and.be.length(2); + + const lastBlockNumber = await helper.chain.getLatestBlockNumber(); + await helper.wait.newBlocks(1); + const lastCharlieBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [charlie.address])).toNumber(); + const lastDaveBlock = (await helper.callRpc('api.query.collatorSelection.lastAuthoredBlock', [dave.address])).toNumber(); + expect(lastCharlieBlock >= lastBlockNumber || lastDaveBlock >= lastBlockNumber).to.be.true; + }); + + after(async () => { + await usingPlaygrounds(async (helper) => { + if (await helper.arrange.isDevNode()) return; + + let nonce = await helper.chain.getNonce(superuser.address); + await Promise.all([ + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [alice.address], true, {nonce: nonce++}), + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [bob.address], true, {nonce: nonce++}), + ]); + + nonce = await helper.chain.getNonce(superuser.address); + await Promise.all([ + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [charlie.address], true, {nonce: nonce++}), + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [dave.address], true, {nonce: nonce++}), + ]); + }); + }); + }); + + describe('Getting and releasing licenses to collate', () => { + let charlie: IKeyringPair; + let dave: IKeyringPair; + let crowd: IKeyringPair[]; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + charlie = await privateKey('//Charlie'); + dave = await privateKey('//Dave'); + crowd = await helper.arrange.createCrowd(20, 100n, superuser); + + // set session keys for everyone + expect((await helper.session.setOwnKeysFromAddress(charlie)) + .status.toLowerCase()).to.be.equal('success'); + expect((await helper.session.setOwnKeysFromAddress(dave)) + .status.toLowerCase()).to.be.equal('success'); + await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc))); + }); + }); + + describe('Positive', () => { + itSub('Can lease and release a license', async ({helper}) => { + const account = crowd.pop()!; + + // make sure it does not have any reserved funds + expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(0n); + + // getting a license reserves a license bond cost + await helper.collatorSelection.obtainLicense(account); + expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond); + expect((await helper.balance.getSubstrateFull(account.address)).reserved).to.be.equal(licenseBond); + + // releasing a license un-reserves the license bond cost + await helper.collatorSelection.releaseLicense(account); + expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n); + + const balance = await helper.balance.getSubstrateFull(account.address); + expect(balance.reserved).to.be.equal(0n); + expect(balance.free > 100n - licenseBond); + }); + + itSub('Can force revoke a license', async ({helper}) => { + const account = crowd.pop()!; + + // getting a license reserves a license bond cost + const previousBalance = await helper.balance.getSubstrateFull(account.address); + await helper.collatorSelection.obtainLicense(account); + expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(licenseBond); + + // force-releasing a license un-reserves the license bond cost as well + await helper.getSudo().collatorSelection.forceReleaseLicense(superuser, account.address); + expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(previousBalance.reserved); + + const balance = await helper.balance.getSubstrateFull(account.address); + expect(balance.reserved).to.be.equal(previousBalance.reserved); + expect(balance.free > previousBalance.free - licenseBond); + }); + }); + + describe('Negative', () => { + itSub('Cannot get a license without session keys set', async ({helper}) => { + const [account] = await helper.arrange.createAccounts([100n], superuser); + await expect(helper.collatorSelection.obtainLicense(account)) + .to.be.rejectedWith(/collatorSelection.ValidatorNotRegistered/); + }); + + itSub('Cannot register a license twice', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await expect(helper.collatorSelection.obtainLicense(account)) + .to.be.rejectedWith(/collatorSelection.AlreadyHoldingLicense/); + }); + + itSub('Cannot release a license twice', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await helper.collatorSelection.releaseLicense(account); + await expect(helper.collatorSelection.releaseLicense(account)) + .to.be.rejectedWith(/collatorSelection.NoLicense/); + }); + + itSub('Cannot force revoke a license as non-sudo', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await expect(helper.collatorSelection.forceReleaseLicense(superuser, account.address)) + .to.be.rejectedWith(/BadOrigin/); + }); + }); + }); + + describe('Onboarding, collating, and offboarding as collator candidates', () => { + // These two are the default invulnerables, and should return to be invulnerables after this suite. + let charlie: IKeyringPair; + let dave: IKeyringPair; + let crowd: IKeyringPair[]; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + charlie = await privateKey('//Charlie'); + dave = await privateKey('//Dave'); + crowd = await helper.arrange.createCrowd(20, 100n, superuser); + + // set session keys for everyone + expect((await helper.session.setOwnKeysFromAddress(charlie)) + .status.toLowerCase()).to.be.equal('success'); + expect((await helper.session.setOwnKeysFromAddress(dave)) + .status.toLowerCase()).to.be.equal('success'); + await Promise.all(crowd.map(acc => helper.session.setOwnKeysFromAddress(acc))); + }); + }); + + describe('Positive', () => { + itSub('Can onboard and offboard repeatedly', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await helper.collatorSelection.onboard(account); + expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]); + + await helper.collatorSelection.offboard(account); + expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]); + + await helper.collatorSelection.onboard(account); + expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([account.address]); + + await helper.collatorSelection.offboard(account); + expect(await helper.collatorSelection.getCandidates()).to.be.deep.equal([]); + }); + + itSub('Penalizes and forfeits license from faulty collators', async ({helper}) => { + // This one shouldn't even be able to produce blocks. + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await helper.collatorSelection.onboard(account); + expect(await helper.collatorSelection.getCandidates()).to.contain(account.address); + + // Wait for 3 new sessions before checking that the collator will be kicked: + // one to get collator onboarded, and another two for the collator to fail + await helper.wait.newSessions(3); + + expect(await helper.collatorSelection.getCandidates()).to.not.contain(account.address); + expect(await helper.collatorSelection.hasLicense(account.address)).to.be.equal(0n); + + // The account's reserved funds get slashed as a penalty + const balance = await helper.balance.getSubstrateFull(account.address); + expect(balance.reserved).to.be.equal(0n); + expect(balance.free < 100n - licenseBond); + }); + }); + + describe('Negative', () => { + itSub('Cannot onboard without a license', async ({helper}) => { + const account = crowd.pop()!; + await expect(helper.collatorSelection.onboard(account)) + .to.be.rejectedWith(/collatorSelection.NoLicense/); + }); + + itSub('Cannot offboard without a license', async ({helper}) => { + const account = crowd.pop()!; + await expect(helper.collatorSelection.offboard(account)) + .to.be.rejectedWith(/collatorSelection.NotCandidate/); + }); + + itSub('Cannot offboard while not onboarded', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await expect(helper.collatorSelection.offboard(account)) + .to.be.rejectedWith(/collatorSelection.NotCandidate/); + }); + + itSub('Cannot onboard while already onboarded', async ({helper}) => { + const account = crowd.pop()!; + await helper.collatorSelection.obtainLicense(account); + await helper.collatorSelection.onboard(account); + await expect(helper.collatorSelection.onboard(account)) + .to.be.rejectedWith(/collatorSelection.AlreadyCandidate/); + }); + }); + }); + + describe('Addition and removal of invulnerables', () => { + before(async function() { + await resetInvulnerables(); + }); + + describe('Positive', () => { + itSub('Adds an invulnerable', async ({helper}) => { + const [account] = await helper.arrange.createAccounts([10n], superuser); + const invulnerables = await helper.collatorSelection.getInvulnerables(); + + await helper.session.setOwnKeysFromAddress(account); + await helper.getSudo().collatorSelection.addInvulnerable(superuser, account.address); + + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + expect(invulnerables.concat(account.address)).to.have.all.members(newInvulnerables); + }); + + itSub('Removes an invulnerable', async ({helper}) => { + const invulnerables = await helper.collatorSelection.getInvulnerables(); + const lastInvulnerable = invulnerables.pop()!; + + await helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable); + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + // invulnerables had its last element removed, so they should be equal + expect(newInvulnerables).to.have.all.members(invulnerables); + }); + }); + + describe('Negative', () => { + itSub('Does not duplicate an invulnerable', async ({helper}) => { + const invulnerables = await helper.collatorSelection.getInvulnerables(); + // adding an already invulnerable should not fail, but should not duplicate it either + await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, invulnerables[0])) + .to.be.fulfilled; + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + expect(newInvulnerables).to.have.all.members(invulnerables); + }); + + itSub('Cannot remove a non-existent invulnerable', async ({helper}) => { + const [account] = await helper.arrange.createAccounts([0n], superuser); + await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, account.address)) + .to.be.rejectedWith(/collatorSelection.NotInvulnerable/); + }); + + itSub('Cannot allow invulnerables to be empty', async ({helper}) => { + const invulnerables = await helper.collatorSelection.getInvulnerables(); + const lastInvulnerable = invulnerables.pop()!; + + let nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(invulnerables.map((i: any) => + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i], true, {nonce: nonce++}))); + + await expect(helper.getSudo().collatorSelection.removeInvulnerable(superuser, lastInvulnerable)) + .to.be.rejectedWith(/collatorSelection.TooFewInvulnerables/); + + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + expect(newInvulnerables).to.be.deep.equal([lastInvulnerable]); + + // restore the invulnerables to the previous state + nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(invulnerables.map((i: any) => + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i], true, {nonce: nonce++}))); + }); + + itSub('Cannot have too many invulnerables', async ({helper}) => { + // todo:collator make sure that there is enough session time for a set of tests + // 28 non-functioning collators, teehee. + + const invulnerablesLength = (await helper.collatorSelection.getInvulnerables()).length; + const invulnerablesUntilLimit = helper.collatorSelection.maxCollators() - invulnerablesLength; + const newInvulnerables = await helper.arrange.createAccounts(Array(invulnerablesUntilLimit).fill(10n), superuser); + const [lastInvulnerable] = await helper.arrange.createAccounts([10n], superuser); + + await Promise.all(newInvulnerables.map((i: IKeyringPair) => + helper.session.setOwnKeysFromAddress(i))); + await helper.session.setOwnKeysFromAddress(lastInvulnerable); + + let nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(newInvulnerables.map((i: IKeyringPair) => + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.addInvulnerable', [i.address], true, {nonce: nonce++}))); + + await expect(helper.getSudo().collatorSelection.addInvulnerable(superuser, lastInvulnerable.address)) + .to.be.rejectedWith(/collatorSelection.TooManyInvulnerables/); + + // restore the invulnerables to the previous state + nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(newInvulnerables.map((i: IKeyringPair) => + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.removeInvulnerable', [i.address], true, {nonce: nonce++}))); + }); + + itSub('Forbids a non-sudo to add an invulnerable', async ({helper}) => { + const [account] = await helper.arrange.createAccounts([10n], superuser); + const invulnerables = await helper.collatorSelection.getInvulnerables(); + + await helper.session.setOwnKeysFromAddress(account); + await expect(helper.collatorSelection.addInvulnerable(superuser, account.address)) + .to.be.rejectedWith(/BadOrigin/); + + const newInvulnerables = await helper.collatorSelection.getInvulnerables(); + expect(newInvulnerables).to.be.members(invulnerables); + }); + + itSub('Forbids a non-sudo to remove an invulnerable', async ({helper}) => { + const invulnerables = await helper.collatorSelection.getInvulnerables(); + await expect(helper.collatorSelection.removeInvulnerable(superuser, invulnerables[0])) + .to.be.rejectedWith(/BadOrigin/); + expect(await helper.collatorSelection.getInvulnerables()).to.have.all.members(invulnerables); + }); + }); + }); + + after(async () => { + // eslint-disable-next-line require-await + await usingPlaygrounds(async (helper) => { + if (helper.fetchMissingPalletNames([Pallets.CollatorSelection]).length != 0) return; + + await helper.getSudo().collatorSelection.setLicenseBond(superuser, previousLicenseBond); + + const candidates = await helper.collatorSelection.getCandidates(); + let nonce = await helper.chain.getNonce(superuser.address); + await Promise.all(candidates.map(candidate => + helper.getSudo().executeExtrinsic(superuser, 'api.tx.collatorSelection.forceReleaseLicense', [candidate], true, {nonce: nonce++}))); + }); + }); +}); \ No newline at end of file --- /dev/null +++ b/tests/src/identity.seqtest.ts @@ -0,0 +1,101 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// This file is part of Unique Network. + +// Unique Network 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. + +// Unique Network 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 Unique Network. If not, see . + +import {IKeyringPair} from '@polkadot/types/types'; +import {usingPlaygrounds, expect, itSub, Pallets, requirePalletsOrSkip} from './util'; +import {UniqueHelper} from './util/playgrounds/unique'; + +async function getIdentities(helper: UniqueHelper) { + const identities: [string, any][] = []; + for(const [key, value] of await helper.getApi().query.identity.identityOf.entries()) + identities.push([(key as any).toHuman(), (value as any).unwrap()]); + return identities; +} + +async function getIdentityAccounts(helper: UniqueHelper) { + return (await getIdentities(helper)).flatMap(([key, _value]) => key); +} + +describe('Integration Test: Identities Manipulation', () => { + let superuser: IKeyringPair; + + before(async function() { + await usingPlaygrounds(async (helper, privateKey) => { + requirePalletsOrSkip(this, helper, [Pallets.Identity]); + superuser = await privateKey('//Alice'); + }); + }); + + itSub('Normal calls do not work', async ({helper}) => { + // console.error = () => {}; + await expect(helper.executeExtrinsic(superuser, 'api.tx.identity.setIdentity', [{info: {display: {Raw: 'Meowser'}}}])) + .to.be.rejectedWith(/Transaction call is not expected/); + }); + + itSub('Sets identities', async ({helper}) => { + const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; + + const crowdSize = 10; + const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + + expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + crowdSize); + }); + + itSub('Setting identities does not delete existing but does overwrite', async ({helper}) => { + const crowd = await helper.arrange.createCrowd(10, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + + // insert a single identity + let singleIdentity = identities.pop()!; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [[singleIdentity]]); + + const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; + + // change an identity and push it with a few new others + singleIdentity = [singleIdentity[0], {info: {display: {Raw: 'something special'}}}]; + identities.push(singleIdentity); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + + // oldIdentitiesCount + 9 because one identity is overwritten, not inserted on top + expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + 9); + expect((await helper.callRpc('api.query.identity.identityOf', [singleIdentity[0]])).toHuman().info.display) + .to.be.deep.equal({Raw: 'something special'}); + }); + + itSub('Removes identities', async ({helper}) => { + const crowd = await helper.arrange.createCrowd(10, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + const oldIdentities = await getIdentityAccounts(helper); + + // delete a couple, check that they are no longer there + const scapegoats = [crowd.pop()!.address, crowd.pop()!.address]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [scapegoats]); + const newIdentities = await getIdentityAccounts(helper); + expect(newIdentities.concat(scapegoats)).to.be.have.members(oldIdentities); + }); + + after(async function() { + await usingPlaygrounds(async helper => { + if (helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) return; + + const identitiesToRemove: string[] = await getIdentityAccounts(helper); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]); + }); + }); +}); --- a/tests/src/interfaces/augment-api-consts.ts +++ b/tests/src/interfaces/augment-api-consts.ts @@ -81,6 +81,9 @@ configuration: { appPromotionDailyRate: Perbill & AugmentedConst; dayRelayBlocks: u32 & AugmentedConst; + defaultCollatorSelectionKickThreshold: u32 & AugmentedConst; + defaultCollatorSelectionLicenseBond: u128 & AugmentedConst; + defaultCollatorSelectionMaxCollators: u32 & AugmentedConst; defaultMinGasPrice: u64 & AugmentedConst; defaultWeightToFeeCoefficient: u64 & AugmentedConst; maxXcmAllowedLocations: u32 & AugmentedConst; --- a/tests/src/interfaces/augment-api-events.ts +++ b/tests/src/interfaces/augment-api-events.ts @@ -194,6 +194,15 @@ **/ [key: string]: AugmentedEvent; }; + configuration: { + NewCollatorKickThreshold: AugmentedEvent], { lengthInBlocks: Option }>; + NewCollatorLicenseBond: AugmentedEvent], { bondCost: Option }>; + NewDesiredCollators: AugmentedEvent], { desiredCollators: Option }>; + /** + * Generic event + **/ + [key: string]: AugmentedEvent; + }; cumulusXcm: { /** * Downward message executed with the given outcome. --- a/tests/src/interfaces/augment-api-query.ts +++ b/tests/src/interfaces/augment-api-query.ts @@ -159,6 +159,9 @@ }; configuration: { appPromomotionConfigurationOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; + collatorSelectionDesiredCollatorsOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; + collatorSelectionKickThresholdOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; + collatorSelectionLicenseBondOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; minGasPriceOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; weightToFeeCoefficientOverride: AugmentedQuery Observable, []> & QueryableStorageEntry; xcmAllowedLocationsOverride: AugmentedQuery Observable>>, []> & QueryableStorageEntry; --- a/tests/src/interfaces/augment-api-tx.ts +++ b/tests/src/interfaces/augment-api-tx.ts @@ -216,6 +216,9 @@ }; configuration: { setAppPromotionConfigurationOverride: AugmentedSubmittable<(configuration: PalletConfigurationAppPromotionConfiguration | { recalculationInterval?: any; pendingInterval?: any; intervalIncome?: any; maxStakersPerCalculation?: any } | string | Uint8Array) => SubmittableExtrinsic, [PalletConfigurationAppPromotionConfiguration]>; + setCollatorSelectionDesiredCollators: AugmentedSubmittable<(max: Option | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic, [Option]>; + setCollatorSelectionKickThreshold: AugmentedSubmittable<(threshold: Option | null | Uint8Array | u32 | AnyNumber) => SubmittableExtrinsic, [Option]>; + setCollatorSelectionLicenseBond: AugmentedSubmittable<(amount: Option | null | Uint8Array | u128 | AnyNumber) => SubmittableExtrinsic, [Option]>; setMinGasPriceOverride: AugmentedSubmittable<(coeff: Option | null | Uint8Array | u64 | AnyNumber) => SubmittableExtrinsic, [Option]>; setWeightToFeeCoefficientOverride: AugmentedSubmittable<(coeff: Option | null | Uint8Array | u64 | AnyNumber) => SubmittableExtrinsic, [Option]>; setXcmAllowedLocations: AugmentedSubmittable<(locations: Option> | null | Uint8Array | Vec | (XcmV1MultiLocation | { parents?: any; interior?: any } | string | Uint8Array)[]) => SubmittableExtrinsic, [Option>]>; --- a/tests/src/interfaces/augment-types.ts +++ b/tests/src/interfaces/augment-types.ts @@ -5,7 +5,7 @@ // this is required to allow for ambient/previous definitions import '@polkadot/types/types/registry'; -import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchDispatchClass, FrameSupportDispatchDispatchInfo, FrameSupportDispatchPays, FrameSupportDispatchPerDispatchClassU32, FrameSupportDispatchPerDispatchClassWeight, FrameSupportDispatchPerDispatchClassWeightsPerClass, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckTxVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensModuleCall, OrmlTokensModuleError, OrmlTokensModuleEvent, OrmlTokensReserveData, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, OrmlXtokensModuleCall, OrmlXtokensModuleError, OrmlXtokensModuleEvent, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationAppPromotionConfiguration, PalletConfigurationCall, PalletConfigurationError, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletEvmMigrationEvent, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletForeignAssetsModuleCall, PalletForeignAssetsModuleError, PalletForeignAssetsModuleEvent, PalletForeignAssetsNativeCurrency, PalletFungibleError, PalletInflationCall, PalletMaintenanceCall, PalletMaintenanceError, PalletMaintenanceEvent, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTestUtilsCall, PalletTestUtilsError, PalletTestUtilsEvent, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionValidityInvalidTransaction, SpRuntimeTransactionValidityTransactionValidityError, SpRuntimeTransactionValidityUnknownTransaction, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, SpWeightsWeightV2Weight, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsRpcCollectionFlags, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, UpPovEstimateRpcPovInfo, UpPovEstimateRpcTrieKeyValue, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAsset, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default'; +import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchDispatchClass, FrameSupportDispatchDispatchInfo, FrameSupportDispatchPays, FrameSupportDispatchPerDispatchClassU32, FrameSupportDispatchPerDispatchClassWeight, FrameSupportDispatchPerDispatchClassWeightsPerClass, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckTxVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls, OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensModuleCall, OrmlTokensModuleError, OrmlTokensModuleEvent, OrmlTokensReserveData, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, OrmlXtokensModuleCall, OrmlXtokensModuleError, OrmlXtokensModuleEvent, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationAppPromotionConfiguration, PalletConfigurationCall, PalletConfigurationError, PalletConfigurationEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletEvmMigrationEvent, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletForeignAssetsModuleCall, PalletForeignAssetsModuleError, PalletForeignAssetsModuleEvent, PalletForeignAssetsNativeCurrency, PalletFungibleError, PalletInflationCall, PalletMaintenanceCall, PalletMaintenanceError, PalletMaintenanceEvent, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTestUtilsCall, PalletTestUtilsError, PalletTestUtilsEvent, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionValidityInvalidTransaction, SpRuntimeTransactionValidityTransactionValidityError, SpRuntimeTransactionValidityUnknownTransaction, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, SpWeightsWeightV2Weight, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsRpcCollectionFlags, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, UpPovEstimateRpcPovInfo, UpPovEstimateRpcTrieKeyValue, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAsset, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from './default'; import type { Data, StorageKey } from '@polkadot/types'; import type { BitVec, Bool, Bytes, F32, F64, I128, I16, I256, I32, I64, I8, Json, Null, OptionBool, Raw, Text, Type, U128, U16, U256, U32, U64, U8, USize, bool, f32, f64, i128, i16, i256, i32, i64, i8, u128, u16, u256, u32, u64, u8, usize } from '@polkadot/types-codec'; import type { AssetApproval, AssetApprovalKey, AssetBalance, AssetDestroyWitness, AssetDetails, AssetMetadata, TAssetBalance, TAssetDepositBalance } from '@polkadot/types/interfaces/assets'; @@ -772,6 +772,7 @@ Offender: Offender; OldV1SessionInfo: OldV1SessionInfo; OpalRuntimeRuntime: OpalRuntimeRuntime; + OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls: OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls; OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance: OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance; OpaqueCall: OpaqueCall; OpaqueKeyOwnershipProof: OpaqueKeyOwnershipProof; @@ -831,6 +832,7 @@ PalletConfigurationAppPromotionConfiguration: PalletConfigurationAppPromotionConfiguration; PalletConfigurationCall: PalletConfigurationCall; PalletConfigurationError: PalletConfigurationError; + PalletConfigurationEvent: PalletConfigurationEvent; PalletConstantMetadataLatest: PalletConstantMetadataLatest; PalletConstantMetadataV14: PalletConstantMetadataV14; PalletErrorMetadataLatest: PalletErrorMetadataLatest; --- a/tests/src/interfaces/default/types.ts +++ b/tests/src/interfaces/default/types.ts @@ -692,6 +692,9 @@ /** @name OpalRuntimeRuntime */ export interface OpalRuntimeRuntime extends Null {} +/** @name OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls */ +export interface OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls extends Null {} + /** @name OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance */ export interface OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance extends Null {} @@ -1306,7 +1309,19 @@ readonly asSetAppPromotionConfigurationOverride: { readonly configuration: PalletConfigurationAppPromotionConfiguration; } & Struct; - readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride' | 'SetXcmAllowedLocations' | 'SetAppPromotionConfigurationOverride'; + readonly isSetCollatorSelectionDesiredCollators: boolean; + readonly asSetCollatorSelectionDesiredCollators: { + readonly max: Option; + } & Struct; + readonly isSetCollatorSelectionLicenseBond: boolean; + readonly asSetCollatorSelectionLicenseBond: { + readonly amount: Option; + } & Struct; + readonly isSetCollatorSelectionKickThreshold: boolean; + readonly asSetCollatorSelectionKickThreshold: { + readonly threshold: Option; + } & Struct; + readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride' | 'SetXcmAllowedLocations' | 'SetAppPromotionConfigurationOverride' | 'SetCollatorSelectionDesiredCollators' | 'SetCollatorSelectionLicenseBond' | 'SetCollatorSelectionKickThreshold'; } /** @name PalletConfigurationError */ @@ -1315,6 +1330,23 @@ readonly type: 'InconsistentConfiguration'; } +/** @name PalletConfigurationEvent */ +export interface PalletConfigurationEvent extends Enum { + readonly isNewDesiredCollators: boolean; + readonly asNewDesiredCollators: { + readonly desiredCollators: Option; + } & Struct; + readonly isNewCollatorLicenseBond: boolean; + readonly asNewCollatorLicenseBond: { + readonly bondCost: Option; + } & Struct; + readonly isNewCollatorKickThreshold: boolean; + readonly asNewCollatorKickThreshold: { + readonly lengthInBlocks: Option; + } & Struct; + readonly type: 'NewDesiredCollators' | 'NewCollatorLicenseBond' | 'NewCollatorKickThreshold'; +} + /** @name PalletEthereumCall */ export interface PalletEthereumCall extends Enum { readonly isTransact: boolean; --- a/tests/src/interfaces/lookup.ts +++ b/tests/src/interfaces/lookup.ts @@ -989,7 +989,23 @@ } }, /** - * Lookup89: pallet_common::pallet::Event + * Lookup89: pallet_configuration::pallet::Event + **/ + PalletConfigurationEvent: { + _enum: { + NewDesiredCollators: { + desiredCollators: 'Option', + }, + NewCollatorLicenseBond: { + bondCost: 'Option', + }, + NewCollatorKickThreshold: { + lengthInBlocks: 'Option' + } + } + }, + /** + * Lookup92: pallet_common::pallet::Event **/ PalletCommonEvent: { _enum: { @@ -1018,7 +1034,7 @@ } }, /** - * Lookup92: pallet_evm::account::BasicCrossAccountIdRepr + * Lookup95: pallet_evm::account::BasicCrossAccountIdRepr **/ PalletEvmAccountBasicCrossAccountIdRepr: { _enum: { @@ -1027,7 +1043,7 @@ } }, /** - * Lookup96: pallet_structure::pallet::Event + * Lookup99: pallet_structure::pallet::Event **/ PalletStructureEvent: { _enum: { @@ -1035,7 +1051,7 @@ } }, /** - * Lookup97: pallet_rmrk_core::pallet::Event + * Lookup100: pallet_rmrk_core::pallet::Event **/ PalletRmrkCoreEvent: { _enum: { @@ -1112,7 +1128,7 @@ } }, /** - * Lookup98: rmrk_traits::nft::AccountIdOrCollectionNftTuple + * Lookup101: rmrk_traits::nft::AccountIdOrCollectionNftTuple **/ RmrkTraitsNftAccountIdOrCollectionNftTuple: { _enum: { @@ -1121,7 +1137,7 @@ } }, /** - * Lookup102: pallet_rmrk_equip::pallet::Event + * Lookup104: pallet_rmrk_equip::pallet::Event **/ PalletRmrkEquipEvent: { _enum: { @@ -1136,7 +1152,7 @@ } }, /** - * Lookup103: pallet_app_promotion::pallet::Event + * Lookup105: pallet_app_promotion::pallet::Event **/ PalletAppPromotionEvent: { _enum: { @@ -1147,7 +1163,7 @@ } }, /** - * Lookup104: pallet_foreign_assets::module::Event + * Lookup106: pallet_foreign_assets::module::Event **/ PalletForeignAssetsModuleEvent: { _enum: { @@ -1172,7 +1188,7 @@ } }, /** - * Lookup105: pallet_foreign_assets::module::AssetMetadata + * Lookup107: pallet_foreign_assets::module::AssetMetadata **/ PalletForeignAssetsModuleAssetMetadata: { name: 'Bytes', @@ -1181,7 +1197,7 @@ minimalBalance: 'u128' }, /** - * Lookup106: pallet_evm::pallet::Event + * Lookup108: pallet_evm::pallet::Event **/ PalletEvmEvent: { _enum: { @@ -1203,7 +1219,7 @@ } }, /** - * Lookup107: ethereum::log::Log + * Lookup109: ethereum::log::Log **/ EthereumLog: { address: 'H160', @@ -1211,7 +1227,7 @@ data: 'Bytes' }, /** - * Lookup109: pallet_ethereum::pallet::Event + * Lookup111: pallet_ethereum::pallet::Event **/ PalletEthereumEvent: { _enum: { @@ -1224,7 +1240,7 @@ } }, /** - * Lookup110: evm_core::error::ExitReason + * Lookup112: evm_core::error::ExitReason **/ EvmCoreErrorExitReason: { _enum: { @@ -1235,13 +1251,13 @@ } }, /** - * Lookup111: evm_core::error::ExitSucceed + * Lookup113: evm_core::error::ExitSucceed **/ EvmCoreErrorExitSucceed: { _enum: ['Stopped', 'Returned', 'Suicided'] }, /** - * Lookup112: evm_core::error::ExitError + * Lookup114: evm_core::error::ExitError **/ EvmCoreErrorExitError: { _enum: { @@ -1263,13 +1279,13 @@ } }, /** - * Lookup115: evm_core::error::ExitRevert + * Lookup117: evm_core::error::ExitRevert **/ EvmCoreErrorExitRevert: { _enum: ['Reverted'] }, /** - * Lookup116: evm_core::error::ExitFatal + * Lookup118: evm_core::error::ExitFatal **/ EvmCoreErrorExitFatal: { _enum: { @@ -1280,7 +1296,7 @@ } }, /** - * Lookup117: pallet_evm_contract_helpers::pallet::Event + * Lookup119: pallet_evm_contract_helpers::pallet::Event **/ PalletEvmContractHelpersEvent: { _enum: { @@ -1290,25 +1306,25 @@ } }, /** - * Lookup118: pallet_evm_migration::pallet::Event + * Lookup120: pallet_evm_migration::pallet::Event **/ PalletEvmMigrationEvent: { _enum: ['TestEvent'] }, /** - * Lookup119: pallet_maintenance::pallet::Event + * Lookup121: pallet_maintenance::pallet::Event **/ PalletMaintenanceEvent: { _enum: ['MaintenanceEnabled', 'MaintenanceDisabled'] }, /** - * Lookup120: pallet_test_utils::pallet::Event + * Lookup122: pallet_test_utils::pallet::Event **/ PalletTestUtilsEvent: { _enum: ['ValueIsSet', 'ShouldRollback', 'BatchCompleted'] }, /** - * Lookup121: frame_system::Phase + * Lookup123: frame_system::Phase **/ FrameSystemPhase: { _enum: { @@ -1318,14 +1334,14 @@ } }, /** - * Lookup124: frame_system::LastRuntimeUpgradeInfo + * Lookup126: frame_system::LastRuntimeUpgradeInfo **/ FrameSystemLastRuntimeUpgradeInfo: { specVersion: 'Compact', specName: 'Text' }, /** - * Lookup125: frame_system::pallet::Call + * Lookup127: frame_system::pallet::Call **/ FrameSystemCall: { _enum: { @@ -1360,7 +1376,7 @@ } }, /** - * Lookup129: frame_system::limits::BlockWeights + * Lookup131: frame_system::limits::BlockWeights **/ FrameSystemLimitsBlockWeights: { baseBlock: 'SpWeightsWeightV2Weight', @@ -1368,7 +1384,7 @@ perClass: 'FrameSupportDispatchPerDispatchClassWeightsPerClass' }, /** - * Lookup130: frame_support::dispatch::PerDispatchClass + * Lookup132: frame_support::dispatch::PerDispatchClass **/ FrameSupportDispatchPerDispatchClassWeightsPerClass: { normal: 'FrameSystemLimitsWeightsPerClass', @@ -1376,7 +1392,7 @@ mandatory: 'FrameSystemLimitsWeightsPerClass' }, /** - * Lookup131: frame_system::limits::WeightsPerClass + * Lookup133: frame_system::limits::WeightsPerClass **/ FrameSystemLimitsWeightsPerClass: { baseExtrinsic: 'SpWeightsWeightV2Weight', @@ -1385,13 +1401,13 @@ reserved: 'Option' }, /** - * Lookup133: frame_system::limits::BlockLength + * Lookup135: frame_system::limits::BlockLength **/ FrameSystemLimitsBlockLength: { max: 'FrameSupportDispatchPerDispatchClassU32' }, /** - * Lookup134: frame_support::dispatch::PerDispatchClass + * Lookup136: frame_support::dispatch::PerDispatchClass **/ FrameSupportDispatchPerDispatchClassU32: { normal: 'u32', @@ -1399,14 +1415,14 @@ mandatory: 'u32' }, /** - * Lookup135: sp_weights::RuntimeDbWeight + * Lookup137: sp_weights::RuntimeDbWeight **/ SpWeightsRuntimeDbWeight: { read: 'u64', write: 'u64' }, /** - * Lookup136: sp_version::RuntimeVersion + * Lookup138: sp_version::RuntimeVersion **/ SpVersionRuntimeVersion: { specName: 'Text', @@ -1419,13 +1435,13 @@ stateVersion: 'u8' }, /** - * Lookup141: frame_system::pallet::Error + * Lookup143: frame_system::pallet::Error **/ FrameSystemError: { _enum: ['InvalidSpecName', 'SpecVersionNeedsToIncrease', 'FailedToExtractRuntimeVersion', 'NonDefaultComposite', 'NonZeroRefCount', 'CallFiltered'] }, /** - * Lookup142: polkadot_primitives::v2::PersistedValidationData + * Lookup144: polkadot_primitives::v2::PersistedValidationData **/ PolkadotPrimitivesV2PersistedValidationData: { parentHead: 'Bytes', @@ -1434,19 +1450,19 @@ maxPovSize: 'u32' }, /** - * Lookup145: polkadot_primitives::v2::UpgradeRestriction + * Lookup147: polkadot_primitives::v2::UpgradeRestriction **/ PolkadotPrimitivesV2UpgradeRestriction: { _enum: ['Present'] }, /** - * Lookup146: sp_trie::storage_proof::StorageProof + * Lookup148: sp_trie::storage_proof::StorageProof **/ SpTrieStorageProof: { trieNodes: 'BTreeSet' }, /** - * Lookup148: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot + * Lookup150: cumulus_pallet_parachain_system::relay_state_snapshot::MessagingStateSnapshot **/ CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot: { dmqMqcHead: 'H256', @@ -1455,7 +1471,7 @@ egressChannels: 'Vec<(u32,PolkadotPrimitivesV2AbridgedHrmpChannel)>' }, /** - * Lookup151: polkadot_primitives::v2::AbridgedHrmpChannel + * Lookup153: polkadot_primitives::v2::AbridgedHrmpChannel **/ PolkadotPrimitivesV2AbridgedHrmpChannel: { maxCapacity: 'u32', @@ -1466,7 +1482,7 @@ mqcHead: 'Option' }, /** - * Lookup152: polkadot_primitives::v2::AbridgedHostConfiguration + * Lookup154: polkadot_primitives::v2::AbridgedHostConfiguration **/ PolkadotPrimitivesV2AbridgedHostConfiguration: { maxCodeSize: 'u32', @@ -1480,14 +1496,14 @@ validationUpgradeDelay: 'u32' }, /** - * Lookup158: polkadot_core_primitives::OutboundHrmpMessage + * Lookup160: polkadot_core_primitives::OutboundHrmpMessage **/ PolkadotCorePrimitivesOutboundHrmpMessage: { recipient: 'u32', data: 'Bytes' }, /** - * Lookup159: cumulus_pallet_parachain_system::pallet::Call + * Lookup161: cumulus_pallet_parachain_system::pallet::Call **/ CumulusPalletParachainSystemCall: { _enum: { @@ -1506,7 +1522,7 @@ } }, /** - * Lookup160: cumulus_primitives_parachain_inherent::ParachainInherentData + * Lookup162: cumulus_primitives_parachain_inherent::ParachainInherentData **/ CumulusPrimitivesParachainInherentParachainInherentData: { validationData: 'PolkadotPrimitivesV2PersistedValidationData', @@ -1515,27 +1531,27 @@ horizontalMessages: 'BTreeMap>' }, /** - * Lookup162: polkadot_core_primitives::InboundDownwardMessage + * Lookup164: polkadot_core_primitives::InboundDownwardMessage **/ PolkadotCorePrimitivesInboundDownwardMessage: { sentAt: 'u32', msg: 'Bytes' }, /** - * Lookup165: polkadot_core_primitives::InboundHrmpMessage + * Lookup167: polkadot_core_primitives::InboundHrmpMessage **/ PolkadotCorePrimitivesInboundHrmpMessage: { sentAt: 'u32', data: 'Bytes' }, /** - * Lookup168: cumulus_pallet_parachain_system::pallet::Error + * Lookup170: cumulus_pallet_parachain_system::pallet::Error **/ CumulusPalletParachainSystemError: { _enum: ['OverlappingUpgrades', 'ProhibitedByPolkadot', 'TooBig', 'ValidationDataNotAvailable', 'HostConfigurationNotAvailable', 'NotScheduled', 'NothingAuthorized', 'Unauthorized'] }, /** - * Lookup170: pallet_balances::BalanceLock + * Lookup172: pallet_balances::BalanceLock **/ PalletBalancesBalanceLock: { id: '[u8;8]', @@ -1543,20 +1559,20 @@ reasons: 'PalletBalancesReasons' }, /** - * Lookup171: pallet_balances::Reasons + * Lookup173: pallet_balances::Reasons **/ PalletBalancesReasons: { _enum: ['Fee', 'Misc', 'All'] }, /** - * Lookup174: pallet_balances::ReserveData + * Lookup176: pallet_balances::ReserveData **/ PalletBalancesReserveData: { id: '[u8;16]', amount: 'u128' }, /** - * Lookup176: pallet_balances::pallet::Call + * Lookup178: pallet_balances::pallet::Call **/ PalletBalancesCall: { _enum: { @@ -1589,13 +1605,13 @@ } }, /** - * Lookup179: pallet_balances::pallet::Error + * Lookup181: pallet_balances::pallet::Error **/ PalletBalancesError: { _enum: ['VestingBalance', 'LiquidityRestrictions', 'InsufficientBalance', 'ExistentialDeposit', 'KeepAlive', 'ExistingVestingSchedule', 'DeadAccount', 'TooManyReserves'] }, /** - * Lookup181: pallet_timestamp::pallet::Call + * Lookup183: pallet_timestamp::pallet::Call **/ PalletTimestampCall: { _enum: { @@ -1605,13 +1621,13 @@ } }, /** - * Lookup183: pallet_transaction_payment::Releases + * Lookup185: pallet_transaction_payment::Releases **/ PalletTransactionPaymentReleases: { _enum: ['V1Ancient', 'V2'] }, /** - * Lookup184: pallet_treasury::Proposal + * Lookup186: pallet_treasury::Proposal **/ PalletTreasuryProposal: { proposer: 'AccountId32', @@ -1620,7 +1636,7 @@ bond: 'u128' }, /** - * Lookup187: pallet_treasury::pallet::Call + * Lookup189: pallet_treasury::pallet::Call **/ PalletTreasuryCall: { _enum: { @@ -1644,17 +1660,17 @@ } }, /** - * Lookup190: frame_support::PalletId + * Lookup191: frame_support::PalletId **/ FrameSupportPalletId: '[u8;8]', /** - * Lookup191: pallet_treasury::pallet::Error + * Lookup192: pallet_treasury::pallet::Error **/ PalletTreasuryError: { _enum: ['InsufficientProposersBalance', 'InvalidIndex', 'TooManyApprovals', 'InsufficientPermission', 'ProposalNotApproved'] }, /** - * Lookup192: pallet_sudo::pallet::Call + * Lookup193: pallet_sudo::pallet::Call **/ PalletSudoCall: { _enum: { @@ -1678,7 +1694,7 @@ } }, /** - * Lookup194: orml_vesting::module::Call + * Lookup195: orml_vesting::module::Call **/ OrmlVestingModuleCall: { _enum: { @@ -1697,7 +1713,7 @@ } }, /** - * Lookup196: orml_xtokens::module::Call + * Lookup197: orml_xtokens::module::Call **/ OrmlXtokensModuleCall: { _enum: { @@ -1740,7 +1756,7 @@ } }, /** - * Lookup197: xcm::VersionedMultiAsset + * Lookup198: xcm::VersionedMultiAsset **/ XcmVersionedMultiAsset: { _enum: { @@ -1749,7 +1765,7 @@ } }, /** - * Lookup200: orml_tokens::module::Call + * Lookup201: orml_tokens::module::Call **/ OrmlTokensModuleCall: { _enum: { @@ -1783,7 +1799,7 @@ } }, /** - * Lookup201: cumulus_pallet_xcmp_queue::pallet::Call + * Lookup202: cumulus_pallet_xcmp_queue::pallet::Call **/ CumulusPalletXcmpQueueCall: { _enum: { @@ -1832,7 +1848,7 @@ } }, /** - * Lookup202: pallet_xcm::pallet::Call + * Lookup203: pallet_xcm::pallet::Call **/ PalletXcmCall: { _enum: { @@ -1886,7 +1902,7 @@ } }, /** - * Lookup203: xcm::VersionedXcm + * Lookup204: xcm::VersionedXcm **/ XcmVersionedXcm: { _enum: { @@ -1896,7 +1912,7 @@ } }, /** - * Lookup204: xcm::v0::Xcm + * Lookup205: xcm::v0::Xcm **/ XcmV0Xcm: { _enum: { @@ -1950,7 +1966,7 @@ } }, /** - * Lookup206: xcm::v0::order::Order + * Lookup207: xcm::v0::order::Order **/ XcmV0Order: { _enum: { @@ -1993,7 +2009,7 @@ } }, /** - * Lookup208: xcm::v0::Response + * Lookup209: xcm::v0::Response **/ XcmV0Response: { _enum: { @@ -2001,7 +2017,7 @@ } }, /** - * Lookup209: xcm::v1::Xcm + * Lookup210: xcm::v1::Xcm **/ XcmV1Xcm: { _enum: { @@ -2060,7 +2076,7 @@ } }, /** - * Lookup211: xcm::v1::order::Order + * Lookup212: xcm::v1::order::Order **/ XcmV1Order: { _enum: { @@ -2105,7 +2121,7 @@ } }, /** - * Lookup213: xcm::v1::Response + * Lookup214: xcm::v1::Response **/ XcmV1Response: { _enum: { @@ -2114,11 +2130,11 @@ } }, /** - * Lookup227: cumulus_pallet_xcm::pallet::Call + * Lookup228: cumulus_pallet_xcm::pallet::Call **/ CumulusPalletXcmCall: 'Null', /** - * Lookup228: cumulus_pallet_dmp_queue::pallet::Call + * Lookup229: cumulus_pallet_dmp_queue::pallet::Call **/ CumulusPalletDmpQueueCall: { _enum: { @@ -2129,7 +2145,7 @@ } }, /** - * Lookup229: pallet_inflation::pallet::Call + * Lookup230: pallet_inflation::pallet::Call **/ PalletInflationCall: { _enum: { @@ -2139,7 +2155,7 @@ } }, /** - * Lookup230: pallet_unique::Call + * Lookup231: pallet_unique::Call **/ PalletUniqueCall: { _enum: { @@ -2283,7 +2299,7 @@ } }, /** - * Lookup235: up_data_structs::CollectionMode + * Lookup236: up_data_structs::CollectionMode **/ UpDataStructsCollectionMode: { _enum: { @@ -2293,7 +2309,7 @@ } }, /** - * Lookup236: up_data_structs::CreateCollectionData + * Lookup237: up_data_structs::CreateCollectionData **/ UpDataStructsCreateCollectionData: { mode: 'UpDataStructsCollectionMode', @@ -2308,13 +2324,13 @@ properties: 'Vec' }, /** - * Lookup238: up_data_structs::AccessMode + * Lookup239: up_data_structs::AccessMode **/ UpDataStructsAccessMode: { _enum: ['Normal', 'AllowList'] }, /** - * Lookup240: up_data_structs::CollectionLimits + * Lookup241: up_data_structs::CollectionLimits **/ UpDataStructsCollectionLimits: { accountTokenOwnershipLimit: 'Option', @@ -2328,7 +2344,7 @@ transfersEnabled: 'Option' }, /** - * Lookup242: up_data_structs::SponsoringRateLimit + * Lookup243: up_data_structs::SponsoringRateLimit **/ UpDataStructsSponsoringRateLimit: { _enum: { @@ -2337,7 +2353,7 @@ } }, /** - * Lookup245: up_data_structs::CollectionPermissions + * Lookup246: up_data_structs::CollectionPermissions **/ UpDataStructsCollectionPermissions: { access: 'Option', @@ -2345,7 +2361,7 @@ nesting: 'Option' }, /** - * Lookup247: up_data_structs::NestingPermissions + * Lookup248: up_data_structs::NestingPermissions **/ UpDataStructsNestingPermissions: { tokenOwner: 'bool', @@ -2353,18 +2369,18 @@ restricted: 'Option' }, /** - * Lookup249: up_data_structs::OwnerRestrictedSet + * Lookup250: up_data_structs::OwnerRestrictedSet **/ UpDataStructsOwnerRestrictedSet: 'BTreeSet', /** - * Lookup254: up_data_structs::PropertyKeyPermission + * Lookup255: up_data_structs::PropertyKeyPermission **/ UpDataStructsPropertyKeyPermission: { key: 'Bytes', permission: 'UpDataStructsPropertyPermission' }, /** - * Lookup255: up_data_structs::PropertyPermission + * Lookup256: up_data_structs::PropertyPermission **/ UpDataStructsPropertyPermission: { mutable: 'bool', @@ -2372,14 +2388,14 @@ tokenOwner: 'bool' }, /** - * Lookup258: up_data_structs::Property + * Lookup259: up_data_structs::Property **/ UpDataStructsProperty: { key: 'Bytes', value: 'Bytes' }, /** - * Lookup261: up_data_structs::CreateItemData + * Lookup262: up_data_structs::CreateItemData **/ UpDataStructsCreateItemData: { _enum: { @@ -2389,26 +2405,26 @@ } }, /** - * Lookup262: up_data_structs::CreateNftData + * Lookup263: up_data_structs::CreateNftData **/ UpDataStructsCreateNftData: { properties: 'Vec' }, /** - * Lookup263: up_data_structs::CreateFungibleData + * Lookup264: up_data_structs::CreateFungibleData **/ UpDataStructsCreateFungibleData: { value: 'u128' }, /** - * Lookup264: up_data_structs::CreateReFungibleData + * Lookup265: up_data_structs::CreateReFungibleData **/ UpDataStructsCreateReFungibleData: { pieces: 'u128', properties: 'Vec' }, /** - * Lookup267: up_data_structs::CreateItemExData> + * Lookup268: up_data_structs::CreateItemExData> **/ UpDataStructsCreateItemExData: { _enum: { @@ -2419,14 +2435,14 @@ } }, /** - * Lookup269: up_data_structs::CreateNftExData> + * Lookup270: up_data_structs::CreateNftExData> **/ UpDataStructsCreateNftExData: { properties: 'Vec', owner: 'PalletEvmAccountBasicCrossAccountIdRepr' }, /** - * Lookup276: up_data_structs::CreateRefungibleExSingleOwner> + * Lookup277: up_data_structs::CreateRefungibleExSingleOwner> **/ UpDataStructsCreateRefungibleExSingleOwner: { user: 'PalletEvmAccountBasicCrossAccountIdRepr', @@ -2434,14 +2450,14 @@ properties: 'Vec' }, /** - * Lookup278: up_data_structs::CreateRefungibleExMultipleOwners> + * Lookup279: up_data_structs::CreateRefungibleExMultipleOwners> **/ UpDataStructsCreateRefungibleExMultipleOwners: { users: 'BTreeMap', properties: 'Vec' }, /** - * Lookup279: pallet_configuration::pallet::Call + * Lookup280: pallet_configuration::pallet::Call **/ PalletConfigurationCall: { _enum: { @@ -2455,12 +2471,21 @@ locations: 'Option>', }, set_app_promotion_configuration_override: { - configuration: 'PalletConfigurationAppPromotionConfiguration' + configuration: 'PalletConfigurationAppPromotionConfiguration', + }, + set_collator_selection_desired_collators: { + max: 'Option', + }, + set_collator_selection_license_bond: { + amount: 'Option', + }, + set_collator_selection_kick_threshold: { + threshold: 'Option' } } }, /** - * Lookup284: pallet_configuration::AppPromotionConfiguration + * Lookup285: pallet_configuration::AppPromotionConfiguration **/ PalletConfigurationAppPromotionConfiguration: { recalculationInterval: 'Option', @@ -2469,15 +2494,15 @@ maxStakersPerCalculation: 'Option' }, /** - * Lookup288: pallet_template_transaction_payment::Call + * Lookup289: pallet_template_transaction_payment::Call **/ PalletTemplateTransactionPaymentCall: 'Null', /** - * Lookup289: pallet_structure::pallet::Call + * Lookup290: pallet_structure::pallet::Call **/ PalletStructureCall: 'Null', /** - * Lookup290: pallet_rmrk_core::pallet::Call + * Lookup291: pallet_rmrk_core::pallet::Call **/ PalletRmrkCoreCall: { _enum: { @@ -2568,7 +2593,7 @@ } }, /** - * Lookup296: rmrk_traits::resource::ResourceTypes, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup297: rmrk_traits::resource::ResourceTypes, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsResourceResourceTypes: { _enum: { @@ -2578,7 +2603,7 @@ } }, /** - * Lookup298: rmrk_traits::resource::BasicResource> + * Lookup299: rmrk_traits::resource::BasicResource> **/ RmrkTraitsResourceBasicResource: { src: 'Option', @@ -2587,7 +2612,7 @@ thumb: 'Option' }, /** - * Lookup300: rmrk_traits::resource::ComposableResource, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup301: rmrk_traits::resource::ComposableResource, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsResourceComposableResource: { parts: 'Vec', @@ -2598,7 +2623,7 @@ thumb: 'Option' }, /** - * Lookup301: rmrk_traits::resource::SlotResource> + * Lookup302: rmrk_traits::resource::SlotResource> **/ RmrkTraitsResourceSlotResource: { base: 'u32', @@ -2609,7 +2634,7 @@ thumb: 'Option' }, /** - * Lookup304: pallet_rmrk_equip::pallet::Call + * Lookup305: pallet_rmrk_equip::pallet::Call **/ PalletRmrkEquipCall: { _enum: { @@ -2630,7 +2655,7 @@ } }, /** - * Lookup307: rmrk_traits::part::PartType, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup308: rmrk_traits::part::PartType, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsPartPartType: { _enum: { @@ -2639,7 +2664,7 @@ } }, /** - * Lookup309: rmrk_traits::part::FixedPart> + * Lookup310: rmrk_traits::part::FixedPart> **/ RmrkTraitsPartFixedPart: { id: 'u32', @@ -2647,7 +2672,7 @@ src: 'Bytes' }, /** - * Lookup310: rmrk_traits::part::SlotPart, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup311: rmrk_traits::part::SlotPart, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsPartSlotPart: { id: 'u32', @@ -2656,7 +2681,7 @@ z: 'u32' }, /** - * Lookup311: rmrk_traits::part::EquippableList> + * Lookup312: rmrk_traits::part::EquippableList> **/ RmrkTraitsPartEquippableList: { _enum: { @@ -2666,7 +2691,7 @@ } }, /** - * Lookup313: rmrk_traits::theme::Theme, sp_core::bounded::bounded_vec::BoundedVec>, S>> + * Lookup314: rmrk_traits::theme::Theme, sp_core::bounded::bounded_vec::BoundedVec>, S>> **/ RmrkTraitsTheme: { name: 'Bytes', @@ -2674,14 +2699,14 @@ inherit: 'bool' }, /** - * Lookup315: rmrk_traits::theme::ThemeProperty> + * Lookup316: rmrk_traits::theme::ThemeProperty> **/ RmrkTraitsThemeThemeProperty: { key: 'Bytes', value: 'Bytes' }, /** - * Lookup317: pallet_app_promotion::pallet::Call + * Lookup318: pallet_app_promotion::pallet::Call **/ PalletAppPromotionCall: { _enum: { @@ -2710,7 +2735,7 @@ } }, /** - * Lookup318: pallet_foreign_assets::module::Call + * Lookup319: pallet_foreign_assets::module::Call **/ PalletForeignAssetsModuleCall: { _enum: { @@ -2727,7 +2752,7 @@ } }, /** - * Lookup319: pallet_evm::pallet::Call + * Lookup320: pallet_evm::pallet::Call **/ PalletEvmCall: { _enum: { @@ -2770,7 +2795,7 @@ } }, /** - * Lookup325: pallet_ethereum::pallet::Call + * Lookup326: pallet_ethereum::pallet::Call **/ PalletEthereumCall: { _enum: { @@ -2780,7 +2805,7 @@ } }, /** - * Lookup326: ethereum::transaction::TransactionV2 + * Lookup327: ethereum::transaction::TransactionV2 **/ EthereumTransactionTransactionV2: { _enum: { @@ -2790,7 +2815,7 @@ } }, /** - * Lookup327: ethereum::transaction::LegacyTransaction + * Lookup328: ethereum::transaction::LegacyTransaction **/ EthereumTransactionLegacyTransaction: { nonce: 'U256', @@ -2802,7 +2827,7 @@ signature: 'EthereumTransactionTransactionSignature' }, /** - * Lookup328: ethereum::transaction::TransactionAction + * Lookup329: ethereum::transaction::TransactionAction **/ EthereumTransactionTransactionAction: { _enum: { @@ -2811,7 +2836,7 @@ } }, /** - * Lookup329: ethereum::transaction::TransactionSignature + * Lookup330: ethereum::transaction::TransactionSignature **/ EthereumTransactionTransactionSignature: { v: 'u64', @@ -2819,7 +2844,7 @@ s: 'H256' }, /** - * Lookup331: ethereum::transaction::EIP2930Transaction + * Lookup332: ethereum::transaction::EIP2930Transaction **/ EthereumTransactionEip2930Transaction: { chainId: 'u64', @@ -2835,14 +2860,14 @@ s: 'H256' }, /** - * Lookup333: ethereum::transaction::AccessListItem + * Lookup334: ethereum::transaction::AccessListItem **/ EthereumTransactionAccessListItem: { address: 'H160', storageKeys: 'Vec' }, /** - * Lookup334: ethereum::transaction::EIP1559Transaction + * Lookup335: ethereum::transaction::EIP1559Transaction **/ EthereumTransactionEip1559Transaction: { chainId: 'u64', @@ -2859,7 +2884,7 @@ s: 'H256' }, /** - * Lookup335: pallet_evm_migration::pallet::Call + * Lookup336: pallet_evm_migration::pallet::Call **/ PalletEvmMigrationCall: { _enum: { @@ -2883,13 +2908,13 @@ } }, /** - * Lookup339: pallet_maintenance::pallet::Call + * Lookup340: pallet_maintenance::pallet::Call **/ PalletMaintenanceCall: { _enum: ['enable', 'disable'] }, /** - * Lookup340: pallet_test_utils::pallet::Call + * Lookup341: pallet_test_utils::pallet::Call **/ PalletTestUtilsCall: { _enum: { @@ -2908,32 +2933,32 @@ } }, /** - * Lookup342: pallet_sudo::pallet::Error + * Lookup343: pallet_sudo::pallet::Error **/ PalletSudoError: { _enum: ['RequireSudo'] }, /** - * Lookup344: orml_vesting::module::Error + * Lookup345: orml_vesting::module::Error **/ OrmlVestingModuleError: { _enum: ['ZeroVestingPeriod', 'ZeroVestingPeriodCount', 'InsufficientBalanceToLock', 'TooManyVestingSchedules', 'AmountLow', 'MaxVestingSchedulesExceeded'] }, /** - * Lookup345: orml_xtokens::module::Error + * Lookup346: orml_xtokens::module::Error **/ OrmlXtokensModuleError: { _enum: ['AssetHasNoReserve', 'NotCrossChainTransfer', 'InvalidDest', 'NotCrossChainTransferableCurrency', 'UnweighableMessage', 'XcmExecutionFailed', 'CannotReanchor', 'InvalidAncestry', 'InvalidAsset', 'DestinationNotInvertible', 'BadVersion', 'DistinctReserveForAssetAndFee', 'ZeroFee', 'ZeroAmount', 'TooManyAssetsBeingSent', 'AssetIndexNonExistent', 'FeeNotEnough', 'NotSupportedMultiLocation', 'MinXcmFeeNotDefined'] }, /** - * Lookup348: orml_tokens::BalanceLock + * Lookup349: orml_tokens::BalanceLock **/ OrmlTokensBalanceLock: { id: '[u8;8]', amount: 'u128' }, /** - * Lookup350: orml_tokens::AccountData + * Lookup351: orml_tokens::AccountData **/ OrmlTokensAccountData: { free: 'u128', @@ -2941,20 +2966,20 @@ frozen: 'u128' }, /** - * Lookup352: orml_tokens::ReserveData + * Lookup353: orml_tokens::ReserveData **/ OrmlTokensReserveData: { id: 'Null', amount: 'u128' }, /** - * Lookup354: orml_tokens::module::Error + * Lookup355: orml_tokens::module::Error **/ OrmlTokensModuleError: { _enum: ['BalanceTooLow', 'AmountIntoBalanceFailed', 'LiquidityRestrictions', 'MaxLocksExceeded', 'KeepAlive', 'ExistentialDeposit', 'DeadAccount', 'TooManyReserves'] }, /** - * Lookup356: cumulus_pallet_xcmp_queue::InboundChannelDetails + * Lookup357: cumulus_pallet_xcmp_queue::InboundChannelDetails **/ CumulusPalletXcmpQueueInboundChannelDetails: { sender: 'u32', @@ -2962,19 +2987,19 @@ messageMetadata: 'Vec<(u32,PolkadotParachainPrimitivesXcmpMessageFormat)>' }, /** - * Lookup357: cumulus_pallet_xcmp_queue::InboundState + * Lookup358: cumulus_pallet_xcmp_queue::InboundState **/ CumulusPalletXcmpQueueInboundState: { _enum: ['Ok', 'Suspended'] }, /** - * Lookup360: polkadot_parachain::primitives::XcmpMessageFormat + * Lookup361: polkadot_parachain::primitives::XcmpMessageFormat **/ PolkadotParachainPrimitivesXcmpMessageFormat: { _enum: ['ConcatenatedVersionedXcm', 'ConcatenatedEncodedBlob', 'Signals'] }, /** - * Lookup363: cumulus_pallet_xcmp_queue::OutboundChannelDetails + * Lookup364: cumulus_pallet_xcmp_queue::OutboundChannelDetails **/ CumulusPalletXcmpQueueOutboundChannelDetails: { recipient: 'u32', @@ -2984,13 +3009,13 @@ lastIndex: 'u16' }, /** - * Lookup364: cumulus_pallet_xcmp_queue::OutboundState + * Lookup365: cumulus_pallet_xcmp_queue::OutboundState **/ CumulusPalletXcmpQueueOutboundState: { _enum: ['Ok', 'Suspended'] }, /** - * Lookup366: cumulus_pallet_xcmp_queue::QueueConfigData + * Lookup367: cumulus_pallet_xcmp_queue::QueueConfigData **/ CumulusPalletXcmpQueueQueueConfigData: { suspendThreshold: 'u32', @@ -3001,29 +3026,29 @@ xcmpMaxIndividualWeight: 'SpWeightsWeightV2Weight' }, /** - * Lookup368: cumulus_pallet_xcmp_queue::pallet::Error + * Lookup369: cumulus_pallet_xcmp_queue::pallet::Error **/ CumulusPalletXcmpQueueError: { _enum: ['FailedToSend', 'BadXcmOrigin', 'BadXcm', 'BadOverweightIndex', 'WeightOverLimit'] }, /** - * Lookup369: pallet_xcm::pallet::Error + * Lookup370: pallet_xcm::pallet::Error **/ PalletXcmError: { _enum: ['Unreachable', 'SendFailure', 'Filtered', 'UnweighableMessage', 'DestinationNotInvertible', 'Empty', 'CannotReanchor', 'TooManyAssets', 'InvalidOrigin', 'BadVersion', 'BadLocation', 'NoSubscription', 'AlreadySubscribed'] }, /** - * Lookup370: cumulus_pallet_xcm::pallet::Error + * Lookup371: cumulus_pallet_xcm::pallet::Error **/ CumulusPalletXcmError: 'Null', /** - * Lookup371: cumulus_pallet_dmp_queue::ConfigData + * Lookup372: cumulus_pallet_dmp_queue::ConfigData **/ CumulusPalletDmpQueueConfigData: { maxIndividual: 'SpWeightsWeightV2Weight' }, /** - * Lookup372: cumulus_pallet_dmp_queue::PageIndexData + * Lookup373: cumulus_pallet_dmp_queue::PageIndexData **/ CumulusPalletDmpQueuePageIndexData: { beginUsed: 'u32', @@ -3031,25 +3056,25 @@ overweightCount: 'u64' }, /** - * Lookup375: cumulus_pallet_dmp_queue::pallet::Error + * Lookup376: cumulus_pallet_dmp_queue::pallet::Error **/ CumulusPalletDmpQueueError: { _enum: ['Unknown', 'OverLimit'] }, /** - * Lookup379: pallet_unique::Error + * Lookup380: pallet_unique::Error **/ PalletUniqueError: { _enum: ['CollectionDecimalPointLimitExceeded', 'EmptyArgument', 'RepartitionCalledOnNonRefungibleCollection'] }, /** - * Lookup380: pallet_configuration::pallet::Error + * Lookup381: pallet_configuration::pallet::Error **/ PalletConfigurationError: { _enum: ['InconsistentConfiguration'] }, /** - * Lookup381: up_data_structs::Collection + * Lookup382: up_data_structs::Collection **/ UpDataStructsCollection: { owner: 'AccountId32', @@ -3063,7 +3088,7 @@ flags: '[u8;1]' }, /** - * Lookup382: up_data_structs::SponsorshipState + * Lookup383: up_data_structs::SponsorshipState **/ UpDataStructsSponsorshipStateAccountId32: { _enum: { @@ -3073,7 +3098,7 @@ } }, /** - * Lookup384: up_data_structs::Properties + * Lookup385: up_data_structs::Properties **/ UpDataStructsProperties: { map: 'UpDataStructsPropertiesMapBoundedVec', @@ -3081,15 +3106,15 @@ spaceLimit: 'u32' }, /** - * Lookup385: up_data_structs::PropertiesMap> + * Lookup386: up_data_structs::PropertiesMap> **/ UpDataStructsPropertiesMapBoundedVec: 'BTreeMap', /** - * Lookup390: up_data_structs::PropertiesMap + * Lookup391: up_data_structs::PropertiesMap **/ UpDataStructsPropertiesMapPropertyPermission: 'BTreeMap', /** - * Lookup397: up_data_structs::CollectionStats + * Lookup398: up_data_structs::CollectionStats **/ UpDataStructsCollectionStats: { created: 'u32', @@ -3097,18 +3122,18 @@ alive: 'u32' }, /** - * Lookup398: up_data_structs::TokenChild + * Lookup399: up_data_structs::TokenChild **/ UpDataStructsTokenChild: { token: 'u32', collection: 'u32' }, /** - * Lookup399: PhantomType::up_data_structs + * Lookup400: PhantomType::up_data_structs **/ PhantomTypeUpDataStructs: '[(UpDataStructsTokenData,UpDataStructsRpcCollection,RmrkTraitsCollectionCollectionInfo,RmrkTraitsNftNftInfo,RmrkTraitsResourceResourceInfo,RmrkTraitsPropertyPropertyInfo,RmrkTraitsBaseBaseInfo,RmrkTraitsPartPartType,RmrkTraitsTheme,RmrkTraitsNftNftChild,UpPovEstimateRpcPovInfo);0]', /** - * Lookup401: up_data_structs::TokenData> + * Lookup402: up_data_structs::TokenData> **/ UpDataStructsTokenData: { properties: 'Vec', @@ -3116,7 +3141,7 @@ pieces: 'u128' }, /** - * Lookup403: up_data_structs::RpcCollection + * Lookup404: up_data_structs::RpcCollection **/ UpDataStructsRpcCollection: { owner: 'AccountId32', @@ -3133,14 +3158,14 @@ flags: 'UpDataStructsRpcCollectionFlags' }, /** - * Lookup404: up_data_structs::RpcCollectionFlags + * Lookup405: up_data_structs::RpcCollectionFlags **/ UpDataStructsRpcCollectionFlags: { foreign: 'bool', erc721metadata: 'bool' }, /** - * Lookup405: rmrk_traits::collection::CollectionInfo, sp_core::bounded::bounded_vec::BoundedVec, sp_core::crypto::AccountId32> + * Lookup406: rmrk_traits::collection::CollectionInfo, sp_core::bounded::bounded_vec::BoundedVec, sp_core::crypto::AccountId32> **/ RmrkTraitsCollectionCollectionInfo: { issuer: 'AccountId32', @@ -3150,7 +3175,7 @@ nftsCount: 'u32' }, /** - * Lookup406: rmrk_traits::nft::NftInfo> + * Lookup407: rmrk_traits::nft::NftInfo> **/ RmrkTraitsNftNftInfo: { owner: 'RmrkTraitsNftAccountIdOrCollectionNftTuple', @@ -3160,14 +3185,14 @@ pending: 'bool' }, /** - * Lookup408: rmrk_traits::nft::RoyaltyInfo + * Lookup409: rmrk_traits::nft::RoyaltyInfo **/ RmrkTraitsNftRoyaltyInfo: { recipient: 'AccountId32', amount: 'Permill' }, /** - * Lookup409: rmrk_traits::resource::ResourceInfo, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup410: rmrk_traits::resource::ResourceInfo, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsResourceResourceInfo: { id: 'u32', @@ -3176,14 +3201,14 @@ pendingRemoval: 'bool' }, /** - * Lookup410: rmrk_traits::property::PropertyInfo, sp_core::bounded::bounded_vec::BoundedVec> + * Lookup411: rmrk_traits::property::PropertyInfo, sp_core::bounded::bounded_vec::BoundedVec> **/ RmrkTraitsPropertyPropertyInfo: { key: 'Bytes', value: 'Bytes' }, /** - * Lookup411: rmrk_traits::base::BaseInfo> + * Lookup412: rmrk_traits::base::BaseInfo> **/ RmrkTraitsBaseBaseInfo: { issuer: 'AccountId32', @@ -3191,14 +3216,14 @@ symbol: 'Bytes' }, /** - * Lookup412: rmrk_traits::nft::NftChild + * Lookup413: rmrk_traits::nft::NftChild **/ RmrkTraitsNftNftChild: { collectionId: 'u32', nftId: 'u32' }, /** - * Lookup413: up_pov_estimate_rpc::PovInfo + * Lookup414: up_pov_estimate_rpc::PovInfo **/ UpPovEstimateRpcPovInfo: { proofSize: 'u64', @@ -3208,7 +3233,7 @@ keyValues: 'Vec' }, /** - * Lookup416: sp_runtime::transaction_validity::TransactionValidityError + * Lookup417: sp_runtime::transaction_validity::TransactionValidityError **/ SpRuntimeTransactionValidityTransactionValidityError: { _enum: { @@ -3217,7 +3242,7 @@ } }, /** - * Lookup417: sp_runtime::transaction_validity::InvalidTransaction + * Lookup418: sp_runtime::transaction_validity::InvalidTransaction **/ SpRuntimeTransactionValidityInvalidTransaction: { _enum: { @@ -3235,7 +3260,7 @@ } }, /** - * Lookup418: sp_runtime::transaction_validity::UnknownTransaction + * Lookup419: sp_runtime::transaction_validity::UnknownTransaction **/ SpRuntimeTransactionValidityUnknownTransaction: { _enum: { @@ -3245,86 +3270,86 @@ } }, /** - * Lookup420: up_pov_estimate_rpc::TrieKeyValue + * Lookup421: up_pov_estimate_rpc::TrieKeyValue **/ UpPovEstimateRpcTrieKeyValue: { key: 'Bytes', value: 'Bytes' }, /** - * Lookup422: pallet_common::pallet::Error + * Lookup423: pallet_common::pallet::Error **/ PalletCommonError: { _enum: ['CollectionNotFound', 'MustBeTokenOwner', 'NoPermission', 'CantDestroyNotEmptyCollection', 'PublicMintingNotAllowed', 'AddressNotInAllowlist', 'CollectionNameLimitExceeded', 'CollectionDescriptionLimitExceeded', 'CollectionTokenPrefixLimitExceeded', 'TotalCollectionsLimitExceeded', 'CollectionAdminCountExceeded', 'CollectionLimitBoundsExceeded', 'OwnerPermissionsCantBeReverted', 'TransferNotAllowed', 'AccountTokenLimitExceeded', 'CollectionTokenLimitExceeded', 'MetadataFlagFrozen', 'TokenNotFound', 'TokenValueTooLow', 'ApprovedValueTooLow', 'CantApproveMoreThanOwned', 'AddressIsZero', 'UnsupportedOperation', 'NotSufficientFounds', 'UserIsNotAllowedToNest', 'SourceCollectionIsNotAllowedToNest', 'CollectionFieldSizeExceeded', 'NoSpaceForProperty', 'PropertyLimitReached', 'PropertyKeyIsTooLong', 'InvalidCharacterInPropertyKey', 'EmptyPropertyKey', 'CollectionIsExternal', 'CollectionIsInternal', 'ConfirmSponsorshipFail', 'UserIsNotCollectionAdmin'] }, /** - * Lookup424: pallet_fungible::pallet::Error + * Lookup425: pallet_fungible::pallet::Error **/ PalletFungibleError: { _enum: ['NotFungibleDataUsedToMintFungibleCollectionToken', 'FungibleItemsHaveNoId', 'FungibleItemsDontHaveData', 'FungibleDisallowsNesting', 'SettingPropertiesNotAllowed', 'SettingAllowanceForAllNotAllowed', 'FungibleTokensAreAlwaysValid'] }, /** - * Lookup428: pallet_refungible::pallet::Error + * Lookup429: pallet_refungible::pallet::Error **/ PalletRefungibleError: { _enum: ['NotRefungibleDataUsedToMintFungibleCollectionToken', 'WrongRefungiblePieces', 'RepartitionWhileNotOwningAllPieces', 'RefungibleDisallowsNesting', 'SettingPropertiesNotAllowed'] }, /** - * Lookup429: pallet_nonfungible::ItemData> + * Lookup430: pallet_nonfungible::ItemData> **/ PalletNonfungibleItemData: { owner: 'PalletEvmAccountBasicCrossAccountIdRepr' }, /** - * Lookup431: up_data_structs::PropertyScope + * Lookup432: up_data_structs::PropertyScope **/ UpDataStructsPropertyScope: { _enum: ['None', 'Rmrk'] }, /** - * Lookup434: pallet_nonfungible::pallet::Error + * Lookup435: pallet_nonfungible::pallet::Error **/ PalletNonfungibleError: { _enum: ['NotNonfungibleDataUsedToMintFungibleCollectionToken', 'NonfungibleItemsHaveNoAmount', 'CantBurnNftWithChildren'] }, /** - * Lookup435: pallet_structure::pallet::Error + * Lookup436: pallet_structure::pallet::Error **/ PalletStructureError: { _enum: ['OuroborosDetected', 'DepthLimit', 'BreadthLimit', 'TokenNotFound'] }, /** - * Lookup436: pallet_rmrk_core::pallet::Error + * Lookup437: pallet_rmrk_core::pallet::Error **/ PalletRmrkCoreError: { _enum: ['CorruptedCollectionType', 'RmrkPropertyKeyIsTooLong', 'RmrkPropertyValueIsTooLong', 'RmrkPropertyIsNotFound', 'UnableToDecodeRmrkData', 'CollectionNotEmpty', 'NoAvailableCollectionId', 'NoAvailableNftId', 'CollectionUnknown', 'NoPermission', 'NonTransferable', 'CollectionFullOrLocked', 'ResourceDoesntExist', 'CannotSendToDescendentOrSelf', 'CannotAcceptNonOwnedNft', 'CannotRejectNonOwnedNft', 'CannotRejectNonPendingNft', 'ResourceNotPending', 'NoAvailableResourceId'] }, /** - * Lookup438: pallet_rmrk_equip::pallet::Error + * Lookup439: pallet_rmrk_equip::pallet::Error **/ PalletRmrkEquipError: { _enum: ['PermissionError', 'NoAvailableBaseId', 'NoAvailablePartId', 'BaseDoesntExist', 'NeedsDefaultThemeFirst', 'PartDoesntExist', 'NoEquippableOnFixedPart'] }, /** - * Lookup444: pallet_app_promotion::pallet::Error + * Lookup445: pallet_app_promotion::pallet::Error **/ PalletAppPromotionError: { _enum: ['AdminNotSet', 'NoPermission', 'NotSufficientFunds', 'PendingForBlockOverflow', 'SponsorNotSet', 'IncorrectLockedBalanceOperation'] }, /** - * Lookup445: pallet_foreign_assets::module::Error + * Lookup446: pallet_foreign_assets::module::Error **/ PalletForeignAssetsModuleError: { _enum: ['BadLocation', 'MultiLocationExisted', 'AssetIdNotExists', 'AssetIdExisted'] }, /** - * Lookup447: pallet_evm::pallet::Error + * Lookup448: pallet_evm::pallet::Error **/ PalletEvmError: { _enum: ['BalanceLow', 'FeeOverflow', 'PaymentOverflow', 'WithdrawFailed', 'GasPriceTooLow', 'InvalidNonce', 'GasLimitTooLow', 'GasLimitTooHigh', 'Undefined', 'Reentrancy', 'TransactionMustComeFromEOA'] }, /** - * Lookup450: fp_rpc::TransactionStatus + * Lookup451: fp_rpc::TransactionStatus **/ FpRpcTransactionStatus: { transactionHash: 'H256', @@ -3336,11 +3361,11 @@ logsBloom: 'EthbloomBloom' }, /** - * Lookup452: ethbloom::Bloom + * Lookup453: ethbloom::Bloom **/ EthbloomBloom: '[u8;256]', /** - * Lookup454: ethereum::receipt::ReceiptV3 + * Lookup455: ethereum::receipt::ReceiptV3 **/ EthereumReceiptReceiptV3: { _enum: { @@ -3350,7 +3375,7 @@ } }, /** - * Lookup455: ethereum::receipt::EIP658ReceiptData + * Lookup456: ethereum::receipt::EIP658ReceiptData **/ EthereumReceiptEip658ReceiptData: { statusCode: 'u8', @@ -3359,7 +3384,7 @@ logs: 'Vec' }, /** - * Lookup456: ethereum::block::Block + * Lookup457: ethereum::block::Block **/ EthereumBlock: { header: 'EthereumHeader', @@ -3367,7 +3392,7 @@ ommers: 'Vec' }, /** - * Lookup457: ethereum::header::Header + * Lookup458: ethereum::header::Header **/ EthereumHeader: { parentHash: 'H256', @@ -3387,23 +3412,23 @@ nonce: 'EthereumTypesHashH64' }, /** - * Lookup458: ethereum_types::hash::H64 + * Lookup459: ethereum_types::hash::H64 **/ EthereumTypesHashH64: '[u8;8]', /** - * Lookup463: pallet_ethereum::pallet::Error + * Lookup464: pallet_ethereum::pallet::Error **/ PalletEthereumError: { _enum: ['InvalidSignature', 'PreLogExists'] }, /** - * Lookup464: pallet_evm_coder_substrate::pallet::Error + * Lookup465: pallet_evm_coder_substrate::pallet::Error **/ PalletEvmCoderSubstrateError: { _enum: ['OutOfGas', 'OutOfFund'] }, /** - * Lookup465: up_data_structs::SponsorshipState> + * Lookup466: up_data_structs::SponsorshipState> **/ UpDataStructsSponsorshipStateBasicCrossAccountIdRepr: { _enum: { @@ -3413,35 +3438,35 @@ } }, /** - * Lookup466: pallet_evm_contract_helpers::SponsoringModeT + * Lookup467: pallet_evm_contract_helpers::SponsoringModeT **/ PalletEvmContractHelpersSponsoringModeT: { _enum: ['Disabled', 'Allowlisted', 'Generous'] }, /** - * Lookup472: pallet_evm_contract_helpers::pallet::Error + * Lookup473: pallet_evm_contract_helpers::pallet::Error **/ PalletEvmContractHelpersError: { _enum: ['NoPermission', 'NoPendingSponsor', 'TooManyMethodsHaveSponsoredLimit'] }, /** - * Lookup473: pallet_evm_migration::pallet::Error + * Lookup474: pallet_evm_migration::pallet::Error **/ PalletEvmMigrationError: { _enum: ['AccountNotEmpty', 'AccountIsNotMigrating', 'BadEvent'] }, /** - * Lookup474: pallet_maintenance::pallet::Error + * Lookup475: pallet_maintenance::pallet::Error **/ PalletMaintenanceError: 'Null', /** - * Lookup475: pallet_test_utils::pallet::Error + * Lookup476: pallet_test_utils::pallet::Error **/ PalletTestUtilsError: { _enum: ['TestPalletDisabled', 'TriggerRollback'] }, /** - * Lookup477: sp_runtime::MultiSignature + * Lookup478: sp_runtime::MultiSignature **/ SpRuntimeMultiSignature: { _enum: { @@ -3451,51 +3476,55 @@ } }, /** - * Lookup478: sp_core::ed25519::Signature + * Lookup479: sp_core::ed25519::Signature **/ SpCoreEd25519Signature: '[u8;64]', /** - * Lookup480: sp_core::sr25519::Signature + * Lookup481: sp_core::sr25519::Signature **/ SpCoreSr25519Signature: '[u8;64]', /** - * Lookup481: sp_core::ecdsa::Signature + * Lookup482: sp_core::ecdsa::Signature **/ SpCoreEcdsaSignature: '[u8;65]', /** - * Lookup484: frame_system::extensions::check_spec_version::CheckSpecVersion + * Lookup485: frame_system::extensions::check_spec_version::CheckSpecVersion **/ FrameSystemExtensionsCheckSpecVersion: 'Null', /** - * Lookup485: frame_system::extensions::check_tx_version::CheckTxVersion + * Lookup486: frame_system::extensions::check_tx_version::CheckTxVersion **/ FrameSystemExtensionsCheckTxVersion: 'Null', /** - * Lookup486: frame_system::extensions::check_genesis::CheckGenesis + * Lookup487: frame_system::extensions::check_genesis::CheckGenesis **/ FrameSystemExtensionsCheckGenesis: 'Null', /** - * Lookup489: frame_system::extensions::check_nonce::CheckNonce + * Lookup490: frame_system::extensions::check_nonce::CheckNonce **/ FrameSystemExtensionsCheckNonce: 'Compact', /** - * Lookup490: frame_system::extensions::check_weight::CheckWeight + * Lookup491: frame_system::extensions::check_weight::CheckWeight **/ FrameSystemExtensionsCheckWeight: 'Null', /** - * Lookup491: opal_runtime::runtime_common::maintenance::CheckMaintenance + * Lookup492: opal_runtime::runtime_common::maintenance::CheckMaintenance **/ OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance: 'Null', /** - * Lookup492: pallet_template_transaction_payment::ChargeTransactionPayment + * Lookup493: opal_runtime::runtime_common::identity::DisableIdentityCalls **/ + OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls: 'Null', + /** + * Lookup494: pallet_template_transaction_payment::ChargeTransactionPayment + **/ PalletTemplateTransactionPaymentChargeTransactionPayment: 'Compact', /** - * Lookup493: opal_runtime::Runtime + * Lookup495: opal_runtime::Runtime **/ OpalRuntimeRuntime: 'Null', /** - * Lookup494: pallet_ethereum::FakeTransactionFinalizer + * Lookup496: pallet_ethereum::FakeTransactionFinalizer **/ PalletEthereumFakeTransactionFinalizer: 'Null' }; --- a/tests/src/interfaces/registry.ts +++ b/tests/src/interfaces/registry.ts @@ -5,7 +5,7 @@ // this is required to allow for ambient/previous definitions import '@polkadot/types/types/registry'; -import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchDispatchClass, FrameSupportDispatchDispatchInfo, FrameSupportDispatchPays, FrameSupportDispatchPerDispatchClassU32, FrameSupportDispatchPerDispatchClassWeight, FrameSupportDispatchPerDispatchClassWeightsPerClass, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckTxVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensModuleCall, OrmlTokensModuleError, OrmlTokensModuleEvent, OrmlTokensReserveData, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, OrmlXtokensModuleCall, OrmlXtokensModuleError, OrmlXtokensModuleEvent, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationAppPromotionConfiguration, PalletConfigurationCall, PalletConfigurationError, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletEvmMigrationEvent, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletForeignAssetsModuleCall, PalletForeignAssetsModuleError, PalletForeignAssetsModuleEvent, PalletForeignAssetsNativeCurrency, PalletFungibleError, PalletInflationCall, PalletMaintenanceCall, PalletMaintenanceError, PalletMaintenanceEvent, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTestUtilsCall, PalletTestUtilsError, PalletTestUtilsEvent, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionValidityInvalidTransaction, SpRuntimeTransactionValidityTransactionValidityError, SpRuntimeTransactionValidityUnknownTransaction, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, SpWeightsWeightV2Weight, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsRpcCollectionFlags, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, UpPovEstimateRpcPovInfo, UpPovEstimateRpcTrieKeyValue, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAsset, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup'; +import type { CumulusPalletDmpQueueCall, CumulusPalletDmpQueueConfigData, CumulusPalletDmpQueueError, CumulusPalletDmpQueueEvent, CumulusPalletDmpQueuePageIndexData, CumulusPalletParachainSystemCall, CumulusPalletParachainSystemError, CumulusPalletParachainSystemEvent, CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot, CumulusPalletXcmCall, CumulusPalletXcmError, CumulusPalletXcmEvent, CumulusPalletXcmpQueueCall, CumulusPalletXcmpQueueError, CumulusPalletXcmpQueueEvent, CumulusPalletXcmpQueueInboundChannelDetails, CumulusPalletXcmpQueueInboundState, CumulusPalletXcmpQueueOutboundChannelDetails, CumulusPalletXcmpQueueOutboundState, CumulusPalletXcmpQueueQueueConfigData, CumulusPrimitivesParachainInherentParachainInherentData, EthbloomBloom, EthereumBlock, EthereumHeader, EthereumLog, EthereumReceiptEip658ReceiptData, EthereumReceiptReceiptV3, EthereumTransactionAccessListItem, EthereumTransactionEip1559Transaction, EthereumTransactionEip2930Transaction, EthereumTransactionLegacyTransaction, EthereumTransactionTransactionAction, EthereumTransactionTransactionSignature, EthereumTransactionTransactionV2, EthereumTypesHashH64, EvmCoreErrorExitError, EvmCoreErrorExitFatal, EvmCoreErrorExitReason, EvmCoreErrorExitRevert, EvmCoreErrorExitSucceed, FpRpcTransactionStatus, FrameSupportDispatchDispatchClass, FrameSupportDispatchDispatchInfo, FrameSupportDispatchPays, FrameSupportDispatchPerDispatchClassU32, FrameSupportDispatchPerDispatchClassWeight, FrameSupportDispatchPerDispatchClassWeightsPerClass, FrameSupportPalletId, FrameSupportTokensMiscBalanceStatus, FrameSystemAccountInfo, FrameSystemCall, FrameSystemError, FrameSystemEvent, FrameSystemEventRecord, FrameSystemExtensionsCheckGenesis, FrameSystemExtensionsCheckNonce, FrameSystemExtensionsCheckSpecVersion, FrameSystemExtensionsCheckTxVersion, FrameSystemExtensionsCheckWeight, FrameSystemLastRuntimeUpgradeInfo, FrameSystemLimitsBlockLength, FrameSystemLimitsBlockWeights, FrameSystemLimitsWeightsPerClass, FrameSystemPhase, OpalRuntimeRuntime, OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls, OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance, OrmlTokensAccountData, OrmlTokensBalanceLock, OrmlTokensModuleCall, OrmlTokensModuleError, OrmlTokensModuleEvent, OrmlTokensReserveData, OrmlVestingModuleCall, OrmlVestingModuleError, OrmlVestingModuleEvent, OrmlVestingVestingSchedule, OrmlXtokensModuleCall, OrmlXtokensModuleError, OrmlXtokensModuleEvent, PalletAppPromotionCall, PalletAppPromotionError, PalletAppPromotionEvent, PalletBalancesAccountData, PalletBalancesBalanceLock, PalletBalancesCall, PalletBalancesError, PalletBalancesEvent, PalletBalancesReasons, PalletBalancesReserveData, PalletCommonError, PalletCommonEvent, PalletConfigurationAppPromotionConfiguration, PalletConfigurationCall, PalletConfigurationError, PalletConfigurationEvent, PalletEthereumCall, PalletEthereumError, PalletEthereumEvent, PalletEthereumFakeTransactionFinalizer, PalletEvmAccountBasicCrossAccountIdRepr, PalletEvmCall, PalletEvmCoderSubstrateError, PalletEvmContractHelpersError, PalletEvmContractHelpersEvent, PalletEvmContractHelpersSponsoringModeT, PalletEvmError, PalletEvmEvent, PalletEvmMigrationCall, PalletEvmMigrationError, PalletEvmMigrationEvent, PalletForeignAssetsAssetIds, PalletForeignAssetsModuleAssetMetadata, PalletForeignAssetsModuleCall, PalletForeignAssetsModuleError, PalletForeignAssetsModuleEvent, PalletForeignAssetsNativeCurrency, PalletFungibleError, PalletInflationCall, PalletMaintenanceCall, PalletMaintenanceError, PalletMaintenanceEvent, PalletNonfungibleError, PalletNonfungibleItemData, PalletRefungibleError, PalletRmrkCoreCall, PalletRmrkCoreError, PalletRmrkCoreEvent, PalletRmrkEquipCall, PalletRmrkEquipError, PalletRmrkEquipEvent, PalletStructureCall, PalletStructureError, PalletStructureEvent, PalletSudoCall, PalletSudoError, PalletSudoEvent, PalletTemplateTransactionPaymentCall, PalletTemplateTransactionPaymentChargeTransactionPayment, PalletTestUtilsCall, PalletTestUtilsError, PalletTestUtilsEvent, PalletTimestampCall, PalletTransactionPaymentEvent, PalletTransactionPaymentReleases, PalletTreasuryCall, PalletTreasuryError, PalletTreasuryEvent, PalletTreasuryProposal, PalletUniqueCall, PalletUniqueError, PalletXcmCall, PalletXcmError, PalletXcmEvent, PhantomTypeUpDataStructs, PolkadotCorePrimitivesInboundDownwardMessage, PolkadotCorePrimitivesInboundHrmpMessage, PolkadotCorePrimitivesOutboundHrmpMessage, PolkadotParachainPrimitivesXcmpMessageFormat, PolkadotPrimitivesV2AbridgedHostConfiguration, PolkadotPrimitivesV2AbridgedHrmpChannel, PolkadotPrimitivesV2PersistedValidationData, PolkadotPrimitivesV2UpgradeRestriction, RmrkTraitsBaseBaseInfo, RmrkTraitsCollectionCollectionInfo, RmrkTraitsNftAccountIdOrCollectionNftTuple, RmrkTraitsNftNftChild, RmrkTraitsNftNftInfo, RmrkTraitsNftRoyaltyInfo, RmrkTraitsPartEquippableList, RmrkTraitsPartFixedPart, RmrkTraitsPartPartType, RmrkTraitsPartSlotPart, RmrkTraitsPropertyPropertyInfo, RmrkTraitsResourceBasicResource, RmrkTraitsResourceComposableResource, RmrkTraitsResourceResourceInfo, RmrkTraitsResourceResourceTypes, RmrkTraitsResourceSlotResource, RmrkTraitsTheme, RmrkTraitsThemeThemeProperty, SpCoreEcdsaSignature, SpCoreEd25519Signature, SpCoreSr25519Signature, SpRuntimeArithmeticError, SpRuntimeDigest, SpRuntimeDigestDigestItem, SpRuntimeDispatchError, SpRuntimeModuleError, SpRuntimeMultiSignature, SpRuntimeTokenError, SpRuntimeTransactionValidityInvalidTransaction, SpRuntimeTransactionValidityTransactionValidityError, SpRuntimeTransactionValidityUnknownTransaction, SpRuntimeTransactionalError, SpTrieStorageProof, SpVersionRuntimeVersion, SpWeightsRuntimeDbWeight, SpWeightsWeightV2Weight, UpDataStructsAccessMode, UpDataStructsCollection, UpDataStructsCollectionLimits, UpDataStructsCollectionMode, UpDataStructsCollectionPermissions, UpDataStructsCollectionStats, UpDataStructsCreateCollectionData, UpDataStructsCreateFungibleData, UpDataStructsCreateItemData, UpDataStructsCreateItemExData, UpDataStructsCreateNftData, UpDataStructsCreateNftExData, UpDataStructsCreateReFungibleData, UpDataStructsCreateRefungibleExMultipleOwners, UpDataStructsCreateRefungibleExSingleOwner, UpDataStructsNestingPermissions, UpDataStructsOwnerRestrictedSet, UpDataStructsProperties, UpDataStructsPropertiesMapBoundedVec, UpDataStructsPropertiesMapPropertyPermission, UpDataStructsProperty, UpDataStructsPropertyKeyPermission, UpDataStructsPropertyPermission, UpDataStructsPropertyScope, UpDataStructsRpcCollection, UpDataStructsRpcCollectionFlags, UpDataStructsSponsoringRateLimit, UpDataStructsSponsorshipStateAccountId32, UpDataStructsSponsorshipStateBasicCrossAccountIdRepr, UpDataStructsTokenChild, UpDataStructsTokenData, UpPovEstimateRpcPovInfo, UpPovEstimateRpcTrieKeyValue, XcmDoubleEncoded, XcmV0Junction, XcmV0JunctionBodyId, XcmV0JunctionBodyPart, XcmV0JunctionNetworkId, XcmV0MultiAsset, XcmV0MultiLocation, XcmV0Order, XcmV0OriginKind, XcmV0Response, XcmV0Xcm, XcmV1Junction, XcmV1MultiAsset, XcmV1MultiLocation, XcmV1MultiassetAssetId, XcmV1MultiassetAssetInstance, XcmV1MultiassetFungibility, XcmV1MultiassetMultiAssetFilter, XcmV1MultiassetMultiAssets, XcmV1MultiassetWildFungibility, XcmV1MultiassetWildMultiAsset, XcmV1MultilocationJunctions, XcmV1Order, XcmV1Response, XcmV1Xcm, XcmV2Instruction, XcmV2Response, XcmV2TraitsError, XcmV2TraitsOutcome, XcmV2WeightLimit, XcmV2Xcm, XcmVersionedMultiAsset, XcmVersionedMultiAssets, XcmVersionedMultiLocation, XcmVersionedXcm } from '@polkadot/types/lookup'; declare module '@polkadot/types/types/registry' { interface InterfaceTypes { @@ -74,6 +74,7 @@ FrameSystemLimitsWeightsPerClass: FrameSystemLimitsWeightsPerClass; FrameSystemPhase: FrameSystemPhase; OpalRuntimeRuntime: OpalRuntimeRuntime; + OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls: OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls; OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance: OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance; OrmlTokensAccountData: OrmlTokensAccountData; OrmlTokensBalanceLock: OrmlTokensBalanceLock; @@ -103,6 +104,7 @@ PalletConfigurationAppPromotionConfiguration: PalletConfigurationAppPromotionConfiguration; PalletConfigurationCall: PalletConfigurationCall; PalletConfigurationError: PalletConfigurationError; + PalletConfigurationEvent: PalletConfigurationEvent; PalletEthereumCall: PalletEthereumCall; PalletEthereumError: PalletEthereumError; PalletEthereumEvent: PalletEthereumEvent; --- a/tests/src/interfaces/types-lookup.ts +++ b/tests/src/interfaces/types-lookup.ts @@ -1109,7 +1109,24 @@ readonly type: 'InvalidFormat' | 'UnsupportedVersion' | 'ExecutedDownward' | 'WeightExhausted' | 'OverweightEnqueued' | 'OverweightServiced'; } - /** @name PalletCommonEvent (89) */ + /** @name PalletConfigurationEvent (89) */ + interface PalletConfigurationEvent extends Enum { + readonly isNewDesiredCollators: boolean; + readonly asNewDesiredCollators: { + readonly desiredCollators: Option; + } & Struct; + readonly isNewCollatorLicenseBond: boolean; + readonly asNewCollatorLicenseBond: { + readonly bondCost: Option; + } & Struct; + readonly isNewCollatorKickThreshold: boolean; + readonly asNewCollatorKickThreshold: { + readonly lengthInBlocks: Option; + } & Struct; + readonly type: 'NewDesiredCollators' | 'NewCollatorLicenseBond' | 'NewCollatorKickThreshold'; + } + + /** @name PalletCommonEvent (92) */ interface PalletCommonEvent extends Enum { readonly isCollectionCreated: boolean; readonly asCollectionCreated: ITuple<[u32, u8, AccountId32]>; @@ -1158,7 +1175,7 @@ readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'ItemCreated' | 'ItemDestroyed' | 'Transfer' | 'Approved' | 'ApprovedForAll' | 'CollectionPropertySet' | 'CollectionPropertyDeleted' | 'TokenPropertySet' | 'TokenPropertyDeleted' | 'PropertyPermissionSet' | 'AllowListAddressAdded' | 'AllowListAddressRemoved' | 'CollectionAdminAdded' | 'CollectionAdminRemoved' | 'CollectionLimitSet' | 'CollectionOwnerChanged' | 'CollectionPermissionSet' | 'CollectionSponsorSet' | 'SponsorshipConfirmed' | 'CollectionSponsorRemoved'; } - /** @name PalletEvmAccountBasicCrossAccountIdRepr (92) */ + /** @name PalletEvmAccountBasicCrossAccountIdRepr (95) */ interface PalletEvmAccountBasicCrossAccountIdRepr extends Enum { readonly isSubstrate: boolean; readonly asSubstrate: AccountId32; @@ -1167,14 +1184,14 @@ readonly type: 'Substrate' | 'Ethereum'; } - /** @name PalletStructureEvent (96) */ + /** @name PalletStructureEvent (99) */ interface PalletStructureEvent extends Enum { readonly isExecuted: boolean; readonly asExecuted: Result; readonly type: 'Executed'; } - /** @name PalletRmrkCoreEvent (97) */ + /** @name PalletRmrkCoreEvent (100) */ interface PalletRmrkCoreEvent extends Enum { readonly isCollectionCreated: boolean; readonly asCollectionCreated: { @@ -1264,7 +1281,7 @@ readonly type: 'CollectionCreated' | 'CollectionDestroyed' | 'IssuerChanged' | 'CollectionLocked' | 'NftMinted' | 'NftBurned' | 'NftSent' | 'NftAccepted' | 'NftRejected' | 'PropertySet' | 'ResourceAdded' | 'ResourceRemoval' | 'ResourceAccepted' | 'ResourceRemovalAccepted' | 'PrioritySet'; } - /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (98) */ + /** @name RmrkTraitsNftAccountIdOrCollectionNftTuple (101) */ interface RmrkTraitsNftAccountIdOrCollectionNftTuple extends Enum { readonly isAccountId: boolean; readonly asAccountId: AccountId32; @@ -1273,7 +1290,7 @@ readonly type: 'AccountId' | 'CollectionAndNftTuple'; } - /** @name PalletRmrkEquipEvent (102) */ + /** @name PalletRmrkEquipEvent (104) */ interface PalletRmrkEquipEvent extends Enum { readonly isBaseCreated: boolean; readonly asBaseCreated: { @@ -1288,7 +1305,7 @@ readonly type: 'BaseCreated' | 'EquippablesUpdated'; } - /** @name PalletAppPromotionEvent (103) */ + /** @name PalletAppPromotionEvent (105) */ interface PalletAppPromotionEvent extends Enum { readonly isStakingRecalculation: boolean; readonly asStakingRecalculation: ITuple<[AccountId32, u128, u128]>; @@ -1301,7 +1318,7 @@ readonly type: 'StakingRecalculation' | 'Stake' | 'Unstake' | 'SetAdmin'; } - /** @name PalletForeignAssetsModuleEvent (104) */ + /** @name PalletForeignAssetsModuleEvent (106) */ interface PalletForeignAssetsModuleEvent extends Enum { readonly isForeignAssetRegistered: boolean; readonly asForeignAssetRegistered: { @@ -1328,7 +1345,7 @@ readonly type: 'ForeignAssetRegistered' | 'ForeignAssetUpdated' | 'AssetRegistered' | 'AssetUpdated'; } - /** @name PalletForeignAssetsModuleAssetMetadata (105) */ + /** @name PalletForeignAssetsModuleAssetMetadata (107) */ interface PalletForeignAssetsModuleAssetMetadata extends Struct { readonly name: Bytes; readonly symbol: Bytes; @@ -1336,7 +1353,7 @@ readonly minimalBalance: u128; } - /** @name PalletEvmEvent (106) */ + /** @name PalletEvmEvent (108) */ interface PalletEvmEvent extends Enum { readonly isLog: boolean; readonly asLog: { @@ -1361,14 +1378,14 @@ readonly type: 'Log' | 'Created' | 'CreatedFailed' | 'Executed' | 'ExecutedFailed'; } - /** @name EthereumLog (107) */ + /** @name EthereumLog (109) */ interface EthereumLog extends Struct { readonly address: H160; readonly topics: Vec; readonly data: Bytes; } - /** @name PalletEthereumEvent (109) */ + /** @name PalletEthereumEvent (111) */ interface PalletEthereumEvent extends Enum { readonly isExecuted: boolean; readonly asExecuted: { @@ -1380,7 +1397,7 @@ readonly type: 'Executed'; } - /** @name EvmCoreErrorExitReason (110) */ + /** @name EvmCoreErrorExitReason (112) */ interface EvmCoreErrorExitReason extends Enum { readonly isSucceed: boolean; readonly asSucceed: EvmCoreErrorExitSucceed; @@ -1393,7 +1410,7 @@ readonly type: 'Succeed' | 'Error' | 'Revert' | 'Fatal'; } - /** @name EvmCoreErrorExitSucceed (111) */ + /** @name EvmCoreErrorExitSucceed (113) */ interface EvmCoreErrorExitSucceed extends Enum { readonly isStopped: boolean; readonly isReturned: boolean; @@ -1401,7 +1418,7 @@ readonly type: 'Stopped' | 'Returned' | 'Suicided'; } - /** @name EvmCoreErrorExitError (112) */ + /** @name EvmCoreErrorExitError (114) */ interface EvmCoreErrorExitError extends Enum { readonly isStackUnderflow: boolean; readonly isStackOverflow: boolean; @@ -1422,13 +1439,13 @@ readonly type: 'StackUnderflow' | 'StackOverflow' | 'InvalidJump' | 'InvalidRange' | 'DesignatedInvalid' | 'CallTooDeep' | 'CreateCollision' | 'CreateContractLimit' | 'OutOfOffset' | 'OutOfGas' | 'OutOfFund' | 'PcUnderflow' | 'CreateEmpty' | 'Other' | 'InvalidCode'; } - /** @name EvmCoreErrorExitRevert (115) */ + /** @name EvmCoreErrorExitRevert (117) */ interface EvmCoreErrorExitRevert extends Enum { readonly isReverted: boolean; readonly type: 'Reverted'; } - /** @name EvmCoreErrorExitFatal (116) */ + /** @name EvmCoreErrorExitFatal (118) */ interface EvmCoreErrorExitFatal extends Enum { readonly isNotSupported: boolean; readonly isUnhandledInterrupt: boolean; @@ -1439,7 +1456,7 @@ readonly type: 'NotSupported' | 'UnhandledInterrupt' | 'CallErrorAsFatal' | 'Other'; } - /** @name PalletEvmContractHelpersEvent (117) */ + /** @name PalletEvmContractHelpersEvent (119) */ interface PalletEvmContractHelpersEvent extends Enum { readonly isContractSponsorSet: boolean; readonly asContractSponsorSet: ITuple<[H160, AccountId32]>; @@ -1450,20 +1467,20 @@ readonly type: 'ContractSponsorSet' | 'ContractSponsorshipConfirmed' | 'ContractSponsorRemoved'; } - /** @name PalletEvmMigrationEvent (118) */ + /** @name PalletEvmMigrationEvent (120) */ interface PalletEvmMigrationEvent extends Enum { readonly isTestEvent: boolean; readonly type: 'TestEvent'; } - /** @name PalletMaintenanceEvent (119) */ + /** @name PalletMaintenanceEvent (121) */ interface PalletMaintenanceEvent extends Enum { readonly isMaintenanceEnabled: boolean; readonly isMaintenanceDisabled: boolean; readonly type: 'MaintenanceEnabled' | 'MaintenanceDisabled'; } - /** @name PalletTestUtilsEvent (120) */ + /** @name PalletTestUtilsEvent (122) */ interface PalletTestUtilsEvent extends Enum { readonly isValueIsSet: boolean; readonly isShouldRollback: boolean; @@ -1471,7 +1488,7 @@ readonly type: 'ValueIsSet' | 'ShouldRollback' | 'BatchCompleted'; } - /** @name FrameSystemPhase (121) */ + /** @name FrameSystemPhase (123) */ interface FrameSystemPhase extends Enum { readonly isApplyExtrinsic: boolean; readonly asApplyExtrinsic: u32; @@ -1480,13 +1497,13 @@ readonly type: 'ApplyExtrinsic' | 'Finalization' | 'Initialization'; } - /** @name FrameSystemLastRuntimeUpgradeInfo (124) */ + /** @name FrameSystemLastRuntimeUpgradeInfo (126) */ interface FrameSystemLastRuntimeUpgradeInfo extends Struct { readonly specVersion: Compact; readonly specName: Text; } - /** @name FrameSystemCall (125) */ + /** @name FrameSystemCall (127) */ interface FrameSystemCall extends Enum { readonly isRemark: boolean; readonly asRemark: { @@ -1524,21 +1541,21 @@ readonly type: 'Remark' | 'SetHeapPages' | 'SetCode' | 'SetCodeWithoutChecks' | 'SetStorage' | 'KillStorage' | 'KillPrefix' | 'RemarkWithEvent'; } - /** @name FrameSystemLimitsBlockWeights (129) */ + /** @name FrameSystemLimitsBlockWeights (131) */ interface FrameSystemLimitsBlockWeights extends Struct { readonly baseBlock: SpWeightsWeightV2Weight; readonly maxBlock: SpWeightsWeightV2Weight; readonly perClass: FrameSupportDispatchPerDispatchClassWeightsPerClass; } - /** @name FrameSupportDispatchPerDispatchClassWeightsPerClass (130) */ + /** @name FrameSupportDispatchPerDispatchClassWeightsPerClass (132) */ interface FrameSupportDispatchPerDispatchClassWeightsPerClass extends Struct { readonly normal: FrameSystemLimitsWeightsPerClass; readonly operational: FrameSystemLimitsWeightsPerClass; readonly mandatory: FrameSystemLimitsWeightsPerClass; } - /** @name FrameSystemLimitsWeightsPerClass (131) */ + /** @name FrameSystemLimitsWeightsPerClass (133) */ interface FrameSystemLimitsWeightsPerClass extends Struct { readonly baseExtrinsic: SpWeightsWeightV2Weight; readonly maxExtrinsic: Option; @@ -1546,25 +1563,25 @@ readonly reserved: Option; } - /** @name FrameSystemLimitsBlockLength (133) */ + /** @name FrameSystemLimitsBlockLength (135) */ interface FrameSystemLimitsBlockLength extends Struct { readonly max: FrameSupportDispatchPerDispatchClassU32; } - /** @name FrameSupportDispatchPerDispatchClassU32 (134) */ + /** @name FrameSupportDispatchPerDispatchClassU32 (136) */ interface FrameSupportDispatchPerDispatchClassU32 extends Struct { readonly normal: u32; readonly operational: u32; readonly mandatory: u32; } - /** @name SpWeightsRuntimeDbWeight (135) */ + /** @name SpWeightsRuntimeDbWeight (137) */ interface SpWeightsRuntimeDbWeight extends Struct { readonly read: u64; readonly write: u64; } - /** @name SpVersionRuntimeVersion (136) */ + /** @name SpVersionRuntimeVersion (138) */ interface SpVersionRuntimeVersion extends Struct { readonly specName: Text; readonly implName: Text; @@ -1576,7 +1593,7 @@ readonly stateVersion: u8; } - /** @name FrameSystemError (141) */ + /** @name FrameSystemError (143) */ interface FrameSystemError extends Enum { readonly isInvalidSpecName: boolean; readonly isSpecVersionNeedsToIncrease: boolean; @@ -1587,7 +1604,7 @@ readonly type: 'InvalidSpecName' | 'SpecVersionNeedsToIncrease' | 'FailedToExtractRuntimeVersion' | 'NonDefaultComposite' | 'NonZeroRefCount' | 'CallFiltered'; } - /** @name PolkadotPrimitivesV2PersistedValidationData (142) */ + /** @name PolkadotPrimitivesV2PersistedValidationData (144) */ interface PolkadotPrimitivesV2PersistedValidationData extends Struct { readonly parentHead: Bytes; readonly relayParentNumber: u32; @@ -1595,18 +1612,18 @@ readonly maxPovSize: u32; } - /** @name PolkadotPrimitivesV2UpgradeRestriction (145) */ + /** @name PolkadotPrimitivesV2UpgradeRestriction (147) */ interface PolkadotPrimitivesV2UpgradeRestriction extends Enum { readonly isPresent: boolean; readonly type: 'Present'; } - /** @name SpTrieStorageProof (146) */ + /** @name SpTrieStorageProof (148) */ interface SpTrieStorageProof extends Struct { readonly trieNodes: BTreeSet; } - /** @name CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot (148) */ + /** @name CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot (150) */ interface CumulusPalletParachainSystemRelayStateSnapshotMessagingStateSnapshot extends Struct { readonly dmqMqcHead: H256; readonly relayDispatchQueueSize: ITuple<[u32, u32]>; @@ -1614,7 +1631,7 @@ readonly egressChannels: Vec>; } - /** @name PolkadotPrimitivesV2AbridgedHrmpChannel (151) */ + /** @name PolkadotPrimitivesV2AbridgedHrmpChannel (153) */ interface PolkadotPrimitivesV2AbridgedHrmpChannel extends Struct { readonly maxCapacity: u32; readonly maxTotalSize: u32; @@ -1624,7 +1641,7 @@ readonly mqcHead: Option; } - /** @name PolkadotPrimitivesV2AbridgedHostConfiguration (152) */ + /** @name PolkadotPrimitivesV2AbridgedHostConfiguration (154) */ interface PolkadotPrimitivesV2AbridgedHostConfiguration extends Struct { readonly maxCodeSize: u32; readonly maxHeadDataSize: u32; @@ -1637,13 +1654,13 @@ readonly validationUpgradeDelay: u32; } - /** @name PolkadotCorePrimitivesOutboundHrmpMessage (158) */ + /** @name PolkadotCorePrimitivesOutboundHrmpMessage (160) */ interface PolkadotCorePrimitivesOutboundHrmpMessage extends Struct { readonly recipient: u32; readonly data: Bytes; } - /** @name CumulusPalletParachainSystemCall (159) */ + /** @name CumulusPalletParachainSystemCall (161) */ interface CumulusPalletParachainSystemCall extends Enum { readonly isSetValidationData: boolean; readonly asSetValidationData: { @@ -1664,7 +1681,7 @@ readonly type: 'SetValidationData' | 'SudoSendUpwardMessage' | 'AuthorizeUpgrade' | 'EnactAuthorizedUpgrade'; } - /** @name CumulusPrimitivesParachainInherentParachainInherentData (160) */ + /** @name CumulusPrimitivesParachainInherentParachainInherentData (162) */ interface CumulusPrimitivesParachainInherentParachainInherentData extends Struct { readonly validationData: PolkadotPrimitivesV2PersistedValidationData; readonly relayChainState: SpTrieStorageProof; @@ -1672,19 +1689,19 @@ readonly horizontalMessages: BTreeMap>; } - /** @name PolkadotCorePrimitivesInboundDownwardMessage (162) */ + /** @name PolkadotCorePrimitivesInboundDownwardMessage (164) */ interface PolkadotCorePrimitivesInboundDownwardMessage extends Struct { readonly sentAt: u32; readonly msg: Bytes; } - /** @name PolkadotCorePrimitivesInboundHrmpMessage (165) */ + /** @name PolkadotCorePrimitivesInboundHrmpMessage (167) */ interface PolkadotCorePrimitivesInboundHrmpMessage extends Struct { readonly sentAt: u32; readonly data: Bytes; } - /** @name CumulusPalletParachainSystemError (168) */ + /** @name CumulusPalletParachainSystemError (170) */ interface CumulusPalletParachainSystemError extends Enum { readonly isOverlappingUpgrades: boolean; readonly isProhibitedByPolkadot: boolean; @@ -1697,14 +1714,14 @@ readonly type: 'OverlappingUpgrades' | 'ProhibitedByPolkadot' | 'TooBig' | 'ValidationDataNotAvailable' | 'HostConfigurationNotAvailable' | 'NotScheduled' | 'NothingAuthorized' | 'Unauthorized'; } - /** @name PalletBalancesBalanceLock (170) */ + /** @name PalletBalancesBalanceLock (172) */ interface PalletBalancesBalanceLock extends Struct { readonly id: U8aFixed; readonly amount: u128; readonly reasons: PalletBalancesReasons; } - /** @name PalletBalancesReasons (171) */ + /** @name PalletBalancesReasons (173) */ interface PalletBalancesReasons extends Enum { readonly isFee: boolean; readonly isMisc: boolean; @@ -1712,13 +1729,13 @@ readonly type: 'Fee' | 'Misc' | 'All'; } - /** @name PalletBalancesReserveData (174) */ + /** @name PalletBalancesReserveData (176) */ interface PalletBalancesReserveData extends Struct { readonly id: U8aFixed; readonly amount: u128; } - /** @name PalletBalancesCall (176) */ + /** @name PalletBalancesCall (178) */ interface PalletBalancesCall extends Enum { readonly isTransfer: boolean; readonly asTransfer: { @@ -1755,7 +1772,7 @@ readonly type: 'Transfer' | 'SetBalance' | 'ForceTransfer' | 'TransferKeepAlive' | 'TransferAll' | 'ForceUnreserve'; } - /** @name PalletBalancesError (179) */ + /** @name PalletBalancesError (181) */ interface PalletBalancesError extends Enum { readonly isVestingBalance: boolean; readonly isLiquidityRestrictions: boolean; @@ -1768,7 +1785,7 @@ readonly type: 'VestingBalance' | 'LiquidityRestrictions' | 'InsufficientBalance' | 'ExistentialDeposit' | 'KeepAlive' | 'ExistingVestingSchedule' | 'DeadAccount' | 'TooManyReserves'; } - /** @name PalletTimestampCall (181) */ + /** @name PalletTimestampCall (183) */ interface PalletTimestampCall extends Enum { readonly isSet: boolean; readonly asSet: { @@ -1777,14 +1794,14 @@ readonly type: 'Set'; } - /** @name PalletTransactionPaymentReleases (183) */ + /** @name PalletTransactionPaymentReleases (185) */ interface PalletTransactionPaymentReleases extends Enum { readonly isV1Ancient: boolean; readonly isV2: boolean; readonly type: 'V1Ancient' | 'V2'; } - /** @name PalletTreasuryProposal (184) */ + /** @name PalletTreasuryProposal (186) */ interface PalletTreasuryProposal extends Struct { readonly proposer: AccountId32; readonly value: u128; @@ -1792,7 +1809,7 @@ readonly bond: u128; } - /** @name PalletTreasuryCall (187) */ + /** @name PalletTreasuryCall (189) */ interface PalletTreasuryCall extends Enum { readonly isProposeSpend: boolean; readonly asProposeSpend: { @@ -1819,10 +1836,10 @@ readonly type: 'ProposeSpend' | 'RejectProposal' | 'ApproveProposal' | 'Spend' | 'RemoveApproval'; } - /** @name FrameSupportPalletId (190) */ + /** @name FrameSupportPalletId (191) */ interface FrameSupportPalletId extends U8aFixed {} - /** @name PalletTreasuryError (191) */ + /** @name PalletTreasuryError (192) */ interface PalletTreasuryError extends Enum { readonly isInsufficientProposersBalance: boolean; readonly isInvalidIndex: boolean; @@ -1832,7 +1849,7 @@ readonly type: 'InsufficientProposersBalance' | 'InvalidIndex' | 'TooManyApprovals' | 'InsufficientPermission' | 'ProposalNotApproved'; } - /** @name PalletSudoCall (192) */ + /** @name PalletSudoCall (193) */ interface PalletSudoCall extends Enum { readonly isSudo: boolean; readonly asSudo: { @@ -1855,7 +1872,7 @@ readonly type: 'Sudo' | 'SudoUncheckedWeight' | 'SetKey' | 'SudoAs'; } - /** @name OrmlVestingModuleCall (194) */ + /** @name OrmlVestingModuleCall (195) */ interface OrmlVestingModuleCall extends Enum { readonly isClaim: boolean; readonly isVestedTransfer: boolean; @@ -1875,7 +1892,7 @@ readonly type: 'Claim' | 'VestedTransfer' | 'UpdateVestingSchedules' | 'ClaimFor'; } - /** @name OrmlXtokensModuleCall (196) */ + /** @name OrmlXtokensModuleCall (197) */ interface OrmlXtokensModuleCall extends Enum { readonly isTransfer: boolean; readonly asTransfer: { @@ -1922,7 +1939,7 @@ readonly type: 'Transfer' | 'TransferMultiasset' | 'TransferWithFee' | 'TransferMultiassetWithFee' | 'TransferMulticurrencies' | 'TransferMultiassets'; } - /** @name XcmVersionedMultiAsset (197) */ + /** @name XcmVersionedMultiAsset (198) */ interface XcmVersionedMultiAsset extends Enum { readonly isV0: boolean; readonly asV0: XcmV0MultiAsset; @@ -1931,7 +1948,7 @@ readonly type: 'V0' | 'V1'; } - /** @name OrmlTokensModuleCall (200) */ + /** @name OrmlTokensModuleCall (201) */ interface OrmlTokensModuleCall extends Enum { readonly isTransfer: boolean; readonly asTransfer: { @@ -1968,7 +1985,7 @@ readonly type: 'Transfer' | 'TransferAll' | 'TransferKeepAlive' | 'ForceTransfer' | 'SetBalance'; } - /** @name CumulusPalletXcmpQueueCall (201) */ + /** @name CumulusPalletXcmpQueueCall (202) */ interface CumulusPalletXcmpQueueCall extends Enum { readonly isServiceOverweight: boolean; readonly asServiceOverweight: { @@ -2004,7 +2021,7 @@ readonly type: 'ServiceOverweight' | 'SuspendXcmExecution' | 'ResumeXcmExecution' | 'UpdateSuspendThreshold' | 'UpdateDropThreshold' | 'UpdateResumeThreshold' | 'UpdateThresholdWeight' | 'UpdateWeightRestrictDecay' | 'UpdateXcmpMaxIndividualWeight'; } - /** @name PalletXcmCall (202) */ + /** @name PalletXcmCall (203) */ interface PalletXcmCall extends Enum { readonly isSend: boolean; readonly asSend: { @@ -2066,7 +2083,7 @@ readonly type: 'Send' | 'TeleportAssets' | 'ReserveTransferAssets' | 'Execute' | 'ForceXcmVersion' | 'ForceDefaultXcmVersion' | 'ForceSubscribeVersionNotify' | 'ForceUnsubscribeVersionNotify' | 'LimitedReserveTransferAssets' | 'LimitedTeleportAssets'; } - /** @name XcmVersionedXcm (203) */ + /** @name XcmVersionedXcm (204) */ interface XcmVersionedXcm extends Enum { readonly isV0: boolean; readonly asV0: XcmV0Xcm; @@ -2077,7 +2094,7 @@ readonly type: 'V0' | 'V1' | 'V2'; } - /** @name XcmV0Xcm (204) */ + /** @name XcmV0Xcm (205) */ interface XcmV0Xcm extends Enum { readonly isWithdrawAsset: boolean; readonly asWithdrawAsset: { @@ -2140,7 +2157,7 @@ readonly type: 'WithdrawAsset' | 'ReserveAssetDeposit' | 'TeleportAsset' | 'QueryResponse' | 'TransferAsset' | 'TransferReserveAsset' | 'Transact' | 'HrmpNewChannelOpenRequest' | 'HrmpChannelAccepted' | 'HrmpChannelClosing' | 'RelayedFrom'; } - /** @name XcmV0Order (206) */ + /** @name XcmV0Order (207) */ interface XcmV0Order extends Enum { readonly isNull: boolean; readonly isDepositAsset: boolean; @@ -2188,14 +2205,14 @@ readonly type: 'Null' | 'DepositAsset' | 'DepositReserveAsset' | 'ExchangeAsset' | 'InitiateReserveWithdraw' | 'InitiateTeleport' | 'QueryHolding' | 'BuyExecution'; } - /** @name XcmV0Response (208) */ + /** @name XcmV0Response (209) */ interface XcmV0Response extends Enum { readonly isAssets: boolean; readonly asAssets: Vec; readonly type: 'Assets'; } - /** @name XcmV1Xcm (209) */ + /** @name XcmV1Xcm (210) */ interface XcmV1Xcm extends Enum { readonly isWithdrawAsset: boolean; readonly asWithdrawAsset: { @@ -2264,7 +2281,7 @@ readonly type: 'WithdrawAsset' | 'ReserveAssetDeposited' | 'ReceiveTeleportedAsset' | 'QueryResponse' | 'TransferAsset' | 'TransferReserveAsset' | 'Transact' | 'HrmpNewChannelOpenRequest' | 'HrmpChannelAccepted' | 'HrmpChannelClosing' | 'RelayedFrom' | 'SubscribeVersion' | 'UnsubscribeVersion'; } - /** @name XcmV1Order (211) */ + /** @name XcmV1Order (212) */ interface XcmV1Order extends Enum { readonly isNoop: boolean; readonly isDepositAsset: boolean; @@ -2314,7 +2331,7 @@ readonly type: 'Noop' | 'DepositAsset' | 'DepositReserveAsset' | 'ExchangeAsset' | 'InitiateReserveWithdraw' | 'InitiateTeleport' | 'QueryHolding' | 'BuyExecution'; } - /** @name XcmV1Response (213) */ + /** @name XcmV1Response (214) */ interface XcmV1Response extends Enum { readonly isAssets: boolean; readonly asAssets: XcmV1MultiassetMultiAssets; @@ -2323,10 +2340,10 @@ readonly type: 'Assets' | 'Version'; } - /** @name CumulusPalletXcmCall (227) */ + /** @name CumulusPalletXcmCall (228) */ type CumulusPalletXcmCall = Null; - /** @name CumulusPalletDmpQueueCall (228) */ + /** @name CumulusPalletDmpQueueCall (229) */ interface CumulusPalletDmpQueueCall extends Enum { readonly isServiceOverweight: boolean; readonly asServiceOverweight: { @@ -2336,7 +2353,7 @@ readonly type: 'ServiceOverweight'; } - /** @name PalletInflationCall (229) */ + /** @name PalletInflationCall (230) */ interface PalletInflationCall extends Enum { readonly isStartInflation: boolean; readonly asStartInflation: { @@ -2345,7 +2362,7 @@ readonly type: 'StartInflation'; } - /** @name PalletUniqueCall (230) */ + /** @name PalletUniqueCall (231) */ interface PalletUniqueCall extends Enum { readonly isCreateCollection: boolean; readonly asCreateCollection: { @@ -2518,7 +2535,7 @@ readonly type: 'CreateCollection' | 'CreateCollectionEx' | 'DestroyCollection' | 'AddToAllowList' | 'RemoveFromAllowList' | 'ChangeCollectionOwner' | 'AddCollectionAdmin' | 'RemoveCollectionAdmin' | 'SetCollectionSponsor' | 'ConfirmSponsorship' | 'RemoveCollectionSponsor' | 'CreateItem' | 'CreateMultipleItems' | 'SetCollectionProperties' | 'DeleteCollectionProperties' | 'SetTokenProperties' | 'DeleteTokenProperties' | 'SetTokenPropertyPermissions' | 'CreateMultipleItemsEx' | 'SetTransfersEnabledFlag' | 'BurnItem' | 'BurnFrom' | 'Transfer' | 'Approve' | 'TransferFrom' | 'SetCollectionLimits' | 'SetCollectionPermissions' | 'Repartition' | 'SetAllowanceForAll' | 'ForceRepairCollection' | 'ForceRepairItem'; } - /** @name UpDataStructsCollectionMode (235) */ + /** @name UpDataStructsCollectionMode (236) */ interface UpDataStructsCollectionMode extends Enum { readonly isNft: boolean; readonly isFungible: boolean; @@ -2527,7 +2544,7 @@ readonly type: 'Nft' | 'Fungible' | 'ReFungible'; } - /** @name UpDataStructsCreateCollectionData (236) */ + /** @name UpDataStructsCreateCollectionData (237) */ interface UpDataStructsCreateCollectionData extends Struct { readonly mode: UpDataStructsCollectionMode; readonly access: Option; @@ -2541,14 +2558,14 @@ readonly properties: Vec; } - /** @name UpDataStructsAccessMode (238) */ + /** @name UpDataStructsAccessMode (239) */ interface UpDataStructsAccessMode extends Enum { readonly isNormal: boolean; readonly isAllowList: boolean; readonly type: 'Normal' | 'AllowList'; } - /** @name UpDataStructsCollectionLimits (240) */ + /** @name UpDataStructsCollectionLimits (241) */ interface UpDataStructsCollectionLimits extends Struct { readonly accountTokenOwnershipLimit: Option; readonly sponsoredDataSize: Option; @@ -2561,7 +2578,7 @@ readonly transfersEnabled: Option; } - /** @name UpDataStructsSponsoringRateLimit (242) */ + /** @name UpDataStructsSponsoringRateLimit (243) */ interface UpDataStructsSponsoringRateLimit extends Enum { readonly isSponsoringDisabled: boolean; readonly isBlocks: boolean; @@ -2569,43 +2586,43 @@ readonly type: 'SponsoringDisabled' | 'Blocks'; } - /** @name UpDataStructsCollectionPermissions (245) */ + /** @name UpDataStructsCollectionPermissions (246) */ interface UpDataStructsCollectionPermissions extends Struct { readonly access: Option; readonly mintMode: Option; readonly nesting: Option; } - /** @name UpDataStructsNestingPermissions (247) */ + /** @name UpDataStructsNestingPermissions (248) */ interface UpDataStructsNestingPermissions extends Struct { readonly tokenOwner: bool; readonly collectionAdmin: bool; readonly restricted: Option; } - /** @name UpDataStructsOwnerRestrictedSet (249) */ + /** @name UpDataStructsOwnerRestrictedSet (250) */ interface UpDataStructsOwnerRestrictedSet extends BTreeSet {} - /** @name UpDataStructsPropertyKeyPermission (254) */ + /** @name UpDataStructsPropertyKeyPermission (255) */ interface UpDataStructsPropertyKeyPermission extends Struct { readonly key: Bytes; readonly permission: UpDataStructsPropertyPermission; } - /** @name UpDataStructsPropertyPermission (255) */ + /** @name UpDataStructsPropertyPermission (256) */ interface UpDataStructsPropertyPermission extends Struct { readonly mutable: bool; readonly collectionAdmin: bool; readonly tokenOwner: bool; } - /** @name UpDataStructsProperty (258) */ + /** @name UpDataStructsProperty (259) */ interface UpDataStructsProperty extends Struct { readonly key: Bytes; readonly value: Bytes; } - /** @name UpDataStructsCreateItemData (261) */ + /** @name UpDataStructsCreateItemData (262) */ interface UpDataStructsCreateItemData extends Enum { readonly isNft: boolean; readonly asNft: UpDataStructsCreateNftData; @@ -2616,23 +2633,23 @@ readonly type: 'Nft' | 'Fungible' | 'ReFungible'; } - /** @name UpDataStructsCreateNftData (262) */ + /** @name UpDataStructsCreateNftData (263) */ interface UpDataStructsCreateNftData extends Struct { readonly properties: Vec; } - /** @name UpDataStructsCreateFungibleData (263) */ + /** @name UpDataStructsCreateFungibleData (264) */ interface UpDataStructsCreateFungibleData extends Struct { readonly value: u128; } - /** @name UpDataStructsCreateReFungibleData (264) */ + /** @name UpDataStructsCreateReFungibleData (265) */ interface UpDataStructsCreateReFungibleData extends Struct { readonly pieces: u128; readonly properties: Vec; } - /** @name UpDataStructsCreateItemExData (267) */ + /** @name UpDataStructsCreateItemExData (268) */ interface UpDataStructsCreateItemExData extends Enum { readonly isNft: boolean; readonly asNft: Vec; @@ -2645,26 +2662,26 @@ readonly type: 'Nft' | 'Fungible' | 'RefungibleMultipleItems' | 'RefungibleMultipleOwners'; } - /** @name UpDataStructsCreateNftExData (269) */ + /** @name UpDataStructsCreateNftExData (270) */ interface UpDataStructsCreateNftExData extends Struct { readonly properties: Vec; readonly owner: PalletEvmAccountBasicCrossAccountIdRepr; } - /** @name UpDataStructsCreateRefungibleExSingleOwner (276) */ + /** @name UpDataStructsCreateRefungibleExSingleOwner (277) */ interface UpDataStructsCreateRefungibleExSingleOwner extends Struct { readonly user: PalletEvmAccountBasicCrossAccountIdRepr; readonly pieces: u128; readonly properties: Vec; } - /** @name UpDataStructsCreateRefungibleExMultipleOwners (278) */ + /** @name UpDataStructsCreateRefungibleExMultipleOwners (279) */ interface UpDataStructsCreateRefungibleExMultipleOwners extends Struct { readonly users: BTreeMap; readonly properties: Vec; } - /** @name PalletConfigurationCall (279) */ + /** @name PalletConfigurationCall (280) */ interface PalletConfigurationCall extends Enum { readonly isSetWeightToFeeCoefficientOverride: boolean; readonly asSetWeightToFeeCoefficientOverride: { @@ -2682,10 +2699,22 @@ readonly asSetAppPromotionConfigurationOverride: { readonly configuration: PalletConfigurationAppPromotionConfiguration; } & Struct; - readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride' | 'SetXcmAllowedLocations' | 'SetAppPromotionConfigurationOverride'; + readonly isSetCollatorSelectionDesiredCollators: boolean; + readonly asSetCollatorSelectionDesiredCollators: { + readonly max: Option; + } & Struct; + readonly isSetCollatorSelectionLicenseBond: boolean; + readonly asSetCollatorSelectionLicenseBond: { + readonly amount: Option; + } & Struct; + readonly isSetCollatorSelectionKickThreshold: boolean; + readonly asSetCollatorSelectionKickThreshold: { + readonly threshold: Option; + } & Struct; + readonly type: 'SetWeightToFeeCoefficientOverride' | 'SetMinGasPriceOverride' | 'SetXcmAllowedLocations' | 'SetAppPromotionConfigurationOverride' | 'SetCollatorSelectionDesiredCollators' | 'SetCollatorSelectionLicenseBond' | 'SetCollatorSelectionKickThreshold'; } - /** @name PalletConfigurationAppPromotionConfiguration (284) */ + /** @name PalletConfigurationAppPromotionConfiguration (285) */ interface PalletConfigurationAppPromotionConfiguration extends Struct { readonly recalculationInterval: Option; readonly pendingInterval: Option; @@ -2693,13 +2722,13 @@ readonly maxStakersPerCalculation: Option; } - /** @name PalletTemplateTransactionPaymentCall (288) */ + /** @name PalletTemplateTransactionPaymentCall (289) */ type PalletTemplateTransactionPaymentCall = Null; - /** @name PalletStructureCall (289) */ + /** @name PalletStructureCall (290) */ type PalletStructureCall = Null; - /** @name PalletRmrkCoreCall (290) */ + /** @name PalletRmrkCoreCall (291) */ interface PalletRmrkCoreCall extends Enum { readonly isCreateCollection: boolean; readonly asCreateCollection: { @@ -2805,7 +2834,7 @@ readonly type: 'CreateCollection' | 'DestroyCollection' | 'ChangeCollectionIssuer' | 'LockCollection' | 'MintNft' | 'BurnNft' | 'Send' | 'AcceptNft' | 'RejectNft' | 'AcceptResource' | 'AcceptResourceRemoval' | 'SetProperty' | 'SetPriority' | 'AddBasicResource' | 'AddComposableResource' | 'AddSlotResource' | 'RemoveResource'; } - /** @name RmrkTraitsResourceResourceTypes (296) */ + /** @name RmrkTraitsResourceResourceTypes (297) */ interface RmrkTraitsResourceResourceTypes extends Enum { readonly isBasic: boolean; readonly asBasic: RmrkTraitsResourceBasicResource; @@ -2816,7 +2845,7 @@ readonly type: 'Basic' | 'Composable' | 'Slot'; } - /** @name RmrkTraitsResourceBasicResource (298) */ + /** @name RmrkTraitsResourceBasicResource (299) */ interface RmrkTraitsResourceBasicResource extends Struct { readonly src: Option; readonly metadata: Option; @@ -2824,7 +2853,7 @@ readonly thumb: Option; } - /** @name RmrkTraitsResourceComposableResource (300) */ + /** @name RmrkTraitsResourceComposableResource (301) */ interface RmrkTraitsResourceComposableResource extends Struct { readonly parts: Vec; readonly base: u32; @@ -2834,7 +2863,7 @@ readonly thumb: Option; } - /** @name RmrkTraitsResourceSlotResource (301) */ + /** @name RmrkTraitsResourceSlotResource (302) */ interface RmrkTraitsResourceSlotResource extends Struct { readonly base: u32; readonly src: Option; @@ -2844,7 +2873,7 @@ readonly thumb: Option; } - /** @name PalletRmrkEquipCall (304) */ + /** @name PalletRmrkEquipCall (305) */ interface PalletRmrkEquipCall extends Enum { readonly isCreateBase: boolean; readonly asCreateBase: { @@ -2866,7 +2895,7 @@ readonly type: 'CreateBase' | 'ThemeAdd' | 'Equippable'; } - /** @name RmrkTraitsPartPartType (307) */ + /** @name RmrkTraitsPartPartType (308) */ interface RmrkTraitsPartPartType extends Enum { readonly isFixedPart: boolean; readonly asFixedPart: RmrkTraitsPartFixedPart; @@ -2875,14 +2904,14 @@ readonly type: 'FixedPart' | 'SlotPart'; } - /** @name RmrkTraitsPartFixedPart (309) */ + /** @name RmrkTraitsPartFixedPart (310) */ interface RmrkTraitsPartFixedPart extends Struct { readonly id: u32; readonly z: u32; readonly src: Bytes; } - /** @name RmrkTraitsPartSlotPart (310) */ + /** @name RmrkTraitsPartSlotPart (311) */ interface RmrkTraitsPartSlotPart extends Struct { readonly id: u32; readonly equippable: RmrkTraitsPartEquippableList; @@ -2890,7 +2919,7 @@ readonly z: u32; } - /** @name RmrkTraitsPartEquippableList (311) */ + /** @name RmrkTraitsPartEquippableList (312) */ interface RmrkTraitsPartEquippableList extends Enum { readonly isAll: boolean; readonly isEmpty: boolean; @@ -2899,20 +2928,20 @@ readonly type: 'All' | 'Empty' | 'Custom'; } - /** @name RmrkTraitsTheme (313) */ + /** @name RmrkTraitsTheme (314) */ interface RmrkTraitsTheme extends Struct { readonly name: Bytes; readonly properties: Vec; readonly inherit: bool; } - /** @name RmrkTraitsThemeThemeProperty (315) */ + /** @name RmrkTraitsThemeThemeProperty (316) */ interface RmrkTraitsThemeThemeProperty extends Struct { readonly key: Bytes; readonly value: Bytes; } - /** @name PalletAppPromotionCall (317) */ + /** @name PalletAppPromotionCall (318) */ interface PalletAppPromotionCall extends Enum { readonly isSetAdminAddress: boolean; readonly asSetAdminAddress: { @@ -2946,7 +2975,7 @@ readonly type: 'SetAdminAddress' | 'Stake' | 'Unstake' | 'SponsorCollection' | 'StopSponsoringCollection' | 'SponsorContract' | 'StopSponsoringContract' | 'PayoutStakers'; } - /** @name PalletForeignAssetsModuleCall (318) */ + /** @name PalletForeignAssetsModuleCall (319) */ interface PalletForeignAssetsModuleCall extends Enum { readonly isRegisterForeignAsset: boolean; readonly asRegisterForeignAsset: { @@ -2963,7 +2992,7 @@ readonly type: 'RegisterForeignAsset' | 'UpdateForeignAsset'; } - /** @name PalletEvmCall (319) */ + /** @name PalletEvmCall (320) */ interface PalletEvmCall extends Enum { readonly isWithdraw: boolean; readonly asWithdraw: { @@ -3008,7 +3037,7 @@ readonly type: 'Withdraw' | 'Call' | 'Create' | 'Create2'; } - /** @name PalletEthereumCall (325) */ + /** @name PalletEthereumCall (326) */ interface PalletEthereumCall extends Enum { readonly isTransact: boolean; readonly asTransact: { @@ -3017,7 +3046,7 @@ readonly type: 'Transact'; } - /** @name EthereumTransactionTransactionV2 (326) */ + /** @name EthereumTransactionTransactionV2 (327) */ interface EthereumTransactionTransactionV2 extends Enum { readonly isLegacy: boolean; readonly asLegacy: EthereumTransactionLegacyTransaction; @@ -3028,7 +3057,7 @@ readonly type: 'Legacy' | 'Eip2930' | 'Eip1559'; } - /** @name EthereumTransactionLegacyTransaction (327) */ + /** @name EthereumTransactionLegacyTransaction (328) */ interface EthereumTransactionLegacyTransaction extends Struct { readonly nonce: U256; readonly gasPrice: U256; @@ -3039,7 +3068,7 @@ readonly signature: EthereumTransactionTransactionSignature; } - /** @name EthereumTransactionTransactionAction (328) */ + /** @name EthereumTransactionTransactionAction (329) */ interface EthereumTransactionTransactionAction extends Enum { readonly isCall: boolean; readonly asCall: H160; @@ -3047,14 +3076,14 @@ readonly type: 'Call' | 'Create'; } - /** @name EthereumTransactionTransactionSignature (329) */ + /** @name EthereumTransactionTransactionSignature (330) */ interface EthereumTransactionTransactionSignature extends Struct { readonly v: u64; readonly r: H256; readonly s: H256; } - /** @name EthereumTransactionEip2930Transaction (331) */ + /** @name EthereumTransactionEip2930Transaction (332) */ interface EthereumTransactionEip2930Transaction extends Struct { readonly chainId: u64; readonly nonce: U256; @@ -3069,13 +3098,13 @@ readonly s: H256; } - /** @name EthereumTransactionAccessListItem (333) */ + /** @name EthereumTransactionAccessListItem (334) */ interface EthereumTransactionAccessListItem extends Struct { readonly address: H160; readonly storageKeys: Vec; } - /** @name EthereumTransactionEip1559Transaction (334) */ + /** @name EthereumTransactionEip1559Transaction (335) */ interface EthereumTransactionEip1559Transaction extends Struct { readonly chainId: u64; readonly nonce: U256; @@ -3091,7 +3120,7 @@ readonly s: H256; } - /** @name PalletEvmMigrationCall (335) */ + /** @name PalletEvmMigrationCall (336) */ interface PalletEvmMigrationCall extends Enum { readonly isBegin: boolean; readonly asBegin: { @@ -3118,14 +3147,14 @@ readonly type: 'Begin' | 'SetData' | 'Finish' | 'InsertEthLogs' | 'InsertEvents'; } - /** @name PalletMaintenanceCall (339) */ + /** @name PalletMaintenanceCall (340) */ interface PalletMaintenanceCall extends Enum { readonly isEnable: boolean; readonly isDisable: boolean; readonly type: 'Enable' | 'Disable'; } - /** @name PalletTestUtilsCall (340) */ + /** @name PalletTestUtilsCall (341) */ interface PalletTestUtilsCall extends Enum { readonly isEnable: boolean; readonly isSetTestValue: boolean; @@ -3145,13 +3174,13 @@ readonly type: 'Enable' | 'SetTestValue' | 'SetTestValueAndRollback' | 'IncTestValue' | 'JustTakeFee' | 'BatchAll'; } - /** @name PalletSudoError (342) */ + /** @name PalletSudoError (343) */ interface PalletSudoError extends Enum { readonly isRequireSudo: boolean; readonly type: 'RequireSudo'; } - /** @name OrmlVestingModuleError (344) */ + /** @name OrmlVestingModuleError (345) */ interface OrmlVestingModuleError extends Enum { readonly isZeroVestingPeriod: boolean; readonly isZeroVestingPeriodCount: boolean; @@ -3162,7 +3191,7 @@ readonly type: 'ZeroVestingPeriod' | 'ZeroVestingPeriodCount' | 'InsufficientBalanceToLock' | 'TooManyVestingSchedules' | 'AmountLow' | 'MaxVestingSchedulesExceeded'; } - /** @name OrmlXtokensModuleError (345) */ + /** @name OrmlXtokensModuleError (346) */ interface OrmlXtokensModuleError extends Enum { readonly isAssetHasNoReserve: boolean; readonly isNotCrossChainTransfer: boolean; @@ -3186,26 +3215,26 @@ readonly type: 'AssetHasNoReserve' | 'NotCrossChainTransfer' | 'InvalidDest' | 'NotCrossChainTransferableCurrency' | 'UnweighableMessage' | 'XcmExecutionFailed' | 'CannotReanchor' | 'InvalidAncestry' | 'InvalidAsset' | 'DestinationNotInvertible' | 'BadVersion' | 'DistinctReserveForAssetAndFee' | 'ZeroFee' | 'ZeroAmount' | 'TooManyAssetsBeingSent' | 'AssetIndexNonExistent' | 'FeeNotEnough' | 'NotSupportedMultiLocation' | 'MinXcmFeeNotDefined'; } - /** @name OrmlTokensBalanceLock (348) */ + /** @name OrmlTokensBalanceLock (349) */ interface OrmlTokensBalanceLock extends Struct { readonly id: U8aFixed; readonly amount: u128; } - /** @name OrmlTokensAccountData (350) */ + /** @name OrmlTokensAccountData (351) */ interface OrmlTokensAccountData extends Struct { readonly free: u128; readonly reserved: u128; readonly frozen: u128; } - /** @name OrmlTokensReserveData (352) */ + /** @name OrmlTokensReserveData (353) */ interface OrmlTokensReserveData extends Struct { readonly id: Null; readonly amount: u128; } - /** @name OrmlTokensModuleError (354) */ + /** @name OrmlTokensModuleError (355) */ interface OrmlTokensModuleError extends Enum { readonly isBalanceTooLow: boolean; readonly isAmountIntoBalanceFailed: boolean; @@ -3218,21 +3247,21 @@ readonly type: 'BalanceTooLow' | 'AmountIntoBalanceFailed' | 'LiquidityRestrictions' | 'MaxLocksExceeded' | 'KeepAlive' | 'ExistentialDeposit' | 'DeadAccount' | 'TooManyReserves'; } - /** @name CumulusPalletXcmpQueueInboundChannelDetails (356) */ + /** @name CumulusPalletXcmpQueueInboundChannelDetails (357) */ interface CumulusPalletXcmpQueueInboundChannelDetails extends Struct { readonly sender: u32; readonly state: CumulusPalletXcmpQueueInboundState; readonly messageMetadata: Vec>; } - /** @name CumulusPalletXcmpQueueInboundState (357) */ + /** @name CumulusPalletXcmpQueueInboundState (358) */ interface CumulusPalletXcmpQueueInboundState extends Enum { readonly isOk: boolean; readonly isSuspended: boolean; readonly type: 'Ok' | 'Suspended'; } - /** @name PolkadotParachainPrimitivesXcmpMessageFormat (360) */ + /** @name PolkadotParachainPrimitivesXcmpMessageFormat (361) */ interface PolkadotParachainPrimitivesXcmpMessageFormat extends Enum { readonly isConcatenatedVersionedXcm: boolean; readonly isConcatenatedEncodedBlob: boolean; @@ -3240,7 +3269,7 @@ readonly type: 'ConcatenatedVersionedXcm' | 'ConcatenatedEncodedBlob' | 'Signals'; } - /** @name CumulusPalletXcmpQueueOutboundChannelDetails (363) */ + /** @name CumulusPalletXcmpQueueOutboundChannelDetails (364) */ interface CumulusPalletXcmpQueueOutboundChannelDetails extends Struct { readonly recipient: u32; readonly state: CumulusPalletXcmpQueueOutboundState; @@ -3249,14 +3278,14 @@ readonly lastIndex: u16; } - /** @name CumulusPalletXcmpQueueOutboundState (364) */ + /** @name CumulusPalletXcmpQueueOutboundState (365) */ interface CumulusPalletXcmpQueueOutboundState extends Enum { readonly isOk: boolean; readonly isSuspended: boolean; readonly type: 'Ok' | 'Suspended'; } - /** @name CumulusPalletXcmpQueueQueueConfigData (366) */ + /** @name CumulusPalletXcmpQueueQueueConfigData (367) */ interface CumulusPalletXcmpQueueQueueConfigData extends Struct { readonly suspendThreshold: u32; readonly dropThreshold: u32; @@ -3266,7 +3295,7 @@ readonly xcmpMaxIndividualWeight: SpWeightsWeightV2Weight; } - /** @name CumulusPalletXcmpQueueError (368) */ + /** @name CumulusPalletXcmpQueueError (369) */ interface CumulusPalletXcmpQueueError extends Enum { readonly isFailedToSend: boolean; readonly isBadXcmOrigin: boolean; @@ -3276,7 +3305,7 @@ readonly type: 'FailedToSend' | 'BadXcmOrigin' | 'BadXcm' | 'BadOverweightIndex' | 'WeightOverLimit'; } - /** @name PalletXcmError (369) */ + /** @name PalletXcmError (370) */ interface PalletXcmError extends Enum { readonly isUnreachable: boolean; readonly isSendFailure: boolean; @@ -3294,29 +3323,29 @@ readonly type: 'Unreachable' | 'SendFailure' | 'Filtered' | 'UnweighableMessage' | 'DestinationNotInvertible' | 'Empty' | 'CannotReanchor' | 'TooManyAssets' | 'InvalidOrigin' | 'BadVersion' | 'BadLocation' | 'NoSubscription' | 'AlreadySubscribed'; } - /** @name CumulusPalletXcmError (370) */ + /** @name CumulusPalletXcmError (371) */ type CumulusPalletXcmError = Null; - /** @name CumulusPalletDmpQueueConfigData (371) */ + /** @name CumulusPalletDmpQueueConfigData (372) */ interface CumulusPalletDmpQueueConfigData extends Struct { readonly maxIndividual: SpWeightsWeightV2Weight; } - /** @name CumulusPalletDmpQueuePageIndexData (372) */ + /** @name CumulusPalletDmpQueuePageIndexData (373) */ interface CumulusPalletDmpQueuePageIndexData extends Struct { readonly beginUsed: u32; readonly endUsed: u32; readonly overweightCount: u64; } - /** @name CumulusPalletDmpQueueError (375) */ + /** @name CumulusPalletDmpQueueError (376) */ interface CumulusPalletDmpQueueError extends Enum { readonly isUnknown: boolean; readonly isOverLimit: boolean; readonly type: 'Unknown' | 'OverLimit'; } - /** @name PalletUniqueError (379) */ + /** @name PalletUniqueError (380) */ interface PalletUniqueError extends Enum { readonly isCollectionDecimalPointLimitExceeded: boolean; readonly isEmptyArgument: boolean; @@ -3324,13 +3353,13 @@ readonly type: 'CollectionDecimalPointLimitExceeded' | 'EmptyArgument' | 'RepartitionCalledOnNonRefungibleCollection'; } - /** @name PalletConfigurationError (380) */ + /** @name PalletConfigurationError (381) */ interface PalletConfigurationError extends Enum { readonly isInconsistentConfiguration: boolean; readonly type: 'InconsistentConfiguration'; } - /** @name UpDataStructsCollection (381) */ + /** @name UpDataStructsCollection (382) */ interface UpDataStructsCollection extends Struct { readonly owner: AccountId32; readonly mode: UpDataStructsCollectionMode; @@ -3343,7 +3372,7 @@ readonly flags: U8aFixed; } - /** @name UpDataStructsSponsorshipStateAccountId32 (382) */ + /** @name UpDataStructsSponsorshipStateAccountId32 (383) */ interface UpDataStructsSponsorshipStateAccountId32 extends Enum { readonly isDisabled: boolean; readonly isUnconfirmed: boolean; @@ -3353,43 +3382,43 @@ readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed'; } - /** @name UpDataStructsProperties (384) */ + /** @name UpDataStructsProperties (385) */ interface UpDataStructsProperties extends Struct { readonly map: UpDataStructsPropertiesMapBoundedVec; readonly consumedSpace: u32; readonly spaceLimit: u32; } - /** @name UpDataStructsPropertiesMapBoundedVec (385) */ + /** @name UpDataStructsPropertiesMapBoundedVec (386) */ interface UpDataStructsPropertiesMapBoundedVec extends BTreeMap {} - /** @name UpDataStructsPropertiesMapPropertyPermission (390) */ + /** @name UpDataStructsPropertiesMapPropertyPermission (391) */ interface UpDataStructsPropertiesMapPropertyPermission extends BTreeMap {} - /** @name UpDataStructsCollectionStats (397) */ + /** @name UpDataStructsCollectionStats (398) */ interface UpDataStructsCollectionStats extends Struct { readonly created: u32; readonly destroyed: u32; readonly alive: u32; } - /** @name UpDataStructsTokenChild (398) */ + /** @name UpDataStructsTokenChild (399) */ interface UpDataStructsTokenChild extends Struct { readonly token: u32; readonly collection: u32; } - /** @name PhantomTypeUpDataStructs (399) */ + /** @name PhantomTypeUpDataStructs (400) */ interface PhantomTypeUpDataStructs extends Vec> {} - /** @name UpDataStructsTokenData (401) */ + /** @name UpDataStructsTokenData (402) */ interface UpDataStructsTokenData extends Struct { readonly properties: Vec; readonly owner: Option; readonly pieces: u128; } - /** @name UpDataStructsRpcCollection (403) */ + /** @name UpDataStructsRpcCollection (404) */ interface UpDataStructsRpcCollection extends Struct { readonly owner: AccountId32; readonly mode: UpDataStructsCollectionMode; @@ -3405,13 +3434,13 @@ readonly flags: UpDataStructsRpcCollectionFlags; } - /** @name UpDataStructsRpcCollectionFlags (404) */ + /** @name UpDataStructsRpcCollectionFlags (405) */ interface UpDataStructsRpcCollectionFlags extends Struct { readonly foreign: bool; readonly erc721metadata: bool; } - /** @name RmrkTraitsCollectionCollectionInfo (405) */ + /** @name RmrkTraitsCollectionCollectionInfo (406) */ interface RmrkTraitsCollectionCollectionInfo extends Struct { readonly issuer: AccountId32; readonly metadata: Bytes; @@ -3420,7 +3449,7 @@ readonly nftsCount: u32; } - /** @name RmrkTraitsNftNftInfo (406) */ + /** @name RmrkTraitsNftNftInfo (407) */ interface RmrkTraitsNftNftInfo extends Struct { readonly owner: RmrkTraitsNftAccountIdOrCollectionNftTuple; readonly royalty: Option; @@ -3429,13 +3458,13 @@ readonly pending: bool; } - /** @name RmrkTraitsNftRoyaltyInfo (408) */ + /** @name RmrkTraitsNftRoyaltyInfo (409) */ interface RmrkTraitsNftRoyaltyInfo extends Struct { readonly recipient: AccountId32; readonly amount: Permill; } - /** @name RmrkTraitsResourceResourceInfo (409) */ + /** @name RmrkTraitsResourceResourceInfo (410) */ interface RmrkTraitsResourceResourceInfo extends Struct { readonly id: u32; readonly resource: RmrkTraitsResourceResourceTypes; @@ -3443,26 +3472,26 @@ readonly pendingRemoval: bool; } - /** @name RmrkTraitsPropertyPropertyInfo (410) */ + /** @name RmrkTraitsPropertyPropertyInfo (411) */ interface RmrkTraitsPropertyPropertyInfo extends Struct { readonly key: Bytes; readonly value: Bytes; } - /** @name RmrkTraitsBaseBaseInfo (411) */ + /** @name RmrkTraitsBaseBaseInfo (412) */ interface RmrkTraitsBaseBaseInfo extends Struct { readonly issuer: AccountId32; readonly baseType: Bytes; readonly symbol: Bytes; } - /** @name RmrkTraitsNftNftChild (412) */ + /** @name RmrkTraitsNftNftChild (413) */ interface RmrkTraitsNftNftChild extends Struct { readonly collectionId: u32; readonly nftId: u32; } - /** @name UpPovEstimateRpcPovInfo (413) */ + /** @name UpPovEstimateRpcPovInfo (414) */ interface UpPovEstimateRpcPovInfo extends Struct { readonly proofSize: u64; readonly compactProofSize: u64; @@ -3471,7 +3500,7 @@ readonly keyValues: Vec; } - /** @name SpRuntimeTransactionValidityTransactionValidityError (416) */ + /** @name SpRuntimeTransactionValidityTransactionValidityError (417) */ interface SpRuntimeTransactionValidityTransactionValidityError extends Enum { readonly isInvalid: boolean; readonly asInvalid: SpRuntimeTransactionValidityInvalidTransaction; @@ -3480,7 +3509,7 @@ readonly type: 'Invalid' | 'Unknown'; } - /** @name SpRuntimeTransactionValidityInvalidTransaction (417) */ + /** @name SpRuntimeTransactionValidityInvalidTransaction (418) */ interface SpRuntimeTransactionValidityInvalidTransaction extends Enum { readonly isCall: boolean; readonly isPayment: boolean; @@ -3497,7 +3526,7 @@ readonly type: 'Call' | 'Payment' | 'Future' | 'Stale' | 'BadProof' | 'AncientBirthBlock' | 'ExhaustsResources' | 'Custom' | 'BadMandatory' | 'MandatoryValidation' | 'BadSigner'; } - /** @name SpRuntimeTransactionValidityUnknownTransaction (418) */ + /** @name SpRuntimeTransactionValidityUnknownTransaction (419) */ interface SpRuntimeTransactionValidityUnknownTransaction extends Enum { readonly isCannotLookup: boolean; readonly isNoUnsignedValidator: boolean; @@ -3506,13 +3535,13 @@ readonly type: 'CannotLookup' | 'NoUnsignedValidator' | 'Custom'; } - /** @name UpPovEstimateRpcTrieKeyValue (420) */ + /** @name UpPovEstimateRpcTrieKeyValue (421) */ interface UpPovEstimateRpcTrieKeyValue extends Struct { readonly key: Bytes; readonly value: Bytes; } - /** @name PalletCommonError (422) */ + /** @name PalletCommonError (423) */ interface PalletCommonError extends Enum { readonly isCollectionNotFound: boolean; readonly isMustBeTokenOwner: boolean; @@ -3553,7 +3582,7 @@ readonly type: 'CollectionNotFound' | 'MustBeTokenOwner' | 'NoPermission' | 'CantDestroyNotEmptyCollection' | 'PublicMintingNotAllowed' | 'AddressNotInAllowlist' | 'CollectionNameLimitExceeded' | 'CollectionDescriptionLimitExceeded' | 'CollectionTokenPrefixLimitExceeded' | 'TotalCollectionsLimitExceeded' | 'CollectionAdminCountExceeded' | 'CollectionLimitBoundsExceeded' | 'OwnerPermissionsCantBeReverted' | 'TransferNotAllowed' | 'AccountTokenLimitExceeded' | 'CollectionTokenLimitExceeded' | 'MetadataFlagFrozen' | 'TokenNotFound' | 'TokenValueTooLow' | 'ApprovedValueTooLow' | 'CantApproveMoreThanOwned' | 'AddressIsZero' | 'UnsupportedOperation' | 'NotSufficientFounds' | 'UserIsNotAllowedToNest' | 'SourceCollectionIsNotAllowedToNest' | 'CollectionFieldSizeExceeded' | 'NoSpaceForProperty' | 'PropertyLimitReached' | 'PropertyKeyIsTooLong' | 'InvalidCharacterInPropertyKey' | 'EmptyPropertyKey' | 'CollectionIsExternal' | 'CollectionIsInternal' | 'ConfirmSponsorshipFail' | 'UserIsNotCollectionAdmin'; } - /** @name PalletFungibleError (424) */ + /** @name PalletFungibleError (425) */ interface PalletFungibleError extends Enum { readonly isNotFungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isFungibleItemsHaveNoId: boolean; @@ -3565,7 +3594,7 @@ readonly type: 'NotFungibleDataUsedToMintFungibleCollectionToken' | 'FungibleItemsHaveNoId' | 'FungibleItemsDontHaveData' | 'FungibleDisallowsNesting' | 'SettingPropertiesNotAllowed' | 'SettingAllowanceForAllNotAllowed' | 'FungibleTokensAreAlwaysValid'; } - /** @name PalletRefungibleError (428) */ + /** @name PalletRefungibleError (429) */ interface PalletRefungibleError extends Enum { readonly isNotRefungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isWrongRefungiblePieces: boolean; @@ -3575,19 +3604,19 @@ readonly type: 'NotRefungibleDataUsedToMintFungibleCollectionToken' | 'WrongRefungiblePieces' | 'RepartitionWhileNotOwningAllPieces' | 'RefungibleDisallowsNesting' | 'SettingPropertiesNotAllowed'; } - /** @name PalletNonfungibleItemData (429) */ + /** @name PalletNonfungibleItemData (430) */ interface PalletNonfungibleItemData extends Struct { readonly owner: PalletEvmAccountBasicCrossAccountIdRepr; } - /** @name UpDataStructsPropertyScope (431) */ + /** @name UpDataStructsPropertyScope (432) */ interface UpDataStructsPropertyScope extends Enum { readonly isNone: boolean; readonly isRmrk: boolean; readonly type: 'None' | 'Rmrk'; } - /** @name PalletNonfungibleError (434) */ + /** @name PalletNonfungibleError (435) */ interface PalletNonfungibleError extends Enum { readonly isNotNonfungibleDataUsedToMintFungibleCollectionToken: boolean; readonly isNonfungibleItemsHaveNoAmount: boolean; @@ -3595,7 +3624,7 @@ readonly type: 'NotNonfungibleDataUsedToMintFungibleCollectionToken' | 'NonfungibleItemsHaveNoAmount' | 'CantBurnNftWithChildren'; } - /** @name PalletStructureError (435) */ + /** @name PalletStructureError (436) */ interface PalletStructureError extends Enum { readonly isOuroborosDetected: boolean; readonly isDepthLimit: boolean; @@ -3604,7 +3633,7 @@ readonly type: 'OuroborosDetected' | 'DepthLimit' | 'BreadthLimit' | 'TokenNotFound'; } - /** @name PalletRmrkCoreError (436) */ + /** @name PalletRmrkCoreError (437) */ interface PalletRmrkCoreError extends Enum { readonly isCorruptedCollectionType: boolean; readonly isRmrkPropertyKeyIsTooLong: boolean; @@ -3628,7 +3657,7 @@ readonly type: 'CorruptedCollectionType' | 'RmrkPropertyKeyIsTooLong' | 'RmrkPropertyValueIsTooLong' | 'RmrkPropertyIsNotFound' | 'UnableToDecodeRmrkData' | 'CollectionNotEmpty' | 'NoAvailableCollectionId' | 'NoAvailableNftId' | 'CollectionUnknown' | 'NoPermission' | 'NonTransferable' | 'CollectionFullOrLocked' | 'ResourceDoesntExist' | 'CannotSendToDescendentOrSelf' | 'CannotAcceptNonOwnedNft' | 'CannotRejectNonOwnedNft' | 'CannotRejectNonPendingNft' | 'ResourceNotPending' | 'NoAvailableResourceId'; } - /** @name PalletRmrkEquipError (438) */ + /** @name PalletRmrkEquipError (439) */ interface PalletRmrkEquipError extends Enum { readonly isPermissionError: boolean; readonly isNoAvailableBaseId: boolean; @@ -3640,7 +3669,7 @@ readonly type: 'PermissionError' | 'NoAvailableBaseId' | 'NoAvailablePartId' | 'BaseDoesntExist' | 'NeedsDefaultThemeFirst' | 'PartDoesntExist' | 'NoEquippableOnFixedPart'; } - /** @name PalletAppPromotionError (444) */ + /** @name PalletAppPromotionError (445) */ interface PalletAppPromotionError extends Enum { readonly isAdminNotSet: boolean; readonly isNoPermission: boolean; @@ -3651,7 +3680,7 @@ readonly type: 'AdminNotSet' | 'NoPermission' | 'NotSufficientFunds' | 'PendingForBlockOverflow' | 'SponsorNotSet' | 'IncorrectLockedBalanceOperation'; } - /** @name PalletForeignAssetsModuleError (445) */ + /** @name PalletForeignAssetsModuleError (446) */ interface PalletForeignAssetsModuleError extends Enum { readonly isBadLocation: boolean; readonly isMultiLocationExisted: boolean; @@ -3660,7 +3689,7 @@ readonly type: 'BadLocation' | 'MultiLocationExisted' | 'AssetIdNotExists' | 'AssetIdExisted'; } - /** @name PalletEvmError (447) */ + /** @name PalletEvmError (448) */ interface PalletEvmError extends Enum { readonly isBalanceLow: boolean; readonly isFeeOverflow: boolean; @@ -3676,7 +3705,7 @@ readonly type: 'BalanceLow' | 'FeeOverflow' | 'PaymentOverflow' | 'WithdrawFailed' | 'GasPriceTooLow' | 'InvalidNonce' | 'GasLimitTooLow' | 'GasLimitTooHigh' | 'Undefined' | 'Reentrancy' | 'TransactionMustComeFromEOA'; } - /** @name FpRpcTransactionStatus (450) */ + /** @name FpRpcTransactionStatus (451) */ interface FpRpcTransactionStatus extends Struct { readonly transactionHash: H256; readonly transactionIndex: u32; @@ -3687,10 +3716,10 @@ readonly logsBloom: EthbloomBloom; } - /** @name EthbloomBloom (452) */ + /** @name EthbloomBloom (453) */ interface EthbloomBloom extends U8aFixed {} - /** @name EthereumReceiptReceiptV3 (454) */ + /** @name EthereumReceiptReceiptV3 (455) */ interface EthereumReceiptReceiptV3 extends Enum { readonly isLegacy: boolean; readonly asLegacy: EthereumReceiptEip658ReceiptData; @@ -3701,7 +3730,7 @@ readonly type: 'Legacy' | 'Eip2930' | 'Eip1559'; } - /** @name EthereumReceiptEip658ReceiptData (455) */ + /** @name EthereumReceiptEip658ReceiptData (456) */ interface EthereumReceiptEip658ReceiptData extends Struct { readonly statusCode: u8; readonly usedGas: U256; @@ -3709,14 +3738,14 @@ readonly logs: Vec; } - /** @name EthereumBlock (456) */ + /** @name EthereumBlock (457) */ interface EthereumBlock extends Struct { readonly header: EthereumHeader; readonly transactions: Vec; readonly ommers: Vec; } - /** @name EthereumHeader (457) */ + /** @name EthereumHeader (458) */ interface EthereumHeader extends Struct { readonly parentHash: H256; readonly ommersHash: H256; @@ -3735,24 +3764,24 @@ readonly nonce: EthereumTypesHashH64; } - /** @name EthereumTypesHashH64 (458) */ + /** @name EthereumTypesHashH64 (459) */ interface EthereumTypesHashH64 extends U8aFixed {} - /** @name PalletEthereumError (463) */ + /** @name PalletEthereumError (464) */ interface PalletEthereumError extends Enum { readonly isInvalidSignature: boolean; readonly isPreLogExists: boolean; readonly type: 'InvalidSignature' | 'PreLogExists'; } - /** @name PalletEvmCoderSubstrateError (464) */ + /** @name PalletEvmCoderSubstrateError (465) */ interface PalletEvmCoderSubstrateError extends Enum { readonly isOutOfGas: boolean; readonly isOutOfFund: boolean; readonly type: 'OutOfGas' | 'OutOfFund'; } - /** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr (465) */ + /** @name UpDataStructsSponsorshipStateBasicCrossAccountIdRepr (466) */ interface UpDataStructsSponsorshipStateBasicCrossAccountIdRepr extends Enum { readonly isDisabled: boolean; readonly isUnconfirmed: boolean; @@ -3762,7 +3791,7 @@ readonly type: 'Disabled' | 'Unconfirmed' | 'Confirmed'; } - /** @name PalletEvmContractHelpersSponsoringModeT (466) */ + /** @name PalletEvmContractHelpersSponsoringModeT (467) */ interface PalletEvmContractHelpersSponsoringModeT extends Enum { readonly isDisabled: boolean; readonly isAllowlisted: boolean; @@ -3770,7 +3799,7 @@ readonly type: 'Disabled' | 'Allowlisted' | 'Generous'; } - /** @name PalletEvmContractHelpersError (472) */ + /** @name PalletEvmContractHelpersError (473) */ interface PalletEvmContractHelpersError extends Enum { readonly isNoPermission: boolean; readonly isNoPendingSponsor: boolean; @@ -3778,7 +3807,7 @@ readonly type: 'NoPermission' | 'NoPendingSponsor' | 'TooManyMethodsHaveSponsoredLimit'; } - /** @name PalletEvmMigrationError (473) */ + /** @name PalletEvmMigrationError (474) */ interface PalletEvmMigrationError extends Enum { readonly isAccountNotEmpty: boolean; readonly isAccountIsNotMigrating: boolean; @@ -3786,17 +3815,17 @@ readonly type: 'AccountNotEmpty' | 'AccountIsNotMigrating' | 'BadEvent'; } - /** @name PalletMaintenanceError (474) */ + /** @name PalletMaintenanceError (475) */ type PalletMaintenanceError = Null; - /** @name PalletTestUtilsError (475) */ + /** @name PalletTestUtilsError (476) */ interface PalletTestUtilsError extends Enum { readonly isTestPalletDisabled: boolean; readonly isTriggerRollback: boolean; readonly type: 'TestPalletDisabled' | 'TriggerRollback'; } - /** @name SpRuntimeMultiSignature (477) */ + /** @name SpRuntimeMultiSignature (478) */ interface SpRuntimeMultiSignature extends Enum { readonly isEd25519: boolean; readonly asEd25519: SpCoreEd25519Signature; @@ -3807,40 +3836,43 @@ readonly type: 'Ed25519' | 'Sr25519' | 'Ecdsa'; } - /** @name SpCoreEd25519Signature (478) */ + /** @name SpCoreEd25519Signature (479) */ interface SpCoreEd25519Signature extends U8aFixed {} - /** @name SpCoreSr25519Signature (480) */ + /** @name SpCoreSr25519Signature (481) */ interface SpCoreSr25519Signature extends U8aFixed {} - /** @name SpCoreEcdsaSignature (481) */ + /** @name SpCoreEcdsaSignature (482) */ interface SpCoreEcdsaSignature extends U8aFixed {} - /** @name FrameSystemExtensionsCheckSpecVersion (484) */ + /** @name FrameSystemExtensionsCheckSpecVersion (485) */ type FrameSystemExtensionsCheckSpecVersion = Null; - /** @name FrameSystemExtensionsCheckTxVersion (485) */ + /** @name FrameSystemExtensionsCheckTxVersion (486) */ type FrameSystemExtensionsCheckTxVersion = Null; - /** @name FrameSystemExtensionsCheckGenesis (486) */ + /** @name FrameSystemExtensionsCheckGenesis (487) */ type FrameSystemExtensionsCheckGenesis = Null; - /** @name FrameSystemExtensionsCheckNonce (489) */ + /** @name FrameSystemExtensionsCheckNonce (490) */ interface FrameSystemExtensionsCheckNonce extends Compact {} - /** @name FrameSystemExtensionsCheckWeight (490) */ + /** @name FrameSystemExtensionsCheckWeight (491) */ type FrameSystemExtensionsCheckWeight = Null; - /** @name OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance (491) */ + /** @name OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance (492) */ type OpalRuntimeRuntimeCommonMaintenanceCheckMaintenance = Null; - /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (492) */ + /** @name OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls (493) */ + type OpalRuntimeRuntimeCommonIdentityDisableIdentityCalls = Null; + + /** @name PalletTemplateTransactionPaymentChargeTransactionPayment (494) */ interface PalletTemplateTransactionPaymentChargeTransactionPayment extends Compact {} - /** @name OpalRuntimeRuntime (493) */ + /** @name OpalRuntimeRuntime (495) */ type OpalRuntimeRuntime = Null; - /** @name PalletEthereumFakeTransactionFinalizer (494) */ + /** @name PalletEthereumFakeTransactionFinalizer (496) */ type PalletEthereumFakeTransactionFinalizer = Null; } // declare module --- a/tests/src/pallet-presence.test.ts +++ b/tests/src/pallet-presence.test.ts @@ -66,6 +66,7 @@ const foreignAssets = 'foreignassets'; const rmrkPallets = ['rmrkcore', 'rmrkequip']; const appPromotion = 'apppromotion'; + const collatorSelection = ['authorship', 'session', 'collatorselection', 'identity']; const testUtils = 'testutils'; if (chain.eq('OPAL by UNIQUE')) { @@ -81,6 +82,7 @@ refungible, appPromotion, foreignAssets, + ...collatorSelection, ); } else if (chain.eq('UNIQUE')) { // Insert Unique additional pallets here --- /dev/null +++ b/tests/src/util/identitySetter.ts @@ -0,0 +1,86 @@ +// Copyright 2019-2022 Unique Network (Gibraltar) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +import {encodeAddress} from '@polkadot/keyring'; +import {usingPlaygrounds, Pallets} from './index'; +import {ChainHelperBase} from './playgrounds/unique'; + +const relayUrl = process.argv[2] ?? 'ws://localhost:9844'; +const paraUrl = process.argv[3] ?? 'ws://localhost:9944'; +const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice'; + +function extractIdentity(key: any, value: any): [string, any] { + return [(key as any).toHuman()[0], (value as any).unwrap()]; +} + +async function getIdentities(helper: ChainHelperBase) { + const identities: [string, any][] = []; + for(const [key, value] of await helper.getApi().query.identity.identityOf.entries()) + identities.push(extractIdentity(key, value)); + return identities; +} + +// This is a utility for pulling +const forceInsertIdentities = async (): Promise => { + const identitiesOnRelay: any[] = []; + const identitiesToRemove: string[] = []; + await usingPlaygrounds(async helper => { + try { + // iterate over every identity + for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) { + const value = v as any; + if (value.isNone) { + // in the nigh-impossible case that storage map would actually give None for a value, might as well delete it + identitiesToRemove.push((key as any).toHuman()[0]); + continue; + } + + // if any of the judgements resulted in a good confirmed outcome, keep this identity + if (value.unwrap().toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue; + identitiesOnRelay.push(extractIdentity(key, value)); + } + } catch (error) { + console.error(error); + throw Error('Error during fetching identities'); + } + }, relayUrl); + + await usingPlaygrounds(async (helper, privateKey) => { + if (helper.fetchMissingPalletNames([Pallets.Identity]).length != 0) console.error('pallet-identity is not included in parachain.'); + try { + const superuser = await privateKey(key); + const ss58Format = helper.chain.getChainProperties().ss58Format; + const paraIdentities = await getIdentities(helper); + const identitiesToAdd: any[] = []; + + // cross-reference every account for changes + for (const [key, value] of identitiesOnRelay) { + const encodedKey = encodeAddress(key, ss58Format); + + const identity = paraIdentities.find(i => i[0] === encodedKey); + if (identity) { + // only update if the identity info does not exist or is changed + if (value.toString() === identity[1].toString()) { + continue; + } + } + identitiesToAdd.push([key, value]); + // exercise caution - in case we have an identity and the realy doesn't, it might mean one of two things: + // 1) it was deleted on the relay; + // 2) it is our own identity, we don't want to delete it. + // identitiesToRemove.push((key as any).toHuman()[0]); + } + + // await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identitiesToAdd]); + console.log(`Tried to upload ${identitiesToAdd.length} identities ` + + `and found ${identitiesToRemove.length} identities for potential removal. ` + + `Now there are ${(await helper.getApi().query.identity.identityOf.keys()).length}.`); + } catch (error) { + console.error(error); + throw Error('Error during setting identities'); + } + }, paraUrl); +}; + +forceInsertIdentities().catch(() => process.exit(1)); \ No newline at end of file --- a/tests/src/util/index.ts +++ b/tests/src/util/index.ts @@ -110,6 +110,9 @@ NFT = 'nonfungible', Scheduler = 'scheduler', AppPromotion = 'apppromotion', + CollatorSelection = 'collatorselection', + Session = 'session', + Identity = 'identity', TestUtils = 'testutils', } --- a/tests/src/util/playgrounds/unique.dev.ts +++ b/tests/src/util/playgrounds/unique.dev.ts @@ -66,6 +66,7 @@ arrange: ArrangeGroup; wait: WaitGroup; admin: AdminGroup; + session: SessionGroup; testUtils: TestUtilGroup; constructor(logger: { log: (msg: any, level: any) => void, level: any }, options: {[key: string]: any} = {}) { @@ -76,6 +77,7 @@ this.wait = new WaitGroup(this); this.admin = new AdminGroup(this); this.testUtils = new TestUtilGroup(this); + this.session = new SessionGroup(this); } async connect(wsEndpoint: string, _listeners?: any): Promise { @@ -91,6 +93,10 @@ extrinsic: {}, payload: {}, }, + DisableIdentityCalls: { + extrinsic: {}, + payload: {}, + }, FakeTransactionFinalizer: { extrinsic: {}, payload: {}, @@ -251,16 +257,17 @@ const accounts: IKeyringPair[] = []; let nonce = await this.helper.chain.getNonce(donor.address); const tokenNominal = this.helper.balance.getOneTokenNominal(); + const ss58Format = this.helper.chain.getChainProperties().ss58Format; for (let i = 0; i < accountsToCreate; i++) { if (i === 500) { // if there are too many accounts to create await Promise.allSettled(transactions); // wait while first 500 (should be 100 for devnode) tx will be settled transactions = []; // nonce = await this.helper.chain.getNonce(donor.address); // update nonce } - const recepient = this.helper.util.fromSeed(mnemonicGenerate()); - accounts.push(recepient); + const recipient = this.helper.util.fromSeed(mnemonicGenerate(), ss58Format); + accounts.push(recipient); if (withBalance !== 0n) { - const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recepient.address}, withBalance * tokenNominal]); + const tx = this.helper.constructApiCall('api.tx.balances.transfer', [{Id: recipient.address}, withBalance * tokenNominal]); transactions.push(this.helper.signTransaction(donor, tx, {nonce}, 'account generation')); nonce++; } @@ -337,7 +344,7 @@ const kvStr = JSON.stringify(kvJson); const chainql = spawnSync( - 'chainql', + 'chainql', [ `--tla-code=data=${kvStr}`, '-e', `function(data) cql.dump(cql.chain("${this.helper.getEndpoint()}").latest._meta, data, {omit_empty:true})`, @@ -457,6 +464,27 @@ } /** + * Launch some async operation, or throw an error after some time. Note that it will still continue executing after the timeout. + * @param promise async operation to race against the timeout + * @param timeoutMS time after which to time out + * @param timeoutError error message to throw + * @returns promise of the same type the operation had + */ + withTimeout( + promise: Promise, + timeoutMS = 30000, + timeoutError = 'The operation has timed out!', + ): Promise { + const timeout = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error(timeoutError)); + }, timeoutMS); + }); + + return Promise.race([promise, timeout]).catch(e => {throw new Error(e);}); + } + + /** * Wait for specified number of blocks * @param blocksCount number of blocks to wait * @returns @@ -478,6 +506,30 @@ return promise; } + /** + * Wait for the specified number of sessions to pass. + * Only applicable if the Session pallet is turned on. + * @param sessionCount number of sessions to wait + * @param blockTimeout time in ms until panicking that the chain has stopped producing blocks + * @returns + */ + async newSessions(sessionCount = 1, blockTimeout = 60000): Promise { + console.log(`Waiting for ${sessionCount} new session${sessionCount > 1 ? 's' : ''}.` + + ' This might take a while -- check SessionPeriod in pallet_session::Config for session time.'); + + const expectedSessionIndex = await (this.helper as DevUniqueHelper).session.getIndex() + sessionCount; + let currentSessionIndex = -1; + + while (currentSessionIndex < expectedSessionIndex) { + // eslint-disable-next-line no-async-promise-executor + currentSessionIndex = await this.withTimeout(new Promise(async (resolve) => { + await this.newBlocks(1); + const res = await (this.helper as DevUniqueHelper).session.getIndex(); + resolve(res); + }), blockTimeout, 'The chain has stopped producing blocks!'); + } + } + async forParachainBlockNumber(blockNumber: bigint | number, timeout?: number) { timeout = timeout ?? 30 * 60 * 1000; // eslint-disable-next-line no-async-promise-executor @@ -561,6 +613,36 @@ } } +class SessionGroup { + helper: ChainHelperBase; + + constructor(helper: ChainHelperBase) { + this.helper = helper; + } + + //todo:collator documentation + async getIndex(): Promise { + return (await this.helper.callRpc('api.query.session.currentIndex')).toNumber(); + } + + newSessions(sessionCount = 1, blockTimeout = 24000): Promise { + return (this.helper as DevUniqueHelper).wait.newSessions(sessionCount, blockTimeout); + } + + setOwnKeys(signer: TSigner, key: string) { + return this.helper.executeExtrinsic( + signer, + 'api.tx.session.setKeys', + [key, '0x0'], + true, + ); + } + + setOwnKeysFromAddress(signer: TSigner) { + return this.setOwnKeys(signer, '0x' + Buffer.from(signer.addressRaw).toString('hex')); + } +} + class TestUtilGroup { helper: DevUniqueHelper; --- a/tests/src/util/playgrounds/unique.ts +++ b/tests/src/util/playgrounds/unique.ts @@ -2733,6 +2733,66 @@ } } +class CollatorSelectionGroup extends HelperGroup { + //todo:collator documentation + addInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.addInvulnerable', [address]); + } + + removeInvulnerable(signer: TSigner, address: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.removeInvulnerable', [address]); + } + + async getInvulnerables(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.invulnerables')).map((x: any) => x.toHuman()); + } + + /** and also total max invulnerables */ + maxCollators(): number { + return (this.helper.getApi().consts.configuration.defaultCollatorSelectionMaxCollators.toJSON() as number); + } + + async getDesiredCollators(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionDesiredCollatorsOverride')).toNumber(); + } + + setLicenseBond(signer: TSigner, amount: bigint) { + return this.helper.executeExtrinsic(signer, 'api.tx.configuration.setCollatorSelectionLicenseBond', [amount]); + } + + async getLicenseBond(): Promise { + return (await this.helper.callRpc('api.query.configuration.collatorSelectionLicenseBondOverride')).toBigInt(); + } + + obtainLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.getLicense', []); + } + + releaseLicense(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.releaseLicense', []); + } + + forceReleaseLicense(signer: TSigner, released: string) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.forceReleaseLicense', [released]); + } + + async hasLicense(address: string): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.licenseDepositOf', [address])).toBigInt(); + } + + onboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.onboard', []); + } + + offboard(signer: TSigner) { + return this.helper.executeExtrinsic(signer, 'api.tx.collatorSelection.offboard', []); + } + + async getCandidates(): Promise { + return (await this.helper.callRpc('api.query.collatorSelection.candidates')).map((x: any) => x.toHuman()); + } +} + class ForeignAssetsGroup extends HelperGroup { async register(signer: TSigner, ownerAddress: TSubstrateAccount, location: any, metadata: IForeignAssetMetadata) { await this.helper.executeExtrinsic( @@ -2957,6 +3017,7 @@ ft: FTGroup; staking: StakingGroup; scheduler: SchedulerGroup; + collatorSelection: CollatorSelectionGroup; foreignAssets: ForeignAssetsGroup; xcm: XcmGroup; xTokens: XTokensGroup; @@ -2972,6 +3033,7 @@ this.ft = new FTGroup(this); this.staking = new StakingGroup(this); this.scheduler = new SchedulerGroup(this); + this.collatorSelection = new CollatorSelectionGroup(this); this.foreignAssets = new ForeignAssetsGroup(this); this.xcm = new XcmGroup(this, 'polkadotXcm'); this.xTokens = new XTokensGroup(this); @@ -3139,19 +3201,35 @@ super(...args); } - executeExtrinsic ( + async executeExtrinsic( sender: IKeyringPair, extrinsic: string, params: any[], expectSuccess?: boolean, + options: Partial|null = null, ): Promise { const call = this.constructApiCall(extrinsic, params); - return super.executeExtrinsic( + const result = await super.executeExtrinsic( sender, 'api.tx.sudo.sudo', [call], expectSuccess, + options, ); + + if (result.status === 'Fail') return result; + + const data = (result.result.events.find(x => x.event.section == 'sudo' && x.event.method == 'Sudid')?.event.data as any).sudoResult; + if (data.isErr) { + if (data.asErr.isModule) { + const error = (result.result.events[1].event.data as any).sudoResult.asErr.asModule; + const metaError = super.getApi()?.registry.findMetaError(error); + throw new Error(`${metaError.section}.${metaError.name}`); + } else { + throw new Error(data.asErr.toHuman()); + } + } + return result; } }; } -- gitstuff