-
Notifications
You must be signed in to change notification settings - Fork 486
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add precompile clear-storage skeleton * implement precompile clear storage * precompile storage cleaner * mock for testing * add some rust tests * add more rust tests * cover suicided contracts with no storage items * fix * fix bugs * fix precompile * Update precompile signature Co-authored-by: Éloïs <[email protected]> * fix precompile signature * add a limit * fix limit * Fix mock * update precompile * format Cargo.toml * rust fmt * fix clippy warnings * fix format * fix tests * refactor precompile and record weights for clear_suicided_contract * refactor tests * fix formatting * refactor to record cost at the begining * fix format * Fix clippy warnings * update Cargo.lock * Fix std feature * update dependencies * Fix clippy warnings --------- Co-authored-by: librelois <[email protected]>
- Loading branch information
1 parent
f68f4c6
commit d3ebb2f
Showing
7 changed files
with
809 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
[package] | ||
name = "pallet-evm-precompile-storage-cleaner" | ||
version = "0.1.0" | ||
license = "Apache-2.0" | ||
description = "Storage cleaner precompile to clean storage of a suicided contracts" | ||
authors = { workspace = true } | ||
edition = { workspace = true } | ||
repository = { workspace = true } | ||
|
||
[dependencies] | ||
scale-codec = { package = "parity-scale-codec", workspace = true } | ||
# Substrate | ||
frame-support = { workspace = true } | ||
frame-system = { workspace = true } | ||
sp-core = { workspace = true } | ||
sp-runtime = { workspace = true } | ||
sp-std = { workspace = true } | ||
# Frontier | ||
fp-evm = { workspace = true } | ||
pallet-evm = { workspace = true } | ||
precompile-utils = { workspace = true } | ||
|
||
[dev-dependencies] | ||
scale-info = { workspace = true } | ||
# Substrate | ||
frame-system = { workspace = true, features = ["default"] } | ||
pallet-balances = { workspace = true, features = ["default", "insecure_zero_ed"] } | ||
pallet-timestamp = { workspace = true, features = ["default"] } | ||
pallet-utility = { workspace = true, features = ["default"] } | ||
rlp = { workspace = true } | ||
sp-core = { workspace = true, features = ["default"] } | ||
sp-io = { workspace = true, features = ["default"] } | ||
sp-runtime = { workspace = true, features = ["default"] } | ||
sp-std = { workspace = true, features = ["default"] } | ||
|
||
# Frontier | ||
precompile-utils = { workspace = true, features = ["std", "testing"] } | ||
|
||
[features] | ||
default = ["std"] | ||
std = [ | ||
"scale-codec/std", | ||
# Substrate | ||
"frame-support/std", | ||
"frame-system/std", | ||
"sp-runtime/std", | ||
"sp-core/std", | ||
"sp-std/std", | ||
# Frontier | ||
"fp-evm/std", | ||
"pallet-evm/std", | ||
"precompile-utils/std", | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// This file is part of Frontier. | ||
// | ||
// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
//! Storage cleaner precompile. This precompile is used to clean the storage entries of smart contract that | ||
//! has been marked as suicided (self-destructed). | ||
|
||
#![cfg_attr(not(feature = "std"), no_std)] | ||
extern crate alloc; | ||
|
||
use core::marker::PhantomData; | ||
use fp_evm::{PrecompileFailure, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE}; | ||
use pallet_evm::AddressMapping; | ||
use precompile_utils::{prelude::*, EvmResult}; | ||
use sp_core::H160; | ||
use sp_runtime::traits::ConstU32; | ||
use sp_std::vec::Vec; | ||
|
||
#[cfg(test)] | ||
mod mock; | ||
#[cfg(test)] | ||
mod tests; | ||
|
||
pub const ARRAY_LIMIT: u32 = 1_000; | ||
type GetArrayLimit = ConstU32<ARRAY_LIMIT>; | ||
// Storage key for suicided contracts: Blake2_128(16) + Key (H160(20)) | ||
pub const SUICIDED_STORAGE_KEY: u64 = 36; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct StorageCleanerPrecompile<Runtime>(PhantomData<Runtime>); | ||
|
||
#[precompile_utils::precompile] | ||
impl<Runtime> StorageCleanerPrecompile<Runtime> | ||
where | ||
Runtime: pallet_evm::Config, | ||
{ | ||
/// Clear Storage entries of smart contracts that has been marked as suicided (self-destructed). It takes a list of | ||
/// addresses and a limit as input. The limit is used to prevent the function from consuming too much gas. The | ||
/// maximum number of storage entries that can be removed is limit - 1. | ||
#[precompile::public("clearSuicidedStorage(address[],uint64)")] | ||
fn clear_suicided_storage( | ||
handle: &mut impl PrecompileHandle, | ||
addresses: BoundedVec<Address, GetArrayLimit>, | ||
limit: u64, | ||
) -> EvmResult { | ||
let addresses: Vec<_> = addresses.into(); | ||
let nb_addresses = addresses.len() as u64; | ||
if limit == 0 { | ||
return Err(revert("Limit should be greater than zero")); | ||
} | ||
|
||
Self::record_max_cost(handle, nb_addresses, limit)?; | ||
let result = Self::clear_suicided_storage_inner(addresses, limit - 1)?; | ||
Self::refund_cost(handle, result, nb_addresses, limit); | ||
|
||
Ok(()) | ||
} | ||
|
||
/// This function iterates over the addresses, checks if each address is marked as suicided, and then deletes the storage | ||
/// entries associated with that address. If there are no remaining entries, we clear the suicided contract by calling the | ||
/// `clear_suicided_contract` function. | ||
fn clear_suicided_storage_inner( | ||
addresses: Vec<Address>, | ||
limit: u64, | ||
) -> Result<RemovalResult, PrecompileFailure> { | ||
let mut deleted_entries = 0u64; | ||
let mut deleted_contracts = 0u64; | ||
|
||
for Address(address) in addresses { | ||
if !pallet_evm::Pallet::<Runtime>::is_account_suicided(&address) { | ||
return Err(revert(alloc::format!("NotSuicided: {}", address))); | ||
} | ||
|
||
let deleted = pallet_evm::AccountStorages::<Runtime>::drain_prefix(address) | ||
.take((limit.saturating_sub(deleted_entries)) as usize) | ||
.count(); | ||
deleted_entries = deleted_entries.saturating_add(deleted as u64); | ||
|
||
// Check if the storage of this contract has been completly removed | ||
if pallet_evm::AccountStorages::<Runtime>::iter_key_prefix(address) | ||
.next() | ||
.is_none() | ||
{ | ||
Self::clear_suicided_contract(address); | ||
deleted_contracts = deleted_contracts.saturating_add(1); | ||
} | ||
|
||
if deleted_entries >= limit { | ||
break; | ||
} | ||
} | ||
|
||
Ok(RemovalResult { | ||
deleted_entries, | ||
deleted_contracts, | ||
}) | ||
} | ||
|
||
/// Record the maximum cost (Worst case Scenario) of the clear_suicided_storage function. | ||
fn record_max_cost( | ||
handle: &mut impl PrecompileHandle, | ||
nb_addresses: u64, | ||
limit: u64, | ||
) -> EvmResult { | ||
let read_cost = RuntimeHelper::<Runtime>::db_read_gas_cost(); | ||
let write_cost = RuntimeHelper::<Runtime>::db_write_gas_cost(); | ||
let ref_time = 0u64 | ||
// EVM:: Suicided (reads = nb_addresses) | ||
.saturating_add(read_cost.saturating_mul(nb_addresses)) | ||
// EVM:: Suicided (writes = nb_addresses) | ||
.saturating_add(write_cost.saturating_mul(nb_addresses)) | ||
// System: AccountInfo (reads = nb_addresses) for decrementing sufficients | ||
.saturating_add(read_cost.saturating_mul(nb_addresses)) | ||
// System: AccountInfo (writes = nb_addresses) for decrementing sufficients | ||
.saturating_add(write_cost.saturating_mul(nb_addresses)) | ||
// EVM: AccountStorage (reads = limit) | ||
.saturating_add(read_cost.saturating_mul(limit)) | ||
// EVM: AccountStorage (writes = limit) | ||
.saturating_add(write_cost.saturating_mul(limit)); | ||
|
||
let proof_size = 0u64 | ||
// Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * nb_addresses | ||
.saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(nb_addresses)) | ||
// Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * limit | ||
.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(limit)) | ||
// Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * nb_addresses | ||
.saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(nb_addresses)); | ||
|
||
handle.record_external_cost(Some(ref_time), Some(proof_size), None)?; | ||
Ok(()) | ||
} | ||
|
||
/// Refund the additional cost recorded for the clear_suicided_storage function. | ||
fn refund_cost( | ||
handle: &mut impl PrecompileHandle, | ||
result: RemovalResult, | ||
nb_addresses: u64, | ||
limit: u64, | ||
) { | ||
let read_cost = RuntimeHelper::<Runtime>::db_read_gas_cost(); | ||
let write_cost = RuntimeHelper::<Runtime>::db_write_gas_cost(); | ||
|
||
let extra_entries = limit.saturating_sub(result.deleted_entries); | ||
let extra_contracts = nb_addresses.saturating_sub(result.deleted_contracts); | ||
|
||
let mut ref_time = 0u64; | ||
let mut proof_size = 0u64; | ||
|
||
// Refund the cost of the remaining entries | ||
if extra_entries > 0 { | ||
ref_time = ref_time | ||
// EVM:: AccountStorage (reads = extra_entries) | ||
.saturating_add(read_cost.saturating_mul(extra_entries)) | ||
// EVM:: AccountStorage (writes = extra_entries) | ||
.saturating_add(write_cost.saturating_mul(extra_entries)); | ||
proof_size = proof_size | ||
// Proof: EVM::AccountStorage (ACCOUNT_BASIC_PROOF_SIZE) * extra_entries | ||
.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(extra_entries)); | ||
} | ||
|
||
// Refund the cost of the remaining contracts | ||
if extra_contracts > 0 { | ||
ref_time = ref_time | ||
// EVM:: Suicided (reads = extra_contracts) | ||
.saturating_add(read_cost.saturating_mul(extra_contracts)) | ||
// EVM:: Suicided (writes = extra_contracts) | ||
.saturating_add(write_cost.saturating_mul(extra_contracts)) | ||
// System: AccountInfo (reads = extra_contracts) for decrementing sufficients | ||
.saturating_add(read_cost.saturating_mul(extra_contracts)) | ||
// System: AccountInfo (writes = extra_contracts) for decrementing sufficients | ||
.saturating_add(write_cost.saturating_mul(extra_contracts)); | ||
proof_size = proof_size | ||
// Proof: EVM::Suicided (SUICIDED_STORAGE_KEY) * extra_contracts | ||
.saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(extra_contracts)) | ||
// Proof: System::AccountInfo (ACCOUNT_BASIC_PROOF_SIZE) * extra_contracts | ||
.saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(extra_contracts)); | ||
} | ||
|
||
handle.refund_external_cost(Some(ref_time), Some(proof_size)); | ||
} | ||
|
||
/// Clears the storage of a suicided contract. | ||
/// | ||
/// This function will remove the given address from the list of suicided contracts | ||
/// and decrement the sufficients of the account associated with the address. | ||
fn clear_suicided_contract(address: H160) { | ||
pallet_evm::Suicided::<Runtime>::remove(address); | ||
|
||
let account_id = Runtime::AddressMapping::into_account_id(address); | ||
let _ = frame_system::Pallet::<Runtime>::dec_sufficients(&account_id); | ||
} | ||
} | ||
|
||
struct RemovalResult { | ||
pub deleted_entries: u64, | ||
pub deleted_contracts: u64, | ||
} |
Oops, something went wrong.