Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • fix_picked_up_file_in_runtime_release
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • network/gtest-1000
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • runtime/gtest-1000
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • tuxmain/fix-change-owner-key
  • update-docker-compose-rpc-squid-names
  • upgradable-multisig
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • gtest-1000
  • gtest-1000-0.11.0
  • gtest-1000-0.11.1
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
88 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-rework-certs
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-cucumber-identity
  • master
  • no-bootnodes
  • poc-oneshot-accounts
  • release/runtime-100
  • release/runtime-200
  • ts-types
  • ud-time-64
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • v0.1.0
28 results
Show changes
Showing
with 3343 additions and 740 deletions
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Certification Pallet
//!
//! This pallet manages certification creation and deletion.
//!
//! Duniter certifications are the *edges* in the Duniter [Web of Trust](../duniter-wot/). They can have different meanings:
//!
//! - In the case of the main WoT, they mean "I have met this person in real life and trust them" (see Ğ1 Licence).
//! - In the case of the smith sub-WoT, they mean "I trust this person to be able to run Duniter securely" (see smith Licence).
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod benchmarking;
pub mod traits; pub mod traits;
mod types; mod types;
pub mod weights;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
...@@ -25,40 +37,40 @@ mod mock; ...@@ -25,40 +37,40 @@ mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use frame_system::pallet_prelude::BlockNumberFor;
pub use pallet::*; pub use pallet::*;
pub use types::*; pub use types::*;
pub use weights::WeightInfo;
use crate::traits::*; use crate::traits::*;
use codec::Codec; use codec::Codec;
use frame_support::traits::StorageVersion; use duniter_primitives::Idty;
use frame_support::{pallet_prelude::*, traits::StorageVersion};
use scale_info::prelude::{collections::BTreeMap, fmt::Debug, vec::Vec};
use sp_runtime::traits::AtLeast32BitUnsigned; use sp_runtime::traits::AtLeast32BitUnsigned;
use sp_std::{fmt::Debug, vec::Vec};
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use sp_runtime::traits::{Convert, Saturating}; use sp_runtime::traits::Saturating;
use sp_std::collections::btree_map::BTreeMap;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)] #[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info] #[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); pub struct Pallet<T>(PhantomData<T>);
#[pallet::config] #[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config { pub trait Config: frame_system::Config {
/// The minimum duration (in blocks) between two certifications issued by the same issuer.
#[pallet::constant] #[pallet::constant]
/// Minimum duration between two certifications issued by the same issuer type CertPeriod: Get<BlockNumberFor<Self>>;
type CertPeriod: Get<Self::BlockNumber>;
/// Because this pallet emits events, it depends on the runtime's definition of an event. /// A short identity index type.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// A short identity index.
type IdtyIndex: Parameter type IdtyIndex: Parameter
+ Member + Member
+ AtLeast32BitUnsigned + AtLeast32BitUnsigned
...@@ -68,94 +80,101 @@ pub mod pallet { ...@@ -68,94 +80,101 @@ pub mod pallet {
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ Debug + Debug
+ MaxEncodedLen; + MaxEncodedLen;
/// Something that give the IdtyIndex on an account id
type IdtyIndexOf: Convert<Self::AccountId, Option<Self::IdtyIndex>>; /// A type that provides methods to get the IdtyIndex of an AccountId and vice versa.
/// type IdtyAttr: duniter_primitives::Idty<Self::IdtyIndex, Self::AccountId>;
type IsCertAllowed: IsCertAllowed<Self::IdtyIndex>;
/// A type that provides a method to check if issuing a certification is allowed.
type CheckCertAllowed: CheckCertAllowed<Self::IdtyIndex>;
/// The maximum number of active certifications that can be issued by a single issuer.
#[pallet::constant] #[pallet::constant]
/// Maximum number of active certifications by issuer
type MaxByIssuer: Get<u32>; type MaxByIssuer: Get<u32>;
/// Minimum number of certifications that must be received to be able to issue
/// certifications. /// The minimum number of certifications received that an identity must have
/// to be allowed to issue a certification.
#[pallet::constant]
type MinReceivedCertToBeAbleToIssueCert: Get<u32>; type MinReceivedCertToBeAbleToIssueCert: Get<u32>;
/// Handler for NewCert event
/// A handler that is called when a new certification event (`NewCert`) occurs.
type OnNewcert: OnNewcert<Self::IdtyIndex>; type OnNewcert: OnNewcert<Self::IdtyIndex>;
/// Handler for Removed event
/// A handler that is called when a certification is removed (`RemovedCert`).
type OnRemovedCert: OnRemovedCert<Self::IdtyIndex>; type OnRemovedCert: OnRemovedCert<Self::IdtyIndex>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
/// The duration (in blocks) for which a certification remains valid.
#[pallet::constant] #[pallet::constant]
/// Duration after which a certification is renewable type ValidityPeriod: Get<BlockNumberFor<Self>>;
type CertRenewablePeriod: Get<Self::BlockNumber>;
#[pallet::constant]
/// Duration of validity of a certification
type ValidityPeriod: Get<Self::BlockNumber>;
} }
// GENESIS STUFF // // GENESIS STUFF //
#[pallet::genesis_config] #[pallet::genesis_config]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> { #[allow(clippy::type_complexity)]
pub struct GenesisConfig<T: Config> {
pub apply_cert_period_at_genesis: bool, pub apply_cert_period_at_genesis: bool,
pub certs_by_issuer: BTreeMap<T::IdtyIndex, BTreeMap<T::IdtyIndex, T::BlockNumber>>, pub certs_by_receiver:
BTreeMap<T::IdtyIndex, BTreeMap<T::IdtyIndex, Option<BlockNumberFor<T>>>>,
} }
#[cfg(feature = "std")] impl<T: Config> Default for GenesisConfig<T> {
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self { fn default() -> Self {
Self { Self {
apply_cert_period_at_genesis: false, apply_cert_period_at_genesis: false,
certs_by_issuer: Default::default(), certs_by_receiver: Default::default(),
} }
} }
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
let mut cert_meta_by_issuer = let mut cert_meta_by_issuer =
BTreeMap::<T::IdtyIndex, IdtyCertMeta<T::BlockNumber>>::new(); BTreeMap::<T::IdtyIndex, IdtyCertMeta<BlockNumberFor<T>>>::new();
let mut certs_by_receiver = BTreeMap::<T::IdtyIndex, Vec<T::IdtyIndex>>::new(); let mut certs_removable_on =
for (issuer, receivers) in &self.certs_by_issuer { BTreeMap::<BlockNumberFor<T>, Vec<(T::IdtyIndex, T::IdtyIndex)>>::new();
for (receiver, issuers) in &self.certs_by_receiver {
// Forbid self-cert
assert!( assert!(
!receivers.contains_key(issuer), !issuers.contains_key(receiver),
"Identity cannot certify it-self." "Identity cannot certify it-self."
); );
assert!(
!receivers.len() >= T::MaxByIssuer::get() as usize,
"Identity n°{:?} exceed MaxByIssuer.",
issuer
);
cert_meta_by_issuer.insert( // We should insert cert_meta for receivers that have not issued any cert.
*issuer, cert_meta_by_issuer
IdtyCertMeta { .entry(*receiver)
issued_count: receivers.len() as u32, .or_insert(IdtyCertMeta {
issued_count: 0,
next_issuable_on: sp_runtime::traits::Zero::zero(), next_issuable_on: sp_runtime::traits::Zero::zero(),
received_count: 0, received_count: 0,
}, })
); .received_count = issuers.len() as u32;
for receiver in receivers.keys() {
certs_by_receiver
.entry(*receiver)
.or_default()
.push(*issuer);
}
}
// Write StorageCertsByReceiver let mut issuers_: Vec<_> = Vec::with_capacity(issuers.len());
for (receiver, mut issuers) in certs_by_receiver { for (issuer, maybe_removable_on) in issuers {
// Count issued certs
cert_meta_by_issuer cert_meta_by_issuer
.entry(receiver) .entry(*issuer)
.and_modify(|cert_meta| cert_meta.received_count = issuers.len() as u32); .or_insert(IdtyCertMeta {
issuers.sort(); issued_count: 0,
<StorageCertsByReceiver<T, I>>::insert(receiver, issuers); next_issuable_on: sp_runtime::traits::Zero::zero(),
} received_count: 0,
// Write StorageCertsByIssuer })
let mut certs_removable_on = .issued_count += 1;
BTreeMap::<T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>>::new();
for (issuer, receivers) in &self.certs_by_issuer { // Compute and store removable_on
for (receiver, removable_on) in receivers { let removable_on = maybe_removable_on.unwrap_or_else(T::ValidityPeriod::get);
issuers_.push((*issuer, removable_on));
// Prepare CertsRemovableOn
certs_removable_on certs_removable_on
.entry(*removable_on) .entry(removable_on)
.or_default() .or_default()
.push((*issuer, *receiver)); .push((*issuer, *receiver));
...@@ -169,255 +188,259 @@ pub mod pallet { ...@@ -169,255 +188,259 @@ pub mod pallet {
} }
} }
} }
let renewable_on = removable_on.saturating_sub(
T::ValidityPeriod::get().saturating_sub(T::CertRenewablePeriod::get()),
);
<StorageCertsByIssuer<T, I>>::insert(
issuer,
receiver,
CertValue {
renewable_on,
removable_on: *removable_on,
},
);
} }
// Write CertsByReceiver
issuers_.sort();
CertsByReceiver::<T>::insert(receiver, issuers_);
} }
// Write StorageIdtyCertMeta // Write StorageIdtyCertMeta
for (issuer, cert_meta) in cert_meta_by_issuer { for (issuer, cert_meta) in cert_meta_by_issuer {
<StorageIdtyCertMeta<T, I>>::insert(issuer, cert_meta); 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>::insert(issuer, cert_meta);
} }
// Write storage StorageCertsRemovableOn // Write storage CertsRemovableOn
for (removable_on, certs) in certs_removable_on { for (removable_on, certs) in certs_removable_on {
<StorageCertsRemovableOn<T, I>>::insert(removable_on, certs); CertsRemovableOn::<T>::insert(removable_on, certs);
} }
} }
} }
// STORAGE // // STORAGE //
/// Certifications metada by issuer /// The certification metadata for each issuer.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn idty_cert_meta)] #[pallet::getter(fn idty_cert_meta)]
pub type StorageIdtyCertMeta<T: Config<I>, I: 'static = ()> = pub type StorageIdtyCertMeta<T: Config> =
StorageMap<_, Twox64Concat, T::IdtyIndex, IdtyCertMeta<T::BlockNumber>, ValueQuery>; StorageMap<_, Twox64Concat, T::IdtyIndex, IdtyCertMeta<BlockNumberFor<T>>, ValueQuery>;
/// Certifications by issuer /// The certifications for each receiver.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn cert)] #[pallet::getter(fn certs_by_receiver)]
/// Certifications by issuer pub type CertsByReceiver<T: Config> = StorageMap<
pub(super) type StorageCertsByIssuer<T: Config<I>, I: 'static = ()> = StorageDoubleMap<
_, _,
Twox64Concat, Twox64Concat,
T::IdtyIndex, T::IdtyIndex,
Twox64Concat, Vec<(T::IdtyIndex, BlockNumberFor<T>)>,
T::IdtyIndex, ValueQuery,
CertValue<T::BlockNumber>,
OptionQuery,
GetDefault,
ConstU32<4_000_000_000>,
>; >;
/// Certifications by receiver /// The certifications that should expire at a given block.
#[pallet::storage]
#[pallet::getter(fn certs_by_receiver)]
pub type StorageCertsByReceiver<T: Config<I>, I: 'static = ()> =
StorageMap<_, Twox64Concat, T::IdtyIndex, Vec<T::IdtyIndex>, ValueQuery>;
/// Certifications removable on
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn certs_removable_on)] #[pallet::getter(fn certs_removable_on)]
pub type StorageCertsRemovableOn<T: Config<I>, I: 'static = ()> = pub type CertsRemovableOn<T: Config> = StorageMap<
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>, OptionQuery>; _,
Twox64Concat,
BlockNumberFor<T>,
Vec<(T::IdtyIndex, T::IdtyIndex)>,
OptionQuery,
>;
// EVENTS //
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> { pub enum Event<T: Config> {
/// New certification /// A new certification was added.
/// [issuer, issuer_issued_count, receiver, receiver_received_count] CertAdded {
NewCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
issuer_issued_count: u32,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
receiver_received_count: u32,
}, },
/// Removed certification /// A certification was removed.
/// [issuer, issuer_issued_count, receiver, receiver_received_count, expiration] CertRemoved {
RemovedCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
issuer_issued_count: u32,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
receiver_received_count: u32,
expiration: bool, expiration: bool,
}, },
/// Renewed certification /// A certification was renewed.
/// [issuer, receiver] CertRenewed {
RenewedCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
}, },
} }
// ERRORS //
#[pallet::error] #[pallet::error]
pub enum Error<T, I = ()> { pub enum Error<T> {
/// An identity cannot certify itself /// Issuer of a certification must have an identity
OriginMustHaveAnIdentity,
/// Identity cannot certify itself.
CannotCertifySelf, CannotCertifySelf,
/// Certification non autorisée /// Identity has already issued the maximum number of certifications.
CertNotAllowed,
/// An identity must receive certifications before it can issue them.
IdtyMustReceiveCertsBeforeCanIssue,
/// This identity has already issued the maximum number of certifications
IssuedTooManyCert, IssuedTooManyCert,
/// Issuer not found /// Insufficient certifications received.
IssuerNotFound,
/// Not enough certifications received
NotEnoughCertReceived, NotEnoughCertReceived,
/// This identity has already issued a certification too recently /// Identity has issued a certification too recently.
NotRespectCertPeriod, NotRespectCertPeriod,
/// This certification has already been issued or renewed recently /// Can not add an already-existing cert
NotRespectRenewablePeriod, CertAlreadyExists,
/// Receiver not found /// Can not renew a non-existing cert
ReceiverNotFound, CertDoesNotExist,
} }
#[pallet::hooks] #[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight { fn on_initialize(n: BlockNumberFor<T>) -> Weight {
Self::prune_certifications(n) Self::prune_certifications(n).saturating_add(T::WeightInfo::on_initialize())
} }
} }
// CALLS // // CALLS //
#[pallet::call] #[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config> Pallet<T> {
#[pallet::weight(1_000_000_000)] /// Add a new certification.
pub fn force_add_cert( #[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::add_cert())]
pub fn add_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
verify_rules: bool,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; let issuer = Self::origin_to_index(origin)?;
let block_number = frame_system::pallet::Pallet::<T>::block_number(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
Self::check_add_cert(issuer, receiver, block_number)?;
let create = if verify_rules { Self::try_add_cert(block_number, issuer, receiver)?;
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T, I>>::get(issuer); Ok(().into())
if issuer_idty_cert_meta.received_count == 0 {
return Err(Error::<T, I>::IdtyMustReceiveCertsBeforeCanIssue.into());
}
// 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());
}
// Verify rule CertRenewablePeriod
if let Ok(CertValue { renewable_on, .. }) =
<StorageCertsByIssuer<T, I>>::try_get(issuer, receiver)
{
if renewable_on > block_number {
return Err(Error::<T, I>::NotRespectRenewablePeriod.into());
}
false
} else {
true
} }
} else {
!StorageCertsByIssuer::<T, I>::contains_key(issuer, receiver)
};
Self::do_add_cert(block_number, create, issuer, receiver) /// Renew an existing certification.
} #[pallet::call_index(3)]
#[pallet::weight(1_000_000_000)] #[pallet::weight(T::WeightInfo::renew_cert())]
pub fn add_cert( pub fn renew_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
receiver: T::AccountId, receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?; let issuer = Self::origin_to_index(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(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
Self::check_renew_cert(issuer, receiver, block_number)?;
// Verify rules CertPeriod and MaxByIssuer Self::try_renew_cert(block_number, issuer, receiver)?;
if issuer_idty_cert_meta.received_count < T::MinReceivedCertToBeAbleToIssueCert::get() { Ok(().into())
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());
}
// Verify rule CertRenewablePeriod
let create = if let Ok(CertValue { renewable_on, .. }) =
<StorageCertsByIssuer<T, I>>::try_get(issuer, receiver)
{
if renewable_on > block_number {
return Err(Error::<T, I>::NotRespectRenewablePeriod.into());
}
false
} else {
true
};
Self::do_add_cert(block_number, create, issuer, receiver)
} }
#[pallet::weight(1_000_000_000)] /// Remove one certification given the issuer and the receiver.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::del_cert())]
pub fn del_cert( pub fn del_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; ensure_root(origin)?;
Self::remove_cert_inner(issuer, receiver, None); Self::do_remove_cert(issuer, receiver, None);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)] /// Remove all certifications received by an identity.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::remove_all_certs_received_by(CertsByReceiver::<T>::get(idty_index).len() as u32))]
pub fn remove_all_certs_received_by( pub fn remove_all_certs_received_by(
origin: OriginFor<T>, origin: OriginFor<T>,
idty_index: T::IdtyIndex, idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; ensure_root(origin)?;
if let Ok(issuers) = <StorageCertsByReceiver<T, I>>::try_get(idty_index) { let _ = Self::do_remove_all_certs_received_by(idty_index);
for issuer in issuers {
Self::remove_cert_inner(issuer, idty_index, None);
}
}
Ok(().into()) Ok(().into())
} }
} }
// INTERNAL FUNCTIONS // // INTERNAL FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config> Pallet<T> {
fn do_add_cert( /// Perform removal of all certifications received by an identity.
block_number: T::BlockNumber, pub fn do_remove_all_certs_received_by(idty_index: T::IdtyIndex) -> Weight {
create: bool, let received_certs = CertsByReceiver::<T>::take(idty_index);
for (receiver_received_count, (issuer, _)) in received_certs.iter().enumerate().rev() {
let issuer_issued_count =
<StorageIdtyCertMeta<T>>::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
});
T::OnRemovedCert::on_removed_cert(
*issuer,
issuer_issued_count,
idty_index,
receiver_received_count as u32,
false,
);
Self::deposit_event(Event::CertRemoved {
issuer: *issuer,
receiver: idty_index,
expiration: false,
});
}
T::WeightInfo::do_remove_all_certs_received_by(received_certs.len() as u32)
}
/// Get the issuer index from the origin.
pub fn origin_to_index(origin: OriginFor<T>) -> Result<T::IdtyIndex, DispatchError> {
let who = ensure_signed(origin)?;
T::IdtyAttr::idty_index(who).ok_or(Error::<T>::OriginMustHaveAnIdentity.into())
}
/// Add a certification without performing checks.
///
/// This function is used during identity creation to add the first certification without
/// validation checks.
// The weight is approximated based on the worst-case scenario path.
pub fn do_add_cert_checked(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
verify_rules: bool,
) -> DispatchResultWithPostInfo {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
if verify_rules {
// only verify internal rules if asked
Self::check_add_cert_internal(issuer, receiver, block_number)?;
};
Self::try_add_cert(block_number, issuer, receiver)?;
Ok(().into())
}
/// Perform certification addition if it does not already exist, otherwise return `CertAlreadyExists`.
// must be transactional
fn try_add_cert(
block_number: BlockNumberFor<T>,
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
// Write CertsRemovableOn
let removable_on = block_number + T::ValidityPeriod::get();
<CertsRemovableOn<T>>::append(removable_on, (issuer, receiver));
// Write CertsByReceiver
CertsByReceiver::<T>::mutate_exists(receiver, |maybe_issuers| {
let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0));
// cert does not exist, must be created
if let Err(index) = issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer)) {
issuers.insert(index, (issuer, removable_on));
Ok(())
} else {
// cert exists, must be renewed instead
Err(Error::<T>::CertAlreadyExists)
}
})?;
// Write StorageIdtyCertMeta for issuer // Write StorageIdtyCertMeta for issuer
let issuer_issued_count = let issuer_issued_count =
StorageIdtyCertMeta::<T, I>::mutate(issuer, |issuer_idty_cert_meta| { StorageIdtyCertMeta::<T>::mutate(issuer, |issuer_idty_cert_meta| {
issuer_idty_cert_meta.issued_count = issuer_idty_cert_meta.issued_count =
issuer_idty_cert_meta.issued_count.saturating_add(1); 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.next_issuable_on = block_number + T::CertPeriod::get();
...@@ -426,123 +449,213 @@ pub mod pallet { ...@@ -426,123 +449,213 @@ pub mod pallet {
// Write StorageIdtyCertMeta for receiver // Write StorageIdtyCertMeta for receiver
let receiver_received_count = let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); 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 = cert_meta.received_count.saturating_add(1);
cert_meta.received_count cert_meta.received_count
}); });
// Write StorageCertsRemovableOn and StorageCertsByIssuer // emit CertAdded event
let cert_value = CertValue { Self::deposit_event(Event::CertAdded { issuer, receiver });
renewable_on: block_number + T::CertRenewablePeriod::get(),
removable_on: block_number + T::ValidityPeriod::get(),
};
<StorageCertsRemovableOn<T, I>>::append(cert_value.removable_on, (issuer, receiver));
<StorageCertsByIssuer<T, I>>::insert(issuer, receiver, cert_value);
if create {
// Write StorageCertsByReceiver
<StorageCertsByReceiver<T, I>>::mutate_exists(receiver, |issuers_opt| {
let issuers = issuers_opt.get_or_insert(Vec::with_capacity(0));
if let Err(index) = issuers.binary_search(&issuer) {
issuers.insert(index, issuer);
}
});
Self::deposit_event(Event::NewCert {
issuer,
issuer_issued_count,
receiver,
receiver_received_count,
});
T::OnNewcert::on_new_cert( T::OnNewcert::on_new_cert(
issuer, issuer,
issuer_issued_count, issuer_issued_count,
receiver, receiver,
receiver_received_count, receiver_received_count,
); );
} else { Ok(().into())
Self::deposit_event(Event::RenewedCert { issuer, receiver });
} }
/// Perform certification renewal if it exists, otherwise return an error indicating `CertDoesNotExist`.
// must be used in transactional context
// (it can fail if certification does not exist after having modified state)
fn try_renew_cert(
block_number: BlockNumberFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
// Write CertsRemovableOn
let removable_on = block_number + T::ValidityPeriod::get();
<CertsRemovableOn<T>>::append(removable_on, (issuer, receiver));
// Write CertsByReceiver
CertsByReceiver::<T>::mutate_exists(receiver, |maybe_issuers| {
let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0));
// cert exists, can be renewed
if let Ok(index) = issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer)) {
issuers[index] = (issuer, removable_on);
Ok(())
} else {
// cert does not exist, must be created
Err(Error::<T>::CertDoesNotExist)
}
})?;
// Update next_issuable_on in StorageIdtyCertMeta for issuer
StorageIdtyCertMeta::<T>::mutate(issuer, |issuer_idty_cert_meta| {
issuer_idty_cert_meta.next_issuable_on = block_number + T::CertPeriod::get();
});
// emit CertRenewed event
Self::deposit_event(Event::CertRenewed { issuer, receiver });
Ok(().into()) Ok(().into())
} }
fn prune_certifications(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
if let Some(certs) = StorageCertsRemovableOn::<T, I>::take(block_number) { /// Remove certifications that are due to expire on the given block.
// (run at on_initialize step)
fn prune_certifications(block_number: BlockNumberFor<T>) -> Weight {
// See on initialize for the overhead weight accounting
let mut weight = Weight::zero();
if let Some(certs) = CertsRemovableOn::<T>::take(block_number) {
for (issuer, receiver) in certs { for (issuer, receiver) in certs {
total_weight += Self::remove_cert_inner(issuer, receiver, Some(block_number)); weight = weight.saturating_add(Self::do_remove_cert(
issuer,
receiver,
Some(block_number),
));
} }
} }
weight
total_weight
} }
fn remove_cert_inner(
/// Perform the certification removal.
///
/// If a block number is provided, this function removes certifications only if they are still
/// scheduled to expire at that block number.
// This function is used because the unscheduling of certification expiry (#110) is not yet implemented.
pub fn do_remove_cert(
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
block_number_opt: Option<T::BlockNumber>, block_number_opt: Option<BlockNumberFor<T>>,
) -> Weight { ) -> Weight {
let mut total_weight: Weight = 0; let mut total_weight = Weight::zero();
let mut removed = false; let mut removed = false;
<StorageCertsByIssuer<T, I>>::mutate_exists(issuer, receiver, |cert_val_opt| { CertsByReceiver::<T>::mutate_exists(receiver, |issuers_opt| {
if let Some(cert_val) = cert_val_opt { let issuers = issuers_opt.get_or_insert(Vec::with_capacity(0));
if Some(cert_val.removable_on) == block_number_opt || block_number_opt.is_none() 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) {
// only remove cert if block number is matching
if *removable_on == block_number {
issuers.remove(index);
removed = true; removed = true;
} }
} }
if removed { } else {
cert_val_opt.take();
}
});
if removed {
<StorageCertsByReceiver<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(&issuer) {
issuers.remove(index); issuers.remove(index);
removed = true;
}
} else {
total_weight += T::WeightInfo::do_remove_cert_noop();
} }
}); });
if removed {
let issuer_issued_count = let issuer_issued_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(issuer, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(issuer, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); 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 = cert_meta.issued_count.saturating_sub(1);
cert_meta.issued_count cert_meta.issued_count
}); });
let receiver_received_count = let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); 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 = cert_meta.received_count.saturating_sub(1);
cert_meta.received_count cert_meta.received_count
}); });
Self::deposit_event(Event::RemovedCert { Self::deposit_event(Event::CertRemoved {
issuer, issuer,
issuer_issued_count,
receiver, receiver,
receiver_received_count,
expiration: block_number_opt.is_some(), expiration: block_number_opt.is_some(),
}); });
total_weight += T::OnRemovedCert::on_removed_cert( T::OnRemovedCert::on_removed_cert(
issuer, issuer,
issuer_issued_count, issuer_issued_count,
receiver, receiver,
receiver_received_count, receiver_received_count,
block_number_opt.is_some(), block_number_opt.is_some(),
); );
// Pessimistic overhead estimation based on the worst path of a successfull
// certificate removal to avoid multiplying benchmarks for every branching,
// include the OnRemovedCert weight.
total_weight.saturating_add(T::WeightInfo::do_remove_cert());
} }
total_weight total_weight
} }
/// Check if adding a certification is allowed.
// 1. no self cert
// 2. issuer received cert count
// 3. issuer max emitted cert
// 4. issuer cert period
fn check_add_cert_internal(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number: BlockNumberFor<T>,
) -> DispatchResult {
// 1. Forbid self cert
ensure!(issuer != receiver, Error::<T>::CannotCertifySelf);
// 2. Verify rule MinReceivedCertToBeAbleToIssueCert
// (this number can differ from the one necessary to be member)
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T>>::get(issuer);
ensure!(
issuer_idty_cert_meta.received_count
>= T::MinReceivedCertToBeAbleToIssueCert::get(),
Error::<T>::NotEnoughCertReceived
);
// 3. Verify rule MaxByIssuer
ensure!(
issuer_idty_cert_meta.issued_count < T::MaxByIssuer::get(),
Error::<T>::IssuedTooManyCert
);
// 4. Verify rule CertPeriod
ensure!(
block_number >= issuer_idty_cert_meta.next_issuable_on,
Error::<T>::NotRespectCertPeriod
);
Ok(())
} }
/// Check if adding a certification is allowed.
// first internal checks
// then external checks
fn check_add_cert(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number: BlockNumberFor<T>,
) -> DispatchResult {
// internal checks
Self::check_add_cert_internal(issuer, receiver, block_number)?;
// --- then external checks
// - issuer is member
// - receiver is confirmed
// - receiver is not revoked
T::CheckCertAllowed::check_cert_allowed(issuer, receiver)?;
Ok(())
} }
impl<T: Config<I>, I: 'static> SetNextIssuableOn<T::BlockNumber, T::IdtyIndex> for Pallet<T, I> { /// Check if renewing a certification is allowed based.
fn set_next_issuable_on( fn check_renew_cert(
idty_index: T::IdtyIndex, issuer: T::IdtyIndex,
next_issuable_on: T::BlockNumber, receiver: T::IdtyIndex,
) -> frame_support::pallet_prelude::Weight { block_number: BlockNumberFor<T>,
<StorageIdtyCertMeta<T, I>>::mutate_exists(idty_index, |cert_meta_opt| { ) -> DispatchResult {
Self::check_add_cert_internal(issuer, receiver, block_number)?;
T::CheckCertAllowed::check_cert_allowed(issuer, receiver)?;
Ok(())
}
}
}
// implement setting next_issuable_on for certification period
impl<T: Config> SetNextIssuableOn<BlockNumberFor<T>, T::IdtyIndex> for Pallet<T> {
fn set_next_issuable_on(idty_index: T::IdtyIndex, next_issuable_on: BlockNumberFor<T>) {
<StorageIdtyCertMeta<T>>::mutate_exists(idty_index, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.next_issuable_on = next_issuable_on; cert_meta.next_issuable_on = next_issuable_on;
}); });
0
} }
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::{self as pallet_certification}; use crate::{self as pallet_certification};
use frame_support::{ use frame_support::{
parameter_types, derive_impl, parameter_types,
traits::{Everything, OnFinalize, OnInitialize}, traits::{Everything, OnFinalize, OnInitialize},
}; };
use frame_system as system; use frame_system as system;
use sp_core::H256; use sp_core::H256;
use sp_runtime::{ use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup}, traits::{BlakeTwo256, IdentityLookup},
BuildStorage, BuildStorage,
}; };
type AccountId = u64; type AccountId = u64;
type BlockNumber = u64;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
pub type IdtyIndex = u64; pub type IdtyIndex = u64;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
// Configure a mock runtime to test the pallet. // Configure a mock runtime to test the pallet.
frame_support::construct_runtime!( frame_support::construct_runtime!(
pub enum Test where pub enum Test {
Block = Block, System: frame_system,
NodeBlock = Block, DefaultCertification: pallet_certification,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
DefaultCertification: pallet_certification::{Pallet, Call, Event<T>, Storage, Config<T>},
} }
); );
...@@ -50,74 +43,50 @@ parameter_types! { ...@@ -50,74 +43,50 @@ parameter_types! {
pub const SS58Prefix: u8 = 42; pub const SS58Prefix: u8 = 42;
} }
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test { impl system::Config for Test {
type AccountId = AccountId;
type BaseCallFilter = Everything; type BaseCallFilter = Everything;
type BlockWeights = (); type Block = Block;
type BlockLength = (); type BlockHashCount = BlockHashCount;
type DbWeight = ();
type Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = BlockNumber;
type Hash = H256; type Hash = H256;
type Hashing = BlakeTwo256; type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>; type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header; type MaxConsumers = frame_support::traits::ConstU32<16>;
type Event = Event; type Nonce = u64;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo; type PalletInfo = PalletInfo;
type AccountData = (); type RuntimeCall = RuntimeCall;
type OnNewAccount = (); type RuntimeEvent = RuntimeEvent;
type OnKilledAccount = (); type RuntimeOrigin = RuntimeOrigin;
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix; type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
pub struct EnsureRoot;
impl frame_support::traits::EnsureOrigin<(Origin, IdtyIndex, IdtyIndex)> for EnsureRoot {
type Success = ();
fn try_origin(
o: (Origin, IdtyIndex, IdtyIndex),
) -> Result<Self::Success, (Origin, IdtyIndex, IdtyIndex)> {
match o.0.clone().into() {
Ok(system::RawOrigin::Root) => Ok(()),
_ => Err(o),
}
}
} }
parameter_types! { parameter_types! {
pub const MaxByIssuer: u32 = 3; pub const MaxByIssuer: u32 = 4;
pub const MinReceivedCertToBeAbleToIssueCert: u32 = 2; pub const MinReceivedCertToBeAbleToIssueCert: u32 = 2;
pub const RenewablePeriod: BlockNumber = 4;
pub const CertPeriod: u64 = 2; pub const CertPeriod: u64 = 2;
pub const ValidityPeriod: u64 = 10; pub const ValidityPeriod: u64 = 10;
} }
impl pallet_certification::Config for Test { impl pallet_certification::Config for Test {
type CertPeriod = CertPeriod; type CertPeriod = CertPeriod;
type Event = Event; type CheckCertAllowed = ();
type IdtyAttr = ();
type IdtyIndex = IdtyIndex; type IdtyIndex = IdtyIndex;
type IdtyIndexOf = sp_runtime::traits::ConvertInto;
type IsCertAllowed = ();
type MaxByIssuer = MaxByIssuer; type MaxByIssuer = MaxByIssuer;
type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert; type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert;
type OnNewcert = (); type OnNewcert = ();
type OnRemovedCert = (); type OnRemovedCert = ();
type CertRenewablePeriod = RenewablePeriod; type RuntimeEvent = RuntimeEvent;
type ValidityPeriod = ValidityPeriod; type ValidityPeriod = ValidityPeriod;
type WeightInfo = ();
} }
// Build genesis storage according to the mock runtime. // Build genesis storage according to the mock runtime.
pub fn new_test_ext( pub fn new_test_ext(
gen_conf: pallet_certification::GenesisConfig<Test>, gen_conf: pallet_certification::GenesisConfig<Test>,
) -> sp_io::TestExternalities { ) -> sp_io::TestExternalities {
GenesisConfig { RuntimeGenesisConfig {
system: SystemConfig::default(), system: SystemConfig::default(),
default_certification: gen_conf, default_certification: gen_conf,
} }
...@@ -130,6 +99,7 @@ pub fn run_to_block(n: u64) { ...@@ -130,6 +99,7 @@ pub fn run_to_block(n: u64) {
while System::block_number() < n { while System::block_number() < n {
DefaultCertification::on_finalize(System::block_number()); DefaultCertification::on_finalize(System::block_number());
System::on_finalize(System::block_number()); System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1); System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number()); System::on_initialize(System::block_number());
DefaultCertification::on_initialize(System::block_number()); DefaultCertification::on_initialize(System::block_number());
......
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::mock::Event as RuntimeEvent; use crate::{mock::*, Error, Event};
use crate::mock::*; use frame_support::{assert_noop, assert_ok};
use crate::{Error, Event};
use frame_support::assert_ok;
//use frame_system::{EventRecord, Phase};
use maplit::btreemap; use maplit::btreemap;
use sp_std::collections::btree_map::BTreeMap; use scale_info::prelude::{collections::BTreeMap, vec};
#[test] #[test]
fn test_must_receive_cert_before_can_issue() { fn test_must_receive_cert_before_can_issue() {
new_test_ext(DefaultCertificationConfig { new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_issuer: BTreeMap::new(), certs_by_receiver: BTreeMap::new(),
}) })
.execute_with(|| { .execute_with(|| {
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 1), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 1),
Err(Error::<Test, _>::IdtyMustReceiveCertsBeforeCanIssue.into()) Err(Error::<Test>::NotEnoughCertReceived.into())
); );
}); });
} }
...@@ -40,15 +37,10 @@ fn test_must_receive_cert_before_can_issue() { ...@@ -40,15 +37,10 @@ fn test_must_receive_cert_before_can_issue() {
fn test_cannot_certify_self() { fn test_cannot_certify_self() {
new_test_ext(DefaultCertificationConfig { new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_issuer: btreemap![ certs_by_receiver: btreemap![
1 => btreemap![ 0 => btreemap![
0 => 5, 1 => Some(5),
], 2 => Some(5),
2 => btreemap![
0 => 5,
],
3 => btreemap![
0 => 5,
], ],
], ],
}) })
...@@ -56,8 +48,8 @@ fn test_cannot_certify_self() { ...@@ -56,8 +48,8 @@ fn test_cannot_certify_self() {
run_to_block(2); run_to_block(2);
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 0), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0),
Err(Error::<Test, _>::CannotCertifySelf.into()) Err(Error::<Test>::CannotCertifySelf.into())
); );
}); });
} }
...@@ -66,18 +58,18 @@ fn test_cannot_certify_self() { ...@@ -66,18 +58,18 @@ fn test_cannot_certify_self() {
fn test_genesis_build() { fn test_genesis_build() {
new_test_ext(DefaultCertificationConfig { new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_issuer: btreemap![ certs_by_receiver: btreemap![
0 => btreemap![ 0 => btreemap![
1 => 10, 1 => Some(7),
2 => 5, 2 => Some(9),
], ],
1 => btreemap![ 1 => btreemap![
0 => 7, 0 => Some(10),
2 => 4, 2 => Some(3),
], ],
2 => btreemap![ 2 => btreemap![
0 => 9, 0 => Some(5),
1 => 3, 1 => Some(4),
], ],
], ],
}) })
...@@ -115,21 +107,14 @@ fn test_genesis_build() { ...@@ -115,21 +107,14 @@ fn test_genesis_build() {
DefaultCertification::certs_removable_on(3), DefaultCertification::certs_removable_on(3),
Some(vec![(2, 1)]), Some(vec![(2, 1)]),
); );
// Cert 2->0 cannot be renewed before #5
assert_eq!(
DefaultCertification::add_cert(Origin::signed(2), 0),
Err(Error::<Test, _>::NotRespectRenewablePeriod.into())
);
run_to_block(3); run_to_block(3);
// Cert 2->1 must have expired // Cert 2->1 must have expired
assert_eq!( assert_eq!(
System::events()[0].event, System::events()[0].event,
RuntimeEvent::DefaultCertification(Event::RemovedCert { RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 2, issuer: 2,
issuer_issued_count: 1,
receiver: 1, receiver: 1,
receiver_received_count: 1,
expiration: true, expiration: true,
},) },)
); );
...@@ -140,53 +125,225 @@ fn test_genesis_build() { ...@@ -140,53 +125,225 @@ fn test_genesis_build() {
fn test_cert_period() { fn test_cert_period() {
new_test_ext(DefaultCertificationConfig { new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_issuer: btreemap![ certs_by_receiver: btreemap![
0 => btreemap![1 => 10], 0 => btreemap![
2 => btreemap![0 => 10], 1 => Some(10),
3 => btreemap![0 => 10], 2 => Some(10),
],
1 => btreemap![
0 => Some(10),
2 => Some(10),
],
2 => btreemap![
0 => Some(10),
1 => Some(10),
],
], ],
}) })
.execute_with(|| { .execute_with(|| {
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 2), DefaultCertification::idty_cert_meta(0),
Err(Error::<Test, _>::NotRespectCertPeriod.into()) crate::IdtyCertMeta {
issued_count: 2,
next_issuable_on: 2,
received_count: 2,
}
);
assert_eq!(
DefaultCertification::add_cert(RuntimeOrigin::signed(0), 3),
Err(Error::<Test>::NotRespectCertPeriod.into())
); );
run_to_block(CertPeriod::get()); run_to_block(CertPeriod::get());
assert_ok!(DefaultCertification::add_cert(Origin::signed(0), 2)); assert_ok!(DefaultCertification::add_cert(RuntimeOrigin::signed(0), 3));
run_to_block(CertPeriod::get() + 1); run_to_block(CertPeriod::get() + 1);
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 3), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 4),
Err(Error::<Test, _>::NotRespectCertPeriod.into()) Err(Error::<Test>::NotRespectCertPeriod.into())
); );
run_to_block((2 * CertPeriod::get()) + 1); run_to_block((2 * CertPeriod::get()) + 1);
assert_ok!(DefaultCertification::add_cert(Origin::signed(0), 3)); assert_ok!(DefaultCertification::add_cert(RuntimeOrigin::signed(0), 4));
}); });
} }
// after given validity period, a certification should expire
#[test] #[test]
fn test_renewable_period() { fn test_cert_expiry() {
new_test_ext(DefaultCertificationConfig { new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_issuer: btreemap![ certs_by_receiver: btreemap![
0 => btreemap![1 => 10], 0 => btreemap![
2 => btreemap![0 => 10], 1 => Some(5),
3 => btreemap![0 => 10], 2 => Some(5),
],
1 => btreemap![
0 => Some(6),
2 => Some(6),
],
2 => btreemap![
0 => Some(7),
1 => Some(7),
],
], ],
}) })
.execute_with(|| { .execute_with(|| {
run_to_block(CertPeriod::get()); run_to_block(5);
// Expiry of cert by issuer 1
System::assert_has_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
// Expiry of cert by issuer 2
System::assert_has_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
receiver: 0,
issuer: 2,
expiration: true,
}));
});
}
// when renewing a certification, it should not expire now, but later
#[test]
fn test_cert_renewal() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
// this certification should expire 10 blocks later (at block 12)
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
run_to_block(12);
// expiry of previously renewed cert
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
});
}
// when renewing a certification, issuer should not be able to emit a new cert before certification delay
#[test]
fn test_cert_renewal_cert_delay() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
run_to_block(3);
// try to renew again
assert_noop!(
DefaultCertification::add_cert(RuntimeOrigin::signed(1), 0),
Error::<Test>::NotRespectCertPeriod,
);
// no renewal event should be emitted
assert_eq!(System::events().last(), None);
});
}
// when renewing a certification, the certification should not expire before new expiration
#[test]
fn test_cert_renewal_expiration() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
// this certification should expire 10 blocks later (at block 12)
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 1), DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Err(Error::<Test, _>::NotRespectRenewablePeriod.into()) Ok(().into())
); );
run_to_block(RenewablePeriod::get()); System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
assert_ok!(DefaultCertification::add_cert(Origin::signed(0), 1)); issuer: 1,
run_to_block(RenewablePeriod::get() + CertPeriod::get()); receiver: 0,
}));
run_to_block(4);
// renew certification from bob to alice again
// this certification should expire 10 blocks later (at block 14)
assert_eq!( assert_eq!(
DefaultCertification::add_cert(Origin::signed(0), 1), DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Err(Error::<Test, _>::NotRespectRenewablePeriod.into()) Ok(().into())
); );
run_to_block((2 * RenewablePeriod::get()) + 1); System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
assert_ok!(DefaultCertification::add_cert(Origin::signed(0), 1)); issuer: 1,
receiver: 0,
}));
// no certification should expire at these blocks
// hint : prune_certifications checks that the certification has not been renewed
run_to_block(12);
assert_eq!(System::events().last(), None);
run_to_block(14);
// expiry of previously renewed cert
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
}); });
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub trait IsCertAllowed<IdtyIndex> { use frame_support::pallet_prelude::*;
fn is_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> bool;
/// Trait for checking if a certification is allowed between two identities.
pub trait CheckCertAllowed<IdtyIndex> {
/// Check if the certification is allowed from the issuer to the receiver.
fn check_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> Result<(), DispatchError>;
} }
impl<IdtyIndex> IsCertAllowed<IdtyIndex> for () { impl<IdtyIndex> CheckCertAllowed<IdtyIndex> for () {
fn is_cert_allowed(_issuer: IdtyIndex, _receiver: IdtyIndex) -> bool { fn check_cert_allowed(_issuer: IdtyIndex, _receiver: IdtyIndex) -> Result<(), DispatchError> {
true Ok(())
} }
} }
/// Trait for handling actions to take when a new certification is issued.
pub trait OnNewcert<IdtyIndex> { pub trait OnNewcert<IdtyIndex> {
/// Called when a new certification is issued.
fn on_new_cert( fn on_new_cert(
issuer: IdtyIndex, issuer: IdtyIndex,
issuer_issued_count: u32, issuer_issued_count: u32,
receiver: IdtyIndex, receiver: IdtyIndex,
receiver_received_count: u32, receiver_received_count: u32,
) -> frame_support::dispatch::Weight; );
} }
impl<IdtyIndex> OnNewcert<IdtyIndex> for () { impl<IdtyIndex> OnNewcert<IdtyIndex> for () {
fn on_new_cert( fn on_new_cert(
_issuer: IdtyIndex, _issuer: IdtyIndex,
_issuer_issued_count: u32, _issuer_issued_count: u32,
_receiver: IdtyIndex, _receiver: IdtyIndex,
_receiver_received_count: u32, _receiver_received_count: u32,
) -> frame_support::dispatch::Weight { ) {
0
} }
} }
/// Trait for handling actions to take when a certification is removed.
pub trait OnRemovedCert<IdtyIndex> { pub trait OnRemovedCert<IdtyIndex> {
/// Called when a certification is removed.
fn on_removed_cert( fn on_removed_cert(
issuer: IdtyIndex, issuer: IdtyIndex,
issuer_issued_count: u32, issuer_issued_count: u32,
receiver: IdtyIndex, receiver: IdtyIndex,
receiver_received_count: u32, receiver_received_count: u32,
expiration: bool, expiration: bool,
) -> frame_support::dispatch::Weight; );
} }
impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () { impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () {
fn on_removed_cert( fn on_removed_cert(
_issuer: IdtyIndex, _issuer: IdtyIndex,
...@@ -59,14 +68,12 @@ impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () { ...@@ -59,14 +68,12 @@ impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () {
_receiver: IdtyIndex, _receiver: IdtyIndex,
_receiver_received_count: u32, _receiver_received_count: u32,
_expiration: bool, _expiration: bool,
) -> frame_support::dispatch::Weight { ) {
0
} }
} }
/// Trait for setting the next issuable block number for an identity.
pub trait SetNextIssuableOn<BlockNumber, IdtyIndex> { pub trait SetNextIssuableOn<BlockNumber, IdtyIndex> {
fn set_next_issuable_on( /// Set the next block number when the identity can issue a certification.
idty_index: IdtyIndex, fn set_next_issuable_on(idty_index: IdtyIndex, next_issuable_on: BlockNumber);
next_issuable_on: BlockNumber,
) -> frame_support::dispatch::Weight;
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! Various basic types for use in the certification pallet. //! Various basic types for use in the certification pallet.
...@@ -20,16 +20,14 @@ use codec::{Decode, Encode}; ...@@ -20,16 +20,14 @@ use codec::{Decode, Encode};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use scale_info::TypeInfo; use scale_info::TypeInfo;
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] /// Represents the certification metadata attached to an identity.
pub struct CertValue<BlockNumber> {
pub renewable_on: BlockNumber,
pub removable_on: BlockNumber,
}
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct IdtyCertMeta<BlockNumber: Default> { pub struct IdtyCertMeta<BlockNumber: Default> {
/// Number of certifications issued by this identity.
pub issued_count: u32, pub issued_count: u32,
/// Block number before which the identity is not allowed to issue a new certification.
pub next_issuable_on: BlockNumber, pub next_issuable_on: BlockNumber,
/// Number of certifications received by this identity.
pub received_count: u32, pub received_count: u32,
} }
......
// Copyright 2021-2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::unnecessary_cast)]
use frame_support::weights::{constants::RocksDbWeight, Weight};
/// Weight functions needed for pallet_universal_dividend.
pub trait WeightInfo {
fn add_cert() -> Weight;
fn renew_cert() -> Weight;
fn del_cert() -> Weight;
fn remove_all_certs_received_by(i: u32) -> Weight;
fn on_initialize() -> Weight;
fn do_remove_cert_noop() -> Weight;
fn do_remove_cert() -> Weight;
fn do_remove_all_certs_received_by(i: u32) -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
// Storage: Identity Identities (r:2 w:0)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Cert CertsRemovableOn (r:1 w:1)
// Storage: Cert CertsByReceiver (r:1 w:1)
fn add_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Identity Identities (r:2 w:0)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Cert CertsRemovableOn (r:1 w:1)
// Storage: Cert CertsByReceiver (r:1 w:1)
fn renew_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
fn del_cert() -> Weight {
// Minimum execution time: 216_762 nanoseconds.
Weight::from_parts(222_570_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(5 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
/// The range of component `i` is `[2, 1000]`.
fn remove_all_certs_received_by(i: u32) -> Weight {
// Minimum execution time: 223_292 nanoseconds.
Weight::from_parts(233_586_000 as u64, 0)
// Standard Error: 598_929
.saturating_add(Weight::from_parts(53_659_501 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
fn on_initialize() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
fn do_remove_cert_noop() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
fn do_remove_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
/// The range of component `i` is `[2, 1000]`.
fn do_remove_all_certs_received_by(i: u32) -> Weight {
// Minimum execution time: 223_292 nanoseconds.
Weight::from_parts(233_586_000 as u64, 0)
// Standard Error: 598_929
.saturating_add(Weight::from_parts(53_659_501 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
}
[package]
authors.workspace = true
description = "duniter pallet distance"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "pallet-distance"
repository.workspace = true
version.workspace = true
[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-authority-members/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-authority-members/std",
"pallet-authorship/std",
"pallet-balances/std",
"pallet-identity/std",
"pallet-session/std",
"scale-info/std",
"sp-consensus-babe/std",
"sp-core/std",
"sp-distance/std",
"sp-inherents/std",
"sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-authority-members/try-runtime",
"pallet-authorship/try-runtime",
"pallet-balances/try-runtime",
"pallet-identity/try-runtime",
"pallet-session/try-runtime",
"sp-distance/try-runtime",
"sp-runtime/try-runtime",
]
[package.metadata.docs.rs]
default-features = false
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-authority-members = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { workspace = true }
pallet-identity = { workspace = true }
pallet-session = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-consensus-babe = { workspace = true }
sp-core = { workspace = true }
sp-distance = { workspace = true }
sp-inherents = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
sp-keystore = { workspace = true, default-features = true }
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
#![allow(clippy::multiple_bound_locations)]
use super::*;
use codec::Encode;
use frame_benchmarking::v2::*;
use frame_support::traits::{fungible::Mutate, Get, OnFinalize, OnInitialize};
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use scale_info::prelude::vec;
use sp_runtime::Perbill;
use crate::Pallet;
#[benchmarks(
where
T: pallet_balances::Config,
BalanceOf<T>: From<u32>,
BlockNumberFor<T>: From<u32>,
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn populate_pool<T: Config>(i: u32) -> Result<(), &'static str> {
EvaluationPool0::<T>::mutate(|current_pool| -> Result<(), &'static str> {
for j in 0..i {
current_pool
.evaluations
.try_push((j, median::MedianAcc::new()))
.map_err(|_| Error::<T>::QueueFull)?;
}
Ok(())
})
}
#[benchmark]
fn request_distance_evaluation() {
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()));
assert!(
PendingEvaluationRequest::<T>::get(idty) == Some(caller.clone()),
"Request not added"
);
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller,
}
.into(),
);
}
#[benchmark]
fn request_distance_evaluation_for() {
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
T::Currency::set_balance(&caller, u32::MAX.into());
let target: T::IdtyIndex = 2u32;
// set target status since targeted distance evaluation only allowed for unvalidated
pallet_identity::Identities::<T>::mutate(target, |idty_val| {
idty_val.as_mut().unwrap().status = pallet_identity::IdtyStatus::Unvalidated
});
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), target);
assert!(
PendingEvaluationRequest::<T>::get(target) == Some(caller.clone()),
"Request not added"
);
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: target,
who: caller,
}
.into(),
);
}
#[benchmark]
fn update_evaluation(i: Linear<1, MAX_EVALUATIONS_PER_SESSION>) -> Result<(), BenchmarkError> {
let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryPlain(
sp_consensus_babe::digests::SecondaryPlainPreDigest {
authority_index: 0u32,
slot: Default::default(),
},
);
// A BABE digest item is needed to check authorship
let digest = sp_runtime::DigestItem::PreRuntime(*b"BABE", digest_data.encode());
<frame_system::Pallet<T>>::deposit_log(digest);
populate_pool::<T>(i)?;
#[extrinsic_call]
_(
RawOrigin::None,
ComputationResult {
distances: vec![Perbill::one(); i as usize],
},
);
Ok(())
}
#[benchmark]
fn force_update_evaluation(
i: Linear<1, MAX_EVALUATIONS_PER_SESSION>,
) -> Result<(), BenchmarkError> {
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
populate_pool::<T>(i)?;
#[extrinsic_call]
_(
RawOrigin::Root,
caller,
ComputationResult {
distances: vec![Perbill::one(); i as usize],
},
);
Ok(())
}
#[benchmark]
fn force_valid_distance_status() {
let idty = T::IdtyIndex::one();
#[extrinsic_call]
_(RawOrigin::Root, idty);
assert_has_event::<T>(
Event::<T>::EvaluatedValid {
idty_index: idty,
distance: Perbill::one(),
}
.into(),
);
}
#[benchmark]
fn on_initialize_overhead() {
// Benchmark on_initialize with no on_finalize and no do_evaluation.
let block_number: BlockNumberFor<T> = (T::EvaluationPeriod::get() + 1).into();
#[block]
{
Pallet::<T>::on_initialize(block_number);
}
}
#[benchmark]
fn do_evaluation_success() -> Result<(), BenchmarkError> {
// Benchmarking do_evaluation in case of a single success.
CurrentPeriodIndex::<T>::put(0);
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
Pallet::<T>::request_distance_evaluation(RawOrigin::Signed(caller.clone()).into())?;
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller.clone(),
}
.into(),
);
CurrentPeriodIndex::<T>::put(2);
Pallet::<T>::force_update_evaluation(
RawOrigin::Root.into(),
caller,
ComputationResult {
distances: vec![Perbill::one()],
},
)?;
#[block]
{
Pallet::<T>::do_evaluation(0);
}
assert_has_event::<T>(
Event::<T>::EvaluatedValid {
idty_index: idty,
distance: Perbill::one(),
}
.into(),
);
Ok(())
}
#[benchmark]
fn do_evaluation_failure() -> Result<(), BenchmarkError> {
// Benchmarking do_evaluation in case of a single failure.
CurrentPeriodIndex::<T>::put(0);
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
Pallet::<T>::request_distance_evaluation(RawOrigin::Signed(caller.clone()).into())?;
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller.clone(),
}
.into(),
);
CurrentPeriodIndex::<T>::put(2);
Pallet::<T>::force_update_evaluation(
RawOrigin::Root.into(),
caller,
ComputationResult {
distances: vec![Perbill::zero()],
},
)?;
#[block]
{
Pallet::<T>::do_evaluation(0);
}
assert_has_event::<T>(
Event::<T>::EvaluatedInvalid {
idty_index: idty,
distance: Perbill::zero(),
}
.into(),
);
Ok(())
}
#[benchmark]
fn do_evaluation_overhead() -> Result<(), BenchmarkError> {
#[block]
{
Pallet::<T>::do_evaluation(0);
}
Ok(())
}
#[benchmark]
fn on_finalize() {
DidUpdate::<T>::set(true);
#[block]
{
Pallet::<T>::on_finalize(Default::default());
}
assert!(!DidUpdate::<T>::get());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Distance Pallet
//!
//! The distance pallet utilizes results provided in a file by the `distance-oracle` offchain worker.
//! At a some point, an inherent is called to submit the results of this file to the blockchain.
//! The pallet then selects the median of the results (reach perbill) from an evaluation pool and fills the storage with the result status.
//! The status of an identity can be:
//!
//! - **Non-existent**: Distance evaluation has not been requested or has expired.
//! - **Pending**: Distance evaluation for this identity has been requested and is awaiting results after two evaluation periods.
//! - **Valid**: Distance has been evaluated positively for this identity.
//!
//! The evaluation result is used by the `duniter-wot` pallet to determine if an identity can gain or should lose membership in the web of trust.
//!
//! ## Process
//!
//! Any account can request a distance evaluation for a given identity provided it has enough currency to reserve. In this case, the distance status is marked as pending, and in the next evaluation period, inherents can start to publish results.
//!
//! This is the process for publishing a result:
//!
//! 1. A local worker creates a file containing the computation result.
//! 2. An inherent is created with the data from this file.
//! 3. The author is registered as an evaluator.
//! 4. The result is added to the current evaluation pool.
//! 5. A flag is set to prevent other distance evaluations in the same block.
//!
//! At the start of each new evaluation period:
//!
//! 1. Old results set to expire at this period are removed.
//! 2. Results from the current pool (results from the previous period's pool) are processed, and for each identity:
//! - The median of the distance results for this identity is chosen.
//! - If the distance is acceptable, it is marked as valid.
//! - If the distance is not acceptable, the result for this identity is discarded, and reserved currency is slashed (from the account which requested the evaluation).
//!
//! Then, in other pallets, when a membership is claimed, it is possible to check if there is a valid distance evaluation for this identity.
//!
//! ## Pools
//!
//! Evaluation pools consist of two components:
//!
//! - A set of evaluators.
//! - A vector of results.
//!
//! The evaluations are divided into three pools:
//!
//! - Pool number N - 1 % 3: Results from the previous evaluation period used in the current one (emptied for the next evaluation period).
//! - Pool number N + 0 % 3: Inherent results are added here.
//! - Pool number N + 1 % 3: Identities are added here for evaluation.
#![cfg_attr(not(feature = "std"), no_std)]
mod median;
pub mod traits;
mod types;
mod weights;
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub use pallet::*;
pub use traits::*;
pub use types::*;
pub use weights::WeightInfo;
use frame_support::{
traits::{
fungible::{self, hold, Credit, Mutate, MutateHold},
tokens::Precision,
OnUnbalanced, StorageVersion,
},
DefaultNoBound,
};
use sp_distance::{InherentError, INHERENT_IDENTIFIER};
use sp_inherents::{InherentData, InherentIdentifier};
use sp_runtime::{
traits::{One, Zero},
Saturating,
};
type IdtyIndex = u32;
/// Maximum number of identities to be evaluated in an evaluation period.
pub const MAX_EVALUATIONS_PER_SESSION: u32 = 1_300; // See https://git.duniter.org/nodes/rust/duniter-v2s/-/merge_requests/252
/// Maximum number of evaluators in an evaluation period.
pub const MAX_EVALUATORS_PER_SESSION: u32 = 100;
#[allow(unreachable_patterns)]
#[frame_support::pallet()]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::Perbill;
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <<T as Config>::Currency as fungible::Inspect<AccountIdOf<T>>>::Balance;
#[pallet::composite_enum]
pub enum HoldReason {
/// The funds are held as deposit for the distance evaluation.
DistanceHold,
}
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config:
frame_system::Config
+ pallet_authorship::Config
+ pallet_identity::Config<IdtyIndex = IdtyIndex>
{
/// Currency type used in this pallet for reserve and slash operations.
type Currency: Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
+ hold::Balanced<Self::AccountId>;
/// The overarching hold reason type.
type RuntimeHoldReason: From<HoldReason>;
/// The amount reserved during evaluation.
#[pallet::constant]
type EvaluationPrice: Get<BalanceOf<Self>>;
/// The evaluation period in blocks.
/// Since the evaluation uses 3 pools, the total evaluation time will be 3 * EvaluationPeriod.
#[pallet::constant]
type EvaluationPeriod: Get<u32>;
/// The maximum distance used to define a referee's accessibility.
/// This value is not used by the runtime but is needed by the client distance oracle.
#[pallet::constant]
type MaxRefereeDistance: Get<u32>;
/// The minimum ratio of accessible referees required.
#[pallet::constant]
type MinAccessibleReferees: Get<Perbill>;
/// Handler for unbalanced reduction when invalid distance causes a slash.
type OnUnbalanced: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
/// A handler that is called when a distance evaluation is successfully validated.
type OnValidDistanceStatus: OnValidDistanceStatus<Self>;
/// A trait that provides a method to check if a distance evaluation request is allowed.
type CheckRequestDistanceEvaluation: CheckRequestDistanceEvaluation<Self>;
}
// STORAGE //
/// The first evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_0)]
pub type EvaluationPool0<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The second evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_1)]
pub type EvaluationPool1<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The third evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_2)]
pub type EvaluationPool2<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The block at which the distance is evaluated.
#[pallet::storage]
pub type EvaluationBlock<T: Config> =
StorageValue<_, <T as frame_system::Config>::Hash, ValueQuery>;
/// The pending evaluation requesters.
#[pallet::storage]
#[pallet::getter(fn pending_evaluation_request)]
pub type PendingEvaluationRequest<T: Config> = StorageMap<
_,
Twox64Concat,
<T as pallet_identity::Config>::IdtyIndex,
<T as frame_system::Config>::AccountId,
OptionQuery,
>;
/// Store if the evaluation was updated in this block.
#[pallet::storage]
pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>;
/// The current evaluation period index.
#[pallet::storage]
#[pallet::getter(fn current_period_index)]
pub(super) type CurrentPeriodIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A distance evaluation was requested.
EvaluationRequested {
idty_index: T::IdtyIndex,
who: T::AccountId,
},
/// Distance rule was found valid.
EvaluatedValid {
idty_index: T::IdtyIndex,
distance: Perbill,
},
/// Distance rule was found invalid.
EvaluatedInvalid {
idty_index: T::IdtyIndex,
distance: Perbill,
},
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// Distance is already under evaluation.
AlreadyInEvaluation,
/// Too many evaluations requested by author.
TooManyEvaluationsByAuthor,
/// Too many evaluations for this block.
TooManyEvaluationsInBlock,
/// No author for this block.
NoAuthor,
/// Caller has no identity.
CallerHasNoIdentity,
/// Caller identity not found.
CallerIdentityNotFound,
/// Caller not member.
CallerNotMember,
// Caller status can only be Unvalidated, Member or NotMember.
CallerStatusInvalid,
/// Target identity not found.
TargetIdentityNotFound,
/// Evaluation queue is full.
QueueFull,
/// Too many evaluators in the current evaluation pool.
TooManyEvaluators,
/// Evaluation result has a wrong length.
WrongResultLength,
/// Targeted distance evaluation request is only possible for an unvalidated identity.
TargetMustBeUnvalidated,
}
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub _config: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
CurrentPeriodIndex::<T>::put(0u32);
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(block: BlockNumberFor<T>) -> Weight
where
BlockNumberFor<T>: From<u32>,
{
let mut weight = <T as pallet::Config>::WeightInfo::on_initialize_overhead();
if block % BlockNumberFor::<T>::one().saturating_mul(T::EvaluationPeriod::get().into())
== BlockNumberFor::<T>::zero()
{
let index = CurrentPeriodIndex::<T>::get() + 1;
CurrentPeriodIndex::<T>::put(index);
weight = weight
.saturating_add(Self::do_evaluation(index % 3))
.saturating_add(T::DbWeight::get().reads_writes(1, 1));
}
weight.saturating_add(<T as pallet::Config>::WeightInfo::on_finalize())
}
fn on_finalize(_n: BlockNumberFor<T>) {
DidUpdate::<T>::take();
}
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Request evaluation of the caller's identity distance.
///
/// This function allows the caller to request an evaluation of their distance.
/// A positive evaluation will lead to claiming or renewing membership, while a negative
/// evaluation will result in slashing for the caller.
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation())]
pub fn request_distance_evaluation(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let idty = Self::check_request_distance_evaluation_self(&who)?;
Pallet::<T>::do_request_distance_evaluation(&who, idty)?;
Ok(().into())
}
/// Request evaluation of a target identity's distance.
///
/// This function allows the caller to request an evaluation of a specific target identity's distance.
/// This action is only permitted for unvalidated identities.
#[pallet::call_index(4)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation_for())]
pub fn request_distance_evaluation_for(
origin: OriginFor<T>,
target: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::check_request_distance_evaluation_for(&who, target)?;
Pallet::<T>::do_request_distance_evaluation(&who, target)?;
Ok(().into())
}
/// Push an evaluation result to the pool.
///
/// This inherent function is called internally by validators to push an evaluation result
/// to the evaluation pool.
#[pallet::call_index(1)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
pub fn update_evaluation(
origin: OriginFor<T>,
computation_result: ComputationResult,
) -> DispatchResult {
// no origin = inherent
ensure_none(origin)?;
ensure!(
!DidUpdate::<T>::exists(),
Error::<T>::TooManyEvaluationsInBlock,
);
let author = pallet_authorship::Pallet::<T>::author().ok_or(Error::<T>::NoAuthor)?;
Pallet::<T>::do_update_evaluation(author, computation_result)?;
DidUpdate::<T>::set(true);
Ok(())
}
/// Force push an evaluation result to the pool.
///
/// It is primarily used for testing purposes.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(2)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::force_update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
pub fn force_update_evaluation(
origin: OriginFor<T>,
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
ensure_root(origin)?;
Pallet::<T>::do_update_evaluation(evaluator, computation_result)
}
/// Force set the distance evaluation status of an identity.
///
/// It is primarily used for testing purposes.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(3)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::force_valid_distance_status())]
pub fn force_valid_distance_status(
origin: OriginFor<T>,
identity: <T as pallet_identity::Config>::IdtyIndex,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_valid_distance_status(identity, Perbill::one());
Ok(())
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Mutate the evaluation pool containing:
/// * when this period begins: the evaluation results to be applied.
/// * when this period ends: the evaluation requests.
fn mutate_current_pool<
R,
F: FnOnce(
&mut EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
) -> R,
>(
index: u32,
f: F,
) -> R {
match index {
0 => EvaluationPool2::<T>::mutate(f),
1 => EvaluationPool0::<T>::mutate(f),
2 => EvaluationPool1::<T>::mutate(f),
_ => unreachable!("index < 3"),
}
}
/// Mutate the evaluation pool containing the results sent by evaluators for this period.
fn mutate_next_pool<
R,
F: FnOnce(
&mut EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
) -> R,
>(
index: u32,
f: F,
) -> R {
match index {
0 => EvaluationPool0::<T>::mutate(f),
1 => EvaluationPool1::<T>::mutate(f),
2 => EvaluationPool2::<T>::mutate(f),
_ => unreachable!("index < 3"),
}
}
/// Take (*and leave empty*) the evaluation pool containing:
/// * when this period begins: the evaluation results to be applied.
/// * when this period ends: the evaluation requests.
#[allow(clippy::type_complexity)]
fn take_current_pool(
index: u32,
) -> EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
> {
match index {
0 => EvaluationPool2::<T>::take(),
1 => EvaluationPool0::<T>::take(),
2 => EvaluationPool1::<T>::take(),
_ => unreachable!("index % 3 < 3"),
}
}
/// Check if requested distance evaluation is allowed.
fn check_request_distance_evaluation_self(
who: &T::AccountId,
) -> Result<<T as pallet_identity::Config>::IdtyIndex, DispatchError> {
// caller has an identity
let idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
let idty = pallet_identity::Identities::<T>::get(idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is (Unvalidated, Member, NotMember)
ensure!(
idty.status == pallet_identity::IdtyStatus::Unvalidated
|| idty.status == pallet_identity::IdtyStatus::Member
|| idty.status == pallet_identity::IdtyStatus::NotMember,
Error::<T>::CallerStatusInvalid
);
Self::check_request_distance_evaluation_common(idty_index)?;
Ok(idty_index)
}
/// check that targeted request distance evaluation is allowed
fn check_request_distance_evaluation_for(
who: &T::AccountId,
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// caller has an identity
let caller_idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
let caller_idty = pallet_identity::Identities::<T>::get(caller_idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is member
ensure!(
caller_idty.status == pallet_identity::IdtyStatus::Member,
Error::<T>::CallerNotMember
);
// target has an identity
let target_idty = pallet_identity::Identities::<T>::get(target)
.ok_or(Error::<T>::TargetIdentityNotFound)?;
// target is unvalidated
ensure!(
target_idty.status == pallet_identity::IdtyStatus::Unvalidated,
Error::<T>::TargetMustBeUnvalidated
);
Self::check_request_distance_evaluation_common(target)?;
Ok(())
}
// common checks between check_request_distance_evaluation _self and _for
fn check_request_distance_evaluation_common(
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// no pending evaluation request
ensure!(
PendingEvaluationRequest::<T>::get(target).is_none(),
Error::<T>::AlreadyInEvaluation
);
// external validation (wot)
// - membership renewal antispam
// - target has received enough certifications
T::CheckRequestDistanceEvaluation::check_request_distance_evaluation(target)
}
/// Request distance evaluation in the current pool.
fn do_request_distance_evaluation(
who: &T::AccountId,
idty_index: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
Pallet::<T>::mutate_current_pool(CurrentPeriodIndex::<T>::get() % 3, |current_pool| {
// extrinsics are transactional by default, this check might not be needed
ensure!(
current_pool.evaluations.len() < (MAX_EVALUATIONS_PER_SESSION as usize),
Error::<T>::QueueFull
);
T::Currency::hold(
&HoldReason::DistanceHold.into(),
who,
<T as Config>::EvaluationPrice::get(),
)?;
current_pool
.evaluations
.try_push((idty_index, median::MedianAcc::new()))
.map_err(|_| Error::<T>::QueueFull)?;
PendingEvaluationRequest::<T>::insert(idty_index, who);
Self::deposit_event(Event::EvaluationRequested {
idty_index,
who: who.clone(),
});
Ok(())
})
}
/// Update distance evaluation in the next pool.
fn do_update_evaluation(
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
Pallet::<T>::mutate_next_pool(CurrentPeriodIndex::<T>::get() % 3, |result_pool| {
// evaluation must be provided for all identities (no more, no less)
ensure!(
computation_result.distances.len() == result_pool.evaluations.len(),
Error::<T>::WrongResultLength
);
// insert the evaluator if not already there
if result_pool
.evaluators
.try_insert(evaluator.clone())
.map_err(|_| Error::<T>::TooManyEvaluators)?
{
// update the median accumulator with the new result
for (distance_value, (_identity, median_acc)) in computation_result
.distances
.into_iter()
.zip(result_pool.evaluations.iter_mut())
{
median_acc.push(distance_value);
}
Ok(())
} else {
// one author can only submit one evaluation
Err(Error::<T>::TooManyEvaluationsByAuthor.into())
}
})
}
/// Set the distance status using for an identity.
pub fn do_valid_distance_status(
idty: <T as pallet_identity::Config>::IdtyIndex,
distance: Perbill,
) {
// callback
T::OnValidDistanceStatus::on_valid_distance_status(idty);
// deposit event
Self::deposit_event(Event::EvaluatedValid {
idty_index: idty,
distance,
});
}
/// Perform evaluation for a specified pool.
///
/// This function executes evaluation logic based on the provided pool index. It retrieves the current
/// evaluation pool for the index, processes each evaluation, and handles the outcomes based on the
/// computed median distances. If a positive evaluation result is obtained, it releases reserved funds
/// and updates the distance status accordingly. For negative or inconclusive results, it slashes funds
/// or releases them, respectively.
pub fn do_evaluation(index: u32) -> Weight {
let mut weight = <T as pallet::Config>::WeightInfo::do_evaluation_overhead();
// set evaluation block
EvaluationBlock::<T>::set(frame_system::Pallet::<T>::parent_hash());
// Apply the results from the current pool (which was previous period's result pool)
// We take the results so the pool is left empty for the new period.
#[allow(clippy::type_complexity)]
let current_pool: EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
> = Pallet::<T>::take_current_pool(index);
for (idty, median_acc) in current_pool.evaluations.into_iter() {
let mut distance_result: Option<Perbill> = None;
// Retrieve the result of the computation from the median accumulator
if let Some(median_result) = median_acc.get_median() {
let distance = match median_result {
MedianResult::One(m) => m,
MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1)
};
// Update distance result
distance_result = Some(distance);
}
// If there's a pending evaluation request with the provided identity
if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) {
// If distance_result is available
if let Some(distance) = distance_result {
if distance >= T::MinAccessibleReferees::get() {
// Positive result, unreserve and apply
let _ = T::Currency::release(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
Precision::Exact,
);
Self::do_valid_distance_status(idty, distance);
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_success()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
} else {
// Negative result, slash and deposit event
let (imbalance, _) = <T::Currency as hold::Balanced<_>>::slash(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
);
T::OnUnbalanced::on_unbalanced(imbalance);
Self::deposit_event(Event::EvaluatedInvalid {
idty_index: idty,
distance,
});
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_failure()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
}
} else {
// No result, unreserve
let _ = T::Currency::release(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
Precision::Exact,
);
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_failure()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
}
}
// If evaluation happened without request, it's ok to do nothing
}
weight
}
}
#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = InherentError;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
data.get_data::<ComputationResult>(&INHERENT_IDENTIFIER)
.expect("Distance inherent data not correctly encoded")
.map(|inherent_data| Call::update_evaluation {
computation_result: inherent_data,
})
}
fn is_inherent(call: &Self::Call) -> bool {
matches!(call, Self::Call::update_evaluation { .. })
}
}
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use frame_support::pallet_prelude::*;
use scale_info::prelude::cmp::Ordering;
/// Represents a median accumulator.
#[derive(Clone, Debug, Decode, Default, Encode, TypeInfo)]
pub struct MedianAcc<
T: Clone + Decode + Encode + Ord + TypeInfo,
const S: u32, /*Get<u32> + TypeInfo*/
> {
samples: BoundedVec<(T, u32), ConstU32<S>>,
median_index: Option<u32>,
median_subindex: u32,
}
/*impl<T: 'static + Clone + Decode + Encode + Ord + TypeInfo, S: 'static + Get<u32>> TypeInfo
for MedianAcc<T, S>
{
type Identity = Self;
fn type_info() -> scale_info::Type<scale_info::form::MetaForm> {}
}*/
/// Represents the result of a median calculation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MedianResult<T: Clone + Ord> {
One(T),
Two(T, T),
}
impl<T: Clone + Decode + Encode + Ord + TypeInfo, const S: u32 /*Get<u32> + TypeInfo*/>
MedianAcc<T, S>
{
pub fn new() -> Self {
Self {
samples: BoundedVec::default(),
median_index: None,
median_subindex: 0,
}
}
pub fn push(&mut self, sample: T) {
if let Some(median_index) = &mut self.median_index {
match self
.samples
.binary_search_by_key(&sample, |(s, _n)| s.clone())
{
Ok(sample_index) => {
self.samples.get_mut(sample_index).expect("unreachable").1 += 1;
match (sample_index as u32).cmp(median_index) {
Ordering::Greater => {
if self.median_subindex
== self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1
{
self.median_subindex = 0;
*median_index += 1;
} else {
self.median_subindex += 1;
}
}
Ordering::Equal => {
self.median_subindex += 1;
}
Ordering::Less => {
if self.median_subindex == 0 {
*median_index -= 1;
self.median_subindex = self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1;
} else {
self.median_subindex -= 1;
}
}
}
}
Err(sample_index) => {
self.samples.try_insert(sample_index, (sample, 1)).ok();
if *median_index as usize >= sample_index {
if self.median_subindex == 0 {
self.median_subindex = self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1;
} else {
self.median_subindex -= 1;
*median_index += 1;
}
} else if self.median_subindex
== self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1
{
self.median_subindex = 0;
*median_index += 1;
} else {
self.median_subindex += 1;
}
}
}
} else {
self.samples.try_push((sample, 1)).ok();
self.median_index = Some(0);
}
}
pub fn get_median(&self) -> Option<MedianResult<T>> {
self.median_index.map(|median_index| {
let (median_sample, median_n) = self
.samples
.get(median_index as usize)
.expect("unreachable");
if self.median_subindex == median_n * 2 - 1 {
MedianResult::Two(
median_sample.clone(),
self.samples
.get(median_index as usize + 1)
.expect("unreachable")
.0
.clone(),
)
} else {
MedianResult::One(median_sample.clone())
}
})
}
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::{self as pallet_distance};
use core::marker::PhantomData;
use frame_support::{
derive_impl, parameter_types,
traits::{Everything, OnFinalize, OnInitialize},
};
use frame_system as system;
use pallet_balances::AccountData;
use pallet_session::ShouldEndSession;
use sp_core::{ConstU32, H256};
use sp_runtime::{
impl_opaque_keys,
key_types::DUMMY,
testing::{TestSignature as SubtrateTestSignature, UintAuthorityId},
traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys},
BuildStorage, KeyTypeId, Perbill,
};
type Balance = u64;
type Block = frame_system::mocking::MockBlock<Test>;
pub type AccountId = u64;
pub struct AccountId32Mock(u64);
impl From<AccountId32Mock> for u64 {
fn from(account: AccountId32Mock) -> u64 {
account.0
}
}
impl From<[u8; 32]> for AccountId32Mock {
fn from(bytes: [u8; 32]) -> Self {
Self(u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
}
/// Test signature that impl From<ed25519::Signature> (required to compile pallet identity)
#[derive(
Clone,
codec::DecodeWithMemTracking,
codec::Decode,
Debug,
Eq,
codec::Encode,
PartialEq,
scale_info::TypeInfo,
)]
pub struct TestSignature(SubtrateTestSignature);
impl From<sp_core::ed25519::Signature> for TestSignature {
fn from(_: sp_core::ed25519::Signature) -> Self {
// Implementation here only to satisfy traits bounds at compilation
// This convertion should not be used inside pallet distance tests
unimplemented!()
}
}
impl sp_runtime::traits::Verify for TestSignature {
type Signer = UintAuthorityId;
fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, msg: L, signer: &u64) -> bool {
<SubtrateTestSignature as sp_runtime::traits::Verify>::verify::<L>(&self.0, msg, signer)
}
}
impl_opaque_keys! {
pub struct MockSessionKeys {
pub dummy: UintAuthorityId,
}
}
impl From<UintAuthorityId> for MockSessionKeys {
fn from(dummy: UintAuthorityId) -> Self {
Self { dummy }
}
}
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test {
System: frame_system,
Session: pallet_session,
Authorship: pallet_authorship,
AuthorityMembers: pallet_authority_members,
Balances: pallet_balances,
Identity: pallet_identity,
Distance: pallet_distance,
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test {
type AccountData = AccountData<u64>;
type AccountId = AccountId;
type BaseCallFilter = Everything;
type Block = Block;
type BlockHashCount = BlockHashCount;
type Hash = H256;
type Hashing = BlakeTwo256;
type Lookup = IdentityLookup<Self::AccountId>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type PalletInfo = PalletInfo;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type SS58Prefix = SS58Prefix;
}
pub struct TestSessionHandler;
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[DUMMY];
fn on_new_session<Ks: OpaqueKeys>(
_changed: bool,
_validators: &[(AccountId, Ks)],
_queued_validators: &[(AccountId, Ks)],
) {
}
fn on_disabled(_validator_index: u32) {}
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AccountId, Ks)]) {}
}
const SESSION_LENGTH: u64 = 5;
pub struct TestShouldEndSession;
impl ShouldEndSession<u64> for TestShouldEndSession {
fn should_end_session(now: u64) -> bool {
now % SESSION_LENGTH == 0
}
}
impl pallet_session::Config for Test {
type DisablingStrategy = ();
type Keys = MockSessionKeys;
type NextSessionRotation = ();
type RuntimeEvent = RuntimeEvent;
type SessionHandler = TestSessionHandler;
type SessionManager = AuthorityMembers;
type ShouldEndSession = TestShouldEndSession;
type ValidatorId = AccountId;
type ValidatorIdOf = ConvertInto;
type WeightInfo = ();
}
pub struct FullIdentificationOfImpl;
impl sp_runtime::traits::Convert<AccountId, Option<()>> for FullIdentificationOfImpl {
fn convert(_: AccountId) -> Option<()> {
Some(())
}
}
impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = FullIdentificationOfImpl;
}
pub struct ConstantAuthor<T>(PhantomData<T>);
impl<T: From<u64>> frame_support::traits::FindAuthor<T> for ConstantAuthor<T> {
fn find_author<'a, I>(_: I) -> Option<T>
where
I: 'a + IntoIterator<Item = (sp_runtime::ConsensusEngineId, &'a [u8])>,
{
Some(1u64.into())
}
}
impl pallet_authorship::Config for Test {
type EventHandler = ();
type FindAuthor = ConstantAuthor<Self::AccountId>;
}
pub struct TestIsSmithMember;
impl IsMember<u32> for TestIsSmithMember {
fn is_member(member_id: &u32) -> bool {
member_id % 3 == 0
}
}
pub struct IdentityIndexOf<T: pallet_identity::Config>(PhantomData<T>);
impl<T: pallet_identity::Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>>
for IdentityIndexOf<T>
{
fn convert(account_id: T::AccountId) -> Option<T::IdtyIndex> {
pallet_identity::Pallet::<T>::identity_index_of(account_id)
}
}
impl pallet_authority_members::Config for Test {
type IsMember = TestIsSmithMember;
type MaxAuthorities = ConstU32<4>;
type MemberId = u32;
type MemberIdOf = IdentityIndexOf<Self>;
type OnIncomingMember = ();
type OnNewSession = ();
type OnOutgoingMember = ();
type RemoveMemberOrigin = system::EnsureRoot<AccountId>;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}
parameter_types! {
pub const ExistentialDeposit: Balance = 10;
pub const MaxLocks: u32 = 50;
}
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = Balance;
type DoneSlashHandler = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type FreezeIdentifier = ();
type MaxFreezes = ConstU32<0>;
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = ();
type RuntimeHoldReason = RuntimeHoldReason;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
}
parameter_types! {
pub const ChangeOwnerKeyPeriod: u64 = 10;
pub const ConfirmPeriod: u64 = 2;
pub const ValidationPeriod: u64 = 3;
pub const AutorevocationPeriod: u64 = 5;
pub const DeletionPeriod: u64 = 7;
pub const IdtyCreationPeriod: u64 = 3;
}
pub struct IdtyNameValidatorTestImpl;
impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
fn validate(idty_name: &pallet_identity::IdtyName) -> bool {
idty_name.0.len() < 16
}
}
impl pallet_identity::Config for Test {
type AccountId32 = AccountId32Mock;
type AccountLinker = ();
type AutorevocationPeriod = AutorevocationPeriod;
type ChangeOwnerKeyPeriod = ChangeOwnerKeyPeriod;
type CheckAccountWorthiness = ();
type CheckIdtyCallAllowed = ();
type ConfirmPeriod = ConfirmPeriod;
type DeletionPeriod = DeletionPeriod;
type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyData = ();
type IdtyIndex = u32;
type IdtyNameValidator = IdtyNameValidatorTestImpl;
type OnKeyChange = ();
type OnNewIdty = ();
type OnRemoveIdty = ();
type RuntimeEvent = RuntimeEvent;
type Signature = TestSignature;
type Signer = UintAuthorityId;
type ValidationPeriod = ValidationPeriod;
type WeightInfo = ();
}
parameter_types! {
pub const MinAccessibleReferees: Perbill = Perbill::from_percent(80);
}
impl pallet_distance::Config for Test {
type CheckRequestDistanceEvaluation = ();
type Currency = Balances;
type EvaluationPeriod = frame_support::traits::ConstU32<300>;
type EvaluationPrice = frame_support::traits::ConstU64<1000>;
type MaxRefereeDistance = frame_support::traits::ConstU32<5>;
type MinAccessibleReferees = MinAccessibleReferees;
type OnUnbalanced = ();
type OnValidDistanceStatus = ();
type RuntimeEvent = RuntimeEvent;
type RuntimeHoldReason = RuntimeHoldReason;
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
#[allow(dead_code)] // ??? Clippy triggers dead code for new_test_ext while it is used during test benchmark
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
pallet_identity::GenesisConfig::<Test> {
identities: (1..=4)
.map(|i| pallet_identity::GenesisIdty {
index: i as u32,
name: pallet_identity::IdtyName::from(NAMES[i - 1]),
value: pallet_identity::IdtyValue {
data: (),
next_creatable_identity_on: 0,
owner_key: i as u64,
old_owner_key: None,
next_scheduled: 0,
status: pallet_identity::IdtyStatus::Member,
},
})
.collect(),
}
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
pub fn run_to_block(n: u64) {
while System::block_number() < n {
Session::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Session::on_initialize(System::block_number());
}
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::{mock::*, *};
use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate};
// allow request distance evaluation for oneself
#[test]
fn test_request_distance_evaluation() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
// call request
assert_ok!(Distance::request_distance_evaluation(
RuntimeOrigin::signed(1)
));
System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested {
idty_index: 1,
who: 1,
}));
// currency was reserved
assert_eq!(Balances::reserved_balance(1), 1000);
});
}
// allow request distance evaluation for an unvalidated identity
#[test]
fn test_request_distance_evaluation_for() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 5));
assert_ok!(Identity::confirm_identity(
RuntimeOrigin::signed(5),
"Eeeve".into()
));
// call request
assert_ok!(Distance::request_distance_evaluation_for(
RuntimeOrigin::signed(1),
5
));
System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested {
idty_index: 5,
who: 1,
}));
// currency was reserved
assert_eq!(Balances::reserved_balance(1), 1000);
});
}
// non member can not request distance evaluation
#[test]
fn test_request_distance_evaluation_non_member() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&5, 10_000);
assert_noop!(
Distance::request_distance_evaluation_for(RuntimeOrigin::signed(5), 1),
Error::<Test>::CallerHasNoIdentity
);
assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 5));
assert_noop!(
Distance::request_distance_evaluation_for(RuntimeOrigin::signed(5), 1),
Error::<Test>::CallerNotMember
);
});
}
// can not request distance eval if already in evaluation
#[test]
fn test_request_distance_evaluation_twice() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
assert_ok!(Distance::request_distance_evaluation(
RuntimeOrigin::signed(1)
));
assert_noop!(
Distance::request_distance_evaluation(RuntimeOrigin::signed(1)),
Error::<Test>::AlreadyInEvaluation
);
});
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use frame_support::pallet_prelude::*;
/// Trait for handling actions when an identity has a valid distance status.
pub trait OnValidDistanceStatus<T: Config> {
/// Called when an identity has been determined to have a valid distance status.
fn on_valid_distance_status(idty_index: T::IdtyIndex);
}
impl<T: Config> OnValidDistanceStatus<T> for () {
fn on_valid_distance_status(_idty_index: T::IdtyIndex) {}
}
/// Trait for checking if a request for distance evaluation is allowed.
pub trait CheckRequestDistanceEvaluation<T: Config> {
/// Check if the request for distance evaluation is allowed for the given identity.
fn check_request_distance_evaluation(idty_index: T::IdtyIndex) -> Result<(), DispatchError>;
}
impl<T: Config> CheckRequestDistanceEvaluation<T> for () {
fn check_request_distance_evaluation(_idty_index: T::IdtyIndex) -> Result<(), DispatchError> {
Ok(())
}
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub use crate::{median::*, MAX_EVALUATIONS_PER_SESSION, MAX_EVALUATORS_PER_SESSION};
pub use sp_distance::ComputationResult;
use codec::{Decode, Encode};
use frame_support::pallet_prelude::*;
use sp_runtime::Perbill;
/// Status of the distance evaluation of an identity.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DistanceStatus {
/// Identity is in evaluation.
Pending,
/// Identity respects the distance.
Valid,
/// Identity doesn't respect the distance.
Invalid,
}
/// Represents a pool where distance evaluation requests and results are stored.
///
/// Depending on the pool rotation, this may not be complete and may still be accepting
/// new evaluation requests (with empty median accumulators) or new evaluations (with evaluators and new samples in the median accumulators).
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo)]
pub struct EvaluationPool<AccountId: Ord, IdtyIndex> {
/// List of identities with their evaluation result.
/// The result is the median of all the evaluations.
pub evaluations: BoundedVec<
(IdtyIndex, MedianAcc<Perbill, MAX_EVALUATORS_PER_SESSION>),
ConstU32<MAX_EVALUATIONS_PER_SESSION>,
>,
/// Evaluators who have published a result.
/// Its length should be the same as the number of samples
/// in each evaluation result `MedianAcc`.
/// An evaluator is not allowed to publish twice in a single session.
pub evaluators: BoundedBTreeSet<AccountId, ConstU32<MAX_EVALUATORS_PER_SESSION>>,
}
impl<AccountId: Ord, IdtyIndex> Default for EvaluationPool<AccountId, IdtyIndex> {
fn default() -> Self {
Self {
evaluations: BoundedVec::default(),
evaluators: BoundedBTreeSet::new(),
}
}
}
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::unnecessary_cast)]
use frame_support::weights::{constants::RocksDbWeight, Weight};
pub trait WeightInfo {
fn request_distance_evaluation() -> Weight;
fn request_distance_evaluation_for() -> Weight;
fn update_evaluation(i: u32) -> Weight;
fn force_update_evaluation(i: u32) -> Weight;
fn force_valid_distance_status() -> Weight;
fn on_initialize_overhead() -> Weight;
fn do_evaluation_overhead() -> Weight;
fn do_evaluation_success() -> Weight;
fn do_evaluation_failure() -> Weight;
fn on_finalize() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
fn request_distance_evaluation() -> Weight {
// Proof Size summary in bytes:
// Measured: `1280`
// Estimated: `4745`
// Minimum execution time: 876_053_000 picoseconds.
Weight::from_parts(898_445_000, 0)
.saturating_add(Weight::from_parts(0, 4745))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(3))
}
fn request_distance_evaluation_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `1485`
// Estimated: `7425`
// Minimum execution time: 1_118_982_000 picoseconds.
Weight::from_parts(1_292_782_000, 0)
.saturating_add(Weight::from_parts(0, 7425))
.saturating_add(RocksDbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(3))
}
fn update_evaluation(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `773 + i * (10 ±0)`
// Estimated: `2256 + i * (10 ±0)`
// Minimum execution time: 463_878_000 picoseconds.
Weight::from_parts(743_823_548, 0)
.saturating_add(Weight::from_parts(0, 2256))
// Standard Error: 292_144
.saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
}
fn force_update_evaluation(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
}
fn force_valid_distance_status() -> Weight {
// Proof Size summary in bytes:
// Measured: `1181`
// Estimated: `7121`
// Minimum execution time: 873_892_000 picoseconds.
Weight::from_parts(1_081_510_000, 0)
.saturating_add(Weight::from_parts(0, 7121))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(5))
}
fn do_evaluation_success() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn do_evaluation_failure() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn do_evaluation_overhead() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn on_initialize_overhead() -> Weight {
// Proof Size summary in bytes:
// Measured: `170`
// Estimated: `1655`
// Minimum execution time: 93_595_000 picoseconds.
Weight::from_parts(109_467_000, 0)
.saturating_add(Weight::from_parts(0, 1655))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn on_finalize() -> Weight {
// Proof Size summary in bytes:
// Measured: `170`
// Estimated: `1655`
// Minimum execution time: 93_595_000 picoseconds.
Weight::from_parts(109_467_000, 0)
.saturating_add(Weight::from_parts(0, 1655))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet duniter account.' description = "duniter pallet for account management"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-duniter-account' name = "pallet-duniter-account"
readme = 'README.md' repository.workspace = true
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' version.workspace = true
version = '3.0.0'
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"pallet-quota/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-treasury/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "duniter-primitives/std",
'frame-system/std', "frame-benchmarking?/std",
'frame-benchmarking/std', "frame-support/std",
'pallet-balances/std', "frame-system/std",
'pallet-provide-randomness/std', "log/std",
'pallet-treasury/std', "pallet-balances/std",
'serde', "pallet-identity/std",
'sp-core/std', "pallet-quota/std",
'sp-io/std', "pallet-transaction-payment/std",
'sp-runtime/std', "pallet-treasury/std",
'sp-std/std', "scale-info/std",
"serde/std",
"sp-api/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-identity/try-runtime",
"pallet-quota/try-runtime",
"pallet-transaction-payment/try-runtime",
"pallet-treasury/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# local
pallet-provide-randomness = { path = "../provide-randomness", default-features = false }
# crates.io
codec = { package = 'parity-scale-codec', version = "2.3.1", default-features = false, features = ["derive"] }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
# substrate
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.pallet-balances]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.pallet-treasury]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] default-features = false
targets = ["x86_64-unknown-linux-gnu"]
### DEV ###
[dev-dependencies.pallet-balances]
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.maplit]
version = '1.0.2'
[dev-dependencies.serde]
version = '1.0.119'
[dev-dependencies.sp-io] [dependencies]
git = 'https://github.com/librelois/substrate.git' # local
branch = 'duniter-monthly-2022-02' pallet-quota = { workspace = true }
pallet-identity = { workspace = true }
duniter-primitives = { workspace = true }
codec = { workspace = true, features = ["derive"] }
log = { workspace = true }
pallet-balances = { workspace = true }
pallet-treasury = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-api = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
serde = { workspace = true, features = ["derive"] }
pallet-transaction-payment = { workspace = true }
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use crate::Pallet;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn unlink_identity() {
let account = account("Alice", 1, 1);
#[extrinsic_call]
_(RawOrigin::Signed(account));
}
#[benchmark]
fn on_revoke_identity() {
let idty: IdtyIdOf<T> = 1u32.into();
#[block]
{
<Pallet<T> as pallet_identity::traits::OnRemoveIdty<T>>::on_revoked(&idty);
}
}
}
// Copyright 2021 Axiom-Team // Copyright 2021-2023 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Account Pallet
//!
//! Duniter customizes the `AccountData` of the `Balances` Substrate pallet to include additional fields
//! such as `linked_idty`.
//!
//! ## Sufficiency
//!
//! DuniterAccount adjusts the Substrate `AccountInfo` to accommodate identity-linked accounts without requiring
//! an existential deposit. This flexibility helps reduce barriers to account creation.
//!
//! ## Linked Identity
//!
//! Duniter allows accounts to be linked to identities using the `linked_idty` field. This linkage facilitates
//! transaction fee refunds through the `OnChargeTransaction` mechanism.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod weights;
mod benchmarking;
mod runtime_api;
mod types; mod types;
pub use pallet::*; pub use pallet::*;
pub use runtime_api::*;
pub use types::*; pub use types::*;
pub use weights::WeightInfo;
use frame_support::pallet_prelude::*; use core::cmp;
use frame_support::traits::{OnUnbalanced, StoredMap}; use duniter_primitives::GetSigner;
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::tokens::fungible::Mutate;
use frame_support::{
dispatch::{DispatchInfo, GetDispatchInfo},
pallet_prelude::*,
traits::{
fungible,
fungible::{Credit, Inspect},
tokens::WithdrawConsequence,
IsSubType, StorageVersion, StoredMap,
},
};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use pallet_provide_randomness::RequestId; use pallet_quota::RefundFee;
use sp_core::H256; use pallet_transaction_payment::OnChargeTransaction;
use sp_runtime::traits::{Convert, Saturating, Zero}; use scale_info::prelude::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
};
use sp_runtime::traits::{
DispatchInfoOf, Dispatchable, ExtrinsicLike, PostDispatchInfoOf, Saturating,
};
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::traits::{Currency, ExistenceRequirement, StorageVersion}; pub type IdtyIdOf<T> = <T as pallet_identity::Config>::IdtyIndex;
pub type CurrencyOf<T> = pallet_balances::Pallet<T>;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <CurrencyOf<T> as fungible::Inspect<AccountIdOf<T>>>::Balance;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)] #[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info] #[pallet::without_storage_info]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
...@@ -46,79 +88,79 @@ pub mod pallet { ...@@ -46,79 +88,79 @@ pub mod pallet {
#[pallet::config] #[pallet::config]
pub trait Config: pub trait Config:
frame_system::Config<AccountData = AccountData<Self::Balance>> frame_system::Config<AccountData = AccountData<Self::Balance, IdtyIdOf<Self>>>
+ pallet_balances::Config + pallet_balances::Config
+ pallet_provide_randomness::Config<Currency = pallet_balances::Pallet<Self>> + pallet_transaction_payment::Config
+ pallet_treasury::Config<Currency = pallet_balances::Pallet<Self>> + pallet_treasury::Config<Currency = pallet_balances::Pallet<Self>>
+ pallet_quota::Config
{ {
type AccountIdToSalt: Convert<Self::AccountId, [u8; 32]>;
/// The overarching event type. /// The overarching event type.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type MaxNewAccountsPerBlock: Get<u32>;
type NewAccountPrice: Get<Self::Balance>;
}
// STORAGE // /// Type representing the weight of this pallet.
type WeightInfo: WeightInfo;
#[pallet::storage] /// A wrapped type that handles the charging of transaction fees.
#[pallet::getter(fn pending_random_id_assignments)] type InnerOnChargeTransaction: OnChargeTransaction<Self>;
pub type PendingRandomIdAssignments<T: Config> =
StorageMap<_, Twox64Concat, RequestId, T::AccountId, OptionQuery>;
#[pallet::storage] /// A type that implements the refund behavior for transaction fees.
#[pallet::getter(fn pending_new_accounts)] type Refund: pallet_quota::RefundFee<Self>;
pub type PendingNewAccounts<T: Config> = }
StorageMap<_, Blake2_128Concat, T::AccountId, (), OptionQuery>;
// GENESIS STUFF // // GENESIS STUFF //
#[pallet::genesis_config] #[pallet::genesis_config]
pub struct GenesisConfig<T: Config> { pub struct GenesisConfig<T: Config> {
pub accounts: pub accounts: BTreeMap<T::AccountId, GenesisAccountData<T::Balance, IdtyIdOf<T>>>,
sp_std::collections::btree_map::BTreeMap<T::AccountId, GenesisAccountData<T::Balance>>, pub treasury_balance: T::Balance,
} }
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> { impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
accounts: Default::default(), accounts: Default::default(),
treasury_balance: T::ExistentialDeposit::get(),
} }
} }
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
// Treasury // Treasury
frame_system::Account::<T>::mutate( frame_system::Account::<T>::mutate(
pallet_treasury::Pallet::<T>::account_id(), pallet_treasury::Pallet::<T>::account_id(),
|account| { |account| {
account.data.random_id = None; account.data.free = self.treasury_balance;
account.data.free = T::ExistentialDeposit::get();
account.providers = 1; account.providers = 1;
}, },
); );
// ensure no duplicate
let endowed_accounts = self.accounts.keys().cloned().collect::<BTreeSet<_>>();
assert!(
endowed_accounts.len() == self.accounts.len(),
"duplicate balances in genesis."
);
// Classic accounts // Classic accounts
for ( for (account_id, GenesisAccountData { balance, idty_id }) in &self.accounts {
account_id, // if the balance is below existential deposit, the account must be an identity
GenesisAccountData { assert!(balance >= &T::ExistentialDeposit::get() || idty_id.is_some());
random_id, // mutate account
balance,
is_identity,
},
) in &self.accounts
{
assert!(!balance.is_zero() || *is_identity);
frame_system::Account::<T>::mutate(account_id, |account| { frame_system::Account::<T>::mutate(account_id, |account| {
account.data.random_id = Some(*random_id);
if !balance.is_zero() {
account.data.free = *balance; account.data.free = *balance;
account.providers = 1; if idty_id.is_some() {
account.data.linked_idty = *idty_id;
} }
if *is_identity { if balance >= &T::ExistentialDeposit::get() {
account.sufficients = 1; // accounts above existential deposit self-provide
account.providers = 1;
} }
// WARN (disabled) all genesis accounts provide for themselves whether they have existential deposit or not
// this is needed to migrate Ğ1 data where identities with zero Ğ1 can exist
// account.providers = 1;
}); });
} }
} }
...@@ -129,126 +171,143 @@ pub mod pallet { ...@@ -129,126 +171,143 @@ pub mod pallet {
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> { pub enum Event<T: Config> {
/// Force the destruction of an account because its free balance is insufficient to pay /// account linked to identity
/// the account creation price. AccountLinked {
/// [who, balance]
ForceDestroy {
who: T::AccountId, who: T::AccountId,
balance: T::Balance, identity: IdtyIdOf<T>,
}, },
/// Random id assigned /// The account was unlinked from its identity.
/// [account_id, random_id] AccountUnlinked(T::AccountId),
RandomIdAssigned { who: T::AccountId, random_id: H256 }, }
}
// CALLS //
// HOOKS // #[pallet::call]
#[pallet::hooks] impl<T: Config> Pallet<T> {
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { /// Unlink the identity associated with the account.
fn on_initialize(_: T::BlockNumber) -> Weight { #[pallet::call_index(0)]
let mut total_weight = 0; #[pallet::weight(<T as pallet::Config>::WeightInfo::unlink_identity())]
for account_id in PendingNewAccounts::<T>::iter_keys() pub fn unlink_identity(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
.drain() let who = ensure_signed(origin)?;
.take(T::MaxNewAccountsPerBlock::get() as usize) Self::do_unlink_identity(who);
Ok(().into())
}
}
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
pub fn estimate_cost<Extrinsic>(
unchecked_extrinsic: Extrinsic,
) -> EstimatedCost<BalanceOf<T>>
where
BalanceOf<T>: PartialOrd,
Extrinsic: Encode + ExtrinsicLike + GetDispatchInfo + GetSigner<T::Lookup>,
T::RuntimeCall: Dispatchable<Info = DispatchInfo>,
<T as pallet_transaction_payment::Config>::OnChargeTransaction:
pallet_transaction_payment::OnChargeTransaction<T, Balance = BalanceOf<T>>,
{ {
if frame_system::Pallet::<T>::sufficients(&account_id) > 0 { let signer = unchecked_extrinsic.get_signer();
// If the account is self-sufficient, it is exempt from account creation fees let len = unchecked_extrinsic.encoded_size();
let request_id = pallet_provide_randomness::Pallet::<T>::force_request( let fees: BalanceOf<T> = pallet_transaction_payment::Pallet::<T>::query_info(
pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo, unchecked_extrinsic,
H256(T::AccountIdToSalt::convert(account_id.clone())), len as u32,
); )
PendingRandomIdAssignments::<T>::insert(request_id, account_id); .partial_fee;
total_weight += 100_000; let refund: BalanceOf<T> = if let Some(signer) = signer {
let account_data = frame_system::Pallet::<T>::get(&signer);
if let Some(idty_index) = account_data.linked_idty {
pallet_quota::Pallet::<T>::estimate_quota_refund(idty_index)
} else { } else {
// If the account is not self-sufficient, it must pay the account creation fees Zero::zero()
let account_data = frame_system::Pallet::<T>::get(&account_id);
let price = T::NewAccountPrice::get();
if account_data.free > price {
// The account can pay the new account price, we should:
// 1. Increment providers to create the account for frame_system point of view
// 2. Withdraw the "new account price" amount
// 3. Increment consumers to prevent the destruction of the account before
// the random id is assigned
// 4. Manage the funds collected
// 5. Submit random id generation request
// 6. Save the id of the random generation request.
frame_system::Pallet::<T>::inc_providers(&account_id);
let res = <pallet_balances::Pallet<T> as Currency<T::AccountId>>::withdraw(
&account_id,
price,
frame_support::traits::WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
);
debug_assert!(
res.is_ok(),
"Cannot fail because we checked that the free balance was sufficient"
);
if let Ok(imbalance) = res {
let res =
frame_system::Pallet::<T>::inc_consumers_without_limit(&account_id);
debug_assert!(
res.is_ok(),
"Cannot fail because providers are incremented just before"
);
T::OnUnbalanced::on_unbalanced(imbalance);
let request_id = pallet_provide_randomness::Pallet::<T>::force_request(
pallet_provide_randomness::RandomnessType::RandomnessFromTwoEpochsAgo,
H256(T::AccountIdToSalt::convert(account_id.clone())),
);
PendingRandomIdAssignments::<T>::insert(request_id, account_id);
total_weight += 200_000;
} }
} else { } else {
// The charges could not be deducted, we slash the account Zero::zero()
let balance_to_suppr = };
account_data.free.saturating_add(account_data.reserved);
// Force account data supression EstimatedCost {
frame_system::Account::<T>::mutate(&account_id, |a| { cost: if fees > refund {
a.data.set_balances(Default::default()) fees - refund
}); } else {
Self::deposit_event(Event::ForceDestroy { Zero::zero()
who: account_id, },
balance: balance_to_suppr, fees,
refund,
}
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Unlink the account from its associated identity.
pub fn do_unlink_identity(account_id: T::AccountId) {
// no-op if account already linked to nothing
frame_system::Account::<T>::mutate(&account_id, |account| {
if account.data.linked_idty.is_some() {
Self::deposit_event(Event::AccountUnlinked(account_id.clone()));
}
account.data.linked_idty = None;
})
}
/// Link an account to an identity.
pub fn do_link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) {
// no-op if identity does not change
if frame_system::Account::<T>::get(account_id).data.linked_idty != Some(idty_id) {
frame_system::Account::<T>::mutate(account_id, |account| {
account.data.linked_idty = Some(idty_id);
Self::deposit_event(Event::AccountLinked {
who: account_id.clone(),
identity: idty_id,
}); });
T::OnUnbalanced::on_unbalanced(pallet_balances::NegativeImbalance::new( })
balance_to_suppr, };
));
total_weight += 300_000;
} }
} }
} }
total_weight
/// Implementing identity removal event handling for the pallet.
impl<T: Config> pallet_identity::traits::OnRemoveIdty<T> for Pallet<T> {
fn on_removed(_idty_index: &IdtyIdOf<T>) -> Weight {
Weight::zero()
}
/// This implementation unlinks account associated with the identity.
fn on_revoked(idty_index: &IdtyIdOf<T>) -> Weight {
if let Some(account) = <pallet_identity::Pallet<T> as duniter_primitives::Idty<
IdtyIdOf<T>,
T::AccountId,
>>::owner_key(*idty_index)
{
Self::do_unlink_identity(account);
} }
<T as pallet::Config>::WeightInfo::on_revoke_identity()
} }
} }
impl<T> pallet_provide_randomness::OnFilledRandomness for Pallet<T> // implement account linker
impl<T> pallet_identity::traits::LinkIdty<T::AccountId, IdtyIdOf<T>> for Pallet<T>
where where
T: Config, T: Config,
{ {
fn on_filled_randomness(request_id: RequestId, randomness: H256) -> Weight { fn link_identity(account_id: &T::AccountId, idty_id: IdtyIdOf<T>) -> Result<(), DispatchError> {
if let Some(account_id) = PendingRandomIdAssignments::<T>::take(request_id) { // Check that account exist
frame_system::Account::<T>::mutate(&account_id, |account| { ensure!(
account.consumers = account.consumers.saturating_sub(1); (frame_system::Account::<T>::get(account_id).providers >= 1)
account.data.random_id = Some(randomness); || (frame_system::Account::<T>::get(account_id).sufficients >= 1),
}); pallet_identity::Error::<T>::AccountNotExist
Self::deposit_event(Event::RandomIdAssigned { );
who: account_id, Self::do_link_identity(account_id, idty_id);
random_id: randomness, Ok(())
});
200_000
} else {
100_000
}
} }
} }
// implement accountdata storedmap
impl<T, AccountId, Balance> impl<T, AccountId, Balance>
frame_support::traits::StoredMap<AccountId, pallet_balances::AccountData<Balance>> for Pallet<T> frame_support::traits::StoredMap<AccountId, pallet_balances::AccountData<Balance>> for Pallet<T>
where where
AccountId: Parameter AccountId: Parameter
+ Member + Member
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ core::fmt::Debug + Debug
+ sp_runtime::traits::MaybeDisplay + sp_runtime::traits::MaybeDisplay
+ Ord + Ord
+ Into<[u8; 32]> + Into<[u8; 32]>
...@@ -260,13 +319,12 @@ where ...@@ -260,13 +319,12 @@ where
+ Default + Default
+ Copy + Copy
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ core::fmt::Debug + Debug
+ codec::MaxEncodedLen + codec::MaxEncodedLen
+ scale_info::TypeInfo, + scale_info::TypeInfo,
T: Config T: Config
+ frame_system::Config<AccountId = AccountId, AccountData = AccountData<Balance>> + frame_system::Config<AccountId = AccountId, AccountData = AccountData<Balance, IdtyIdOf<T>>>
+ pallet_balances::Config<Balance = Balance> + pallet_balances::Config<Balance = Balance>,
+ pallet_provide_randomness::Config,
{ {
fn get(k: &AccountId) -> pallet_balances::AccountData<Balance> { fn get(k: &AccountId) -> pallet_balances::AccountData<Balance> {
frame_system::Account::<T>::get(k).data.into() frame_system::Account::<T>::get(k).data.into()
...@@ -277,7 +335,7 @@ where ...@@ -277,7 +335,7 @@ where
f: impl FnOnce(&mut Option<pallet_balances::AccountData<Balance>>) -> Result<R, E>, f: impl FnOnce(&mut Option<pallet_balances::AccountData<Balance>>) -> Result<R, E>,
) -> Result<R, E> { ) -> Result<R, E> {
let account = frame_system::Account::<T>::get(account_id); let account = frame_system::Account::<T>::get(account_id);
let was_providing = account.data.was_providing(); let was_providing = !account.data.free.is_zero() || !account.data.reserved.is_zero();
let mut some_data = if was_providing { let mut some_data = if was_providing {
Some(account.data.into()) Some(account.data.into())
} else { } else {
...@@ -285,27 +343,134 @@ where ...@@ -285,27 +343,134 @@ where
}; };
let result = f(&mut some_data)?; let result = f(&mut some_data)?;
let is_providing = some_data.is_some(); let is_providing = some_data.is_some();
if !was_providing && is_providing { match (was_providing, is_providing) {
if !frame_system::Pallet::<T>::account_exists(account_id) { // the account has just been created, increment its provider
// If the account does not exist, we should program its creation (false, true) => {
PendingNewAccounts::<T>::insert(account_id, ());
} else {
// If the account already exists, we should register increment providers directly
frame_system::Pallet::<T>::inc_providers(account_id); frame_system::Pallet::<T>::inc_providers(account_id);
} }
} else if was_providing && !is_providing { // the account was existing but is not anymore, decrement the provider
(true, false) => {
match frame_system::Pallet::<T>::dec_providers(account_id)? { match frame_system::Pallet::<T>::dec_providers(account_id)? {
frame_system::DecRefStatus::Reaped => return Ok(result), frame_system::DecRefStatus::Reaped => return Ok(result),
frame_system::DecRefStatus::Exists => { frame_system::DecRefStatus::Exists => {
// Update value as normal... // Update value as normal
} }
} }
} else if !was_providing && !is_providing { }
// mutation on unprovided account
(false, false) => {
return Ok(result); return Ok(result);
} }
// mutation on provided account
(true, true) => {
// Update value as normal
}
}
// do mutate the account by setting the balances
frame_system::Account::<T>::mutate(account_id, |a| { frame_system::Account::<T>::mutate(account_id, |a| {
a.data.set_balances(some_data.unwrap_or_default()) a.data.set_balances(some_data.unwrap_or_default())
}); });
Ok(result) Ok(result)
} }
} }
// ------
// allows pay fees with quota instead of currency if available
impl<T: Config> OnChargeTransaction<T> for Pallet<T>
where
T::RuntimeCall: IsSubType<Call<T>>,
T::InnerOnChargeTransaction: OnChargeTransaction<
T,
Balance = BalanceOf<T>,
LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>,
>,
{
type Balance = BalanceOf<T>;
type LiquidityInfo = Option<Credit<T::AccountId, T::Currency>>;
fn can_withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<(), TransactionValidityError> {
T::InnerOnChargeTransaction::can_withdraw_fee(who, call, dispatch_info, fee, tip)
}
fn withdraw_fee(
who: &T::AccountId,
call: &T::RuntimeCall,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
fee: Self::Balance,
tip: Self::Balance,
) -> Result<Self::LiquidityInfo, TransactionValidityError> {
// does not change the withdraw fee step (still fallback to currency adapter or oneshot account)
T::InnerOnChargeTransaction::withdraw_fee(who, call, dispatch_info, fee, tip)
}
fn correct_and_deposit_fee(
who: &T::AccountId,
dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
post_info: &PostDispatchInfoOf<T::RuntimeCall>,
corrected_fee: Self::Balance,
tip: Self::Balance,
already_withdrawn: Self::LiquidityInfo,
) -> Result<(), TransactionValidityError> {
// in any case, the default behavior is applied
T::InnerOnChargeTransaction::correct_and_deposit_fee(
who,
dispatch_info,
post_info,
corrected_fee,
tip,
already_withdrawn,
)?;
// if account can be exonerated, add it to a refund queue
let account_data = frame_system::Pallet::<T>::get(who);
if let Some(idty_index) = account_data.linked_idty {
T::Refund::request_refund(who.clone(), idty_index, corrected_fee.saturating_sub(tip));
}
Ok(())
}
#[cfg(feature = "runtime-benchmarks")]
fn endow_account(who: &T::AccountId, amount: Self::Balance) {
T::InnerOnChargeTransaction::endow_account(who, amount);
}
#[cfg(feature = "runtime-benchmarks")]
fn minimum_balance() -> Self::Balance {
T::InnerOnChargeTransaction::minimum_balance()
}
}
/// Implementation of the CheckAccountWorthiness trait for the Pallet.
/// This trait is used to verify the worthiness of an account in terms
/// of existence and sufficient balance to handle identity creation.
impl<AccountId, T: Config> pallet_identity::traits::CheckAccountWorthiness<T> for Pallet<T>
where
T: frame_system::Config<AccountId = AccountId>,
AccountId: cmp::Eq,
{
/// Checks that the account exists and has the balance to handle the
/// identity creation process.
fn check_account_worthiness(account: &AccountId) -> Result<(), DispatchError> {
ensure!(
frame_system::Pallet::<T>::providers(account) > 0,
pallet_identity::Error::<T>::AccountNotExist
);
// This check verifies that the account can withdraw at least twice the minimum balance.
ensure!(
T::Currency::can_withdraw(account, T::Currency::minimum_balance() * 2u32.into())
== WithdrawConsequence::Success,
pallet_identity::Error::<T>::InsufficientBalance
);
Ok(())
}
#[cfg(feature = "runtime-benchmarks")]
fn set_worthy(account: &AccountId) {
T::Currency::set_balance(account, T::Currency::minimum_balance() * 4u32.into());
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S 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.
//
// Duniter-v2S 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 Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use codec::{Codec, Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::RuntimeDebug;
sp_api::decl_runtime_apis! {
/// Runtime API for duniter account pallet
pub trait DuniterAccountApi<Balance>
where
EstimatedCost<Balance>: Codec,
{
/// Simulate the maximum cost of an extrinsic
///
/// Returns an object with two fields:
/// - `max_cost`: estimated effective cost for the user (fees - refund)
/// - `max_fees`: estimated amount of fees for the extrinsic
/// - `min_refund`: estimated amount of refund from quota
fn estimate_cost(
uxt: Block::Extrinsic,
) -> EstimatedCost<Balance>;
}
}
/// Account total balance information
#[derive(Encode, Decode, TypeInfo, Clone, PartialEq, RuntimeDebug)]
pub struct EstimatedCost<Balance> {
/// The estimated effective cost for the user (fees - refund)
pub cost: Balance,
/// The estimated amount of fees for the extrinsic
pub fees: Balance,
/// The estimated amount of refund from quota
pub refund: Balance,
}
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// You should have received a copy of the GNU Affero General Public License // 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/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use codec::{Decode, Encode, MaxEncodedLen}; use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_core::H256;
use sp_runtime::traits::Zero; use sp_runtime::traits::Zero;
#[derive(Clone, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] /// Account data structure.
pub struct AccountData<Balance> { ///
pub(super) random_id: Option<H256>, /// For details, refer to `struct AccountData` in Substrate code.
#[derive(Clone, Decode, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] // Default,
pub struct AccountData<Balance, IdtyId> {
/// Free balance of the account.
pub(super) free: Balance, pub(super) free: Balance,
/// Reserved balance of the account.
pub(super) reserved: Balance, pub(super) reserved: Balance,
/// Frozen fee balance of the account.
fee_frozen: Balance, fee_frozen: Balance,
/// Optional pointer to an identity used to determine if this account is linked to a member and in the quota system for fee refunds.
pub linked_idty: Option<IdtyId>,
} }
impl<Balance: Zero> AccountData<Balance> { // explicit implementation of default trait (can not be derived)
impl<Balance: Zero, IdtyId> Default for AccountData<Balance, IdtyId> {
fn default() -> Self {
Self {
linked_idty: None,
free: Balance::zero(),
reserved: Balance::zero(),
fee_frozen: Balance::zero(),
}
}
}
impl<Balance: Zero, IdtyId> AccountData<Balance, IdtyId> {
pub fn set_balances(&mut self, new_balances: pallet_balances::AccountData<Balance>) { pub fn set_balances(&mut self, new_balances: pallet_balances::AccountData<Balance>) {
self.free = new_balances.free; self.free = new_balances.free;
self.reserved = new_balances.reserved; self.reserved = new_balances.reserved;
self.fee_frozen = new_balances.fee_frozen; self.fee_frozen = new_balances.frozen;
}
pub fn was_providing(&self) -> bool {
!self.free.is_zero() || !self.reserved.is_zero()
} }
} }
impl<Balance: Zero> From<AccountData<Balance>> for pallet_balances::AccountData<Balance> { // convert Duniter AccountData to Balances AccountData
fn from(account_data: AccountData<Balance>) -> Self { // needed for trait implementation
impl<Balance: Zero, IdtyId> From<AccountData<Balance, IdtyId>>
for pallet_balances::AccountData<Balance>
{
fn from(account_data: AccountData<Balance, IdtyId>) -> Self {
Self { Self {
free: account_data.free, free: account_data.free,
reserved: account_data.reserved, reserved: account_data.reserved,
misc_frozen: Zero::zero(), frozen: account_data.fee_frozen,
fee_frozen: account_data.fee_frozen, flags: Default::default(), // default flags since not used
} }
} }
} }
#[derive(Clone, Decode, Default, Encode, Eq, MaxEncodedLen, PartialEq, RuntimeDebug, TypeInfo)] #[derive(
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] Clone,
pub struct GenesisAccountData<Balance> { Decode,
pub random_id: H256, Default,
Encode,
Eq,
MaxEncodedLen,
PartialEq,
RuntimeDebug,
TypeInfo,
serde::Serialize,
serde::Deserialize,
)]
#[serde(deny_unknown_fields)]
pub struct GenesisAccountData<Balance, IdtyId> {
pub balance: Balance, pub balance: Balance,
pub is_identity: bool, pub idty_id: Option<IdtyId>,
} }