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

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
Show changes
Showing
with 1107 additions and 1080 deletions
......@@ -35,7 +35,6 @@ pub use types::*;
use crate::traits::*;
use codec::Codec;
use frame_support::dispatch::Weight;
use frame_system::RawOrigin;
use sp_runtime::traits::{AtLeast32BitUnsigned, One, Saturating, Zero};
use sp_std::fmt::Debug;
use sp_std::prelude::*;
......@@ -46,7 +45,6 @@ pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_support::traits::StorageVersion;
use frame_system::pallet_prelude::*;
use sp_membership::traits::MembershipAction as _;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
......@@ -54,6 +52,7 @@ pub mod pallet {
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
// CONFIG //
......@@ -65,18 +64,10 @@ pub mod pallet {
type ConfirmPeriod: Get<Self::BlockNumber>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// Origin allowed to add a right to an identity
type AddRightOrigin: EnsureOrigin<Self::Origin>;
/// Origin allowed to delete a right to an identity
type DelRightOrigin: EnsureOrigin<Self::Origin>;
/// Management of the authorizations of the different calls. (The default implementation only allows root)
type EnsureIdtyCallAllowed: EnsureIdtyCallAllowed<Self>;
/// Minimum duration between the creation of 2 identities by the same creator
type IdtyCreationPeriod: Get<Self::BlockNumber>;
/// Identity custom data
type IdtyData: Parameter + Member + MaybeSerializeDeserialize + Debug + Default;
/// Identity custom data provider
type IdtyDataProvider: ProvideIdtyData<Self>;
/// A short identity index.
type IdtyIndex: Parameter
+ Member
......@@ -91,23 +82,32 @@ pub mod pallet {
type IdtyNameValidator: IdtyNameValidator;
/// Origin allowed to validate identity
type IdtyValidationOrigin: EnsureOrigin<Self::Origin>;
/// Rights that an identity can have
type IdtyRight: IdtyRight;
///
type IsMember: sp_runtime::traits::IsMember<Self::IdtyIndex>;
/// On identity confirmed by it's owner
type OnIdtyChange: OnIdtyChange<Self>;
/// On right key change
type OnRightKeyChange: OnRightKeyChange<Self>;
#[pallet::constant]
/// Maximum period with no rights, after this period, the identity is permanently deleted
type MaxNoRightPeriod: Get<Self::BlockNumber>;
///
type Membership: sp_membership::traits::MembershipAction<Self::IdtyIndex, Self::Origin>;
/// Maximum period with disabled status, after this period, the identity is permanently
/// deleted
type MaxDisabledPeriod: Get<Self::BlockNumber>;
/// Handle the logic that remove all identity consumers.
/// "identity consumers" mean all things that rely on the existence of the identity.
type RemoveIdentityConsumers: RemoveIdentityConsumers<Self::IdtyIndex>;
}
// GENESIS STUFF //
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))]
#[derive(Encode, Decode, Clone, PartialEq, Eq)]
pub struct GenesisIdty<T: Config> {
pub index: T::IdtyIndex,
pub name: IdtyName,
pub value: IdtyValue<T::BlockNumber, T::AccountId>,
}
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub identities: Vec<IdtyValue<T::AccountId, T::BlockNumber, T::IdtyData, T::IdtyRight>>,
pub identities: Vec<GenesisIdty<T>>,
}
#[cfg(feature = "std")]
......@@ -123,79 +123,63 @@ pub mod pallet {
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
let mut names = sp_std::collections::btree_set::BTreeSet::new();
for idty_value in &self.identities {
for idty in &self.identities {
assert!(
!names.contains(&idty_value.name),
!names.contains(&idty.name),
"Idty name {:?} is present twice",
&idty_value.name
&idty.name
);
if idty_value.status == IdtyStatus::Validated {
if idty_value.rights.is_empty() {
assert!(idty_value.removable_on > T::BlockNumber::zero());
} else {
assert!(idty_value.removable_on == T::BlockNumber::zero());
}
} else {
assert!(idty_value.removable_on > T::BlockNumber::zero());
assert!(idty_value.rights.is_empty())
}
names.insert(idty_value.name.clone());
assert!(idty.value.removable_on == T::BlockNumber::zero());
names.insert(idty.name.clone());
}
// We need to sort identities to ensure determinisctic result
let mut identities = self.identities.clone();
identities.sort_by(|idty_val_1, idty_val_2| idty_val_1.name.cmp(&idty_val_2.name));
identities.sort_unstable_by(|a, b| a.index.cmp(&b.index));
<IdentitiesCount<T>>::put(self.identities.len() as u64);
for idty_value in &identities {
for idty in identities.into_iter() {
let idty_index = Pallet::<T>::get_next_idty_index();
if idty_value.removable_on > T::BlockNumber::zero() {
if idty.value.removable_on > T::BlockNumber::zero() {
<IdentitiesRemovableOn<T>>::append(
idty_value.removable_on,
(idty_index, idty_value.status),
idty.value.removable_on,
(idty_index, idty.value.status),
)
}
<Identities<T>>::insert(idty_index, idty_value);
<Identities<T>>::insert(idty_index, idty.value.clone());
IdentitiesNames::<T>::insert(idty.name.clone(), ());
IdentityIndexOf::<T>::insert(idty.value.owner_key, idty_index);
}
}
}
// STORAGE //
/// Identities
#[pallet::storage]
#[pallet::getter(fn identity)]
pub type Identities<T: Config> = StorageMap<
pub type Identities<T: Config> = CountedStorageMap<
_,
Blake2_128Concat,
Twox64Concat,
T::IdtyIndex,
IdtyValue<T::AccountId, T::BlockNumber, T::IdtyData, T::IdtyRight>,
IdtyValue<T::BlockNumber, T::AccountId>,
OptionQuery,
>;
/// IdentitiesByDid
#[pallet::storage]
#[pallet::getter(fn identity_by_did)]
pub type IdentitiesByDid<T: Config> =
StorageMap<_, Blake2_128Concat, IdtyName, T::IdtyIndex, ValueQuery>;
#[pallet::getter(fn identity_index_of)]
pub type IdentityIndexOf<T: Config> =
StorageMap<_, Blake2_128, T::AccountId, T::IdtyIndex, OptionQuery>;
#[pallet::storage]
pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>;
#[pallet::getter(fn identity_by_did)]
pub type IdentitiesNames<T: Config> = StorageMap<_, Blake2_128, IdtyName, (), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn identities_count)]
pub(super) type IdentitiesCount<T: Config> = StorageValue<_, u64, ValueQuery>;
pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>;
/// Identities by removed block
#[pallet::storage]
#[pallet::getter(fn removable_on)]
pub type IdentitiesRemovableOn<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::BlockNumber,
Vec<(T::IdtyIndex, IdtyStatus)>,
ValueQuery,
>;
pub type IdentitiesRemovableOn<T: Config> =
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, IdtyStatus)>, ValueQuery>;
// HOOKS //
......@@ -218,31 +202,24 @@ pub mod pallet {
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A new identity has been created
/// [idty, owner_key]
IdtyCreated(IdtyName, T::AccountId),
/// [idty_index, owner_key]
IdtyCreated {
idty_index: T::IdtyIndex,
owner_key: T::AccountId,
},
/// An identity has been confirmed by it's owner
/// [idty]
IdtyConfirmed(IdtyName),
/// [idty_index, owner_key, name]
IdtyConfirmed {
idty_index: T::IdtyIndex,
owner_key: T::AccountId,
name: IdtyName,
},
/// An identity has been validated
/// [idty]
IdtyValidated(IdtyName),
/// An identity was renewed by it's owner
/// [idty]
IdtyRenewed(IdtyName),
/// An identity has acquired a new right
/// [idty, right]
IdtyAcquireRight(IdtyName, T::IdtyRight),
/// An identity lost a right
/// [idty, righ]
IdtyLostRight(IdtyName, T::IdtyRight),
/// An identity has modified a subkey associated with a right
/// [idty_name, right, old_subkey_opt, new_subkey_opt]
IdtySetRightSubKey(
IdtyName,
T::IdtyRight,
Option<T::AccountId>,
Option<T::AccountId>,
),
/// [idty_index]
IdtyValidated { idty_index: T::IdtyIndex },
/// An identity has been removed
/// [idty_index]
IdtyRemoved { idty_index: T::IdtyIndex },
}
// CALLS //
......@@ -255,51 +232,37 @@ pub mod pallet {
#[pallet::weight(0)]
pub fn create_identity(
origin: OriginFor<T>,
creator: T::IdtyIndex,
idty_name: IdtyName,
owner_key: T::AccountId,
) -> DispatchResultWithPostInfo {
// Verification phase //
let who = ensure_signed(origin)?;
let creator =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
let creator_idty_val =
Identities::<T>::try_get(&creator).map_err(|_| Error::<T>::CreatorNotExist)?;
let expected_account = if let Ok(index) = creator_idty_val
.rights
.binary_search_by(|(right_, _)| right_.cmp(&T::IdtyRight::create_idty_right()))
{
creator_idty_val.rights[index]
.1
.clone()
.unwrap_or(creator_idty_val.owner_key)
} else {
return Err(Error::<T>::CreatorNotHaveRightToCreateIdty.into());
};
Identities::<T>::try_get(&creator).map_err(|_| Error::<T>::IdtyNotFound)?;
if who != expected_account {
return Err(Error::<T>::RequireToBeOwner.into());
}
let block_number = frame_system::pallet::Pallet::<T>::block_number();
ensure!(
frame_system::Pallet::<T>::account_exists(&owner_key),
Error::<T>::OwnerAccountNotExist
);
if creator_idty_val.next_creatable_identity_on > block_number {
return Err(Error::<T>::NotRespectIdtyCreationPeriod.into());
if IdentityIndexOf::<T>::contains_key(&owner_key) {
return Err(Error::<T>::IdtyAlreadyCreated.into());
}
if !T::EnsureIdtyCallAllowed::can_create_identity(creator) {
return Err(Error::<T>::CreatorNotAllowedToCreateIdty.into());
}
if !T::IdtyNameValidator::validate(&idty_name) {
return Err(Error::<T>::IdtyNameInvalid.into());
}
if <IdentitiesByDid<T>>::contains_key(&idty_name) {
return Err(Error::<T>::IdtyNameAlreadyExist.into());
let block_number = frame_system::pallet::Pallet::<T>::block_number();
if creator_idty_val.next_creatable_identity_on > block_number {
return Err(Error::<T>::NotRespectIdtyCreationPeriod.into());
}
// Apply phase //
frame_system::Pallet::<T>::inc_sufficients(&owner_key);
<Identities<T>>::mutate_exists(creator, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.next_creatable_identity_on =
......@@ -307,28 +270,24 @@ pub mod pallet {
}
});
let idty_data =
T::IdtyDataProvider::provide_identity_data(creator, &idty_name, &owner_key);
let removable_on = block_number + T::ConfirmPeriod::get();
let idty_index = Self::get_next_idty_index();
<Identities<T>>::insert(
idty_index,
IdtyValue {
name: idty_name.clone(),
next_creatable_identity_on: T::BlockNumber::zero(),
owner_key: owner_key.clone(),
removable_on,
rights: Vec::with_capacity(0),
status: IdtyStatus::Created,
data: idty_data,
},
);
<IdentitiesByDid<T>>::insert(idty_name.clone(), idty_index);
IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created));
Self::inc_identities_counter();
Self::deposit_event(Event::IdtyCreated(idty_name, owner_key));
IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index);
Self::deposit_event(Event::IdtyCreated {
idty_index,
owner_key,
});
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Created { creator });
Ok(().into())
}
......@@ -336,271 +295,122 @@ pub mod pallet {
pub fn confirm_identity(
origin: OriginFor<T>,
idty_name: IdtyName,
idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
// Verification phase //
let who = ensure_signed(origin)?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
if who == idty_value.owner_key {
if idty_value.status != IdtyStatus::Created {
return Err(Error::<T>::IdtyAlreadyConfirmed.into());
}
if idty_value.name != idty_name {
return Err(Error::<T>::NotSameIdtyName.into());
}
T::Membership::request_membership_(RawOrigin::Signed(who).into(), idty_index)?;
let idty_index =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
idty_value.status = IdtyStatus::ConfirmedByOwner;
let mut idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::IdtyConfirmed(idty_name));
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Confirmed);
Ok(().into())
} else {
Err(Error::<T>::RequireToBeOwner.into())
}
} else {
Err(Error::<T>::IdtyNotFound.into())
if idty_value.status != IdtyStatus::Created {
return Err(Error::<T>::IdtyAlreadyConfirmed.into());
}
}
#[pallet::weight(0)]
pub fn renew_identity(
origin: OriginFor<T>,
idty_name: IdtyName,
idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
if who == idty_value.owner_key {
match idty_value.status {
IdtyStatus::Created | IdtyStatus::ConfirmedByOwner => {
Err(Error::<T>::IdtyNotValidated.into())
}
IdtyStatus::Validated | IdtyStatus::Expired => {
let _post_info = T::Membership::renew_membership_(
RawOrigin::Signed(who).into(),
idty_index,
)?;
let old_status = idty_value.status;
idty_value.status = IdtyStatus::Validated;
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::IdtyRenewed(idty_name));
if old_status == IdtyStatus::Expired {
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated);
}
Ok(().into())
}
}
} else {
Err(Error::<T>::RequireToBeOwner.into())
}
} else {
Err(Error::<T>::IdtyNotFound.into())
if !T::IdtyNameValidator::validate(&idty_name) {
return Err(Error::<T>::IdtyNameInvalid.into());
}
if <IdentitiesNames<T>>::contains_key(&idty_name) {
return Err(Error::<T>::IdtyNameAlreadyExist.into());
}
if !T::EnsureIdtyCallAllowed::can_confirm_identity(idty_index, who.clone()) {
return Err(Error::<T>::NotAllowedToConfirmIdty.into());
}
// Apply phase //
idty_value.status = IdtyStatus::ConfirmedByOwner;
<Identities<T>>::insert(idty_index, idty_value);
<IdentitiesNames<T>>::insert(idty_name.clone(), ());
Self::deposit_event(Event::IdtyConfirmed {
idty_index,
owner_key: who,
name: idty_name,
});
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Confirmed);
Ok(().into())
}
#[pallet::weight(0)]
pub fn validate_identity(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
idty_rights: Vec<T::IdtyRight>,
) -> DispatchResultWithPostInfo {
T::IdtyValidationOrigin::ensure_origin(origin.clone())?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
match idty_value.status {
IdtyStatus::Created => Err(Error::<T>::IdtyNotConfirmedByOwner.into()),
IdtyStatus::ConfirmedByOwner => {
let _post_info = T::Membership::claim_membership_(origin, idty_index)?;
idty_value.removable_on = T::BlockNumber::zero();
idty_value.rights =
idty_rights.iter().map(|right| (*right, None)).collect();
idty_value.status = IdtyStatus::Validated;
let name = idty_value.name.clone();
let owner_key = idty_value.owner_key.clone();
<Identities<T>>::insert(idty_index, idty_value);
if idty_rights.is_empty() {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let removable_on = block_number + T::MaxNoRightPeriod::get();
<IdentitiesRemovableOn<T>>::append(
removable_on,
(idty_index, IdtyStatus::Validated),
);
}
Self::deposit_event(Event::IdtyValidated(name.clone()));
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated);
for right in idty_rights {
Self::deposit_event(Event::IdtyAcquireRight(name.clone(), right));
if right.allow_owner_key() {
T::OnRightKeyChange::on_right_key_change(
idty_index,
right,
None,
Some(owner_key.clone()),
);
}
}
Ok(().into())
}
IdtyStatus::Validated | IdtyStatus::Expired => {
Err(Error::<T>::IdtyAlreadyValidated.into())
// Verification phase //
T::IdtyValidationOrigin::ensure_origin(origin)?;
let mut idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
match idty_value.status {
IdtyStatus::Created => return Err(Error::<T>::IdtyNotConfirmedByOwner.into()),
IdtyStatus::ConfirmedByOwner => {
if !T::EnsureIdtyCallAllowed::can_validate_identity(idty_index) {
return Err(Error::<T>::NotAllowedToValidateIdty.into());
}
}
} else {
Err(Error::<T>::IdtyNotFound.into())
IdtyStatus::Validated => return Err(Error::<T>::IdtyAlreadyValidated.into()),
}
// Apply phase //
idty_value.removable_on = T::BlockNumber::zero();
idty_value.status = IdtyStatus::Validated;
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::IdtyValidated { idty_index });
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated);
Ok(().into())
}
#[pallet::weight(0)]
pub fn add_right(
pub fn remove_identity(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
right: T::IdtyRight,
idty_name: Option<IdtyName>,
) -> DispatchResultWithPostInfo {
T::AddRightOrigin::ensure_origin(origin)?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
if idty_value.status != IdtyStatus::Validated {
return Err(Error::<T>::IdtyNotValidated.into());
}
ensure_root(origin)?;
if let Err(index) = idty_value
.rights
.binary_search_by(|(right_, _)| right_.cmp(&right))
{
let name = idty_value.name.clone();
let new_key = if right.allow_owner_key() {
Some(idty_value.owner_key.clone())
} else {
None
};
idty_value.removable_on = T::BlockNumber::zero();
idty_value.rights.insert(index, (right, None));
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::<T>::IdtyAcquireRight(name, right));
if new_key.is_some() {
T::OnRightKeyChange::on_right_key_change(idty_index, right, None, new_key);
}
Ok(().into())
} else {
Err(Error::<T>::RightAlreadyAdded.into())
}
} else {
Err(Error::<T>::IdtyNotFound.into())
Self::do_remove_identity(idty_index);
if let Some(idty_name) = idty_name {
<IdentitiesNames<T>>::remove(idty_name);
}
Ok(().into())
}
#[pallet::weight(0)]
pub fn del_right(
pub fn prune_item_identities_names(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
right: T::IdtyRight,
names: Vec<IdtyName>,
) -> DispatchResultWithPostInfo {
T::DelRightOrigin::ensure_origin(origin)?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
if idty_value.status != IdtyStatus::Validated {
return Err(Error::<T>::IdtyNotValidated.into());
}
ensure_root(origin)?;
if let Ok(index) = idty_value
.rights
.binary_search_by(|(right_, _)| right_.cmp(&right))
{
let name = idty_value.name.clone();
let old_key_opt = if let Some(ref subkey) = idty_value.rights[index].1 {
Some(subkey.clone())
} else if right.allow_owner_key() {
Some(idty_value.owner_key.clone())
} else {
None
};
idty_value.rights.remove(index);
if idty_value.rights.is_empty() {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let removable_on = block_number + T::MaxNoRightPeriod::get();
idty_value.removable_on = removable_on;
<IdentitiesRemovableOn<T>>::append(
removable_on,
(idty_index, IdtyStatus::Validated),
);
}
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::<T>::IdtyLostRight(name, right));
if old_key_opt.is_some() {
T::OnRightKeyChange::on_right_key_change(
idty_index,
right,
old_key_opt,
None,
);
}
Ok(().into())
} else {
Err(Error::<T>::RightNotExist.into())
}
} else {
Err(Error::<T>::IdtyNotFound.into())
for name in names {
<IdentitiesNames<T>>::remove(name);
}
Ok(().into())
}
#[pallet::weight(0)]
pub fn set_right_subkey(
pub fn prune_item_identity_index_of(
origin: OriginFor<T>,
idty_index: T::IdtyIndex,
right: T::IdtyRight,
subkey_opt: Option<T::AccountId>,
accounts_ids: Vec<T::AccountId>,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
if let Ok(mut idty_value) = <Identities<T>>::try_get(idty_index) {
if who == idty_value.owner_key {
if idty_value.status != IdtyStatus::Validated {
return Err(Error::<T>::IdtyNotValidated.into());
}
ensure_root(origin)?;
if let Ok(index) = idty_value
.rights
.binary_search_by(|(right_, _)| right_.cmp(&right))
{
let name = idty_value.name.clone();
let old_subkey_opt = idty_value.rights[index].1.clone();
idty_value.rights[index].1 = subkey_opt.clone();
let new_key = if let Some(ref subkey) = subkey_opt {
Some(subkey.clone())
} else if right.allow_owner_key() {
Some(idty_value.owner_key.clone())
} else {
None
};
<Identities<T>>::insert(idty_index, idty_value);
Self::deposit_event(Event::<T>::IdtySetRightSubKey(
name,
right,
old_subkey_opt.clone(),
subkey_opt,
));
T::OnRightKeyChange::on_right_key_change(
idty_index,
right,
old_subkey_opt,
new_key,
);
Ok(().into())
accounts_ids
.into_iter()
.filter(|account_id| {
if let Ok(idty_index) = IdentityIndexOf::<T>::try_get(&account_id) {
!Identities::<T>::contains_key(&idty_index)
} else {
Err(Error::<T>::RightNotExist.into())
false
}
} else {
Err(Error::<T>::RequireToBeOwner.into())
}
} else {
Err(Error::<T>::IdtyNotFound.into())
}
})
.for_each(IdentityIndexOf::<T>::remove);
Ok(().into())
}
}
......@@ -608,18 +418,18 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T> {
/// Creator not exist
CreatorNotExist,
/// Creator not allowed to create identities
CreatorNotAllowedToCreateIdty,
/// Creator not have right to create identities
CreatorNotHaveRightToCreateIdty,
/// Identity already confirmed
IdtyAlreadyConfirmed,
/// Identity already created
IdtyAlreadyCreated,
/// Identity already validated
IdtyAlreadyValidated,
/// You are not allowed to create a new identity now
IdtyCreationNotAllowed,
/// Identity index not found
IdtyIndexNotFound,
/// Identity name already exist
IdtyNameAlreadyExist,
/// Idty name invalid
......@@ -628,70 +438,47 @@ pub mod pallet {
IdtyNotConfirmedByOwner,
/// Identity not found
IdtyNotFound,
/// Idty not member
IdtyNotMember,
/// Identity not validated
IdtyNotValidated,
/// Identity not yet renewable
IdtyNotYetRenewable,
/// Not allowed to confirm identity
NotAllowedToConfirmIdty,
/// Not allowed to validate identity
NotAllowedToValidateIdty,
/// Not same identity name
NotSameIdtyName,
/// This operation requires to be the owner of the identity
RequireToBeOwner,
/// Right already added
RightAlreadyAdded,
/// Right not exist
RightNotExist,
/// Not respect IdtyCreationPeriod
NotRespectIdtyCreationPeriod,
/// Owner account not exist
OwnerAccountNotExist,
}
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
pub fn set_idty_data(idty_index: T::IdtyIndex, idty_data: T::IdtyData) {
Identities::<T>::mutate_exists(idty_index, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.data = idty_data;
}
});
pub fn identities_count() -> u32 {
Identities::<T>::count()
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
fn dec_identities_counter() {
if let Ok(counter) = <IdentitiesCount<T>>::try_get() {
<IdentitiesCount<T>>::put(counter.saturating_sub(1));
} else {
panic!("storage corrupted")
}
}
pub(super) fn do_expire_identity(idty_index: T::IdtyIndex) -> Weight {
let mut total_weight: Weight = 0;
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let removable_on = block_number + T::MaxNoRightPeriod::get();
<Identities<T>>::mutate_exists(idty_index, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.removable_on = removable_on;
idty_val.rights = Vec::with_capacity(0);
}
});
<IdentitiesRemovableOn<T>>::append(removable_on, (idty_index, IdtyStatus::Expired));
total_weight += T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Expired);
total_weight
}
pub(super) fn do_remove_identity(idty_index: T::IdtyIndex) -> Weight {
let mut total_weight: Weight = 0;
if let Some(idty_val) = <Identities<T>>::take(idty_index) {
<IdentitiesByDid<T>>::remove(idty_val.name);
if let Some(idty_val) = Identities::<T>::take(idty_index) {
T::RemoveIdentityConsumers::remove_idty_consumers(idty_index);
frame_system::Pallet::<T>::dec_sufficients(&idty_val.owner_key);
Self::deposit_event(Event::IdtyRemoved { idty_index });
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Removed);
}
Self::dec_identities_counter();
total_weight += T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Removed);
total_weight
0
}
fn get_next_idty_index() -> T::IdtyIndex {
if let Ok(next_index) = <NextIdtyIndex<T>>::try_get() {
......@@ -702,25 +489,13 @@ pub mod pallet {
T::IdtyIndex::one()
}
}
fn inc_identities_counter() {
if let Ok(counter) = <IdentitiesCount<T>>::try_get() {
<IdentitiesCount<T>>::put(counter.saturating_add(1));
} else {
<IdentitiesCount<T>>::put(1);
}
}
fn prune_identities(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
use frame_support::storage::generator::StorageMap as _;
if let Some(identities) = IdentitiesRemovableOn::<T>::from_query_to_optional_value(
IdentitiesRemovableOn::<T>::take(block_number),
) {
for (idty_index, idty_status) in identities {
if let Ok(idty_val) = <Identities<T>>::try_get(idty_index) {
if idty_val.removable_on == block_number && idty_val.status == idty_status {
total_weight += Self::do_remove_identity(idty_index)
}
for (idty_index, idty_status) in IdentitiesRemovableOn::<T>::take(block_number) {
if let Ok(idty_val) = <Identities<T>>::try_get(idty_index) {
if idty_val.removable_on == block_number && idty_val.status == idty_status {
total_weight += Self::do_remove_identity(idty_index)
}
}
}
......@@ -730,44 +505,8 @@ pub mod pallet {
}
}
impl<T: Config> sp_membership::traits::IsOriginAllowedToUseIdty<T::Origin, T::IdtyIndex>
for Pallet<T>
{
fn is_origin_allowed_to_use_idty(
origin: &T::Origin,
idty_index: &T::IdtyIndex,
) -> sp_membership::OriginPermission {
match origin.clone().into() {
Ok(RawOrigin::Root) => sp_membership::OriginPermission::Root,
Ok(RawOrigin::Signed(account_id)) => {
if let Some(idty_val) = Pallet::<T>::identity(idty_index) {
if account_id == idty_val.owner_key {
sp_membership::OriginPermission::Allowed
} else {
sp_membership::OriginPermission::Forbidden
}
} else {
sp_membership::OriginPermission::Forbidden
}
}
_ => sp_membership::OriginPermission::Forbidden,
}
}
}
impl<T: Config> sp_membership::traits::OnEvent<T::IdtyIndex> for Pallet<T> {
fn on_event(membership_event: sp_membership::Event<T::IdtyIndex>) -> Weight {
match membership_event {
sp_membership::Event::<T::IdtyIndex>::MembershipAcquired(_) => 0,
sp_membership::Event::<T::IdtyIndex>::MembershipExpired(idty_index) => {
Pallet::<T>::do_expire_identity(idty_index)
}
sp_membership::Event::<T::IdtyIndex>::MembershipRenewed(_) => 0,
sp_membership::Event::<T::IdtyIndex>::MembershipRequested(_) => 0,
sp_membership::Event::<T::IdtyIndex>::MembershipRevoked(_) => 0,
sp_membership::Event::<T::IdtyIndex>::PendingMembershipExpired(idty_index) => {
Pallet::<T>::do_remove_identity(idty_index)
}
}
impl<T: Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>> for Pallet<T> {
fn convert(account_id: T::AccountId) -> Option<T::IdtyIndex> {
Self::identity_index_of(account_id)
}
}
......@@ -17,57 +17,20 @@
use super::*;
use crate::{self as pallet_identity};
use frame_support::{
codec::{Decode, Encode},
parameter_types,
traits::{Everything, OnFinalize, OnInitialize},
RuntimeDebug,
traits::{Everything, GenesisBuild, OnFinalize, OnInitialize},
};
use frame_system as system;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::H256;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
traits::{BlakeTwo256, IdentityLookup, IsMember},
};
type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
#[derive(
Encode,
Decode,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
RuntimeDebug,
Deserialize,
Serialize,
TypeInfo,
)]
pub enum IdtyRight {
Right1,
Right2,
}
impl Default for IdtyRight {
fn default() -> Self {
IdtyRight::Right1
}
}
impl pallet_identity::traits::IdtyRight for IdtyRight {
fn allow_owner_key(self) -> bool {
self == Self::Right1
}
fn create_idty_right() -> Self {
Self::Right1
}
}
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test where
......@@ -116,7 +79,7 @@ parameter_types! {
pub const ConfirmPeriod: u64 = 2;
pub const IdtyCreationPeriod: u64 = 3;
pub const MaxInactivityPeriod: u64 = 5;
pub const MaxNoRightPeriod: u64 = 4;
pub const MaxDisabledPeriod: u64 = 4;
pub const RenewablePeriod: u64 = 3;
pub const ValidationPeriod: u64 = 2;
}
......@@ -128,34 +91,42 @@ impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
}
}
pub struct IsMemberTestImpl;
impl IsMember<u64> for IsMemberTestImpl {
fn is_member(_: &u64) -> bool {
true
}
}
impl pallet_identity::Config for Test {
type ConfirmPeriod = ConfirmPeriod;
type Event = Event;
type AddRightOrigin = system::EnsureRoot<AccountId>;
type DelRightOrigin = system::EnsureRoot<AccountId>;
type EnsureIdtyCallAllowed = ();
type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyData = ();
type IdtyDataProvider = ();
type IdtyNameValidator = IdtyNameValidatorTestImpl;
type IdtyIndex = u64;
type IdtyValidationOrigin = system::EnsureRoot<AccountId>;
type IdtyRight = IdtyRight;
type IsMember = IsMemberTestImpl;
type OnIdtyChange = ();
type OnRightKeyChange = ();
type MaxNoRightPeriod = MaxNoRightPeriod;
type Membership = ();
type MaxDisabledPeriod = MaxDisabledPeriod;
type RemoveIdentityConsumers = ();
}
// Build genesis storage according to the mock runtime.
pub fn new_test_ext(gen_conf: pallet_identity::GenesisConfig<Test>) -> sp_io::TestExternalities {
GenesisConfig {
system: SystemConfig::default(),
identity: gen_conf,
}
.build_storage()
.unwrap()
.into()
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap();
gen_conf.assimilate_storage(&mut t).unwrap();
frame_support::BasicExternalities::execute_with_storage(&mut t, || {
// Some dedicated test account
frame_system::Pallet::<Test>::inc_providers(&2);
frame_system::Pallet::<Test>::inc_providers(&3);
});
sp_io::TestExternalities::new(t)
}
pub fn run_to_block(n: u64) {
......
......@@ -14,95 +14,37 @@
// 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/>.
use crate::mock::IdtyRight as Right;
use crate::mock::*;
use crate::{Error, IdtyName, IdtyValue};
use frame_support::assert_err;
use crate::{Error, GenesisIdty, IdtyName, IdtyValue};
//use frame_support::assert_err;
use frame_support::assert_ok;
use frame_system::{EventRecord, Phase};
type IdtyVal = IdtyValue<u64, u64, (), Right>;
type IdtyVal = IdtyValue<u64, u64>;
fn alice() -> IdtyVal {
IdtyVal {
data: (),
owner_key: 1,
fn alice() -> GenesisIdty<Test> {
GenesisIdty {
index: 1,
name: IdtyName::from("Alice"),
next_creatable_identity_on: 0,
removable_on: 0,
rights: vec![(Right::Right1, None)],
status: crate::IdtyStatus::Validated,
}
}
fn bob() -> IdtyVal {
IdtyVal {
data: (),
owner_key: 2,
name: IdtyName::from("Bob"),
next_creatable_identity_on: 0,
removable_on: 0,
rights: vec![(Right::Right2, Some(20))],
status: crate::IdtyStatus::Validated,
value: IdtyVal {
next_creatable_identity_on: 0,
owner_key: 1,
removable_on: 0,
status: crate::IdtyStatus::Validated,
},
}
}
#[test]
fn test_no_identity() {
new_test_ext(IdentityConfig {
identities: Vec::with_capacity(0),
identities: Vec::new(),
})
.execute_with(|| {
assert_eq!(Identity::identities_count(), 0);
});
}
#[test]
fn test_creator_not_exist() {
new_test_ext(IdentityConfig {
identities: Vec::with_capacity(0),
})
.execute_with(|| {
assert_eq!(
Identity::create_identity(Origin::signed(1), 1, IdtyName::from("bob"), 2),
Err(Error::<Test>::CreatorNotExist.into())
);
});
}
#[test]
fn test_creator_not_have_right_to_create_identity() {
new_test_ext(IdentityConfig {
identities: vec![bob()],
})
.execute_with(|| {
// We need to initialize at least one block before any call
run_to_block(1);
// Bob not have right to create identities
assert_eq!(
Identity::create_identity(Origin::signed(2), 1, IdtyName::from("Charlie"), 3),
Err(Error::<Test>::CreatorNotHaveRightToCreateIdty.into())
);
})
}
#[test]
fn test_creator_not_owner() {
new_test_ext(IdentityConfig {
identities: vec![alice()],
})
.execute_with(|| {
// We need to initialize at least one block before any call
run_to_block(1);
// Someone try to create an identity pretending to be Alice
assert_eq!(
Identity::create_identity(Origin::signed(2), 1, IdtyName::from("Charlie"), 3),
Err(Error::<Test>::RequireToBeOwner.into())
);
})
}
#[test]
fn test_create_identity_ok() {
new_test_ext(IdentityConfig {
......@@ -113,19 +55,17 @@ fn test_create_identity_ok() {
run_to_block(1);
// Alice should be able te create an identity
assert_ok!(Identity::create_identity(
Origin::signed(1),
1,
IdtyName::from("bob"),
2
));
assert_ok!(Identity::create_identity(Origin::signed(1), 2));
let events = System::events();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated(IdtyName::from("bob"), 2)),
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 2,
owner_key: 2,
}),
topics: vec![],
}
);
......@@ -142,19 +82,17 @@ fn test_idty_creation_period() {
run_to_block(1);
// Alice should be able te create an identity
assert_ok!(Identity::create_identity(
Origin::signed(1),
1,
IdtyName::from("bob"),
2
));
assert_ok!(Identity::create_identity(Origin::signed(1), 2));
let events = System::events();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated(IdtyName::from("bob"), 2)),
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 2,
owner_key: 2,
}),
topics: vec![],
}
);
......@@ -163,87 +101,25 @@ fn test_idty_creation_period() {
// Alice cannot create a new identity before block #4
run_to_block(2);
assert_eq!(
Identity::create_identity(Origin::signed(1), 1, IdtyName::from("Charlie"), 3),
Identity::create_identity(Origin::signed(1), 3),
Err(Error::<Test>::NotRespectIdtyCreationPeriod.into())
);
// Alice should be able te create a second identity after block #4
run_to_block(4);
assert_ok!(Identity::create_identity(
Origin::signed(1),
1,
IdtyName::from("Charlie"),
3
));
assert_ok!(Identity::create_identity(Origin::signed(1), 3));
let events = System::events();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated(IdtyName::from("Charlie"), 3)),
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 3,
owner_key: 3,
}),
topics: vec![],
}
);
});
}
#[test]
fn test_two_identities() {
let identities = vec![alice(), bob()];
new_test_ext(IdentityConfig { identities }).execute_with(|| {
// Should have two identities
assert_eq!(Identity::identities_count(), 2);
// We need to initialize at least one block before any call
run_to_block(1);
// Add right Right1 for Alice
// Should fail because Alice already have this right
assert_err!(
Identity::add_right(Origin::root(), 1, Right::Right1),
Error::<Test>::RightAlreadyAdded
);
// Add right Right2 for alice
// Should succes and trigger the correct event
assert_ok!(Identity::add_right(Origin::root(), 1, Right::Right2));
let events = System::events();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyAcquireRight(
IdtyName::from("Alice"),
Right::Right2
)),
topics: vec![],
}
);
run_to_block(3);
// Delete right Right2 for Bob
// Should succes and trigger the correct event
assert_ok!(Identity::del_right(Origin::root(), 2, Right::Right2));
let events = System::events();
assert_eq!(events.len(), 1);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyLostRight(
IdtyName::from("Bob"),
Right::Right2
)),
topics: vec![],
}
);
// The Bob identity has no more rights, the inactivity period must start to run
let idty2 = Identity::identity(2).expect("idty not found");
assert!(idty2.rights.is_empty());
assert_eq!(idty2.removable_on, 7);
});
}
......@@ -16,37 +16,22 @@
use crate::*;
use frame_support::pallet_prelude::*;
use sp_runtime::traits::MaybeSerializeDeserialize;
use sp_std::fmt::Debug;
pub trait EnsureIdtyCallAllowed<T: Config> {
fn can_create_identity(creator: T::IdtyIndex) -> bool;
fn can_confirm_identity(idty_index: T::IdtyIndex, owner_key: T::AccountId) -> bool;
fn can_validate_identity(idty_index: T::IdtyIndex) -> bool;
}
impl<T: Config> EnsureIdtyCallAllowed<T> for () {
fn can_create_identity(_creator: T::IdtyIndex) -> bool {
fn can_create_identity(_: T::IdtyIndex) -> bool {
true
}
}
pub trait ProvideIdtyData<T: Config> {
fn provide_identity_data(
creator: T::IdtyIndex,
idty_name: &IdtyName,
idty_owner_key: &T::AccountId,
) -> T::IdtyData;
}
impl<T: Config> ProvideIdtyData<T> for ()
where
T::IdtyData: Default,
{
fn provide_identity_data(
_creator: T::IdtyIndex,
_idty_name: &IdtyName,
_idty_owner_key: &T::AccountId,
) -> T::IdtyData {
Default::default()
fn can_confirm_identity(_: T::IdtyIndex, _: T::AccountId) -> bool {
true
}
fn can_validate_identity(_: T::IdtyIndex) -> bool {
true
}
}
......@@ -54,27 +39,6 @@ pub trait IdtyNameValidator {
fn validate(idty_name: &IdtyName) -> bool;
}
pub trait IdtyRight:
frame_support::Parameter
+ frame_support::pallet_prelude::Member
+ MaybeSerializeDeserialize
+ Debug
+ Default
+ Copy
+ Ord
{
fn allow_owner_key(self) -> bool;
fn create_idty_right() -> Self;
}
pub enum IdtyEvent<T: Config> {
Created { creator: T::IdtyIndex },
Confirmed,
Validated,
Expired,
Removed,
}
pub trait OnIdtyChange<T: Config> {
fn on_idty_change(idty_index: T::IdtyIndex, idty_event: IdtyEvent<T>) -> Weight;
}
......@@ -84,21 +48,11 @@ impl<T: Config> OnIdtyChange<T> for () {
}
}
pub trait OnRightKeyChange<T: Config> {
fn on_right_key_change(
idty_index: T::IdtyIndex,
right: T::IdtyRight,
old_key: Option<T::AccountId>,
new_key: Option<T::AccountId>,
);
pub trait RemoveIdentityConsumers<IndtyIndex> {
fn remove_idty_consumers(idty_index: IndtyIndex) -> Weight;
}
impl<T: Config> OnRightKeyChange<T> for () {
fn on_right_key_change(
_idty_index: T::IdtyIndex,
_right: T::IdtyRight,
_old_key: Option<T::AccountId>,
_new_key: Option<T::AccountId>,
) {
impl<IndtyIndex> RemoveIdentityConsumers<IndtyIndex> for () {
fn remove_idty_consumers(_: IndtyIndex) -> Weight {
0
}
}
......@@ -14,7 +14,7 @@
// 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/>.
//! Various basic types for use in the certification pallet.
//! Various basic types for use in the identity pallet.
use codec::{Decode, Encode};
use frame_support::pallet_prelude::*;
......@@ -23,6 +23,13 @@ use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_std::vec::Vec;
pub enum IdtyEvent<T: crate::Config> {
Created { creator: T::IdtyIndex },
Confirmed,
Validated,
Removed,
}
#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug)]
pub struct IdtyName(pub Vec<u8>);
......@@ -63,7 +70,6 @@ pub enum IdtyStatus {
Created,
ConfirmedByOwner,
Validated,
Expired,
}
impl Default for IdtyStatus {
fn default() -> Self {
......@@ -73,43 +79,9 @@ impl Default for IdtyStatus {
#[cfg_attr(feature = "std", derive(Deserialize, Serialize))]
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct IdtyValue<
AccountId: Decode + Encode + TypeInfo,
BlockNumber: Decode + Encode + TypeInfo,
IdtyData: Decode + Encode + TypeInfo,
IdtyRight: Decode + Encode + TypeInfo,
> {
pub data: IdtyData,
pub owner_key: AccountId,
pub name: IdtyName,
pub struct IdtyValue<BlockNumber, AccountId> {
pub next_creatable_identity_on: BlockNumber,
pub owner_key: AccountId,
pub removable_on: BlockNumber,
pub rights: Vec<(IdtyRight, Option<AccountId>)>,
pub status: IdtyStatus,
}
impl<AccountId, BlockNumber, IdtyData, IdtyRight>
IdtyValue<AccountId, BlockNumber, IdtyData, IdtyRight>
where
AccountId: Clone + Decode + Encode + TypeInfo,
BlockNumber: Decode + Encode + TypeInfo,
IdtyData: Decode + Encode + TypeInfo,
IdtyRight: crate::traits::IdtyRight + Decode + Encode + TypeInfo,
{
pub fn get_right_key(&self, right: IdtyRight) -> Option<AccountId> {
if let Ok(index) = self
.rights
.binary_search_by(|(right_, _)| right_.cmp(&right))
{
if self.rights[index].1.is_some() {
self.rights[index].1.clone()
} else if right.allow_owner_key() {
Some(self.owner_key.clone())
} else {
None
}
} else {
None
}
}
}
......@@ -41,17 +41,17 @@ version = '2.3.1'
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
......@@ -61,17 +61,17 @@ features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
### DOC ###
......@@ -88,4 +88,4 @@ version = '1.0.2'
[dev-dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
......@@ -29,9 +29,11 @@ mod benchmarking;*/
pub use pallet::*;
use frame_support::dispatch::Weight;
use frame_support::pallet_prelude::DispatchResultWithPostInfo;
use frame_support::error::BadOrigin;
use frame_support::pallet_prelude::*;
use frame_system::RawOrigin;
use sp_membership::traits::*;
use sp_membership::{MembershipData, OriginPermission};
use sp_membership::MembershipData;
use sp_runtime::traits::Zero;
use sp_std::prelude::*;
#[cfg(feature = "std")]
......@@ -40,9 +42,9 @@ use std::collections::BTreeMap;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::StorageVersion;
use frame_system::pallet_prelude::*;
use sp_runtime::traits::Convert;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
......@@ -50,32 +52,28 @@ pub mod pallet {
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(_);
// CONFIG //
#[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config {
type IsIdtyAllowedToClaimMembership: IsIdtyAllowedToClaimMembership<Self::IdtyId>;
type IsIdtyAllowedToRenewMembership: IsIdtyAllowedToRenewMembership<Self::IdtyId>;
type IsIdtyAllowedToRequestMembership: IsIdtyAllowedToRequestMembership<Self::IdtyId>;
type IsOriginAllowedToUseIdty: IsOriginAllowedToUseIdty<Self::Origin, Self::IdtyId>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self, I>> + IsType<<Self as frame_system::Config>::Event>;
/// Specify true if you want to externalize the storage of memberships, but in this case
/// you must provide an implementation of `MembershipExternalStorage`
type ExternalizeMembershipStorage: Get<bool>;
/// Something that identifies an identity
type IdtyId: Copy + MaybeSerializeDeserialize + Parameter + Ord;
/// On event handler
type OnEvent: OnEvent<Self::IdtyId>;
/// Provide your implementation of membership storage here, if you want the pallet to
/// handle the storage for you, specify `()` and set `ExternalizeMembershipStorage` to
/// `false`.
type MembershipExternalStorage: MembershipExternalStorage<Self::BlockNumber, Self::IdtyId>;
/// Something that give the IdtyId on an account id
type IdtyIdOf: Convert<Self::AccountId, Option<Self::IdtyId>>;
/// Optional metadata
type MetaData: Parameter + Validate<Self::AccountId>;
#[pallet::constant]
/// Maximum life span of a non-renewable membership (in number of blocks)
type MembershipPeriod: Get<Self::BlockNumber>;
/// On event handler
type OnEvent: OnEvent<Self::IdtyId, Self::MetaData>;
#[pallet::constant]
/// Maximum period (in number of blocks), where an identity can remain pending subscription.
type PendingMembershipPeriod: Get<Self::BlockNumber>;
......@@ -108,11 +106,7 @@ pub mod pallet {
fn build(&self) {
for (idty_id, membership_data) in &self.memberships {
MembershipsExpireOn::<T, I>::append(membership_data.expire_on, idty_id);
if T::ExternalizeMembershipStorage::get() {
T::MembershipExternalStorage::insert(*idty_id, *membership_data);
} else {
Membership::<T, I>::insert(idty_id, membership_data);
}
Membership::<T, I>::insert(idty_id, membership_data);
}
}
}
......@@ -122,32 +116,32 @@ pub mod pallet {
#[pallet::storage]
#[pallet::getter(fn membership)]
pub type Membership<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::IdtyId, MembershipData<T::BlockNumber>, OptionQuery>;
CountedStorageMap<_, Twox64Concat, T::IdtyId, MembershipData<T::BlockNumber>, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn memberships_expire_on)]
pub type MembershipsExpireOn<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::BlockNumber, Vec<T::IdtyId>, OptionQuery>;
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<T::IdtyId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn pending_membership)]
pub type PendingMembership<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::IdtyId, (), OptionQuery>;
StorageMap<_, Twox64Concat, T::IdtyId, T::MetaData, OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn pending_memberships_expire_on)]
pub type PendingMembershipsExpireOn<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::BlockNumber, Vec<T::IdtyId>, OptionQuery>;
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<T::IdtyId>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn revoked_membership)]
pub type RevokedMembership<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::IdtyId, (), OptionQuery>;
StorageMap<_, Twox64Concat, T::IdtyId, (), OptionQuery>;
#[pallet::storage]
#[pallet::getter(fn revoked_memberships_pruned_on)]
pub type RevokedMembershipsPrunedOn<T: Config<I>, I: 'static = ()> =
StorageMap<_, Blake2_128Concat, T::BlockNumber, Vec<T::IdtyId>, OptionQuery>;
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<T::IdtyId>, OptionQuery>;
// EVENTS //
......@@ -178,14 +172,18 @@ pub mod pallet {
#[pallet::error]
pub enum Error<T, I = ()> {
/// Identity not allowed to claim membership
IdtyNotAllowedToClaimMembership,
/// Identity not allowed to request membership
IdtyNotAllowedToRequestMembership,
/// Identity not allowed to renew membership
IdtyNotAllowedToRenewMembership,
/// Invalid meta data
InvalidMetaData,
/// Identity id not found
IdtyIdNotFound,
/// Membership already acquired
MembershipAlreadyAcquired,
/// Membership already requested
MembershipAlreadyRequested,
/// Membership not yet renewable
MembershipNotYetRenewable,
/// Membership not found
......@@ -218,71 +216,54 @@ pub mod pallet {
#[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> {
#[pallet::weight(0)]
pub fn request_membership(
pub fn force_request_membership(
origin: OriginFor<T>,
idty_id: T::IdtyId,
metadata: T::MetaData,
) -> DispatchResultWithPostInfo {
let allowed =
match T::IsOriginAllowedToUseIdty::is_origin_allowed_to_use_idty(&origin, &idty_id)
{
OriginPermission::Forbidden => {
return Err(Error::<T, I>::OriginNotAllowedToUseIdty.into())
}
OriginPermission::Allowed => {
T::IsIdtyAllowedToRequestMembership::is_idty_allowed_to_request_membership(
&idty_id,
)
}
OriginPermission::Root => true,
};
if !allowed {
return Err(Error::<T, I>::IdtyNotAllowedToRequestMembership.into());
}
if Membership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipAlreadyAcquired.into());
}
if RevokedMembership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipRevokedRecently.into());
}
ensure_root(origin)?;
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let expire_on = block_number + T::PendingMembershipPeriod::get();
Self::do_request_membership(idty_id, metadata)
}
PendingMembership::<T, I>::insert(idty_id, ());
PendingMembershipsExpireOn::<T, I>::append(expire_on, idty_id);
Self::deposit_event(Event::MembershipRequested(idty_id));
T::OnEvent::on_event(sp_membership::Event::MembershipRequested(idty_id));
#[pallet::weight(0)]
pub fn request_membership(
origin: OriginFor<T>,
metadata: T::MetaData,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Ok(().into())
let idty_id = T::IdtyIdOf::convert(who.clone()).ok_or(Error::<T, I>::IdtyIdNotFound)?;
if !metadata.validate(&who) {
return Err(Error::<T, I>::InvalidMetaData.into());
}
if !T::IsIdtyAllowedToRequestMembership::is_idty_allowed_to_request_membership(&idty_id)
{
return Err(Error::<T, I>::IdtyNotAllowedToRequestMembership.into());
}
Self::do_request_membership(idty_id, metadata)
}
#[pallet::weight(0)]
pub fn claim_membership(
origin: OriginFor<T>,
idty_id: T::IdtyId,
maybe_idty_id: Option<T::IdtyId>,
) -> DispatchResultWithPostInfo {
let allowed =
match T::IsOriginAllowedToUseIdty::is_origin_allowed_to_use_idty(&origin, &idty_id)
{
OriginPermission::Forbidden => {
return Err(Error::<T, I>::OriginNotAllowedToUseIdty.into())
}
OriginPermission::Allowed => {
T::IsIdtyAllowedToClaimMembership::is_idty_allowed_to_claim_membership(
&idty_id,
)
}
OriginPermission::Root => true,
};
if !allowed {
return Err(Error::<T, I>::IdtyNotAllowedToClaimMembership.into());
}
// Verify phase
let idty_id = Self::ensure_origin_and_get_idty_id(origin, maybe_idty_id)?;
if !PendingMembership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipRequestNotFound.into());
if Membership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipAlreadyAcquired.into());
}
let _ = Self::do_claim_membership(idty_id);
let metadata = PendingMembership::<T, I>::take(&idty_id)
.ok_or(Error::<T, I>::MembershipRequestNotFound)?;
// Apply phase
Self::do_renew_membership_inner(idty_id);
Self::deposit_event(Event::MembershipAcquired(idty_id));
T::OnEvent::on_event(&sp_membership::Event::MembershipAcquired(idty_id, metadata));
Ok(().into())
}
......@@ -290,22 +271,12 @@ pub mod pallet {
#[pallet::weight(0)]
pub fn renew_membership(
origin: OriginFor<T>,
idty_id: T::IdtyId,
maybe_idty_id: Option<T::IdtyId>,
) -> DispatchResultWithPostInfo {
let allowed =
match T::IsOriginAllowedToUseIdty::is_origin_allowed_to_use_idty(&origin, &idty_id)
{
OriginPermission::Forbidden => {
return Err(Error::<T, I>::OriginNotAllowedToUseIdty.into())
}
OriginPermission::Allowed => {
T::IsIdtyAllowedToRenewMembership::is_idty_allowed_to_renew_membership(
&idty_id,
)
}
OriginPermission::Root => true,
};
if !allowed {
// Verify phase
let idty_id = Self::ensure_origin_and_get_idty_id(origin, maybe_idty_id)?;
if !T::IsIdtyAllowedToRenewMembership::is_idty_allowed_to_renew_membership(&idty_id) {
return Err(Error::<T, I>::IdtyNotAllowedToRenewMembership.into());
}
......@@ -326,14 +297,12 @@ pub mod pallet {
#[pallet::weight(0)]
pub fn revoke_membership(
origin: OriginFor<T>,
idty_id: T::IdtyId,
maybe_idty_id: Option<T::IdtyId>,
) -> DispatchResultWithPostInfo {
if T::IsOriginAllowedToUseIdty::is_origin_allowed_to_use_idty(&origin, &idty_id)
== OriginPermission::Forbidden
{
return Err(Error::<T, I>::OriginNotAllowedToUseIdty.into());
}
// Verify phase
let idty_id = Self::ensure_origin_and_get_idty_id(origin, maybe_idty_id)?;
// Apply phase
let _ = Self::do_revoke_membership(idty_id);
Ok(().into())
......@@ -343,18 +312,10 @@ pub mod pallet {
// INTERNAL FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> {
pub(super) fn do_claim_membership(idty_id: T::IdtyId) -> Weight {
let mut total_weight = 1;
PendingMembership::<T, I>::remove(&idty_id);
total_weight += Self::do_renew_membership_inner(idty_id);
Self::deposit_event(Event::MembershipAcquired(idty_id));
T::OnEvent::on_event(sp_membership::Event::MembershipAcquired(idty_id));
total_weight
}
pub(super) fn do_renew_membership(idty_id: T::IdtyId) -> Weight {
let total_weight = Self::do_renew_membership_inner(idty_id);
Self::deposit_event(Event::MembershipRenewed(idty_id));
T::OnEvent::on_event(sp_membership::Event::MembershipRenewed(idty_id));
T::OnEvent::on_event(&sp_membership::Event::MembershipRenewed(idty_id));
total_weight
}
fn do_renew_membership_inner(idty_id: T::IdtyId) -> Weight {
......@@ -372,36 +333,68 @@ pub mod pallet {
MembershipsExpireOn::<T, I>::append(expire_on, idty_id);
0
}
fn do_request_membership(
idty_id: T::IdtyId,
metadata: T::MetaData,
) -> DispatchResultWithPostInfo {
if PendingMembership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipAlreadyRequested.into());
}
if Membership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipAlreadyAcquired.into());
}
if RevokedMembership::<T, I>::contains_key(&idty_id) {
return Err(Error::<T, I>::MembershipRevokedRecently.into());
}
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let expire_on = block_number + T::PendingMembershipPeriod::get();
PendingMembership::<T, I>::insert(idty_id, metadata);
PendingMembershipsExpireOn::<T, I>::append(expire_on, idty_id);
Self::deposit_event(Event::MembershipRequested(idty_id));
T::OnEvent::on_event(&sp_membership::Event::MembershipRequested(idty_id));
Ok(().into())
}
pub(super) fn do_revoke_membership(idty_id: T::IdtyId) -> Weight {
Self::remove_membership(&idty_id);
if T::RevocationPeriod::get() > Zero::zero() {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let pruned_on = block_number + T::RevocationPeriod::get();
if Self::remove_membership(&idty_id) {
if T::RevocationPeriod::get() > Zero::zero() {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let pruned_on = block_number + T::RevocationPeriod::get();
RevokedMembership::<T, I>::insert(idty_id, ());
RevokedMembershipsPrunedOn::<T, I>::append(pruned_on, idty_id);
RevokedMembership::<T, I>::insert(idty_id, ());
RevokedMembershipsPrunedOn::<T, I>::append(pruned_on, idty_id);
}
Self::deposit_event(Event::MembershipRevoked(idty_id));
T::OnEvent::on_event(&sp_membership::Event::MembershipRevoked(idty_id));
}
Self::deposit_event(Event::MembershipRevoked(idty_id));
T::OnEvent::on_event(sp_membership::Event::MembershipRevoked(idty_id));
0
}
fn ensure_origin_and_get_idty_id(
origin: OriginFor<T>,
maybe_idty_id: Option<T::IdtyId>,
) -> Result<T::IdtyId, DispatchError> {
match origin.into() {
Ok(RawOrigin::Root) => {
maybe_idty_id.ok_or_else(|| Error::<T, I>::IdtyIdNotFound.into())
}
Ok(RawOrigin::Signed(account_id)) => T::IdtyIdOf::convert(account_id)
.ok_or_else(|| Error::<T, I>::IdtyIdNotFound.into()),
_ => Err(BadOrigin.into()),
}
}
fn expire_memberships(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
use frame_support::storage::generator::StorageMap as _;
if let Some(identities_ids) = MembershipsExpireOn::<T, I>::from_query_to_optional_value(
MembershipsExpireOn::<T, I>::take(block_number),
) {
for idty_id in identities_ids {
if let Some(member_data) = Self::get_membership(&idty_id) {
if member_data.expire_on == block_number {
Self::remove_membership(&idty_id);
Self::deposit_event(Event::MembershipExpired(idty_id));
total_weight += T::OnEvent::on_event(
sp_membership::Event::MembershipExpired(idty_id),
);
}
for idty_id in MembershipsExpireOn::<T, I>::take(block_number) {
if let Some(member_data) = Self::get_membership(&idty_id) {
if member_data.expire_on == block_number {
Self::remove_membership(&idty_id);
Self::deposit_event(Event::MembershipExpired(idty_id));
total_weight +=
T::OnEvent::on_event(&sp_membership::Event::MembershipExpired(idty_id));
}
}
}
......@@ -411,19 +404,11 @@ pub mod pallet {
fn expire_pending_memberships(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
use frame_support::storage::generator::StorageMap as _;
if let Some(identities_ids) =
PendingMembershipsExpireOn::<T, I>::from_query_to_optional_value(
PendingMembershipsExpireOn::<T, I>::take(block_number),
)
{
for idty_id in identities_ids {
PendingMembership::<T, I>::remove(&idty_id);
Self::deposit_event(Event::PendingMembershipExpired(idty_id));
total_weight += T::OnEvent::on_event(
sp_membership::Event::PendingMembershipExpired(idty_id),
);
}
for idty_id in PendingMembershipsExpireOn::<T, I>::take(block_number) {
PendingMembership::<T, I>::remove(&idty_id);
Self::deposit_event(Event::PendingMembershipExpired(idty_id));
total_weight +=
T::OnEvent::on_event(&sp_membership::Event::PendingMembershipExpired(idty_id));
}
total_weight
......@@ -431,12 +416,7 @@ pub mod pallet {
fn prune_revoked_memberships(block_number: T::BlockNumber) -> Weight {
let total_weight: Weight = 0;
use frame_support::storage::generator::StorageMap as _;
if let Some(identities_ids) =
RevokedMembershipsPrunedOn::<T, I>::from_query_to_optional_value(
RevokedMembershipsPrunedOn::<T, I>::take(block_number),
)
{
if let Some(identities_ids) = RevokedMembershipsPrunedOn::<T, I>::take(block_number) {
for idty_id in identities_ids {
RevokedMembership::<T, I>::remove(idty_id);
}
......@@ -446,32 +426,16 @@ pub mod pallet {
}
pub(super) fn is_member_inner(idty_id: &T::IdtyId) -> bool {
if T::ExternalizeMembershipStorage::get() {
T::MembershipExternalStorage::is_member(idty_id)
} else {
Membership::<T, I>::contains_key(idty_id)
}
Membership::<T, I>::contains_key(idty_id)
}
fn insert_membership(idty_id: T::IdtyId, membership_data: MembershipData<T::BlockNumber>) {
if T::ExternalizeMembershipStorage::get() {
T::MembershipExternalStorage::insert(idty_id, membership_data);
} else {
Membership::<T, I>::insert(idty_id, membership_data);
}
Membership::<T, I>::insert(idty_id, membership_data);
}
fn get_membership(idty_id: &T::IdtyId) -> Option<MembershipData<T::BlockNumber>> {
if T::ExternalizeMembershipStorage::get() {
T::MembershipExternalStorage::get(idty_id)
} else {
Membership::<T, I>::try_get(idty_id).ok()
}
Membership::<T, I>::try_get(idty_id).ok()
}
fn remove_membership(idty_id: &T::IdtyId) {
if T::ExternalizeMembershipStorage::get() {
T::MembershipExternalStorage::remove(idty_id);
} else {
Membership::<T, I>::remove(idty_id);
}
fn remove_membership(idty_id: &T::IdtyId) -> bool {
Membership::<T, I>::take(idty_id).is_some()
}
}
}
......@@ -482,33 +446,14 @@ impl<T: Config<I>, I: 'static> IsInPendingMemberships<T::IdtyId> for Pallet<T, I
}
}
impl<T: Config<I>, I: 'static> IsMember<T::IdtyId> for Pallet<T, I> {
impl<T: Config<I>, I: 'static> sp_runtime::traits::IsMember<T::IdtyId> for Pallet<T, I> {
fn is_member(idty_id: &T::IdtyId) -> bool {
Self::is_member_inner(idty_id)
}
}
impl<T: Config<I>, I: 'static> MembershipAction<T::IdtyId, T::Origin> for Pallet<T, I> {
fn request_membership_(origin: T::Origin, idty_id: T::IdtyId) -> DispatchResultWithPostInfo {
Pallet::<T, I>::request_membership(origin, idty_id)
}
fn claim_membership_(origin: T::Origin, idty_id: T::IdtyId) -> DispatchResultWithPostInfo {
Pallet::<T, I>::claim_membership(origin, idty_id)
}
fn renew_membership_(origin: T::Origin, idty_id: T::IdtyId) -> DispatchResultWithPostInfo {
Pallet::<T, I>::renew_membership(origin, idty_id)
}
fn revoke_membership_(origin: T::Origin, idty_id: T::IdtyId) -> DispatchResultWithPostInfo {
Pallet::<T, I>::revoke_membership(origin, idty_id)
}
fn force_claim_membership(idty_id: T::IdtyId) -> Weight {
Self::do_claim_membership(idty_id)
}
fn force_renew_membership(idty_id: T::IdtyId) -> Weight {
Self::do_renew_membership(idty_id)
}
fn force_revoke_membership(idty_id: T::IdtyId) -> Weight {
Self::do_revoke_membership(idty_id)
impl<T: Config<I>, I: 'static> MembersCount for Pallet<T, I> {
fn members_count() -> u32 {
Membership::<T, I>::count()
}
}
......@@ -21,11 +21,9 @@ use frame_support::{
};
use frame_system as system;
use sp_core::H256;
use sp_membership::traits::IsOriginAllowedToUseIdty;
use sp_membership::OriginPermission;
use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup},
traits::{BlakeTwo256, ConvertInto, IdentityLookup},
BuildStorage,
};
......@@ -79,21 +77,7 @@ impl system::Config for Test {
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
pub struct IsOriginAllowedToUseIdtyImpl;
impl IsOriginAllowedToUseIdty<Origin, IdtyId> for IsOriginAllowedToUseIdtyImpl {
fn is_origin_allowed_to_use_idty(o: &Origin, idty_id: &IdtyId) -> OriginPermission {
match o.clone().into() {
Ok(system::RawOrigin::Root) => OriginPermission::Root,
Ok(system::RawOrigin::Signed(account_id)) if account_id == *idty_id => {
OriginPermission::Allowed
}
_ => OriginPermission::Forbidden,
}
}
}
parameter_types! {
pub const ExternalizeMembershipStorage: bool = false;
pub const MembershipPeriod: BlockNumber = 5;
pub const PendingMembershipPeriod: BlockNumber = 3;
pub const RenewablePeriod: BlockNumber = 2;
......@@ -101,16 +85,14 @@ parameter_types! {
}
impl pallet_membership::Config for Test {
type IsIdtyAllowedToClaimMembership = ();
type IsIdtyAllowedToRenewMembership = ();
type IsIdtyAllowedToRequestMembership = ();
type IsOriginAllowedToUseIdty = IsOriginAllowedToUseIdtyImpl;
type Event = Event;
type ExternalizeMembershipStorage = ExternalizeMembershipStorage;
type IdtyId = IdtyId;
type OnEvent = ();
type MembershipExternalStorage = ();
type IdtyIdOf = ConvertInto;
type MembershipPeriod = MembershipPeriod;
type MetaData = ();
type OnEvent = ();
type PendingMembershipPeriod = PendingMembershipPeriod;
type RenewablePeriod = RenewablePeriod;
type RevocationPeriod = RevocationPeriod;
......
......@@ -19,8 +19,9 @@ use crate::mock::*;
use crate::{Error, Event};
use frame_support::assert_ok;
use maplit::btreemap;
use sp_membership::traits::{IsInPendingMemberships, IsMember};
use sp_membership::traits::*;
use sp_membership::MembershipData;
use sp_runtime::traits::IsMember;
fn default_gen_conf() -> DefaultMembershipConfig {
DefaultMembershipConfig {
......@@ -45,6 +46,7 @@ fn test_genesis_build() {
renewable_on: 2
})
);
assert_eq!(DefaultMembership::members_count(), 1);
});
}
......@@ -54,19 +56,31 @@ fn test_membership_not_yet_renewable() {
run_to_block(1);
// Merbership 0 cannot be renewed before #2
assert_eq!(
DefaultMembership::renew_membership(Origin::signed(0), 0),
DefaultMembership::renew_membership(Origin::signed(0), None),
Err(Error::<Test, _>::MembershipNotYetRenewable.into())
);
});
}
#[test]
fn test_membership_already_acquired() {
new_test_ext(default_gen_conf()).execute_with(|| {
run_to_block(1);
// Merbership 0 cannot be reclaimed
assert_eq!(
DefaultMembership::claim_membership(Origin::signed(0), None),
Err(Error::<Test, _>::MembershipAlreadyAcquired.into())
);
});
}
#[test]
fn test_membership_request_not_found() {
new_test_ext(default_gen_conf()).execute_with(|| {
run_to_block(1);
// Merbership 0 cannot be reclaimed
assert_eq!(
DefaultMembership::claim_membership(Origin::signed(0), 0),
DefaultMembership::claim_membership(Origin::signed(1), None),
Err(Error::<Test, _>::MembershipRequestNotFound.into())
);
});
......@@ -77,7 +91,7 @@ fn test_membership_renewal() {
new_test_ext(default_gen_conf()).execute_with(|| {
run_to_block(2);
// Merbership 0 can be renewable on block #2
assert_ok!(DefaultMembership::renew_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::renew_membership(Origin::signed(0), None),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipRenewed(0))
......@@ -106,7 +120,10 @@ fn test_membership_revocation() {
new_test_ext(default_gen_conf()).execute_with(|| {
run_to_block(1);
// Merbership 0 can be revocable on block #1
assert_ok!(DefaultMembership::revoke_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::revoke_membership(
Origin::signed(0),
None
),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipRevoked(0))
......@@ -115,13 +132,13 @@ fn test_membership_revocation() {
// Membership 0 can't request membership before the end of RevokePeriod (1 + 4 = 5)
run_to_block(2);
assert_eq!(
DefaultMembership::request_membership(Origin::signed(0), 0),
DefaultMembership::request_membership(Origin::signed(0), ()),
Err(Error::<Test, _>::MembershipRevokedRecently.into())
);
// Membership 0 can request membership after the end of RevokePeriod (1 + 4 = 5)
run_to_block(5);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), ()),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipRequested(0))
......@@ -134,7 +151,7 @@ fn test_pending_membership_expiration() {
new_test_ext(Default::default()).execute_with(|| {
// Idty 0 request membership
run_to_block(1);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), ()),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipRequested(0))
......@@ -159,7 +176,7 @@ fn test_membership_workflow() {
new_test_ext(Default::default()).execute_with(|| {
// Idty 0 request membership
run_to_block(1);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::request_membership(Origin::signed(0), ()),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipRequested(0))
......@@ -167,7 +184,7 @@ fn test_membership_workflow() {
// Then, idty 0 claim membership
run_to_block(2);
assert_ok!(DefaultMembership::claim_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::claim_membership(Origin::signed(0), None),);
assert_eq!(
System::events()[0].event,
RuntimeEvent::DefaultMembership(Event::MembershipAcquired(0))
......@@ -176,13 +193,13 @@ fn test_membership_workflow() {
// Then, idty 0 claim renewal, should fail
run_to_block(3);
assert_eq!(
DefaultMembership::renew_membership(Origin::signed(0), 0),
DefaultMembership::renew_membership(Origin::signed(0), None),
Err(Error::<Test, _>::MembershipNotYetRenewable.into())
);
// Then, idty 0 claim renewal after renewable period, should success
run_to_block(2 + RenewablePeriod::get());
assert_ok!(DefaultMembership::renew_membership(Origin::signed(0), 0),);
assert_ok!(DefaultMembership::renew_membership(Origin::signed(0), None),);
// Then, idty 0 shoul still member until membership period ended
run_to_block(2 + RenewablePeriod::get() + MembershipPeriod::get() - 1);
......
[package]
authors = ['librelois <c@elo.tf>']
description = 'FRAME pallet to provide randomness to users.'
edition = '2018'
homepage = 'https://substrate.dev'
license = 'AGPL-3.0'
name = 'pallet-provide-randomness'
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
version = '3.0.0'
[features]
default = ['std']
runtime-benchmarks = ['frame-benchmarking']
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
'frame-benchmarking/std',
"sp-core/std",
"sp-io/std",
"sp-std/std",
]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[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.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-std]
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'
### DOC ###
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::boxed_local)]
mod types;
use frame_support::pallet_prelude::Weight;
use sp_core::H256;
use sp_std::prelude::*;
pub use pallet::*;
pub use types::*;
pub type RequestId = u64;
pub trait OnFilledRandomness {
fn on_filled_randomness(request_id: RequestId, randomness: H256) -> Weight;
}
impl OnFilledRandomness for () {
fn on_filled_randomness(_: RequestId, _: H256) -> Weight {
0
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::{
Currency, ExistenceRequirement, OnUnbalanced, Randomness, StorageVersion, WithdrawReasons,
};
use frame_system::pallet_prelude::*;
use sp_core::H256;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
<T as frame_system::Config>::AccountId,
>>::NegativeImbalance;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
/// Configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config<Hash = H256> {
// The currency
type Currency: Currency<Self::AccountId>;
/// The overarching event type.
type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>;
/// Get the current epoch index
type GetCurrentEpochIndex: Get<u64>;
/// Maximum number of not yet filled requests
#[pallet::constant]
type MaxRequests: Get<u32>;
/// The price of a request
#[pallet::constant]
type RequestPrice: Get<BalanceOf<Self>>;
/// On filled randomness
type OnFilledRandomness: OnFilledRandomness;
/// Handler for the unbalanced reduction when the requestor pays fees.
type OnUnbalanced: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// A safe source of randomness from the current block
type CurrentBlockRandomness: Randomness<Option<H256>, Self::BlockNumber>;
/// A safe source of randomness from one epoch ago
type RandomnessFromOneEpochAgo: Randomness<H256, Self::BlockNumber>;
}
// STORAGE //
#[pallet::storage]
pub(super) type NexEpochHookIn<T: Config> = StorageValue<_, u8, ValueQuery>;
#[pallet::storage]
pub(super) type RequestIdProvider<T: Config> = StorageValue<_, RequestId, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn requests_ready_at_next_block)]
pub type RequestsReadyAtNextBlock<T: Config> = StorageValue<_, Vec<Request>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn requests_ready_at_epoch)]
pub type RequestsReadyAtEpoch<T: Config> =
StorageMap<_, Twox64Concat, u64, Vec<Request>, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn requests_ids)]
pub type RequestsIds<T: Config> =
CountedStorageMap<_, Twox64Concat, RequestId, (), OptionQuery>;
// EVENTS //
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// Filled randomness
FilledRandomness {
request_id: RequestId,
randomness: H256,
},
/// Requested randomness
RequestedRandomness {
request_id: RequestId,
salt: H256,
r#type: RandomnessType,
},
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// The queue is full, pleasy retry later
FullQueue,
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Request a randomness
#[pallet::weight(100_000_000)]
pub fn request(
origin: OriginFor<T>,
randomness_type: RandomnessType,
salt: H256,
) -> DispatchResult {
let who = ensure_signed(origin)?;
let request_id = Self::do_request(&who, randomness_type, salt)?;
Self::deposit_event(Event::RequestedRandomness {
request_id,
salt,
r#type: randomness_type,
});
Ok(())
}
}
// HOOKS //
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: T::BlockNumber) -> Weight {
let mut total_weight = 0;
total_weight += 100_000;
for Request { request_id, salt } in RequestsReadyAtNextBlock::<T>::take() {
let randomness = T::CurrentBlockRandomness::random(salt.as_ref())
.0
.unwrap_or_default();
total_weight += T::OnFilledRandomness::on_filled_randomness(request_id, randomness);
Self::deposit_event(Event::FilledRandomness {
request_id,
randomness,
});
total_weight += 100_000;
}
let next_epoch_hook_in = NexEpochHookIn::<T>::mutate(|next_in| {
core::mem::replace(next_in, next_in.saturating_sub(1))
});
if next_epoch_hook_in == 1 {
total_weight += 100_000;
for Request { request_id, salt } in
RequestsReadyAtEpoch::<T>::take(T::GetCurrentEpochIndex::get())
{
let randomness = T::RandomnessFromOneEpochAgo::random(salt.as_ref()).0;
total_weight +=
T::OnFilledRandomness::on_filled_randomness(request_id, randomness);
Self::deposit_event(Event::FilledRandomness {
request_id,
randomness,
});
total_weight += 100_000;
}
}
total_weight
}
}
// PUBLIC FUNCTIONS //
impl<T: Config> Pallet<T> {
pub fn do_request(
requestor: &T::AccountId,
randomness_type: RandomnessType,
salt: H256,
) -> Result<RequestId, DispatchError> {
// Verify phase
ensure!(
RequestsIds::<T>::count() < T::MaxRequests::get(),
Error::<T>::FullQueue
);
Self::pay_request(requestor)?;
// Apply phase
Ok(Self::apply_request(randomness_type, salt))
}
pub fn force_request(randomness_type: RandomnessType, salt: H256) -> RequestId {
Self::apply_request(randomness_type, salt)
}
pub fn on_new_epoch() {
NexEpochHookIn::<T>::put(5)
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
fn pay_request(requestor: &T::AccountId) -> DispatchResult {
let imbalance = T::Currency::withdraw(
requestor,
T::RequestPrice::get(),
WithdrawReasons::FEE,
ExistenceRequirement::KeepAlive,
)?;
T::OnUnbalanced::on_unbalanced(imbalance);
Ok(())
}
fn apply_request(randomness_type: RandomnessType, salt: H256) -> RequestId {
let request_id = RequestIdProvider::<T>::mutate(|next_request_id| {
core::mem::replace(next_request_id, next_request_id.saturating_add(1))
});
RequestsIds::<T>::insert(request_id, ());
let current_epoch = T::GetCurrentEpochIndex::get();
match randomness_type {
RandomnessType::RandomnessFromPreviousBlock => {
RequestsReadyAtNextBlock::<T>::append(Request { request_id, salt });
}
RandomnessType::RandomnessFromOneEpochAgo => {
if NexEpochHookIn::<T>::get() > 1 {
RequestsReadyAtEpoch::<T>::append(
current_epoch + 3,
Request { request_id, salt },
);
} else {
RequestsReadyAtEpoch::<T>::append(
current_epoch + 2,
Request { request_id, salt },
);
}
}
RandomnessType::RandomnessFromTwoEpochsAgo => {
if NexEpochHookIn::<T>::get() > 1 {
RequestsReadyAtEpoch::<T>::append(
current_epoch + 4,
Request { request_id, salt },
);
} else {
RequestsReadyAtEpoch::<T>::append(
current_epoch + 3,
Request { request_id, salt },
);
}
}
}
request_id
}
}
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
//! Various basic types for use in pallet provide randomness
use super::RequestId;
use codec::{Decode, Encode};
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use sp_core::H256;
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub enum RandomnessType {
RandomnessFromPreviousBlock,
RandomnessFromOneEpochAgo,
RandomnessFromTwoEpochsAgo,
}
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct Request {
pub request_id: RequestId,
pub salt: H256,
}
......@@ -36,22 +36,22 @@ version = '2.3.1'
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
### DOC ###
......@@ -65,19 +65,19 @@ version = '1.0.119'
[dev-dependencies.pallet-balances]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
......@@ -34,49 +34,40 @@ use sp_std::prelude::*;
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use scale_info::TypeInfo;
use frame_support::traits::StorageVersion;
// CONFIG //
#[pallet::config]
pub trait Config: frame_system::Config {}
// STORAGE //
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
// A value placed in storage that represents the current version of the Balances storage.
// This value is used by the `on_runtime_upgrade` logic to determine whether we run
// storage migration logic. This should match directly with the semantic versions of the Rust crate.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum Releases {
V1_0_0,
}
impl Default for Releases {
fn default() -> Self {
Releases::V1_0_0
}
}
// CONFIG //
/// Storage version of the pallet.
#[pallet::storage]
pub(super) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
#[pallet::config]
pub trait Config: frame_system::Config {}
// STORAGE //
#[pallet::storage]
#[pallet::getter(fn ud_accounts)]
pub type UdAccounts<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, (), ValueQuery>;
pub type UdAccounts<T: Config> = StorageMap<_, Blake2_128Concat, T::AccountId, u32, ValueQuery>;
#[pallet::storage]
#[pallet::getter(fn ud_accounts_count)]
pub(super) type UdAccountsCounter<T: Config> = StorageValue<_, u64, ValueQuery>;
#[pallet::storage]
pub(super) type ToBeRemoved<T: Config> = StorageValue<_, Vec<u32>, ValueQuery>;
// GENESIS //
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub ud_accounts: sp_std::collections::btree_set::BTreeSet<T::AccountId>,
pub ud_accounts: sp_std::collections::btree_map::BTreeMap<T::AccountId, u32>,
}
#[cfg(feature = "std")]
......@@ -91,10 +82,9 @@ pub mod pallet {
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
<StorageVersion<T>>::put(Releases::V1_0_0);
<UdAccountsCounter<T>>::put(self.ud_accounts.len() as u64);
for account in &self.ud_accounts {
<UdAccounts<T>>::insert(account, ());
for (account, index) in &self.ud_accounts {
<UdAccounts<T>>::insert(account, index);
}
}
}
......@@ -103,29 +93,55 @@ pub mod pallet {
impl<T: Config> Pallet<T> {
pub fn account_list() -> Vec<T::AccountId> {
<UdAccounts<T>>::iter().map(|(k, _v)| k).collect()
let mut to_be_removed = ToBeRemoved::<T>::take();
to_be_removed.sort_unstable();
let mut accounts_to_pass = Vec::new();
let mut accounts_to_remove = Vec::new();
<UdAccounts<T>>::iter().for_each(|(k, v)| {
if to_be_removed.binary_search(&v).is_ok() {
accounts_to_remove.push(k);
} else {
accounts_to_pass.push(k);
}
});
for account in accounts_to_remove {
UdAccounts::<T>::remove(account);
}
accounts_to_pass
}
pub fn replace_account(
old_account_opt: Option<T::AccountId>,
new_account_opt: Option<T::AccountId>,
index: u32,
) -> Weight {
if let Some(old_account) = old_account_opt {
if let Some(new_account) = new_account_opt {
Self::replace_account_inner(old_account, new_account)
Self::replace_account_inner(old_account, new_account, index)
} else {
Self::del_account(old_account)
}
} else if let Some(new_account) = new_account_opt {
Self::add_account(new_account)
Self::add_account(new_account, index)
} else {
0
}
}
fn replace_account_inner(old_account: T::AccountId, new_account: T::AccountId) -> Weight {
pub fn remove_account(account_index: u32) -> Weight {
ToBeRemoved::<T>::append(account_index);
UdAccountsCounter::<T>::mutate(|counter| counter.saturating_sub(1));
0
}
fn replace_account_inner(
old_account: T::AccountId,
new_account: T::AccountId,
index: u32,
) -> Weight {
if <UdAccounts<T>>::contains_key(&old_account) {
if !<UdAccounts<T>>::contains_key(&new_account) {
<UdAccounts<T>>::remove(&old_account);
<UdAccounts<T>>::insert(&new_account, ());
<UdAccounts<T>>::insert(&new_account, index);
} else {
frame_support::runtime_print!(
"ERROR: replace_account(): new_account {:?} already added",
......@@ -140,14 +156,10 @@ pub mod pallet {
}
0
}
fn add_account(account: T::AccountId) -> Weight {
fn add_account(account: T::AccountId, index: u32) -> Weight {
if !<UdAccounts<T>>::contains_key(&account) {
<UdAccounts<T>>::insert(&account, ());
if let Ok(counter) = <UdAccountsCounter<T>>::try_get() {
<UdAccountsCounter<T>>::put(counter.saturating_add(1));
} else {
<UdAccountsCounter<T>>::put(1);
}
<UdAccounts<T>>::insert(&account, index);
UdAccountsCounter::<T>::mutate(|counter| counter.saturating_add(1));
} else {
frame_support::runtime_print!(
"ERROR: add_account(): account {:?} already added",
......@@ -159,13 +171,8 @@ pub mod pallet {
fn del_account(account: T::AccountId) -> Weight {
if <UdAccounts<T>>::contains_key(&account) {
<UdAccounts<T>>::remove(&account);
if let Ok(counter) = <UdAccountsCounter<T>>::try_get() {
<UdAccountsCounter<T>>::put(counter.saturating_sub(1));
} else {
frame_support::runtime_print!(
"FATAL ERROR: del_account(): UdAccountsCounter is None!"
);
}
UdAccountsCounter::<T>::mutate(|counter| counter.saturating_sub(1));
} else {
frame_support::runtime_print!(
"ERROR: del_account(): account {:?} already deleted",
......
......@@ -37,37 +37,37 @@ version = '2.3.1'
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-arithmetic]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
### DOC ###
......@@ -80,7 +80,7 @@ targets = ['x86_64-unknown-linux-gnu']
[dev-dependencies.pallet-balances]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.serde]
features = ["derive"]
......@@ -89,14 +89,14 @@ version = '1.0.119'
[dev-dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dev-dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
......@@ -41,12 +41,22 @@ const OFFCHAIN_PREFIX_UD_HISTORY: &[u8] = b"ud::history::";
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_support::traits::StorageVersion;
use frame_system::pallet_prelude::*;
use scale_info::TypeInfo;
pub type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config {
// The currency
......@@ -83,27 +93,6 @@ pub mod pallet {
// STORAGE //
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
// A value placed in storage that represents the current version of the Balances storage.
// This value is used by the `on_runtime_upgrade` logic to determine whether we run
// storage migration logic. This should match directly with the semantic versions of the Rust crate.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum Releases {
V1_0_0,
}
impl Default for Releases {
fn default() -> Self {
Releases::V1_0_0
}
}
/// Storage version of the pallet.
#[pallet::storage]
pub(super) type StorageVersion<T: Config> = StorageValue<_, Releases, ValueQuery>;
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct LastReeval<T: Config> {
members_count: BalanceOf<T>,
......@@ -154,7 +143,6 @@ pub mod pallet {
assert!(!self.first_ud.is_zero());
assert!(self.initial_monetary_mass >= T::Currency::total_issuance());
<StorageVersion<T>>::put(Releases::V1_0_0);
<CurrentUdStorage<T>>::put(self.first_ud);
<MonetaryMassStorage<T>>::put(self.initial_monetary_mass);
}
......@@ -189,11 +177,19 @@ pub mod pallet {
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A new universal dividend is created
/// [ud_amout, members_count]
NewUdCreated(BalanceOf<T>, BalanceOf<T>),
/// [amout, members_count]
NewUdCreated {
amount: BalanceOf<T>,
monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>,
},
/// The universal dividend has been re-evaluated
/// [new_ud_amount, monetary_mass, members_count]
UdReevalued(BalanceOf<T>, BalanceOf<T>, BalanceOf<T>),
UdReevalued {
new_ud_amount: BalanceOf<T>,
monetary_mass: BalanceOf<T>,
members_count: BalanceOf<T>,
},
}
// INTERNAL FUNCTIONS //
......@@ -209,8 +205,14 @@ pub mod pallet {
Self::write_ud_history(n, account_id, ud_amount);
}
<MonetaryMassStorage<T>>::put(monetary_mass + (ud_amount * members_count));
Self::deposit_event(Event::NewUdCreated(ud_amount, members_count));
let new_monetary_mass =
monetary_mass.saturating_add(ud_amount.saturating_mul(members_count));
MonetaryMassStorage::<T>::put(new_monetary_mass);
Self::deposit_event(Event::NewUdCreated {
amount: ud_amount,
members_count,
monetary_mass: new_monetary_mass,
});
total_weight
}
......@@ -249,11 +251,11 @@ pub mod pallet {
<CurrentUdStorage<T>>::put(new_ud_amount);
Self::deposit_event(Event::UdReevalued(
Self::deposit_event(Event::UdReevalued {
new_ud_amount,
monetary_mass,
members_count,
));
});
total_weight
}
......
......@@ -47,7 +47,11 @@ fn test_ud_creation() {
events[9],
EventRecord {
phase: Phase::Initialization,
event: Event::UniversalDividend(crate::Event::NewUdCreated(1000, 3)),
event: Event::UniversalDividend(crate::Event::NewUdCreated {
amount: 1_000,
monetary_mass: 3_000,
members_count: 3,
}),
topics: vec![],
}
);
......
[package]
authors = ['librelois <c@elo.tf>']
description = 'FRAME pallet to upgrade specified origin to root.'
edition = '2018'
homepage = 'https://substrate.dev'
license = 'AGPL-3.0'
name = 'pallet-upgrade-origin'
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s'
version = '3.0.0'
[features]
default = ['std']
runtime-benchmarks = ['frame-benchmarking']
std = [
'codec/std',
'frame-support/std',
'frame-system/std',
'frame-benchmarking/std',
"sp-io/std",
"sp-std/std",
]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[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.sp-io]
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'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Substrate-Libre-Currency is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::boxed_local)]
use frame_support::{
dispatch::PostDispatchInfo,
traits::{IsSubType, UnfilteredDispatchable},
weights::GetDispatchInfo,
};
use sp_runtime::traits::Dispatchable;
use sp_std::prelude::*;
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// Configuration trait.
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>;
/// The overarching call type.
type Call: Parameter
+ Dispatchable<Origin = Self::Origin, PostInfo = PostDispatchInfo>
+ GetDispatchInfo
+ From<frame_system::Call<Self>>
+ UnfilteredDispatchable<Origin = Self::Origin>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::Call>;
/// The upgradable origin
type UpgradableOrigin: EnsureOrigin<Self::Origin>;
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
/// A call was dispatched as root from an upgradable origin
DispatchedAsRoot { result: DispatchResult },
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Dispatches a function call from root origin.
///
/// The weight of this call is defined by the caller.
#[pallet::weight(*_weight)]
pub fn dispatch_as_root(
origin: OriginFor<T>,
call: Box<<T as Config>::Call>,
_weight: Weight,
) -> DispatchResultWithPostInfo {
T::UpgradableOrigin::ensure_origin(origin)?;
let res = call.dispatch_bypass_filter(frame_system::RawOrigin::Root.into());
Self::deposit_event(Event::DispatchedAsRoot {
result: res.map(|_| ()).map_err(|e| e.error),
});
Ok(Pays::No.into())
}
}
}
......@@ -15,6 +15,7 @@ std = [
'codec/std',
'frame-support/std',
'serde',
'sp-runtime/std',
'sp-std/std',
]
try-runtime = ['frame-support/try-runtime']
......@@ -33,17 +34,22 @@ version = '2.3.1'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-01'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[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-01'
branch = 'duniter-monthly-2022-02'
### DOC ###
......