#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
pub mod migration;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub mod weights;
use sp_runtime::traits::{BadOrigin, Hash, Saturating};
use sp_std::{borrow::Cow, prelude::*};
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
dispatch::Pays,
ensure,
pallet_prelude::Get,
traits::{
Currency, Defensive, FetchResult, Hash as PreimageHash, PreimageProvider,
PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage,
},
BoundedSlice, BoundedVec,
};
use scale_info::TypeInfo;
pub use weights::WeightInfo;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
pub use pallet::*;
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
pub enum RequestStatus<AccountId, Balance> {
Unrequested { deposit: (AccountId, Balance), len: u32 },
Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option<u32> },
}
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
const MAX_SIZE: u32 = 4 * 1024 * 1024;
#[frame_support::pallet]
pub mod pallet {
use super::*;
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
type WeightInfo: weights::WeightInfo;
type Currency: ReservableCurrency<Self::AccountId>;
type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
type BaseDeposit: Get<BalanceOf<Self>>;
type ByteDeposit: Get<BalanceOf<Self>>;
}
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
Noted { hash: T::Hash },
Requested { hash: T::Hash },
Cleared { hash: T::Hash },
}
#[pallet::error]
pub enum Error<T> {
TooBig,
AlreadyNoted,
NotAuthorized,
NotNoted,
Requested,
NotRequested,
}
#[pallet::storage]
pub(super) type StatusFor<T: Config> =
StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, BalanceOf<T>>>;
#[pallet::storage]
pub(super) type PreimageFor<T: Config> =
StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))]
pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo {
let maybe_sender = Self::ensure_signed_or_manager(origin)?;
let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?;
if system_requested || maybe_sender.is_none() {
Ok(Pays::No.into())
} else {
Ok(().into())
}
}
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::unnote_preimage())]
pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
let maybe_sender = Self::ensure_signed_or_manager(origin)?;
Self::do_unnote_preimage(&hash, maybe_sender)
}
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::request_preimage())]
pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
T::ManagerOrigin::ensure_origin(origin)?;
Self::do_request_preimage(&hash);
Ok(())
}
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::unrequest_preimage())]
pub fn unrequest_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
T::ManagerOrigin::ensure_origin(origin)?;
Self::do_unrequest_preimage(&hash)
}
}
}
impl<T: Config> Pallet<T> {
fn ensure_signed_or_manager(
origin: T::RuntimeOrigin,
) -> Result<Option<T::AccountId>, BadOrigin> {
if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() {
return Ok(None)
}
let who = ensure_signed(origin)?;
Ok(Some(who))
}
fn note_bytes(
preimage: Cow<[u8]>,
maybe_depositor: Option<&T::AccountId>,
) -> Result<(bool, T::Hash), DispatchError> {
let hash = T::Hashing::hash(&preimage);
let len = preimage.len() as u32;
ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
let status = match (StatusFor::<T>::get(hash), maybe_depositor) {
(Some(RequestStatus::Requested { count, deposit, .. }), _) =>
RequestStatus::Requested { count, deposit, len: Some(len) },
(Some(RequestStatus::Unrequested { .. }), Some(_)) =>
return Err(Error::<T>::AlreadyNoted.into()),
(Some(RequestStatus::Unrequested { len, deposit }), None) =>
RequestStatus::Requested { deposit: Some(deposit), count: 1, len: Some(len) },
(None, None) => RequestStatus::Requested { count: 1, len: Some(len), deposit: None },
(None, Some(depositor)) => {
let length = preimage.len() as u32;
let deposit = T::BaseDeposit::get()
.saturating_add(T::ByteDeposit::get().saturating_mul(length.into()));
T::Currency::reserve(depositor, deposit)?;
RequestStatus::Unrequested { deposit: (depositor.clone(), deposit), len }
},
};
let was_requested = matches!(status, RequestStatus::Requested { .. });
StatusFor::<T>::insert(hash, status);
let _ = Self::insert(&hash, preimage)
.defensive_proof("Unable to insert. Logic error in `note_bytes`?");
Self::deposit_event(Event::Noted { hash });
Ok((was_requested, hash))
}
fn do_request_preimage(hash: &T::Hash) {
let (count, len, deposit) =
StatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
RequestStatus::Requested { mut count, len, deposit } => {
count.saturating_inc();
(count, len, deposit)
},
RequestStatus::Unrequested { deposit, len } => (1, Some(len), Some(deposit)),
});
StatusFor::<T>::insert(hash, RequestStatus::Requested { count, len, deposit });
if count == 1 {
Self::deposit_event(Event::Requested { hash: *hash });
}
}
fn do_unnote_preimage(
hash: &T::Hash,
maybe_check_owner: Option<T::AccountId>,
) -> DispatchResult {
match StatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
RequestStatus::Requested { deposit: Some((owner, deposit)), count, len } => {
ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
T::Currency::unreserve(&owner, deposit);
StatusFor::<T>::insert(
hash,
RequestStatus::Requested { deposit: None, count, len },
);
Ok(())
},
RequestStatus::Requested { deposit: None, .. } => {
ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
Self::do_unrequest_preimage(hash)
},
RequestStatus::Unrequested { deposit: (owner, deposit), len } => {
ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
T::Currency::unreserve(&owner, deposit);
StatusFor::<T>::remove(hash);
Self::remove(hash, len);
Self::deposit_event(Event::Cleared { hash: *hash });
Ok(())
},
}
}
fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult {
match StatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
RequestStatus::Requested { mut count, len, deposit } if count > 1 => {
count.saturating_dec();
StatusFor::<T>::insert(hash, RequestStatus::Requested { count, len, deposit });
},
RequestStatus::Requested { count, len, deposit } => {
debug_assert!(count == 1, "preimage request counter at zero?");
match (len, deposit) {
(None, _) => StatusFor::<T>::remove(hash),
(Some(len), None) => {
Self::remove(hash, len);
StatusFor::<T>::remove(hash);
Self::deposit_event(Event::Cleared { hash: *hash });
},
(Some(len), Some(deposit)) => {
StatusFor::<T>::insert(hash, RequestStatus::Unrequested { deposit, len });
},
}
},
RequestStatus::Unrequested { .. } => return Err(Error::<T>::NotRequested.into()),
}
Ok(())
}
fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> {
BoundedSlice::<u8, ConstU32<MAX_SIZE>>::try_from(preimage.as_ref())
.map_err(|_| ())
.map(|s| PreimageFor::<T>::insert((hash, s.len() as u32), s))
}
fn remove(hash: &T::Hash, len: u32) {
PreimageFor::<T>::remove((hash, len))
}
fn have(hash: &T::Hash) -> bool {
Self::len(hash).is_some()
}
fn len(hash: &T::Hash) -> Option<u32> {
use RequestStatus::*;
match StatusFor::<T>::get(hash) {
Some(Requested { len: Some(len), .. }) | Some(Unrequested { len, .. }) => Some(len),
_ => None,
}
}
fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?;
PreimageFor::<T>::get((hash, len))
.map(|p| p.into_inner())
.map(Into::into)
.ok_or(DispatchError::Unavailable)
}
}
impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> {
fn have_preimage(hash: &T::Hash) -> bool {
Self::have(hash)
}
fn preimage_requested(hash: &T::Hash) -> bool {
matches!(StatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
}
fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
Self::fetch(hash, None).ok().map(Cow::into_owned)
}
fn request_preimage(hash: &T::Hash) {
Self::do_request_preimage(hash)
}
fn unrequest_preimage(hash: &T::Hash) {
let res = Self::do_unrequest_preimage(hash);
debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
}
}
impl<T: Config> PreimageRecipient<T::Hash> for Pallet<T> {
type MaxSize = ConstU32<MAX_SIZE>; fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) {
let _ = Self::note_bytes(bytes.into_inner().into(), None);
}
fn unnote_preimage(hash: &T::Hash) {
let res = Self::do_unrequest_preimage(hash);
debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
}
}
impl<T: Config<Hash = PreimageHash>> QueryPreimage for Pallet<T> {
fn len(hash: &T::Hash) -> Option<u32> {
Pallet::<T>::len(hash)
}
fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
Pallet::<T>::fetch(hash, len)
}
fn is_requested(hash: &T::Hash) -> bool {
matches!(StatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
}
fn request(hash: &T::Hash) {
Self::do_request_preimage(hash)
}
fn unrequest(hash: &T::Hash) {
let res = Self::do_unrequest_preimage(hash);
debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
}
}
impl<T: Config<Hash = PreimageHash>> StorePreimage for Pallet<T> {
const MAX_LENGTH: usize = MAX_SIZE as usize;
fn note(bytes: Cow<[u8]>) -> Result<T::Hash, DispatchError> {
let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h);
if maybe_hash == Err(DispatchError::from(Error::<T>::TooBig)) {
Err(DispatchError::Exhausted)
} else {
maybe_hash
}
}
fn unnote(hash: &T::Hash) {
let res = Self::do_unnote_preimage(hash, None);
debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
}
}