// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Tests for Identity Pallet use super::*; use crate::{ self as pallet_identity, legacy::{IdentityField, IdentityInfo}, }; use codec::{Decode, Encode}; use frame_support::{ assert_noop, assert_ok, derive_impl, parameter_types, traits::{ConstU32, ConstU64, Get, OnFinalize, OnInitialize}, BoundedVec, }; 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, 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!( pub enum Test { System: frame_system, Balances: pallet_balances, Identity: pallet_identity, } ); #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for Test { type AccountId = AccountId; type Lookup = IdentityLookup; type Block = Block; type AccountData = pallet_balances::AccountData; } impl pallet_balances::Config for Test { type Balance = u64; type RuntimeEvent = RuntimeEvent; type DustRemoval = (); type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); type FreezeIdentifier = (); type MaxFreezes = (); type RuntimeHoldReason = (); type RuntimeFreezeReason = (); } parameter_types! { pub const MaxAdditionalFields: u32 = 2; pub const MaxRegistrars: u32 = 20; } impl pallet_identity::Config for Test { type RuntimeEvent = RuntimeEvent; type Currency = Balances; type Slashed = (); type BasicDeposit = ConstU64<100>; type ByteDeposit = ConstU64<10>; type SubAccountDeposit = ConstU64<100>; type MaxSubAccounts = ConstU32<2>; type IdentityInformation = IdentityInfo; type MaxRegistrars = MaxRegistrars; 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![ (account(1), 100), (account(2), 100), (account(3), 100), (account(10), 1000), (account(20), 1000), (account(30), 1000), ], } .assimilate_storage(&mut t) .unwrap(); 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 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()), ..Default::default() } } 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()), ..Default::default() } } fn id_deposit(id: &IdentityInfo) -> u64 { let base_deposit: u64 = <::BasicDeposit as Get>::get(); let byte_deposit: u64 = <::ByteDeposit as Get>::get() * TryInto::::try_into(id.encoded_size()).unwrap(); base_deposit + byte_deposit } #[test] fn identity_fields_repr_works() { // `IdentityField` sanity checks. assert_eq!(IdentityField::Display as u64, 1 << 0); assert_eq!(IdentityField::Legal as u64, 1 << 1); assert_eq!(IdentityField::Web as u64, 1 << 2); assert_eq!(IdentityField::Riot as u64, 1 << 3); assert_eq!(IdentityField::Email as u64, 1 << 4); assert_eq!(IdentityField::PgpFingerprint as u64, 1 << 5); assert_eq!(IdentityField::Image as u64, 1 << 6); assert_eq!(IdentityField::Twitter as u64, 1 << 7); let fields = IdentityField::Legal | IdentityField::Web | IdentityField::Riot | IdentityField::PgpFingerprint | IdentityField::Twitter; assert!(!fields.contains(IdentityField::Display)); assert!(fields.contains(IdentityField::Legal)); assert!(fields.contains(IdentityField::Web)); assert!(fields.contains(IdentityField::Riot)); assert!(!fields.contains(IdentityField::Email)); assert!(fields.contains(IdentityField::PgpFingerprint)); assert!(!fields.contains(IdentityField::Image)); assert!(fields.contains(IdentityField::Twitter)); // Ensure that the `u64` representation matches what we expect. assert_eq!( fields.bits(), 0b00000000_00000000_00000000_00000000_00000000_00000000_00000000_10101110 ); } #[test] 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(ten.clone()), twenty.clone(), data(1)), Error::::NoIdentity ); 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(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(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(ten.clone()), three.clone(), data(3)), Error::::TooManySubAccounts ); // rename first sub account 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(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(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); }); } #[test] 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_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(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(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(one.clone()))); // 1 gets the 10 that 10 paid. 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(twenty), one, data(1))); }); } #[test] fn trailing_zeros_decodes_into_default_data() { let encoded = Data::Raw(b"Hello".to_vec().try_into().unwrap()).encode(); assert!(<(Data, Data)>::decode(&mut &encoded[..]).is_err()); let input = &mut &encoded[..]; let (a, b) = <(Data, Data)>::decode(&mut AppendZerosInput::new(input)).unwrap(); assert_eq!(a, Data::Raw(b"Hello".to_vec().try_into().unwrap())); assert_eq!(b, Data::None); } #[test] fn adding_registrar_invalid_index() { new_test_ext().execute_with(|| { 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(three), 100, fields.bits()), Error::::InvalidIndex ); }); } #[test] fn adding_registrar_should_work() { new_test_ext().execute_with(|| { 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(three.clone()), 0, fields.bits())); assert_eq!( Identity::registrars(), vec![Some(RegistrarInfo { account: three, fee: 10, fields: fields.bits() })] ); }); } #[test] fn amount_of_registrars_is_limited() { new_test_ext().execute_with(|| { for ii in 1..MaxRegistrars::get() + 1 { assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), account_from_u32(ii))); } let last_registrar = MaxRegistrars::get() + 1; assert_noop!( Identity::add_registrar(RuntimeOrigin::root(), account_from_u32(last_registrar)), Error::::TooManyRegistrars ); }); } #[test] fn registration_should_work() { new_test_ext().execute_with(|| { 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_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(three.clone()), 0, ten.clone(), Judgement::Reasonable, H256::random() ), Error::::InvalidIndex ); assert_ok!(Identity::add_registrar(RuntimeOrigin::root(), three.clone())); assert_noop!( Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::Reasonable, H256::random() ), Error::::InvalidTarget ); assert_ok!(Identity::set_identity( RuntimeOrigin::signed(ten.clone()), Box::new(infoof_ten()) )); assert_noop!( Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::Reasonable, H256::random() ), Error::::JudgementForDifferentIdentity ); let identity_hash = BlakeTwo256::hash_of(&infoof_ten()); assert_noop!( Identity::provide_judgement( RuntimeOrigin::signed(ten.clone()), 0, ten.clone(), Judgement::Reasonable, identity_hash ), Error::::InvalidIndex ); assert_noop!( Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::FeePaid(1), identity_hash ), Error::::InvalidJudgement ); assert_ok!(Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::Reasonable, identity_hash )); assert_eq!(Identity::identity(ten).unwrap().0.judgements, vec![(0, Judgement::Reasonable)]); }); } #[test] fn clearing_judgement_should_work() { new_test_ext().execute_with(|| { 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(three.clone()), 0, ten.clone(), Judgement::Reasonable, BlakeTwo256::hash_of(&infoof_ten()) )); 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 [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::root(), ten), Error::::NoIdentity ); }); } #[test] fn setting_subaccounts_should_work() { new_test_ext().execute_with(|| { 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![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))]; assert_noop!( Identity::set_subs(RuntimeOrigin::signed(ten.clone()), subs.clone()), Error::::NotFound ); 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((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] = (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(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(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((twenty, Data::Raw(vec![40; 1].try_into().unwrap()))); assert_noop!( Identity::set_subs(RuntimeOrigin::signed(ten), subs.clone()), Error::::TooManySubAccounts ); }); } #[test] fn clearing_account_should_remove_subaccounts_and_refund() { new_test_ext().execute_with(|| { 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(ten.clone()), vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))] )); 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, 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(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()), vec![(twenty.clone(), Data::Raw(vec![40; 1].try_into().unwrap()))] )); 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(|| { 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(ten.clone()), 0), Error::::NoIdentity ); 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(ten.clone()), 0), Error::::NotFound ); assert_ok!(Identity::provide_judgement( RuntimeOrigin::signed(three), 0, ten.clone(), Judgement::Reasonable, BlakeTwo256::hash_of(&ten_info) )); assert_noop!( Identity::cancel_request(RuntimeOrigin::signed(ten), 0), Error::::JudgementGiven ); }); } #[test] fn requesting_judgement_should_work() { new_test_ext().execute_with(|| { 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(ten.clone()), 0, 9), Error::::FeeChanged ); 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(ten.clone()), 1000 - id_deposit - 10); // Re-requesting won't work as we already paid. assert_noop!( Identity::request_judgement(RuntimeOrigin::signed(ten.clone()), 0, 10), Error::::StickyJudgement ); assert_ok!(Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::Erroneous, BlakeTwo256::hash_of(&ten_info) )); // Registrar got their payment now. // 100 initial balance and 10 for the judgement. 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(ten.clone()), 0, 10), Error::::StickyJudgement ); // Requesting from a second registrar still works. 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(three), 0, ten.clone(), Judgement::OutOfDate, BlakeTwo256::hash_of(&ten_info) )); 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 [_, _, 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(ten.clone()), 1000 - id_deposit - 10); // This forces judgement payment failed error Balances::make_free_balance_be(&three, 0); assert_noop!( Identity::provide_judgement( RuntimeOrigin::signed(three.clone()), 0, ten.clone(), Judgement::Erroneous, BlakeTwo256::hash_of(&ten_info) ), Error::::JudgementPaymentFailed ); }); } #[test] fn field_deposit_should_work() { new_test_ext().execute_with(|| { 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![ ( Data::Raw(b"number".to_vec().try_into().unwrap()), Data::Raw(10u32.encode().try_into().unwrap()), ), ( Data::Raw(b"text".to_vec().try_into().unwrap()), Data::Raw(b"10".to_vec().try_into().unwrap()), ), ] .try_into() .unwrap(), ..Default::default() }; let id_deposit = id_deposit(&id); 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(|| { 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(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(three.clone()), 0, four.clone())); // account 4 can now, because that's their new ID. assert_ok!(Identity::set_account_id(RuntimeOrigin::signed(four), 0, three)); }); } #[test] fn test_has_identity() { new_test_ext().execute_with(|| { 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( &ten, IdentityField::Display as u64 | IdentityField::Legal as u64 )); assert!(!Identity::has_identity( &ten, IdentityField::Display as u64 | IdentityField::Legal as u64 | IdentityField::Web as u64 )); }); } #[test] fn reap_identity_works() { new_test_ext().execute_with(|| { 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(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(ten.clone()), 1000 - id_deposit - subs_deposit); // reap assert_ok!(Identity::reap_identity(&ten)); // no identity or subs assert!(Identity::identity(ten.clone()).is_none()); assert!(Identity::super_of(twenty).is_none()); // balance is unreserved assert_eq!(Balances::free_balance(ten), 1000); }); } #[test] fn poke_deposit_works() { new_test_ext().execute_with(|| { let [_, _, _, _, ten, twenty, _, _] = accounts(); let ten_info = infoof_ten(); // Set a custom registration with 0 deposit 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, BoundedVec, ConstU32<2>>)>( &ten, (0, vec![twenty.clone()].try_into().unwrap()), ); SuperOf::::insert(&twenty, (&ten, Data::Raw(vec![1; 1].try_into().unwrap()))); // Balance is free assert_eq!(Balances::free_balance(ten.clone()), 1000); // poke 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(ten.clone()), 1000 - id_deposit - subs_deposit); // new registration deposit is 10 assert_eq!( 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 poke_deposit_does_not_insert_new_subs_storage() { new_test_ext().execute_with(|| { let [_, _, _, _, ten, _, _, _] = accounts(); let ten_info = infoof_ten(); // Set a custom registration with 0 deposit IdentityOf::::insert::< _, ( Registration>, Option>, ), >( &ten, ( Registration { judgements: Default::default(), deposit: Zero::zero(), info: ten_info.clone(), }, None::>, ), ); assert!(Identity::identity(ten.clone()).is_some()); // Balance is free assert_eq!(Balances::free_balance(ten.clone()), 1000); // poke assert_ok!(Identity::poke_deposit(&ten)); // free balance reduced correctly let id_deposit = id_deposit(&ten_info); assert_eq!(Balances::free_balance(ten.clone()), 1000 - id_deposit); // new registration deposit is 10 assert_eq!( Identity::identity(&ten), Some(( Registration { judgements: Default::default(), deposit: id_deposit, info: infoof_ten() }, None )) ); // No new subs storage item. assert!(!SubsOf::::contains_key(&ten)); }); } #[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 }) ); // 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()); }); }