diff --git a/Cargo.lock b/Cargo.lock index c3e5e2ff41bc..924aeb7848c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/people.rs b/cumulus/parachains/runtimes/people/people-rococo/src/people.rs index 9ff249318d92..88a89711019d 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/people.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/people.rs @@ -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; type ForceOrigin = EnsureRoot; type RegistrarOrigin = EnsureRoot; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -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, + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs index 65cac7875bad..1e8ba87e2510 100644 --- a/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs +++ b/cumulus/parachains/runtimes/people/people-rococo/src/weights/pallet_identity.rs @@ -312,4 +312,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .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)) + } } diff --git a/cumulus/parachains/runtimes/people/people-westend/src/people.rs b/cumulus/parachains/runtimes/people/people-westend/src/people.rs index d279be65110a..a5c0e66a3f88 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/people.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/people.rs @@ -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; type ForceOrigin = EnsureRoot; type RegistrarOrigin = EnsureRoot; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -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, + } + } +} diff --git a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs index 65cac7875bad..1e8ba87e2510 100644 --- a/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs +++ b/cumulus/parachains/runtimes/people/people-westend/src/weights/pallet_identity.rs @@ -312,4 +312,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .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)) + } } diff --git a/polkadot/runtime/common/src/integration_tests.rs b/polkadot/runtime/common/src/integration_tests.rs index 4870432d22f9..cfa8f4c3ad92 100644 --- a/polkadot/runtime/common/src/integration_tests.rs +++ b/polkadot/runtime/common/src/integration_tests.rs @@ -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; type ForceOrigin = EnsureRoot; + type OffchainSignature = MultiSignature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<100>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = (); } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 67caa347bc38..3fd3f2bc6354 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -668,6 +668,12 @@ impl pallet_identity::Config for Runtime { type Slashed = Treasury; type ForceOrigin = EitherOf, GeneralAdmin>; type RegistrarOrigin = EitherOf, GeneralAdmin>; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -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, @@ -1655,6 +1664,9 @@ pub mod migrations { // Remove `im-online` pallet on-chain storage frame_support::migrations::RemovePallet::DbWeight>, + + // Migrate Identity pallet for Usernames + pallet_identity::migration::versioned::V0ToV1, parachains_configuration::migration::v11::MigrateToV11, // This needs to come after the `parachains_configuration` above as we are reading the configuration. coretime::migration::MigrateToCoretime, diff --git a/polkadot/runtime/rococo/src/weights/pallet_identity.rs b/polkadot/runtime/rococo/src/weights/pallet_identity.rs index e8c25269ac37..b334e21ea031 100644 --- a/polkadot/runtime/rococo/src/weights/pallet_identity.rs +++ b/polkadot/runtime/rococo/src/weights/pallet_identity.rs @@ -334,4 +334,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .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)) + } } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index e0b9fd0fb53e..e1416c4b7543 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -878,6 +878,12 @@ impl pallet_identity::Config for Runtime { type MaxRegistrars = MaxRegistrars; type ForceOrigin = EitherOf, GeneralAdmin>; type RegistrarOrigin = EitherOf, GeneralAdmin>; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = weights::pallet_identity::WeightInfo; } @@ -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, @@ -1644,6 +1653,8 @@ pub mod migrations { ImOnlinePalletName, ::DbWeight, >, + // Migrate Identity pallet for Usernames + pallet_identity::migration::versioned::V0ToV1, parachains_configuration::migration::v11::MigrateToV11, ); } diff --git a/polkadot/runtime/westend/src/weights/pallet_identity.rs b/polkadot/runtime/westend/src/weights/pallet_identity.rs index dea631b9316b..dc7061615c95 100644 --- a/polkadot/runtime/westend/src/weights/pallet_identity.rs +++ b/polkadot/runtime/westend/src/weights/pallet_identity.rs @@ -338,4 +338,98 @@ impl pallet_identity::WeightInfo for WeightInfo { .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)) + } } diff --git a/prdoc/pr_2651.prdoc b/prdoc/pr_2651.prdoc new file mode 100644 index 000000000000..e28013d4330e --- /dev/null +++ b/prdoc/pr_2651.prdoc @@ -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: [ ] diff --git a/substrate/bin/node/runtime/src/impls.rs b/substrate/bin/node/runtime/src/impls.rs index 717fbeadada4..7ff52a758b3d 100644 --- a/substrate/bin/node/runtime/src/impls.rs +++ b/substrate/bin/node/runtime/src/impls.rs @@ -64,7 +64,7 @@ impl IdentityVerifier 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() diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 6e7bfb4f4b42..ec1601929fcf 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -1500,6 +1500,12 @@ impl pallet_identity::Config for Runtime { type Slashed = Treasury; type ForceOrigin = EnsureRootOrHalfCouncil; type RegistrarOrigin = EnsureRootOrHalfCouncil; + type OffchainSignature = Signature; + type SigningPublicKey = ::Signer; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU32<{ 7 * DAYS }>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = pallet_identity::weights::SubstrateWeight; } @@ -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, pallet_alliance::migration::Migration, pallet_contracts::Migration, + pallet_identity::migration::versioned::V0ToV1, ); type EventRecord = frame_system::EventRecord< diff --git a/substrate/frame/alliance/src/mock.rs b/substrate/frame/alliance/src/mock.rs index 01e0e01fe7ec..22aea9005efa 100644 --- a/substrate/frame/alliance/src/mock.rs +++ b/substrate/frame/alliance/src/mock.rs @@ -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>( + &self, + _msg: L, + _signer: &::AccountId, + ) -> bool { + false + } +} + pub struct AllianceIdentityVerifier; impl IdentityVerifier for AllianceIdentityVerifier { fn has_required_identities(who: &AccountId) -> bool { @@ -135,7 +164,7 @@ impl IdentityVerifier 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() diff --git a/substrate/frame/identity/Cargo.toml b/substrate/frame/identity/Cargo.toml index 78f966c0535e..1197a37ecae3 100644 --- a/substrate/frame/identity/Cargo.toml +++ b/substrate/frame/identity/Cargo.toml @@ -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", ] diff --git a/substrate/frame/identity/src/benchmarking.rs b/substrate/frame/identity/src/benchmarking.rs index 3d976bd6c881..fe2fb0b04893 100644 --- a/substrate/frame/identity/src/benchmarking.rs +++ b/substrate/frame/identity/src/benchmarking.rs @@ -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(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_has_event(generic_event.into()); +} + fn assert_last_event(generic_event: ::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } +fn run_to_block(n: frame_system::pallet_prelude::BlockNumberFor) { + while frame_system::Pallet::::block_number() < n { + crate::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + frame_system::Pallet::::on_finalize(frame_system::Pallet::::block_number()); + frame_system::Pallet::::set_block_number( + frame_system::Pallet::::block_number() + One::one(), + ); + frame_system::Pallet::::on_initialize(frame_system::Pallet::::block_number()); + crate::Pallet::::on_initialize(frame_system::Pallet::::block_number()); + } +} + // Adds `r` registrars to the Identity Pallet. These registrars will have set fees and fields. fn add_registrars(r: u32) -> Result<(), &'static str> { for i in 0..r { @@ -95,7 +116,28 @@ fn add_sub_accounts( Ok(subs) } -#[benchmarks] +fn bench_suffix() -> Vec { + b"bench".to_vec() +} + +fn bench_username() -> Vec { + // len = 24 + b"veryfastbenchmarkmachine".to_vec() +} + +fn bounded_username(username: Vec, suffix: Vec) -> Username { + 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::::try_from(full_username).expect("test usernames should fit within bounds") +} + +#[benchmarks( + where + ::AccountId: From, + T::OffchainSignature: From, +)] 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::(Event::::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::::add_username_authority( + origin.clone(), + authority_lookup.clone(), + suffix, + allocation + )); + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, authority_lookup); + + assert_last_event::(Event::::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::::add_username_authority( + auth_origin, + authority_lookup, + suffix.clone(), + allocation, + )?; + + let username = bench_username(); + let bounded_username = bounded_username::(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::( + Event::::UsernameSet { + who: who_account.clone(), + username: bounded_username.clone(), + } + .into(), + ); + assert_has_event::( + Event::::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::(bench_username(), bench_suffix()); + + Identity::::queue_acceptance(&caller, username.clone()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), username.clone()); + + assert_last_event::(Event::::UsernameSet { who: caller, username }.into()); + Ok(()) + } + + #[benchmark] + fn remove_expired_approval() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + let username = bounded_username::(bench_username(), bench_suffix()); + Identity::::queue_acceptance(&caller, username.clone()); + + let expected_exiration = + frame_system::Pallet::::block_number() + T::PendingUsernameExpiration::get(); + + run_to_block::(expected_exiration + One::one()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), username); + + assert_last_event::(Event::::PreapprovalExpired { whose: caller }.into()); + Ok(()) + } + + #[benchmark] + fn set_primary_username() -> Result<(), BenchmarkError> { + let caller: T::AccountId = whitelisted_caller(); + let first_username = bounded_username::(bench_username(), bench_suffix()); + let second_username = bounded_username::(b"slowbenchmark".to_vec(), bench_suffix()); + + // First one will be set as primary. Second will not be. + Identity::::insert_username(&caller, first_username); + Identity::::insert_username(&caller, second_username.clone()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), second_username.clone()); + + assert_last_event::( + Event::::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::(bench_username(), bench_suffix()); + let second_username = bounded_username::(b"slowbenchmark".to_vec(), bench_suffix()); + + // First one will be set as primary. Second will not be. + Identity::::insert_username(&caller, first_username); + Identity::::insert_username(&caller, second_username.clone()); + + // User calls `clear_identity`, leaving their second username as "dangling" + Identity::::clear_identity(RawOrigin::Signed(caller.clone()).into())?; + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), second_username.clone()); + + assert_last_event::( + Event::::DanglingUsernameRemoved { who: caller, username: second_username }.into(), + ); + Ok(()) + } + impl_benchmark_test_suite!(Identity, crate::tests::new_test_ext(), crate::tests::Test); } diff --git a/substrate/frame/identity/src/legacy.rs b/substrate/frame/identity/src/legacy.rs index a7953f7e1786..60e812c2238b 100644 --- a/substrate/frame/identity/src/legacy.rs +++ b/substrate/frame/identity/src/legacy.rs @@ -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> { /// Additional fields of the identity that are not catered for with the struct's explicit @@ -155,6 +154,22 @@ impl + 'static> IdentityInformationProvider for IdentityInf } } +impl> Default for IdentityInfo { + 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> IdentityInfo { pub(crate) fn fields(&self) -> BitFlags { let mut res = >::empty(); diff --git a/substrate/frame/identity/src/lib.rs b/substrate/frame/identity/src/lib.rs index 8588612cd5bc..1df0a619ea40 100644 --- a/substrate/frame/identity/src/lib.rs +++ b/substrate/frame/identity/src/lib.rs @@ -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 = <::Currency as Currency<::AccountId>>::Balance; @@ -115,7 +140,7 @@ pub mod pallet { /// The currency trait. type Currency: ReservableCurrency; - /// The amount held on deposit for a registered identity + /// The amount held on deposit for a registered identity. #[pallet::constant] type BasicDeposit: Get>; @@ -150,14 +175,41 @@ pub mod pallet { /// The origin which may add or remove registrars. Root can always do this. type RegistrarOrigin: EnsureOrigin; + /// Signature type for pre-authorizing usernames off-chain. + /// + /// Can verify whether an `Self::SigningPublicKey` created a signature. + type OffchainSignature: Verify + Parameter; + + /// Public key that corresponds to an on-chain `Self::AccountId`. + type SigningPublicKey: IdentifyAccount; + + /// The origin which may add or remove username authorities. Root can always do this. + type UsernameAuthorityOrigin: EnsureOrigin; + + /// The number of blocks within which a username grant must be accepted. + #[pallet::constant] + type PendingUsernameExpiration: Get>; + + /// The maximum length of a suffix. + #[pallet::constant] + type MaxSuffixLength: Get; + + /// The maximum length of a username, including its suffix and any system-added delimiters. + #[pallet::constant] + type MaxUsernameLength: Get; + /// 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(_); - /// 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, T::MaxRegistrars, T::IdentityInformation>, + (Registration, T::MaxRegistrars, T::IdentityInformation>, Option>), 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 = + StorageMap<_, Twox64Concat, T::AccountId, AuthorityPropertiesOf, 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 = + StorageMap<_, Blake2_128Concat, Username, 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 = StorageMap< + _, + Blake2_128Concat, + Username, + (T::AccountId, BlockNumberFor), + OptionQuery, + >; + #[pallet::error] pub enum Error { /// 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 }, + /// 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 }, + /// A username was queued, but `who` must accept it prior to `expiration`. + UsernameQueued { who: T::AccountId, username: Username, expiration: BlockNumberFor }, + /// 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 }, + /// 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 }, } #[pallet::call] @@ -331,36 +448,34 @@ pub mod pallet { info: Box, ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; - let encoded_byte_size = info.encoded_size() as u32; - let byte_deposit = - T::ByteDeposit::get().saturating_mul(>::from(encoded_byte_size)); - - let mut id = match >::get(&sender) { - Some(mut id) => { - // Only keep non-positive judgements. - id.judgements.retain(|j| j.1.is_sticky()); - id.info = *info; - id - }, - None => Registration { - info: *info, - judgements: BoundedVec::default(), - deposit: Zero::zero(), - }, + + let (mut id, username) = match >::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(); - >::insert(&sender, id); + >::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) = >::take(&sender); - let id = >::take(&sender).ok_or(Error::::NotNamed)?; + let (id, maybe_username) = + >::take(&sender).ok_or(Error::::NoIdentity)?; let deposit = id.total_deposit().saturating_add(subs_deposit); for sub in sub_ids.iter() { >::remove(sub); } + if let Some(username) = maybe_username { + AccountOfUsername::::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::::EmptyIndex)?; ensure!(max_fee >= registrar.fee, Error::::FeeChanged); - let mut id = >::get(&sender).ok_or(Error::::NoIdentity)?; + let (mut id, username) = >::get(&sender).ok_or(Error::::NoIdentity)?; let item = (reg_index, Judgement::FeePaid(registrar.fee)); match id.judgements.binary_search_by_key(®_index, |x| x.0) { @@ -518,7 +637,7 @@ pub mod pallet { T::Currency::reserve(&sender, registrar.fee)?; let judgements = id.judgements.len(); - >::insert(&sender, id); + >::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 = >::get(&sender).ok_or(Error::::NoIdentity)?; + let (mut id, username) = >::get(&sender).ok_or(Error::::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(); - >::insert(&sender, id); + >::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::::InvalidIndex)?; - let mut id = >::get(&target).ok_or(Error::::InvalidTarget)?; + let (mut id, username) = + >::get(&target).ok_or(Error::::InvalidTarget)?; if T::Hashing::hash_of(&id.info) != identity { return Err(Error::::JudgementForDifferentIdentity.into()) @@ -724,7 +846,7 @@ pub mod pallet { } let judgements = id.judgements.len(); - >::insert(&target, id); + >::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) = >::take(&target); - let id = >::take(&target).ok_or(Error::::NotNamed)?; + let (id, maybe_username) = + >::take(&target).ok_or(Error::::NoIdentity)?; let deposit = id.total_deposit().saturating_add(subs_deposit); for sub in sub_ids.iter() { >::remove(sub); } + if let Some(username) = maybe_username { + AccountOfUsername::::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, + authority: AccountIdLookupOf, + suffix: Vec, + 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::::InvalidSuffix)?; + let suffix = Suffix::::try_from(suffix).map_err(|_| Error::::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::::insert( + &authority, + AuthorityPropertiesOf:: { 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, + authority: AccountIdLookupOf, + ) -> DispatchResult { + T::UsernameAuthorityOrigin::ensure_origin(origin)?; + let authority = T::Lookup::lookup(authority)?; + UsernameAuthorities::::take(&authority).ok_or(Error::::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, + who: AccountIdLookupOf, + username: Vec, + signature: Option, + ) -> DispatchResult { + // Ensure origin is a Username Authority and has an allocation. Decrement their + // allocation by one. + let sender = ensure_signed(origin)?; + let suffix = UsernameAuthorities::::try_mutate( + &sender, + |maybe_authority| -> Result, DispatchError> { + let properties = + maybe_authority.as_mut().ok_or(Error::::NotUsernameAuthority)?; + ensure!(properties.allocation > 0, Error::::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::::try_from(full_username).map_err(|_| Error::::InvalidUsername)?; + + // Usernames must be unique. Ensure it's not taken. + ensure!( + !AccountOfUsername::::contains_key(&bounded_username), + Error::::UsernameTaken + ); + ensure!( + !PendingUsernames::::contains_key(&bounded_username), + Error::::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, + username: Username, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let (approved_for, _) = + PendingUsernames::::take(&username).ok_or(Error::::NoUsername)?; + ensure!(approved_for == who.clone(), Error::::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, + username: Username, + ) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + if let Some((who, expiration)) = PendingUsernames::::take(&username) { + let now = frame_system::Pallet::::block_number(); + ensure!(now > expiration, Error::::NotExpired); + Self::deposit_event(Event::PreapprovalExpired { whose: who.clone() }); + Ok(Pays::No.into()) + } else { + Err(Error::::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, username: Username) -> DispatchResult { + // ensure `username` maps to `origin` (i.e. has already been set by an authority). + let who = ensure_signed(origin)?; + ensure!(AccountOfUsername::::contains_key(&username), Error::::NoUsername); + let (registration, _maybe_username) = + IdentityOf::::get(&who).ok_or(Error::::NoIdentity)?; + IdentityOf::::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, + username: Username, + ) -> DispatchResultWithPostInfo { + // ensure `username` maps to `origin` (i.e. has already been set by an authority). + let _ = ensure_signed(origin)?; + let who = AccountOfUsername::::take(&username).ok_or(Error::::NoUsername)?; + ensure!(!IdentityOf::::contains_key(&who), Error::::InvalidUsername); + Self::deposit_event(Event::DanglingUsernameRemoved { who: who.clone(), username }); + Ok(Pays::No.into()) + } } } @@ -925,7 +1231,104 @@ impl Pallet { fields: ::FieldsIdentifier, ) -> bool { IdentityOf::::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 { + let bytes = info.encoded_size() as u32; + let byte_deposit = T::ByteDeposit::get().saturating_mul(>::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, length: Option) -> 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::::InvalidUsername); + } + // Usernames cannot be empty. + ensure!(!username.is_empty(), Error::::InvalidUsername); + // Username must be lowercase and alphanumeric. + ensure!( + username.iter().all(|byte| byte.is_ascii_digit() || byte.is_ascii_lowercase()), + Error::::InvalidUsername + ); + Ok(()) + } + + /// Validate a signature. Supports signatures on raw `data` or `data` wrapped in HTML ``. + pub fn validate_signature( + data: &Vec, + 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 + // ` + data + `, so why we support both wrapped and raw versions. + let prefix = b""; + let suffix = b""; + let mut wrapped: Vec = Vec::with_capacity(data.len() + prefix.len() + suffix.len()); + wrapped.extend(prefix); + wrapped.extend(data); + wrapped.extend(suffix); + + ensure!(signature.verify(&wrapped[..], &signer), Error::::InvalidSignature); + + Ok(()) + } + + /// A username has met all conditions. Insert the relevant storage items. + pub fn insert_username(who: &T::AccountId, username: Username) { + // 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 >::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::::insert(&who, (reg, Some(primary_username))); + // Enter in username map. + AccountOfUsername::::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) { + let now = frame_system::Pallet::::block_number(); + let expiration = now.saturating_add(T::PendingUsernameExpiration::get()); + PendingUsernames::::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 Pallet { pub fn reap_identity(who: &T::AccountId) -> Result<(u32, u32, u32), DispatchError> { // `take` any storage items keyed by `target` // identity - let id = >::take(&who).ok_or(Error::::NotNamed)?; + let (id, _maybe_username) = >::take(&who).ok_or(Error::::NoIdentity)?; let registrars = id.judgements.len() as u32; let encoded_byte_size = id.info.encoded_size() as u32; @@ -976,8 +1379,8 @@ impl Pallet { // Identity Deposit let new_id_deposit = IdentityOf::::try_mutate( &target, - |registration| -> Result, DispatchError> { - let reg = registration.as_mut().ok_or(Error::::NoIdentity)?; + |identity_of| -> Result, DispatchError> { + let (reg, _) = identity_of.as_mut().ok_or(Error::::NoIdentity)?; // Calculate what deposit should be let encoded_byte_size = reg.info.encoded_size() as u32; let byte_deposit = @@ -1014,11 +1417,14 @@ impl Pallet { ) -> DispatchResult { IdentityOf::::insert( &who, - Registration { - judgements: Default::default(), - deposit: Zero::zero(), - info: info.clone(), - }, + ( + Registration { + judgements: Default::default(), + deposit: Zero::zero(), + info: info.clone(), + }, + None::>, + ), ); Ok(()) } @@ -1030,7 +1436,6 @@ impl Pallet { who: &T::AccountId, subs: Vec<(T::AccountId, Data)>, ) -> DispatchResult { - use frame_support::BoundedVec; let mut sub_accounts = BoundedVec::::default(); for (sub, name) in subs { >::insert(&sub, (who.clone(), name)); diff --git a/substrate/frame/identity/src/migration.rs b/substrate/frame/identity/src/migration.rs new file mode 100644 index 000000000000..88ac08d1bf56 --- /dev/null +++ b/substrate/frame/identity/src/migration.rs @@ -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 = VersionedMigration< + 0, + 1, + v1::VersionUncheckedMigrateV0ToV1, + crate::pallet::Pallet, + ::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 = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + Registration< + BalanceOf, + ::MaxRegistrars, + ::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(PhantomData); + impl OnRuntimeUpgrade for VersionUncheckedMigrateV0ToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let identities = v0::IdentityOf::::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::::iter() { + IdentityOf::::insert(account, (registration, None::>)); + 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) -> Result<(), TryRuntimeError> { + let identities_to_migrate: u64 = Decode::decode(&mut &state[..]) + .expect("failed to decode the state from pre-upgrade."); + let identities = IdentityOf::::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(()) + } + } +} diff --git a/substrate/frame/identity/src/tests.rs b/substrate/frame/identity/src/tests.rs index 8ac7b4d66cb6..4da317822611 100644 --- a/substrate/frame/identity/src/tests.rs +++ b/substrate/frame/identity/src/tests.rs @@ -25,17 +25,23 @@ use crate::{ use codec::{Decode, Encode}; use frame_support::{ - assert_noop, assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, EitherOfDiverse, Get}, + assert_noop, assert_ok, derive_impl, parameter_types, + traits::{ConstU32, ConstU64, Get, OnFinalize, OnInitialize}, BoundedVec, }; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_system::EnsureRoot; use sp_core::H256; +use sp_io::crypto::{sr25519_generate, sr25519_sign}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{BadOrigin, BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, MultiSigner, }; +type AccountIdOf = ::AccountId; +pub type AccountPublic = ::Signer; +pub type AccountId = ::AccountId; + type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( @@ -57,7 +63,7 @@ impl frame_system::Config for Test { type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; type RuntimeEvent = RuntimeEvent; @@ -96,12 +102,6 @@ parameter_types! { pub const MaxRegistrars: u32 = 20; } -ord_parameter_types! { - pub const One: u64 = 1; - pub const Two: u64 = 2; -} -type EnsureOneOrRoot = EitherOfDiverse, EnsureSignedBy>; -type EnsureTwoOrRoot = EitherOfDiverse, EnsureSignedBy>; impl pallet_identity::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -112,22 +112,102 @@ impl pallet_identity::Config for Test { type MaxSubAccounts = ConstU32<2>; type IdentityInformation = IdentityInfo; type MaxRegistrars = MaxRegistrars; - type RegistrarOrigin = EnsureOneOrRoot; - type ForceOrigin = EnsureTwoOrRoot; + type RegistrarOrigin = EnsureRoot; + type ForceOrigin = EnsureRoot; + type OffchainSignature = MultiSignature; + type SigningPublicKey = AccountPublic; + type UsernameAuthorityOrigin = EnsureRoot; + type PendingUsernameExpiration = ConstU64<100>; + type MaxSuffixLength = ConstU32<7>; + type MaxUsernameLength = ConstU32<32>; type WeightInfo = (); } pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 100), (2, 100), (3, 100), (10, 1000), (20, 1000), (30, 1000)], + balances: vec![ + (account(1), 100), + (account(2), 100), + (account(3), 100), + (account(10), 1000), + (account(20), 1000), + (account(30), 1000), + ], } .assimilate_storage(&mut t) .unwrap(); - t.into() + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn run_to_block(n: u64) { + while System::block_number() < n { + Identity::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + Identity::on_initialize(System::block_number()); + } +} + +fn account(id: u8) -> AccountIdOf { + [id; 32].into() +} + +fn account_from_u32(id: u32) -> AccountIdOf { + let mut buffer = [255u8; 32]; + let id_bytes = id.to_le_bytes(); + let id_size = id_bytes.len(); + for ii in 0..buffer.len() / id_size { + let s = ii * id_size; + let e = s + id_size; + buffer[s..e].clone_from_slice(&id_bytes[..]); + } + buffer.into() +} + +fn accounts() -> [AccountIdOf; 8] { + [ + account(1), + account(2), + account(3), + account(4), // unfunded + account(10), + account(20), + account(30), + account(40), // unfunded + ] +} + +fn unfunded_accounts() -> [AccountIdOf; 2] { + [account(100), account(101)] +} + +// First return value is a username that would be submitted as a parameter to the dispatchable. As +// in, it has no suffix attached. Second is a full BoundedVec username with suffix, which is what a +// user would need to sign. +fn test_username_of(int: Vec, suffix: Vec) -> (Vec, Username) { + let base = b"testusername"; + let mut username = Vec::with_capacity(base.len() + int.len()); + username.extend(base); + username.extend(int); + + let mut bounded_username = Vec::with_capacity(username.len() + suffix.len() + 1); + bounded_username.extend(username.clone()); + bounded_username.extend(b"."); + bounded_username.extend(suffix); + let bounded_username = Username::::try_from(bounded_username) + .expect("test usernames should fit within bounds"); + + (username, bounded_username) } -fn ten() -> IdentityInfo { +fn infoof_ten() -> IdentityInfo { IdentityInfo { display: Data::Raw(b"ten".to_vec().try_into().unwrap()), legal: Data::Raw(b"The Right Ordinal Ten, Esq.".to_vec().try_into().unwrap()), @@ -135,7 +215,7 @@ fn ten() -> IdentityInfo { } } -fn twenty() -> IdentityInfo { +fn infoof_twenty() -> IdentityInfo { IdentityInfo { display: Data::Raw(b"twenty".to_vec().try_into().unwrap()), legal: Data::Raw(b"The Right Ordinal Twenty, Esq.".to_vec().try_into().unwrap()), @@ -188,54 +268,58 @@ fn identity_fields_repr_works() { fn editing_subaccounts_should_work() { new_test_ext().execute_with(|| { let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); + let [one, two, three, _, ten, twenty, _, _] = accounts(); assert_noop!( - Identity::add_sub(RuntimeOrigin::signed(10), 20, data(1)), + Identity::add_sub(RuntimeOrigin::signed(ten.clone()), twenty.clone(), data(1)), Error::::NoIdentity ); - let ten = ten(); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - let id_deposit = id_deposit(&ten); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + let id_deposit = id_deposit(&ten_info); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); let sub_deposit: u64 = <::SubAccountDeposit as Get>::get(); // first sub account - assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); - assert_eq!(SuperOf::::get(1), Some((10, data(1)))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - sub_deposit); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(ten.clone()), one.clone(), data(1))); + assert_eq!(SuperOf::::get(one.clone()), Some((ten.clone(), data(1)))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - sub_deposit); // second sub account - assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 2, data(2))); - assert_eq!(SuperOf::::get(1), Some((10, data(1)))); - assert_eq!(SuperOf::::get(2), Some((10, data(2)))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 2 * sub_deposit); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(ten.clone()), two.clone(), data(2))); + assert_eq!(SuperOf::::get(one.clone()), Some((ten.clone(), data(1)))); + assert_eq!(SuperOf::::get(two.clone()), Some((ten.clone(), data(2)))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 2 * sub_deposit); // third sub account is too many assert_noop!( - Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3)), + Identity::add_sub(RuntimeOrigin::signed(ten.clone()), three.clone(), data(3)), Error::::TooManySubAccounts ); // rename first sub account - assert_ok!(Identity::rename_sub(RuntimeOrigin::signed(10), 1, data(11))); - assert_eq!(SuperOf::::get(1), Some((10, data(11)))); - assert_eq!(SuperOf::::get(2), Some((10, data(2)))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 2 * sub_deposit); + assert_ok!(Identity::rename_sub(RuntimeOrigin::signed(ten.clone()), one.clone(), data(11))); + assert_eq!(SuperOf::::get(one.clone()), Some((ten.clone(), data(11)))); + assert_eq!(SuperOf::::get(two.clone()), Some((ten.clone(), data(2)))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 2 * sub_deposit); // remove first sub account - assert_ok!(Identity::remove_sub(RuntimeOrigin::signed(10), 1)); - assert_eq!(SuperOf::::get(1), None); - assert_eq!(SuperOf::::get(2), Some((10, data(2)))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - sub_deposit); + assert_ok!(Identity::remove_sub(RuntimeOrigin::signed(ten.clone()), one.clone())); + assert_eq!(SuperOf::::get(one.clone()), None); + assert_eq!(SuperOf::::get(two.clone()), Some((ten.clone(), data(2)))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - sub_deposit); // add third sub account - assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 3, data(3))); - assert_eq!(SuperOf::::get(1), None); - assert_eq!(SuperOf::::get(2), Some((10, data(2)))); - assert_eq!(SuperOf::::get(3), Some((10, data(3)))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 2 * sub_deposit); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(ten.clone()), three.clone(), data(3))); + assert_eq!(SuperOf::::get(one), None); + assert_eq!(SuperOf::::get(two), Some((ten.clone(), data(2)))); + assert_eq!(SuperOf::::get(three), Some((ten.clone(), data(3)))); + assert_eq!(Balances::free_balance(ten), 1000 - id_deposit - 2 * sub_deposit); }); } @@ -243,35 +327,39 @@ fn editing_subaccounts_should_work() { fn resolving_subaccount_ownership_works() { new_test_ext().execute_with(|| { let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); + let [one, _, _, _, ten, twenty, _, _] = accounts(); let sub_deposit: u64 = <::SubAccountDeposit as Get>::get(); - let ten = ten(); - let ten_deposit = id_deposit(&ten); - let twenty = twenty(); - let twenty_deposit = id_deposit(&twenty); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten))); - assert_eq!(Balances::free_balance(10), 1000 - ten_deposit); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(20), Box::new(twenty))); - assert_eq!(Balances::free_balance(20), 1000 - twenty_deposit); + let ten_info = infoof_ten(); + let ten_deposit = id_deposit(&ten_info); + let twenty_info = infoof_twenty(); + let twenty_deposit = id_deposit(&twenty_info); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(ten.clone()), Box::new(ten_info))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - ten_deposit); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(twenty.clone()), + Box::new(twenty_info) + )); + assert_eq!(Balances::free_balance(twenty.clone()), 1000 - twenty_deposit); // 10 claims 1 as a subaccount - assert_ok!(Identity::add_sub(RuntimeOrigin::signed(10), 1, data(1))); - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::free_balance(10), 1000 - ten_deposit - sub_deposit); - assert_eq!(Balances::reserved_balance(10), ten_deposit + sub_deposit); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(ten.clone()), one.clone(), data(1))); + assert_eq!(Balances::free_balance(one.clone()), 100); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - ten_deposit - sub_deposit); + assert_eq!(Balances::reserved_balance(ten.clone()), ten_deposit + sub_deposit); // 20 cannot claim 1 now assert_noop!( - Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1)), + Identity::add_sub(RuntimeOrigin::signed(twenty.clone()), one.clone(), data(1)), Error::::AlreadyClaimed ); // 1 wants to be with 20 so it quits from 10 - assert_ok!(Identity::quit_sub(RuntimeOrigin::signed(1))); + assert_ok!(Identity::quit_sub(RuntimeOrigin::signed(one.clone()))); // 1 gets the 10 that 10 paid. - assert_eq!(Balances::free_balance(1), 100 + sub_deposit); - assert_eq!(Balances::free_balance(10), 1000 - ten_deposit - sub_deposit); - assert_eq!(Balances::reserved_balance(10), ten_deposit); + assert_eq!(Balances::free_balance(one.clone()), 100 + sub_deposit); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - ten_deposit - sub_deposit); + assert_eq!(Balances::reserved_balance(ten), ten_deposit); // 20 can claim 1 now - assert_ok!(Identity::add_sub(RuntimeOrigin::signed(20), 1, data(1))); + assert_ok!(Identity::add_sub(RuntimeOrigin::signed(twenty), one, data(1))); }); } @@ -288,11 +376,12 @@ fn trailing_zeros_decodes_into_default_data() { #[test] fn adding_registrar_invalid_index() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let [_, _, three, _, _, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); let fields = IdentityField::Display | IdentityField::Legal; assert_noop!( - Identity::set_fields(RuntimeOrigin::signed(3), 100, fields.bits()), + Identity::set_fields(RuntimeOrigin::signed(three), 100, fields.bits()), Error::::InvalidIndex ); }); @@ -301,13 +390,14 @@ fn adding_registrar_invalid_index() { #[test] fn adding_registrar_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let [_, _, three, _, _, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); let fields = IdentityField::Display | IdentityField::Legal; - assert_ok!(Identity::set_fields(RuntimeOrigin::signed(3), 0, fields.bits())); + assert_ok!(Identity::set_fields(RuntimeOrigin::signed(three.clone()), 0, fields.bits())); assert_eq!( Identity::registrars(), - vec![Some(RegistrarInfo { account: 3, fee: 10, fields: fields.bits() })] + vec![Some(RegistrarInfo { account: three, fee: 10, fields: fields.bits() })] ); }); } @@ -315,12 +405,12 @@ fn adding_registrar_should_work() { #[test] fn amount_of_registrars_is_limited() { new_test_ext().execute_with(|| { - for i in 1..MaxRegistrars::get() + 1 { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), i as u64)); + for ii in 1..MaxRegistrars::get() + 1 { + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), account_from_u32(ii))); } - let last_registrar = MaxRegistrars::get() as u64 + 1; + let last_registrar = MaxRegistrars::get() + 1; assert_noop!( - Identity::add_registrar(RuntimeOrigin::signed(1), last_registrar), + Identity::add_registrar(RuntimeOrigin::root(), account_from_u32(last_registrar)), Error::::TooManyRegistrars ); }); @@ -329,68 +419,79 @@ fn amount_of_registrars_is_limited() { #[test] fn registration_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); - let mut three_fields = ten(); + let [_, _, three, _, ten, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); + let mut three_fields = infoof_ten(); three_fields.additional.try_push(Default::default()).unwrap(); three_fields.additional.try_push(Default::default()).unwrap(); assert!(three_fields.additional.try_push(Default::default()).is_err()); - let ten = ten(); - let id_deposit = id_deposit(&ten); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - assert_eq!(Identity::identity(10).unwrap().info, ten); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); - assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); - assert_eq!(Balances::free_balance(10), 1000); - assert_noop!(Identity::clear_identity(RuntimeOrigin::signed(10)), Error::::NotNamed); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + assert_eq!(Identity::identity(ten.clone()).unwrap().0.info, ten_info); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(ten.clone()))); + assert_eq!(Balances::free_balance(ten.clone()), 1000); + assert_noop!( + Identity::clear_identity(RuntimeOrigin::signed(ten)), + Error::::NoIdentity + ); }); } #[test] fn uninvited_judgement_should_work() { new_test_ext().execute_with(|| { + let [_, _, three, _, ten, _, _, _] = accounts(); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, H256::random() ), Error::::InvalidIndex ); - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, H256::random() ), Error::::InvalidTarget ); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(infoof_ten()) + )); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, H256::random() ), Error::::JudgementForDifferentIdentity ); - let identity_hash = BlakeTwo256::hash_of(&ten()); + let identity_hash = BlakeTwo256::hash_of(&infoof_ten()); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(ten.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, identity_hash ), @@ -398,9 +499,9 @@ fn uninvited_judgement_should_work() { ); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::FeePaid(1), identity_hash ), @@ -408,46 +509,51 @@ fn uninvited_judgement_should_work() { ); assert_ok!(Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, identity_hash )); - assert_eq!(Identity::identity(10).unwrap().judgements, vec![(0, Judgement::Reasonable)]); + assert_eq!(Identity::identity(ten).unwrap().0.judgements, vec![(0, Judgement::Reasonable)]); }); } #[test] fn clearing_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); + let [_, _, three, _, ten, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(infoof_ten()) + )); assert_ok!(Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Reasonable, - BlakeTwo256::hash_of(&ten()) + BlakeTwo256::hash_of(&infoof_ten()) )); - assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); - assert_eq!(Identity::identity(10), None); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(ten.clone()))); + assert_eq!(Identity::identity(ten), None); }); } #[test] fn killing_slashing_should_work() { new_test_ext().execute_with(|| { - let ten = ten(); - let id_deposit = id_deposit(&ten); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten))); - assert_noop!(Identity::kill_identity(RuntimeOrigin::signed(1), 10), BadOrigin); - assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); - assert_eq!(Identity::identity(10), None); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); + let [one, _, _, _, ten, _, _, _] = accounts(); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(ten.clone()), Box::new(ten_info))); + assert_noop!(Identity::kill_identity(RuntimeOrigin::signed(one), ten.clone()), BadOrigin); + assert_ok!(Identity::kill_identity(RuntimeOrigin::root(), ten.clone())); + assert_eq!(Identity::identity(ten.clone()), None); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); assert_noop!( - Identity::kill_identity(RuntimeOrigin::signed(2), 10), - Error::::NotNamed + Identity::kill_identity(RuntimeOrigin::root(), ten), + Error::::NoIdentity ); }); } @@ -455,50 +561,75 @@ fn killing_slashing_should_work() { #[test] fn setting_subaccounts_should_work() { new_test_ext().execute_with(|| { - let ten = ten(); - let id_deposit = id_deposit(&ten); + let [_, _, _, _, ten, twenty, thirty, forty] = accounts(); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); let sub_deposit: u64 = <::SubAccountDeposit as Get>::get(); - let mut subs = vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))]; + let mut subs = vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))]; assert_noop!( - Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), + Identity::set_subs(RuntimeOrigin::signed(ten.clone()), subs.clone()), Error::::NotFound ); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); - assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - sub_deposit); - assert_eq!(Identity::subs_of(10), (sub_deposit, vec![20].try_into().unwrap())); - assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1].try_into().unwrap())))); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(ten.clone()), Box::new(ten_info))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(ten.clone()), subs.clone())); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - sub_deposit); + assert_eq!( + Identity::subs_of(ten.clone()), + (sub_deposit, vec![twenty.clone()].try_into().unwrap()) + ); + assert_eq!( + Identity::super_of(twenty.clone()), + Some((ten.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))) + ); // push another item and re-set it. - subs.push((30, Data::Raw(vec![50; 1].try_into().unwrap()))); - assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 2 * sub_deposit); - assert_eq!(Identity::subs_of(10), (2 * sub_deposit, vec![20, 30].try_into().unwrap())); - assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1].try_into().unwrap())))); - assert_eq!(Identity::super_of(30), Some((10, Data::Raw(vec![50; 1].try_into().unwrap())))); + subs.push((thirty.clone(), Data::Raw(vec![50; 1].try_into().unwrap()))); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(ten.clone()), subs.clone())); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 2 * sub_deposit); + assert_eq!( + Identity::subs_of(ten.clone()), + (2 * sub_deposit, vec![twenty.clone(), thirty.clone()].try_into().unwrap()) + ); + assert_eq!( + Identity::super_of(twenty.clone()), + Some((ten.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))) + ); + assert_eq!( + Identity::super_of(thirty.clone()), + Some((ten.clone(), Data::Raw(vec![50; 1].try_into().unwrap()))) + ); // switch out one of the items and re-set. - subs[0] = (40, Data::Raw(vec![60; 1].try_into().unwrap())); - assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), subs.clone())); + subs[0] = (forty.clone(), Data::Raw(vec![60; 1].try_into().unwrap())); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(ten.clone()), subs.clone())); // no change in the balance - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 2 * sub_deposit); - assert_eq!(Identity::subs_of(10), (2 * sub_deposit, vec![40, 30].try_into().unwrap())); - assert_eq!(Identity::super_of(20), None); - assert_eq!(Identity::super_of(30), Some((10, Data::Raw(vec![50; 1].try_into().unwrap())))); - assert_eq!(Identity::super_of(40), Some((10, Data::Raw(vec![60; 1].try_into().unwrap())))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 2 * sub_deposit); + assert_eq!( + Identity::subs_of(ten.clone()), + (2 * sub_deposit, vec![forty.clone(), thirty.clone()].try_into().unwrap()) + ); + assert_eq!(Identity::super_of(twenty.clone()), None); + assert_eq!( + Identity::super_of(thirty.clone()), + Some((ten.clone(), Data::Raw(vec![50; 1].try_into().unwrap()))) + ); + assert_eq!( + Identity::super_of(forty.clone()), + Some((ten.clone(), Data::Raw(vec![60; 1].try_into().unwrap()))) + ); // clear - assert_ok!(Identity::set_subs(RuntimeOrigin::signed(10), vec![])); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); - assert_eq!(Identity::subs_of(10), (0, BoundedVec::default())); - assert_eq!(Identity::super_of(30), None); - assert_eq!(Identity::super_of(40), None); + assert_ok!(Identity::set_subs(RuntimeOrigin::signed(ten.clone()), vec![])); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); + assert_eq!(Identity::subs_of(ten.clone()), (0, BoundedVec::default())); + assert_eq!(Identity::super_of(thirty.clone()), None); + assert_eq!(Identity::super_of(forty), None); - subs.push((20, Data::Raw(vec![40; 1].try_into().unwrap()))); + subs.push((twenty, Data::Raw(vec![40; 1].try_into().unwrap()))); assert_noop!( - Identity::set_subs(RuntimeOrigin::signed(10), subs.clone()), + Identity::set_subs(RuntimeOrigin::signed(ten), subs.clone()), Error::::TooManySubAccounts ); }); @@ -507,67 +638,76 @@ fn setting_subaccounts_should_work() { #[test] fn clearing_account_should_remove_subaccounts_and_refund() { new_test_ext().execute_with(|| { - let ten = ten(); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit(&ten)); + let [_, _, _, _, ten, twenty, _, _] = accounts(); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit(&ten_info)); assert_ok!(Identity::set_subs( - RuntimeOrigin::signed(10), - vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] + RuntimeOrigin::signed(ten.clone()), + vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))] )); - assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(10))); - assert_eq!(Balances::free_balance(10), 1000); - assert!(Identity::super_of(20).is_none()); + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(ten.clone()))); + assert_eq!(Balances::free_balance(ten), 1000); + assert!(Identity::super_of(twenty).is_none()); }); } #[test] fn killing_account_should_remove_subaccounts_and_not_refund() { new_test_ext().execute_with(|| { - let ten = ten(); - let id_deposit = id_deposit(&ten); + let [_, _, _, _, ten, twenty, _, _] = accounts(); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); let sub_deposit: u64 = <::SubAccountDeposit as Get>::get(); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(ten.clone()), Box::new(ten_info))); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); assert_ok!(Identity::set_subs( - RuntimeOrigin::signed(10), - vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] + RuntimeOrigin::signed(ten.clone()), + vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))] )); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - sub_deposit); - assert_ok!(Identity::kill_identity(RuntimeOrigin::signed(2), 10)); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - sub_deposit); - assert!(Identity::super_of(20).is_none()); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - sub_deposit); + assert_ok!(Identity::kill_identity(RuntimeOrigin::root(), ten.clone())); + assert_eq!(Balances::free_balance(ten), 1000 - id_deposit - sub_deposit); + assert!(Identity::super_of(twenty).is_none()); }); } #[test] fn cancelling_requested_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let [_, _, three, _, ten, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); assert_noop!( - Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Identity::cancel_request(RuntimeOrigin::signed(ten.clone()), 0), Error::::NoIdentity ); - let ten = ten(); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit(&ten)); - assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); - assert_ok!(Identity::cancel_request(RuntimeOrigin::signed(10), 0)); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit(&ten)); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit(&ten_info)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10)); + assert_ok!(Identity::cancel_request(RuntimeOrigin::signed(ten.clone()), 0)); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit(&ten_info)); assert_noop!( - Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Identity::cancel_request(RuntimeOrigin::signed(ten.clone()), 0), Error::::NotFound ); assert_ok!(Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three), 0, - 10, + ten.clone(), Judgement::Reasonable, - BlakeTwo256::hash_of(&ten) + BlakeTwo256::hash_of(&ten_info) )); assert_noop!( - Identity::cancel_request(RuntimeOrigin::signed(10), 0), + Identity::cancel_request(RuntimeOrigin::signed(ten), 0), Error::::JudgementGiven ); }); @@ -576,79 +716,87 @@ fn cancelling_requested_judgement_should_work() { #[test] fn requesting_judgement_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); - let ten = ten(); - let id_deposit = id_deposit(&ten); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); + let [_, _, three, four, ten, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); assert_noop!( - Identity::request_judgement(RuntimeOrigin::signed(10), 0, 9), + Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 9), Error::::FeeChanged ); - assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10)); // 10 for the judgement request and the deposit for the identity. - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 10); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 10); // Re-requesting won't work as we already paid. assert_noop!( - Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), + Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10), Error::::StickyJudgement ); assert_ok!(Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Erroneous, - BlakeTwo256::hash_of(&ten) + BlakeTwo256::hash_of(&ten_info) )); // Registrar got their payment now. // 100 initial balance and 10 for the judgement. - assert_eq!(Balances::free_balance(3), 100 + 10); + assert_eq!(Balances::free_balance(three.clone()), 100 + 10); // Re-requesting still won't work as it's erroneous. assert_noop!( - Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10), + Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10), Error::::StickyJudgement ); // Requesting from a second registrar still works. - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 4)); - assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 1, 10)); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), four)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 1, 10)); // Re-requesting after the judgement has been reduced works. assert_ok!(Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three), 0, - 10, + ten.clone(), Judgement::OutOfDate, - BlakeTwo256::hash_of(&ten) + BlakeTwo256::hash_of(&ten_info) )); - assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(ten), 0, 10)); }); } #[test] fn provide_judgement_should_return_judgement_payment_failed_error() { new_test_ext().execute_with(|| { - let ten = ten(); - let id_deposit = id_deposit(&ten); - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten.clone()))); - assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(10), 0, 10)); + let [_, _, three, _, ten, _, _, _] = accounts(); + let ten_info = infoof_ten(); + let id_deposit = id_deposit(&ten_info); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three.clone()), 0, 10)); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); + assert_ok!(Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10)); // 10 for the judgement request and the deposit for the identity. - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - 10); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - 10); // This forces judgement payment failed error - Balances::make_free_balance_be(&3, 0); + Balances::make_free_balance_be(&three, 0); assert_noop!( Identity::provide_judgement( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(three.clone()), 0, - 10, + ten.clone(), Judgement::Erroneous, - BlakeTwo256::hash_of(&ten) + BlakeTwo256::hash_of(&ten_info) ), Error::::JudgementPaymentFailed ); @@ -658,8 +806,9 @@ fn provide_judgement_should_return_judgement_payment_failed_error() { #[test] fn field_deposit_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); - assert_ok!(Identity::set_fee(RuntimeOrigin::signed(3), 0, 10)); + let [_, _, three, _, ten, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); + assert_ok!(Identity::set_fee(RuntimeOrigin::signed(three), 0, 10)); let id = IdentityInfo { additional: vec![ ( @@ -676,39 +825,44 @@ fn field_deposit_should_work() { ..Default::default() }; let id_deposit = id_deposit(&id); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(id))); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit); + assert_ok!(Identity::set_identity(RuntimeOrigin::signed(ten.clone()), Box::new(id))); + assert_eq!(Balances::free_balance(ten), 1000 - id_deposit); }); } #[test] fn setting_account_id_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Identity::add_registrar(RuntimeOrigin::signed(1), 3)); + let [_, _, three, four, _, _, _, _] = accounts(); + assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); // account 4 cannot change the first registrar's identity since it's owned by 3. assert_noop!( - Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3), + Identity::set_account_id(RuntimeOrigin::signed(four.clone()), 0, three.clone()), Error::::InvalidIndex ); // account 3 can, because that's the registrar's current account. - assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(3), 0, 4)); + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(three.clone()), 0, four.clone())); // account 4 can now, because that's their new ID. - assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(4), 0, 3)); + assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(four), 0, three)); }); } #[test] fn test_has_identity() { new_test_ext().execute_with(|| { - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten()))); - assert!(Identity::has_identity(&10, IdentityField::Display as u64)); - assert!(Identity::has_identity(&10, IdentityField::Legal as u64)); + let [_, _, _, _, ten, _, _, _] = accounts(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(infoof_ten()) + )); + assert!(Identity::has_identity(&ten, IdentityField::Display as u64)); + assert!(Identity::has_identity(&ten, IdentityField::Legal as u64)); assert!(Identity::has_identity( - &10, + &ten, IdentityField::Display as u64 | IdentityField::Legal as u64 )); assert!(!Identity::has_identity( - &10, + &ten, IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64 )); }); @@ -717,66 +871,759 @@ fn test_has_identity() { #[test] fn reap_identity_works() { new_test_ext().execute_with(|| { - let ten_info = ten(); - assert_ok!(Identity::set_identity(RuntimeOrigin::signed(10), Box::new(ten_info.clone()))); + let [_, _, _, _, ten, twenty, _, _] = accounts(); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(ten.clone()), + Box::new(ten_info.clone()) + )); assert_ok!(Identity::set_subs( - RuntimeOrigin::signed(10), - vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))] + RuntimeOrigin::signed(ten.clone()), + vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))] )); // deposit is correct let id_deposit = id_deposit(&ten_info); let subs_deposit: u64 = <::SubAccountDeposit as Get>::get(); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - subs_deposit); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - subs_deposit); // reap - assert_ok!(Identity::reap_identity(&10)); + assert_ok!(Identity::reap_identity(&ten)); // no identity or subs - assert!(Identity::identity(10).is_none()); - assert!(Identity::super_of(20).is_none()); + assert!(Identity::identity(ten.clone()).is_none()); + assert!(Identity::super_of(twenty).is_none()); // balance is unreserved - assert_eq!(Balances::free_balance(10), 1000); + assert_eq!(Balances::free_balance(ten), 1000); }); } #[test] fn poke_deposit_works() { new_test_ext().execute_with(|| { - let ten_info = ten(); + let [_, _, _, _, ten, twenty, _, _] = accounts(); + let ten_info = infoof_ten(); // Set a custom registration with 0 deposit - IdentityOf::::insert( - &10, - Registration { - judgements: BoundedVec::default(), - deposit: Zero::zero(), - info: ten_info.clone(), - }, - ); - assert!(Identity::identity(10).is_some()); + IdentityOf::::insert::< + _, + ( + Registration>, + Option>, + ), + >( + &ten, + ( + Registration { + judgements: Default::default(), + deposit: Zero::zero(), + info: ten_info.clone(), + }, + None::>, + ), + ); + assert!(Identity::identity(ten.clone()).is_some()); // Set a sub with zero deposit - SubsOf::::insert::<&u64, (u64, BoundedVec>)>( - &10, - (0, vec![20].try_into().unwrap()), + SubsOf::::insert::<_, (u64, BoundedVec, ConstU32<2>>)>( + &ten, + (0, vec![twenty.clone()].try_into().unwrap()), ); - SuperOf::::insert(&20, (&10, Data::Raw(vec![1; 1].try_into().unwrap()))); + SuperOf::::insert(&twenty, (&ten, Data::Raw(vec![1; 1].try_into().unwrap()))); // Balance is free - assert_eq!(Balances::free_balance(10), 1000); + assert_eq!(Balances::free_balance(ten.clone()), 1000); // poke - assert_ok!(Identity::poke_deposit(&10)); + assert_ok!(Identity::poke_deposit(&ten)); // free balance reduced correctly let id_deposit = id_deposit(&ten_info); let subs_deposit: u64 = <::SubAccountDeposit as Get>::get(); - assert_eq!(Balances::free_balance(10), 1000 - id_deposit - subs_deposit); + assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit - subs_deposit); // new registration deposit is 10 assert_eq!( - Identity::identity(&10), - Some(Registration { - judgements: BoundedVec::default(), - deposit: id_deposit, - info: ten() + Identity::identity(&ten), + Some(( + Registration { + judgements: Default::default(), + deposit: id_deposit, + info: infoof_ten() + }, + None + )) + ); + // new subs deposit is 10 vvvvvvvvvvvv + assert_eq!(Identity::subs_of(ten), (subs_deposit, vec![twenty].try_into().unwrap())); + }); +} + +#[test] +fn adding_and_removing_authorities_should_work() { + new_test_ext().execute_with(|| { + let [authority, _] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + + // add + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + assert_eq!( + UsernameAuthorities::::get(&authority), + Some(AuthorityPropertiesOf:: { + suffix: suffix.clone().try_into().unwrap(), + allocation + }) + ); + + // update allocation + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + 11u32 + )); + assert_eq!( + UsernameAuthorities::::get(&authority), + Some(AuthorityPropertiesOf:: { + suffix: suffix.try_into().unwrap(), + allocation: 11 }) ); - // new subs deposit is 10 vvvvvvvvvvvv - assert_eq!(Identity::subs_of(10), (subs_deposit, vec![20].try_into().unwrap())); + + // remove + assert_ok!(Identity::remove_username_authority(RuntimeOrigin::root(), authority.clone(),)); + assert!(UsernameAuthorities::::get(&authority).is_none()); + }); +} + +#[test] +fn set_username_with_signature_without_existing_identity_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, _] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up username + let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix); + let encoded_username = Encode::encode(&username_to_sign.to_vec()); + + // set up user and sign message + let public = sr25519_generate(0.into(), None); + let who_account: AccountIdOf = MultiSigner::Sr25519(public).into_account().into(); + let signature = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap()); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who_account.clone(), + username.clone(), + Some(signature) + )); + + // Even though user has no balance and no identity, they get a default one for free. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(username_to_sign.clone()) + )) + ); + // Lookup from username to account works. + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_to_sign), + Some(who_account) + ); + }); +} + +#[test] +fn set_username_with_signature_with_existing_identity_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, _] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up username + let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix); + let encoded_username = Encode::encode(&username_to_sign.to_vec()); + + // set up user and sign message + let public = sr25519_generate(0.into(), None); + let who_account: AccountIdOf = MultiSigner::Sr25519(public).into_account().into(); + let signature = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap()); + + // Set an identity for who. They need some balance though. + Balances::make_free_balance_be(&who_account, 1000); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(who_account.clone()), + Box::new(ten_info.clone()) + )); + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who_account.clone(), + username.clone(), + Some(signature) + )); + + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: id_deposit(&ten_info), + info: ten_info + }, + Some(username_to_sign.clone()) + )) + ); + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_to_sign), + Some(who_account) + ); + }); +} + +#[test] +fn set_username_with_bytes_signature_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, _] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up user + let public = sr25519_generate(0.into(), None); + let who_account: AccountIdOf = MultiSigner::Sr25519(public).into_account().into(); + + // set up username + let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix); + let unwrapped_username = username_to_sign.to_vec(); + + // Sign an unwrapped version, as in `username.suffix`. + let unwrapped_encoded = Encode::encode(&unwrapped_username); + let signature_on_unwrapped = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &unwrapped_encoded).unwrap()); + + // Trivial + assert_ok!(Identity::validate_signature( + &unwrapped_encoded, + &signature_on_unwrapped, + &who_account + )); + + // Here we are going to wrap the username and suffix in "" and verify that the + // signature verification still works, but only the username gets set in storage. + let prehtml = b""; + let posthtml = b""; + let mut wrapped_username: Vec = + Vec::with_capacity(unwrapped_username.len() + prehtml.len() + posthtml.len()); + wrapped_username.extend(prehtml); + wrapped_username.extend(unwrapped_encoded.clone()); + wrapped_username.extend(posthtml); + let signature_on_wrapped = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &wrapped_username).unwrap()); + + // We want to call `validate_signature` on the *unwrapped* username, but the signature on + // the *wrapped* data. + assert_ok!(Identity::validate_signature( + &unwrapped_encoded, + &signature_on_wrapped, + &who_account + )); + + // Make sure it really works in context. Call `set_username_for` with the signature on the + // wrapped data. + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who_account.clone(), + username, + Some(signature_on_wrapped) + )); + + // The username in storage should not include ``. As in, it's the original + // `username_to_sign`. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(username_to_sign.clone()) + )) + ); + // Likewise for the lookup. + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_to_sign), + Some(who_account) + ); + }); +} + +#[test] +fn set_username_with_acceptance_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, who] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up username + let (username, full_username) = test_username_of(b"101".to_vec(), suffix); + let now = frame_system::Pallet::::block_number(); + let expiration = now + <::PendingUsernameExpiration as Get>::get(); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who.clone(), + username.clone(), + None + )); + + // Should be pending + assert_eq!( + PendingUsernames::::get::<&Username>(&full_username), + Some((who.clone(), expiration)) + ); + + // Now the user can accept + assert_ok!(Identity::accept_username( + RuntimeOrigin::signed(who.clone()), + full_username.clone() + )); + + // No more pending + assert!(PendingUsernames::::get::<&Username>(&full_username).is_none()); + // Check Identity storage + assert_eq!( + Identity::identity(&who), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(full_username.clone()) + )) + ); + // Check reverse lookup + assert_eq!(AccountOfUsername::::get::<&Username>(&full_username), Some(who)); + }); +} + +#[test] +fn invalid_usernames_should_be_rejected() { + new_test_ext().execute_with(|| { + let [authority, who] = unfunded_accounts(); + let allocation: u32 = 10; + let valid_suffix = b"test".to_vec(); + let invalid_suffixes = [ + b"te.st".to_vec(), // not alphanumeric + b"su:ffx".to_vec(), + b"su_ffx".to_vec(), + b"Suffix".to_vec(), // capital + b"suffixes".to_vec(), // too long + ]; + for suffix in invalid_suffixes { + assert_noop!( + Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + ), + Error::::InvalidSuffix + ); + } + + // set a valid one now + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + valid_suffix, + allocation + )); + + // set up usernames + let invalid_usernames = [ + b"TestUsername".to_vec(), + b"test_username".to_vec(), + b"test-username".to_vec(), + b"test:username".to_vec(), + b"test.username".to_vec(), + b"test@username".to_vec(), + b"test$username".to_vec(), + //0 1 2 v With `.test` this makes it too long. + b"testusernametestusernametest".to_vec(), + ]; + for username in invalid_usernames { + assert_noop!( + Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + who.clone(), + username.clone(), + None + ), + Error::::InvalidUsername + ); + } + + // valid one works + let valid_username = b"testusernametestusernametes".to_vec(); + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who, + valid_username, + None + )); + }); +} + +#[test] +fn authorities_should_run_out_of_allocation() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, _] = unfunded_accounts(); + let [pi, e, c, _, _, _, _, _] = accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 2; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + pi, + b"username314159".to_vec(), + None + )); + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + e, + b"username271828".to_vec(), + None + )); + assert_noop!( + Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + c, + b"username299792458".to_vec(), + None + ), + Error::::NoAllocation + ); + }); +} + +#[test] +fn setting_primary_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, _] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up user + let public = sr25519_generate(0.into(), None); + let who_account: AccountIdOf = MultiSigner::Sr25519(public).into_account().into(); + + // set up username + let (first_username, first_to_sign) = test_username_of(b"42".to_vec(), suffix.clone()); + let encoded_username = Encode::encode(&first_to_sign.to_vec()); + let first_signature = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap()); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + who_account.clone(), + first_username.clone(), + Some(first_signature) + )); + + // First username set as primary. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(first_to_sign.clone()) + )) + ); + + // set up username + let (second_username, second_to_sign) = test_username_of(b"101".to_vec(), suffix); + let encoded_username = Encode::encode(&second_to_sign.to_vec()); + let second_signature = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap()); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who_account.clone(), + second_username.clone(), + Some(second_signature) + )); + + // The primary is still the first username. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(first_to_sign.clone()) + )) + ); + + // Lookup from both works. + assert_eq!( + AccountOfUsername::::get::<&Username>(&first_to_sign), + Some(who_account.clone()) + ); + assert_eq!( + AccountOfUsername::::get::<&Username>(&second_to_sign), + Some(who_account.clone()) + ); + + assert_ok!(Identity::set_primary_username( + RuntimeOrigin::signed(who_account.clone()), + second_to_sign.clone() + )); + + // The primary is now the second username. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: 0, + info: Default::default() + }, + Some(second_to_sign.clone()) + )) + ); + + // Lookup from both still works. + assert_eq!( + AccountOfUsername::::get::<&Username>(&first_to_sign), + Some(who_account.clone()) + ); + assert_eq!( + AccountOfUsername::::get::<&Username>(&second_to_sign), + Some(who_account) + ); + }); +} + +#[test] +fn unaccepted_usernames_should_expire() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, who] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up username + let (username, full_username) = test_username_of(b"101".to_vec(), suffix); + let now = frame_system::Pallet::::block_number(); + let expiration = now + <::PendingUsernameExpiration as Get>::get(); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who.clone(), + username.clone(), + None + )); + + // Should be pending + assert_eq!( + PendingUsernames::::get::<&Username>(&full_username), + Some((who.clone(), expiration)) + ); + + run_to_block(now + expiration - 1); + + // Cannot be removed + assert_noop!( + Identity::remove_expired_approval( + RuntimeOrigin::signed(account(1)), + full_username.clone() + ), + Error::::NotExpired + ); + + run_to_block(now + expiration); + + // Anyone can remove + assert_ok!(Identity::remove_expired_approval( + RuntimeOrigin::signed(account(1)), + full_username.clone() + )); + + // No more pending + assert!(PendingUsernames::::get::<&Username>(&full_username).is_none()); + }); +} + +#[test] +fn removing_dangling_usernames_should_work() { + new_test_ext().execute_with(|| { + // set up authority + let [authority, caller] = unfunded_accounts(); + let suffix: Vec = b"test".to_vec(); + let allocation: u32 = 10; + assert_ok!(Identity::add_username_authority( + RuntimeOrigin::root(), + authority.clone(), + suffix.clone(), + allocation + )); + + // set up username + let (username, username_to_sign) = test_username_of(b"42".to_vec(), suffix.clone()); + let encoded_username = Encode::encode(&username_to_sign.to_vec()); + + // set up user and sign message + let public = sr25519_generate(0.into(), None); + let who_account: AccountIdOf = MultiSigner::Sr25519(public).into_account().into(); + let signature = + MultiSignature::Sr25519(sr25519_sign(0.into(), &public, &encoded_username).unwrap()); + + // Set an identity for who. They need some balance though. + Balances::make_free_balance_be(&who_account, 1000); + let ten_info = infoof_ten(); + assert_ok!(Identity::set_identity( + RuntimeOrigin::signed(who_account.clone()), + Box::new(ten_info.clone()) + )); + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority.clone()), + who_account.clone(), + username.clone(), + Some(signature) + )); + + // Now they set up a second username. + let (username_two, username_two_to_sign) = test_username_of(b"43".to_vec(), suffix); + let encoded_username_two = Encode::encode(&username_two_to_sign.to_vec()); + + // set up user and sign message + let signature_two = MultiSignature::Sr25519( + sr25519_sign(0.into(), &public, &encoded_username_two).unwrap(), + ); + + assert_ok!(Identity::set_username_for( + RuntimeOrigin::signed(authority), + who_account.clone(), + username_two.clone(), + Some(signature_two) + )); + + // The primary should still be the first one. + assert_eq!( + Identity::identity(&who_account), + Some(( + Registration { + judgements: Default::default(), + deposit: id_deposit(&ten_info), + info: ten_info + }, + Some(username_to_sign.clone()) + )) + ); + + // But both usernames should look up the account. + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_to_sign), + Some(who_account.clone()) + ); + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_two_to_sign), + Some(who_account.clone()) + ); + + // Someone tries to remove it, but they can't + assert_noop!( + Identity::remove_dangling_username( + RuntimeOrigin::signed(caller.clone()), + username_to_sign.clone() + ), + Error::::InvalidUsername + ); + + // Now the user calls `clear_identity` + assert_ok!(Identity::clear_identity(RuntimeOrigin::signed(who_account.clone()),)); + + // Identity is gone + assert!(Identity::identity(who_account.clone()).is_none()); + + // The reverse lookup of the primary is gone. + assert!(AccountOfUsername::::get::<&Username>(&username_to_sign).is_none()); + + // But the reverse lookup of the non-primary is still there + assert_eq!( + AccountOfUsername::::get::<&Username>(&username_two_to_sign), + Some(who_account) + ); + + // Now it can be removed + assert_ok!(Identity::remove_dangling_username( + RuntimeOrigin::signed(caller), + username_two_to_sign.clone() + )); + + // And the reverse lookup is gone + assert!(AccountOfUsername::::get::<&Username>(&username_two_to_sign).is_none()); }); } diff --git a/substrate/frame/identity/src/types.rs b/substrate/frame/identity/src/types.rs index d3e6bf3973f0..10f0db8c25d8 100644 --- a/substrate/frame/identity/src/types.rs +++ b/substrate/frame/identity/src/types.rs @@ -232,7 +232,7 @@ impl = AuthorityProperties>; + +/// The number of usernames that an authority may allocate. +type Allocation = u32; +/// A byte vec used to represent a username. +pub(crate) type Suffix = BoundedVec::MaxSuffixLength>; + +/// Properties of a username authority. +#[derive(Clone, Encode, Decode, MaxEncodedLen, TypeInfo, PartialEq, Debug)] +pub struct AuthorityProperties { + /// 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 = BoundedVec::MaxUsernameLength>; + #[cfg(test)] mod tests { use super::*; diff --git a/substrate/frame/identity/src/weights.rs b/substrate/frame/identity/src/weights.rs index 95898e6c6cdf..1feb8252c845 100644 --- a/substrate/frame/identity/src/weights.rs +++ b/substrate/frame/identity/src/weights.rs @@ -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 WeightInfo for SubstrateWeight { .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)) + } }