git.delta.rocks / unique-network / refs/commits / e8fdadb89923

difftreelog

source

pallets/contracts/src/rent.rs19.3 KiBsourcehistory
1// This file is part of Substrate.23// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd.4// SPDX-License-Identifier: Apache-2.056// 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 at9//10// 	http://www.apache.org/licenses/LICENSE-2.011//12// Unless required by applicable law or agreed to in writing, software13// 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 and16// limitations under the License.1718//! A module responsible for computing the right amount of weight and charging it.1920use crate::{21	AliveContractInfo, BalanceOf, ContractInfo, ContractInfoOf, Pallet, Event,22	TombstoneContractInfo, Config, CodeHash, Error, storage::Storage, wasm::PrefabWasmModule,23	exec::Executable,24};25use sp_std::prelude::*;26use sp_io::hashing::blake2_256;27use sp_core::crypto::UncheckedFrom;28use frame_support::{29	storage::child,30	traits::{Currency, ExistenceRequirement, Get, OnUnbalanced, WithdrawReasons},31};32use pallet_contracts_primitives::{ContractAccessError, RentProjection, RentProjectionResult};33use sp_runtime::{34	DispatchError,35	traits::{Bounded, CheckedDiv, CheckedMul, SaturatedConversion, Saturating, Zero},36};3738/// The amount to charge.39///40/// This amount respects the contract's rent allowance and the subsistence deposit.41/// Because of that, charging the amount cannot remove the contract.42struct OutstandingAmount<T: Config> {43	amount: BalanceOf<T>,44}4546impl<T: Config> OutstandingAmount<T> {47	/// Create the new outstanding amount.48	///49	/// The amount should be always withdrawable and it should not kill the account.50	fn new(amount: BalanceOf<T>) -> Self {51		Self { amount }52	}5354	/// Returns the amount this instance wraps.55	fn peek(&self) -> BalanceOf<T> {56		self.amount57	}5859	/// Withdraws the outstanding amount from the given account.60	fn withdraw(self, account: &T::AccountId) {61		if let Ok(imbalance) = T::Currency::withdraw(62			account,63			self.amount,64			WithdrawReasons::FEE,65			ExistenceRequirement::KeepAlive,66		) {67			// This should never fail. However, let's err on the safe side.68			T::RentPayment::on_unbalanced(imbalance);69		}70	}71}7273enum Verdict<T: Config> {74	/// The contract is exempted from paying rent.75	///76	/// For example, it already paid its rent in the current block, or it has enough deposit for not77	/// paying rent at all.78	Exempt,79	/// The contract cannot afford payment within its rent budget so it gets evicted. However,80	/// because its balance is greater than the subsistence threshold it leaves a tombstone.81	Evict {82		amount: Option<OutstandingAmount<T>>,83	},84	/// Everything is OK, we just only take some charge.85	Charge { amount: OutstandingAmount<T> },86}8788pub struct Rent<T, E>(sp_std::marker::PhantomData<(T, E)>);8990impl<T, E> Rent<T, E>91where92	T: Config,93	T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,94	E: Executable<T>,95{96	/// Returns a fee charged per block from the contract.97	///98	/// This function accounts for the storage rent deposit. I.e. if the contract possesses enough funds99	/// then the fee can drop to zero.100	fn compute_fee_per_block(101		free_balance: &BalanceOf<T>,102		contract: &AliveContractInfo<T>,103		code_size_share: u32,104	) -> BalanceOf<T> {105		let uncovered_by_balance = T::DepositPerStorageByte::get()106			.saturating_mul(contract.storage_size.saturating_add(code_size_share).into())107			.saturating_add(108				T::DepositPerStorageItem::get().saturating_mul(contract.pair_count.into()),109			)110			.saturating_add(T::DepositPerContract::get())111			.saturating_sub(*free_balance);112		T::RentFraction::get().mul_ceil(uncovered_by_balance)113	}114115	/// Returns amount of funds available to consume by rent mechanism.116	///117	/// Rent mechanism cannot consume more than `rent_allowance` set by the contract and it cannot make118	/// the balance lower than [`subsistence_threshold`].119	///120	/// In case the toal_balance is below the subsistence threshold, this function returns `None`.121	fn rent_budget(122		total_balance: &BalanceOf<T>,123		free_balance: &BalanceOf<T>,124		contract: &AliveContractInfo<T>,125	) -> Option<BalanceOf<T>> {126		let subsistence_threshold = Pallet::<T>::subsistence_threshold();127		// Reserved balance contributes towards the subsistence threshold to stay consistent128		// with the existential deposit where the reserved balance is also counted.129		if *total_balance < subsistence_threshold {130			return None;131		}132133		// However, reserved balance cannot be charged so we need to use the free balance134		// to calculate the actual budget (which can be 0).135		let rent_allowed_to_charge = free_balance.saturating_sub(subsistence_threshold);136		Some(<BalanceOf<T>>::min(137			contract.rent_allowance,138			rent_allowed_to_charge,139		))140	}141142	/// Consider the case for rent payment of the given account and returns a `Verdict`.143	///144	/// Use `handicap` in case you want to change the reference block number. (To get more details see145	/// `try_eviction` ).146	fn consider_case(147		account: &T::AccountId,148		current_block_number: T::BlockNumber,149		handicap: T::BlockNumber,150		contract: &AliveContractInfo<T>,151		code_size: u32,152	) -> Verdict<T> {153		// How much block has passed since the last deduction for the contract.154		let blocks_passed = {155			// Calculate an effective block number, i.e. after adjusting for handicap.156			let effective_block_number = current_block_number.saturating_sub(handicap);157			effective_block_number.saturating_sub(contract.deduct_block)158		};159		if blocks_passed.is_zero() {160			// Rent has already been paid161			return Verdict::Exempt;162		}163164		let total_balance = T::Currency::total_balance(account);165		let free_balance = T::Currency::free_balance(account);166167		// An amount of funds to charge per block for storage taken up by the contract.168		let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);169		if fee_per_block.is_zero() {170			// The rent deposit offset reduced the fee to 0. This means that the contract171			// gets the rent for free.172			return Verdict::Exempt;173		}174175		let rent_budget = match Self::rent_budget(&total_balance, &free_balance, contract) {176			Some(rent_budget) => rent_budget,177			None => {178				// All functions that allow a contract to transfer balance enforce179				// that the contract always stays above the subsistence threshold.180				// We want the rent system to always leave a tombstone to prevent the181				// accidental loss of a contract. Ony `seal_terminate` can remove a182				// contract without a tombstone. Therefore this case should be never183				// hit.184				log::error!(185					target: "runtime::contracts",186					"Tombstoned a contract that is below the subsistence threshold: {:?}",187					account,188				);189				0u32.into()190			}191		};192193		let dues = fee_per_block194			.checked_mul(&blocks_passed.saturated_into::<u32>().into())195			.unwrap_or_else(|| <BalanceOf<T>>::max_value());196		let insufficient_rent = rent_budget < dues;197198		// If the rent payment cannot be withdrawn due to locks on the account balance, then evict the199		// account.200		//201		// NOTE: This seems problematic because it provides a way to tombstone an account while202		// avoiding the last rent payment. In effect, someone could retroactively set rent_allowance203		// for their contract to 0.204		let dues_limited = dues.min(rent_budget);205		let can_withdraw_rent = T::Currency::ensure_can_withdraw(206			account,207			dues_limited,208			WithdrawReasons::FEE,209			free_balance.saturating_sub(dues_limited),210		)211		.is_ok();212213		if insufficient_rent || !can_withdraw_rent {214			// The contract cannot afford the rent payment and has a balance above the subsistence215			// threshold, so it leaves a tombstone.216			let amount = if can_withdraw_rent {217				Some(OutstandingAmount::new(dues_limited))218			} else {219				None220			};221			return Verdict::Evict { amount };222		}223224		return Verdict::Charge {225			// We choose to use `dues_limited` here instead of `dues` just to err on the safer side.226			amount: OutstandingAmount::new(dues_limited),227		};228	}229230	/// Enacts the given verdict and returns the updated `ContractInfo`.231	///232	/// `alive_contract_info` should be from the same address as `account`.233	///234	/// # Note235	///236	/// if `evictable_code` is `None` an `Evict` verdict will not be enacted. This is for237	/// when calling this function during a `call` where access to the soon to be evicted238	/// contract should be denied but storage should be left unmodified.239	fn enact_verdict(240		account: &T::AccountId,241		alive_contract_info: AliveContractInfo<T>,242		current_block_number: T::BlockNumber,243		verdict: Verdict<T>,244		evictable_code: Option<PrefabWasmModule<T>>,245	) -> Result<Option<AliveContractInfo<T>>, DispatchError> {246		match (verdict, evictable_code) {247			(Verdict::Evict { amount }, Some(code)) => {248				// We need to remove the trie first because it is the only operation249				// that can fail and this function is called without a storage250				// transaction when called through `claim_surcharge`.251				Storage::<T>::queue_trie_for_deletion(&alive_contract_info)?;252253				if let Some(amount) = amount {254					amount.withdraw(account);255				}256257				// Note: this operation is heavy.258				let child_storage_root = child::root(&alive_contract_info.child_trie_info());259260				let tombstone = <TombstoneContractInfo<T>>::new(261					&child_storage_root[..],262					alive_contract_info.code_hash,263				);264				let tombstone_info = ContractInfo::Tombstone(tombstone);265				<ContractInfoOf<T>>::insert(account, &tombstone_info);266				code.drop_from_storage();267				<Pallet<T>>::deposit_event(Event::Evicted(account.clone()));268				Ok(None)269			}270			(Verdict::Evict { amount: _ }, None) => Ok(None),271			(Verdict::Exempt, _) => {272				let contract = ContractInfo::Alive(AliveContractInfo::<T> {273					deduct_block: current_block_number,274					..alive_contract_info275				});276				<ContractInfoOf<T>>::insert(account, &contract);277				Ok(Some(278					contract279						.get_alive()280						.expect("We just constructed it as alive. qed"),281				))282			}283			(Verdict::Charge { amount }, _) => {284				let contract = ContractInfo::Alive(AliveContractInfo::<T> {285					rent_allowance: alive_contract_info.rent_allowance - amount.peek(),286					deduct_block: current_block_number,287					rent_payed: alive_contract_info.rent_payed.saturating_add(amount.peek()),288					..alive_contract_info289				});290				<ContractInfoOf<T>>::insert(account, &contract);291				amount.withdraw(account);292				Ok(Some(293					contract294						.get_alive()295						.expect("We just constructed it as alive. qed"),296				))297			}298		}299	}300301	/// Make account paying the rent for the current block number302	///303	/// This functions does **not** evict the contract. It returns `None` in case the304	/// contract is in need of eviction. [`try_eviction`] must305	/// be called to perform the eviction.306	pub fn charge(307		account: &T::AccountId,308		contract: AliveContractInfo<T>,309		code_size: u32,310	) -> Result<Option<AliveContractInfo<T>>, DispatchError> {311		let current_block_number = <frame_system::Pallet<T>>::block_number();312		let verdict = Self::consider_case(313			account,314			current_block_number,315			Zero::zero(),316			&contract,317			code_size,318		);319		Self::enact_verdict(account, contract, current_block_number, verdict, None)320	}321322	/// Process a report that a contract under the given address should be evicted.323	///324	/// Enact the eviction right away if the contract should be evicted and return the amount325	/// of rent that the contract payed over its lifetime.326	/// Otherwise, **do nothing** and return None.327	///328	/// The `handicap` parameter gives a way to check the rent to a moment in the past instead329	/// of current block. E.g. if the contract is going to be evicted at the current block,330	/// `handicap = 1` can defer the eviction for 1 block. This is useful to handicap certain snitchers331	/// relative to others.332	///333	/// NOTE this function performs eviction eagerly. All changes are read and written directly to334	/// storage.335	pub fn try_eviction(336		account: &T::AccountId,337		handicap: T::BlockNumber,338	) -> Result<(Option<BalanceOf<T>>, u32), DispatchError> {339		let contract = <ContractInfoOf<T>>::get(account);340		let contract = match contract {341			None | Some(ContractInfo::Tombstone(_)) => return Ok((None, 0)),342			Some(ContractInfo::Alive(contract)) => contract,343		};344		let module = PrefabWasmModule::<T>::from_storage_noinstr(contract.code_hash)?;345		let code_len = module.code_len();346		let current_block_number = <frame_system::Pallet<T>>::block_number();347		let verdict = Self::consider_case(348			account,349			current_block_number,350			handicap,351			&contract,352			module.occupied_storage(),353		);354355		// Enact the verdict only if the contract gets removed.356		match verdict {357			Verdict::Evict { ref amount } => {358				// The outstanding `amount` is withdrawn inside `enact_verdict`.359				let rent_payed = amount360					.as_ref()361					.map(|a| a.peek())362					.unwrap_or_else(|| <BalanceOf<T>>::zero())363					.saturating_add(contract.rent_payed);364				Self::enact_verdict(365					account,366					contract,367					current_block_number,368					verdict,369					Some(module),370				)?;371				Ok((Some(rent_payed), code_len))372			}373			_ => Ok((None, code_len)),374		}375	}376377	/// Returns the projected time a given contract will be able to sustain paying its rent. The378	/// returned projection is relevant for the current block, i.e. it is as if the contract was379	/// accessed at the beginning of the current block. Returns `None` in case if the contract was380	/// evicted before or as a result of the rent collection.381	///382	/// The returned value is only an estimation. It doesn't take into account any top ups, changing the383	/// rent allowance, or any problems coming from withdrawing the dues.384	///385	/// NOTE that this is not a side-effect free function! It will actually collect rent and then386	/// compute the projection. This function is only used for implementation of an RPC method through387	/// `RuntimeApi` meaning that the changes will be discarded anyway.388	pub fn compute_projection(account: &T::AccountId) -> RentProjectionResult<T::BlockNumber> {389		use ContractAccessError::IsTombstone;390391		let contract_info = <ContractInfoOf<T>>::get(account);392		let alive_contract_info = match contract_info {393			None | Some(ContractInfo::Tombstone(_)) => return Err(IsTombstone),394			Some(ContractInfo::Alive(contract)) => contract,395		};396		let module = <PrefabWasmModule<T>>::from_storage_noinstr(alive_contract_info.code_hash)397			.map_err(|_| IsTombstone)?;398		let code_size = module.occupied_storage();399		let current_block_number = <frame_system::Pallet<T>>::block_number();400		let verdict = Self::consider_case(401			account,402			current_block_number,403			Zero::zero(),404			&alive_contract_info,405			code_size,406		);407408		// We skip the eviction in case one is in order.409		// Evictions should only be performed by [`try_eviction`].410		let new_contract_info = Self::enact_verdict(411			account,412			alive_contract_info,413			current_block_number,414			verdict,415			None,416		);417418		// Check what happened after enaction of the verdict.419		let alive_contract_info = new_contract_info420			.map_err(|_| IsTombstone)?421			.ok_or_else(|| IsTombstone)?;422423		// Compute how much would the fee per block be with the *updated* balance.424		let total_balance = T::Currency::total_balance(account);425		let free_balance = T::Currency::free_balance(account);426		let fee_per_block =427			Self::compute_fee_per_block(&free_balance, &alive_contract_info, code_size);428		if fee_per_block.is_zero() {429			return Ok(RentProjection::NoEviction);430		}431432		// Then compute how much the contract will sustain under these circumstances.433		let rent_budget = Self::rent_budget(&total_balance, &free_balance, &alive_contract_info)434			.expect(435				"the contract exists and in the alive state;436			the updated balance must be greater than subsistence deposit;437			this function doesn't return `None`;438			qed439			",440			);441		let blocks_left = match rent_budget.checked_div(&fee_per_block) {442			Some(blocks_left) => blocks_left,443			None => {444				// `fee_per_block` is not zero here, so `checked_div` can return `None` if445				// there is an overflow. This cannot happen with integers though. Return446				// `NoEviction` here just in case.447				return Ok(RentProjection::NoEviction);448			}449		};450451		let blocks_left = blocks_left.saturated_into::<u32>().into();452		Ok(RentProjection::EvictionAt(453			current_block_number + blocks_left,454		))455	}456457	/// Restores the destination account using the origin as prototype.458	///459	/// The restoration will be performed iff:460	/// - the supplied code_hash does still exist on-chain461	/// - origin exists and is alive,462	/// - the origin's storage is not written in the current block463	/// - the restored account has tombstone464	/// - the tombstone matches the hash of the origin storage root, and code hash.465	///466	/// Upon succesful restoration, `origin` will be destroyed, all its funds are transferred to467	/// the restored account. The restored account will inherit the last write block and its last468	/// deduct block will be set to the current block.469	///470	/// # Return Value471	///472	/// Result<(CallerCodeSize, DestCodeSize), (DispatchError, CallerCodeSize, DestCodesize)>473	pub fn restore_to(474		origin: T::AccountId,475		dest: T::AccountId,476		code_hash: CodeHash<T>,477		rent_allowance: BalanceOf<T>,478		delta: Vec<crate::exec::StorageKey>,479	) -> Result<(u32, u32), (DispatchError, u32, u32)> {480		let mut origin_contract = <ContractInfoOf<T>>::get(&origin)481			.and_then(|c| c.get_alive())482			.ok_or((Error::<T>::InvalidSourceContract.into(), 0, 0))?;483484		let child_trie_info = origin_contract.child_trie_info();485486		let current_block = <frame_system::Pallet<T>>::block_number();487488		if origin_contract.last_write == Some(current_block) {489			return Err((Error::<T>::InvalidContractOrigin.into(), 0, 0));490		}491492		let dest_tombstone = <ContractInfoOf<T>>::get(&dest)493			.and_then(|c| c.get_tombstone())494			.ok_or((Error::<T>::InvalidDestinationContract.into(), 0, 0))?;495496		let last_write = if !delta.is_empty() {497			Some(current_block)498		} else {499			origin_contract.last_write500		};501502		// Fails if the code hash does not exist on chain503		let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;504505		// We are allowed to eagerly modify storage even though the function can506		// fail later due to tombstones not matching. This is because the restoration507		// is always called from a contract and therefore in a storage transaction.508		// The failure of this function will lead to this transaction's rollback.509		let bytes_taken: u32 = delta510			.iter()511			.filter_map(|key| {512				let key = blake2_256(key);513				child::get_raw(&child_trie_info, &key).map(|value| {514					child::kill(&child_trie_info, &key);515					value.len() as u32516				})517			})518			.sum();519520		let tombstone = <TombstoneContractInfo<T>>::new(521			// This operation is cheap enough because last_write (delta not included)522			// is not this block as it has been checked earlier.523			&child::root(&child_trie_info)[..],524			code_hash,525		);526527		if tombstone != dest_tombstone {528			return Err((Error::<T>::InvalidTombstone.into(), caller_code_len, 0));529		}530531		origin_contract.storage_size -= bytes_taken;532533		<ContractInfoOf<T>>::remove(&origin);534		let tombstone_code_len = E::remove_user(origin_contract.code_hash);535		<ContractInfoOf<T>>::insert(536			&dest,537			ContractInfo::Alive(AliveContractInfo::<T> {538				code_hash,539				rent_allowance,540				rent_payed: <BalanceOf<T>>::zero(),541				deduct_block: current_block,542				last_write,543				..origin_contract544			}),545		);546547		let origin_free_balance = T::Currency::free_balance(&origin);548		T::Currency::make_free_balance_be(&origin, <BalanceOf<T>>::zero());549		T::Currency::deposit_creating(&dest, origin_free_balance);550551		Ok((caller_code_len, tombstone_code_len))552	}553}