#![cfg_attr(not(feature = "std"), no_std)]
use frame_support::pallet_prelude::*;
pub use pallet::*;
use scale_info::TypeInfo;
use sp_std::vec::Vec;
pub use weights::*;
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum KeyPurpose {
P2PDiscovery,
P2PDocumentSigning,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum KeyType {
ECDSA,
EDDSA,
}
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub struct Key<BlockNumber, Balance> {
purpose: KeyPurpose,
key_type: KeyType,
revoked_at: Option<BlockNumber>,
deposit: Balance,
}
pub type KeyId<Hash> = (Hash, KeyPurpose);
#[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct AddKey<Hash> {
key: Hash,
purpose: KeyPurpose,
key_type: KeyType,
}
#[frame_support::pallet]
pub mod pallet {
use frame_support::traits::ReservableCurrency;
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::AtLeast32BitUnsigned, FixedPointOperand};
use super::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type Balance: Member
+ Parameter
+ AtLeast32BitUnsigned
+ Default
+ Copy
+ MaxEncodedLen
+ FixedPointOperand
+ From<u64>
+ From<u128>
+ TypeInfo
+ TryInto<u64>;
type Currency: ReservableCurrency<Self::AccountId, Balance = Self::Balance>;
#[pallet::constant]
type MaxKeys: Get<u32>;
type DefaultKeyDeposit: Get<Self::Balance>;
type AdminOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type WeightInfo: WeightInfo;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
#[pallet::getter(fn get_key)]
pub(crate) type Keys<T: Config> = StorageDoubleMap<
_,
Blake2_128Concat,
T::AccountId,
Blake2_128Concat,
KeyId<T::Hash>,
Key<BlockNumberFor<T>, T::Balance>,
>;
#[pallet::storage]
#[pallet::getter(fn get_last_key_by_purpose)]
pub(crate) type LastKeyByPurpose<T: Config> =
StorageDoubleMap<_, Blake2_128Concat, T::AccountId, Blake2_128Concat, KeyPurpose, T::Hash>;
#[pallet::storage]
#[pallet::getter(fn get_key_deposit)]
pub(crate) type KeyDeposit<T: Config> =
StorageValue<_, T::Balance, ValueQuery, T::DefaultKeyDeposit>;
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
KeyAdded {
owner: T::AccountId,
key: T::Hash,
purpose: KeyPurpose,
key_type: KeyType,
},
KeyRevoked {
owner: T::AccountId,
key: T::Hash,
block_number: BlockNumberFor<T>,
},
DepositSet { new_deposit: T::Balance },
}
#[pallet::error]
pub enum Error<T> {
NoKeys,
TooManyKeys,
KeyAlreadyExists,
KeyNotFound,
KeyAlreadyRevoked,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(T::WeightInfo::add_keys(T::MaxKeys::get()))]
#[pallet::call_index(0)]
pub fn add_keys(origin: OriginFor<T>, keys: Vec<AddKey<T::Hash>>) -> DispatchResult {
let account_id = ensure_signed(origin)?;
ensure!(!keys.is_empty(), Error::<T>::NoKeys);
ensure!(
keys.len() <= T::MaxKeys::get() as usize,
Error::<T>::TooManyKeys
);
let key_deposit = <KeyDeposit<T>>::get();
for add_key in keys {
Self::add_key(account_id.clone(), add_key.clone(), key_deposit)?;
}
Ok(())
}
#[pallet::weight(T::WeightInfo::revoke_keys(T::MaxKeys::get()))]
#[pallet::call_index(1)]
pub fn revoke_keys(
origin: OriginFor<T>,
keys: Vec<T::Hash>,
key_purpose: KeyPurpose,
) -> DispatchResult {
let account_id = ensure_signed(origin)?;
ensure!(!keys.is_empty(), Error::<T>::NoKeys);
ensure!(
keys.len() <= T::MaxKeys::get() as usize,
Error::<T>::TooManyKeys
);
for key in keys {
Self::revoke_key(account_id.clone(), key, key_purpose.clone())?;
}
Ok(())
}
#[pallet::weight(T::WeightInfo::set_deposit())]
#[pallet::call_index(2)]
pub fn set_deposit(origin: OriginFor<T>, new_deposit: T::Balance) -> DispatchResult {
T::AdminOrigin::ensure_origin(origin)?;
<KeyDeposit<T>>::set(new_deposit);
Self::deposit_event(Event::DepositSet { new_deposit });
Ok(())
}
}
impl<T: Config> Pallet<T> {
fn add_key(
account_id: T::AccountId,
add_key: AddKey<T::Hash>,
key_deposit: T::Balance,
) -> DispatchResult {
T::Currency::reserve(&account_id, key_deposit)?;
let key_id: KeyId<T::Hash> = (add_key.key, add_key.purpose.clone());
<Keys<T>>::try_mutate(account_id.clone(), key_id, |key_opt| -> DispatchResult {
match key_opt {
Some(_) => Err(Error::<T>::KeyAlreadyExists.into()),
None => {
let _ = key_opt.insert(Key {
purpose: add_key.purpose.clone(),
key_type: add_key.key_type.clone(),
revoked_at: None,
deposit: key_deposit,
});
Ok(())
}
}
})?;
<LastKeyByPurpose<T>>::insert(account_id.clone(), add_key.purpose.clone(), add_key.key);
Self::deposit_event(Event::KeyAdded {
owner: account_id,
key: add_key.key,
purpose: add_key.purpose,
key_type: add_key.key_type,
});
Ok(())
}
fn revoke_key(
account_id: T::AccountId,
key: T::Hash,
key_purpose: KeyPurpose,
) -> DispatchResult {
let key_id: KeyId<T::Hash> = (key, key_purpose);
<Keys<T>>::try_mutate(
account_id.clone(),
key_id,
|storage_key| -> DispatchResult {
let storage_key = storage_key.as_mut().ok_or(Error::<T>::KeyNotFound)?;
if storage_key.revoked_at.is_some() {
return Err(Error::<T>::KeyAlreadyRevoked.into());
}
let block_number = <frame_system::Pallet<T>>::block_number();
storage_key.revoked_at = Some(block_number);
Self::deposit_event(Event::KeyRevoked {
owner: account_id,
key,
block_number,
});
Ok(())
},
)
}
}
}