referrerpolicy=no-referrer-when-downgrade

pallet_revive/
storage.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! This module contains routines for accessing and altering a contract related state.
19
20use 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	/// An account that is a contract.
54	AccountId(AccountIdOf<T>),
55	/// An externally owned account (EOA).
56	Address(H160),
57}
58
59/// Represents the account information for a contract or an externally owned account (EOA).
60#[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	/// The type of the account.
66	pub account_type: AccountType<T>,
67
68	// The  amount that was transferred to this account that is less than the
69	// NativeToEthRatio, and can be represented in the native currency
70	pub dust: u32,
71}
72
73/// The account type is used to distinguish between contracts and externally owned accounts.
74#[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	/// An account that is a contract.
80	Contract(ContractInfo<T>),
81
82	/// An externally owned account (no delegation).
83	#[default]
84	EOA,
85
86	/// An EOA that has been delegated via EIP-7702.
87	/// Once delegated, the account stays `DelegatedEOA` even after clearing.
88	DelegatedEOA {
89		/// When `Some`, the account delegates code execution to that address.
90		delegate_target: Option<H160>,
91		/// Storage accounting for this EOA's child trie.
92		contract_info: ContractInfo<T>,
93		/// Account that paid the current `contract_info.storage_base_deposit`, so that a
94		/// clear or re-delegation refunds them rather than whoever relays it.
95		payer: Option<T::AccountId>,
96	},
97}
98
99/// `storage_base_deposit` before and after [`AccountInfo::set_delegation`].
100///
101/// `current` covers the account entry itself, which outlives the delegation, plus the code
102/// lockup while one is held. The caller refunds `previous` to `previous_payer` and charges
103/// `current` to the new payer, which `set_delegation` has already recorded on the entry.
104#[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/// Information for managing an account and its sub trie abstraction.
112/// This is the required info to cache for an account.
113#[derive(Encode, Decode, CloneNoBound, PartialEq, Eq, DebugNoBound, TypeInfo, MaxEncodedLen)]
114#[scale_info(skip_type_params(T))]
115pub struct ContractInfo<T: Config> {
116	/// Unique ID for the subtree encoded as a bytes vector.
117	pub trie_id: TrieId,
118	/// The code associated with a given account.
119	pub code_hash: sp_core::H256,
120	/// How many bytes of storage are accumulated in this contract's child trie.
121	pub storage_bytes: u32,
122	/// How many items of storage are accumulated in this contract's child trie.
123	pub storage_items: u32,
124	/// This records to how much deposit the accumulated `storage_bytes` amount to.
125	pub storage_byte_deposit: BalanceOf<T>,
126	/// This records to how much deposit the accumulated `storage_items` amount to.
127	pub storage_item_deposit: BalanceOf<T>,
128	/// This records how much deposit is put down in order to pay for the contract itself.
129	///
130	/// We need to store this information separately so it is not used when calculating any refunds
131	/// since the base deposit can only ever be refunded on contract termination.
132	pub storage_base_deposit: BalanceOf<T>,
133	/// The size of the immutable data of this contract.
134	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	/// Returns the ContractInfo if this account type has loadable contract code.
169	///
170	/// For `DelegatedEOA`, only returns `Some` when delegation is active and the
171	/// code_hash is non-default (i.e., the target is a contract).
172	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	/// Returns true if the account is a contract.
187	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	/// Returns the balance of the account at the given address.
193	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	/// Returns the balance of this account info.
199	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	/// All the remaining in an account including ed and locked balances.
209	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	/// Loads the `ContractInfo` backing the address's storage namespace.
216	///
217	/// Returns `Some` for deployed contracts *and* for EIP-7702 delegated EOAs with an
218	/// active delegation; in the latter case the returned info is the authority's own.
219	/// Use [`Self::is_contract`] for a strict "deployed contract" check.
220	pub fn load_contract(address: &H160) -> Option<ContractInfo<T>> {
221		<AccountInfoOf<T>>::get(address)?.account_type.contract_info()
222	}
223
224	/// [`Self::load_contract`] plus the EIP-7702 delegation target, from a single read.
225	///
226	/// Callers that need both must use this rather than pairing `load_contract` with
227	/// [`Self::get_delegation_target`], which decodes the same entry twice.
228	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	/// Insert a contract, existing dust if any will be unchanged.
240	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	/// Updates the ContractInfo for storage operations at a given address.
256	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	/// EIP-7702: Check if an account has a delegation indicator set
269	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	/// EIP-7702: Get the delegation target for an address
275	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	/// EIP-7702: Build the 23-byte delegation indicator `0xef0100 || target`.
284	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	/// EIP-7702: Set or clear the delegation indicator for an EOA.
294	///
295	/// `Some(target)` marks the account as delegated to that address; `None` clears an
296	/// existing delegation — the account stays `DelegatedEOA` with `delegate_target = None`,
297	/// preserving the child trie and deposit accounting, while clearing an account that was
298	/// never delegated is a no-op (no entry is created).
299	/// The `DelegatedEOA` variant always carries a `ContractInfo`, but per EIP-7702 it only
300	/// snapshots the target's `code_hash`/deposit when the target is a contract. If the target is
301	/// itself delegated or a plain EOA, those fields are zeroed (no chain following). Existing
302	/// deposit accounting is preserved across re-delegations.
303	///
304	/// `payer` is recorded on the entry whenever the resulting deposit is non-zero. Returns the
305	/// deposit movement and the previously recorded payer (see [`DelegationDepositChange`]) so
306	/// the caller can refund the old deposit and charge the new one.
307	///
308	/// Not atomic on its own: an `Err` from the refcount update leaves the account mutation in
309	/// place. Run it inside a transactional storage layer (`process_authorizations` wraps each
310	/// tuple in one) or treat any `Err` as fatal to the surrounding operation.
311	///
312	/// # Spec deviation: code is resolved at delegation time, not at call time
313	///
314	/// The target's `code_hash` (and the resulting `ContractInfo`) is snapshotted from
315	/// `AccountInfoOf::<T>::get(&target)` here, not looked up live on every call. This is
316	/// stable when `target` is already a deployed contract: the only way to change a
317	/// contract's code is via root `set_code`, so the snapshot stays accurate.
318	///
319	/// It is **not** spec-compliant when `target` is **empty** at delegation time and a
320	/// contract is later deployed to that address (e.g., via `CREATE2` or Nick's method,
321	/// possibly even in the same transaction as the delegation). Spec-compliant clients
322	/// resolve code at call time, so a post-delegation deployment would "wake up" the
323	/// delegation. Here, the snapshot stays at zero and the authority continues to
324	/// behave like a no-code EOA. The niche but real case this breaks is a single EIP-7702
325	/// transaction that calls a factory which deploys to the future target *and* delegates
326	/// to it — on revive the delegation never activates.
327	pub(crate) fn set_delegation(
328		address: &H160,
329		target: Option<H160>,
330		payer: &T::AccountId,
331	) -> Result<DelegationDepositChange<T>, DispatchError> {
332		// `Some` iff target is a deployed contract with a real (non-zero) code
333		// hash. Precompiles and other special accounts surface as
334		// `AccountType::Contract` with `code_hash == 0` and have no `CodeInfo`;
335		// per EIP-7702 they should be delegated to successfully and behave as
336		// empty code on call, so we filter them out here. The deposit is looked
337		// up separately so a contract with a non-zero hash but missing
338		// `CodeInfo` (malformed state) still snapshots and surfaces via the
339		// refcount bump below.
340		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		// Ensure the account is `DelegatedEOA` (creating one if necessary), then
350		// update its fields in a single pass. `None` when clearing an account that
351		// was never delegated: per spec that authorization is still valid, but no
352		// entry must be created just to record an empty delegation.
353		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					// Preserve `dust`; only swap `account_type`.
375					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					// Deposit is only updated if we found the `CodeInfo`; if not,
397					// `new_deposit` stays at zero and the failing `increment_refcount`
398					// below propagates the malformed-state error.
399					target_code_deposit
400						.map(|d| contract_info.update_base_deposit(d))
401						.unwrap_or(Zero::zero())
402				},
403				None => {
404					// Clearing, or delegating to a non-contract: drop any stale
405					// snapshot so a later re-delegation doesn't double-account
406					// refcount/deposit. Still non-zero: only the code lockup drops
407					// out, the account entry stays charged.
408					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		// Manage code refcounts, skipping when the hash is unchanged.
425		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	/// Constructs a new contract info **without** writing it to storage.
446	///
447	/// This returns an `Err` if an contract with the supplied `account` already exists
448	/// in storage.
449	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		// Reject reuse of an address whose previous occupant still has unflushed
459		// `NativeDepositOf` rows in the deletion queue. The on_idle drain will eventually
460		// clear them; until it does, instantiating here would let the new contract inherit
461		// stale per-payer entitlements.
462		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	/// Constructs a new contract info for a delegated account (EIP-7702).
490	///
491	/// Delegated accounts have their own child trie for storage but use the code hash
492	/// of the target contract they delegate to. The trie_id is derived solely from the
493	/// address so that storage persists across re-delegations to different targets.
494	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	/// Associated child trie unique id is built from the hash part of the trie id.
516	pub fn child_trie_info(&self) -> ChildInfo {
517		ChildInfo::new_default(self.trie_id.as_ref())
518	}
519
520	/// The deposit paying for the accumulated storage generated within the contract's child trie.
521	pub fn extra_deposit(&self) -> BalanceOf<T> {
522		self.storage_byte_deposit.saturating_add(self.storage_item_deposit)
523	}
524
525	/// Same as [`Self::extra_deposit`] but including the base deposit.
526	pub fn total_deposit(&self) -> BalanceOf<T> {
527		self.extra_deposit().saturating_add(self.storage_base_deposit)
528	}
529
530	/// Returns the storage base deposit of the contract.
531	pub fn storage_base_deposit(&self) -> BalanceOf<T> {
532		self.storage_base_deposit
533	}
534
535	/// Reads a storage kv pair of a contract.
536	///
537	/// The read is performed from the `trie_id` only. The `address` is not necessary. If the
538	/// contract doesn't store under the given `key` `None` is returned.
539	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	/// Returns `Some(len)` (in bytes) if a storage item exists at `key`.
549	///
550	/// Returns `None` if the `key` wasn't previously set by `set_storage` or
551	/// was deleted.
552	pub fn size(&self, key: &Key) -> Option<u32> {
553		child::len(&self.child_trie_info(), key.hash().as_slice())
554	}
555
556	/// Update a storage entry into a contract's kv storage.
557	///
558	/// If the `new_value` is `None` then the kv pair is removed. If `take` is true
559	/// a [`WriteOutcome::Taken`] is returned instead of a [`WriteOutcome::Overwritten`].
560	///
561	/// This function also records how much storage was created or removed if a `storage_meter`
562	/// is supplied. It should only be absent for testing or benchmarking code.
563	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	/// Update a storage entry into a contract's kv storage.
581	/// Function used in benchmarks, which can simulate prefix collision in keys.
582	#[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	/// Sets and returns the contract base deposit.
644	///
645	/// The base deposit is updated when the `code_hash` of the contract changes, as it depends on
646	/// the deposit paid to upload the contract's code. It also depends on the size of immutable
647	/// storage which is also changed when the code hash of a contract is changed.
648	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		// Instantiating the contract prevents its code to be deleted, therefore the base deposit
660		// includes a fraction (`T::CodeHashLockupDepositPercent`) of the original storage deposit
661		// to prevent abuse.
662		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	/// Push a contract's trie and account to the deletion queue for lazy removal.
670	///
671	/// You must make sure that the contract is also removed when queuing for deletion.
672	/// Both the contract's child trie and any [`NativeDepositOf`] entries it held are drained
673	/// lazily in `on_idle`.
674	pub fn queue_for_deletion(trie_id: TrieId, contract: AccountIdOf<T>) {
675		DeletionQueueManager::<T>::load().insert(DeletionQueueItem::new(trie_id, contract));
676	}
677
678	/// Returns the total weight available for deletion-queue processing after subtracting
679	/// the fixed [`WeightInfo::deletion_queue_batch`] base.
680	pub fn deletion_budget(meter: &WeightMeter) -> Weight {
681		meter.limit().saturating_sub(T::WeightInfo::deletion_queue_batch())
682	}
683
684	/// Delete as many items from the deletion queue as possible within the supplied weight
685	/// limit.
686	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			// `w == 0` would be a benchmark misconfiguration; refuse to touch keys in that case
708			// rather than loop forever.
709			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			// Charge the per-entry overhead.
716			let Some(after_entry) = remaining.checked_sub(&weight_per_entry) else { break };
717			remaining = after_entry;
718
719			// Phase 1: drain `NativeDepositOf` rows for this contract.
720			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			// Phase 2: kill the child trie.
733			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	/// Returns the code hash of the contract specified by `account` ID.
761	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	/// Returns the amount of immutable bytes of this contract.
766	pub fn immutable_data_len(&self) -> u32 {
767		self.immutable_data_len
768	}
769
770	/// Set the number of immutable bytes of this contract.
771	pub fn set_immutable_data_len(&mut self, immutable_data_len: u32) {
772		self.immutable_data_len = immutable_data_len;
773	}
774}
775
776/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`].
777#[derive(Clone, Eq, PartialEq, Encode, Decode, Debug, TypeInfo)]
778pub enum WriteOutcome {
779	/// No value existed at the specified key.
780	New,
781	/// A value of the returned length was overwritten.
782	Overwritten(u32),
783	/// The returned value was taken out of storage before being overwritten.
784	///
785	/// This is only returned when specifically requested because it causes additional work
786	/// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`]
787	/// is returned instead.
788	Taken(Vec<u8>),
789}
790
791impl WriteOutcome {
792	/// Extracts the size of the overwritten value or `0` if there
793	/// was no value in storage.
794	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	/// Extracts the size of the overwritten value or `SENTINEL` if there
803	/// was no value in storage.
804	///
805	/// # Note
806	///
807	/// We cannot use `0` as sentinel value because there could be a zero sized
808	/// storage entry which is different from a non existing one.
809	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/// Manage the removal of contracts storage that are marked for deletion.
819///
820/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible
821/// immediately, but the deletion of the storage items it has accumulated is performed
822/// later by pulling the contract from the queue in the `on_idle` hook.
823#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)]
824#[scale_info(skip_type_params(T))]
825pub struct DeletionQueueManager<T: Config> {
826	/// Counter used as a key for inserting a new deleted contract in the queue.
827	/// The counter is incremented after each insertion.
828	insert_counter: u32,
829	/// The index used to read the next element to be deleted in the queue.
830	/// The counter is incremented after each deletion.
831	delete_counter: u32,
832
833	_phantom: PhantomData<T>,
834}
835
836/// A contract queued for lazy cleanup.
837///
838/// Holds the data needed to drain both the contract's [`NativeDepositOf`] rows and its child
839/// trie. Cleanup runs in two phases per batch (native rows first, then the trie); the entry
840/// stays in the queue until both phases have finished for it.
841#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, CloneNoBound, DebugNoBound, PartialEq, Eq)]
842#[scale_info(skip_type_params(T))]
843pub struct DeletionQueueItem<T: Config> {
844	/// The contract's child trie.
845	pub trie_id: TrieId,
846	/// The contract account whose [`NativeDepositOf`] entries must be cleared.
847	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
856/// View on a contract that is marked for deletion.
857struct DeletionQueueEntry<'a, T: Config> {
858	/// The queued deletion record.
859	value: DeletionQueueItem<T>,
860
861	/// A mutable reference on the queue so that the contract can be removed, and none can be added
862	/// or read in the meantime.
863	queue: &'a mut DeletionQueueManager<T>,
864}
865
866impl<'a, T: Config> DeletionQueueEntry<'a, T> {
867	/// Remove the contract from the deletion queue.
868	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	/// Load the `DeletionQueueCounter`, so we can perform read or write operations on the
877	/// DeletionQueue storage.
878	fn load() -> Self {
879		<DeletionQueueCounter<T>>::get()
880	}
881
882	/// Returns `true` if the queue contains no elements.
883	fn is_empty(&self) -> bool {
884		self.insert_counter.wrapping_sub(self.delete_counter) == 0
885	}
886
887	/// Insert a contract in the deletion queue.
888	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	/// Fetch the next contract to be deleted.
895	///
896	/// Note:
897	/// we use the delete counter to get the next value to read from the queue and thus don't pay
898	/// the cost of an extra call to `sp_io::storage::next_key` to lookup the next entry in the map
899	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}