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 sp_runtime::{
44 traits::{BadOrigin, Hash, Saturating},
45 Perbill,
46};
47
48use codec::{Decode, Encode, MaxEncodedLen};
49use frame_support::{
50 dispatch::Pays,
51 ensure,
52 pallet_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 frame_support::pallet_prelude::*;
63use frame_system::pallet_prelude::*;
64
65pub use pallet::*;
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 frame_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#[frame_support::pallet]
123#[allow(deprecated)]
124pub mod pallet {
125 use super::*;
126
127 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
129
130 #[pallet::config]
131 pub trait Config: frame_system::Config {
132 #[allow(deprecated)]
134 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
135
136 type WeightInfo: weights::WeightInfo;
138
139 type Currency: ReservableCurrency<Self::AccountId>;
142
143 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
146
147 type Consideration: Consideration<Self::AccountId, Footprint>;
149 }
150
151 #[pallet::pallet]
152 #[pallet::storage_version(STORAGE_VERSION)]
153 pub struct Pallet<T>(_);
154
155 #[pallet::event]
156 #[pallet::generate_deposit(pub fn deposit_event)]
157 pub enum Event<T: Config> {
158 Noted { hash: T::Hash },
160 Requested { hash: T::Hash },
162 Cleared { hash: T::Hash },
164 }
165
166 #[pallet::error]
167 pub enum Error<T> {
168 TooBig,
170 AlreadyNoted,
172 NotAuthorized,
174 NotNoted,
176 Requested,
178 NotRequested,
180 TooMany,
182 TooFew,
184 }
185
186 #[pallet::composite_enum]
188 pub enum HoldReason {
189 Preimage,
191 }
192
193 #[deprecated = "RequestStatusFor"]
195 #[pallet::storage]
196 pub type StatusFor<T: Config> =
197 StorageMap<_, Identity, T::Hash, OldRequestStatus<T::AccountId, BalanceOf<T>>>;
198
199 #[pallet::storage]
201 pub type RequestStatusFor<T: Config> =
202 StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, TicketOf<T>>>;
203
204 #[pallet::storage]
205 pub type PreimageFor<T: Config> =
206 StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
207
208 #[pallet::call(weight = T::WeightInfo)]
209 impl<T: Config> Pallet<T> {
210 #[pallet::call_index(0)]
215 #[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))]
216 pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo {
217 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
220 let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?;
221 if system_requested || maybe_sender.is_none() {
222 Ok(Pays::No.into())
223 } else {
224 Ok(().into())
225 }
226 }
227
228 #[pallet::call_index(1)]
235 pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
236 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
237 Self::do_unnote_preimage(&hash, maybe_sender)
238 }
239
240 #[pallet::call_index(2)]
245 pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
246 T::ManagerOrigin::ensure_origin(origin)?;
247 Self::do_request_preimage(&hash);
248 Ok(())
249 }
250
251 #[pallet::call_index(3)]
255 pub fn unrequest_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
256 T::ManagerOrigin::ensure_origin(origin)?;
257 Self::do_unrequest_preimage(&hash)
258 }
259
260 #[pallet::call_index(4)]
264 #[pallet::weight(T::WeightInfo::ensure_updated(hashes.len() as u32))]
265 pub fn ensure_updated(
266 origin: OriginFor<T>,
267 hashes: Vec<T::Hash>,
268 ) -> DispatchResultWithPostInfo {
269 ensure_signed(origin)?;
270 ensure!(hashes.len() > 0, Error::<T>::TooFew);
271 ensure!(hashes.len() <= MAX_HASH_UPGRADE_BULK_COUNT as usize, Error::<T>::TooMany);
272
273 let updated = hashes.iter().map(Self::do_ensure_updated).filter(|b| *b).count() as u32;
274 let ratio = Perbill::from_rational(updated, hashes.len() as u32);
275
276 let pays: Pays = (ratio < Perbill::from_percent(90)).into();
277 Ok(pays.into())
278 }
279 }
280}
281
282impl<T: Config> Pallet<T> {
283 fn do_ensure_updated(h: &T::Hash) -> bool {
284 #[allow(deprecated)]
285 let r = match StatusFor::<T>::take(h) {
286 Some(r) => r,
287 None => return false,
288 };
289 let n = match r {
290 OldRequestStatus::Unrequested { deposit: (who, amount), len } => {
291 T::Currency::unreserve(&who, amount);
293 let Ok(ticket) =
295 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
296 .defensive_proof("Unexpected inability to take deposit after unreserved")
297 else {
298 return true
299 };
300 RequestStatus::Unrequested { ticket: (who, ticket), len }
301 },
302 OldRequestStatus::Requested { deposit: maybe_deposit, count, len: maybe_len } => {
303 let maybe_ticket = if let Some((who, deposit)) = maybe_deposit {
304 T::Currency::unreserve(&who, deposit);
306 if let Some(len) = maybe_len {
308 let Ok(ticket) =
309 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
310 .defensive_proof(
311 "Unexpected inability to take deposit after unreserved",
312 )
313 else {
314 return true
315 };
316 Some((who, ticket))
317 } else {
318 None
319 }
320 } else {
321 None
322 };
323 RequestStatus::Requested { maybe_ticket, count, maybe_len }
324 },
325 };
326 RequestStatusFor::<T>::insert(h, n);
327 true
328 }
329
330 fn ensure_signed_or_manager(
332 origin: T::RuntimeOrigin,
333 ) -> Result<Option<T::AccountId>, BadOrigin> {
334 if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() {
335 return Ok(None)
336 }
337 let who = ensure_signed(origin)?;
338 Ok(Some(who))
339 }
340
341 fn note_bytes(
349 preimage: Cow<[u8]>,
350 maybe_depositor: Option<&T::AccountId>,
351 ) -> Result<(bool, T::Hash), DispatchError> {
352 let hash = T::Hashing::hash(&preimage);
353 let len = preimage.len() as u32;
354 ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
355
356 Self::do_ensure_updated(&hash);
357 let status = match (RequestStatusFor::<T>::get(hash), maybe_depositor) {
360 (Some(RequestStatus::Requested { maybe_ticket, count, .. }), _) =>
361 RequestStatus::Requested { maybe_ticket, count, maybe_len: Some(len) },
362 (Some(RequestStatus::Unrequested { .. }), Some(_)) =>
363 return Err(Error::<T>::AlreadyNoted.into()),
364 (Some(RequestStatus::Unrequested { ticket, len }), None) => RequestStatus::Requested {
365 maybe_ticket: Some(ticket),
366 count: 1,
367 maybe_len: Some(len),
368 },
369 (None, None) =>
370 RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) },
371 (None, Some(depositor)) => {
372 let ticket =
373 T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?;
374 RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len }
375 },
376 };
377 let was_requested = matches!(status, RequestStatus::Requested { .. });
378 RequestStatusFor::<T>::insert(hash, status);
379
380 let _ = Self::insert(&hash, preimage)
381 .defensive_proof("Unable to insert. Logic error in `note_bytes`?");
382
383 Self::deposit_event(Event::Noted { hash });
384
385 Ok((was_requested, hash))
386 }
387
388 fn do_request_preimage(hash: &T::Hash) {
393 Self::do_ensure_updated(&hash);
394 let (count, maybe_len, maybe_ticket) =
395 RequestStatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
396 RequestStatus::Requested { maybe_ticket, mut count, maybe_len } => {
397 count.saturating_inc();
398 (count, maybe_len, maybe_ticket)
399 },
400 RequestStatus::Unrequested { ticket, len } => (1, Some(len), Some(ticket)),
401 });
402 RequestStatusFor::<T>::insert(
403 hash,
404 RequestStatus::Requested { maybe_ticket, count, maybe_len },
405 );
406 if count == 1 {
407 Self::deposit_event(Event::Requested { hash: *hash });
408 }
409 }
410
411 fn do_unnote_preimage(
418 hash: &T::Hash,
419 maybe_check_owner: Option<T::AccountId>,
420 ) -> DispatchResult {
421 Self::do_ensure_updated(&hash);
422 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
423 RequestStatus::Requested { maybe_ticket: Some((owner, ticket)), count, maybe_len } => {
424 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
425 let _ = ticket.drop(&owner);
426 RequestStatusFor::<T>::insert(
427 hash,
428 RequestStatus::Requested { maybe_ticket: None, count, maybe_len },
429 );
430 Ok(())
431 },
432 RequestStatus::Requested { maybe_ticket: None, .. } => {
433 ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
434 Self::do_unrequest_preimage(hash)
435 },
436 RequestStatus::Unrequested { ticket: (owner, ticket), len } => {
437 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
438 let _ = ticket.drop(&owner);
439 RequestStatusFor::<T>::remove(hash);
440
441 Self::remove(hash, len);
442 Self::deposit_event(Event::Cleared { hash: *hash });
443 Ok(())
444 },
445 }
446 }
447
448 fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult {
450 Self::do_ensure_updated(&hash);
451 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
452 RequestStatus::Requested { mut count, maybe_len, maybe_ticket } if count > 1 => {
453 count.saturating_dec();
454 RequestStatusFor::<T>::insert(
455 hash,
456 RequestStatus::Requested { maybe_ticket, count, maybe_len },
457 );
458 },
459 RequestStatus::Requested { count, maybe_len, maybe_ticket } => {
460 debug_assert!(count == 1, "preimage request counter at zero?");
461 match (maybe_len, maybe_ticket) {
462 (None, _) => RequestStatusFor::<T>::remove(hash),
464 (Some(len), None) => {
466 Self::remove(hash, len);
467 RequestStatusFor::<T>::remove(hash);
468 Self::deposit_event(Event::Cleared { hash: *hash });
469 },
470 (Some(len), Some(ticket)) => {
472 RequestStatusFor::<T>::insert(
473 hash,
474 RequestStatus::Unrequested { ticket, len },
475 );
476 },
477 }
478 },
479 RequestStatus::Unrequested { .. } => return Err(Error::<T>::NotRequested.into()),
480 }
481 Ok(())
482 }
483
484 fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> {
485 BoundedSlice::<u8, ConstU32<MAX_SIZE>>::try_from(preimage.as_ref())
486 .map_err(|_| ())
487 .map(|s| PreimageFor::<T>::insert((hash, s.len() as u32), s))
488 }
489
490 fn remove(hash: &T::Hash, len: u32) {
491 PreimageFor::<T>::remove((hash, len))
492 }
493
494 fn have(hash: &T::Hash) -> bool {
495 Self::len(hash).is_some()
496 }
497
498 fn len(hash: &T::Hash) -> Option<u32> {
499 use RequestStatus::*;
500 Self::do_ensure_updated(&hash);
501 match RequestStatusFor::<T>::get(hash) {
502 Some(Requested { maybe_len: Some(len), .. }) | Some(Unrequested { len, .. }) =>
503 Some(len),
504 _ => None,
505 }
506 }
507
508 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
509 let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?;
510 PreimageFor::<T>::get((hash, len))
511 .map(|p| p.into_inner())
512 .map(Into::into)
513 .ok_or(DispatchError::Unavailable)
514 }
515}
516
517impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> {
518 fn have_preimage(hash: &T::Hash) -> bool {
519 Self::have(hash)
520 }
521
522 fn preimage_requested(hash: &T::Hash) -> bool {
523 Self::do_ensure_updated(hash);
524 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
525 }
526
527 fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
528 Self::fetch(hash, None).ok().map(Cow::into_owned)
529 }
530
531 fn request_preimage(hash: &T::Hash) {
532 Self::do_request_preimage(hash)
533 }
534
535 fn unrequest_preimage(hash: &T::Hash) {
536 let res = Self::do_unrequest_preimage(hash);
537 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
538 }
539}
540
541impl<T: Config> PreimageRecipient<T::Hash> for Pallet<T> {
542 type MaxSize = ConstU32<MAX_SIZE>; fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) {
545 let _ = Self::note_bytes(bytes.into_inner().into(), None);
548 }
549
550 fn unnote_preimage(hash: &T::Hash) {
551 let res = Self::do_unrequest_preimage(hash);
553 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
554 }
555}
556
557impl<T: Config> QueryPreimage for Pallet<T> {
558 type H = T::Hashing;
559
560 fn len(hash: &T::Hash) -> Option<u32> {
561 Pallet::<T>::len(hash)
562 }
563
564 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
565 Pallet::<T>::fetch(hash, len)
566 }
567
568 fn is_requested(hash: &T::Hash) -> bool {
569 Self::do_ensure_updated(&hash);
570 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
571 }
572
573 fn request(hash: &T::Hash) {
574 Self::do_request_preimage(hash)
575 }
576
577 fn unrequest(hash: &T::Hash) {
578 let res = Self::do_unrequest_preimage(hash);
579 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
580 }
581}
582
583impl<T: Config> StorePreimage for Pallet<T> {
584 const MAX_LENGTH: usize = MAX_SIZE as usize;
585
586 fn note(bytes: Cow<[u8]>) -> Result<T::Hash, DispatchError> {
587 let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h);
590 if maybe_hash == Err(DispatchError::from(Error::<T>::TooBig)) {
592 Err(DispatchError::Exhausted)
593 } else {
594 maybe_hash
595 }
596 }
597
598 fn unnote(hash: &T::Hash) {
599 let res = Self::do_unnote_preimage(hash, None);
601 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
602 }
603}