Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Storage Cleaner Precompile #1224

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4c9cdd0
add precompile clear-storage skeleton
librelois Oct 3, 2023
7a9cf23
implement precompile clear storage
librelois Oct 8, 2023
50c50fc
precompile storage cleaner
ahmadkaouk Oct 16, 2023
893ce1e
mock for testing
ahmadkaouk Oct 16, 2023
946ee9c
add some rust tests
ahmadkaouk Oct 16, 2023
8f80890
add more rust tests
ahmadkaouk Oct 16, 2023
aa01440
cover suicided contracts with no storage items
ahmadkaouk Oct 16, 2023
8e853c9
fix
ahmadkaouk Oct 16, 2023
c43ba7d
fix bugs
ahmadkaouk Oct 16, 2023
b93051c
fix precompile
ahmadkaouk Oct 19, 2023
997ec3c
Update precompile signature
ahmadkaouk Oct 19, 2023
a301ebb
fix precompile signature
ahmadkaouk Oct 19, 2023
76921f1
add a limit
ahmadkaouk Nov 2, 2023
d8e89ee
fix limit
ahmadkaouk Nov 15, 2023
3b5e877
Merge branch 'master' into ahmad-clear-suicided-storage
ahmadkaouk Jan 25, 2024
8f3deb4
Fix mock
ahmadkaouk Jan 25, 2024
a2d3a1f
update precompile
ahmadkaouk Jan 26, 2024
851bad5
format Cargo.toml
ahmadkaouk Jan 26, 2024
e2a5b3a
rust fmt
ahmadkaouk Jan 26, 2024
59f8436
fix clippy warnings
ahmadkaouk Jan 26, 2024
c848f79
fix format
ahmadkaouk Jan 26, 2024
4fa6d38
fix tests
ahmadkaouk Jan 26, 2024
5eab3c2
refactor precompile and record weights for clear_suicided_contract
ahmadkaouk Feb 28, 2024
0a40c2f
refactor tests
ahmadkaouk Feb 28, 2024
73d32bd
fix formatting
ahmadkaouk Feb 28, 2024
afc3a3a
Merge branch 'master' into ahmad-clear-suicided-storage
ahmadkaouk Feb 28, 2024
11d085d
refactor to record cost at the begining
ahmadkaouk Feb 29, 2024
c85332c
fix format
ahmadkaouk Mar 6, 2024
a2532bf
Fix clippy warnings
ahmadkaouk Mar 13, 2024
66c9805
Merge branch 'master' into ahmad-clear-suicided-storage
ahmadkaouk Mar 14, 2024
8e5fe4d
update Cargo.lock
ahmadkaouk Mar 14, 2024
0310225
Fix std feature
ahmadkaouk Apr 2, 2024
3e98d56
Merge branch 'master' into ahmad-clear-suicided-storage
ahmadkaouk Apr 2, 2024
dacc2bd
update dependencies
ahmadkaouk Apr 2, 2024
1c9f297
Fix clippy warnings
ahmadkaouk Apr 2, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"frame/evm/precompile/bls12381",
"frame/evm/precompile/dispatch",
"frame/evm/precompile/curve25519",
"frame/evm/precompile/storage-cleaner",
"client/api",
"client/consensus",
"client/rpc-core",
Expand Down
53 changes: 53 additions & 0 deletions frame/evm/precompile/storage-cleaner/Cargo.toml
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",
]
210 changes: 210 additions & 0 deletions frame/evm/precompile/storage-cleaner/src/lib.rs
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,
}
Loading
Loading