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