#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::boxed_local)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
mod types;
pub mod weights;
use frame_support::{
pallet_prelude::Weight,
traits::{
fungible::{self, Balanced, Credit},
tokens::{Fortitude, Precision, Preservation},
},
};
use sp_core::H256;
use sp_std::prelude::*;
pub use pallet::*;
pub use types::*;
pub use weights::WeightInfo;
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 {
Weight::zero()
}
}
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{
pallet_prelude::*,
traits::{OnUnbalanced, Randomness, StorageVersion},
};
use frame_system::pallet_prelude::*;
use sp_core::H256;
type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <<T as Config>::Currency as fungible::Inspect<AccountIdOf<T>>>::Balance;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: frame_system::Config<Hash = H256> {
type Currency: fungible::Balanced<Self::AccountId> + fungible::Mutate<Self::AccountId>;
type GetCurrentEpochIndex: Get<u64>;
#[pallet::constant]
type MaxRequests: Get<u32>;
#[pallet::constant]
type RequestPrice: Get<BalanceOf<Self>>;
type OnFilledRandomness: OnFilledRandomness;
type OnUnbalanced: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
type ParentBlockRandomness: Randomness<Option<H256>, BlockNumberFor<Self>>;
type RandomnessFromOneEpochAgo: Randomness<H256, BlockNumberFor<Self>>;
type RuntimeEvent: From<Event> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: WeightInfo;
}
#[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>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event {
FilledRandomness {
request_id: RequestId,
randomness: H256,
},
RequestedRandomness {
request_id: RequestId,
salt: H256,
r#type: RandomnessType,
},
}
#[pallet::error]
pub enum Error<T> {
QueueFull,
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::request())]
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(())
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(_: BlockNumberFor<T>) -> Weight {
let mut total_weight = T::WeightInfo::on_initialize(0);
for Request { request_id, salt } in RequestsReadyAtNextBlock::<T>::take() {
let randomness = T::ParentBlockRandomness::random(salt.as_ref())
.0
.unwrap_or_default();
RequestsIds::<T>::remove(request_id);
total_weight += T::OnFilledRandomness::on_filled_randomness(request_id, randomness);
Self::deposit_event(Event::FilledRandomness {
request_id,
randomness,
});
total_weight +=
T::WeightInfo::on_initialize(2).saturating_sub(T::WeightInfo::on_initialize(1));
}
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 {
for Request { request_id, salt } in
RequestsReadyAtEpoch::<T>::take(T::GetCurrentEpochIndex::get())
{
let randomness = T::RandomnessFromOneEpochAgo::random(salt.as_ref()).0;
RequestsIds::<T>::remove(request_id);
total_weight +=
T::OnFilledRandomness::on_filled_randomness(request_id, randomness);
Self::deposit_event(Event::FilledRandomness {
request_id,
randomness,
});
total_weight += T::WeightInfo::on_initialize_epoch(2)
.saturating_sub(T::WeightInfo::on_initialize_epoch(1));
}
}
total_weight
}
}
impl<T: Config> Pallet<T> {
pub fn do_request(
requestor: &T::AccountId,
randomness_type: RandomnessType,
salt: H256,
) -> Result<RequestId, DispatchError> {
ensure!(
RequestsIds::<T>::count() < T::MaxRequests::get(),
Error::<T>::QueueFull
);
Self::pay_request(requestor)?;
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)
}
}
impl<T: Config> Pallet<T> {
fn pay_request(requestor: &T::AccountId) -> DispatchResult {
let imbalance = T::Currency::withdraw(
requestor,
T::RequestPrice::get(),
Precision::Exact,
Preservation::Preserve,
Fortitude::Polite,
)?;
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
}
}
}