mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
Unique Usernames in Identity Pallet (#2651)
This PR allows _username authorities_ to issue unique usernames that correspond with an account. It also provides two-way lookup, that is from `AccountId` to a single, "primary" `Username` (alongside `Registration`) and multiple unique `Username`s to an `AccountId`. Key features: - Username Authorities added (and removed) via privileged origin. - Authorities have a `suffix` and an `allocation`. They can grant up to `allocation` usernames. Their `suffix` will be appended to the usernames that they issue. A suffix may be up to 7 characters long. - Users can ask an authority to grant them a username. This will take the form `myusername.suffix`. The entire name (including suffix) must be less than or equal to 32 alphanumeric characters. - Users can approve a username for themselves in one of two ways (that is, authorities cannot grant them arbitrarily): - Pre-sign the entire username (including suffix) with a secret key that corresponds to their `AccountId` (for keyed accounts, obviously); or - Accept the username after it has been granted by an authority (it will be queued until accepted) (for non-keyed accounts like pure proxies or multisigs). - The system does not require any funds or deposits. Users without an identity will be given a default one (presumably all fields set to `None`). If they update this info, they will need to place the normal storage deposit. - If a user does not have any username, their first one will be set as `Primary`, and their `AccountId` will map to that one. If they get subsequent usernames, they can choose which one to be their primary via `set_primary_username`. - There are some state cleanup functions to remove expired usernames that have not been accepted and dangling usernames whose owners have called `clear_identity`. TODO: - [x] Add migration to runtimes - [x] Probably do off-chain migration into People Chain genesis - [x] Address a few TODO questions in code (please review) --------- Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Dónal Murray <donal.murray@parity.io>
This commit is contained in:
Generated
+2
@@ -10047,11 +10047,13 @@ dependencies = [
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"log",
|
||||
"pallet-balances",
|
||||
"parity-scale-codec",
|
||||
"scale-info",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-keystore",
|
||||
"sp-runtime",
|
||||
"sp-std 8.0.0",
|
||||
]
|
||||
|
||||
@@ -22,9 +22,12 @@ use frame_support::{
|
||||
RuntimeDebugNoBound,
|
||||
};
|
||||
use pallet_identity::{Data, IdentityInformationProvider};
|
||||
use parachains_common::impls::ToParentTreasury;
|
||||
use parachains_common::{impls::ToParentTreasury, DAYS};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, Verify},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
parameter_types! {
|
||||
@@ -51,6 +54,12 @@ impl pallet_identity::Config for Runtime {
|
||||
type Slashed = ToParentTreasury<RelayTreasuryAccount, LocationToAccountId, Runtime>;
|
||||
type ForceOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RegistrarOrigin = EnsureRoot<Self::AccountId>;
|
||||
type OffchainSignature = Signature;
|
||||
type SigningPublicKey = <Signature as Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
|
||||
}
|
||||
|
||||
@@ -84,7 +93,6 @@ pub enum IdentityField {
|
||||
TypeInfo,
|
||||
)]
|
||||
#[codec(mel_bound())]
|
||||
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
|
||||
pub struct IdentityInfo {
|
||||
/// A reasonable display name for the controller of the account. This should be whatever the
|
||||
/// account is typically known as and should not be confusable with other entities, given
|
||||
@@ -202,3 +210,21 @@ impl IdentityInfo {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Default` identity. This is given to users who get a username but have not set an identity.
|
||||
impl Default for IdentityInfo {
|
||||
fn default() -> Self {
|
||||
IdentityInfo {
|
||||
display: Data::None,
|
||||
legal: Data::None,
|
||||
web: Data::None,
|
||||
matrix: Data::None,
|
||||
email: Data::None,
|
||||
pgp_fingerprint: None,
|
||||
image: Data::None,
|
||||
twitter: Data::None,
|
||||
github: Data::None,
|
||||
discord: Data::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,4 +312,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(2))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ use frame_support::{
|
||||
RuntimeDebugNoBound,
|
||||
};
|
||||
use pallet_identity::{Data, IdentityInformationProvider};
|
||||
use parachains_common::impls::ToParentTreasury;
|
||||
use parachains_common::{impls::ToParentTreasury, DAYS};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_runtime::{traits::AccountIdConversion, RuntimeDebug};
|
||||
use sp_runtime::{
|
||||
traits::{AccountIdConversion, Verify},
|
||||
RuntimeDebug,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
parameter_types! {
|
||||
@@ -51,6 +54,12 @@ impl pallet_identity::Config for Runtime {
|
||||
type Slashed = ToParentTreasury<RelayTreasuryAccount, LocationToAccountId, Runtime>;
|
||||
type ForceOrigin = EnsureRoot<Self::AccountId>;
|
||||
type RegistrarOrigin = EnsureRoot<Self::AccountId>;
|
||||
type OffchainSignature = Signature;
|
||||
type SigningPublicKey = <Signature as Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
|
||||
}
|
||||
|
||||
@@ -84,7 +93,6 @@ pub enum IdentityField {
|
||||
TypeInfo,
|
||||
)]
|
||||
#[codec(mel_bound())]
|
||||
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
|
||||
pub struct IdentityInfo {
|
||||
/// 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
|
||||
@@ -202,3 +210,21 @@ impl IdentityInfo {
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// A `Default` identity. This is given to users who get a username but have not set an identity.
|
||||
impl Default for IdentityInfo {
|
||||
fn default() -> Self {
|
||||
IdentityInfo {
|
||||
display: Data::None,
|
||||
legal: Data::None,
|
||||
web: Data::None,
|
||||
matrix: Data::None,
|
||||
email: Data::None,
|
||||
pgp_fingerprint: None,
|
||||
image: Data::None,
|
||||
twitter: Data::None,
|
||||
github: Data::None,
|
||||
discord: Data::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,4 +312,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(2))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ use sp_io::TestExternalities;
|
||||
use sp_keyring::Sr25519Keyring;
|
||||
use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
|
||||
use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentityLookup, One},
|
||||
traits::{BlakeTwo256, IdentityLookup, One, Verify},
|
||||
transaction_validity::TransactionPriority,
|
||||
AccountId32, BuildStorage,
|
||||
AccountId32, BuildStorage, MultiSignature,
|
||||
};
|
||||
use sp_std::sync::Arc;
|
||||
|
||||
@@ -293,6 +293,12 @@ impl pallet_identity::Config for Test {
|
||||
type MaxRegistrars = ConstU32<20>;
|
||||
type RegistrarOrigin = EnsureRoot<AccountId>;
|
||||
type ForceOrigin = EnsureRoot<AccountId>;
|
||||
type OffchainSignature = MultiSignature;
|
||||
type SigningPublicKey = <MultiSignature as Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<100>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
|
||||
@@ -668,6 +668,12 @@ impl pallet_identity::Config for Runtime {
|
||||
type Slashed = Treasury;
|
||||
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
|
||||
type RegistrarOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
|
||||
type OffchainSignature = Signature;
|
||||
type SigningPublicKey = <Signature as Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
|
||||
}
|
||||
|
||||
@@ -1620,6 +1626,9 @@ pub mod migrations {
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a limit in the Relay Chain.
|
||||
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
|
||||
|
||||
/// Unreleased migrations. Add new ones here:
|
||||
pub type Unreleased = (
|
||||
pallet_society::migrations::MigrateToV2<Runtime, (), ()>,
|
||||
@@ -1655,6 +1664,9 @@ pub mod migrations {
|
||||
|
||||
// Remove `im-online` pallet on-chain storage
|
||||
frame_support::migrations::RemovePallet<ImOnlinePalletName, <Runtime as frame_system::Config>::DbWeight>,
|
||||
|
||||
// Migrate Identity pallet for Usernames
|
||||
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
|
||||
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
|
||||
// This needs to come after the `parachains_configuration` above as we are reading the configuration.
|
||||
coretime::migration::MigrateToCoretime<Runtime, crate::xcm_config::XcmRouter, GetLegacyLeaseImpl>,
|
||||
|
||||
@@ -334,4 +334,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(2))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -878,6 +878,12 @@ impl pallet_identity::Config for Runtime {
|
||||
type MaxRegistrars = MaxRegistrars;
|
||||
type ForceOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
|
||||
type RegistrarOrigin = EitherOf<EnsureRoot<Self::AccountId>, GeneralAdmin>;
|
||||
type OffchainSignature = Signature;
|
||||
type SigningPublicKey = <Signature as Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = weights::pallet_identity::WeightInfo<Runtime>;
|
||||
}
|
||||
|
||||
@@ -1626,6 +1632,9 @@ pub mod migrations {
|
||||
}
|
||||
}
|
||||
|
||||
// We don't have a limit in the Relay Chain.
|
||||
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
|
||||
|
||||
/// Unreleased migrations. Add new ones here:
|
||||
pub type Unreleased = (
|
||||
parachains_configuration::migration::v7::MigrateToV7<Runtime>,
|
||||
@@ -1644,6 +1653,8 @@ pub mod migrations {
|
||||
ImOnlinePalletName,
|
||||
<Runtime as frame_system::Config>::DbWeight,
|
||||
>,
|
||||
// Migrate Identity pallet for Usernames
|
||||
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
|
||||
parachains_configuration::migration::v11::MigrateToV11<Runtime>,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,4 +338,98 @@ impl<T: frame_system::Config> pallet_identity::WeightInfo for WeightInfo<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(2))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
|
||||
# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
|
||||
|
||||
title: Unique Usernames for Identity
|
||||
|
||||
doc:
|
||||
- audience: Runtime User
|
||||
description: |
|
||||
Adds the ability to add unique usernames for an account with reverse lookup (as in `AccountId`
|
||||
to `Username` and `Username` to `AccountId`).
|
||||
|
||||
crates: [ ]
|
||||
@@ -64,7 +64,7 @@ impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
|
||||
fn has_good_judgement(who: &AccountId) -> bool {
|
||||
use pallet_identity::Judgement;
|
||||
crate::Identity::identity(who)
|
||||
.map(|registration| registration.judgements)
|
||||
.map(|(registration, _)| registration.judgements)
|
||||
.map_or(false, |judgements| {
|
||||
judgements
|
||||
.iter()
|
||||
|
||||
@@ -1500,6 +1500,12 @@ impl pallet_identity::Config for Runtime {
|
||||
type Slashed = Treasury;
|
||||
type ForceOrigin = EnsureRootOrHalfCouncil;
|
||||
type RegistrarOrigin = EnsureRootOrHalfCouncil;
|
||||
type OffchainSignature = Signature;
|
||||
type SigningPublicKey = <Signature as traits::Verify>::Signer;
|
||||
type UsernameAuthorityOrigin = EnsureRoot<Self::AccountId>;
|
||||
type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = pallet_identity::weights::SubstrateWeight<Runtime>;
|
||||
}
|
||||
|
||||
@@ -2208,6 +2214,9 @@ pub type Executive = frame_executive::Executive<
|
||||
Migrations,
|
||||
>;
|
||||
|
||||
// We don't have a limit in the Relay Chain.
|
||||
const IDENTITY_MIGRATION_KEY_LIMIT: u64 = u64::MAX;
|
||||
|
||||
// All migrations executed on runtime upgrade as a nested tuple of types implementing
|
||||
// `OnRuntimeUpgrade`. Note: These are examples and do not need to be run directly
|
||||
// after the genesis block.
|
||||
@@ -2215,6 +2224,7 @@ type Migrations = (
|
||||
pallet_nomination_pools::migration::versioned::V6ToV7<Runtime>,
|
||||
pallet_alliance::migration::Migration<Runtime>,
|
||||
pallet_contracts::Migration<Runtime>,
|
||||
pallet_identity::migration::versioned::V0ToV1<Runtime, IDENTITY_MIGRATION_KEY_LIMIT>,
|
||||
);
|
||||
|
||||
type EventRecord = frame_system::EventRecord<
|
||||
|
||||
@@ -19,7 +19,10 @@
|
||||
|
||||
pub use sp_core::H256;
|
||||
use sp_runtime::traits::Hash;
|
||||
pub use sp_runtime::{traits::BlakeTwo256, BuildStorage};
|
||||
pub use sp_runtime::{
|
||||
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Lazy, Verify},
|
||||
BuildStorage, MultiSignature,
|
||||
};
|
||||
use sp_std::convert::{TryFrom, TryInto};
|
||||
|
||||
pub use frame_support::{
|
||||
@@ -101,6 +104,7 @@ parameter_types! {
|
||||
pub const MaxSubAccounts: u32 = 2;
|
||||
pub const MaxAdditionalFields: u32 = 2;
|
||||
pub const MaxRegistrars: u32 = 20;
|
||||
pub const PendingUsernameExpiration: u64 = 100;
|
||||
}
|
||||
ord_parameter_types! {
|
||||
pub const One: u64 = 1;
|
||||
@@ -124,9 +128,34 @@ impl pallet_identity::Config for Test {
|
||||
type Slashed = ();
|
||||
type RegistrarOrigin = EnsureOneOrRoot;
|
||||
type ForceOrigin = EnsureTwoOrRoot;
|
||||
type OffchainSignature = AccountU64;
|
||||
type SigningPublicKey = AccountU64;
|
||||
type UsernameAuthorityOrigin = EnsureOneOrRoot;
|
||||
type PendingUsernameExpiration = PendingUsernameExpiration;
|
||||
type MaxSuffixLength = ConstU32<7>;
|
||||
type MaxUsernameLength = ConstU32<32>;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Encode, Decode, PartialEq, Eq, TypeInfo)]
|
||||
pub struct AccountU64(u64);
|
||||
impl IdentifyAccount for AccountU64 {
|
||||
type AccountId = u64;
|
||||
fn into_account(self) -> u64 {
|
||||
0u64
|
||||
}
|
||||
}
|
||||
impl Verify for AccountU64 {
|
||||
type Signer = AccountU64;
|
||||
fn verify<L: Lazy<[u8]>>(
|
||||
&self,
|
||||
_msg: L,
|
||||
_signer: &<Self::Signer as IdentifyAccount>::AccountId,
|
||||
) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AllianceIdentityVerifier;
|
||||
impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
|
||||
fn has_required_identities(who: &AccountId) -> bool {
|
||||
@@ -135,7 +164,7 @@ impl IdentityVerifier<AccountId> for AllianceIdentityVerifier {
|
||||
|
||||
fn has_good_judgement(who: &AccountId) -> bool {
|
||||
if let Some(judgements) =
|
||||
Identity::identity(who).map(|registration| registration.judgements)
|
||||
Identity::identity(who).map(|(registration, _)| registration.judgements)
|
||||
{
|
||||
judgements
|
||||
.iter()
|
||||
|
||||
@@ -18,6 +18,7 @@ targets = ["x86_64-unknown-linux-gnu"]
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] }
|
||||
enumflags2 = { version = "0.7.7" }
|
||||
log = { version = "0.4.17", default-features = false }
|
||||
scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
|
||||
frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true }
|
||||
frame-support = { path = "../support", default-features = false }
|
||||
@@ -29,6 +30,7 @@ sp-std = { path = "../../primitives/std", default-features = false }
|
||||
[dev-dependencies]
|
||||
pallet-balances = { path = "../balances" }
|
||||
sp-core = { path = "../../primitives/core" }
|
||||
sp-keystore = { path = "../../primitives/keystore" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
@@ -39,10 +41,12 @@ std = [
|
||||
"frame-benchmarking?/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"log/std",
|
||||
"pallet-balances/std",
|
||||
"scale-info/std",
|
||||
"sp-core/std",
|
||||
"sp-io/std",
|
||||
"sp-keystore/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
]
|
||||
|
||||
@@ -22,22 +22,43 @@
|
||||
use super::*;
|
||||
|
||||
use crate::Pallet as Identity;
|
||||
use codec::Encode;
|
||||
use frame_benchmarking::{
|
||||
account, impl_benchmark_test_suite, v2::*, whitelisted_caller, BenchmarkError,
|
||||
};
|
||||
use frame_support::{
|
||||
ensure,
|
||||
traits::{EnsureOrigin, Get},
|
||||
assert_ok, ensure,
|
||||
traits::{EnsureOrigin, Get, OnFinalize, OnInitialize},
|
||||
};
|
||||
use frame_system::RawOrigin;
|
||||
use sp_runtime::traits::Bounded;
|
||||
use sp_io::crypto::{sr25519_generate, sr25519_sign};
|
||||
use sp_runtime::{
|
||||
traits::{Bounded, IdentifyAccount, One},
|
||||
MultiSignature, MultiSigner,
|
||||
};
|
||||
|
||||
const SEED: u32 = 0;
|
||||
|
||||
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn assert_last_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
|
||||
frame_system::Pallet::<T>::assert_last_event(generic_event.into());
|
||||
}
|
||||
|
||||
fn run_to_block<T: Config>(n: frame_system::pallet_prelude::BlockNumberFor<T>) {
|
||||
while frame_system::Pallet::<T>::block_number() < n {
|
||||
crate::Pallet::<T>::on_finalize(frame_system::Pallet::<T>::block_number());
|
||||
frame_system::Pallet::<T>::on_finalize(frame_system::Pallet::<T>::block_number());
|
||||
frame_system::Pallet::<T>::set_block_number(
|
||||
frame_system::Pallet::<T>::block_number() + One::one(),
|
||||
);
|
||||
frame_system::Pallet::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
|
||||
crate::Pallet::<T>::on_initialize(frame_system::Pallet::<T>::block_number());
|
||||
}
|
||||
}
|
||||
|
||||
// Adds `r` registrars to the Identity Pallet. These registrars will have set fees and fields.
|
||||
fn add_registrars<T: Config>(r: u32) -> Result<(), &'static str> {
|
||||
for i in 0..r {
|
||||
@@ -95,7 +116,28 @@ fn add_sub_accounts<T: Config>(
|
||||
Ok(subs)
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
fn bench_suffix() -> Vec<u8> {
|
||||
b"bench".to_vec()
|
||||
}
|
||||
|
||||
fn bench_username() -> Vec<u8> {
|
||||
// len = 24
|
||||
b"veryfastbenchmarkmachine".to_vec()
|
||||
}
|
||||
|
||||
fn bounded_username<T: Config>(username: Vec<u8>, suffix: Vec<u8>) -> Username<T> {
|
||||
let mut full_username = Vec::with_capacity(username.len() + suffix.len() + 1);
|
||||
full_username.extend(username);
|
||||
full_username.extend(b".");
|
||||
full_username.extend(suffix);
|
||||
Username::<T>::try_from(full_username).expect("test usernames should fit within bounds")
|
||||
}
|
||||
|
||||
#[benchmarks(
|
||||
where
|
||||
<T as frame_system::Config>::AccountId: From<sp_runtime::AccountId32>,
|
||||
T::OffchainSignature: From<MultiSignature>,
|
||||
)]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
@@ -523,5 +565,166 @@ mod benchmarks {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn add_username_authority() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
|
||||
|
||||
let authority: T::AccountId = account("authority", 0, SEED);
|
||||
let authority_lookup = T::Lookup::unlookup(authority.clone());
|
||||
let suffix = bench_suffix();
|
||||
let allocation = 10;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, authority_lookup, suffix, allocation);
|
||||
|
||||
assert_last_event::<T>(Event::<T>::AuthorityAdded { authority }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_username_authority() -> Result<(), BenchmarkError> {
|
||||
let origin =
|
||||
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
|
||||
|
||||
let authority: T::AccountId = account("authority", 0, SEED);
|
||||
let authority_lookup = T::Lookup::unlookup(authority.clone());
|
||||
let suffix = bench_suffix();
|
||||
let allocation = 10;
|
||||
|
||||
assert_ok!(Identity::<T>::add_username_authority(
|
||||
origin.clone(),
|
||||
authority_lookup.clone(),
|
||||
suffix,
|
||||
allocation
|
||||
));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(origin as T::RuntimeOrigin, authority_lookup);
|
||||
|
||||
assert_last_event::<T>(Event::<T>::AuthorityRemoved { authority }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_username_for() -> Result<(), BenchmarkError> {
|
||||
// Set up a username authority.
|
||||
let auth_origin =
|
||||
T::UsernameAuthorityOrigin::try_successful_origin().expect("can generate origin");
|
||||
let authority: T::AccountId = account("authority", 0, SEED);
|
||||
let authority_lookup = T::Lookup::unlookup(authority.clone());
|
||||
let suffix = bench_suffix();
|
||||
let allocation = 10;
|
||||
|
||||
Identity::<T>::add_username_authority(
|
||||
auth_origin,
|
||||
authority_lookup,
|
||||
suffix.clone(),
|
||||
allocation,
|
||||
)?;
|
||||
|
||||
let username = bench_username();
|
||||
let bounded_username = bounded_username::<T>(username.clone(), suffix.clone());
|
||||
let encoded_username = Encode::encode(&bounded_username.to_vec());
|
||||
|
||||
let public = sr25519_generate(0.into(), None);
|
||||
let who_account: T::AccountId = MultiSigner::Sr25519(public).into_account().into();
|
||||
let who_lookup = T::Lookup::unlookup(who_account.clone());
|
||||
|
||||
let signature =
|
||||
MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap());
|
||||
|
||||
// Verify signature here to avoid surprise errors at runtime
|
||||
assert!(signature.verify(&encoded_username[..], &public.into()));
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(authority.clone()), who_lookup, username, Some(signature.into()));
|
||||
|
||||
assert_has_event::<T>(
|
||||
Event::<T>::UsernameSet {
|
||||
who: who_account.clone(),
|
||||
username: bounded_username.clone(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert_has_event::<T>(
|
||||
Event::<T>::PrimaryUsernameSet { who: who_account, username: bounded_username }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn accept_username() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let username = bounded_username::<T>(bench_username(), bench_suffix());
|
||||
|
||||
Identity::<T>::queue_acceptance(&caller, username.clone());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), username.clone());
|
||||
|
||||
assert_last_event::<T>(Event::<T>::UsernameSet { who: caller, username }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_expired_approval() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let username = bounded_username::<T>(bench_username(), bench_suffix());
|
||||
Identity::<T>::queue_acceptance(&caller, username.clone());
|
||||
|
||||
let expected_exiration =
|
||||
frame_system::Pallet::<T>::block_number() + T::PendingUsernameExpiration::get();
|
||||
|
||||
run_to_block::<T>(expected_exiration + One::one());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), username);
|
||||
|
||||
assert_last_event::<T>(Event::<T>::PreapprovalExpired { whose: caller }.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn set_primary_username() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let first_username = bounded_username::<T>(bench_username(), bench_suffix());
|
||||
let second_username = bounded_username::<T>(b"slowbenchmark".to_vec(), bench_suffix());
|
||||
|
||||
// First one will be set as primary. Second will not be.
|
||||
Identity::<T>::insert_username(&caller, first_username);
|
||||
Identity::<T>::insert_username(&caller, second_username.clone());
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), second_username.clone());
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::<T>::PrimaryUsernameSet { who: caller, username: second_username }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn remove_dangling_username() -> Result<(), BenchmarkError> {
|
||||
let caller: T::AccountId = whitelisted_caller();
|
||||
let first_username = bounded_username::<T>(bench_username(), bench_suffix());
|
||||
let second_username = bounded_username::<T>(b"slowbenchmark".to_vec(), bench_suffix());
|
||||
|
||||
// First one will be set as primary. Second will not be.
|
||||
Identity::<T>::insert_username(&caller, first_username);
|
||||
Identity::<T>::insert_username(&caller, second_username.clone());
|
||||
|
||||
// User calls `clear_identity`, leaving their second username as "dangling"
|
||||
Identity::<T>::clear_identity(RawOrigin::Signed(caller.clone()).into())?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::Signed(caller.clone()), second_username.clone());
|
||||
|
||||
assert_last_event::<T>(
|
||||
Event::<T>::DanglingUsernameRemoved { who: caller, username: second_username }.into(),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,6 @@ impl TypeInfo for IdentityField {
|
||||
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
|
||||
@@ -155,6 +154,22 @@ impl<FieldLimit: Get<u32> + 'static> IdentityInformationProvider for IdentityInf
|
||||
}
|
||||
}
|
||||
|
||||
impl<FieldLimit: Get<u32>> Default for IdentityInfo<FieldLimit> {
|
||||
fn default() -> Self {
|
||||
IdentityInfo {
|
||||
additional: BoundedVec::default(),
|
||||
display: Data::None,
|
||||
legal: Data::None,
|
||||
web: Data::None,
|
||||
riot: Data::None,
|
||||
email: Data::None,
|
||||
pgp_fingerprint: None,
|
||||
image: Data::None,
|
||||
twitter: Data::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<FieldLimit: Get<u32>> IdentityInfo<FieldLimit> {
|
||||
pub(crate) fn fields(&self) -> BitFlags<IdentityField> {
|
||||
let mut res = <BitFlags<IdentityField>>::empty();
|
||||
|
||||
@@ -40,32 +40,53 @@
|
||||
//! The number of registrars should be limited, and the deposit made sufficiently large, to ensure
|
||||
//! no state-bloat attack is viable.
|
||||
//!
|
||||
//! ### Usernames
|
||||
//!
|
||||
//! The pallet provides functionality for username authorities to issue usernames. When an account
|
||||
//! receives a username, they get a default instance of `IdentityInfo`. Usernames also serve as a
|
||||
//! reverse lookup from username to account.
|
||||
//!
|
||||
//! Username authorities are given an allocation by governance to prevent state bloat. Usernames
|
||||
//! impose no cost or deposit on the user.
|
||||
//!
|
||||
//! Users can have multiple usernames that map to the same `AccountId`, however one `AccountId` can
|
||||
//! only map to a single username, known as the _primary_.
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Dispatchable Functions
|
||||
//!
|
||||
//! #### For general users
|
||||
//! #### For General Users
|
||||
//! * `set_identity` - Set the associated identity of an account; a small deposit is reserved if not
|
||||
//! already taken.
|
||||
//! * `clear_identity` - Remove an account's associated identity; the deposit is returned.
|
||||
//! * `request_judgement` - Request a judgement from a registrar, paying a fee.
|
||||
//! * `cancel_request` - Cancel the previous request for a judgement.
|
||||
//! * `accept_username` - Accept a username issued by a username authority.
|
||||
//! * `remove_expired_approval` - Remove a username that was issued but never accepted.
|
||||
//! * `set_primary_username` - Set a given username as an account's primary.
|
||||
//! * `remove_dangling_username` - Remove a username that maps to an account without an identity.
|
||||
//!
|
||||
//! #### For general users with sub-identities
|
||||
//! #### For General Users with Sub-Identities
|
||||
//! * `set_subs` - Set the sub-accounts of an identity.
|
||||
//! * `add_sub` - Add a sub-identity to an identity.
|
||||
//! * `remove_sub` - Remove a sub-identity of an identity.
|
||||
//! * `rename_sub` - Rename a sub-identity of an identity.
|
||||
//! * `quit_sub` - Remove a sub-identity of an identity (called by the sub-identity).
|
||||
//!
|
||||
//! #### For registrars
|
||||
//! #### For Registrars
|
||||
//! * `set_fee` - Set the fee required to be paid for a judgement to be given by the registrar.
|
||||
//! * `set_fields` - Set the fields that a registrar cares about in their judgements.
|
||||
//! * `provide_judgement` - Provide a judgement to an identity.
|
||||
//!
|
||||
//! #### For super-users
|
||||
//! #### For Username Authorities
|
||||
//! * `set_username_for` - Set a username for a given account. The account must approve it.
|
||||
//!
|
||||
//! #### For Superusers
|
||||
//! * `add_registrar` - Add a new registrar to the system.
|
||||
//! * `kill_identity` - Forcibly remove the associated identity; the deposit is lost.
|
||||
//! * `add_username_authority` - Add an account with the ability to issue usernames.
|
||||
//! * `remove_username_authority` - Remove an account with the ability to issue usernames.
|
||||
//!
|
||||
//! [`Call`]: ./enum.Call.html
|
||||
//! [`Config`]: ./trait.Config.html
|
||||
@@ -74,25 +95,29 @@
|
||||
|
||||
mod benchmarking;
|
||||
pub mod legacy;
|
||||
pub mod migration;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod types;
|
||||
pub mod weights;
|
||||
|
||||
use crate::types::{AuthorityPropertiesOf, Suffix, Username};
|
||||
use codec::Encode;
|
||||
use frame_support::{
|
||||
ensure,
|
||||
pallet_prelude::{DispatchError, DispatchResult},
|
||||
traits::{BalanceStatus, Currency, Get, OnUnbalanced, ReservableCurrency},
|
||||
traits::{BalanceStatus, Currency, Get, OnUnbalanced, ReservableCurrency, StorageVersion},
|
||||
BoundedVec,
|
||||
};
|
||||
use sp_runtime::traits::{AppendZerosInput, Hash, Saturating, StaticLookup, Zero};
|
||||
use sp_std::prelude::*;
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub use pallet::*;
|
||||
use sp_runtime::traits::{
|
||||
AppendZerosInput, Hash, IdentifyAccount, Saturating, StaticLookup, Verify, Zero,
|
||||
};
|
||||
use sp_std::prelude::*;
|
||||
pub use types::{
|
||||
Data, IdentityInformationProvider, Judgement, RegistrarIndex, RegistrarInfo, Registration,
|
||||
};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
@@ -115,7 +140,7 @@ pub mod pallet {
|
||||
/// The currency trait.
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// The amount held on deposit for a registered identity
|
||||
/// The amount held on deposit for a registered identity.
|
||||
#[pallet::constant]
|
||||
type BasicDeposit: Get<BalanceOf<Self>>;
|
||||
|
||||
@@ -150,14 +175,41 @@ pub mod pallet {
|
||||
/// The origin which may add or remove registrars. Root can always do this.
|
||||
type RegistrarOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Signature type for pre-authorizing usernames off-chain.
|
||||
///
|
||||
/// Can verify whether an `Self::SigningPublicKey` created a signature.
|
||||
type OffchainSignature: Verify<Signer = Self::SigningPublicKey> + Parameter;
|
||||
|
||||
/// Public key that corresponds to an on-chain `Self::AccountId`.
|
||||
type SigningPublicKey: IdentifyAccount<AccountId = Self::AccountId>;
|
||||
|
||||
/// The origin which may add or remove username authorities. Root can always do this.
|
||||
type UsernameAuthorityOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The number of blocks within which a username grant must be accepted.
|
||||
#[pallet::constant]
|
||||
type PendingUsernameExpiration: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The maximum length of a suffix.
|
||||
#[pallet::constant]
|
||||
type MaxSuffixLength: Get<u32>;
|
||||
|
||||
/// The maximum length of a username, including its suffix and any system-added delimiters.
|
||||
#[pallet::constant]
|
||||
type MaxUsernameLength: Get<u32>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// Information that is pertinent to identify the entity behind an account.
|
||||
/// Information that is pertinent to identify the entity behind an account. First item is the
|
||||
/// registration, second is the account's primary username.
|
||||
///
|
||||
/// TWOX-NOTE: OK ― `AccountId` is a secure hash.
|
||||
#[pallet::storage]
|
||||
@@ -166,7 +218,7 @@ pub mod pallet {
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Registration<BalanceOf<T>, T::MaxRegistrars, T::IdentityInformation>,
|
||||
(Registration<BalanceOf<T>, T::MaxRegistrars, T::IdentityInformation>, Option<Username<T>>),
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
@@ -213,6 +265,38 @@ pub mod pallet {
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
/// A map of the accounts who are authorized to grant usernames.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn authority)]
|
||||
pub(super) type UsernameAuthorities<T: Config> =
|
||||
StorageMap<_, Twox64Concat, T::AccountId, AuthorityPropertiesOf<T>, OptionQuery>;
|
||||
|
||||
/// Reverse lookup from `username` to the `AccountId` that has registered it. The value should
|
||||
/// be a key in the `IdentityOf` map, but it may not if the user has cleared their identity.
|
||||
///
|
||||
/// Multiple usernames may map to the same `AccountId`, but `IdentityOf` will only map to one
|
||||
/// primary username.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn username)]
|
||||
pub(super) type AccountOfUsername<T: Config> =
|
||||
StorageMap<_, Blake2_128Concat, Username<T>, T::AccountId, OptionQuery>;
|
||||
|
||||
/// Usernames that an authority has granted, but that the account controller has not confirmed
|
||||
/// that they want it. Used primarily in cases where the `AccountId` cannot provide a signature
|
||||
/// because they are a pure proxy, multisig, etc. In order to confirm it, they should call
|
||||
/// [`Call::accept_username`].
|
||||
///
|
||||
/// First tuple item is the account and second is the acceptance deadline.
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn preapproved_usernames)]
|
||||
pub type PendingUsernames<T: Config> = StorageMap<
|
||||
_,
|
||||
Blake2_128Concat,
|
||||
Username<T>,
|
||||
(T::AccountId, BlockNumberFor<T>),
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Too many subs-accounts.
|
||||
@@ -249,6 +333,24 @@ pub mod pallet {
|
||||
JudgementForDifferentIdentity,
|
||||
/// Error that occurs when there is an issue paying for judgement.
|
||||
JudgementPaymentFailed,
|
||||
/// The provided suffix is too long.
|
||||
InvalidSuffix,
|
||||
/// The sender does not have permission to issue a username.
|
||||
NotUsernameAuthority,
|
||||
/// The authority cannot allocate any more usernames.
|
||||
NoAllocation,
|
||||
/// The signature on a username was not valid.
|
||||
InvalidSignature,
|
||||
/// Setting this username requires a signature, but none was provided.
|
||||
RequiresSignature,
|
||||
/// The username does not meet the requirements.
|
||||
InvalidUsername,
|
||||
/// The username is already taken.
|
||||
UsernameTaken,
|
||||
/// The requested username does not exist.
|
||||
NoUsername,
|
||||
/// The username cannot be forcefully removed because it can still be accepted.
|
||||
NotExpired,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
@@ -275,6 +377,21 @@ pub mod pallet {
|
||||
/// A sub-identity was cleared, and the given deposit repatriated from the
|
||||
/// main identity account to the sub-identity account.
|
||||
SubIdentityRevoked { sub: T::AccountId, main: T::AccountId, deposit: BalanceOf<T> },
|
||||
/// A username authority was added.
|
||||
AuthorityAdded { authority: T::AccountId },
|
||||
/// A username authority was removed.
|
||||
AuthorityRemoved { authority: T::AccountId },
|
||||
/// A username was set for `who`.
|
||||
UsernameSet { who: T::AccountId, username: Username<T> },
|
||||
/// A username was queued, but `who` must accept it prior to `expiration`.
|
||||
UsernameQueued { who: T::AccountId, username: Username<T>, expiration: BlockNumberFor<T> },
|
||||
/// A queued username passed its expiration without being claimed and was removed.
|
||||
PreapprovalExpired { whose: T::AccountId },
|
||||
/// A username was set as a primary and can be looked up from `who`.
|
||||
PrimaryUsernameSet { who: T::AccountId, username: Username<T> },
|
||||
/// A dangling username (as in, a username corresponding to an account that has removed its
|
||||
/// identity) has been removed.
|
||||
DanglingUsernameRemoved { who: T::AccountId, username: Username<T> },
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
@@ -331,36 +448,34 @@ pub mod pallet {
|
||||
info: Box<T::IdentityInformation>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let encoded_byte_size = info.encoded_size() as u32;
|
||||
let byte_deposit =
|
||||
T::ByteDeposit::get().saturating_mul(<BalanceOf<T>>::from(encoded_byte_size));
|
||||
|
||||
let mut id = match <IdentityOf<T>>::get(&sender) {
|
||||
Some(mut id) => {
|
||||
// Only keep non-positive judgements.
|
||||
id.judgements.retain(|j| j.1.is_sticky());
|
||||
id.info = *info;
|
||||
id
|
||||
},
|
||||
None => Registration {
|
||||
info: *info,
|
||||
judgements: BoundedVec::default(),
|
||||
deposit: Zero::zero(),
|
||||
},
|
||||
let (mut id, username) = match <IdentityOf<T>>::get(&sender) {
|
||||
Some((mut id, maybe_username)) => (
|
||||
{
|
||||
// Only keep non-positive judgements.
|
||||
id.judgements.retain(|j| j.1.is_sticky());
|
||||
id.info = *info;
|
||||
id
|
||||
},
|
||||
maybe_username,
|
||||
),
|
||||
None => (
|
||||
Registration {
|
||||
info: *info,
|
||||
judgements: BoundedVec::default(),
|
||||
deposit: Zero::zero(),
|
||||
},
|
||||
None,
|
||||
),
|
||||
};
|
||||
|
||||
let new_deposit = Self::calculate_identity_deposit(&id.info);
|
||||
let old_deposit = id.deposit;
|
||||
id.deposit = T::BasicDeposit::get().saturating_add(byte_deposit);
|
||||
if id.deposit > old_deposit {
|
||||
T::Currency::reserve(&sender, id.deposit - old_deposit)?;
|
||||
}
|
||||
if old_deposit > id.deposit {
|
||||
let err_amount = T::Currency::unreserve(&sender, old_deposit - id.deposit);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
}
|
||||
Self::rejig_deposit(&sender, old_deposit, new_deposit)?;
|
||||
|
||||
id.deposit = new_deposit;
|
||||
let judgements = id.judgements.len();
|
||||
<IdentityOf<T>>::insert(&sender, id);
|
||||
<IdentityOf<T>>::insert(&sender, (id, username));
|
||||
Self::deposit_event(Event::IdentitySet { who: sender });
|
||||
|
||||
Ok(Some(T::WeightInfo::set_identity(judgements as u32)).into())
|
||||
@@ -452,11 +567,15 @@ pub mod pallet {
|
||||
let sender = ensure_signed(origin)?;
|
||||
|
||||
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&sender);
|
||||
let id = <IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NotNamed)?;
|
||||
let (id, maybe_username) =
|
||||
<IdentityOf<T>>::take(&sender).ok_or(Error::<T>::NoIdentity)?;
|
||||
let deposit = id.total_deposit().saturating_add(subs_deposit);
|
||||
for sub in sub_ids.iter() {
|
||||
<SuperOf<T>>::remove(sub);
|
||||
}
|
||||
if let Some(username) = maybe_username {
|
||||
AccountOfUsername::<T>::remove(username);
|
||||
}
|
||||
|
||||
let err_amount = T::Currency::unreserve(&sender, deposit);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
@@ -501,7 +620,7 @@ pub mod pallet {
|
||||
.and_then(Option::as_ref)
|
||||
.ok_or(Error::<T>::EmptyIndex)?;
|
||||
ensure!(max_fee >= registrar.fee, Error::<T>::FeeChanged);
|
||||
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
|
||||
let (mut id, username) = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
|
||||
|
||||
let item = (reg_index, Judgement::FeePaid(registrar.fee));
|
||||
match id.judgements.binary_search_by_key(®_index, |x| x.0) {
|
||||
@@ -518,7 +637,7 @@ pub mod pallet {
|
||||
T::Currency::reserve(&sender, registrar.fee)?;
|
||||
|
||||
let judgements = id.judgements.len();
|
||||
<IdentityOf<T>>::insert(&sender, id);
|
||||
<IdentityOf<T>>::insert(&sender, (id, username));
|
||||
|
||||
Self::deposit_event(Event::JudgementRequested {
|
||||
who: sender,
|
||||
@@ -545,7 +664,7 @@ pub mod pallet {
|
||||
reg_index: RegistrarIndex,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let mut id = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
|
||||
let (mut id, username) = <IdentityOf<T>>::get(&sender).ok_or(Error::<T>::NoIdentity)?;
|
||||
|
||||
let pos = id
|
||||
.judgements
|
||||
@@ -560,7 +679,7 @@ pub mod pallet {
|
||||
let err_amount = T::Currency::unreserve(&sender, fee);
|
||||
debug_assert!(err_amount.is_zero());
|
||||
let judgements = id.judgements.len();
|
||||
<IdentityOf<T>>::insert(&sender, id);
|
||||
<IdentityOf<T>>::insert(&sender, (id, username));
|
||||
|
||||
Self::deposit_event(Event::JudgementUnrequested {
|
||||
who: sender,
|
||||
@@ -679,6 +798,8 @@ pub mod pallet {
|
||||
/// - `identity`: The hash of the [`IdentityInformationProvider`] for that the judgement is
|
||||
/// provided.
|
||||
///
|
||||
/// Note: Judgements do not apply to a username.
|
||||
///
|
||||
/// Emits `JudgementGiven` if successful.
|
||||
#[pallet::call_index(9)]
|
||||
#[pallet::weight(T::WeightInfo::provide_judgement(T::MaxRegistrars::get()))]
|
||||
@@ -697,7 +818,8 @@ pub mod pallet {
|
||||
.and_then(Option::as_ref)
|
||||
.filter(|r| r.account == sender)
|
||||
.ok_or(Error::<T>::InvalidIndex)?;
|
||||
let mut id = <IdentityOf<T>>::get(&target).ok_or(Error::<T>::InvalidTarget)?;
|
||||
let (mut id, username) =
|
||||
<IdentityOf<T>>::get(&target).ok_or(Error::<T>::InvalidTarget)?;
|
||||
|
||||
if T::Hashing::hash_of(&id.info) != identity {
|
||||
return Err(Error::<T>::JudgementForDifferentIdentity.into())
|
||||
@@ -724,7 +846,7 @@ pub mod pallet {
|
||||
}
|
||||
|
||||
let judgements = id.judgements.len();
|
||||
<IdentityOf<T>>::insert(&target, id);
|
||||
<IdentityOf<T>>::insert(&target, (id, username));
|
||||
Self::deposit_event(Event::JudgementGiven { target, registrar_index: reg_index });
|
||||
|
||||
Ok(Some(T::WeightInfo::provide_judgement(judgements as u32)).into())
|
||||
@@ -757,11 +879,15 @@ pub mod pallet {
|
||||
let target = T::Lookup::lookup(target)?;
|
||||
// Grab their deposit (and check that they have one).
|
||||
let (subs_deposit, sub_ids) = <SubsOf<T>>::take(&target);
|
||||
let id = <IdentityOf<T>>::take(&target).ok_or(Error::<T>::NotNamed)?;
|
||||
let (id, maybe_username) =
|
||||
<IdentityOf<T>>::take(&target).ok_or(Error::<T>::NoIdentity)?;
|
||||
let deposit = id.total_deposit().saturating_add(subs_deposit);
|
||||
for sub in sub_ids.iter() {
|
||||
<SuperOf<T>>::remove(sub);
|
||||
}
|
||||
if let Some(username) = maybe_username {
|
||||
AccountOfUsername::<T>::remove(username);
|
||||
}
|
||||
// Slash their deposit from them.
|
||||
T::Slashed::on_unbalanced(T::Currency::slash_reserved(&target, deposit).0);
|
||||
|
||||
@@ -886,6 +1012,186 @@ pub mod pallet {
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add an `AccountId` with permission to grant usernames with a given `suffix` appended.
|
||||
///
|
||||
/// The authority can grant up to `allocation` usernames. To top up their allocation, they
|
||||
/// should just issue (or request via governance) a new `add_username_authority` call.
|
||||
#[pallet::call_index(15)]
|
||||
#[pallet::weight(T::WeightInfo::add_username_authority())]
|
||||
pub fn add_username_authority(
|
||||
origin: OriginFor<T>,
|
||||
authority: AccountIdLookupOf<T>,
|
||||
suffix: Vec<u8>,
|
||||
allocation: u32,
|
||||
) -> DispatchResult {
|
||||
T::UsernameAuthorityOrigin::ensure_origin(origin)?;
|
||||
let authority = T::Lookup::lookup(authority)?;
|
||||
// We don't need to check the length because it gets checked when casting into a
|
||||
// `BoundedVec`.
|
||||
Self::validate_username(&suffix, None).map_err(|_| Error::<T>::InvalidSuffix)?;
|
||||
let suffix = Suffix::<T>::try_from(suffix).map_err(|_| Error::<T>::InvalidSuffix)?;
|
||||
// The authority may already exist, but we don't need to check. They might be changing
|
||||
// their suffix or adding allocation, so we just want to overwrite whatever was there.
|
||||
UsernameAuthorities::<T>::insert(
|
||||
&authority,
|
||||
AuthorityPropertiesOf::<T> { suffix, allocation },
|
||||
);
|
||||
Self::deposit_event(Event::AuthorityAdded { authority });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove `authority` from the username authorities.
|
||||
#[pallet::call_index(16)]
|
||||
#[pallet::weight(T::WeightInfo::remove_username_authority())]
|
||||
pub fn remove_username_authority(
|
||||
origin: OriginFor<T>,
|
||||
authority: AccountIdLookupOf<T>,
|
||||
) -> DispatchResult {
|
||||
T::UsernameAuthorityOrigin::ensure_origin(origin)?;
|
||||
let authority = T::Lookup::lookup(authority)?;
|
||||
UsernameAuthorities::<T>::take(&authority).ok_or(Error::<T>::NotUsernameAuthority)?;
|
||||
Self::deposit_event(Event::AuthorityRemoved { authority });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the username for `who`. Must be called by a username authority.
|
||||
///
|
||||
/// The authority must have an `allocation`. Users can either pre-sign their usernames or
|
||||
/// accept them later.
|
||||
///
|
||||
/// Usernames must:
|
||||
/// - Only contain lowercase ASCII characters or digits.
|
||||
/// - When combined with the suffix of the issuing authority be _less than_ the
|
||||
/// `MaxUsernameLength`.
|
||||
#[pallet::call_index(17)]
|
||||
#[pallet::weight(T::WeightInfo::set_username_for())]
|
||||
pub fn set_username_for(
|
||||
origin: OriginFor<T>,
|
||||
who: AccountIdLookupOf<T>,
|
||||
username: Vec<u8>,
|
||||
signature: Option<T::OffchainSignature>,
|
||||
) -> DispatchResult {
|
||||
// Ensure origin is a Username Authority and has an allocation. Decrement their
|
||||
// allocation by one.
|
||||
let sender = ensure_signed(origin)?;
|
||||
let suffix = UsernameAuthorities::<T>::try_mutate(
|
||||
&sender,
|
||||
|maybe_authority| -> Result<Suffix<T>, DispatchError> {
|
||||
let properties =
|
||||
maybe_authority.as_mut().ok_or(Error::<T>::NotUsernameAuthority)?;
|
||||
ensure!(properties.allocation > 0, Error::<T>::NoAllocation);
|
||||
properties.allocation.saturating_dec();
|
||||
Ok(properties.suffix.clone())
|
||||
},
|
||||
)?;
|
||||
|
||||
// Ensure that the username only contains allowed characters. We already know the suffix
|
||||
// does.
|
||||
let username_length = username.len().saturating_add(suffix.len()) as u32;
|
||||
Self::validate_username(&username, Some(username_length))?;
|
||||
|
||||
// Concatenate the username with suffix and cast into a BoundedVec. Should be infallible
|
||||
// since we already ensured it is below the max length.
|
||||
let mut full_username =
|
||||
Vec::with_capacity(username.len().saturating_add(suffix.len()).saturating_add(1));
|
||||
full_username.extend(username);
|
||||
full_username.extend(b".");
|
||||
full_username.extend(suffix);
|
||||
let bounded_username =
|
||||
Username::<T>::try_from(full_username).map_err(|_| Error::<T>::InvalidUsername)?;
|
||||
|
||||
// Usernames must be unique. Ensure it's not taken.
|
||||
ensure!(
|
||||
!AccountOfUsername::<T>::contains_key(&bounded_username),
|
||||
Error::<T>::UsernameTaken
|
||||
);
|
||||
ensure!(
|
||||
!PendingUsernames::<T>::contains_key(&bounded_username),
|
||||
Error::<T>::UsernameTaken
|
||||
);
|
||||
|
||||
// Insert or queue.
|
||||
let who = T::Lookup::lookup(who)?;
|
||||
if let Some(s) = signature {
|
||||
// Account has pre-signed an authorization. Verify the signature provided and grant
|
||||
// the username directly.
|
||||
let encoded = Encode::encode(&bounded_username.to_vec());
|
||||
Self::validate_signature(&encoded, &s, &who)?;
|
||||
Self::insert_username(&who, bounded_username);
|
||||
} else {
|
||||
// The user must accept the username, therefore, queue it.
|
||||
Self::queue_acceptance(&who, bounded_username);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Accept a given username that an `authority` granted. The call must include the full
|
||||
/// username, as in `username.suffix`.
|
||||
#[pallet::call_index(18)]
|
||||
#[pallet::weight(T::WeightInfo::accept_username())]
|
||||
pub fn accept_username(
|
||||
origin: OriginFor<T>,
|
||||
username: Username<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let who = ensure_signed(origin)?;
|
||||
let (approved_for, _) =
|
||||
PendingUsernames::<T>::take(&username).ok_or(Error::<T>::NoUsername)?;
|
||||
ensure!(approved_for == who.clone(), Error::<T>::InvalidUsername);
|
||||
Self::insert_username(&who, username.clone());
|
||||
Self::deposit_event(Event::UsernameSet { who: who.clone(), username });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Remove an expired username approval. The username was approved by an authority but never
|
||||
/// accepted by the user and must now be beyond its expiration. The call must include the
|
||||
/// full username, as in `username.suffix`.
|
||||
#[pallet::call_index(19)]
|
||||
#[pallet::weight(T::WeightInfo::remove_expired_approval())]
|
||||
pub fn remove_expired_approval(
|
||||
origin: OriginFor<T>,
|
||||
username: Username<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let _ = ensure_signed(origin)?;
|
||||
if let Some((who, expiration)) = PendingUsernames::<T>::take(&username) {
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
ensure!(now > expiration, Error::<T>::NotExpired);
|
||||
Self::deposit_event(Event::PreapprovalExpired { whose: who.clone() });
|
||||
Ok(Pays::No.into())
|
||||
} else {
|
||||
Err(Error::<T>::NoUsername.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a given username as the primary. The username should include the suffix.
|
||||
#[pallet::call_index(20)]
|
||||
#[pallet::weight(T::WeightInfo::set_primary_username())]
|
||||
pub fn set_primary_username(origin: OriginFor<T>, username: Username<T>) -> DispatchResult {
|
||||
// ensure `username` maps to `origin` (i.e. has already been set by an authority).
|
||||
let who = ensure_signed(origin)?;
|
||||
ensure!(AccountOfUsername::<T>::contains_key(&username), Error::<T>::NoUsername);
|
||||
let (registration, _maybe_username) =
|
||||
IdentityOf::<T>::get(&who).ok_or(Error::<T>::NoIdentity)?;
|
||||
IdentityOf::<T>::insert(&who, (registration, Some(username.clone())));
|
||||
Self::deposit_event(Event::PrimaryUsernameSet { who: who.clone(), username });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Remove a username that corresponds to an account with no identity. Exists when a user
|
||||
/// gets a username but then calls `clear_identity`.
|
||||
#[pallet::call_index(21)]
|
||||
#[pallet::weight(T::WeightInfo::remove_dangling_username())]
|
||||
pub fn remove_dangling_username(
|
||||
origin: OriginFor<T>,
|
||||
username: Username<T>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
// ensure `username` maps to `origin` (i.e. has already been set by an authority).
|
||||
let _ = ensure_signed(origin)?;
|
||||
let who = AccountOfUsername::<T>::take(&username).ok_or(Error::<T>::NoUsername)?;
|
||||
ensure!(!IdentityOf::<T>::contains_key(&who), Error::<T>::InvalidUsername);
|
||||
Self::deposit_event(Event::DanglingUsernameRemoved { who: who.clone(), username });
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,7 +1231,104 @@ impl<T: Config> Pallet<T> {
|
||||
fields: <T::IdentityInformation as IdentityInformationProvider>::FieldsIdentifier,
|
||||
) -> bool {
|
||||
IdentityOf::<T>::get(who)
|
||||
.map_or(false, |registration| (registration.info.has_identity(fields)))
|
||||
.map_or(false, |(registration, _username)| (registration.info.has_identity(fields)))
|
||||
}
|
||||
|
||||
/// Calculate the deposit required for an identity.
|
||||
fn calculate_identity_deposit(info: &T::IdentityInformation) -> BalanceOf<T> {
|
||||
let bytes = info.encoded_size() as u32;
|
||||
let byte_deposit = T::ByteDeposit::get().saturating_mul(<BalanceOf<T>>::from(bytes));
|
||||
T::BasicDeposit::get().saturating_add(byte_deposit)
|
||||
}
|
||||
|
||||
/// Validate that a username conforms to allowed characters/format.
|
||||
///
|
||||
/// The function will validate the characters in `username` and that `length` (if `Some`)
|
||||
/// conforms to the limit. It is not expected to pass a fully formatted username here (i.e. one
|
||||
/// with any protocol-added characters included, such as a `.`). The suffix is also separately
|
||||
/// validated by this function to ensure the full username conforms.
|
||||
fn validate_username(username: &Vec<u8>, length: Option<u32>) -> DispatchResult {
|
||||
// Verify input length before allocating a Vec with the user's input. `<` instead of `<=`
|
||||
// because it needs one element for the point (`username` + `.` + `suffix`).
|
||||
if let Some(l) = length {
|
||||
ensure!(l < T::MaxUsernameLength::get(), Error::<T>::InvalidUsername);
|
||||
}
|
||||
// Usernames cannot be empty.
|
||||
ensure!(!username.is_empty(), Error::<T>::InvalidUsername);
|
||||
// Username must be lowercase and alphanumeric.
|
||||
ensure!(
|
||||
username.iter().all(|byte| byte.is_ascii_digit() || byte.is_ascii_lowercase()),
|
||||
Error::<T>::InvalidUsername
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validate a signature. Supports signatures on raw `data` or `data` wrapped in HTML `<Bytes>`.
|
||||
pub fn validate_signature(
|
||||
data: &Vec<u8>,
|
||||
signature: &T::OffchainSignature,
|
||||
signer: &T::AccountId,
|
||||
) -> DispatchResult {
|
||||
// Happy path, user has signed the raw data.
|
||||
if signature.verify(&data[..], &signer) {
|
||||
return Ok(())
|
||||
}
|
||||
// NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into
|
||||
// `<Bytes> + data + </Bytes>`, so why we support both wrapped and raw versions.
|
||||
let prefix = b"<Bytes>";
|
||||
let suffix = b"</Bytes>";
|
||||
let mut wrapped: Vec<u8> = Vec::with_capacity(data.len() + prefix.len() + suffix.len());
|
||||
wrapped.extend(prefix);
|
||||
wrapped.extend(data);
|
||||
wrapped.extend(suffix);
|
||||
|
||||
ensure!(signature.verify(&wrapped[..], &signer), Error::<T>::InvalidSignature);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// A username has met all conditions. Insert the relevant storage items.
|
||||
pub fn insert_username(who: &T::AccountId, username: Username<T>) {
|
||||
// Check if they already have a primary. If so, leave it. If not, set it.
|
||||
// Likewise, check if they have an identity. If not, give them a minimal one.
|
||||
let (reg, primary_username, new_is_primary) = match <IdentityOf<T>>::get(&who) {
|
||||
// User has an existing Identity and a primary username. Leave it.
|
||||
Some((reg, Some(primary))) => (reg, primary, false),
|
||||
// User has an Identity but no primary. Set the new one as primary.
|
||||
Some((reg, None)) => (reg, username.clone(), true),
|
||||
// User does not have an existing Identity. Give them a fresh default one and set
|
||||
// their username as primary.
|
||||
None => (
|
||||
Registration {
|
||||
info: Default::default(),
|
||||
judgements: Default::default(),
|
||||
deposit: Zero::zero(),
|
||||
},
|
||||
username.clone(),
|
||||
true,
|
||||
),
|
||||
};
|
||||
|
||||
// Enter in identity map. Note: In the case that the user did not have a pre-existing
|
||||
// Identity, we have given them the storage item for free. If they ever call
|
||||
// `set_identity` with identity info, then they will need to place the normal identity
|
||||
// deposit.
|
||||
IdentityOf::<T>::insert(&who, (reg, Some(primary_username)));
|
||||
// Enter in username map.
|
||||
AccountOfUsername::<T>::insert(username.clone(), &who);
|
||||
Self::deposit_event(Event::UsernameSet { who: who.clone(), username: username.clone() });
|
||||
if new_is_primary {
|
||||
Self::deposit_event(Event::PrimaryUsernameSet { who: who.clone(), username });
|
||||
}
|
||||
}
|
||||
|
||||
/// A username was granted by an authority, but must be accepted by `who`. Put the username
|
||||
/// into a queue for acceptance.
|
||||
pub fn queue_acceptance(who: &T::AccountId, username: Username<T>) {
|
||||
let now = frame_system::Pallet::<T>::block_number();
|
||||
let expiration = now.saturating_add(T::PendingUsernameExpiration::get());
|
||||
PendingUsernames::<T>::insert(&username, (who.clone(), expiration));
|
||||
Self::deposit_event(Event::UsernameQueued { who: who.clone(), username, expiration });
|
||||
}
|
||||
|
||||
/// Reap an identity, clearing associated storage items and refunding any deposits. This
|
||||
@@ -943,7 +1346,7 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn reap_identity(who: &T::AccountId) -> Result<(u32, u32, u32), DispatchError> {
|
||||
// `take` any storage items keyed by `target`
|
||||
// identity
|
||||
let id = <IdentityOf<T>>::take(&who).ok_or(Error::<T>::NotNamed)?;
|
||||
let (id, _maybe_username) = <IdentityOf<T>>::take(&who).ok_or(Error::<T>::NoIdentity)?;
|
||||
let registrars = id.judgements.len() as u32;
|
||||
let encoded_byte_size = id.info.encoded_size() as u32;
|
||||
|
||||
@@ -976,8 +1379,8 @@ impl<T: Config> Pallet<T> {
|
||||
// Identity Deposit
|
||||
let new_id_deposit = IdentityOf::<T>::try_mutate(
|
||||
&target,
|
||||
|registration| -> Result<BalanceOf<T>, DispatchError> {
|
||||
let reg = registration.as_mut().ok_or(Error::<T>::NoIdentity)?;
|
||||
|identity_of| -> Result<BalanceOf<T>, DispatchError> {
|
||||
let (reg, _) = identity_of.as_mut().ok_or(Error::<T>::NoIdentity)?;
|
||||
// Calculate what deposit should be
|
||||
let encoded_byte_size = reg.info.encoded_size() as u32;
|
||||
let byte_deposit =
|
||||
@@ -1014,11 +1417,14 @@ impl<T: Config> Pallet<T> {
|
||||
) -> DispatchResult {
|
||||
IdentityOf::<T>::insert(
|
||||
&who,
|
||||
Registration {
|
||||
judgements: Default::default(),
|
||||
deposit: Zero::zero(),
|
||||
info: info.clone(),
|
||||
},
|
||||
(
|
||||
Registration {
|
||||
judgements: Default::default(),
|
||||
deposit: Zero::zero(),
|
||||
info: info.clone(),
|
||||
},
|
||||
None::<Username<T>>,
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
@@ -1030,7 +1436,6 @@ impl<T: Config> Pallet<T> {
|
||||
who: &T::AccountId,
|
||||
subs: Vec<(T::AccountId, Data)>,
|
||||
) -> DispatchResult {
|
||||
use frame_support::BoundedVec;
|
||||
let mut sub_accounts = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
|
||||
for (sub, name) in subs {
|
||||
<SuperOf<T>>::insert(&sub, (who.clone(), name));
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
// 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.
|
||||
|
||||
//! Storage migrations for the Identity pallet.
|
||||
|
||||
use super::*;
|
||||
use frame_support::{migrations::VersionedMigration, pallet_prelude::*, traits::OnRuntimeUpgrade};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use codec::{Decode, Encode};
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_runtime::TryRuntimeError;
|
||||
|
||||
pub mod versioned {
|
||||
use super::*;
|
||||
|
||||
pub type V0ToV1<T, const KL: u64> = VersionedMigration<
|
||||
0,
|
||||
1,
|
||||
v1::VersionUncheckedMigrateV0ToV1<T, KL>,
|
||||
crate::pallet::Pallet<T>,
|
||||
<T as frame_system::Config>::DbWeight,
|
||||
>;
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
|
||||
/// The log target.
|
||||
const TARGET: &'static str = "runtime::identity::migration::v1";
|
||||
|
||||
/// The old identity type, useful in pre-upgrade.
|
||||
mod v0 {
|
||||
use super::*;
|
||||
use frame_support::storage_alias;
|
||||
|
||||
#[storage_alias]
|
||||
pub type IdentityOf<T: Config> = StorageMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
<T as frame_system::Config>::AccountId,
|
||||
Registration<
|
||||
BalanceOf<T>,
|
||||
<T as pallet::Config>::MaxRegistrars,
|
||||
<T as pallet::Config>::IdentityInformation,
|
||||
>,
|
||||
OptionQuery,
|
||||
>;
|
||||
}
|
||||
|
||||
/// Migration to add usernames to Identity info.
|
||||
///
|
||||
/// `T` is the runtime and `KL` is the key limit to migrate. This is just a safety guard to
|
||||
/// prevent stalling a parachain by accumulating too much weight in the migration. To have an
|
||||
/// unlimited migration (e.g. in a chain without PoV limits), set this to `u64::MAX`.
|
||||
pub struct VersionUncheckedMigrateV0ToV1<T, const KL: u64>(PhantomData<T>);
|
||||
impl<T: Config, const KL: u64> OnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1<T, KL> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let identities = v0::IdentityOf::<T>::iter().count();
|
||||
log::info!(
|
||||
target: TARGET,
|
||||
"pre-upgrade state contains '{}' identities.",
|
||||
identities
|
||||
);
|
||||
ensure!((identities as u64) < KL, "too many identities to migrate");
|
||||
Ok((identities as u64).encode())
|
||||
}
|
||||
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
log::info!(
|
||||
target: TARGET,
|
||||
"running storage migration from version 0 to version 1."
|
||||
);
|
||||
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
let mut translated: u64 = 0;
|
||||
let mut interrupted = false;
|
||||
|
||||
for (account, registration) in v0::IdentityOf::<T>::iter() {
|
||||
IdentityOf::<T>::insert(account, (registration, None::<Username<T>>));
|
||||
translated.saturating_inc();
|
||||
if translated >= KL {
|
||||
log::warn!(
|
||||
"Incomplete! Migration limit reached. Only {} identities migrated.",
|
||||
translated
|
||||
);
|
||||
interrupted = true;
|
||||
break
|
||||
}
|
||||
}
|
||||
if !interrupted {
|
||||
log::info!("all {} identities migrated", translated);
|
||||
}
|
||||
|
||||
weight.saturating_accrue(T::DbWeight::get().reads_writes(translated, translated));
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(1));
|
||||
weight
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> Result<(), TryRuntimeError> {
|
||||
let identities_to_migrate: u64 = Decode::decode(&mut &state[..])
|
||||
.expect("failed to decode the state from pre-upgrade.");
|
||||
let identities = IdentityOf::<T>::iter().count() as u64;
|
||||
log::info!("post-upgrade expects '{}' identities to have been migrated.", identities);
|
||||
ensure!(identities_to_migrate == identities, "must migrate all identities.");
|
||||
log::info!(target: TARGET, "migrated all identities.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
+1107
-260
File diff suppressed because it is too large
Load Diff
@@ -232,7 +232,7 @@ impl<Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + Part
|
||||
|
||||
/// Information concerning the identity of the controller of an account.
|
||||
pub trait IdentityInformationProvider:
|
||||
Encode + Decode + MaxEncodedLen + Clone + Debug + Eq + PartialEq + TypeInfo
|
||||
Encode + Decode + MaxEncodedLen + Clone + Debug + Eq + PartialEq + TypeInfo + Default
|
||||
{
|
||||
/// Type capable of holding information on which identity fields are set.
|
||||
type FieldsIdentifier: Member + Encode + Decode + MaxEncodedLen + TypeInfo + Default;
|
||||
@@ -319,6 +319,28 @@ pub struct RegistrarInfo<
|
||||
pub fields: IdField,
|
||||
}
|
||||
|
||||
/// Authority properties for a given pallet configuration.
|
||||
pub type AuthorityPropertiesOf<T> = AuthorityProperties<Suffix<T>>;
|
||||
|
||||
/// The number of usernames that an authority may allocate.
|
||||
type Allocation = u32;
|
||||
/// A byte vec used to represent a username.
|
||||
pub(crate) type Suffix<T> = BoundedVec<u8, <T as Config>::MaxSuffixLength>;
|
||||
|
||||
/// Properties of a username authority.
|
||||
#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Debug)]
|
||||
pub struct AuthorityProperties<Suffix> {
|
||||
/// The suffix added to usernames granted by this authority. Will be appended to usernames; for
|
||||
/// example, a suffix of `wallet` will result in `.wallet` being appended to a user's selected
|
||||
/// name.
|
||||
pub suffix: Suffix,
|
||||
/// The number of usernames remaining that this authority can grant.
|
||||
pub allocation: Allocation,
|
||||
}
|
||||
|
||||
/// A byte vec used to represent a username.
|
||||
pub(crate) type Username<T> = BoundedVec<u8, <T as Config>::MaxUsernameLength>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
Generated
+195
@@ -68,6 +68,13 @@ pub trait WeightInfo {
|
||||
fn rename_sub(s: u32, ) -> Weight;
|
||||
fn remove_sub(s: u32, ) -> Weight;
|
||||
fn quit_sub(s: u32, ) -> Weight;
|
||||
fn add_username_authority() -> Weight;
|
||||
fn remove_username_authority() -> Weight;
|
||||
fn set_username_for() -> Weight;
|
||||
fn accept_username() -> Weight;
|
||||
fn remove_expired_approval() -> Weight;
|
||||
fn set_primary_username() -> Weight;
|
||||
fn remove_dangling_username() -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for pallet_identity using the Substrate node and recommended hardware.
|
||||
@@ -345,6 +352,100 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
|
||||
.saturating_add(T::DbWeight::get().reads(3_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(3))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(T::DbWeight::get().reads(1))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(T::DbWeight::get().reads(2))
|
||||
.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests
|
||||
@@ -621,4 +722,98 @@ impl WeightInfo for () {
|
||||
.saturating_add(RocksDbWeight::get().reads(3_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(2_u64))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn add_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 13_873_000 picoseconds.
|
||||
Weight::from_parts(13_873_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:0 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
fn remove_username_authority() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `0`
|
||||
// Estimated: `0`
|
||||
// Minimum execution time: 10_653_000 picoseconds.
|
||||
Weight::from_parts(10_653_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 0))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::UsernameAuthorities` (r:1 w:1)
|
||||
/// Proof: `Identity::UsernameAuthorities` (`max_values`: None, `max_size`: Some(52), added: 2527, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_username_for() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `80`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 75_928_000 picoseconds.
|
||||
Weight::from_parts(75_928_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(RocksDbWeight::get().reads(3))
|
||||
.saturating_add(RocksDbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::AccountOfUsername` (r:0 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
fn accept_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 38_157_000 picoseconds.
|
||||
Weight::from_parts(38_157_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(3))
|
||||
}
|
||||
/// Storage: `Identity::PendingUsernames` (r:1 w:1)
|
||||
/// Proof: `Identity::PendingUsernames` (`max_values`: None, `max_size`: Some(77), added: 2552, mode: `MaxEncodedLen`)
|
||||
fn remove_expired_approval() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `106`
|
||||
// Estimated: `3542`
|
||||
// Minimum execution time: 46_821_000 picoseconds.
|
||||
Weight::from_parts(46_821_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 3542))
|
||||
.saturating_add(RocksDbWeight::get().reads(1))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:0)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:1)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn set_primary_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `247`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 22_515_000 picoseconds.
|
||||
Weight::from_parts(22_515_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
/// Storage: `Identity::AccountOfUsername` (r:1 w:1)
|
||||
/// Proof: `Identity::AccountOfUsername` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
|
||||
/// Storage: `Identity::IdentityOf` (r:1 w:0)
|
||||
/// Proof: `Identity::IdentityOf` (`max_values`: None, `max_size`: Some(7572), added: 10047, mode: `MaxEncodedLen`)
|
||||
fn remove_dangling_username() -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `126`
|
||||
// Estimated: `11037`
|
||||
// Minimum execution time: 15_997_000 picoseconds.
|
||||
Weight::from_parts(15_997_000, 0)
|
||||
.saturating_add(Weight::from_parts(0, 11037))
|
||||
.saturating_add(RocksDbWeight::get().reads(2))
|
||||
.saturating_add(RocksDbWeight::get().writes(1))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user