#![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>;
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,
{
#[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(())
}
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);
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,
})
}
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
.saturating_add(read_cost.saturating_mul(nb_addresses))
.saturating_add(write_cost.saturating_mul(nb_addresses))
.saturating_add(read_cost.saturating_mul(nb_addresses))
.saturating_add(write_cost.saturating_mul(nb_addresses))
.saturating_add(read_cost.saturating_mul(limit))
.saturating_add(write_cost.saturating_mul(limit));
let proof_size = 0u64
.saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(nb_addresses))
.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(limit))
.saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(nb_addresses));
handle.record_external_cost(Some(ref_time), Some(proof_size), None)?;
Ok(())
}
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;
if extra_entries > 0 {
ref_time = ref_time
.saturating_add(read_cost.saturating_mul(extra_entries))
.saturating_add(write_cost.saturating_mul(extra_entries));
proof_size = proof_size
.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE.saturating_mul(extra_entries));
}
if extra_contracts > 0 {
ref_time = ref_time
.saturating_add(read_cost.saturating_mul(extra_contracts))
.saturating_add(write_cost.saturating_mul(extra_contracts))
.saturating_add(read_cost.saturating_mul(extra_contracts))
.saturating_add(write_cost.saturating_mul(extra_contracts));
proof_size = proof_size
.saturating_add(SUICIDED_STORAGE_KEY.saturating_mul(extra_contracts))
.saturating_add(ACCOUNT_BASIC_PROOF_SIZE.saturating_mul(extra_contracts));
}
handle.refund_external_cost(Some(ref_time), Some(proof_size));
}
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,
}