diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index fce79c56f80a9..ed905d407d90f 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "2.2.0", default-features = false, features = ["derive", "max-encoded-len"] } enumflags2 = { version = "0.6.2" } sp-std = { version = "3.0.0", default-features = false, path = "../../primitives/std" } sp-io = { version = "3.0.0", default-features = false, path = "../../primitives/io" } diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index 0cd2d50529dd1..4fb76fcb4138c 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -56,7 +56,7 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { fn create_sub_accounts(who: &T::AccountId, s: u32) -> Result, &'static str> { let mut subs = Vec::new(); let who_origin = RawOrigin::Signed(who.clone()); - let data = Data::Raw(vec![0; 32]); + let data = Data::Raw(vec![0; 32].try_into().unwrap()); for i in 0..s { let sub_account = account("sub", i, SEED); @@ -84,11 +84,11 @@ fn add_sub_accounts(who: &T::AccountId, s: u32) -> Result(num_fields: u32) -> IdentityInfo { - let data = Data::Raw(vec![0; 32]); +fn create_identity_info(num_fields: u32) -> IdentityInfo { + let data = Data::Raw(vec![0; 32].try_into().unwrap()); let info = IdentityInfo { - additional: vec![(data.clone(), data.clone()); num_fields as usize], + additional: vec![(data.clone(), data.clone()); num_fields as usize].try_into().unwrap(), display: data.clone(), legal: data.clone(), web: data.clone(), @@ -353,7 +353,7 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); let _ = add_sub_accounts::(&caller, s)?; let sub = account("new_sub", 0, SEED); - let data = Data::Raw(vec![0; 32]); + let data = Data::Raw(vec![0; 32].try_into().unwrap()); ensure!(SubsOf::::get(&caller).1.len() as u32 == s, "Subs not set."); }: _(RawOrigin::Signed(caller.clone()), T::Lookup::unlookup(sub), data) verify { @@ -365,7 +365,7 @@ benchmarks! { let caller: T::AccountId = whitelisted_caller(); let (sub, _) = add_sub_accounts::(&caller, s)?.remove(0); - let data = Data::Raw(vec![1; 32]); + let data = Data::Raw(vec![1; 32].try_into().unwrap()); ensure!(SuperOf::::get(&sub).unwrap().1 != data, "data already set"); }: _(RawOrigin::Signed(caller), T::Lookup::unlookup(sub.clone()), data.clone()) verify { @@ -390,7 +390,7 @@ benchmarks! { let sup = account("super", 0, SEED); let _ = add_sub_accounts::(&sup, s)?; let sup_origin = RawOrigin::Signed(sup).into(); - Identity::::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32]))?; + Identity::::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32].try_into().unwrap()))?; ensure!(SuperOf::::contains_key(&caller), "Sub doesn't exists"); }: _(RawOrigin::Signed(caller.clone())) verify { diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index d398384887d98..f6e3f0639f16a 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -74,285 +74,25 @@ #[cfg(test)] mod tests; +mod types; mod benchmarking; pub mod weights; use sp_std::prelude::*; -use sp_std::{fmt::Debug, ops::Add, iter::once}; -use enumflags2::BitFlags; -use codec::{Encode, Decode}; -use sp_runtime::RuntimeDebug; +use sp_std::convert::TryInto; use sp_runtime::traits::{StaticLookup, Zero, AppendZerosInput, Saturating}; -use frame_support::traits::{Currency, ReservableCurrency, OnUnbalanced, BalanceStatus}; +use frame_support::traits::{BalanceStatus, Currency, OnUnbalanced, ReservableCurrency}; pub use weights::WeightInfo; pub use pallet::*; +pub use types::{ + Data, IdentityField, IdentityFields, IdentityInfo, Judgement, RegistrarIndex, + RegistrarInfo, Registration, +}; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; -/// Either underlying data blob if it is at most 32 bytes, or a hash of it. If the data is greater -/// than 32-bytes then it will be truncated when encoding. -/// -/// Can also be `None`. -#[derive(Clone, Eq, PartialEq, RuntimeDebug)] -pub enum Data { - /// No data here. - None, - /// The data is stored directly. - Raw(Vec), - /// Only the Blake2 hash of the data is stored. The preimage of the hash may be retrieved - /// through some hash-lookup service. - BlakeTwo256([u8; 32]), - /// Only the SHA2-256 hash of the data is stored. The preimage of the hash may be retrieved - /// through some hash-lookup service. - Sha256([u8; 32]), - /// Only the Keccak-256 hash of the data is stored. The preimage of the hash may be retrieved - /// through some hash-lookup service. - Keccak256([u8; 32]), - /// Only the SHA3-256 hash of the data is stored. The preimage of the hash may be retrieved - /// through some hash-lookup service. - ShaThree256([u8; 32]), -} - -impl Decode for Data { - fn decode(input: &mut I) -> sp_std::result::Result { - let b = input.read_byte()?; - Ok(match b { - 0 => Data::None, - n @ 1 ..= 33 => { - let mut r = vec![0u8; n as usize - 1]; - input.read(&mut r[..])?; - Data::Raw(r) - } - 34 => Data::BlakeTwo256(<[u8; 32]>::decode(input)?), - 35 => Data::Sha256(<[u8; 32]>::decode(input)?), - 36 => Data::Keccak256(<[u8; 32]>::decode(input)?), - 37 => Data::ShaThree256(<[u8; 32]>::decode(input)?), - _ => return Err(codec::Error::from("invalid leading byte")), - }) - } -} - -impl Encode for Data { - fn encode(&self) -> Vec { - match self { - Data::None => vec![0u8; 1], - Data::Raw(ref x) => { - let l = x.len().min(32); - let mut r = vec![l as u8 + 1; l + 1]; - r[1..].copy_from_slice(&x[..l as usize]); - r - } - Data::BlakeTwo256(ref h) => once(34u8).chain(h.iter().cloned()).collect(), - Data::Sha256(ref h) => once(35u8).chain(h.iter().cloned()).collect(), - Data::Keccak256(ref h) => once(36u8).chain(h.iter().cloned()).collect(), - Data::ShaThree256(ref h) => once(37u8).chain(h.iter().cloned()).collect(), - } - } -} -impl codec::EncodeLike for Data {} - -impl Default for Data { - fn default() -> Self { - Self::None - } -} - -/// An identifier for a single name registrar/identity verification service. -pub type RegistrarIndex = u32; - -/// An attestation of a registrar over how accurate some `IdentityInfo` is in describing an account. -/// -/// NOTE: Registrars may pay little attention to some fields. Registrars may want to make clear -/// which fields their attestation is relevant for by off-chain means. -#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)] -pub enum Judgement< - Balance: Encode + Decode + Copy + Clone + Debug + Eq + PartialEq -> { - /// The default value; no opinion is held. - Unknown, - /// No judgement is yet in place, but a deposit is reserved as payment for providing one. - FeePaid(Balance), - /// The data appears to be reasonably acceptable in terms of its accuracy, however no in depth - /// checks (such as in-person meetings or formal KYC) have been conducted. - Reasonable, - /// The target is known directly by the registrar and the registrar can fully attest to the - /// the data's accuracy. - KnownGood, - /// The data was once good but is currently out of date. There is no malicious intent in the - /// inaccuracy. This judgement can be removed through updating the data. - OutOfDate, - /// The data is imprecise or of sufficiently low-quality to be problematic. It is not - /// indicative of malicious intent. This judgement can be removed through updating the data. - LowQuality, - /// The data is erroneous. This may be indicative of malicious intent. This cannot be removed - /// except by the registrar. - Erroneous, -} - -impl< - Balance: Encode + Decode + Copy + Clone + Debug + Eq + PartialEq -> Judgement { - /// Returns `true` if this judgement is indicative of a deposit being currently held. This means - /// it should not be cleared or replaced except by an operation which utilizes the deposit. - fn has_deposit(&self) -> bool { - match self { - Judgement::FeePaid(_) => true, - _ => false, - } - } - - /// Returns `true` if this judgement is one that should not be generally be replaced outside - /// of specialized handlers. Examples include "malicious" judgements and deposit-holding - /// judgements. - fn is_sticky(&self) -> bool { - match self { - Judgement::FeePaid(_) | Judgement::Erroneous => true, - _ => false, - } - } -} - -/// The fields that we use to identify the owner of an account with. Each corresponds to a field -/// in the `IdentityInfo` struct. -#[repr(u64)] -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, BitFlags, RuntimeDebug)] -pub enum IdentityField { - Display = 0b0000000000000000000000000000000000000000000000000000000000000001, - Legal = 0b0000000000000000000000000000000000000000000000000000000000000010, - Web = 0b0000000000000000000000000000000000000000000000000000000000000100, - Riot = 0b0000000000000000000000000000000000000000000000000000000000001000, - Email = 0b0000000000000000000000000000000000000000000000000000000000010000, - PgpFingerprint = 0b0000000000000000000000000000000000000000000000000000000000100000, - Image = 0b0000000000000000000000000000000000000000000000000000000001000000, - Twitter = 0b0000000000000000000000000000000000000000000000000000000010000000, -} - -/// Wrapper type for `BitFlags` that implements `Codec`. -#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] -pub struct IdentityFields(BitFlags); - -impl Eq for IdentityFields {} -impl Encode for IdentityFields { - fn using_encoded R>(&self, f: F) -> R { - self.0.bits().using_encoded(f) - } -} -impl Decode for IdentityFields { - fn decode(input: &mut I) -> sp_std::result::Result { - let field = u64::decode(input)?; - Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) - } -} - -/// Information concerning the identity of the controller of an account. -/// -/// NOTE: This should be stored at the end of the storage item to facilitate the addition of extra -/// fields in a backwards compatible way through a specialized `Decode` impl. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)] -#[cfg_attr(test, derive(Default))] -pub struct IdentityInfo { - /// Additional fields of the identity that are not catered for with the struct's explicit - /// fields. - pub additional: Vec<(Data, Data)>, - - /// A reasonable display name for the controller of the account. This should be whatever it is - /// that it is typically known as and should not be confusable with other entities, given - /// reasonable context. - /// - /// Stored as UTF-8. - pub display: Data, - - /// The full legal name in the local jurisdiction of the entity. This might be a bit - /// long-winded. - /// - /// Stored as UTF-8. - pub legal: Data, - - /// A representative website held by the controller of the account. - /// - /// NOTE: `https://` is automatically prepended. - /// - /// Stored as UTF-8. - pub web: Data, - - /// The Riot/Matrix handle held by the controller of the account. - /// - /// Stored as UTF-8. - pub riot: Data, - - /// The email address of the controller of the account. - /// - /// Stored as UTF-8. - pub email: Data, - - /// The PGP/GPG public key of the controller of the account. - pub pgp_fingerprint: Option<[u8; 20]>, - - /// A graphic image representing the controller of the account. Should be a company, - /// organization or project logo or a headshot in the case of a human. - pub image: Data, - - /// The Twitter identity. The leading `@` character may be elided. - pub twitter: Data, -} - -/// Information concerning the identity of the controller of an account. -/// -/// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a -/// backwards compatible way through a specialized `Decode` impl. -#[derive(Clone, Encode, Eq, PartialEq, RuntimeDebug)] -pub struct Registration< - Balance: Encode + Decode + Copy + Clone + Debug + Eq + PartialEq -> { - /// Judgements from the registrars on this identity. Stored ordered by `RegistrarIndex`. There - /// may be only a single judgement from each registrar. - pub judgements: Vec<(RegistrarIndex, Judgement)>, - - /// Amount held on deposit for this information. - pub deposit: Balance, - - /// Information on the identity. - pub info: IdentityInfo, -} - -impl < - Balance: Encode + Decode + Copy + Clone + Debug + Eq + PartialEq + Zero + Add, -> Registration { - fn total_deposit(&self) -> Balance { - self.deposit + self.judgements.iter() - .map(|(_, ref j)| if let Judgement::FeePaid(fee) = j { *fee } else { Zero::zero() }) - .fold(Zero::zero(), |a, i| a + i) - } -} - -impl< - Balance: Encode + Decode + Copy + Clone + Debug + Eq + PartialEq, -> Decode for Registration { - fn decode(input: &mut I) -> sp_std::result::Result { - let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?; - Ok(Self { judgements, deposit, info }) - } -} - -/// Information concerning a registrar. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug)] -pub struct RegistrarInfo< - Balance: Encode + Decode + Clone + Debug + Eq + PartialEq, - AccountId: Encode + Decode + Clone + Debug + Eq + PartialEq -> { - /// The account of the registrar. - pub account: AccountId, - - /// Amount required to be given to the registrar for them to provide judgement. - pub fee: Balance, - - /// Relevant fields for this registrar. Registrar judgements are limited to attestations on - /// these fields. - pub fields: IdentityFields, -} - #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; @@ -411,6 +151,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet(_); /// Information that is pertinent to identify the entity behind an account. @@ -422,7 +163,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - Registration>, + Registration, T::MaxRegistrars, T::MaxAdditionalFields>, OptionQuery, >; @@ -449,7 +190,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - (BalanceOf, Vec), + (BalanceOf, BoundedVec), ValueQuery, >; @@ -461,7 +202,7 @@ pub mod pallet { #[pallet::getter(fn registrars)] pub(super) type Registrars = StorageValue< _, - Vec, T::AccountId>>>, + BoundedVec, T::AccountId>>, T::MaxRegistrars>, ValueQuery, >; @@ -554,10 +295,10 @@ pub mod pallet { let (i, registrar_count) = >::try_mutate( |registrars| -> Result<(RegistrarIndex, usize), DispatchError> { - ensure!(registrars.len() < T::MaxRegistrars::get() as usize, Error::::TooManyRegistrars); - registrars.push(Some(RegistrarInfo { + registrars.try_push(Some(RegistrarInfo { account, fee: Zero::zero(), fields: Default::default() - })); + })) + .map_err(|_| Error::::TooManyRegistrars)?; Ok(((registrars.len() - 1) as RegistrarIndex, registrars.len())) } )?; @@ -590,7 +331,7 @@ pub mod pallet { T::MaxRegistrars::get().into(), // R T::MaxAdditionalFields::get().into(), // X ))] - pub fn set_identity(origin: OriginFor, info: IdentityInfo) -> DispatchResultWithPostInfo { + pub fn set_identity(origin: OriginFor, info: IdentityInfo) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; let extra_fields = info.additional.len() as u32; ensure!(extra_fields <= T::MaxAdditionalFields::get(), Error::::TooManyFields); @@ -603,7 +344,7 @@ pub mod pallet { id.info = info; id } - None => Registration { info, judgements: Vec::new(), deposit: Zero::zero() }, + None => Registration { info, judgements: BoundedVec::default(), deposit: Zero::zero() }, }; let old_deposit = id.deposit; @@ -678,10 +419,11 @@ pub mod pallet { for s in old_ids.iter() { >::remove(s); } - let ids = subs.into_iter().map(|(id, name)| { + let mut ids = BoundedVec::::default(); + for (id, name) in subs { >::insert(&id, (sender.clone(), name)); - id - }).collect::>(); + ids.try_push(id).expect("subs length is less than T::MaxSubAccounts; qed"); + } let new_subs = ids.len(); if ids.is_empty() { @@ -786,7 +528,10 @@ pub mod pallet { } else { id.judgements[i] = item }, - Err(i) => id.judgements.insert(i, item), + Err(i) => id + .judgements + .try_insert(i, item) + .map_err(|_| Error::::TooManyRegistrars)?, } T::Currency::reserve(&sender, registrar.fee)?; @@ -988,7 +733,10 @@ pub mod pallet { } id.judgements[position] = item } - Err(position) => id.judgements.insert(position, item), + Err(position) => id + .judgements + .try_insert(position, item) + .map_err(|_| Error::::TooManyRegistrars)?, } let judgements = id.judgements.len(); @@ -1075,7 +823,7 @@ pub mod pallet { T::Currency::reserve(&sender, deposit)?; SuperOf::::insert(&sub, (sender.clone(), data)); - sub_ids.push(sub.clone()); + sub_ids.try_push(sub.clone()).expect("sub ids length checked above; qed"); *subs_deposit = subs_deposit.saturating_add(deposit); Self::deposit_event(Event::SubIdentityAdded(sub, sender.clone(), deposit)); diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index 262b3211b6d1b..fea83dc3b10aa 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -20,8 +20,9 @@ use super::*; use crate as pallet_identity; +use codec::{Encode, Decode}; use sp_runtime::traits::BadOrigin; -use frame_support::{assert_ok, assert_noop, parameter_types, ord_parameter_types}; +use frame_support::{assert_ok, assert_noop, parameter_types, ord_parameter_types, BoundedVec}; use sp_core::H256; use frame_system::{EnsureSignedBy, EnsureOneOf, EnsureRoot}; use sp_runtime::{ @@ -139,18 +140,18 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } -fn ten() -> IdentityInfo { +fn ten() -> IdentityInfo { IdentityInfo { - display: Data::Raw(b"ten".to_vec()), - legal: Data::Raw(b"The Right Ordinal Ten, Esq.".to_vec()), + 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 twenty() -> IdentityInfo { +fn twenty() -> IdentityInfo { IdentityInfo { - display: Data::Raw(b"twenty".to_vec()), - legal: Data::Raw(b"The Right Ordinal Twenty, Esq.".to_vec()), + 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() } } @@ -158,7 +159,7 @@ fn twenty() -> IdentityInfo { #[test] fn editing_subaccounts_should_work() { new_test_ext().execute_with(|| { - let data = |x| Data::Raw(vec![x; 1]); + let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); assert_noop!(Identity::add_sub(Origin::signed(10), 20, data(1)), Error::::NoIdentity); @@ -202,7 +203,7 @@ fn editing_subaccounts_should_work() { #[test] fn resolving_subaccount_ownership_works() { new_test_ext().execute_with(|| { - let data = |x| Data::Raw(vec![x; 1]); + let data = |x| Data::Raw(vec![x; 1].try_into().unwrap()); assert_ok!(Identity::set_identity(Origin::signed(10), ten())); assert_ok!(Identity::set_identity(Origin::signed(20), twenty())); @@ -227,11 +228,11 @@ fn resolving_subaccount_ownership_works() { #[test] fn trailing_zeros_decodes_into_default_data() { - let encoded = Data::Raw(b"Hello".to_vec()).encode(); + 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())); + assert_eq!(a, Data::Raw(b"Hello".to_vec().try_into().unwrap())); assert_eq!(b, Data::None); } @@ -268,13 +269,9 @@ fn registration_should_work() { assert_ok!(Identity::add_registrar(Origin::signed(1), 3)); assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); let mut three_fields = ten(); - three_fields.additional.push(Default::default()); - three_fields.additional.push(Default::default()); - three_fields.additional.push(Default::default()); - assert_noop!( - Identity::set_identity(Origin::signed(10), three_fields), - Error::::TooManyFields - ); + three_fields.additional.try_push(Default::default()).unwrap(); + three_fields.additional.try_push(Default::default()).unwrap(); + assert_eq!(three_fields.additional.try_push(Default::default()), Err(())); assert_ok!(Identity::set_identity(Origin::signed(10), ten())); assert_eq!(Identity::identity(10).unwrap().info, ten()); assert_eq!(Balances::free_balance(10), 90); @@ -339,40 +336,40 @@ fn killing_slashing_should_work() { #[test] fn setting_subaccounts_should_work() { new_test_ext().execute_with(|| { - let mut subs = vec![(20, Data::Raw(vec![40; 1]))]; + let mut subs = vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))]; assert_noop!(Identity::set_subs(Origin::signed(10), subs.clone()), Error::::NotFound); assert_ok!(Identity::set_identity(Origin::signed(10), ten())); assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 80); - assert_eq!(Identity::subs_of(10), (10, vec![20])); - assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1])))); + assert_eq!(Identity::subs_of(10), (10, vec![20].try_into().unwrap())); + assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1].try_into().unwrap())))); // push another item and re-set it. - subs.push((30, Data::Raw(vec![50; 1]))); + subs.push((30, Data::Raw(vec![50; 1].try_into().unwrap()))); assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 70); - assert_eq!(Identity::subs_of(10), (20, vec![20, 30])); - assert_eq!(Identity::super_of(20), Some((10, Data::Raw(vec![40; 1])))); - assert_eq!(Identity::super_of(30), Some((10, Data::Raw(vec![50; 1])))); + assert_eq!(Identity::subs_of(10), (20, 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())))); // switch out one of the items and re-set. - subs[0] = (40, Data::Raw(vec![60; 1])); + subs[0] = (40, Data::Raw(vec![60; 1].try_into().unwrap())); assert_ok!(Identity::set_subs(Origin::signed(10), subs.clone())); assert_eq!(Balances::free_balance(10), 70); // no change in the balance - assert_eq!(Identity::subs_of(10), (20, vec![40, 30])); + assert_eq!(Identity::subs_of(10), (20, 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])))); - assert_eq!(Identity::super_of(40), Some((10, Data::Raw(vec![60; 1])))); + 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())))); // clear assert_ok!(Identity::set_subs(Origin::signed(10), vec![])); assert_eq!(Balances::free_balance(10), 90); - assert_eq!(Identity::subs_of(10), (0, vec![])); + assert_eq!(Identity::subs_of(10), (0, BoundedVec::default())); assert_eq!(Identity::super_of(30), None); assert_eq!(Identity::super_of(40), None); - subs.push((20, Data::Raw(vec![40; 1]))); + subs.push((20, Data::Raw(vec![40; 1].try_into().unwrap()))); assert_noop!(Identity::set_subs(Origin::signed(10), subs.clone()), Error::::TooManySubAccounts); }); } @@ -381,7 +378,7 @@ fn setting_subaccounts_should_work() { fn clearing_account_should_remove_subaccounts_and_refund() { new_test_ext().execute_with(|| { assert_ok!(Identity::set_identity(Origin::signed(10), ten())); - assert_ok!(Identity::set_subs(Origin::signed(10), vec![(20, Data::Raw(vec![40; 1]))])); + assert_ok!(Identity::set_subs(Origin::signed(10), vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))])); assert_ok!(Identity::clear_identity(Origin::signed(10))); assert_eq!(Balances::free_balance(10), 100); assert!(Identity::super_of(20).is_none()); @@ -392,7 +389,7 @@ fn clearing_account_should_remove_subaccounts_and_refund() { fn killing_account_should_remove_subaccounts_and_not_refund() { new_test_ext().execute_with(|| { assert_ok!(Identity::set_identity(Origin::signed(10), ten())); - assert_ok!(Identity::set_subs(Origin::signed(10), vec![(20, Data::Raw(vec![40; 1]))])); + assert_ok!(Identity::set_subs(Origin::signed(10), vec![(20, Data::Raw(vec![40; 1].try_into().unwrap()))])); assert_ok!(Identity::kill_identity(Origin::signed(2), 10)); assert_eq!(Balances::free_balance(10), 80); assert!(Identity::super_of(20).is_none()); @@ -453,9 +450,11 @@ fn field_deposit_should_work() { assert_ok!(Identity::set_fee(Origin::signed(3), 0, 10)); assert_ok!(Identity::set_identity(Origin::signed(10), IdentityInfo { additional: vec![ - (Data::Raw(b"number".to_vec()), Data::Raw(10u32.encode())), - (Data::Raw(b"text".to_vec()), Data::Raw(b"10".to_vec())), - ], .. Default::default() + (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() })); assert_eq!(Balances::free_balance(10), 70); }); diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs new file mode 100644 index 0000000000000..59781aadbd31e --- /dev/null +++ b/frame/identity/src/types.rs @@ -0,0 +1,318 @@ +// This file is part of Substrate. + +// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::{Encode, Decode, MaxEncodedLen}; +use enumflags2::BitFlags; +use frame_support::{ + traits::{ConstU32, Get}, + BoundedVec, CloneNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use sp_std::prelude::*; +use sp_std::{fmt::Debug, iter::once, ops::Add}; +use sp_runtime::{ + traits::Zero, + RuntimeDebug, +}; +use super::*; + +/// Either underlying data blob if it is at most 32 bytes, or a hash of it. If the data is greater +/// than 32-bytes then it will be truncated when encoding. +/// +/// Can also be `None`. +#[derive(Clone, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)] +pub enum Data { + /// No data here. + None, + /// The data is stored directly. + Raw(BoundedVec>), + /// Only the Blake2 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + BlakeTwo256([u8; 32]), + /// Only the SHA2-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + Sha256([u8; 32]), + /// Only the Keccak-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + Keccak256([u8; 32]), + /// Only the SHA3-256 hash of the data is stored. The preimage of the hash may be retrieved + /// through some hash-lookup service. + ShaThree256([u8; 32]), +} + +impl Decode for Data { + fn decode(input: &mut I) -> sp_std::result::Result { + let b = input.read_byte()?; + Ok(match b { + 0 => Data::None, + n @ 1 ..= 33 => { + let mut r: BoundedVec<_, _> = vec![0u8; n as usize - 1] + .try_into() + .expect("bound checked in match arm condition; qed"); + input.read(&mut r[..])?; + Data::Raw(r) + } + 34 => Data::BlakeTwo256(<[u8; 32]>::decode(input)?), + 35 => Data::Sha256(<[u8; 32]>::decode(input)?), + 36 => Data::Keccak256(<[u8; 32]>::decode(input)?), + 37 => Data::ShaThree256(<[u8; 32]>::decode(input)?), + _ => return Err(codec::Error::from("invalid leading byte")), + }) + } +} + +impl Encode for Data { + fn encode(&self) -> Vec { + match self { + Data::None => vec![0u8; 1], + Data::Raw(ref x) => { + let l = x.len().min(32); + let mut r = vec![l as u8 + 1; l + 1]; + r[1..].copy_from_slice(&x[..l as usize]); + r + } + Data::BlakeTwo256(ref h) => once(34u8).chain(h.iter().cloned()).collect(), + Data::Sha256(ref h) => once(35u8).chain(h.iter().cloned()).collect(), + Data::Keccak256(ref h) => once(36u8).chain(h.iter().cloned()).collect(), + Data::ShaThree256(ref h) => once(37u8).chain(h.iter().cloned()).collect(), + } + } +} +impl codec::EncodeLike for Data {} + +impl Default for Data { + fn default() -> Self { + Self::None + } +} + +/// An identifier for a single name registrar/identity verification service. +pub type RegistrarIndex = u32; + +/// An attestation of a registrar over how accurate some `IdentityInfo` is in describing an account. +/// +/// NOTE: Registrars may pay little attention to some fields. Registrars may want to make clear +/// which fields their attestation is relevant for by off-chain means. +#[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)] +pub enum Judgement< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq +> { + /// The default value; no opinion is held. + Unknown, + /// No judgement is yet in place, but a deposit is reserved as payment for providing one. + FeePaid(Balance), + /// The data appears to be reasonably acceptable in terms of its accuracy, however no in depth + /// checks (such as in-person meetings or formal KYC) have been conducted. + Reasonable, + /// The target is known directly by the registrar and the registrar can fully attest to the + /// the data's accuracy. + KnownGood, + /// The data was once good but is currently out of date. There is no malicious intent in the + /// inaccuracy. This judgement can be removed through updating the data. + OutOfDate, + /// The data is imprecise or of sufficiently low-quality to be problematic. It is not + /// indicative of malicious intent. This judgement can be removed through updating the data. + LowQuality, + /// The data is erroneous. This may be indicative of malicious intent. This cannot be removed + /// except by the registrar. + Erroneous, +} + +impl< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq +> Judgement { + /// Returns `true` if this judgement is indicative of a deposit being currently held. This means + /// it should not be cleared or replaced except by an operation which utilizes the deposit. + pub(crate) fn has_deposit(&self) -> bool { + match self { + Judgement::FeePaid(_) => true, + _ => false, + } + } + + /// Returns `true` if this judgement is one that should not be generally be replaced outside + /// of specialized handlers. Examples include "malicious" judgements and deposit-holding + /// judgements. + pub(crate) fn is_sticky(&self) -> bool { + match self { + Judgement::FeePaid(_) | Judgement::Erroneous => true, + _ => false, + } + } +} + +/// The fields that we use to identify the owner of an account with. Each corresponds to a field +/// in the `IdentityInfo` struct. +#[repr(u64)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, BitFlags, RuntimeDebug)] +pub enum IdentityField { + Display = 0b0000000000000000000000000000000000000000000000000000000000000001, + Legal = 0b0000000000000000000000000000000000000000000000000000000000000010, + Web = 0b0000000000000000000000000000000000000000000000000000000000000100, + Riot = 0b0000000000000000000000000000000000000000000000000000000000001000, + Email = 0b0000000000000000000000000000000000000000000000000000000000010000, + PgpFingerprint = 0b0000000000000000000000000000000000000000000000000000000000100000, + Image = 0b0000000000000000000000000000000000000000000000000000000001000000, + Twitter = 0b0000000000000000000000000000000000000000000000000000000010000000, +} + +impl MaxEncodedLen for IdentityField { + fn max_encoded_len() -> usize { + u64::max_encoded_len() + } +} + +/// Wrapper type for `BitFlags` that implements `Codec`. +#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)] +pub struct IdentityFields(pub(crate) BitFlags); + +impl MaxEncodedLen for IdentityFields { + fn max_encoded_len() -> usize { + IdentityField::max_encoded_len() + } +} + +impl Eq for IdentityFields {} +impl Encode for IdentityFields { + fn using_encoded R>(&self, f: F) -> R { + self.0.bits().using_encoded(f) + } +} +impl Decode for IdentityFields { + fn decode(input: &mut I) -> sp_std::result::Result { + let field = u64::decode(input)?; + Ok(Self(>::from_bits(field as u64).map_err(|_| "invalid value")?)) + } +} + +/// Information concerning the identity of the controller of an account. +/// +/// NOTE: This should be stored at the end of the storage item to facilitate the addition of extra +/// fields in a backwards compatible way through a specialized `Decode` impl. +#[derive(CloneNoBound, Encode, Decode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound)] +#[codec(mel_bound(FieldLimit: Get))] +#[cfg_attr(test, derive(frame_support::DefaultNoBound))] +pub struct IdentityInfo> { + /// Additional fields of the identity that are not catered for with the struct's explicit + /// fields. + pub additional: BoundedVec<(Data, Data), FieldLimit>, + + /// A reasonable display name for the controller of the account. This should be whatever it is + /// that it is typically known as and should not be confusable with other entities, given + /// reasonable context. + /// + /// Stored as UTF-8. + pub display: Data, + + /// The full legal name in the local jurisdiction of the entity. This might be a bit + /// long-winded. + /// + /// Stored as UTF-8. + pub legal: Data, + + /// A representative website held by the controller of the account. + /// + /// NOTE: `https://` is automatically prepended. + /// + /// Stored as UTF-8. + pub web: Data, + + /// The Riot/Matrix handle held by the controller of the account. + /// + /// Stored as UTF-8. + pub riot: Data, + + /// The email address of the controller of the account. + /// + /// Stored as UTF-8. + pub email: Data, + + /// The PGP/GPG public key of the controller of the account. + pub pgp_fingerprint: Option<[u8; 20]>, + + /// A graphic image representing the controller of the account. Should be a company, + /// organization or project logo or a headshot in the case of a human. + pub image: Data, + + /// The Twitter identity. The leading `@` character may be elided. + pub twitter: Data, +} + +/// Information concerning the identity of the controller of an account. +/// +/// NOTE: This is stored separately primarily to facilitate the addition of extra fields in a +/// backwards compatible way through a specialized `Decode` impl. +#[derive(CloneNoBound, Encode, Eq, MaxEncodedLen, PartialEqNoBound, RuntimeDebugNoBound)] +#[codec(mel_bound( + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add, + MaxJudgements: Get, + MaxAdditionalFields: Get, +))] +pub struct Registration< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, + MaxJudgements: Get, + MaxAdditionalFields: Get, +> { + /// Judgements from the registrars on this identity. Stored ordered by `RegistrarIndex`. There + /// may be only a single judgement from each registrar. + pub judgements: BoundedVec<(RegistrarIndex, Judgement), MaxJudgements>, + + /// Amount held on deposit for this information. + pub deposit: Balance, + + /// Information on the identity. + pub info: IdentityInfo, +} + +impl < + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add, + MaxJudgements: Get, + MaxAdditionalFields: Get, +> Registration { + pub(crate) fn total_deposit(&self) -> Balance { + self.deposit + self.judgements.iter() + .map(|(_, ref j)| if let Judgement::FeePaid(fee) = j { *fee } else { Zero::zero() }) + .fold(Zero::zero(), |a, i| a + i) + } +} + +impl< + Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq, + MaxJudgements: Get, + MaxAdditionalFields: Get, +> Decode for Registration { + fn decode(input: &mut I) -> sp_std::result::Result { + let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?; + Ok(Self { judgements, deposit, info }) + } +} + +/// Information concerning a registrar. +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen)] +pub struct RegistrarInfo< + Balance: Encode + Decode + Clone + Debug + Eq + PartialEq, + AccountId: Encode + Decode + Clone + Debug + Eq + PartialEq +> { + /// The account of the registrar. + pub account: AccountId, + + /// Amount required to be given to the registrar for them to provide judgement. + pub fee: Balance, + + /// Relevant fields for this registrar. Registrar judgements are limited to attestations on + /// these fields. + pub fields: IdentityFields, +} diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 589fe0920744d..b5b5252f9ec41 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -122,6 +122,14 @@ impl BoundedVec { pub fn retain bool>(&mut self, f: F) { self.0.retain(f) } + + /// Exactly the same semantics as [`Vec::get_mut`]. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } } impl> From> for Vec { diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index e5a4843000bbf..a98d2182d0919 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -89,6 +89,14 @@ impl WeakBoundedVec { pub fn retain bool>(&mut self, f: F) { self.0.retain(f) } + + /// Exactly the same semantics as [`Vec::get_mut`]. + pub fn get_mut>( + &mut self, + index: I, + ) -> Option<&mut >::Output> { + self.0.get_mut(index) + } } impl> WeakBoundedVec {