1use crate::{
21 AccountInfoOf, BalanceOf, BalanceWithDust, CodeInfoOf, Config, DeletionQueue,
22 DeletionQueueCounter, Error, LOG_TARGET, NativeDepositOf, SENTINEL, TrieId,
23 address::AddressMapper,
24 exec::{AccountIdOf, Key},
25 metering::FrameMeter,
26 tracing::if_tracing,
27 vm::CodeInfo,
28 weights::WeightInfo,
29};
30use alloc::vec::Vec;
31use codec::{Decode, Encode, MaxEncodedLen};
32use core::marker::PhantomData;
33use frame_support::{
34 CloneNoBound, DebugNoBound, DefaultNoBound,
35 storage::child::{self, ChildInfo},
36 traits::{
37 fungible::Inspect,
38 tokens::{Fortitude, Preservation},
39 },
40 weights::{Weight, WeightMeter},
41};
42use scale_info::TypeInfo;
43use sp_core::{Get, H160};
44use sp_io::KillStorageResult;
45use sp_runtime::{
46 Debug, DispatchError,
47 traits::{Hash, Saturating, Zero},
48};
49
50use crate::metering::Diff;
51
52pub enum AccountIdOrAddress<T: Config> {
53 AccountId(AccountIdOf<T>),
55 Address(H160),
57}
58
59#[derive(
61 DefaultNoBound, Encode, Decode, CloneNoBound, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
62)]
63#[scale_info(skip_type_params(T))]
64pub struct AccountInfo<T: Config> {
65 pub account_type: AccountType<T>,
67
68 pub dust: u32,
71}
72
73#[derive(
75 DefaultNoBound, Encode, Decode, CloneNoBound, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen,
76)]
77#[scale_info(skip_type_params(T))]
78pub enum AccountType<T: Config> {
79 Contract(ContractInfo<T>),
81
82 #[default]
84 EOA,
85
86 DelegatedEOA {
89 delegate_target: Option<H160>,
91 contract_info: ContractInfo<T>,
93 payer: Option<T::AccountId>,
96 },
97}
98
99#[derive(Debug, PartialEq, Eq)]
105pub struct DelegationDepositChange<T: Config> {
106 pub previous: BalanceOf<T>,
107 pub current: BalanceOf<T>,
108 pub previous_payer: Option<T::AccountId>,
109}
110
111#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, DebugNoBound, TypeInfo, MaxEncodedLen)]
114#[scale_info(skip_type_params(T))]
115pub struct ContractInfo<T: Config> {
116 pub trie_id: TrieId,
118 pub code_hash: sp_core::H256,
120 pub storage_bytes: u32,
122 pub storage_items: u32,
124 pub storage_byte_deposit: BalanceOf<T>,
126 pub storage_item_deposit: BalanceOf<T>,
128 pub storage_base_deposit: BalanceOf<T>,
133 pub immutable_data_len: u32,
135}
136
137impl<T: Config> From<H160> for AccountIdOrAddress<T> {
138 fn from(address: H160) -> Self {
139 AccountIdOrAddress::Address(address)
140 }
141}
142
143impl<T: Config> AccountIdOrAddress<T> {
144 pub fn address(&self) -> H160 {
145 match self {
146 AccountIdOrAddress::AccountId(id) => {
147 <T::AddressMapper as AddressMapper<T>>::to_address(id)
148 },
149 AccountIdOrAddress::Address(address) => *address,
150 }
151 }
152
153 pub fn account_id(&self) -> AccountIdOf<T> {
154 match self {
155 AccountIdOrAddress::AccountId(id) => id.clone(),
156 AccountIdOrAddress::Address(address) => T::AddressMapper::to_account_id(address),
157 }
158 }
159}
160
161impl<T: Config> From<ContractInfo<T>> for AccountType<T> {
162 fn from(contract_info: ContractInfo<T>) -> Self {
163 AccountType::Contract(contract_info)
164 }
165}
166
167impl<T: Config> AccountType<T> {
168 pub fn contract_info(self) -> Option<ContractInfo<T>> {
173 match self {
174 AccountType::Contract(info) => Some(info),
175 AccountType::DelegatedEOA { delegate_target: Some(_), contract_info, .. }
176 if !contract_info.code_hash.is_zero() =>
177 {
178 Some(contract_info)
179 },
180 _ => None,
181 }
182 }
183}
184
185impl<T: Config> AccountInfo<T> {
186 pub fn is_contract(address: &H160) -> bool {
188 let Some(info) = <AccountInfoOf<T>>::get(address) else { return false };
189 matches!(info.account_type, AccountType::Contract(_))
190 }
191
192 pub fn balance_of(account: AccountIdOrAddress<T>) -> BalanceWithDust<BalanceOf<T>> {
194 let info = <AccountInfoOf<T>>::get(account.address()).unwrap_or_default();
195 info.balance(&account.account_id(), Preservation::Preserve)
196 }
197
198 pub fn balance(
200 &self,
201 account: &AccountIdOf<T>,
202 preservation: Preservation,
203 ) -> BalanceWithDust<BalanceOf<T>> {
204 let value = T::Currency::reducible_balance(account, preservation, Fortitude::Polite);
205 BalanceWithDust::new_unchecked::<T>(value, self.dust)
206 }
207
208 pub fn total_balance(account: AccountIdOrAddress<T>) -> BalanceWithDust<BalanceOf<T>> {
210 let value = T::Currency::total_balance(&account.account_id());
211 let dust = <AccountInfoOf<T>>::get(account.address()).map(|a| a.dust).unwrap_or_default();
212 BalanceWithDust::new_unchecked::<T>(value, dust)
213 }
214
215 pub fn load_contract(address: &H160) -> Option<ContractInfo<T>> {
221 <AccountInfoOf<T>>::get(address)?.account_type.contract_info()
222 }
223
224 pub fn load_contract_with_delegation(
229 address: &H160,
230 ) -> (Option<ContractInfo<T>>, Option<H160>) {
231 let Some(info) = <AccountInfoOf<T>>::get(address) else { return (None, None) };
232 let target = match &info.account_type {
233 AccountType::DelegatedEOA { delegate_target, .. } => *delegate_target,
234 _ => None,
235 };
236 (info.account_type.contract_info(), target)
237 }
238
239 pub fn insert_contract(address: &H160, contract: ContractInfo<T>) {
241 AccountInfoOf::<T>::mutate(address, |account| {
242 if let Some(account) = account {
243 match &mut account.account_type {
244 AccountType::DelegatedEOA { contract_info, .. } => {
245 *contract_info = contract;
246 },
247 _ => account.account_type = contract.into(),
248 }
249 } else {
250 *account = Some(AccountInfo { account_type: contract.into(), dust: 0 });
251 }
252 });
253 }
254
255 pub fn update_contract_info(address: &H160, contract_info: ContractInfo<T>) {
257 AccountInfoOf::<T>::mutate(address, |account| {
258 if let Some(account) = account {
259 match &mut account.account_type {
260 AccountType::Contract(info) => *info = contract_info,
261 AccountType::DelegatedEOA { contract_info: info, .. } => *info = contract_info,
262 AccountType::EOA => {},
263 }
264 }
265 });
266 }
267
268 pub fn is_delegated(address: &H160) -> bool {
270 let Some(info) = <AccountInfoOf<T>>::get(address) else { return false };
271 matches!(info.account_type, AccountType::DelegatedEOA { delegate_target: Some(_), .. })
272 }
273
274 pub fn get_delegation_target(address: &H160) -> Option<H160> {
276 let info = <AccountInfoOf<T>>::get(address)?;
277 match info.account_type {
278 AccountType::DelegatedEOA { delegate_target: Some(target), .. } => Some(target),
279 _ => None,
280 }
281 }
282
283 pub fn delegation_indicator(target: &H160) -> [u8; 23] {
285 let mut buf = [0u8; 23];
286 buf[0] = 0xef;
287 buf[1] = 0x01;
288 buf[2] = 0x00;
289 buf[3..23].copy_from_slice(target.as_bytes());
290 buf
291 }
292
293 pub(crate) fn set_delegation(
328 address: &H160,
329 target: Option<H160>,
330 payer: &T::AccountId,
331 ) -> Result<DelegationDepositChange<T>, DispatchError> {
332 let target_code_hash: Option<sp_core::H256> = target
341 .and_then(|target| <AccountInfoOf<T>>::get(&target))
342 .and_then(|info| match info.account_type {
343 AccountType::Contract(c) if !c.code_hash.is_zero() => Some(c.code_hash),
344 _ => None,
345 });
346 let target_code_deposit: Option<BalanceOf<T>> =
347 target_code_hash.and_then(|h| CodeInfoOf::<T>::get(h).map(|ci| ci.deposit()));
348
349 let mutation = AccountInfoOf::<T>::mutate(address, |slot| {
354 let fresh_delegated = || AccountType::DelegatedEOA {
355 delegate_target: None,
356 contract_info: ContractInfo::<T>::new_for_delegation(address, Default::default()),
357 payer: None,
358 };
359 if target.is_none() &&
360 !matches!(
361 slot,
362 Some(AccountInfo { account_type: AccountType::DelegatedEOA { .. }, .. })
363 ) {
364 return None;
365 }
366 match slot.as_mut() {
367 None => *slot = Some(AccountInfo { account_type: fresh_delegated(), dust: 0 }),
368 Some(AccountInfo { account_type: AccountType::DelegatedEOA { .. }, .. }) => {},
369 Some(account) => {
370 debug_assert!(
371 !matches!(account.account_type, AccountType::Contract(_)),
372 "set_delegation must not be called on contract accounts"
373 );
374 account.account_type = fresh_delegated();
376 },
377 }
378
379 let Some(AccountInfo {
380 account_type:
381 AccountType::DelegatedEOA { delegate_target, contract_info, payer: stored_payer },
382 ..
383 }) = slot
384 else {
385 unreachable!("initialized to DelegatedEOA above; qed")
386 };
387
388 let old_code_hash = Some(contract_info.code_hash).filter(|h| !h.is_zero());
389 let old_deposit = contract_info.storage_base_deposit;
390 let previous_payer = stored_payer.clone();
391
392 *delegate_target = target;
393 let new_deposit = match target_code_hash {
394 Some(code_hash) => {
395 contract_info.code_hash = code_hash;
396 target_code_deposit
400 .map(|d| contract_info.update_base_deposit(d))
401 .unwrap_or(Zero::zero())
402 },
403 None => {
404 contract_info.code_hash = Default::default();
409 contract_info.update_base_deposit(Zero::zero())
410 },
411 };
412 *stored_payer = if new_deposit.is_zero() { None } else { Some(payer.clone()) };
413
414 Some((old_code_hash, old_deposit, new_deposit, previous_payer))
415 });
416 let Some((old_code_hash, old_deposit, new_deposit, previous_payer)) = mutation else {
417 return Ok(DelegationDepositChange {
418 previous: Zero::zero(),
419 current: Zero::zero(),
420 previous_payer: None,
421 });
422 };
423
424 if let Some(new_hash) = target_code_hash &&
426 Some(new_hash) != old_code_hash
427 {
428 CodeInfo::<T>::increment_refcount(new_hash).inspect_err(|e| {
429 log::warn!(target: LOG_TARGET, "increment_refcount({new_hash:?}) failed: {e:?}");
430 })?;
431 }
432 if let Some(old_hash) = old_code_hash &&
433 Some(old_hash) != target_code_hash
434 {
435 let _ = CodeInfo::<T>::decrement_refcount(old_hash).inspect_err(|e| {
436 log::warn!(target: LOG_TARGET, "decrement_refcount({old_hash:?}) failed: {e:?}");
437 })?;
438 }
439
440 Ok(DelegationDepositChange { previous: old_deposit, current: new_deposit, previous_payer })
441 }
442}
443
444impl<T: Config> ContractInfo<T> {
445 pub fn new(
450 address: &H160,
451 nonce: T::Nonce,
452 code_hash: sp_core::H256,
453 ) -> Result<Self, DispatchError> {
454 if <AccountInfo<T>>::is_contract(address) {
455 return Err(Error::<T>::DuplicateContract.into());
456 }
457
458 let account_id = T::AddressMapper::to_fallback_account_id(address);
463 if NativeDepositOf::<T>::iter_prefix(&account_id).next().is_some() {
464 return Err(Error::<T>::PendingDepositCleanup.into());
465 }
466
467 let trie_id = {
468 let buf = ("bcontract_trie_v1", address, nonce).using_encoded(T::Hashing::hash);
469 buf.as_ref()
470 .to_vec()
471 .try_into()
472 .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
473 };
474
475 let contract = Self {
476 trie_id,
477 code_hash,
478 storage_bytes: 0,
479 storage_items: 0,
480 storage_byte_deposit: Zero::zero(),
481 storage_item_deposit: Zero::zero(),
482 storage_base_deposit: Zero::zero(),
483 immutable_data_len: 0,
484 };
485
486 Ok(contract)
487 }
488
489 pub fn new_for_delegation(address: &H160, target_code_hash: sp_core::H256) -> Self {
495 let trie_id = {
496 let buf = ("delegated_trie_v1", address).using_encoded(T::Hashing::hash);
497 buf.as_ref()
498 .to_vec()
499 .try_into()
500 .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed")
501 };
502
503 Self {
504 trie_id,
505 code_hash: target_code_hash,
506 storage_bytes: 0,
507 storage_items: 0,
508 storage_byte_deposit: Zero::zero(),
509 storage_item_deposit: Zero::zero(),
510 storage_base_deposit: Zero::zero(),
511 immutable_data_len: 0,
512 }
513 }
514
515 pub fn child_trie_info(&self) -> ChildInfo {
517 ChildInfo::new_default(self.trie_id.as_ref())
518 }
519
520 pub fn extra_deposit(&self) -> BalanceOf<T> {
522 self.storage_byte_deposit.saturating_add(self.storage_item_deposit)
523 }
524
525 pub fn total_deposit(&self) -> BalanceOf<T> {
527 self.extra_deposit().saturating_add(self.storage_base_deposit)
528 }
529
530 pub fn storage_base_deposit(&self) -> BalanceOf<T> {
532 self.storage_base_deposit
533 }
534
535 pub fn read(&self, key: &Key) -> Option<Vec<u8>> {
540 let value = child::get_raw(&self.child_trie_info(), key.hash().as_slice());
541 log::trace!(target: crate::LOG_TARGET, "contract storage: read value {:?} for key {:x?}", value, key);
542 if_tracing(|t| {
543 t.storage_read(key, value.as_deref());
544 });
545 return value;
546 }
547
548 pub fn size(&self, key: &Key) -> Option<u32> {
553 child::len(&self.child_trie_info(), key.hash().as_slice())
554 }
555
556 pub fn write(
564 &self,
565 key: &Key,
566 new_value: Option<Vec<u8>>,
567 frame_meter: Option<&mut FrameMeter<T>>,
568 take: bool,
569 ) -> Result<WriteOutcome, DispatchError> {
570 log::trace!(target: crate::LOG_TARGET, "contract storage: writing value {:?} for key {:x?}", new_value, key);
571 let hashed_key = key.hash();
572 if_tracing(|t| {
573 let old = child::get_raw(&self.child_trie_info(), hashed_key.as_slice());
574 t.storage_write(key, old, new_value.as_deref());
575 });
576
577 self.write_raw(&hashed_key, new_value.as_deref(), frame_meter, take)
578 }
579
580 #[cfg(feature = "runtime-benchmarks")]
583 pub fn bench_write_raw(
584 &self,
585 key: &[u8],
586 new_value: Option<Vec<u8>>,
587 take: bool,
588 ) -> Result<WriteOutcome, DispatchError> {
589 self.write_raw(key, new_value.as_deref(), None, take)
590 }
591
592 fn write_raw(
593 &self,
594 key: &[u8],
595 new_value: Option<&[u8]>,
596 frame_meter: Option<&mut FrameMeter<T>>,
597 take: bool,
598 ) -> Result<WriteOutcome, DispatchError> {
599 let child_trie_info = &self.child_trie_info();
600 let (old_len, old_value) = if take {
601 let val = child::get_raw(child_trie_info, key);
602 (val.as_ref().map(|v| v.len() as u32), val)
603 } else {
604 (child::len(child_trie_info, key), None)
605 };
606
607 if let Some(frame_meter) = frame_meter {
608 let mut diff = Diff::default();
609 let key_len = key.len() as u32;
610 match (old_len, new_value.as_ref().map(|v| v.len() as u32)) {
611 (Some(old_len), Some(new_len)) => {
612 if new_len > old_len {
613 diff.bytes_added = new_len - old_len;
614 } else {
615 diff.bytes_removed = old_len - new_len;
616 }
617 },
618 (None, Some(new_len)) => {
619 diff.bytes_added = new_len.saturating_add(key_len);
620 diff.items_added = 1;
621 },
622 (Some(old_len), None) => {
623 diff.bytes_removed = old_len.saturating_add(key_len);
624 diff.items_removed = 1;
625 },
626 (None, None) => (),
627 }
628 frame_meter.record_contract_storage_changes(&diff)?;
629 }
630
631 match &new_value {
632 Some(new_value) => child::put_raw(child_trie_info, key, new_value),
633 None => child::kill(child_trie_info, key),
634 }
635
636 Ok(match (old_len, old_value) {
637 (None, _) => WriteOutcome::New,
638 (Some(old_len), None) => WriteOutcome::Overwritten(old_len),
639 (Some(_), Some(old_value)) => WriteOutcome::Taken(old_value),
640 })
641 }
642
643 pub fn update_base_deposit(&mut self, code_deposit: BalanceOf<T>) -> BalanceOf<T> {
649 let contract_deposit = {
650 let bytes_added: u32 =
651 (self.encoded_size() as u32).saturating_add(self.immutable_data_len);
652 let items_added: u32 = if self.immutable_data_len == 0 { 1 } else { 2 };
653
654 T::DepositPerByte::get()
655 .saturating_mul(bytes_added.into())
656 .saturating_add(T::DepositPerItem::get().saturating_mul(items_added.into()))
657 };
658
659 let code_deposit = T::CodeHashLockupDepositPercent::get().mul_ceil(code_deposit);
663
664 let deposit = contract_deposit.saturating_add(code_deposit);
665 self.storage_base_deposit = deposit;
666 deposit
667 }
668
669 pub fn queue_for_deletion(trie_id: TrieId, contract: AccountIdOf<T>) {
675 DeletionQueueManager::<T>::load().insert(DeletionQueueItem::new(trie_id, contract));
676 }
677
678 pub fn deletion_budget(meter: &WeightMeter) -> Weight {
681 meter.limit().saturating_sub(T::WeightInfo::deletion_queue_batch())
682 }
683
684 pub fn process_deletion_queue_batch(meter: &mut WeightMeter) {
687 if meter.try_consume(T::WeightInfo::deletion_queue_batch()).is_err() {
688 return;
689 };
690
691 let mut queue = <DeletionQueueManager<T>>::load();
692 if queue.is_empty() {
693 return;
694 }
695
696 let weight_per_entry = T::WeightInfo::deletion_queue_per_entry()
697 .saturating_sub(T::WeightInfo::deletion_queue_batch());
698 let weight_per_native_key = T::WeightInfo::deletion_queue_per_native_deposit_key(1)
699 .saturating_sub(T::WeightInfo::deletion_queue_per_native_deposit_key(0));
700 let weight_per_trie_key = T::WeightInfo::deletion_queue_per_trie_key(1)
701 .saturating_sub(T::WeightInfo::deletion_queue_per_trie_key(0));
702
703 let budget = Self::deletion_budget(&meter);
704 let mut remaining = budget;
705
706 let key_budget_for = |remaining: Weight, w: Weight| -> u32 {
707 remaining.checked_div_per_component(&w).unwrap_or(0).min(u32::MAX as u64) as u32
710 };
711
712 loop {
713 let Some(entry) = queue.next() else { break };
714
715 let Some(after_entry) = remaining.checked_sub(&weight_per_entry) else { break };
717 remaining = after_entry;
718
719 let key_budget = key_budget_for(remaining, weight_per_native_key);
721 if key_budget == 0 {
722 break;
723 }
724 let result =
725 NativeDepositOf::<T>::clear_prefix(&entry.value.account_id, key_budget, None);
726 remaining = remaining
727 .saturating_sub(weight_per_native_key.saturating_mul(u64::from(result.unique)));
728 if result.maybe_cursor.is_some() {
729 break;
730 }
731
732 let key_budget = key_budget_for(remaining, weight_per_trie_key);
734 if key_budget == 0 {
735 break;
736 }
737 #[allow(deprecated)]
738 let outcome = child::kill_storage(
739 &ChildInfo::new_default(&entry.value.trie_id),
740 Some(key_budget),
741 );
742 match outcome {
743 KillStorageResult::SomeRemaining(keys_removed) => {
744 remaining = remaining
745 .saturating_sub(weight_per_trie_key.saturating_mul(keys_removed.into()));
746 break;
747 },
748 KillStorageResult::AllRemoved(keys_removed) => {
749 remaining = remaining.saturating_sub(
750 weight_per_trie_key.saturating_mul(u64::from(keys_removed)),
751 );
752 entry.remove();
753 },
754 };
755 }
756
757 meter.consume(budget.saturating_sub(remaining));
758 }
759
760 pub fn load_code_hash(account: &AccountIdOf<T>) -> Option<sp_core::H256> {
762 <AccountInfo<T>>::load_contract(&T::AddressMapper::to_address(account)).map(|i| i.code_hash)
763 }
764
765 pub fn immutable_data_len(&self) -> u32 {
767 self.immutable_data_len
768 }
769
770 pub fn set_immutable_data_len(&mut self, immutable_data_len: u32) {
772 self.immutable_data_len = immutable_data_len;
773 }
774}
775
776#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
778pub enum WriteOutcome {
779 New,
781 Overwritten(u32),
783 Taken(Vec<u8>),
789}
790
791impl WriteOutcome {
792 pub fn old_len(&self) -> u32 {
795 match self {
796 Self::New => 0,
797 Self::Overwritten(len) => *len,
798 Self::Taken(value) => value.len() as u32,
799 }
800 }
801
802 pub fn old_len_with_sentinel(&self) -> u32 {
810 match self {
811 Self::New => SENTINEL,
812 Self::Overwritten(len) => *len,
813 Self::Taken(value) => value.len() as u32,
814 }
815 }
816}
817
818#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
824#[scale_info(skip_type_params(T))]
825pub struct DeletionQueueManager<T: Config> {
826 insert_counter: u32,
829 delete_counter: u32,
832
833 _phantom: PhantomData<T>,
834}
835
836#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, CloneNoBound, DebugNoBound, PartialEq, Eq)]
842#[scale_info(skip_type_params(T))]
843pub struct DeletionQueueItem<T: Config> {
844 pub trie_id: TrieId,
846 pub account_id: AccountIdOf<T>,
848}
849
850impl<T: Config> DeletionQueueItem<T> {
851 pub fn new(trie_id: TrieId, account_id: AccountIdOf<T>) -> Self {
852 Self { trie_id, account_id }
853 }
854}
855
856struct DeletionQueueEntry<'a, T: Config> {
858 value: DeletionQueueItem<T>,
860
861 queue: &'a mut DeletionQueueManager<T>,
864}
865
866impl<'a, T: Config> DeletionQueueEntry<'a, T> {
867 fn remove(self) {
869 <DeletionQueue<T>>::remove(self.queue.delete_counter);
870 self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1);
871 <DeletionQueueCounter<T>>::set(self.queue.clone());
872 }
873}
874
875impl<T: Config> DeletionQueueManager<T> {
876 fn load() -> Self {
879 <DeletionQueueCounter<T>>::get()
880 }
881
882 fn is_empty(&self) -> bool {
884 self.insert_counter.wrapping_sub(self.delete_counter) == 0
885 }
886
887 fn insert(&mut self, value: DeletionQueueItem<T>) {
889 <DeletionQueue<T>>::insert(self.insert_counter, value);
890 self.insert_counter = self.insert_counter.wrapping_add(1);
891 <DeletionQueueCounter<T>>::set(self.clone());
892 }
893
894 fn next(&mut self) -> Option<DeletionQueueEntry<'_, T>> {
900 if self.is_empty() {
901 return None;
902 }
903
904 let entry = <DeletionQueue<T>>::get(self.delete_counter);
905 entry.map(|value| DeletionQueueEntry { value, queue: self })
906 }
907}
908
909#[cfg(test)]
910impl<T: Config> DeletionQueueManager<T> {
911 pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self {
912 Self { insert_counter, delete_counter, _phantom: Default::default() }
913 }
914 pub fn as_test_tuple(&self) -> (u32, u32) {
915 (self.insert_counter, self.delete_counter)
916 }
917}