|
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
| 2 | +// This file is part of Frontier. |
| 3 | +// |
| 4 | +// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd. |
| 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 | +//! Storage cleaner precompile. This precompile is used to clean the storage entries of smart contract that |
| 19 | +//! has been marked as suicided (self-destructed). |
| 20 | +
|
| 21 | +#![cfg_attr(not(feature = "std"), no_std)] |
| 22 | +extern crate alloc; |
| 23 | + |
| 24 | +use core::marker::PhantomData; |
| 25 | +use fp_evm::{PrecompileFailure, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE}; |
| 26 | +use pallet_evm::AddressMapping; |
| 27 | +use precompile_utils::{prelude::*, EvmResult}; |
| 28 | +use sp_core::H160; |
| 29 | +use sp_runtime::traits::ConstU32; |
| 30 | +use sp_std::vec::Vec; |
| 31 | + |
| 32 | +#[cfg(test)] |
| 33 | +mod mock; |
| 34 | +#[cfg(test)] |
| 35 | +mod tests; |
| 36 | + |
| 37 | +pub const ARRAY_LIMIT: u32 = 1_000; |
| 38 | +type GetArrayLimit = ConstU32<ARRAY_LIMIT>; |
| 39 | +// Storage key for suicided contracts: Blake2_128(16) + Key (H160(20)) |
| 40 | +pub const SUICIDED_STORAGE_KEY: u64 = 36; |
| 41 | + |
| 42 | +#[derive(Debug, Clone)] |
| 43 | +pub struct StorageCleanerPrecompile<Runtime>(PhantomData<Runtime>); |
| 44 | + |
| 45 | +#[precompile_utils::precompile] |
| 46 | +impl<Runtime> StorageCleanerPrecompile<Runtime> |
| 47 | +where |
| 48 | + Runtime: pallet_evm::Config, |
| 49 | +{ |
| 50 | + /// Clear Storage entries of smart contracts that has been marked as suicided (self-destructed). It takes a list of |
| 51 | + /// addresses and a limit as input. The limit is used to prevent the function from consuming too much gas. The |
| 52 | + /// maximum number of storage entries that can be removed is limit - 1. |
| 53 | + #[precompile::public("clearSuicidedStorage(address[],uint64)")] |
| 54 | + fn clear_suicided_storage( |
| 55 | + handle: &mut impl PrecompileHandle, |
| 56 | + addresses: BoundedVec<Address, GetArrayLimit>, |
| 57 | + limit: u64, |
| 58 | + ) -> EvmResult { |
| 59 | + let addresses: Vec<_> = addresses.into(); |
| 60 | + let nb_addresses = addresses.len() as u64; |
| 61 | + if limit == 0 { |
| 62 | + return Err(revert("Limit should be greater than zero")); |
| 63 | + } |
| 64 | + |
| 65 | + Self::record_max_cost(handle, nb_addresses, limit)?; |
| 66 | + let result = Self::clear_suicided_storage_inner(addresses, limit - 1)?; |
| 67 | + Self::refund_cost(handle, result, nb_addresses, limit); |
| 68 | + |
| 69 | + Ok(()) |
| 70 | + } |
| 71 | + |
| 72 | + /// This function iterates over the addresses, checks if each address is marked as suicided, and then deletes the storage |
| 73 | + /// entries associated with that address. If there are no remaining entries, we clear the suicided contract by calling the |
| 74 | + /// `clear_suicided_contract` function. |
| 75 | + fn clear_suicided_storage_inner( |
| 76 | + addresses: Vec<Address>, |
| 77 | + limit: u64, |
| 78 | + ) -> Result<RemovalResult, PrecompileFailure> { |
| 79 | + let mut deleted_entries = 0u64; |
| 80 | + let mut deleted_contracts = 0u64; |
| 81 | + |
| 82 | + for Address(address) in addresses { |
| 83 | + if !pallet_evm::Pallet::<Runtime>::is_account_suicided(&address) { |
| 84 | + return Err(revert(alloc::format!("NotSuicided: {}", address))); |
| 85 | + } |
| 86 | + |
| 87 | + let deleted = pallet_evm::AccountStorages::<Runtime>::drain_prefix(address) |
| 88 | + .take((limit.saturating_sub(deleted_entries)) as usize) |
| 89 | + .count(); |
| 90 | + deleted_entries = deleted_entries.saturating_add(deleted as u64); |
| 91 | + |
| 92 | + // Check if the storage of this contract has been completly removed |
| 93 | + if pallet_evm::AccountStorages::<Runtime>::iter_key_prefix(address) |
| 94 | + .next() |
| 95 | + .is_none() |
| 96 | + { |
| 97 | + Self::clear_suicided_contract(address); |
| 98 | + deleted_contracts = deleted_contracts.saturating_add(1); |
| 99 | + } |
| 100 | + |
| 101 | + if deleted_entries >= limit { |
| 102 | + break; |
| 103 | + } |
| 104 | + } |
| 105 | + |
| 106 | + Ok(RemovalResult { |
| 107 | + deleted_entries, |
| 108 | + deleted_contracts, |
| 109 | + }) |
| 110 | + } |
| 111 | + |
| 112 | + /// Record the maximum cost (Worst case Scenario) of the clear_suicided_storage function. |
| 113 | + fn record_max_cost( |
| 114 | + handle: &mut impl PrecompileHandle, |
| 115 | + nb_addresses: u64, |
| 116 | + limit: u64, |
| 117 | + ) -> EvmResult { |
| 118 | + let read_cost = RuntimeHelper::<Runtime>::db_read_gas_cost(); |
| 119 | + let write_cost = RuntimeHelper::<Runtime>::db_write_gas_cost(); |
| 120 | + let ref_time = 0u64 |
| 121 | + // EVM:: Suicided (reads = nb_addresses) |
| 122 | + .saturating_add(read_cost.saturating_mul(nb_addresses)) |
| 123 | + // EVM:: Suicided (writes = nb_addresses) |
| 124 | + .saturating_add(write_cost.saturating_mul(nb_addresses)) |
| 125 | + // System: AccountInfo (reads = nb_addresses) for decrementing sufficients |
| 126 | + .saturating_add(read_cost.saturating_mul(nb_addresses)) |
| 127 | + // System: AccountInfo (writes = nb_addresses) for decrementing sufficients |
| 128 | + .saturating_add(write_cost.saturating_mul(nb_addresses)) |
| 129 | + // EVM: AccountStorage (reads = limit) |
| 130 | + .saturating_add(read_cost.saturating_mul(limit)) |
| 131 | + // EVM: AccountStorage (writes = limit) |
| 132 | + .saturating_add(write_cost.saturating_mul(limit)); |
| 133 | + |
| 134 | + let proof_size = 0u64 |
| 135 | + // Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * nb_addresses |
| 136 | + .saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(nb_addresses)) |
| 137 | + // Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * limit |
| 138 | + .saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(limit)) |
| 139 | + // Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * nb_addresses |
| 140 | + .saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(nb_addresses)); |
| 141 | + |
| 142 | + handle.record_external_cost(Some(ref_time), Some(proof_size), None)?; |
| 143 | + Ok(()) |
| 144 | + } |
| 145 | + |
| 146 | + /// Refund the additional cost recorded for the clear_suicided_storage function. |
| 147 | + fn refund_cost( |
| 148 | + handle: &mut impl PrecompileHandle, |
| 149 | + result: RemovalResult, |
| 150 | + nb_addresses: u64, |
| 151 | + limit: u64, |
| 152 | + ) { |
| 153 | + let read_cost = RuntimeHelper::<Runtime>::db_read_gas_cost(); |
| 154 | + let write_cost = RuntimeHelper::<Runtime>::db_write_gas_cost(); |
| 155 | + |
| 156 | + let extra_entries = limit.saturating_sub(result.deleted_entries); |
| 157 | + let extra_contracts = nb_addresses.saturating_sub(result.deleted_contracts); |
| 158 | + |
| 159 | + let mut ref_time = 0u64; |
| 160 | + let mut proof_size = 0u64; |
| 161 | + |
| 162 | + // Refund the cost of the remaining entries |
| 163 | + if extra_entries > 0 { |
| 164 | + ref_time = ref_time |
| 165 | + // EVM:: AccountStorage (reads = extra_entries) |
| 166 | + .saturating_add(read_cost.saturating_mul(extra_entries)) |
| 167 | + // EVM:: AccountStorage (writes = extra_entries) |
| 168 | + .saturating_add(write_cost.saturating_mul(extra_entries)); |
| 169 | + proof_size = proof_size |
| 170 | + // Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * extra_entries |
| 171 | + .saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(extra_entries)); |
| 172 | + } |
| 173 | + |
| 174 | + // Refund the cost of the remaining contracts |
| 175 | + if extra_contracts > 0 { |
| 176 | + ref_time = ref_time |
| 177 | + // EVM:: Suicided (reads = extra_contracts) |
| 178 | + .saturating_add(read_cost.saturating_mul(extra_contracts)) |
| 179 | + // EVM:: Suicided (writes = extra_contracts) |
| 180 | + .saturating_add(write_cost.saturating_mul(extra_contracts)) |
| 181 | + // System: AccountInfo (reads = extra_contracts) for decrementing sufficients |
| 182 | + .saturating_add(read_cost.saturating_mul(extra_contracts)) |
| 183 | + // System: AccountInfo (writes = extra_contracts) for decrementing sufficients |
| 184 | + .saturating_add(write_cost.saturating_mul(extra_contracts)); |
| 185 | + proof_size = proof_size |
| 186 | + // Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * extra_contracts |
| 187 | + .saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(extra_contracts)) |
| 188 | + // Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * extra_contracts |
| 189 | + .saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(extra_contracts)); |
| 190 | + } |
| 191 | + |
| 192 | + handle.refund_external_cost(Some(ref_time), Some(proof_size)); |
| 193 | + } |
| 194 | + |
| 195 | + /// Clears the storage of a suicided contract. |
| 196 | + /// |
| 197 | + /// This function will remove the given address from the list of suicided contracts |
| 198 | + /// and decrement the sufficients of the account associated with the address. |
| 199 | + fn clear_suicided_contract(address: H160) { |
| 200 | + pallet_evm::Suicided::<Runtime>::remove(address); |
| 201 | + |
| 202 | + let account_id = Runtime::AddressMapping::into_account_id(address); |
| 203 | + let _ = frame_system::Pallet::<Runtime>::dec_sufficients(&account_id); |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +struct RemovalResult { |
| 208 | + pub deleted_entries: u64, |
| 209 | + pub deleted_contracts: u64, |
| 210 | +} |
0 commit comments