mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 04:01:10 +00:00
FRAME: Revamp Preimage pallet to use Consideration (#1363)
Make Preimage pallet use Consideration instead of handling deposits directly. Other half of paritytech/substrate#13666. Depends/based on #1361. Script for the lazy migration that should be run manually once: [migrate-preimage-lazy.py](https://github.com/ggwpez/substrate-scripts/blob/master/migrate-preimage-lazy.py). ## TODO - [x] Migration code. --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: command-bot <> Co-authored-by: Francisco Aguirre <franciscoaguirreperez@gmail.com> Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -37,7 +37,10 @@ mod mock;
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
use sp_runtime::traits::{BadOrigin, Hash, Saturating};
|
||||
use sp_runtime::{
|
||||
traits::{BadOrigin, Hash, Saturating},
|
||||
Perbill,
|
||||
};
|
||||
use sp_std::{borrow::Cow, prelude::*};
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
@@ -46,8 +49,8 @@ use frame_support::{
|
||||
ensure,
|
||||
pallet_prelude::Get,
|
||||
traits::{
|
||||
Currency, Defensive, FetchResult, Hash as PreimageHash, PreimageProvider,
|
||||
PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage,
|
||||
Consideration, Currency, Defensive, FetchResult, Footprint, Hash as PreimageHash,
|
||||
PreimageProvider, PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage,
|
||||
},
|
||||
BoundedSlice, BoundedVec,
|
||||
};
|
||||
@@ -61,7 +64,7 @@ pub use pallet::*;
|
||||
|
||||
/// A type to note whether a preimage is owned by a user or the system.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub enum RequestStatus<AccountId, Balance> {
|
||||
pub enum OldRequestStatus<AccountId, Balance> {
|
||||
/// The associated preimage has not yet been requested by the system. The given deposit (if
|
||||
/// some) is being held until either it becomes requested or the user retracts the preimage.
|
||||
Unrequested { deposit: (AccountId, Balance), len: u32 },
|
||||
@@ -71,13 +74,31 @@ pub enum RequestStatus<AccountId, Balance> {
|
||||
Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option<u32> },
|
||||
}
|
||||
|
||||
/// A type to note whether a preimage is owned by a user or the system.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, RuntimeDebug)]
|
||||
pub enum RequestStatus<AccountId, Ticket> {
|
||||
/// The associated preimage has not yet been requested by the system. The given deposit (if
|
||||
/// some) is being held until either it becomes requested or the user retracts the preimage.
|
||||
Unrequested { ticket: (AccountId, Ticket), len: u32 },
|
||||
/// There are a non-zero number of outstanding requests for this hash by this chain. If there
|
||||
/// is a preimage registered, then `len` is `Some` and it may be removed iff this counter
|
||||
/// becomes zero.
|
||||
Requested { maybe_ticket: Option<(AccountId, Ticket)>, count: u32, maybe_len: Option<u32> },
|
||||
}
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
type TicketOf<T> = <T as Config>::Consideration;
|
||||
|
||||
/// Maximum size of preimage we can store is 4mb.
|
||||
const MAX_SIZE: u32 = 4 * 1024 * 1024;
|
||||
/// Hard-limit on the number of hashes that can be passed to `ensure_updated`.
|
||||
///
|
||||
/// Exists only for benchmarking purposes.
|
||||
pub const MAX_HASH_UPGRADE_BULK_COUNT: u32 = 1024;
|
||||
|
||||
#[frame_support::pallet]
|
||||
#[allow(deprecated)]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
@@ -93,17 +114,15 @@ pub mod pallet {
|
||||
type WeightInfo: weights::WeightInfo;
|
||||
|
||||
/// Currency type for this pallet.
|
||||
// TODO#1569: Remove.
|
||||
type Currency: ReservableCurrency<Self::AccountId>;
|
||||
|
||||
/// An origin that can request a preimage be placed on-chain without a deposit or fee, or
|
||||
/// manage existing preimages.
|
||||
type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The base deposit for placing a preimage on chain.
|
||||
type BaseDeposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The per-byte deposit for placing a preimage on chain.
|
||||
type ByteDeposit: Get<BalanceOf<Self>>;
|
||||
/// A means of providing some cost while data is stored on-chain.
|
||||
type Consideration: Consideration<Self::AccountId>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -135,18 +154,33 @@ pub mod pallet {
|
||||
Requested,
|
||||
/// The preimage request cannot be removed since no outstanding requests exist.
|
||||
NotRequested,
|
||||
/// More than `MAX_HASH_UPGRADE_BULK_COUNT` hashes were requested to be upgraded at once.
|
||||
TooMany,
|
||||
}
|
||||
|
||||
/// A reason for this pallet placing a hold on funds.
|
||||
#[pallet::composite_enum]
|
||||
pub enum HoldReason {
|
||||
/// The funds are held as storage deposit for a preimage.
|
||||
Preimage,
|
||||
}
|
||||
|
||||
/// The request status of a given hash.
|
||||
#[deprecated = "RequestStatusFor"]
|
||||
#[pallet::storage]
|
||||
pub(super) type StatusFor<T: Config> =
|
||||
StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, BalanceOf<T>>>;
|
||||
StorageMap<_, Identity, T::Hash, OldRequestStatus<T::AccountId, BalanceOf<T>>>;
|
||||
|
||||
/// The request status of a given hash.
|
||||
#[pallet::storage]
|
||||
pub(super) type RequestStatusFor<T: Config> =
|
||||
StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, TicketOf<T>>>;
|
||||
|
||||
#[pallet::storage]
|
||||
pub(super) type PreimageFor<T: Config> =
|
||||
StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
|
||||
|
||||
#[pallet::call]
|
||||
#[pallet::call(weight = T::WeightInfo)]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register a preimage on-chain.
|
||||
///
|
||||
@@ -173,7 +207,6 @@ pub mod pallet {
|
||||
/// - `hash`: The hash of the preimage to be removed from the store.
|
||||
/// - `len`: The length of the preimage of `hash`.
|
||||
#[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)
|
||||
@@ -184,7 +217,6 @@ pub mod pallet {
|
||||
/// If the preimage requests has already been provided on-chain, we unreserve any deposit
|
||||
/// a user may have paid, and take the control of the preimage out of their hands.
|
||||
#[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);
|
||||
@@ -195,15 +227,80 @@ pub mod pallet {
|
||||
///
|
||||
/// NOTE: THIS MUST NOT BE CALLED ON `hash` MORE TIMES THAN `request_preimage`.
|
||||
#[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)
|
||||
}
|
||||
|
||||
/// Ensure that the a bulk of pre-images is upgraded.
|
||||
///
|
||||
/// The caller pays no fee if at least 90% of pre-images were successfully updated.
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(T::WeightInfo::ensure_updated(hashes.len() as u32))]
|
||||
pub fn ensure_updated(
|
||||
origin: OriginFor<T>,
|
||||
hashes: Vec<T::Hash>,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_signed(origin)?;
|
||||
ensure!(hashes.len() <= MAX_HASH_UPGRADE_BULK_COUNT as usize, Error::<T>::TooMany);
|
||||
|
||||
let updated = hashes.iter().map(Self::do_ensure_updated).filter(|b| *b).count() as u32;
|
||||
let ratio = Perbill::from_rational(updated, hashes.len() as u32);
|
||||
|
||||
let pays: Pays = (ratio < Perbill::from_percent(90)).into();
|
||||
Ok(pays.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
fn do_ensure_updated(h: &T::Hash) -> bool {
|
||||
#[allow(deprecated)]
|
||||
let r = match StatusFor::<T>::take(h) {
|
||||
Some(r) => r,
|
||||
None => return false,
|
||||
};
|
||||
let n = match r {
|
||||
OldRequestStatus::Unrequested { deposit: (who, amount), len } => {
|
||||
// unreserve deposit
|
||||
T::Currency::unreserve(&who, amount);
|
||||
// take consideration
|
||||
let Ok(ticket) =
|
||||
T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
|
||||
.defensive_proof("Unexpected inability to take deposit after unreserved")
|
||||
else {
|
||||
return true
|
||||
};
|
||||
RequestStatus::Unrequested { ticket: (who, ticket), len }
|
||||
},
|
||||
OldRequestStatus::Requested { deposit: maybe_deposit, count, len: maybe_len } => {
|
||||
let maybe_ticket = if let Some((who, deposit)) = maybe_deposit {
|
||||
// unreserve deposit
|
||||
T::Currency::unreserve(&who, deposit);
|
||||
// take consideration
|
||||
if let Some(len) = maybe_len {
|
||||
let Ok(ticket) =
|
||||
T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
|
||||
.defensive_proof(
|
||||
"Unexpected inability to take deposit after unreserved",
|
||||
)
|
||||
else {
|
||||
return true
|
||||
};
|
||||
Some((who, ticket))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
RequestStatus::Requested { maybe_ticket, count, maybe_len }
|
||||
},
|
||||
};
|
||||
RequestStatusFor::<T>::insert(h, n);
|
||||
true
|
||||
}
|
||||
|
||||
/// Ensure that the origin is either the `ManagerOrigin` or a signed origin.
|
||||
fn ensure_signed_or_manager(
|
||||
origin: T::RuntimeOrigin,
|
||||
@@ -230,26 +327,29 @@ impl<T: Config> Pallet<T> {
|
||||
let len = preimage.len() as u32;
|
||||
ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
|
||||
|
||||
Self::do_ensure_updated(&hash);
|
||||
// We take a deposit only if there is a provided depositor and the preimage was not
|
||||
// previously requested. This also allows the tx to pay no fee.
|
||||
let status = match (StatusFor::<T>::get(hash), maybe_depositor) {
|
||||
(Some(RequestStatus::Requested { count, deposit, .. }), _) =>
|
||||
RequestStatus::Requested { count, deposit, len: Some(len) },
|
||||
let status = match (RequestStatusFor::<T>::get(hash), maybe_depositor) {
|
||||
(Some(RequestStatus::Requested { maybe_ticket, count, .. }), _) =>
|
||||
RequestStatus::Requested { maybe_ticket, count, maybe_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 },
|
||||
(Some(RequestStatus::Unrequested { ticket, len }), None) => RequestStatus::Requested {
|
||||
maybe_ticket: Some(ticket),
|
||||
count: 1,
|
||||
maybe_len: Some(len),
|
||||
},
|
||||
(None, None) =>
|
||||
RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) },
|
||||
(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 ticket =
|
||||
T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?;
|
||||
RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len }
|
||||
},
|
||||
};
|
||||
let was_requested = matches!(status, RequestStatus::Requested { .. });
|
||||
StatusFor::<T>::insert(hash, status);
|
||||
RequestStatusFor::<T>::insert(hash, status);
|
||||
|
||||
let _ = Self::insert(&hash, preimage)
|
||||
.defensive_proof("Unable to insert. Logic error in `note_bytes`?");
|
||||
@@ -264,15 +364,19 @@ impl<T: Config> Pallet<T> {
|
||||
// If the preimage already exists before the request is made, the deposit for the preimage is
|
||||
// returned to the user, and removed from their management.
|
||||
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 } => {
|
||||
Self::do_ensure_updated(&hash);
|
||||
let (count, maybe_len, maybe_ticket) =
|
||||
RequestStatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
|
||||
RequestStatus::Requested { maybe_ticket, mut count, maybe_len } => {
|
||||
count.saturating_inc();
|
||||
(count, len, deposit)
|
||||
(count, maybe_len, maybe_ticket)
|
||||
},
|
||||
RequestStatus::Unrequested { deposit, len } => (1, Some(len), Some(deposit)),
|
||||
RequestStatus::Unrequested { ticket, len } => (1, Some(len), Some(ticket)),
|
||||
});
|
||||
StatusFor::<T>::insert(hash, RequestStatus::Requested { count, len, deposit });
|
||||
RequestStatusFor::<T>::insert(
|
||||
hash,
|
||||
RequestStatus::Requested { maybe_ticket, count, maybe_len },
|
||||
);
|
||||
if count == 1 {
|
||||
Self::deposit_event(Event::Requested { hash: *hash });
|
||||
}
|
||||
@@ -288,24 +392,25 @@ impl<T: Config> Pallet<T> {
|
||||
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 } => {
|
||||
Self::do_ensure_updated(&hash);
|
||||
match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
|
||||
RequestStatus::Requested { maybe_ticket: Some((owner, ticket)), count, maybe_len } => {
|
||||
ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
|
||||
T::Currency::unreserve(&owner, deposit);
|
||||
StatusFor::<T>::insert(
|
||||
let _ = ticket.drop(&owner);
|
||||
RequestStatusFor::<T>::insert(
|
||||
hash,
|
||||
RequestStatus::Requested { deposit: None, count, len },
|
||||
RequestStatus::Requested { maybe_ticket: None, count, maybe_len },
|
||||
);
|
||||
Ok(())
|
||||
},
|
||||
RequestStatus::Requested { deposit: None, .. } => {
|
||||
RequestStatus::Requested { maybe_ticket: None, .. } => {
|
||||
ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
|
||||
Self::do_unrequest_preimage(hash)
|
||||
},
|
||||
RequestStatus::Unrequested { deposit: (owner, deposit), len } => {
|
||||
RequestStatus::Unrequested { ticket: (owner, ticket), len } => {
|
||||
ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
|
||||
T::Currency::unreserve(&owner, deposit);
|
||||
StatusFor::<T>::remove(hash);
|
||||
let _ = ticket.drop(&owner);
|
||||
RequestStatusFor::<T>::remove(hash);
|
||||
|
||||
Self::remove(hash, len);
|
||||
Self::deposit_event(Event::Cleared { hash: *hash });
|
||||
@@ -316,25 +421,32 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
/// Clear a preimage request.
|
||||
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 => {
|
||||
Self::do_ensure_updated(&hash);
|
||||
match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
|
||||
RequestStatus::Requested { mut count, maybe_len, maybe_ticket } if count > 1 => {
|
||||
count.saturating_dec();
|
||||
StatusFor::<T>::insert(hash, RequestStatus::Requested { count, len, deposit });
|
||||
RequestStatusFor::<T>::insert(
|
||||
hash,
|
||||
RequestStatus::Requested { maybe_ticket, count, maybe_len },
|
||||
);
|
||||
},
|
||||
RequestStatus::Requested { count, len, deposit } => {
|
||||
RequestStatus::Requested { count, maybe_len, maybe_ticket } => {
|
||||
debug_assert!(count == 1, "preimage request counter at zero?");
|
||||
match (len, deposit) {
|
||||
match (maybe_len, maybe_ticket) {
|
||||
// Preimage was never noted.
|
||||
(None, _) => StatusFor::<T>::remove(hash),
|
||||
(None, _) => RequestStatusFor::<T>::remove(hash),
|
||||
// Preimage was noted without owner - just remove it.
|
||||
(Some(len), None) => {
|
||||
Self::remove(hash, len);
|
||||
StatusFor::<T>::remove(hash);
|
||||
RequestStatusFor::<T>::remove(hash);
|
||||
Self::deposit_event(Event::Cleared { hash: *hash });
|
||||
},
|
||||
// Preimage was noted with owner - move to unrequested so they can get refund.
|
||||
(Some(len), Some(deposit)) => {
|
||||
StatusFor::<T>::insert(hash, RequestStatus::Unrequested { deposit, len });
|
||||
(Some(len), Some(ticket)) => {
|
||||
RequestStatusFor::<T>::insert(
|
||||
hash,
|
||||
RequestStatus::Unrequested { ticket, len },
|
||||
);
|
||||
},
|
||||
}
|
||||
},
|
||||
@@ -359,8 +471,10 @@ impl<T: Config> Pallet<T> {
|
||||
|
||||
fn len(hash: &T::Hash) -> Option<u32> {
|
||||
use RequestStatus::*;
|
||||
match StatusFor::<T>::get(hash) {
|
||||
Some(Requested { len: Some(len), .. }) | Some(Unrequested { len, .. }) => Some(len),
|
||||
Self::do_ensure_updated(&hash);
|
||||
match RequestStatusFor::<T>::get(hash) {
|
||||
Some(Requested { maybe_len: Some(len), .. }) | Some(Unrequested { len, .. }) =>
|
||||
Some(len),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -380,7 +494,8 @@ impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> {
|
||||
}
|
||||
|
||||
fn preimage_requested(hash: &T::Hash) -> bool {
|
||||
matches!(StatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
|
||||
Self::do_ensure_updated(hash);
|
||||
matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
|
||||
}
|
||||
|
||||
fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
|
||||
@@ -423,7 +538,8 @@ impl<T: Config<Hash = PreimageHash>> QueryPreimage for Pallet<T> {
|
||||
}
|
||||
|
||||
fn is_requested(hash: &T::Hash) -> bool {
|
||||
matches!(StatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
|
||||
Self::do_ensure_updated(&hash);
|
||||
matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
|
||||
}
|
||||
|
||||
fn request(hash: &T::Hash) {
|
||||
|
||||
Reference in New Issue
Block a user