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, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Debug, DecodeWithMemTracking,
70)]
71pub enum OldRequestStatus<AccountId, Balance> {
72 Unrequested { deposit: (AccountId, Balance), len: u32 },
75 Requested { deposit: Option<(AccountId, Balance)>, count: u32, len: Option<u32> },
79}
80
81#[derive(
83 Clone, Eq, PartialEq, Encode, Decode, TypeInfo, MaxEncodedLen, Debug, DecodeWithMemTracking,
84)]
85pub enum RequestStatus<AccountId, Ticket> {
86 Unrequested { ticket: (AccountId, Ticket), len: u32 },
89 Requested { maybe_ticket: Option<(AccountId, Ticket)>, count: u32, maybe_len: Option<u32> },
93}
94
95pub type BalanceOf<T> =
96 <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
97pub type TicketOf<T> = <T as Config>::Consideration;
98
99pub const MAX_SIZE: u32 = 4 * 1024 * 1024;
101pub const MAX_HASH_UPGRADE_BULK_COUNT: u32 = 1024;
105
106#[frame_support::pallet]
107#[allow(deprecated)]
108pub mod pallet {
109 use super::*;
110
111 const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
113
114 #[pallet::config]
115 pub trait Config: frame_system::Config {
116 #[allow(deprecated)]
118 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
119
120 type WeightInfo: weights::WeightInfo;
122
123 type Currency: ReservableCurrency<Self::AccountId>;
126
127 type ManagerOrigin: EnsureOrigin<Self::RuntimeOrigin>;
130
131 type Consideration: Consideration<Self::AccountId, Footprint>;
133 }
134
135 #[pallet::pallet]
136 #[pallet::storage_version(STORAGE_VERSION)]
137 pub struct Pallet<T>(_);
138
139 #[pallet::event]
140 #[pallet::generate_deposit(pub fn deposit_event)]
141 pub enum Event<T: Config> {
142 Noted { hash: T::Hash },
144 Requested { hash: T::Hash },
146 Cleared { hash: T::Hash },
148 }
149
150 #[pallet::error]
151 pub enum Error<T> {
152 TooBig,
154 AlreadyNoted,
156 NotAuthorized,
158 NotNoted,
160 Requested,
162 NotRequested,
164 TooMany,
166 TooFew,
168 }
169
170 #[pallet::composite_enum]
172 pub enum HoldReason {
173 Preimage,
175 }
176
177 #[deprecated = "RequestStatusFor"]
179 #[pallet::storage]
180 pub type StatusFor<T: Config> =
181 StorageMap<_, Identity, T::Hash, OldRequestStatus<T::AccountId, BalanceOf<T>>>;
182
183 #[pallet::storage]
185 pub type RequestStatusFor<T: Config> =
186 StorageMap<_, Identity, T::Hash, RequestStatus<T::AccountId, TicketOf<T>>>;
187
188 #[pallet::storage]
189 pub type PreimageFor<T: Config> =
190 StorageMap<_, Identity, (T::Hash, u32), BoundedVec<u8, ConstU32<MAX_SIZE>>>;
191
192 #[pallet::call(weight = T::WeightInfo)]
193 impl<T: Config> Pallet<T> {
194 #[pallet::call_index(0)]
199 #[pallet::weight(T::WeightInfo::note_preimage(bytes.len() as u32))]
200 pub fn note_preimage(origin: OriginFor<T>, bytes: Vec<u8>) -> DispatchResultWithPostInfo {
201 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
204 let (system_requested, _) = Self::note_bytes(bytes.into(), maybe_sender.as_ref())?;
205 if system_requested || maybe_sender.is_none() {
206 Ok(Pays::No.into())
207 } else {
208 Ok(().into())
209 }
210 }
211
212 #[pallet::call_index(1)]
219 pub fn unnote_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
220 let maybe_sender = Self::ensure_signed_or_manager(origin)?;
221 Self::do_unnote_preimage(&hash, maybe_sender)
222 }
223
224 #[pallet::call_index(2)]
229 pub fn request_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
230 T::ManagerOrigin::ensure_origin(origin)?;
231 Self::do_request_preimage(&hash);
232 Ok(())
233 }
234
235 #[pallet::call_index(3)]
239 pub fn unrequest_preimage(origin: OriginFor<T>, hash: T::Hash) -> DispatchResult {
240 T::ManagerOrigin::ensure_origin(origin)?;
241 Self::do_unrequest_preimage(&hash)
242 }
243
244 #[pallet::call_index(4)]
248 #[pallet::weight(T::WeightInfo::ensure_updated(hashes.len() as u32))]
249 pub fn ensure_updated(
250 origin: OriginFor<T>,
251 hashes: Vec<T::Hash>,
252 ) -> DispatchResultWithPostInfo {
253 ensure_signed(origin)?;
254 ensure!(hashes.len() > 0, Error::<T>::TooFew);
255 ensure!(hashes.len() <= MAX_HASH_UPGRADE_BULK_COUNT as usize, Error::<T>::TooMany);
256
257 let updated = hashes.iter().map(Self::do_ensure_updated).filter(|b| *b).count() as u32;
258 let ratio = Perbill::from_rational(updated, hashes.len() as u32);
259
260 let pays: Pays = (ratio < Perbill::from_percent(90)).into();
261 Ok(pays.into())
262 }
263 }
264}
265
266impl<T: Config> Pallet<T> {
267 fn do_ensure_updated(h: &T::Hash) -> bool {
268 #[allow(deprecated)]
269 let r = match StatusFor::<T>::take(h) {
270 Some(r) => r,
271 None => return false,
272 };
273 let n = match r {
274 OldRequestStatus::Unrequested { deposit: (who, amount), len } => {
275 T::Currency::unreserve(&who, amount);
277 let Ok(ticket) =
279 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
280 .defensive_proof("Unexpected inability to take deposit after unreserved")
281 else {
282 return true
283 };
284 RequestStatus::Unrequested { ticket: (who, ticket), len }
285 },
286 OldRequestStatus::Requested { deposit: maybe_deposit, count, len: maybe_len } => {
287 let maybe_ticket = if let Some((who, deposit)) = maybe_deposit {
288 T::Currency::unreserve(&who, deposit);
290 if let Some(len) = maybe_len {
292 let Ok(ticket) =
293 T::Consideration::new(&who, Footprint::from_parts(1, len as usize))
294 .defensive_proof(
295 "Unexpected inability to take deposit after unreserved",
296 )
297 else {
298 return true
299 };
300 Some((who, ticket))
301 } else {
302 None
303 }
304 } else {
305 None
306 };
307 RequestStatus::Requested { maybe_ticket, count, maybe_len }
308 },
309 };
310 RequestStatusFor::<T>::insert(h, n);
311 true
312 }
313
314 fn ensure_signed_or_manager(
316 origin: T::RuntimeOrigin,
317 ) -> Result<Option<T::AccountId>, BadOrigin> {
318 if T::ManagerOrigin::ensure_origin(origin.clone()).is_ok() {
319 return Ok(None)
320 }
321 let who = ensure_signed(origin)?;
322 Ok(Some(who))
323 }
324
325 fn note_bytes(
333 preimage: Cow<[u8]>,
334 maybe_depositor: Option<&T::AccountId>,
335 ) -> Result<(bool, T::Hash), DispatchError> {
336 let hash = T::Hashing::hash(&preimage);
337 let len = preimage.len() as u32;
338 ensure!(len <= MAX_SIZE, Error::<T>::TooBig);
339
340 Self::do_ensure_updated(&hash);
341 let status = match (RequestStatusFor::<T>::get(hash), maybe_depositor) {
344 (Some(RequestStatus::Requested { maybe_ticket, count, .. }), _) =>
345 RequestStatus::Requested { maybe_ticket, count, maybe_len: Some(len) },
346 (Some(RequestStatus::Unrequested { .. }), Some(_)) =>
347 return Err(Error::<T>::AlreadyNoted.into()),
348 (Some(RequestStatus::Unrequested { ticket, len }), None) => RequestStatus::Requested {
349 maybe_ticket: Some(ticket),
350 count: 1,
351 maybe_len: Some(len),
352 },
353 (None, None) =>
354 RequestStatus::Requested { maybe_ticket: None, count: 1, maybe_len: Some(len) },
355 (None, Some(depositor)) => {
356 let ticket =
357 T::Consideration::new(depositor, Footprint::from_parts(1, len as usize))?;
358 RequestStatus::Unrequested { ticket: (depositor.clone(), ticket), len }
359 },
360 };
361 let was_requested = matches!(status, RequestStatus::Requested { .. });
362 RequestStatusFor::<T>::insert(hash, status);
363
364 let _ = Self::insert(&hash, preimage)
365 .defensive_proof("Unable to insert. Logic error in `note_bytes`?");
366
367 Self::deposit_event(Event::Noted { hash });
368
369 Ok((was_requested, hash))
370 }
371
372 fn do_request_preimage(hash: &T::Hash) {
377 Self::do_ensure_updated(&hash);
378 let (count, maybe_len, maybe_ticket) =
379 RequestStatusFor::<T>::get(hash).map_or((1, None, None), |x| match x {
380 RequestStatus::Requested { maybe_ticket, mut count, maybe_len } => {
381 count.saturating_inc();
382 (count, maybe_len, maybe_ticket)
383 },
384 RequestStatus::Unrequested { ticket, len } => (1, Some(len), Some(ticket)),
385 });
386 RequestStatusFor::<T>::insert(
387 hash,
388 RequestStatus::Requested { maybe_ticket, count, maybe_len },
389 );
390 if count == 1 {
391 Self::deposit_event(Event::Requested { hash: *hash });
392 }
393 }
394
395 fn do_unnote_preimage(
402 hash: &T::Hash,
403 maybe_check_owner: Option<T::AccountId>,
404 ) -> DispatchResult {
405 Self::do_ensure_updated(&hash);
406 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotNoted)? {
407 RequestStatus::Requested { maybe_ticket: Some((owner, ticket)), count, maybe_len } => {
408 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
409 let _ = ticket.drop(&owner);
410 RequestStatusFor::<T>::insert(
411 hash,
412 RequestStatus::Requested { maybe_ticket: None, count, maybe_len },
413 );
414 Ok(())
415 },
416 RequestStatus::Requested { maybe_ticket: None, .. } => {
417 ensure!(maybe_check_owner.is_none(), Error::<T>::NotAuthorized);
418 Self::do_unrequest_preimage(hash)
419 },
420 RequestStatus::Unrequested { ticket: (owner, ticket), len } => {
421 ensure!(maybe_check_owner.map_or(true, |c| c == owner), Error::<T>::NotAuthorized);
422 let _ = ticket.drop(&owner);
423 RequestStatusFor::<T>::remove(hash);
424
425 Self::remove(hash, len);
426 Self::deposit_event(Event::Cleared { hash: *hash });
427 Ok(())
428 },
429 }
430 }
431
432 fn do_unrequest_preimage(hash: &T::Hash) -> DispatchResult {
434 Self::do_ensure_updated(&hash);
435 match RequestStatusFor::<T>::get(hash).ok_or(Error::<T>::NotRequested)? {
436 RequestStatus::Requested { mut count, maybe_len, maybe_ticket } if count > 1 => {
437 count.saturating_dec();
438 RequestStatusFor::<T>::insert(
439 hash,
440 RequestStatus::Requested { maybe_ticket, count, maybe_len },
441 );
442 },
443 RequestStatus::Requested { count, maybe_len, maybe_ticket } => {
444 debug_assert!(count == 1, "preimage request counter at zero?");
445 match (maybe_len, maybe_ticket) {
446 (None, _) => RequestStatusFor::<T>::remove(hash),
448 (Some(len), None) => {
450 Self::remove(hash, len);
451 RequestStatusFor::<T>::remove(hash);
452 Self::deposit_event(Event::Cleared { hash: *hash });
453 },
454 (Some(len), Some(ticket)) => {
456 RequestStatusFor::<T>::insert(
457 hash,
458 RequestStatus::Unrequested { ticket, len },
459 );
460 },
461 }
462 },
463 RequestStatus::Unrequested { .. } => return Err(Error::<T>::NotRequested.into()),
464 }
465 Ok(())
466 }
467
468 fn insert(hash: &T::Hash, preimage: Cow<[u8]>) -> Result<(), ()> {
469 BoundedSlice::<u8, ConstU32<MAX_SIZE>>::try_from(preimage.as_ref())
470 .map_err(|_| ())
471 .map(|s| PreimageFor::<T>::insert((hash, s.len() as u32), s))
472 }
473
474 fn remove(hash: &T::Hash, len: u32) {
475 PreimageFor::<T>::remove((hash, len))
476 }
477
478 fn have(hash: &T::Hash) -> bool {
479 Self::len(hash).is_some()
480 }
481
482 fn len(hash: &T::Hash) -> Option<u32> {
483 use RequestStatus::*;
484 Self::do_ensure_updated(&hash);
485 match RequestStatusFor::<T>::get(hash) {
486 Some(Requested { maybe_len: Some(len), .. }) | Some(Unrequested { len, .. }) =>
487 Some(len),
488 _ => None,
489 }
490 }
491
492 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
493 let len = len.or_else(|| Self::len(hash)).ok_or(DispatchError::Unavailable)?;
494 PreimageFor::<T>::get((hash, len))
495 .map(|p| p.into_inner())
496 .map(Into::into)
497 .ok_or(DispatchError::Unavailable)
498 }
499}
500
501impl<T: Config> PreimageProvider<T::Hash> for Pallet<T> {
502 fn have_preimage(hash: &T::Hash) -> bool {
503 Self::have(hash)
504 }
505
506 fn preimage_requested(hash: &T::Hash) -> bool {
507 Self::do_ensure_updated(hash);
508 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
509 }
510
511 fn get_preimage(hash: &T::Hash) -> Option<Vec<u8>> {
512 Self::fetch(hash, None).ok().map(Cow::into_owned)
513 }
514
515 fn request_preimage(hash: &T::Hash) {
516 Self::do_request_preimage(hash)
517 }
518
519 fn unrequest_preimage(hash: &T::Hash) {
520 let res = Self::do_unrequest_preimage(hash);
521 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
522 }
523}
524
525impl<T: Config> PreimageRecipient<T::Hash> for Pallet<T> {
526 type MaxSize = ConstU32<MAX_SIZE>; fn note_preimage(bytes: BoundedVec<u8, Self::MaxSize>) {
529 let _ = Self::note_bytes(bytes.into_inner().into(), None);
532 }
533
534 fn unnote_preimage(hash: &T::Hash) {
535 let res = Self::do_unrequest_preimage(hash);
537 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
538 }
539}
540
541impl<T: Config> QueryPreimage for Pallet<T> {
542 type H = T::Hashing;
543
544 fn len(hash: &T::Hash) -> Option<u32> {
545 Pallet::<T>::len(hash)
546 }
547
548 fn fetch(hash: &T::Hash, len: Option<u32>) -> FetchResult {
549 Pallet::<T>::fetch(hash, len)
550 }
551
552 fn is_requested(hash: &T::Hash) -> bool {
553 Self::do_ensure_updated(&hash);
554 matches!(RequestStatusFor::<T>::get(hash), Some(RequestStatus::Requested { .. }))
555 }
556
557 fn request(hash: &T::Hash) {
558 Self::do_request_preimage(hash)
559 }
560
561 fn unrequest(hash: &T::Hash) {
562 let res = Self::do_unrequest_preimage(hash);
563 debug_assert!(res.is_ok(), "do_unrequest_preimage failed - counter underflow?");
564 }
565}
566
567impl<T: Config> StorePreimage for Pallet<T> {
568 const MAX_LENGTH: usize = MAX_SIZE as usize;
569
570 fn note(bytes: Cow<[u8]>) -> Result<T::Hash, DispatchError> {
571 let maybe_hash = Self::note_bytes(bytes, None).map(|(_, h)| h);
574 if maybe_hash == Err(DispatchError::from(Error::<T>::TooBig)) {
576 Err(DispatchError::Exhausted)
577 } else {
578 maybe_hash
579 }
580 }
581
582 fn unnote(hash: &T::Hash) {
583 let res = Self::do_unnote_preimage(hash, None);
585 debug_assert!(res.is_ok(), "unnote_preimage failed - request outstanding?");
586 }
587}