mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-19 22:45:40 +00:00
Ensure data size of identity pallet is bounded (#9168)
* Ensure data size of identity pallet is bounded * Fix unit tests for identity pallet * Move identity pallet custom types into its own module * Make use of NoBound family traits * Fix identity pallet benchmarks * Enumerate type imports * Properly convert to BoundedVec in benchmarks * Re-export types * Use BoundedVec when storing sub identities * Add generate_storage_info * Manually implement MaxEncodedLen on select types * Use ConstU32 instead of parameter_type * Leverage DefaultNoBound and add some comments * Use max_encoded_len() instead of hardcoded constant * Use MaxEncodedLen in parity-scal-codec * Add get_mut method for WeakBoundedVec * Use expect on an infallible operation * Rewrite as for loop
This commit is contained in:
@@ -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" }
|
||||
|
||||
@@ -56,7 +56,7 @@ fn add_registrars<T: Config>(r: u32) -> Result<(), &'static str> {
|
||||
fn create_sub_accounts<T: Config>(who: &T::AccountId, s: u32) -> Result<Vec<(T::AccountId, Data)>, &'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<T: Config>(who: &T::AccountId, s: u32) -> Result<Vec<(T::Acc
|
||||
|
||||
// This creates an `IdentityInfo` object with `num_fields` extra fields.
|
||||
// All data is pre-populated with some arbitrary bytes.
|
||||
fn create_identity_info<T: Config>(num_fields: u32) -> IdentityInfo {
|
||||
let data = Data::Raw(vec![0; 32]);
|
||||
fn create_identity_info<T: Config>(num_fields: u32) -> IdentityInfo<T::MaxAdditionalFields> {
|
||||
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::<T>(&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::<T>::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::<T>(&caller, s)?.remove(0);
|
||||
let data = Data::Raw(vec![1; 32]);
|
||||
let data = Data::Raw(vec![1; 32].try_into().unwrap());
|
||||
ensure!(SuperOf::<T>::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::<T>(&sup, s)?;
|
||||
let sup_origin = RawOrigin::Signed(sup).into();
|
||||
Identity::<T>::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32]))?;
|
||||
Identity::<T>::add_sub(sup_origin, T::Lookup::unlookup(caller.clone()), Data::Raw(vec![0; 32].try_into().unwrap()))?;
|
||||
ensure!(SuperOf::<T>::contains_key(&caller), "Sub doesn't exists");
|
||||
}: _(RawOrigin::Signed(caller.clone()))
|
||||
verify {
|
||||
|
||||
@@ -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<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<<T as frame_system::Config>::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<u8>),
|
||||
/// 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<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
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<u8> {
|
||||
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<Balance> {
|
||||
/// 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<IdentityField>` that implements `Codec`.
|
||||
#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)]
|
||||
pub struct IdentityFields(BitFlags<IdentityField>);
|
||||
|
||||
impl Eq for IdentityFields {}
|
||||
impl Encode for IdentityFields {
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
self.0.bits().using_encoded(f)
|
||||
}
|
||||
}
|
||||
impl Decode for IdentityFields {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
let field = u64::decode(input)?;
|
||||
Ok(Self(<BitFlags<IdentityField>>::from_bits(field as u64).map_err(|_| "invalid value")?))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Balance>)>,
|
||||
|
||||
/// 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<Balance> {
|
||||
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<Balance> {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?;
|
||||
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<T>(_);
|
||||
|
||||
/// Information that is pertinent to identify the entity behind an account.
|
||||
@@ -422,7 +163,7 @@ pub mod pallet {
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Registration<BalanceOf<T>>,
|
||||
Registration<BalanceOf<T>, T::MaxRegistrars, T::MaxAdditionalFields>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
@@ -449,7 +190,7 @@ pub mod pallet {
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
(BalanceOf<T>, Vec<T::AccountId>),
|
||||
(BalanceOf<T>, BoundedVec<T::AccountId, T::MaxSubAccounts>),
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
@@ -461,7 +202,7 @@ pub mod pallet {
|
||||
#[pallet::getter(fn registrars)]
|
||||
pub(super) type Registrars<T: Config> = StorageValue<
|
||||
_,
|
||||
Vec<Option<RegistrarInfo<BalanceOf<T>, T::AccountId>>>,
|
||||
BoundedVec<Option<RegistrarInfo<BalanceOf<T>, T::AccountId>>, T::MaxRegistrars>,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
@@ -554,10 +295,10 @@ pub mod pallet {
|
||||
|
||||
let (i, registrar_count) = <Registrars<T>>::try_mutate(
|
||||
|registrars| -> Result<(RegistrarIndex, usize), DispatchError> {
|
||||
ensure!(registrars.len() < T::MaxRegistrars::get() as usize, Error::<T>::TooManyRegistrars);
|
||||
registrars.push(Some(RegistrarInfo {
|
||||
registrars.try_push(Some(RegistrarInfo {
|
||||
account, fee: Zero::zero(), fields: Default::default()
|
||||
}));
|
||||
}))
|
||||
.map_err(|_| Error::<T>::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<T>, info: IdentityInfo) -> DispatchResultWithPostInfo {
|
||||
pub fn set_identity(origin: OriginFor<T>, info: IdentityInfo<T::MaxAdditionalFields>) -> DispatchResultWithPostInfo {
|
||||
let sender = ensure_signed(origin)?;
|
||||
let extra_fields = info.additional.len() as u32;
|
||||
ensure!(extra_fields <= T::MaxAdditionalFields::get(), Error::<T>::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() {
|
||||
<SuperOf<T>>::remove(s);
|
||||
}
|
||||
let ids = subs.into_iter().map(|(id, name)| {
|
||||
let mut ids = BoundedVec::<T::AccountId, T::MaxSubAccounts>::default();
|
||||
for (id, name) in subs {
|
||||
<SuperOf<T>>::insert(&id, (sender.clone(), name));
|
||||
id
|
||||
}).collect::<Vec<_>>();
|
||||
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::<T>::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::<T>::TooManyRegistrars)?,
|
||||
}
|
||||
|
||||
let judgements = id.judgements.len();
|
||||
@@ -1075,7 +823,7 @@ pub mod pallet {
|
||||
T::Currency::reserve(&sender, deposit)?;
|
||||
|
||||
SuperOf::<T>::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));
|
||||
|
||||
@@ -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<MaxAdditionalFields> {
|
||||
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<MaxAdditionalFields> {
|
||||
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::<Test>::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::<Test>::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::<Test>::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::<Test>::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);
|
||||
});
|
||||
|
||||
@@ -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<u8, ConstU32<32>>),
|
||||
/// 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<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
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<u8> {
|
||||
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<Balance> {
|
||||
/// 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<IdentityField>` that implements `Codec`.
|
||||
#[derive(Clone, Copy, PartialEq, Default, RuntimeDebug)]
|
||||
pub struct IdentityFields(pub(crate) BitFlags<IdentityField>);
|
||||
|
||||
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, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
self.0.bits().using_encoded(f)
|
||||
}
|
||||
}
|
||||
impl Decode for IdentityFields {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
let field = u64::decode(input)?;
|
||||
Ok(Self(<BitFlags<IdentityField>>::from_bits(field as u64).map_err(|_| "invalid value")?))
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<u32>))]
|
||||
#[cfg_attr(test, derive(frame_support::DefaultNoBound))]
|
||||
pub struct IdentityInfo<FieldLimit: Get<u32>> {
|
||||
/// Additional fields of the identity that are not catered for with the struct's explicit
|
||||
/// fields.
|
||||
pub additional: BoundedVec<(Data, Data), FieldLimit>,
|
||||
|
||||
/// A reasonable display name for the controller of the account. This should be whatever it is
|
||||
/// that it is typically known as and should not be confusable with other entities, given
|
||||
/// reasonable context.
|
||||
///
|
||||
/// Stored as UTF-8.
|
||||
pub display: Data,
|
||||
|
||||
/// The full legal name in the local jurisdiction of the entity. This might be a bit
|
||||
/// long-winded.
|
||||
///
|
||||
/// Stored as UTF-8.
|
||||
pub legal: Data,
|
||||
|
||||
/// A representative website held by the controller of the account.
|
||||
///
|
||||
/// NOTE: `https://` is automatically prepended.
|
||||
///
|
||||
/// Stored as UTF-8.
|
||||
pub web: Data,
|
||||
|
||||
/// The Riot/Matrix handle held by the controller of the account.
|
||||
///
|
||||
/// Stored as UTF-8.
|
||||
pub riot: Data,
|
||||
|
||||
/// The email address of the controller of the account.
|
||||
///
|
||||
/// Stored as UTF-8.
|
||||
pub email: Data,
|
||||
|
||||
/// The PGP/GPG public key of the controller of the account.
|
||||
pub pgp_fingerprint: Option<[u8; 20]>,
|
||||
|
||||
/// A graphic image representing the controller of the account. Should be a company,
|
||||
/// organization or project logo or a headshot in the case of a human.
|
||||
pub image: Data,
|
||||
|
||||
/// The Twitter identity. The leading `@` character may be elided.
|
||||
pub twitter: Data,
|
||||
}
|
||||
|
||||
/// 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<u32>,
|
||||
MaxAdditionalFields: Get<u32>,
|
||||
))]
|
||||
pub struct Registration<
|
||||
Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq,
|
||||
MaxJudgements: Get<u32>,
|
||||
MaxAdditionalFields: Get<u32>,
|
||||
> {
|
||||
/// 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<Balance>), MaxJudgements>,
|
||||
|
||||
/// Amount held on deposit for this information.
|
||||
pub deposit: Balance,
|
||||
|
||||
/// Information on the identity.
|
||||
pub info: IdentityInfo<MaxAdditionalFields>,
|
||||
}
|
||||
|
||||
impl <
|
||||
Balance: Encode + Decode + MaxEncodedLen + Copy + Clone + Debug + Eq + PartialEq + Zero + Add,
|
||||
MaxJudgements: Get<u32>,
|
||||
MaxAdditionalFields: Get<u32>,
|
||||
> Registration<Balance, MaxJudgements, MaxAdditionalFields> {
|
||||
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<u32>,
|
||||
MaxAdditionalFields: Get<u32>,
|
||||
> Decode for Registration<Balance, MaxJudgements, MaxAdditionalFields> {
|
||||
fn decode<I: codec::Input>(input: &mut I) -> sp_std::result::Result<Self, codec::Error> {
|
||||
let (judgements, deposit, info) = Decode::decode(&mut AppendZerosInput::new(input))?;
|
||||
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,
|
||||
}
|
||||
@@ -122,6 +122,14 @@ impl<T, S> BoundedVec<T, S> {
|
||||
pub fn retain<F: FnMut(&T) -> bool>(&mut self, f: F) {
|
||||
self.0.retain(f)
|
||||
}
|
||||
|
||||
/// Exactly the same semantics as [`Vec::get_mut`].
|
||||
pub fn get_mut<I: SliceIndex<[T]>>(
|
||||
&mut self,
|
||||
index: I,
|
||||
) -> Option<&mut <I as SliceIndex<[T]>>::Output> {
|
||||
self.0.get_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Get<u32>> From<BoundedVec<T, S>> for Vec<T> {
|
||||
|
||||
@@ -89,6 +89,14 @@ impl<T, S> WeakBoundedVec<T, S> {
|
||||
pub fn retain<F: FnMut(&T) -> bool>(&mut self, f: F) {
|
||||
self.0.retain(f)
|
||||
}
|
||||
|
||||
/// Exactly the same semantics as [`Vec::get_mut`].
|
||||
pub fn get_mut<I: SliceIndex<[T]>>(
|
||||
&mut self,
|
||||
index: I,
|
||||
) -> Option<&mut <I as SliceIndex<[T]>>::Output> {
|
||||
self.0.get_mut(index)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Get<u32>> WeakBoundedVec<T, S> {
|
||||
|
||||
Reference in New Issue
Block a user