Make IdentityInfo generic in pallet-identity (#1661)

Fixes #179 

# Description

This PR makes the structure containing identity information used in
`pallet-identity` generic through the pallet `Config`. Additionally, the
old structure is now available in a separate module called `simple`
(pending rename) and is compatible with the new interface.

Another change in this PR is that while the `additional` field in
`IdentityInfo` stays for backwards compatibility reasons, the associated
costs are stil present in the pallet through the `additional` function
in the `IdentityInformationProvider` interface. This function is marked
as deprecated as it is only a temporary solution to the backwards
compatibility problem we had. In short, we could have removed the
additional fields in the struct and done a migration, but we chose to
wait and do it off-chain through the genesis of the system parachain.
After we move the identity pallet to the parachain, additional fields
will be migrated into the existing fields and the `additional` key-value
store will be removed. Until that happens, this interface will provide
the necessary information to properly account for the associated costs.

Additionally, this PR fixes an unrelated issue; the `IdentityField` enum
used to represent the fields as bitflags couldn't store more than 8
fields, even though it was marked as `#[repr(u64)]`. This was because of
the `derive` implementation of `TypeInfo`, which assumed `u8` semantics.
The custom implementation of this trait in
https://github.com/paritytech/polkadot-sdk/commit/0105cc0396b7a53d0b290f48b1225847f6d17321
fixes the issue.

---------

Signed-off-by: georgepisaltu <george.pisaltu@parity.io>
Co-authored-by: Sam Johnson <sam@durosoft.com>
Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com>
This commit is contained in:
georgepisaltu
2023-10-24 13:47:11 +02:00
committed by GitHub
parent 35eb133baa
commit 9185195185
10 changed files with 402 additions and 292 deletions
+2
View File
@@ -76,6 +76,7 @@ use frame_support::{
};
use frame_system::EnsureRoot;
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
use pallet_identity::simple::IdentityInfo;
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use pallet_session::historical as session_historical;
use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo};
@@ -610,6 +611,7 @@ impl pallet_identity::Config for Runtime {
type SubAccountDeposit = SubAccountDeposit;
type MaxSubAccounts = MaxSubAccounts;
type MaxAdditionalFields = MaxAdditionalFields;
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
type MaxRegistrars = MaxRegistrars;
type Slashed = Treasury;
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
+2
View File
@@ -40,6 +40,7 @@ use frame_support::{
};
use frame_system::EnsureRoot;
use pallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
use pallet_identity::simple::IdentityInfo;
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use pallet_session::historical as session_historical;
use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo};
@@ -876,6 +877,7 @@ impl pallet_identity::Config for Runtime {
type SubAccountDeposit = SubAccountDeposit;
type MaxSubAccounts = MaxSubAccounts;
type MaxAdditionalFields = MaxAdditionalFields;
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
type MaxRegistrars = MaxRegistrars;
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
type RegistrarOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
+2
View File
@@ -60,6 +60,7 @@ use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce};
use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter};
use pallet_broker::{CoreAssignment, CoreIndex, CoretimeInterface, PartsOf57600};
use pallet_election_provider_multi_phase::{GeometricDepositBase, SolutionAccuracyOf};
use pallet_identity::simple::IdentityInfo;
use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use pallet_nfts::PalletFeatures;
use pallet_nis::WithMaximumOf;
@@ -1474,6 +1475,7 @@ impl pallet_identity::Config for Runtime {
type SubAccountDeposit = SubAccountDeposit;
type MaxSubAccounts = MaxSubAccounts;
type MaxAdditionalFields = MaxAdditionalFields;
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
type MaxRegistrars = MaxRegistrars;
type Slashed = Treasury;
type ForceOrigin = EnsureRootOrHalfCouncil;
+1 -1
View File
@@ -112,7 +112,7 @@ use frame_support::{
},
weights::Weight,
};
use pallet_identity::IdentityField;
use pallet_identity::simple::IdentityField;
use scale_info::TypeInfo;
pub use pallet::*;
+2 -1
View File
@@ -31,7 +31,7 @@ pub use frame_support::{
BoundedVec,
};
use frame_system::{EnsureRoot, EnsureSignedBy};
use pallet_identity::{Data, IdentityInfo, Judgement};
use pallet_identity::{simple::IdentityInfo, Data, Judgement};
pub use crate as pallet_alliance;
@@ -121,6 +121,7 @@ impl pallet_identity::Config for Test {
type SubAccountDeposit = SubAccountDeposit;
type MaxSubAccounts = MaxSubAccounts;
type MaxAdditionalFields = MaxAdditionalFields;
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
type MaxRegistrars = MaxRegistrars;
type Slashed = ();
type RegistrarOrigin = EnsureOneOrRoot;
+18 -42
View File
@@ -22,6 +22,7 @@
use super::*;
use crate::Pallet as Identity;
use enumflags2::BitFlag;
use frame_benchmarking::{
account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError,
};
@@ -48,14 +49,9 @@ fn add_registrars<T: Config>(r: u32) -> Result<(), &'static str> {
.expect("RegistrarOrigin has no successful origin required for the benchmark");
Identity::<T>::add_registrar(registrar_origin, registrar_lookup)?;
Identity::<T>::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,
);
let fields = IdentityFields(
<T::IdentityInformation as IdentityInformationProvider>::IdentityField::all(),
);
Identity::<T>::set_fields(RawOrigin::Signed(registrar.clone()).into(), i, fields)?;
}
@@ -81,7 +77,7 @@ fn create_sub_accounts<T: Config>(
// Set identity so `set_subs` does not fail.
if IdentityOf::<T>::get(who).is_none() {
let _ = T::Currency::make_free_balance_be(who, BalanceOf::<T>::max_value() / 2u32.into());
let info = create_identity_info::<T>(1);
let info = T::IdentityInformation::create_identity_info(1);
Identity::<T>::set_identity(who_origin.into(), Box::new(info))?;
}
@@ -102,24 +98,6 @@ fn add_sub_accounts<T: Config>(
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<T: Config>(num_fields: u32) -> IdentityInfo<T::MaxAdditionalFields> {
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]
mod benchmarks {
use super::*;
@@ -153,7 +131,7 @@ mod benchmarks {
let _ = T::Currency::make_free_balance_be(&caller, BalanceOf::<T>::max_value());
// Add an initial identity
let initial_info = create_identity_info::<T>(1);
let initial_info = T::IdentityInformation::create_identity_info(1);
Identity::<T>::set_identity(caller_origin.clone(), Box::new(initial_info.clone()))?;
// User requests judgement from all the registrars, and they approve
@@ -174,7 +152,10 @@ mod benchmarks {
}
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), Box::new(create_identity_info::<T>(x)));
_(
RawOrigin::Signed(caller.clone()),
Box::new(T::IdentityInformation::create_identity_info(x)),
);
assert_last_event::<T>(Event::<T>::IdentitySet { who: caller }.into());
Ok(())
@@ -235,7 +216,7 @@ mod benchmarks {
let _ = add_sub_accounts::<T>(&caller, s)?;
// Create their main identity with x additional fields
let info = create_identity_info::<T>(x);
let info = T::IdentityInformation::create_identity_info(x);
Identity::<T>::set_identity(caller_origin.clone(), Box::new(info.clone()))?;
// User requests judgement from all the registrars, and they approve
@@ -275,7 +256,7 @@ mod benchmarks {
add_registrars::<T>(r)?;
// Create their main identity with x additional fields
let info = create_identity_info::<T>(x);
let info = T::IdentityInformation::create_identity_info(x);
let caller_origin =
<T as frame_system::Config>::RuntimeOrigin::from(RawOrigin::Signed(caller.clone()));
Identity::<T>::set_identity(caller_origin.clone(), Box::new(info))?;
@@ -302,7 +283,7 @@ mod benchmarks {
add_registrars::<T>(r)?;
// Create their main identity with x additional fields
let info = create_identity_info::<T>(x);
let info = T::IdentityInformation::create_identity_info(x);
let caller_origin =
<T as frame_system::Config>::RuntimeOrigin::from(RawOrigin::Signed(caller.clone()));
Identity::<T>::set_identity(caller_origin.clone(), Box::new(info))?;
@@ -386,14 +367,9 @@ mod benchmarks {
.expect("RegistrarOrigin has no successful origin required for the benchmark");
Identity::<T>::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 fields = IdentityFields(
<T::IdentityInformation as IdentityInformationProvider>::IdentityField::all(),
);
let registrars = Registrars::<T>::get();
ensure!(
@@ -431,7 +407,7 @@ mod benchmarks {
add_registrars::<T>(r)?;
let info = create_identity_info::<T>(x);
let info = T::IdentityInformation::create_identity_info(x);
let info_hash = T::Hashing::hash_of(&info);
Identity::<T>::set_identity(user_origin.clone(), Box::new(info))?;
@@ -464,7 +440,7 @@ mod benchmarks {
let target_lookup = T::Lookup::unlookup(target.clone());
let _ = T::Currency::make_free_balance_be(&target, BalanceOf::<T>::max_value());
let info = create_identity_info::<T>(x);
let info = T::IdentityInformation::create_identity_info(x);
Identity::<T>::set_identity(target_origin.clone(), Box::new(info.clone()))?;
let _ = add_sub_accounts::<T>(&target, s)?;
+63 -100
View File
@@ -73,6 +73,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
pub mod simple;
#[cfg(test)]
mod tests;
mod types;
@@ -85,7 +86,7 @@ pub use weights::WeightInfo;
pub use pallet::*;
pub use types::{
Data, IdentityField, IdentityFields, IdentityInfo, Judgement, RegistrarIndex, RegistrarInfo,
Data, IdentityFields, IdentityInformationProvider, Judgement, RegistrarIndex, RegistrarInfo,
Registration,
};
@@ -133,6 +134,9 @@ pub mod pallet {
#[pallet::constant]
type MaxAdditionalFields: Get<u32>;
/// Structure holding information about an identity.
type IdentityInformation: IdentityInformationProvider;
/// Maxmimum number of registrars allowed in the system. Needed to bound the complexity
/// of, e.g., updating judgements.
#[pallet::constant]
@@ -163,7 +167,7 @@ pub mod pallet {
_,
Twox64Concat,
T::AccountId,
Registration<BalanceOf<T>, T::MaxRegistrars, T::MaxAdditionalFields>,
Registration<BalanceOf<T>, T::MaxRegistrars, T::IdentityInformation>,
OptionQuery,
>;
@@ -197,7 +201,16 @@ pub mod pallet {
#[pallet::getter(fn registrars)]
pub(super) type Registrars<T: Config> = StorageValue<
_,
BoundedVec<Option<RegistrarInfo<BalanceOf<T>, T::AccountId>>, T::MaxRegistrars>,
BoundedVec<
Option<
RegistrarInfo<
BalanceOf<T>,
T::AccountId,
<T::IdentityInformation as IdentityInformationProvider>::IdentityField,
>,
>,
T::MaxRegistrars,
>,
ValueQuery,
>;
@@ -277,9 +290,6 @@ pub mod pallet {
/// - `account`: the account of the registrar.
///
/// Emits `RegistrarAdded` if successful.
///
/// ## Complexity
/// - `O(R)` where `R` registrar-count (governance-bounded and code-bounded).
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))]
pub fn add_registrar(
@@ -317,22 +327,18 @@ pub mod pallet {
/// - `info`: The identity information.
///
/// Emits `IdentitySet` if successful.
///
/// ## Complexity
/// - `O(X + X' + R)`
/// - where `X` additional-field-count (deposit-bounded and code-bounded)
/// - where `R` judgements-count (registrar-count-bounded)
#[pallet::call_index(1)]
#[pallet::weight( T::WeightInfo::set_identity(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
#[pallet::weight(T::WeightInfo::set_identity(
T::MaxRegistrars::get(),
T::MaxAdditionalFields::get(),
))]
pub fn set_identity(
origin: OriginFor<T>,
info: Box<IdentityInfo<T::MaxAdditionalFields>>,
info: Box<T::IdentityInformation>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let extra_fields = info.additional.len() as u32;
#[allow(deprecated)]
let extra_fields = info.additional() as u32;
ensure!(extra_fields <= T::MaxAdditionalFields::get(), Error::<T>::TooManyFields);
let fd = <BalanceOf<T>>::from(extra_fields) * T::FieldDeposit::get();
@@ -364,11 +370,7 @@ pub mod pallet {
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::IdentitySet { who: sender });
Ok(Some(T::WeightInfo::set_identity(
judgements as u32, // R
extra_fields, // X
))
.into())
Ok(Some(T::WeightInfo::set_identity(judgements as u32, extra_fields)).into())
}
/// Set the sub-accounts of the sender.
@@ -380,11 +382,6 @@ pub mod pallet {
/// identity.
///
/// - `subs`: The identity's (new) sub-accounts.
///
/// ## Complexity
/// - `O(P + S)`
/// - where `P` old-subs-count (hard- and deposit-bounded).
/// - where `S` subs-count (hard- and deposit-bounded).
// 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
@@ -392,8 +389,8 @@ pub mod pallet {
// 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.
#[pallet::weight(T::WeightInfo::set_subs_old(T::MaxSubAccounts::get())
.saturating_add(T::WeightInfo::set_subs_new(subs.len() as u32))
)]
pub fn set_subs(
origin: OriginFor<T>,
@@ -453,17 +450,11 @@ pub mod pallet {
/// identity.
///
/// Emits `IdentityCleared` if successful.
///
/// ## Complexity
/// - `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).
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::clear_identity(
T::MaxRegistrars::get(), // R
T::MaxSubAccounts::get(), // S
T::MaxAdditionalFields::get(), // X
T::MaxRegistrars::get(),
T::MaxSubAccounts::get(),
T::MaxAdditionalFields::get(),
))]
pub fn clear_identity(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
@@ -480,10 +471,11 @@ pub mod pallet {
Self::deposit_event(Event::IdentityCleared { who: sender, deposit });
#[allow(deprecated)]
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
id.judgements.len() as u32,
sub_ids.len() as u32,
id.info.additional() as u32,
))
.into())
}
@@ -504,15 +496,10 @@ pub mod pallet {
/// ```
///
/// Emits `JudgementRequested` if successful.
///
/// ## Complexity
/// - `O(R + X)`.
/// - where `R` registrar-count (governance-bounded).
/// - where `X` additional-field-count (deposit-bounded and code-bounded).
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::request_judgement(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
T::MaxRegistrars::get(),
T::MaxAdditionalFields::get(),
))]
pub fn request_judgement(
origin: OriginFor<T>,
@@ -543,7 +530,8 @@ pub mod pallet {
T::Currency::reserve(&sender, registrar.fee)?;
let judgements = id.judgements.len();
let extra_fields = id.info.additional.len();
#[allow(deprecated)]
let extra_fields = id.info.additional();
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::JudgementRequested {
@@ -565,15 +553,10 @@ pub mod pallet {
/// - `reg_index`: The index of the registrar whose judgement is no longer requested.
///
/// Emits `JudgementUnrequested` if successful.
///
/// ## Complexity
/// - `O(R + X)`.
/// - where `R` registrar-count (governance-bounded).
/// - where `X` additional-field-count (deposit-bounded and code-bounded).
#[pallet::call_index(5)]
#[pallet::weight(T::WeightInfo::cancel_request(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
T::MaxRegistrars::get(),
T::MaxAdditionalFields::get(),
))]
pub fn cancel_request(
origin: OriginFor<T>,
@@ -595,7 +578,8 @@ pub mod pallet {
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();
#[allow(deprecated)]
let extra_fields = id.info.additional();
<IdentityOf<T>>::insert(&sender, id);
Self::deposit_event(Event::JudgementUnrequested {
@@ -613,12 +597,8 @@ pub mod pallet {
///
/// - `index`: the index of the registrar whose fee is to be set.
/// - `fee`: the new fee.
///
/// ## Complexity
/// - `O(R)`.
/// - where `R` registrar-count (governance-bounded).
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R
#[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))]
pub fn set_fee(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
@@ -640,7 +620,7 @@ pub mod pallet {
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_fee(registrars as u32)).into()) // R
Ok(Some(T::WeightInfo::set_fee(registrars as u32)).into())
}
/// Change the account associated with a registrar.
@@ -650,12 +630,8 @@ pub mod pallet {
///
/// - `index`: the index of the registrar whose fee is to be set.
/// - `new`: the new account ID.
///
/// ## Complexity
/// - `O(R)`.
/// - where `R` registrar-count (governance-bounded).
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R
#[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))]
pub fn set_account_id(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
@@ -678,7 +654,7 @@ pub mod pallet {
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_account_id(registrars as u32)).into()) // R
Ok(Some(T::WeightInfo::set_account_id(registrars as u32)).into())
}
/// Set the field information for a registrar.
@@ -688,16 +664,14 @@ pub mod pallet {
///
/// - `index`: the index of the registrar whose fee is to be set.
/// - `fields`: the fields that the registrar concerns themselves with.
///
/// ## Complexity
/// - `O(R)`.
/// - where `R` registrar-count (governance-bounded).
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R
#[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))]
pub fn set_fields(
origin: OriginFor<T>,
#[pallet::compact] index: RegistrarIndex,
fields: IdentityFields,
fields: IdentityFields<
<T::IdentityInformation as IdentityInformationProvider>::IdentityField,
>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
@@ -715,10 +689,7 @@ pub mod pallet {
.ok_or_else(|| DispatchError::from(Error::<T>::InvalidIndex))?;
Ok(rs.len())
})?;
Ok(Some(T::WeightInfo::set_fields(
registrars as u32, // R
))
.into())
Ok(Some(T::WeightInfo::set_fields(registrars as u32)).into())
}
/// Provide a judgement for an account's identity.
@@ -730,18 +701,14 @@ pub mod pallet {
/// - `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.
/// - `identity`: The hash of the [`IdentityInformationProvider`] for that the judgement is
/// provided.
///
/// Emits `JudgementGiven` if successful.
///
/// ## Complexity
/// - `O(R + X)`.
/// - where `R` registrar-count (governance-bounded).
/// - where `X` additional-field-count (deposit-bounded and code-bounded).
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::provide_judgement(
T::MaxRegistrars::get(), // R
T::MaxAdditionalFields::get(), // X
T::MaxRegistrars::get(),
T::MaxAdditionalFields::get(),
))]
pub fn provide_judgement(
origin: OriginFor<T>,
@@ -785,7 +752,8 @@ pub mod pallet {
}
let judgements = id.judgements.len();
let extra_fields = id.info.additional.len();
#[allow(deprecated)]
let extra_fields = id.info.additional();
<IdentityOf<T>>::insert(&target, id);
Self::deposit_event(Event::JudgementGiven { target, registrar_index: reg_index });
@@ -805,17 +773,11 @@ pub mod pallet {
/// with a registered identity.
///
/// Emits `IdentityKilled` if successful.
///
/// ## Complexity
/// - `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).
#[pallet::call_index(10)]
#[pallet::weight(T::WeightInfo::kill_identity(
T::MaxRegistrars::get(), // R
T::MaxSubAccounts::get(), // S
T::MaxAdditionalFields::get(), // X
T::MaxRegistrars::get(),
T::MaxSubAccounts::get(),
T::MaxAdditionalFields::get(),
))]
pub fn kill_identity(
origin: OriginFor<T>,
@@ -837,10 +799,11 @@ pub mod pallet {
Self::deposit_event(Event::IdentityKilled { who: target, deposit });
#[allow(deprecated)]
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
id.judgements.len() as u32,
sub_ids.len() as u32,
id.info.additional() as u32,
))
.into())
}
@@ -975,6 +938,6 @@ impl<T: Config> Pallet<T> {
/// Check if the account has corresponding identity information by the identity field.
pub fn has_identity(who: &T::AccountId, fields: u64) -> bool {
IdentityOf::<T>::get(who)
.map_or(false, |registration| (registration.info.fields().0.bits() & fields) == fields)
.map_or(false, |registration| (registration.info.has_identity(fields)))
}
}
+185
View File
@@ -0,0 +1,185 @@
// 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, Encode, MaxEncodedLen};
use enumflags2::{bitflags, BitFlags};
use frame_support::{traits::Get, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound};
use scale_info::{build::Variants, Path, Type, TypeInfo};
use sp_runtime::{BoundedVec, RuntimeDebug};
use sp_std::prelude::*;
use crate::types::{Data, IdentityFields, IdentityInformationProvider, U64BitFlag};
/// 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)]
pub enum IdentityField {
Display,
Legal,
Web,
Riot,
Email,
PgpFingerprint,
Image,
Twitter,
}
impl TypeInfo for IdentityField {
type Identity = Self;
fn type_info() -> scale_info::Type {
Type::builder().path(Path::new("IdentityField", module_path!())).variant(
Variants::new()
.variant("Display", |v| v.index(0))
.variant("Legal", |v| v.index(1))
.variant("Web", |v| v.index(2))
.variant("Riot", |v| v.index(3))
.variant("Email", |v| v.index(4))
.variant("PgpFingerprint", |v| v.index(5))
.variant("Image", |v| v.index(6))
.variant("Twitter", |v| v.index(7)),
)
}
}
impl U64BitFlag for 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,
EqNoBound,
MaxEncodedLen,
PartialEqNoBound,
RuntimeDebugNoBound,
TypeInfo,
)]
#[codec(mel_bound())]
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
#[scale_info(skip_type_params(FieldLimit))]
pub struct IdentityInfo<FieldLimit: Get<u32>> {
/// 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<FieldLimit: Get<u32> + 'static> IdentityInformationProvider for IdentityInfo<FieldLimit> {
type IdentityField = IdentityField;
fn has_identity(&self, fields: u64) -> bool {
self.fields().0.bits() & fields == fields
}
fn additional(&self) -> usize {
self.additional.len()
}
#[cfg(feature = "runtime-benchmarks")]
fn create_identity_info(num_fields: u32) -> Self {
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,
}
}
}
impl<FieldLimit: Get<u32>> IdentityInfo<FieldLimit> {
#[allow(unused)]
pub(crate) fn fields(&self) -> IdentityFields<IdentityField> {
let mut res = <BitFlags<IdentityField>>::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)
}
}
+49 -6
View File
@@ -18,7 +18,10 @@
// Tests for Identity Pallet
use super::*;
use crate as pallet_identity;
use crate::{
self as pallet_identity,
simple::{IdentityField as SimpleIdentityField, IdentityInfo},
};
use codec::{Decode, Encode};
use frame_support::{
@@ -107,6 +110,7 @@ impl pallet_identity::Config for Test {
type SubAccountDeposit = ConstU64<10>;
type MaxSubAccounts = ConstU32<2>;
type MaxAdditionalFields = MaxAdditionalFields;
type IdentityInformation = IdentityInfo<MaxAdditionalFields>;
type MaxRegistrars = MaxRegistrars;
type RegistrarOrigin = EnsureOneOrRoot;
type ForceOrigin = EnsureTwoOrRoot;
@@ -139,6 +143,43 @@ fn twenty() -> IdentityInfo<MaxAdditionalFields> {
}
}
#[test]
fn identity_fields_repr_works() {
// `SimpleIdentityField` sanity checks.
assert_eq!(SimpleIdentityField::Display as u64, 1 << 0);
assert_eq!(SimpleIdentityField::Legal as u64, 1 << 1);
assert_eq!(SimpleIdentityField::Web as u64, 1 << 2);
assert_eq!(SimpleIdentityField::Riot as u64, 1 << 3);
assert_eq!(SimpleIdentityField::Email as u64, 1 << 4);
assert_eq!(SimpleIdentityField::PgpFingerprint as u64, 1 << 5);
assert_eq!(SimpleIdentityField::Image as u64, 1 << 6);
assert_eq!(SimpleIdentityField::Twitter as u64, 1 << 7);
let fields = IdentityFields(
SimpleIdentityField::Legal |
SimpleIdentityField::Web |
SimpleIdentityField::Riot |
SimpleIdentityField::PgpFingerprint |
SimpleIdentityField::Twitter,
);
assert!(!fields.0.contains(SimpleIdentityField::Display));
assert!(fields.0.contains(SimpleIdentityField::Legal));
assert!(fields.0.contains(SimpleIdentityField::Web));
assert!(fields.0.contains(SimpleIdentityField::Riot));
assert!(!fields.0.contains(SimpleIdentityField::Email));
assert!(fields.0.contains(SimpleIdentityField::PgpFingerprint));
assert!(!fields.0.contains(SimpleIdentityField::Image));
assert!(fields.0.contains(SimpleIdentityField::Twitter));
// The `IdentityFields` inner `BitFlags::bits` is used for `Encode`/`Decode`, so we ensure that
// the `u64` representation matches what we expect during encode/decode operations.
assert_eq!(
fields.0.bits(),
0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_10101110
);
}
#[test]
fn editing_subaccounts_should_work() {
new_test_ext().execute_with(|| {
@@ -233,7 +274,7 @@ 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);
let fields = IdentityFields(SimpleIdentityField::Display | SimpleIdentityField::Legal);
assert_ok!(Identity::set_fields(RuntimeOrigin::signed(3), 0, fields));
assert_eq!(
Identity::registrars(),
@@ -608,15 +649,17 @@ fn setting_account_id_should_work() {
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, SimpleIdentityField::Display as u64));
assert!(Identity::has_identity(&10, SimpleIdentityField::Legal as u64));
assert!(Identity::has_identity(
&10,
IdentityField::Display as u64 | IdentityField::Legal as u64
SimpleIdentityField::Display as u64 | SimpleIdentityField::Legal as u64
));
assert!(!Identity::has_identity(
&10,
IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64
SimpleIdentityField::Display as u64 |
SimpleIdentityField::Legal as u64 |
SimpleIdentityField::Web as u64
));
});
}
+78 -142
View File
@@ -17,7 +17,7 @@
use super::*;
use codec::{Decode, Encode, MaxEncodedLen};
use enumflags2::{bitflags, BitFlags};
use enumflags2::{BitFlag, BitFlags, _internal::RawBitFlags};
use frame_support::{
traits::{ConstU32, Get},
BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound,
@@ -29,6 +29,11 @@ use scale_info::{
use sp_runtime::{traits::Zero, RuntimeDebug};
use sp_std::{fmt::Debug, iter::once, ops::Add, prelude::*};
/// An identifier for a single name registrar/identity verification service.
pub type RegistrarIndex = u32;
pub trait U64BitFlag: BitFlag + RawBitFlags<Numeric = u64> {}
/// 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.
///
@@ -180,9 +185,6 @@ impl Default for Data {
}
}
/// 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
@@ -228,143 +230,31 @@ impl<Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + Part
}
}
/// 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<IdentityField>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)]
pub struct IdentityFields(pub BitFlags<IdentityField>);
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, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
self.0.bits().using_encoded(f)
}
}
impl Decode for IdentityFields {
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
let field = u64::decode(input)?;
Ok(Self(<BitFlags<IdentityField>>::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::<IdentityField>()))])
.composite(Fields::unnamed().field(|f| f.ty::<u64>().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<FieldLimit: Get<u32>> {
/// Additional fields of the identity that are not catered for with the struct's explicit
/// fields.
pub additional: BoundedVec<(Data, Data), FieldLimit>,
pub trait IdentityInformationProvider:
Encode + Decode + MaxEncodedLen + Clone + Debug + Eq + PartialEq + TypeInfo
{
/// Type capable of representing all of the fields present in the identity information as bit
/// flags in `u64` format.
type IdentityField: Clone + Debug + Eq + PartialEq + TypeInfo + U64BitFlag;
/// 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,
/// Check if an identity registered information for some given `fields`.
fn has_identity(&self, fields: u64) -> bool;
/// 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<FieldLimit: Get<u32>> IdentityInfo<FieldLimit> {
pub(crate) fn fields(&self) -> IdentityFields {
let mut res = <BitFlags<IdentityField>>::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)
/// Interface for providing the number of additional fields this identity information provider
/// holds, used to charge for additional storage and weight. This interface is present for
/// backwards compatibility reasons only and will be removed as soon as the reference identity
/// provider removes additional fields.
#[deprecated]
fn additional(&self) -> usize {
0
}
#[cfg(feature = "runtime-benchmarks")]
fn create_identity_info(num_fields: u32) -> Self;
}
/// Information concerning the identity of the controller of an account.
/// Information on an identity along with judgements from registrars.
///
/// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a
/// backwards compatible way through a specialized `Decode` impl.
@@ -376,7 +266,7 @@ impl<FieldLimit: Get<u32>> IdentityInfo<FieldLimit> {
pub struct Registration<
Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq,
MaxJudgements: Get<u32>,
MaxAdditionalFields: Get<u32>,
IdentityInfo: IdentityInformationProvider,
> {
/// Judgements from the registrars on this identity. Stored ordered by `RegistrarIndex`. There
/// may be only a single judgement from each registrar.
@@ -386,14 +276,14 @@ pub struct Registration<
pub deposit: Balance,
/// Information on the identity.
pub info: IdentityInfo<MaxAdditionalFields>,
pub info: IdentityInfo,
}
impl<
Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add,
MaxJudgements: Get<u32>,
MaxAdditionalFields: Get<u32>,
> Registration<Balance, MaxJudgements, MaxAdditionalFields>
IdentityInfo: IdentityInformationProvider,
> Registration<Balance, MaxJudgements, IdentityInfo>
{
pub(crate) fn total_deposit(&self) -> Balance {
self.deposit +
@@ -407,8 +297,8 @@ impl<
impl<
Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq,
MaxJudgements: Get<u32>,
MaxAdditionalFields: Get<u32>,
> Decode for Registration<Balance, MaxJudgements, MaxAdditionalFields>
IdentityInfo: IdentityInformationProvider,
> Decode for Registration<Balance, MaxJudgements, IdentityInfo>
{
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?;
@@ -421,6 +311,7 @@ impl<
pub struct RegistrarInfo<
Balance: Encode + Decode + Clone + Debug + Eq + PartialEq,
AccountId: Encode + Decode + Clone + Debug + Eq + PartialEq,
IdField: Clone + Debug + Eq + PartialEq + TypeInfo + U64BitFlag,
> {
/// The account of the registrar.
pub account: AccountId,
@@ -430,7 +321,52 @@ pub struct RegistrarInfo<
/// Relevant fields for this registrar. Registrar judgements are limited to attestations on
/// these fields.
pub fields: IdentityFields,
pub fields: IdentityFields<IdField>,
}
/// Wrapper type for `BitFlags<IdentityField>` that implements `Codec`.
#[derive(Clone, Copy, PartialEq, RuntimeDebug)]
pub struct IdentityFields<IdField: BitFlag>(pub BitFlags<IdField>);
impl<IdField: U64BitFlag> Default for IdentityFields<IdField> {
fn default() -> Self {
Self(Default::default())
}
}
impl<IdField: U64BitFlag> MaxEncodedLen for IdentityFields<IdField>
where
IdentityFields<IdField>: Encode,
{
fn max_encoded_len() -> usize {
u64::max_encoded_len()
}
}
impl<IdField: U64BitFlag + PartialEq> Eq for IdentityFields<IdField> {}
impl<IdField: U64BitFlag> Encode for IdentityFields<IdField> {
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
let bits: u64 = self.0.bits();
bits.using_encoded(f)
}
}
impl<IdField: U64BitFlag> Decode for IdentityFields<IdField> {
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
let field = u64::decode(input)?;
Ok(Self(<BitFlags<IdField>>::from_bits(field).map_err(|_| "invalid value")?))
}
}
impl<IdField: Clone + Debug + Eq + PartialEq + TypeInfo + U64BitFlag> TypeInfo
for IdentityFields<IdField>
{
type Identity = Self;
fn type_info() -> Type {
Type::builder()
.path(Path::new("BitFlags", module_path!()))
.type_params(vec![TypeParameter::new("T", Some(meta_type::<IdField>()))])
.composite(Fields::unnamed().field(|f| f.ty::<u64>().type_name("IdentityField")))
}
}
#[cfg(test)]