Newer
Older
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(test)]
mod mock;
use frame_support::traits::StorageVersion;
use sp_runtime::traits::AtLeast32BitUnsigned;
use sp_std::{fmt::Debug, vec::Vec};
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use sp_runtime::traits::{Convert, Saturating};
use sp_std::collections::btree_map::BTreeMap;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
#[pallet::constant]
/// Minimum duration between two certifications issued by the same issuer
type CertPeriod: Get<Self::BlockNumber>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// A short identity index.
type IdtyIndex: Parameter
+ Member
+ AtLeast32BitUnsigned
+ Codec
+ Default
+ Copy
+ MaybeSerializeDeserialize
+ Debug
+ MaxEncodedLen;
/// Something that give the IdtyIndex on an account id
type IdtyIndexOf: Convert<Self::AccountId, Option<Self::IdtyIndex>>;
///
type IsCertAllowed: IsCertAllowed<Self::IdtyIndex>;
/// Maximum number of active certifications by issuer
type MaxByIssuer: Get<u32>;
/// Minimum number of certifications that must be received to be able to issue
/// certifications.
type MinReceivedCertToBeAbleToIssueCert: Get<u32>;
/// Handler for NewCert event
type OnNewcert: OnNewcert<Self::IdtyIndex>;
/// Handler for Removed event
type OnRemovedCert: OnRemovedCert<Self::IdtyIndex>;
/// Duration of validity of a certification
type ValidityPeriod: Get<Self::BlockNumber>;
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
pub certs_by_receiver: BTreeMap<T::IdtyIndex, BTreeMap<T::IdtyIndex, T::BlockNumber>>,
#[cfg(feature = "std")]
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self {
Self {
certs_by_receiver: Default::default(),
#[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> {
fn build(&self) {
let mut cert_meta_by_issuer =
BTreeMap::<T::IdtyIndex, IdtyCertMeta<T::BlockNumber>>::new();
let mut certs_removable_on =
BTreeMap::<T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>>::new();
for (receiver, issuers) in &self.certs_by_receiver {
// Forbid self-cert
!issuers.contains_key(receiver),
"Identity cannot certify it-self."
);
// We should insert cert_meta for receivers that have not issued any cert.
cert_meta_by_issuer
.entry(*receiver)
.or_insert(IdtyCertMeta {
issued_count: 0,
next_issuable_on: sp_runtime::traits::Zero::zero(),
received_count: issuers.len() as u32,
});
for (issuer, removable_on) in issuers {
// Count issued certs
cert_meta_by_issuer
.entry(*issuer)
.or_insert(IdtyCertMeta {
issued_count: 0,
next_issuable_on: sp_runtime::traits::Zero::zero(),
received_count: issuers.len() as u32,
})
.issued_count += 1;
// Prepare CertsRemovableOn
certs_removable_on
.entry(*removable_on)
.or_default()
.push((*issuer, *receiver));
if self.apply_cert_period_at_genesis {
let issuer_next_issuable_on = removable_on
.saturating_add(T::CertPeriod::get())
.saturating_sub(T::ValidityPeriod::get());
if let Some(cert_meta) = cert_meta_by_issuer.get_mut(issuer) {
if cert_meta.next_issuable_on < issuer_next_issuable_on {
cert_meta.next_issuable_on = issuer_next_issuable_on;
}
// Write CertsByReceiver
let mut issuers: Vec<_> = issuers.iter().collect();
issuers.sort();
CertsByReceiver::<T, I>::insert(receiver, issuers);
// Write StorageIdtyCertMeta
for (issuer, cert_meta) in cert_meta_by_issuer {
assert!(
!cert_meta.issued_count >= T::MaxByIssuer::get(),
"Identity n°{:?} exceed MaxByIssuer.",
issuer
);
assert!(
!cert_meta.received_count >= T::MinReceivedCertToBeAbleToIssueCert::get(),
"Identity n°{:?} not respect MinReceivedCertToBeAbleToIssueCert.",
issuer
);
StorageIdtyCertMeta::<T, I>::insert(issuer, cert_meta);
}
// Write storage StorageCertsRemovableOn
for (removable_on, certs) in certs_removable_on {
StorageCertsRemovableOn::<T, I>::insert(removable_on, certs);
/// Certifications metada by issuer
#[pallet::storage]
#[pallet::getter(fn idty_cert_meta)]
pub type StorageIdtyCertMeta<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::IdtyIndex, IdtyCertMeta<T::BlockNumber>, ValueQuery>;
/// Certifications by receiver
#[pallet::storage]
#[pallet::getter(fn certs_by_receiver)]
pub type CertsByReceiver<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::IdtyIndex, Vec<(T::IdtyIndex, T::BlockNumber)>, ValueQuery>;
/// Certifications removable on
#[pallet::storage]
#[pallet::getter(fn certs_removable_on)]
pub type StorageCertsRemovableOn<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>, OptionQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> {
/// New certification
/// [issuer, issuer_issued_count, receiver, receiver_received_count]
NewCert {
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
receiver_received_count: u32,
},
/// Removed certification
/// [issuer, issuer_issued_count, receiver, receiver_received_count, expiration]
RemovedCert {
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
receiver_received_count: u32,
expiration: bool,
},
/// Renewed certification
/// [issuer, receiver]
RenewedCert {
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
},
}
#[pallet::error]
pub enum Error<T, I = ()> {
/// An identity cannot certify itself
CannotCertifySelf,
/// Certification non autorisée
CertNotAllowed,
/// An identity must receive certifications before it can issue them.
IdtyMustReceiveCertsBeforeCanIssue,
/// This identity has already issued the maximum number of certifications
IssuedTooManyCert,
/// Issuer not found
IssuerNotFound,
/// Not enough certifications received
NotEnoughCertReceived,
/// This identity has already issued a certification too recently
NotRespectCertPeriod,
/// Receiver not found
ReceiverNotFound,
#[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
fn on_initialize(n: T::BlockNumber) -> Weight {
Self::prune_certifications(n)
}
}
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::weight(1_000_000_000)]
origin: OriginFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
let block_number = frame_system::pallet::Pallet::<T>::block_number();
if verify_rules {
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T, I>>::get(issuer);
if issuer_idty_cert_meta.received_count == 0 {
return Err(Error::<T, I>::IdtyMustReceiveCertsBeforeCanIssue.into());
}
if issuer_idty_cert_meta.received_count
< T::MinReceivedCertToBeAbleToIssueCert::get()
{
return Err(Error::<T, I>::NotEnoughCertReceived.into());
} else if issuer_idty_cert_meta.next_issuable_on > block_number {
return Err(Error::<T, I>::NotRespectCertPeriod.into());
} else if issuer_idty_cert_meta.issued_count >= T::MaxByIssuer::get() {
return Err(Error::<T, I>::IssuedTooManyCert.into());
}
Self::do_add_cert(block_number, issuer, receiver)
/// Add a new certification or renew an existing one
///
/// - `receiver`: the account receiving the certification from the origin
///
/// The origin must be allow to certify.
#[pallet::weight(1_000_000_000)]
pub fn add_cert(
origin: OriginFor<T>,
receiver: T::AccountId,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
ensure!(who != receiver, Error::<T, I>::CannotCertifySelf);
let issuer = T::IdtyIndexOf::convert(who).ok_or(Error::<T, I>::IssuerNotFound)?;
let receiver =
T::IdtyIndexOf::convert(receiver).ok_or(Error::<T, I>::ReceiverNotFound)?;
if !T::IsCertAllowed::is_cert_allowed(issuer, receiver) {
return Err(Error::<T, I>::CertNotAllowed.into());
}
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T, I>>::get(issuer);
if issuer_idty_cert_meta.received_count == 0 {
return Err(Error::<T, I>::IdtyMustReceiveCertsBeforeCanIssue.into());
}
let block_number = frame_system::pallet::Pallet::<T>::block_number();
// Verify rules CertPeriod and MaxByIssuer
if issuer_idty_cert_meta.received_count < T::MinReceivedCertToBeAbleToIssueCert::get() {
return Err(Error::<T, I>::NotEnoughCertReceived.into());
} else if issuer_idty_cert_meta.next_issuable_on > block_number {
return Err(Error::<T, I>::NotRespectCertPeriod.into());
} else if issuer_idty_cert_meta.issued_count >= T::MaxByIssuer::get() {
return Err(Error::<T, I>::IssuedTooManyCert.into());
}
Self::do_add_cert(block_number, issuer, receiver)
#[pallet::weight(1_000_000_000)]
pub fn del_cert(
origin: OriginFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
Self::remove_cert_inner(issuer, receiver, None);
Ok(().into())
}
#[pallet::weight(1_000_000_000)]
pub fn remove_all_certs_received_by(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
for (issuer, _) in CertsByReceiver::<T, I>::get(idty_index) {
Self::remove_cert_inner(issuer, idty_index, None);
}
Ok(().into())
}
}
// INTERNAL FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> {
fn do_add_cert(
block_number: T::BlockNumber,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
// Write StorageCertsRemovableOn
let removable_on = block_number + T::ValidityPeriod::get();
<StorageCertsRemovableOn<T, I>>::append(removable_on, (issuer, receiver));
// Write CertsByReceiver
let mut created = false;
CertsByReceiver::<T, I>::mutate_exists(receiver, |maybe_issuers| {
let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0));
if let Err(index) = issuers.binary_search_by(|(issuer_, _)| issuer.cmp(issuer_)) {
issuers.insert(index, (issuer, removable_on));
created = true;
}
});
if created {
// Write StorageIdtyCertMeta for issuer
let issuer_issued_count =
StorageIdtyCertMeta::<T, I>::mutate(issuer, |issuer_idty_cert_meta| {
issuer_idty_cert_meta.issued_count =
issuer_idty_cert_meta.issued_count.saturating_add(1);
issuer_idty_cert_meta.next_issuable_on =
block_number + T::CertPeriod::get();
issuer_idty_cert_meta.issued_count
});
// Write StorageIdtyCertMeta for receiver
let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.received_count = cert_meta.received_count.saturating_add(1);
cert_meta.received_count
});
Self::deposit_event(Event::NewCert {
issuer,
issuer_issued_count,
receiver,
receiver_received_count,
});
T::OnNewcert::on_new_cert(
issuer,
issuer_issued_count,
receiver,
receiver_received_count,
);
} else {
Self::deposit_event(Event::RenewedCert { issuer, receiver });
fn prune_certifications(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
if let Some(certs) = StorageCertsRemovableOn::<T, I>::take(block_number) {
for (issuer, receiver) in certs {
total_weight += Self::remove_cert_inner(issuer, receiver, Some(block_number));
}
}
total_weight
}
fn remove_cert_inner(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number_opt: Option<T::BlockNumber>,
) -> Weight {
let mut total_weight: Weight = 0;
let mut removed = false;
CertsByReceiver::<T, I>::mutate_exists(receiver, |issuers_opt| {
let issuers = issuers_opt.get_or_insert(Vec::with_capacity(0));
if let Ok(index) = issuers.binary_search_by(|(issuer_, _)| issuer.cmp(issuer_)) {
if let Some(block_number) = block_number_opt {
if let Some((_, removable_on)) = issuers.get(index) {
if *removable_on == block_number {
issuers.remove(index);
removed = true;
}
}
} else {
issuers.remove(index);
});
if removed {
let issuer_issued_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(issuer, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.issued_count = cert_meta.issued_count.saturating_sub(1);
cert_meta.issued_count
});
let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.received_count = cert_meta.received_count.saturating_sub(1);
cert_meta.received_count
});
issuer,
issuer_issued_count,
receiver,
receiver_received_count,
expiration: block_number_opt.is_some(),
});
total_weight += T::OnRemovedCert::on_removed_cert(
issuer,
issuer_issued_count,
receiver,
receiver_received_count,
block_number_opt.is_some(),
);
impl<T: Config<I>, I: 'static> SetNextIssuableOn<T::BlockNumber, T::IdtyIndex> for Pallet<T, I> {
fn set_next_issuable_on(
idty_index: T::IdtyIndex,
next_issuable_on: T::BlockNumber,
) -> frame_support::pallet_prelude::Weight {
<StorageIdtyCertMeta<T, I>>::mutate_exists(idty_index, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.next_issuable_on = next_issuable_on;
});
0
}
}