diff --git a/Cargo.lock b/Cargo.lock index 8737101494..bcf52ff339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8534,7 +8534,6 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-preimage", - "pallet-registry", "pallet-safe-mode", "pallet-scheduler", "pallet-session", @@ -10532,25 +10531,6 @@ dependencies = [ "sp-runtime", ] -[[package]] -name = "pallet-registry" -version = "4.0.0-dev" -dependencies = [ - "enumflags2", - "frame-benchmarking", - "frame-support", - "frame-system", - "pallet-balances", - "parity-scale-codec", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", - "subtensor-macros", - "subtensor-runtime-common", -] - [[package]] name = "pallet-remark" version = "41.0.0" diff --git a/Cargo.toml b/Cargo.toml index 1a219ca99e..e66ecc3a06 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,7 +59,6 @@ node-subtensor-runtime = { path = "runtime", default-features = false } pallet-admin-utils = { path = "pallets/admin-utils", default-features = false } pallet-limit-orders = { path = "pallets/limit-orders", default-features = false } pallet-commitments = { path = "pallets/commitments", default-features = false } -pallet-registry = { path = "pallets/registry", default-features = false } pallet-crowdloan = { path = "pallets/crowdloan", default-features = false } pallet-subtensor = { path = "pallets/subtensor", default-features = false } pallet-subtensor-swap = { path = "pallets/swap", default-features = false } diff --git a/pallets/registry/Cargo.toml b/pallets/registry/Cargo.toml deleted file mode 100644 index 08d774884a..0000000000 --- a/pallets/registry/Cargo.toml +++ /dev/null @@ -1,62 +0,0 @@ -[package] -name = "pallet-registry" -version = "4.0.0-dev" -description = "Simplified identity system for network participants." -authors = ["Bittensor Nucleus Team"] -homepage = "https://bittensor.com" -edition.workspace = true -license = "Unlicense" -publish = false -repository = "https://github.com/opentensor/subtensor" - -[lints] -workspace = true - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -subtensor-macros.workspace = true -codec = { workspace = true, features = ["derive", "max-encoded-len"] } -scale-info = { workspace = true, features = ["derive"] } -frame-benchmarking = { workspace = true, optional = true } -frame-support.workspace = true -frame-system.workspace = true -sp-runtime.workspace = true -sp-std.workspace = true -enumflags2.workspace = true -sp-core.workspace = true -sp-io.workspace = true -pallet-balances.workspace = true -subtensor-runtime-common.workspace = true - -[features] -default = ["std"] -std = [ - "codec/std", - "frame-benchmarking?/std", - "frame-support/std", - "frame-system/std", - "scale-info/std", - "sp-std/std", - "sp-runtime/std", - "enumflags2/std", - "sp-io/std", - "pallet-balances/std", - "subtensor-runtime-common/std", - "sp-core/std", -] -runtime-benchmarks = [ - "frame-benchmarking/runtime-benchmarks", - "frame-support/runtime-benchmarks", - "frame-system/runtime-benchmarks", - "sp-runtime/runtime-benchmarks", - "pallet-balances/runtime-benchmarks", - "subtensor-runtime-common/runtime-benchmarks", -] -try-runtime = [ - "frame-support/try-runtime", - "frame-system/try-runtime", - "sp-runtime/try-runtime", - "pallet-balances/try-runtime", -] diff --git a/pallets/registry/src/benchmarking.rs b/pallets/registry/src/benchmarking.rs deleted file mode 100644 index 244ffe2599..0000000000 --- a/pallets/registry/src/benchmarking.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! Benchmarking setup -#![cfg(feature = "runtime-benchmarks")] -#![allow( - clippy::arithmetic_side_effects, - clippy::expect_used, - clippy::unwrap_used -)] -use super::*; - -#[allow(unused)] -use crate::Pallet as Registry; -use frame_benchmarking::v2::*; -use frame_support::traits::{Get, tokens::fungible::Mutate}; -use frame_system::RawOrigin; -use sp_std::vec; - -fn assert_last_event( - generic_event: ::RuntimeEvent, -) { - frame_system::Pallet::::assert_last_event(generic_event.into()); -} - -// 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() - .expect("size does not exceed 64; qed"), - ); - - IdentityInfo { - additional: Default::default(), - 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(where BalanceOf: From)] -mod benchmarks { - use super::*; - - #[benchmark] - fn set_identity() { - // The target user - let caller: T::AccountId = whitelisted_caller(); - let deposit = T::InitialDeposit::get() * 10u64.into(); - let _ = T::Currency::set_balance(&caller, deposit); - - #[extrinsic_call] - _( - RawOrigin::Signed(caller.clone()), - caller.clone(), - Box::new(create_identity_info::(0)), - ); - - assert_last_event::(Event::::IdentitySet { who: caller }.into()); - } - - #[benchmark] - fn clear_identity() { - // The target user - let caller: T::AccountId = whitelisted_caller(); - let _ = T::Currency::set_balance(&caller, T::InitialDeposit::get() * 10u64.into()); - - Registry::::set_identity( - RawOrigin::Signed(caller.clone()).into(), - caller.clone(), - Box::new(create_identity_info::(0)), - ) - .unwrap(); - - #[extrinsic_call] - _(RawOrigin::Signed(caller.clone()), caller.clone()); - - assert_last_event::(Event::::IdentityDissolved { who: caller }.into()); - } - - impl_benchmark_test_suite!(Registry, crate::mock::new_test_ext(), crate::mock::Test); -} diff --git a/pallets/registry/src/lib.rs b/pallets/registry/src/lib.rs deleted file mode 100644 index f3b76bc529..0000000000 --- a/pallets/registry/src/lib.rs +++ /dev/null @@ -1,213 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -#[cfg(test)] -pub mod mock; -#[cfg(test)] -mod tests; - -mod benchmarking; -pub mod types; -pub mod weights; - -pub use pallet::*; -pub use types::*; -pub use weights::WeightInfo; - -use frame_support::traits::tokens::{ - Precision, - fungible::{self, MutateHold as _}, -}; -use sp_runtime::{Saturating, traits::Zero}; -use sp_std::boxed::Box; - -type BalanceOf = - <::Currency as fungible::Inspect<::AccountId>>::Balance; - -#[deny(missing_docs)] -#[frame_support::pallet] -#[allow(clippy::expect_used)] -pub mod pallet { - use super::*; - use frame_support::{pallet_prelude::*, traits::tokens::fungible}; - use frame_system::pallet_prelude::*; - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - // Configure the pallet by specifying the parameters and types on which it depends. - #[pallet::config] - pub trait Config: frame_system::Config { - /// Currency type that will be used to place deposits on neurons - #[allow(deprecated)] - type Currency: fungible::Mutate - + fungible::MutateHold; - - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; - - /// Interface to allow other pallets to control who can register identities - type CanRegister: crate::CanRegisterIdentity; - - /// Configuration fields - /// Maximum user-configured additional fields - #[pallet::constant] - type MaxAdditionalFields: Get; - - /// The amount held on deposit for a registered identity - #[pallet::constant] - type InitialDeposit: Get>; - - /// The amount held on deposit per additional field for a registered identity. - #[pallet::constant] - type FieldDeposit: Get>; - - /// Reasons for putting funds on hold. - type RuntimeHoldReason: From; - } - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - /// Emitted when a user registers an identity - IdentitySet { - /// The account that registered the identity - who: T::AccountId, - }, - /// Emitted when a user dissolves an identity - IdentityDissolved { - /// The account that dissolved the identity - who: T::AccountId, - }, - } - - #[pallet::error] - pub enum Error { - /// Account attempted to register an identity but does not meet the requirements. - CannotRegister, - /// Account passed too many additional fields to their identity - TooManyFieldsInIdentityInfo, - /// Account doesn't have a registered identity - NotRegistered, - } - - /// Enum to hold reasons for putting funds on hold. - #[pallet::composite_enum] - pub enum HoldReason { - /// Funds are held for identity registration - RegistryIdentity, - } - - /// Identity data by account - #[pallet::storage] - #[pallet::getter(fn identity_of)] - pub(super) type IdentityOf = StorageMap< - _, - Twox64Concat, - T::AccountId, - Registration, T::MaxAdditionalFields>, - OptionQuery, - >; - - #[pallet::call] - impl Pallet { - #![deny(clippy::expect_used)] - - /// Register an identity for an account. This will overwrite any existing identity. - #[pallet::call_index(0)] - #[pallet::weight(( - T::WeightInfo::set_identity(), - DispatchClass::Normal - ))] - pub fn set_identity( - origin: OriginFor, - identified: T::AccountId, - info: Box>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - ensure!( - T::CanRegister::can_register(&who, &identified), - Error::::CannotRegister - ); - - let extra_fields = info.additional.len() as u32; - ensure!( - extra_fields <= T::MaxAdditionalFields::get(), - Error::::TooManyFieldsInIdentityInfo - ); - - let fd = >::from(extra_fields).saturating_mul(T::FieldDeposit::get()); - let mut id = match >::get(&identified) { - Some(mut id) => { - id.info = *info; - id - } - None => Registration { - info: *info, - deposit: Zero::zero(), - }, - }; - - let old_deposit = id.deposit; - id.deposit = T::InitialDeposit::get().saturating_add(fd); - if id.deposit > old_deposit { - T::Currency::hold( - &HoldReason::RegistryIdentity.into(), - &who, - id.deposit.saturating_sub(old_deposit), - )?; - } - if old_deposit > id.deposit { - let release_res = T::Currency::release( - &HoldReason::RegistryIdentity.into(), - &who, - old_deposit.saturating_sub(id.deposit), - Precision::BestEffort, - ); - debug_assert!(release_res.is_ok_and( - |released_amount| released_amount == old_deposit.saturating_sub(id.deposit) - )); - } - - >::insert(&identified, id); - Self::deposit_event(Event::IdentitySet { who: identified }); - - Ok(()) - } - - /// Clear the identity of an account. - #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::clear_identity())] - pub fn clear_identity( - origin: OriginFor, - identified: T::AccountId, - ) -> DispatchResultWithPostInfo { - let who = ensure_signed(origin)?; - - let id = >::take(&identified).ok_or(Error::::NotRegistered)?; - let deposit = id.total_deposit(); - - let release_res = T::Currency::release( - &HoldReason::RegistryIdentity.into(), - &who, - deposit, - Precision::BestEffort, - ); - debug_assert!(release_res.is_ok_and(|released_amount| released_amount == deposit)); - - Self::deposit_event(Event::IdentityDissolved { who: identified }); - - Ok(().into()) - } - } -} -// Interfaces to interact with other pallets -pub trait CanRegisterIdentity { - fn can_register(who: &AccountId, identified: &AccountId) -> bool; -} - -impl CanRegisterIdentity for () { - fn can_register(_: &A, _: &A) -> bool { - false - } -} diff --git a/pallets/registry/src/mock.rs b/pallets/registry/src/mock.rs deleted file mode 100644 index 32957c40bb..0000000000 --- a/pallets/registry/src/mock.rs +++ /dev/null @@ -1,81 +0,0 @@ -#![allow(clippy::expect_used)] -use crate as pallet_registry; -use frame_support::{derive_impl, parameter_types}; -use sp_core::U256; -use sp_runtime::{BuildStorage, traits::IdentityLookup}; -use subtensor_runtime_common::TaoBalance; - -type Block = frame_system::mocking::MockBlock; - -// Configure a mock runtime to test the pallet. -frame_support::construct_runtime!( - pub enum Test - { - System: frame_system = 1, - Balances: pallet_balances = 2, - Registry: pallet_registry = 3, - } -); - -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for Test { - type Block = Block; - type AccountId = U256; - type AccountData = pallet_balances::AccountData; - type Lookup = IdentityLookup; -} - -parameter_types! { - pub const ExistentialDeposit: TaoBalance = TaoBalance::new(1); -} - -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for Test { - type AccountStore = System; - type Balance = TaoBalance; - type ExistentialDeposit = ExistentialDeposit; -} - -parameter_types! { - pub const MaxAdditionalFields: u32 = 16; - pub const InitialDeposit: TaoBalance = TaoBalance::new(100); - pub const FieldDeposit: TaoBalance = TaoBalance::new(10); -} - -pub struct CanRegister; -impl pallet_registry::CanRegisterIdentity for CanRegister { - fn can_register(who: &U256, identified: &U256) -> bool { - who == identified - } -} - -impl pallet_registry::Config for Test { - type Currency = Balances; - type WeightInfo = (); - type MaxAdditionalFields = MaxAdditionalFields; - type CanRegister = CanRegister; - type InitialDeposit = InitialDeposit; - type FieldDeposit = FieldDeposit; - type RuntimeHoldReason = RuntimeHoldReason; -} - -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::::default() - .build_storage() - .expect("system storage should build ok"); - pallet_balances::GenesisConfig:: { - balances: vec![ - (U256::from(1), 10.into()), - (U256::from(2), 10.into()), - (U256::from(3), 10.into()), - (U256::from(4), 10.into()), - (U256::from(5), 3.into()), - ], - dev_accounts: None, - } - .assimilate_storage(&mut t) - .expect("balances storage should build ok"); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext -} diff --git a/pallets/registry/src/tests.rs b/pallets/registry/src/tests.rs deleted file mode 100644 index d233fe0783..0000000000 --- a/pallets/registry/src/tests.rs +++ /dev/null @@ -1 +0,0 @@ -// Testing diff --git a/pallets/registry/src/types.rs b/pallets/registry/src/types.rs deleted file mode 100644 index 0e5cbe3332..0000000000 --- a/pallets/registry/src/types.rs +++ /dev/null @@ -1,483 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 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 codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen}; -use enumflags2::{BitFlags, bitflags}; -use frame_support::{ - BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, - traits::{ConstU32, Get}, -}; -use scale_info::{ - Path, Type, TypeInfo, TypeParameter, - build::{Fields, Variants}, - meta_type, -}; -use sp_runtime::{ - RuntimeDebug, - traits::{AppendZerosInput, Zero}, -}; -use sp_std::{fmt::Debug, iter::once, ops::Add, prelude::*}; -use subtensor_macros::freeze_struct; - -/// 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, DecodeWithMemTracking, 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..=65 => { - let mut r: BoundedVec<_, _> = vec![0u8; (n as usize).saturating_sub(1)] - .try_into() - .map_err(|_| codec::Error::from("bounded vec length exceeds limit"))?; - input.read(&mut r[..])?; - Data::Raw(r) - } - 66 => Data::BlakeTwo256(<[u8; 32]>::decode(input)?), - 67 => Data::Sha256(<[u8; 32]>::decode(input)?), - 68 => Data::Keccak256(<[u8; 32]>::decode(input)?), - 69 => 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(x) => { - let l = x.len().min(64) as u8; - let mut r = vec![l.saturating_add(1)]; - r.extend_from_slice(&x[..]); - r - } - Data::BlakeTwo256(h) => once(66u8).chain(h.iter().cloned()).collect(), - Data::Sha256(h) => once(67u8).chain(h.iter().cloned()).collect(), - Data::Keccak256(h) => once(68u8).chain(h.iter().cloned()).collect(), - Data::ShaThree256(h) => once(69u8).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), - (34, 33), - (35, 34), - (36, 35), - (37, 36), - (38, 37), - (39, 38), - (40, 39), - (41, 40), - (42, 41), - (43, 42), - (44, 43), - (45, 44), - (46, 45), - (47, 46), - (48, 47), - (49, 48), - (50, 49), - (51, 50), - (52, 51), - (53, 52), - (54, 53), - (55, 54), - (56, 55), - (57, 56), - (58, 57), - (59, 58), - (60, 59), - (61, 60), - (62, 61), - (63, 62), - (64, 63), - (65, 64) - ); - - let variants = variants - .variant("BlakeTwo256", |v| { - v.index(66) - .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) - }) - .variant("Sha256", |v| { - v.index(67) - .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) - }) - .variant("Keccak256", |v| { - v.index(68) - .fields(Fields::unnamed().field(|f| f.ty::<[u8; 32]>())) - }) - .variant("ShaThree256", |v| { - v.index(69) - .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 - } -} - -/// 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).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. -#[freeze_struct("4015f12f49280ee")] -#[derive( - CloneNoBound, - Encode, - Decode, - DecodeWithMemTracking, - Eq, - MaxEncodedLen, - PartialEqNoBound, - RuntimeDebugNoBound, - TypeInfo, -)] -#[codec(mel_bound())] -#[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 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. -#[freeze_struct("797b69e82710bb21")] -#[derive( - CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound, TypeInfo, -)] -#[codec(mel_bound())] -#[scale_info(skip_type_params(MaxAdditionalFields))] -pub struct Registration< - Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, - MaxAdditionalFields: Get, -> { - /// 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, - MaxAdditionalFields: Get, -> Registration -{ - pub(crate) fn total_deposit(&self) -> Balance { - self.deposit - } -} - -impl< - Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, - MaxAdditionalFields: Get, -> Decode for Registration -{ - fn decode(input: &mut I) -> sp_std::result::Result { - let (deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?; - Ok(Self { deposit, info }) - } -} - -#[cfg(test)] -#[allow(clippy::indexing_slicing, clippy::unwrap_used)] -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) - .unwrap_or_else(|| panic!("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..64 { - data.push(Data::Raw(vec![0u8; n as usize].try_into().unwrap())) - } - - for d in data.iter() { - check_type_info(d); - } - } -} diff --git a/pallets/registry/src/weights.rs b/pallets/registry/src/weights.rs deleted file mode 100644 index b927be26ad..0000000000 --- a/pallets/registry/src/weights.rs +++ /dev/null @@ -1,107 +0,0 @@ - -//! Autogenerated weights for `pallet_registry` -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 49.1.0 -//! DATE: 2026-03-23, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `runnervm46oaq`, CPU: `AMD EPYC 7763 64-Core Processor` -//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` - -// Executed Command: -// /home/runner/work/subtensor/subtensor/target/production/node-subtensor -// benchmark -// pallet -// --runtime -// /home/runner/work/subtensor/subtensor/target/production/wbuild/node-subtensor-runtime/node_subtensor_runtime.compact.compressed.wasm -// --genesis-builder=runtime -// --genesis-builder-preset=benchmark -// --wasm-execution=compiled -// --pallet -// pallet_registry -// --extrinsic -// * -// --steps -// 50 -// --repeat -// 20 -// --no-storage-info -// --no-min-squares -// --no-median-slopes -// --output=/tmp/tmp.SfIpjZbmqj -// --template=/home/runner/work/subtensor/subtensor/.maintain/frame-weight-template.hbs - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] -#![allow(missing_docs)] -#![allow(dead_code)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use core::marker::PhantomData; - -/// Weight functions needed for `pallet_registry`. -pub trait WeightInfo { - fn set_identity() -> Weight; - fn clear_identity() -> Weight; -} - -/// Weights for `pallet_registry` using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: `Registry::IdentityOf` (r:1 w:1) - /// Proof: `Registry::IdentityOf` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(99), added: 2574, mode: `MaxEncodedLen`) - fn set_identity() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `3564` - // Minimum execution time: 50_534_000 picoseconds. - Weight::from_parts(51_626_000, 3564) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } - /// Storage: `Registry::IdentityOf` (r:1 w:1) - /// Proof: `Registry::IdentityOf` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(99), added: 2574, mode: `MaxEncodedLen`) - fn clear_identity() -> Weight { - // Proof Size summary in bytes: - // Measured: `382` - // Estimated: `3847` - // Minimum execution time: 42_379_000 picoseconds. - Weight::from_parts(43_501_000, 3847) - .saturating_add(T::DbWeight::get().reads(2_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } -} - -// For backwards compatibility and tests. -impl WeightInfo for () { - /// Storage: `Registry::IdentityOf` (r:1 w:1) - /// Proof: `Registry::IdentityOf` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(99), added: 2574, mode: `MaxEncodedLen`) - fn set_identity() -> Weight { - // Proof Size summary in bytes: - // Measured: `6` - // Estimated: `3564` - // Minimum execution time: 50_534_000 picoseconds. - Weight::from_parts(51_626_000, 3564) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } - /// Storage: `Registry::IdentityOf` (r:1 w:1) - /// Proof: `Registry::IdentityOf` (`max_values`: None, `max_size`: None, mode: `Measured`) - /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(99), added: 2574, mode: `MaxEncodedLen`) - fn clear_identity() -> Weight { - // Proof Size summary in bytes: - // Measured: `382` - // Estimated: `3847` - // Minimum execution time: 42_379_000 picoseconds. - Weight::from_parts(43_501_000, 3847) - .saturating_add(RocksDbWeight::get().reads(2_u64)) - .saturating_add(RocksDbWeight::get().writes(2_u64)) - } -} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index bff2b3d935..946e797f21 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -61,6 +61,7 @@ sp-authority-discovery.workspace = true subtensor-runtime-common.workspace = true subtensor-precompiles.workspace = true sp-weights.workspace = true +sp-io.workspace = true # Temporary sudo pallet-sudo.workspace = true @@ -88,9 +89,6 @@ pallet-transaction-payment-rpc-runtime-api.workspace = true frame-benchmarking = { workspace = true, optional = true } frame-system-benchmarking = { workspace = true, optional = true } -# Identity registry pallet for registering project info -pallet-registry.workspace = true - # Metadata commitment pallet pallet-commitments.workspace = true @@ -162,7 +160,6 @@ ethereum.workspace = true [dev-dependencies] frame-metadata.workspace = true -sp-io.workspace = true sp-tracing.workspace = true sp-keyring.workspace = true precompile-utils = { workspace = true, features = ["testing"] } @@ -218,7 +215,6 @@ std = [ "sp-transaction-pool/std", "sp-version/std", "substrate-wasm-builder", - "pallet-registry/std", "pallet-admin-utils/std", "subtensor-custom-rpc-runtime-api/std", "subtensor-transaction-fee/std", @@ -302,7 +298,6 @@ runtime-benchmarks = [ "pallet-safe-mode/runtime-benchmarks", "pallet-subtensor/runtime-benchmarks", "pallet-subtensor-proxy/runtime-benchmarks", - "pallet-registry/runtime-benchmarks", "pallet-commitments/runtime-benchmarks", "pallet-admin-utils/runtime-benchmarks", "pallet-multisig/runtime-benchmarks", @@ -362,7 +357,6 @@ try-runtime = [ "sp-runtime/try-runtime", "pallet-admin-utils/try-runtime", "pallet-commitments/try-runtime", - "pallet-registry/try-runtime", "pallet-crowdloan/try-runtime", "pallet-babe/try-runtime", "pallet-session/try-runtime", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 15607d1e09..32e16ab9d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -31,7 +31,6 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureRootWithSuccess, EnsureSigned}; use pallet_commitments::{CanCommit, OnMetadataCommitment}; use pallet_grandpa::{AuthorityId as GrandpaId, fg_primitives}; -use pallet_registry::CanRegisterIdentity; pub use pallet_shield; use pallet_subtensor::rpc_info::{ delegate_info::DelegateInfo, @@ -235,7 +234,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // `spec_version`, and `authoring_version` are the same between Wasm and native. // This value is set to 100 to notify Polkadot-JS App (https://polkadot.js.org/apps) to use // the compatible custom types. - spec_version: 418, + spec_version: 419, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1, @@ -858,43 +857,6 @@ impl pallet_preimage::Config for Runtime { >; } -pub struct AllowIdentityReg; - -impl CanRegisterIdentity for AllowIdentityReg { - #[cfg(not(feature = "runtime-benchmarks"))] - fn can_register(address: &AccountId, identified: &AccountId) -> bool { - if address != identified { - SubtensorModule::coldkey_owns_hotkey(address, identified) - && SubtensorModule::is_hotkey_registered_on_network(NetUid::ROOT, identified) - } else { - SubtensorModule::is_subnet_owner(address) - } - } - - #[cfg(feature = "runtime-benchmarks")] - fn can_register(_: &AccountId, _: &AccountId) -> bool { - true - } -} - -// Configure registry pallet. -parameter_types! { - pub const MaxAdditionalFields: u32 = 1; - pub const InitialDeposit: Balance = TaoBalance::new(100_000_000); // 0.1 TAO - pub const FieldDeposit: Balance = TaoBalance::new(100_000_000); // 0.1 TAO -} - -impl pallet_registry::Config for Runtime { - type RuntimeHoldReason = RuntimeHoldReason; - type Currency = Balances; - type CanRegister = AllowIdentityReg; - type WeightInfo = pallet_registry::weights::SubstrateWeight; - - type MaxAdditionalFields = MaxAdditionalFields; - type InitialDeposit = InitialDeposit; - type FieldDeposit = FieldDeposit; -} - parameter_types! { pub const MaxCommitFieldsInner: u32 = 3; pub const CommitmentInitialDeposit: Balance = TaoBalance::ZERO; // Free @@ -1622,7 +1584,7 @@ construct_runtime!( Preimage: pallet_preimage = 14, Scheduler: pallet_scheduler = 15, Proxy: pallet_proxy = 16, - Registry: pallet_registry = 17, + // pallet_registry was 17 Commitments: pallet_commitments = 18, AdminUtils: pallet_admin_utils = 19, SafeMode: pallet_safe_mode = 20, @@ -1679,6 +1641,7 @@ type Migrations = ( pallet_subtensor::migrations::migrate_init_total_issuance::initialise_total_issuance::Migration< Runtime, >, + migrations::PalletRegistryCleanupMigration, ); // Unchecked extrinsic type as expected by this runtime. @@ -1716,7 +1679,6 @@ mod benches { [pallet_balances, Balances] [pallet_timestamp, Timestamp] [pallet_sudo, Sudo] - [pallet_registry, Registry] [pallet_commitments, Commitments] [pallet_admin_utils, AdminUtils] [pallet_subtensor, SubtensorModule] diff --git a/runtime/src/migrations/mod.rs b/runtime/src/migrations/mod.rs index ecc48efcdb..d0fdf7f3da 100644 --- a/runtime/src/migrations/mod.rs +++ b/runtime/src/migrations/mod.rs @@ -1 +1,3 @@ -//! Export migrations from here. +mod pallet_registry_cleanup_migration; + +pub use pallet_registry_cleanup_migration::*; diff --git a/runtime/src/migrations/pallet_registry_cleanup_migration.rs b/runtime/src/migrations/pallet_registry_cleanup_migration.rs new file mode 100644 index 0000000000..9a98ce77c4 --- /dev/null +++ b/runtime/src/migrations/pallet_registry_cleanup_migration.rs @@ -0,0 +1,539 @@ +use crate::{Runtime, RuntimeHoldReason}; +use alloc::string::String; +#[cfg(feature = "try-runtime")] +use alloc::vec::Vec; +#[cfg(feature = "try-runtime")] +use codec::{Decode, Encode}; +use deprecated::RegistryHoldReason as OldRegistryHoldReason; +use deprecated::RuntimeHoldReason as OldRuntimeHoldReason; +use frame_support::{ + BoundedVec, + pallet_prelude::Zero, + storage::unhashed, + traits::{OnRuntimeUpgrade, StoredMap, tokens::IdAmount}, + weights::Weight, +}; +use sp_io::hashing::twox_128; +use sp_runtime::Saturating; + +type DbWeightOf = ::DbWeight; +#[cfg(feature = "try-runtime")] +type AccountIdOf = ::AccountId; +type BalanceOf = ::Balance; +type AccountStoreOf = ::AccountStore; + +const MIGRATION_NAME: &[u8] = b"pallet_registry_cleanup_migration"; +const REGISTRY_PALLET_NAME: &[u8] = b"Registry"; +#[cfg(test)] +const REGISTRY_IDENTITY_OF_STORAGE_NAME: &[u8] = b"IdentityOf"; + +mod deprecated { + use super::BalanceOf; + use crate::Runtime; + use codec::Decode; + use frame_support::{ + BoundedVec, + traits::{ConstU32, tokens::IdAmount}, + }; + + #[cfg_attr(test, derive(codec::Encode))] + #[derive(Decode, Copy, Clone, Eq, PartialEq, Debug)] + pub(super) enum RegistryHoldReason { + #[codec(index = 0)] + RegistryIdentity, + } + + #[cfg_attr(test, derive(codec::Encode))] + #[derive(Decode, Copy, Clone, Eq, PartialEq, Debug)] + pub(super) enum RuntimeHoldReason { + #[codec(index = 14)] + Preimage(pallet_preimage::HoldReason), + #[codec(index = 17)] + Registry(RegistryHoldReason), + #[codec(index = 20)] + SafeMode(pallet_safe_mode::HoldReason), + #[codec(index = 29)] + Contracts(pallet_contracts::HoldReason), + } + + // Aggregated variant count across all pallets defining a + // composite HoldReason when the pallet was removed. + pub(super) const VARIANT_COUNT: u32 = 5; + + pub(super) type Holds = + BoundedVec>, ConstU32>; +} + +pub struct PalletRegistryCleanupMigration; + +impl OnRuntimeUpgrade for PalletRegistryCleanupMigration { + fn on_runtime_upgrade() -> Weight { + let migration_name = MIGRATION_NAME.to_vec(); + let mut weight = Weight::zero(); + + if pallet_subtensor::HasMigrationRun::::get(&migration_name) { + log::info!( + "Migration '{}' has already run. Skipping.", + String::from_utf8_lossy(&migration_name) + ); + return weight; + } + + log::info!( + "Running migration '{}'", + String::from_utf8_lossy(&migration_name) + ); + + pallet_balances::Holds::::translate::( + |account_id, old_holds| { + weight.saturating_accrue(DbWeightOf::::get().reads_writes(1, 1)); + let mut current_holds = BoundedVec::new(); + let mut unlocked_amount = BalanceOf::::zero(); + + // Translate old holds to new holds and keep track of cleaned up amount. + for hold in old_holds { + match map_reason(hold.id) { + Some(id) => { + if current_holds + .try_push(IdAmount { + id, + amount: hold.amount, + }) + .is_err() + { + log::error!( + "too many balance holds after migration for account {:?}", + account_id + ); + } + } + None => { + unlocked_amount = unlocked_amount.saturating_add(hold.amount); + } + } + } + + // Unlock the balance if there is any. + if !unlocked_amount.is_zero() { + weight.saturating_accrue(DbWeightOf::::get().reads_writes(1, 1)); + if let Err(error) = AccountStoreOf::::mutate(&account_id, |account| { + account.reserved = account.reserved.saturating_sub(unlocked_amount); + account.free = account.free.saturating_add(unlocked_amount); + }) { + log::error!( + "failed to unlock balance during holds migration: {:?}", + error + ); + } + } + + (!current_holds.is_empty()).then_some(current_holds) + }, + ); + + let registry_prefix = twox_128(REGISTRY_PALLET_NAME); + let result = unhashed::clear_prefix(®istry_prefix, Some(u32::MAX), None); + weight.saturating_accrue( + DbWeightOf::::get().reads_writes(result.loops as u64, result.unique as u64), + ); + log::info!( + "Removed {} entries from Registry pallet storage.", + result.unique + ); + + pallet_subtensor::HasMigrationRun::::insert(&migration_name, true); + weight = weight.saturating_add(DbWeightOf::::get().writes(1)); + + log::info!( + "Migration '{}' completed successfully.", + String::from_utf8_lossy(&migration_name) + ); + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + let mut affected_accounts = Vec::new(); + + for account_id in pallet_balances::Holds::::iter_keys() { + let old_holds = decode_deprecated_holds(&account_id)?; + let mut unlocked_amount = BalanceOf::::zero(); + + for hold in old_holds { + if matches!(hold.id, OldRuntimeHoldReason::Registry(_)) { + unlocked_amount = unlocked_amount.saturating_add(hold.amount); + } + } + + if !unlocked_amount.is_zero() { + let account = AccountStoreOf::::get(&account_id); + affected_accounts.push(AffectedAccount { + account_id, + free: account.free, + reserved: account.reserved, + unlocked: unlocked_amount, + }); + } + } + + let state = PreUpgradeState { + total_issuance: pallet_balances::TotalIssuance::::get(), + affected_accounts, + }; + + Ok(state.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + let state = PreUpgradeState::decode(&mut state.as_slice()) + .map_err(|_| "failed to decode registry cleanup pre-upgrade state")?; + + if !pallet_subtensor::HasMigrationRun::::get(MIGRATION_NAME.to_vec()) { + return Err("registry cleanup migration marker was not set".into()); + } + + if pallet_balances::TotalIssuance::::get() != state.total_issuance { + return Err("registry cleanup migration changed total issuance".into()); + } + + for affected_account in state.affected_accounts { + let account = AccountStoreOf::::get(&affected_account.account_id); + let expected_free = affected_account + .free + .saturating_add(affected_account.unlocked); + let expected_reserved = affected_account + .reserved + .saturating_sub(affected_account.unlocked); + + if account.free != expected_free { + return Err("registry cleanup migration did not unlock free balance".into()); + } + + if account.reserved != expected_reserved { + return Err("registry cleanup migration did not reduce reserved balance".into()); + } + } + + for account_id in pallet_balances::Holds::::iter_keys() { + pallet_balances::Holds::::try_get(&account_id) + .map_err(|_| "failed to decode migrated balances holds")?; + } + + let registry_prefix = twox_128(REGISTRY_PALLET_NAME); + if unhashed::contains_prefixed_key(®istry_prefix) { + return Err("registry pallet storage was not cleared".into()); + } + + Ok(()) + } +} + +#[cfg(test)] +fn registry_storage_prefix(storage_name: &[u8]) -> Vec { + let mut prefix = twox_128(REGISTRY_PALLET_NAME).to_vec(); + prefix.extend_from_slice(&twox_128(storage_name)); + prefix +} + +fn map_reason(reason: OldRuntimeHoldReason) -> Option { + match reason { + OldRuntimeHoldReason::Preimage(reason) => Some(RuntimeHoldReason::Preimage(reason)), + OldRuntimeHoldReason::SafeMode(reason) => Some(RuntimeHoldReason::SafeMode(reason)), + OldRuntimeHoldReason::Contracts(reason) => Some(RuntimeHoldReason::Contracts(reason)), + OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity) => None, + } +} + +#[cfg(feature = "try-runtime")] +#[derive(Encode, Decode)] +struct PreUpgradeState { + total_issuance: BalanceOf, + affected_accounts: Vec, +} + +#[cfg(feature = "try-runtime")] +#[derive(Encode, Decode)] +struct AffectedAccount { + account_id: AccountIdOf, + free: BalanceOf, + reserved: BalanceOf, + unlocked: BalanceOf, +} + +#[cfg(feature = "try-runtime")] +fn decode_deprecated_holds( + account_id: &AccountIdOf, +) -> Result { + let key = pallet_balances::Holds::::hashed_key_for(account_id); + unhashed::get::(&key) + .ok_or("failed to decode deprecated balances holds".into()) +} + +#[cfg(test)] +#[allow(clippy::expect_used)] +mod tests { + use super::*; + use alloc::vec; + use codec::Encode; + use frame_support::{ + assert_ok, + storage::unhashed, + traits::{Currency, ReservableCurrency}, + }; + use sp_runtime::{AccountId32, BuildStorage}; + + fn new_test_ext() -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = crate::RuntimeGenesisConfig::default() + .build_storage() + .expect("runtime genesis storage should build") + .into(); + ext.execute_with(|| crate::System::set_block_number(1)); + ext + } + + fn account(seed: u8) -> AccountId32 { + AccountId32::new([seed; 32]) + } + + fn balance(amount: u64) -> BalanceOf { + amount.into() + } + + fn old_hold( + id: OldRuntimeHoldReason, + amount: u64, + ) -> IdAmount> { + IdAmount { + id, + amount: balance(amount), + } + } + + fn old_holds( + holds: alloc::vec::Vec>>, + ) -> deprecated::Holds { + holds + .try_into() + .expect("test old holds should fit the deprecated bound") + } + + fn holds_key(account_id: &AccountId32) -> alloc::vec::Vec { + pallet_balances::Holds::::hashed_key_for(account_id) + } + + fn insert_old_holds(account_id: &AccountId32, holds: deprecated::Holds) { + unhashed::put_raw(&holds_key(account_id), &holds.encode()); + } + + fn registry_identity_prefix() -> alloc::vec::Vec { + registry_storage_prefix(REGISTRY_IDENTITY_OF_STORAGE_NAME) + } + + fn insert_old_registry_identity_storage(suffix: &[u8]) -> alloc::vec::Vec { + let mut key = registry_identity_prefix(); + key.extend_from_slice(suffix); + unhashed::put_raw(&key, &[1]); + key + } + + fn insert_old_registry_storage_version() -> alloc::vec::Vec { + let key = registry_storage_prefix(b":__STORAGE_VERSION__:"); + unhashed::put_raw(&key, &[1]); + key + } + + #[test] + fn drops_registry_holds_and_unlocks_their_balance() { + new_test_ext().execute_with(|| { + let account_id = account(1); + + assert!(!pallet_subtensor::HasMigrationRun::::get( + MIGRATION_NAME.to_vec() + )); + + let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); + assert_ok!(crate::Balances::reserve(&account_id, balance(225))); + + insert_old_holds( + &account_id, + old_holds(vec![ + old_hold( + OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), + 125, + ), + old_hold( + OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), + 75, + ), + old_hold( + OldRuntimeHoldReason::SafeMode(pallet_safe_mode::HoldReason::EnterOrExtend), + 25, + ), + ]), + ); + + let registry_identity_key = insert_old_registry_identity_storage(b"account-1"); + let registry_storage_version_key = insert_old_registry_storage_version(); + assert!(unhashed::contains_prefixed_key(&twox_128( + REGISTRY_PALLET_NAME + ))); + + let issuance_before = crate::Balances::total_issuance(); + + let weight = PalletRegistryCleanupMigration::on_runtime_upgrade(); + + let account = crate::System::account(&account_id).data; + assert!(!weight.is_zero()); + assert_eq!(account.free, balance(9_900)); + assert_eq!(account.reserved, balance(100)); + assert_eq!(crate::Balances::total_issuance(), issuance_before); + + let current_holds = pallet_balances::Holds::::get(&account_id); + assert_eq!(current_holds.len(), 2); + assert!(current_holds.contains(&IdAmount { + id: RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), + amount: balance(75), + })); + assert!(current_holds.contains(&IdAmount { + id: RuntimeHoldReason::SafeMode(pallet_safe_mode::HoldReason::EnterOrExtend), + amount: balance(25), + })); + + assert!(pallet_subtensor::HasMigrationRun::::get( + MIGRATION_NAME.to_vec() + )); + assert!(unhashed::get_raw(®istry_identity_key).is_none()); + assert!(unhashed::get_raw(®istry_storage_version_key).is_none()); + assert!(!unhashed::contains_prefixed_key(&twox_128( + REGISTRY_PALLET_NAME + ))); + + let second_weight = PalletRegistryCleanupMigration::on_runtime_upgrade(); + let account_after_second = crate::System::account(&account_id).data; + + assert!(second_weight.is_zero()); + assert_eq!(account_after_second.free, account.free); + assert_eq!(account_after_second.reserved, account.reserved); + assert_eq!(account_after_second.frozen, account.frozen); + assert_eq!( + pallet_balances::Holds::::get(&account_id), + current_holds + ); + }); + } + + #[test] + fn removes_holds_storage_when_only_registry_holds_remain() { + new_test_ext().execute_with(|| { + let account_id = account(2); + + let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); + assert_ok!(crate::Balances::reserve(&account_id, balance(125))); + + insert_old_holds( + &account_id, + old_holds(vec![old_hold( + OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), + 125, + )]), + ); + + let storage_key = holds_key(&account_id); + let issuance_before = crate::Balances::total_issuance(); + + PalletRegistryCleanupMigration::on_runtime_upgrade(); + + let account = crate::System::account(&account_id).data; + assert_eq!(account.free, balance(10_000)); + assert_eq!(account.reserved, balance(0)); + assert_eq!(crate::Balances::total_issuance(), issuance_before); + assert!(pallet_balances::Holds::::get(&account_id).is_empty()); + assert!(unhashed::get_raw(&storage_key).is_none()); + }); + } + + #[test] + fn preserves_non_registry_holds_without_changing_balances() { + new_test_ext().execute_with(|| { + let account_id = account(3); + + let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); + assert_ok!(crate::Balances::reserve(&account_id, balance(100))); + + insert_old_holds( + &account_id, + old_holds(vec![ + old_hold( + OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), + 70, + ), + old_hold( + OldRuntimeHoldReason::Contracts( + pallet_contracts::HoldReason::StorageDepositReserve, + ), + 30, + ), + ]), + ); + + let issuance_before = crate::Balances::total_issuance(); + + PalletRegistryCleanupMigration::on_runtime_upgrade(); + + let account = crate::System::account(&account_id).data; + assert_eq!(account.free, balance(9_900)); + assert_eq!(account.reserved, balance(100)); + assert_eq!(crate::Balances::total_issuance(), issuance_before); + + let current_holds = pallet_balances::Holds::::get(&account_id); + assert_eq!(current_holds.len(), 2); + assert!(current_holds.contains(&IdAmount { + id: RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), + amount: balance(70), + })); + assert!(current_holds.contains(&IdAmount { + id: RuntimeHoldReason::Contracts( + pallet_contracts::HoldReason::StorageDepositReserve, + ), + amount: balance(30), + })); + }); + } + + #[cfg(feature = "try-runtime")] + #[test] + fn try_runtime_checks_validate_cleanup() { + new_test_ext().execute_with(|| { + let account_id = account(4); + + let _ = crate::Balances::make_free_balance_be(&account_id, balance(10_000)); + assert_ok!(crate::Balances::reserve(&account_id, balance(150))); + + insert_old_holds( + &account_id, + old_holds(vec![ + old_hold( + OldRuntimeHoldReason::Registry(OldRegistryHoldReason::RegistryIdentity), + 100, + ), + old_hold( + OldRuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage), + 50, + ), + ]), + ); + + insert_old_registry_identity_storage(b"account-4"); + + let state = PalletRegistryCleanupMigration::pre_upgrade() + .expect("pre-upgrade check should decode old holds"); + + PalletRegistryCleanupMigration::on_runtime_upgrade(); + + PalletRegistryCleanupMigration::post_upgrade(state) + .expect("post-upgrade check should validate migrated holds"); + }); + } +} diff --git a/runtime/tests/metadata.rs b/runtime/tests/metadata.rs index 3409098b41..fb73c58890 100644 --- a/runtime/tests/metadata.rs +++ b/runtime/tests/metadata.rs @@ -9,7 +9,6 @@ fn is_pallet_error(segments: &[String]) -> bool { "pallet_admin_utils", "pallet_subtensor_collective", "pallet_commitments", - "pallet_registry", "pallet_subtensor", ]; diff --git a/support/linting/src/pallet_index.rs b/support/linting/src/pallet_index.rs index e14617be24..f6fae12fbe 100644 --- a/support/linting/src/pallet_index.rs +++ b/support/linting/src/pallet_index.rs @@ -174,7 +174,6 @@ mod tests { Preimage : pallet_preimage = 14, Scheduler : pallet_scheduler = 15, Proxy : pallet_subtensor_proxy = 16, - Registry : pallet_registry = 17, Commitments : pallet_commitments = 18, AdminUtils : pallet_admin_utils = 19, SafeMode : pallet_safe_mode = 20