1234567891011121314151617181920use 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};373839404142struct OutstandingAmount<T: Config> {43 amount: BalanceOf<T>,44}4546impl<T: Config> OutstandingAmount<T> {47 48 49 50 fn new(amount: BalanceOf<T>) -> Self {51 Self { amount }52 }5354 55 fn peek(&self) -> BalanceOf<T> {56 self.amount57 }5859 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 68 T::RentPayment::on_unbalanced(imbalance);69 }70 }71}7273enum Verdict<T: Config> {74 75 76 77 78 Exempt,79 80 81 Evict {82 amount: Option<OutstandingAmount<T>>,83 },84 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 97 98 99 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 116 117 118 119 120 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 128 129 if *total_balance < subsistence_threshold {130 return None;131 }132133 134 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 143 144 145 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 154 let blocks_passed = {155 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 161 return Verdict::Exempt;162 }163164 let total_balance = T::Currency::total_balance(account);165 let free_balance = T::Currency::free_balance(account);166167 168 let fee_per_block = Self::compute_fee_per_block(&free_balance, contract, code_size);169 if fee_per_block.is_zero() {170 171 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 179 180 181 182 183 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 199 200 201 202 203 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 215 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 226 amount: OutstandingAmount::new(dues_limited),227 };228 }229230 231 232 233 234 235 236 237 238 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 249 250 251 Storage::<T>::queue_trie_for_deletion(&alive_contract_info)?;252253 if let Some(amount) = amount {254 amount.withdraw(account);255 }256257 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 302 303 304 305 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 323 324 325 326 327 328 329 330 331 332 333 334 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 356 match verdict {357 Verdict::Evict { ref amount } => {358 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 378 379 380 381 382 383 384 385 386 387 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 409 410 let new_contract_info = Self::enact_verdict(411 account,412 alive_contract_info,413 current_block_number,414 verdict,415 None,416 );417418 419 let alive_contract_info = new_contract_info420 .map_err(|_| IsTombstone)?421 .ok_or_else(|| IsTombstone)?;422423 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 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 445 446 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 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 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 503 let caller_code_len = E::add_user(code_hash).map_err(|e| (e, 0, 0))?;504505 506 507 508 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 522 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}