1#![cfg_attr(not(feature = "std"), no_std)]
30
31#[cfg(feature = "runtime-benchmarks")]
32mod benchmarking;
33pub mod migration;
34#[cfg(test)]
35mod mock;
36#[cfg(test)]
37mod tests;
38pub mod weights;
39
40extern crate alloc;
41
42use alloc::{borrow::Cow, vec::Vec};
43use pezsp_runtime::{
44 traits::{BadOrigin, Hash, Saturating},
45 Perbill,
46};
47
48use codec::{Decode, Encode, MaxEncodedLen};
49use pezframe_support::{
50 dispatch::Pays,
51 ensure,
52 pezpallet_prelude::Get,
53 traits::{
54 Consideration, Currency, Defensive, FetchResult, Footprint, PreimageProvider,
55 PreimageRecipient, QueryPreimage, ReservableCurrency, StorePreimage,
56 },
57 BoundedSlice, BoundedVec,
58};
59use scale_info::TypeInfo;
60pub use weights::WeightInfo;
61
62use pezframe_support::pezpallet_prelude::*;
63use pezframe_system::pezpallet_prelude::*;
64
65pub use pezpallet::*;
66
67#[derive(
69 Clone,
70 Eq,
71 PartialEq,
72 Encode,
73 Decode,
74 TypeInfo,
75 MaxEncodedLen,
76 RuntimeDebug,
77 DecodeWithMemTracking,
78)]
79pub enum OldRequestStatus<AccountId, Balance> {
80 Unrequested { deposit: (AccountId, Balance), len: u32 },
83 Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option<u32> },
87}
88
89#[derive(
91 Clone,
92 Eq,
93 PartialEq,
94 Encode,
95 Decode,
96 TypeInfo,
97 MaxEncodedLen,
98 RuntimeDebug,
99 DecodeWithMemTracking,
100)]
101pub enum RequestStatus<AccountId, Ticket> {
102 Unrequested { ticket: (AccountId, Ticket), len: u32 },
105 Requested { maybe_ticket: Option<(AccountId, Ticket)>, count: u32, maybe_len: Option<u32> },
109}
110
111pub type BalanceOf<T> =
112 <<T as Config>::Currency as Currency<<T as pezframe_system::Config>::AccountId>>::Balance;
113pub type TicketOf<T> = <T as Config>::Consideration;
114
115pub const MAX_SIZE: u32 = 4 * 1024 * 1024;
117pub const MAX_HASH_UPGRADE_BULK_COUNT: u32 = 1024;
121
122#[pezframe_support::pezpallet]
123#[allow(deprecated)]
124pub mod pezpallet {
125 use super::*;
126
127 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
129
130 #[pezpallet::config]
131 pub trait Config: pezframe_system::Config {
132 #[allow(deprecated)]
134 type RuntimeEvent: From<Event<Self>>
135 + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
136
137 type WeightInfo: weights::WeightInfo;
139
140 type Currency: ReservableCurrency<Self::AccountId>;
143
144 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
147
148 type Consideration: Consideration<Self::AccountId, Footprint>;
150 }
151
152 #[pezpallet::pezpallet]
153 #[pezpallet::storage_version(STORAGE_VERSION)]
154 pub struct Pezpallet<T>(_);
155
156 #[pezpallet::event]
157 #[pezpallet::generate_deposit(pub fn deposit_event)]
158 pub enum Event<T: Config> {
159 Noted { hash: T::Hash },
161 Requested { hash: T::Hash },
163 Cleared { hash: T::Hash },
165 }
166
167 #[pezpallet::error]
168 pub enum Error<T> {
169 TooBig,
171 AlreadyNoted,
173 NotAuthorized,
175 NotNoted,
177 Requested,
179 NotRequested,
181 TooMany,
183 TooFew,
185 }
186
187 #[pezpallet::composite_enum]
189 pub enum HoldReason {
190 Preimage,
192 }
193
194 #[deprecated = "RequestStatusFor"]
196 #[pezpallet::storage]
197 pub type StatusFor<T: Config> =
198 StorageMap<_, Identity, T::Hash, OldRequestStatus<T::AccountId, BalanceOf<T>>>;
199
200 #[pezpallet::storage]
202 pub type RequestStatusFor<T: Config> =
203 StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, TicketOf<T>>>;
204
205 #[pezpallet::storage]
206 pub type PreimageFor<T: Config> =
207 StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
208
209 #[pezpallet::call(weight = T::WeightInfo)]
210 impl<T: Config> Pezpallet<T> {
211 #[pezpallet::call_index(0)]
216 #[pezpallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))]
217 pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo {
218 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
221 let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?;
222 if system_requested || maybe_sender.is_none() {
223 Ok(Pays::No.into())
224 } else {
225 Ok(().into())
226 }
227 }
228
229 #[pezpallet::call_index(1)]
236 pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
237 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
238 Self::do_unnote_preimage(&hash, maybe_sender)
239 }
240
241 #[pezpallet::call_index(2)]
246 pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
247 T::ManagerOrigin::ensure_origin(origin)?;
248 Self::do_request_preimage(&hash);
249 Ok(())
250 }
251
252 #[pezpallet::call_index(3)]
256 pub fn unrequest_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
257 T::ManagerOrigin::ensure_origin(origin)?;
258 Self::do_unrequest_preimage(&hash)
259 }
260
261 #[pezpallet::call_index(4)]
265 #[pezpallet::weight(T::WeightInfo::ensure_updated(hashes.len() as u32))]
266 pub fn ensure_updated(
267 origin: OriginFor<T>,
268 hashes: Vec<T::Hash>,
269 ) -> DispatchResultWithPostInfo {
270 ensure_signed(origin)?;
271 ensure!(hashes.len() > 0, Error::<T>::TooFew);
272 ensure!(hashes.len() <= MAX_HASH_UPGRADE_BULK_COUNT as usize, Error::<T>::TooMany);
273
274 let updated = hashes.iter().map(Self::do_ensure_updated).filter(|b| *b).count() as u32;
275 let ratio = Perbill::from_rational(updated, hashes.len() as u32);
276
277 let pays: Pays = (ratio < Perbill::from_percent(90)).into();
278 Ok(pays.into())
279 }
280 }
281}
282
283impl<T: Config> Pezpallet<T> {
284 fn do_ensure_updated(h: &T::Hash) -> bool {
285 #[allow(deprecated)]
286 let r = match StatusFor::<T>::take(h) {
287 Some(r) => r,
288 None => return false,
289 };
290 let n = match r {
291 OldRequestStatus::Unrequested { deposit: (who, amount), len } => {
292 T::Currency::unreserve(&who, amount);
294 let Ok(ticket) =
296 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
297 .defensive_proof("Unexpected inability to take deposit after unreserved")
298 else {
299 return true;
300 };
301 RequestStatus::Unrequested { ticket: (who, ticket), len }
302 },
303 OldRequestStatus::Requested { deposit: maybe_deposit, count, len: maybe_len } => {
304 let maybe_ticket = if let Some((who, deposit)) = maybe_deposit {
305 T::Currency::unreserve(&who, deposit);
307 if let Some(len) = maybe_len {
309 let Ok(ticket) =
310 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
311 .defensive_proof(
312 "Unexpected inability to take deposit after unreserved",
313 )
314 else {
315 return true;
316 };
317 Some((who, ticket))
318 } else {
319 None
320 }
321 } else {
322 None
323 };
324 RequestStatus::Requested { maybe_ticket, count, maybe_len }
325 },
326 };
327 RequestStatusFor::<T>::insert(h, n);
328 true
329 }
330
331 fn ensure_signed_or_manager(
333 origin: T::RuntimeOrigin,
334 ) -> Result<Option<T::AccountId>, BadOrigin> {
335 if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() {
336 return Ok(None);
337 }
338 let who = ensure_signed(origin)?;
339 Ok(Some(who))
340 }
341
342 fn note_bytes(
350 preimage: Cow<[u8]>,
351 maybe_depositor: Option<&T::AccountId>,
352 ) -> Result<(bool, T::Hash), DispatchError> {
353 let hash = T::Hashing::hash(&preimage);
354 let len = preimage.len() as u32;
355 ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
356
357 Self::do_ensure_updated(&hash);
358 let status = match (RequestStatusFor::<T>::get(hash), maybe_depositor) {
361 (Some(RequestStatus::Requested { maybe_ticket, count, .. }), _) => {
362 RequestStatus::Requested { maybe_ticket, count, maybe_len: Some(len) }
363 },
364 (Some(RequestStatus::Unrequested { .. }), Some(_)) => {
365 return Err(Error::<T>::AlreadyNoted.into())
366 },
367 (Some(RequestStatus::Unrequested { ticket, len }), None) => RequestStatus::Requested {
368 maybe_ticket: Some(ticket),
369 count: 1,
370 maybe_len: Some(len),
371 },
372 (None, None) => {
373 RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) }
374 },
375 (None, Some(depositor)) => {
376 let ticket =
377 T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?;
378 RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len }
379 },
380 };
381 let was_requested = matches!(status, RequestStatus::Requested { .. });
382 RequestStatusFor::<T>::insert(hash, status);
383
384 let _ = Self::insert(&hash, preimage)
385 .defensive_proof("Unable to insert. Logic error in `note_bytes`?");
386
387 Self::deposit_event(Event::Noted { hash });
388
389 Ok((was_requested, hash))
390 }
391
392 fn do_request_preimage(hash: &T::Hash) {
397 Self::do_ensure_updated(&hash);
398 let (count, maybe_len, maybe_ticket) =
399 RequestStatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
400 RequestStatus::Requested { maybe_ticket, mut count, maybe_len } => {
401 count.saturating_inc();
402 (count, maybe_len, maybe_ticket)
403 },
404 RequestStatus::Unrequested { ticket, len } => (1, Some(len), Some(ticket)),
405 });
406 RequestStatusFor::<T>::insert(
407 hash,
408 RequestStatus::Requested { maybe_ticket, count, maybe_len },
409 );
410 if count == 1 {
411 Self::deposit_event(Event::Requested { hash: *hash });
412 }
413 }
414
415 fn do_unnote_preimage(
422 hash: &T::Hash,
423 maybe_check_owner: Option<T::AccountId>,
424 ) -> DispatchResult {
425 Self::do_ensure_updated(&hash);
426 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
427 RequestStatus::Requested { maybe_ticket: Some((owner, ticket)), count, maybe_len } => {
428 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
429 let _ = ticket.drop(&owner);
430 RequestStatusFor::<T>::insert(
431 hash,
432 RequestStatus::Requested { maybe_ticket: None, count, maybe_len },
433 );
434 Ok(())
435 },
436 RequestStatus::Requested { maybe_ticket: None, .. } => {
437 ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
438 Self::do_unrequest_preimage(hash)
439 },
440 RequestStatus::Unrequested { ticket: (owner, ticket), len } => {
441 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
442 let _ = ticket.drop(&owner);
443 RequestStatusFor::<T>::remove(hash);
444
445 Self::remove(hash, len);
446 Self::deposit_event(Event::Cleared { hash: *hash });
447 Ok(())
448 },
449 }
450 }
451
452 fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult {
454 Self::do_ensure_updated(&hash);
455 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
456 RequestStatus::Requested { mut count, maybe_len, maybe_ticket } if count > 1 => {
457 count.saturating_dec();
458 RequestStatusFor::<T>::insert(
459 hash,
460 RequestStatus::Requested { maybe_ticket, count, maybe_len },
461 );
462 },
463 RequestStatus::Requested { count, maybe_len, maybe_ticket } => {
464 debug_assert!(count == 1, "preimage request counter at zero?");
465 match (maybe_len, maybe_ticket) {
466 (None, _) => RequestStatusFor::<T>::remove(hash),
468 (Some(len), None) => {
470 Self::remove(hash, len);
471 RequestStatusFor::<T>::remove(hash);
472 Self::deposit_event(Event::Cleared { hash: *hash });
473 },
474 (Some(len), Some(ticket)) => {
476 RequestStatusFor::<T>::insert(
477 hash,
478 RequestStatus::Unrequested { ticket, len },
479 );
480 },
481 }
482 },
483 RequestStatus::Unrequested { .. } => return Err(Error::<T>::NotRequested.into()),
484 }
485 Ok(())
486 }
487
488 fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> {
489 BoundedSlice::<u8, ConstU32<MAX_SIZE>>::try_from(preimage.as_ref())
490 .map_err(|_| ())
491 .map(|s| PreimageFor::<T>::insert((hash, s.len() as u32), s))
492 }
493
494 fn remove(hash: &T::Hash, len: u32) {
495 PreimageFor::<T>::remove((hash, len))
496 }
497
498 fn have(hash: &T::Hash) -> bool {
499 Self::len(hash).is_some()
500 }
501
502 fn len(hash: &T::Hash) -> Option<u32> {
503 use RequestStatus::*;
504 Self::do_ensure_updated(&hash);
505 match RequestStatusFor::<T>::get(hash) {
506 Some(Requested { maybe_len: Some(len), .. }) | Some(Unrequested { len, .. }) => {
507 Some(len)
508 },
509 _ => None,
510 }
511 }
512
513 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
514 let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?;
515 PreimageFor::<T>::get((hash, len))
516 .map(|p| p.into_inner())
517 .map(Into::into)
518 .ok_or(DispatchError::Unavailable)
519 }
520}
521
522impl<T: Config> PreimageProvider<T::Hash> for Pezpallet<T> {
523 fn have_preimage(hash: &T::Hash) -> bool {
524 Self::have(hash)
525 }
526
527 fn preimage_requested(hash: &T::Hash) -> bool {
528 Self::do_ensure_updated(hash);
529 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
530 }
531
532 fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
533 Self::fetch(hash, None).ok().map(Cow::into_owned)
534 }
535
536 fn request_preimage(hash: &T::Hash) {
537 Self::do_request_preimage(hash)
538 }
539
540 fn unrequest_preimage(hash: &T::Hash) {
541 let res = Self::do_unrequest_preimage(hash);
542 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
543 }
544}
545
546impl<T: Config> PreimageRecipient<T::Hash> for Pezpallet<T> {
547 type MaxSize = ConstU32<MAX_SIZE>; fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) {
550 let _ = Self::note_bytes(bytes.into_inner().into(), None);
553 }
554
555 fn unnote_preimage(hash: &T::Hash) {
556 let res = Self::do_unrequest_preimage(hash);
558 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
559 }
560}
561
562impl<T: Config> QueryPreimage for Pezpallet<T> {
563 type H = T::Hashing;
564
565 fn len(hash: &T::Hash) -> Option<u32> {
566 Pezpallet::<T>::len(hash)
567 }
568
569 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
570 Pezpallet::<T>::fetch(hash, len)
571 }
572
573 fn is_requested(hash: &T::Hash) -> bool {
574 Self::do_ensure_updated(&hash);
575 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
576 }
577
578 fn request(hash: &T::Hash) {
579 Self::do_request_preimage(hash)
580 }
581
582 fn unrequest(hash: &T::Hash) {
583 let res = Self::do_unrequest_preimage(hash);
584 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
585 }
586}
587
588impl<T: Config> StorePreimage for Pezpallet<T> {
589 const MAX_LENGTH: usize = MAX_SIZE as usize;
590
591 fn note(bytes: Cow<[u8]>) -> Result<T::Hash, DispatchError> {
592 let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h);
595 if maybe_hash == Err(DispatchError::from(Error::<T>::TooBig)) {
597 Err(DispatchError::Exhausted)
598 } else {
599 maybe_hash
600 }
601 }
602
603 fn unnote(hash: &T::Hash) {
604 let res = Self::do_unnote_preimage(hash, None);
606 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
607 }
608}