Skip to content

Commit

Permalink
add pallet::call to hotfix sufficients for non-zero nonce accounts (p…
Browse files Browse the repository at this point in the history
…olkadot-evm#619)

* add pallet::call to hotfix sufficients for non-zero nonce accounts

* make hotfix to fix sufficients payable

* cargo fmt

* use distinct pallet for hotfix

* cleanup traces from pallet evm

* add pallet-hotfix-sufficients

* fix description

* fix benchmarking

* update test condition, add docs

* use safe add

Co-authored-by: tgmichel <telmo@purestake.com>
  • Loading branch information
nbaztec and tgmichel authored Jun 2, 2022
1 parent 8a1ceff commit 2ceaa68
Show file tree
Hide file tree
Showing 14 changed files with 727 additions and 0 deletions.
17 changes: 17 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 @@ -4,6 +4,7 @@ members = [
"frame/dynamic-fee",
"frame/ethereum",
"frame/evm",
"frame/hotfix-sufficients",
"frame/evm/precompile/sha3fips",
"frame/evm/precompile/simple",
"frame/evm/precompile/modexp",
Expand Down
109 changes: 109 additions & 0 deletions benchmarking/frame-weight-template.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Apache-2.0
// This file is part of Frontier.
//
// Copyright (c) 2020 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.

//! Autogenerated weights for {{pallet}}
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION {{version}}
//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: {{cmd.repeat}}, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}`
//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}}

// Executed Command:
{{#each args as |arg|~}}
// {{arg}}
{{/each}}

#![allow(unused_parens)]
#![allow(unused_imports)]

use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use sp_std::marker::PhantomData;

/// Weight functions needed for {{pallet}}.
pub trait WeightInfo {
{{#each benchmarks as |benchmark|}}
fn {{benchmark.name~}}
(
{{~#each benchmark.components as |c| ~}}
{{c.name}}: u32, {{/each~}}
) -> Weight;
{{/each}}
}

/// Weights for {{pallet}} using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
{{#each benchmarks as |benchmark|}}
{{#each benchmark.comments as |comment|}}
// {{comment}}
{{/each}}
fn {{benchmark.name~}}
(
{{~#each benchmark.components as |c| ~}}
{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
) -> Weight {
({{underscore benchmark.base_weight}} as Weight)
{{#each benchmark.component_weight as |cw|}}
// Standard Error: {{underscore cw.error}}
.saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))
{{/each}}
{{#if (ne benchmark.base_reads "0")}}
.saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}} as Weight))
{{/if}}
{{#each benchmark.component_reads as |cr|}}
.saturating_add(T::DbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight)))
{{/each}}
{{#if (ne benchmark.base_writes "0")}}
.saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}} as Weight))
{{/if}}
{{#each benchmark.component_writes as |cw|}}
.saturating_add(T::DbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)))
{{/each}}
}
{{/each}}
}

// For backwards compatibility and tests
impl WeightInfo for () {
{{#each benchmarks as |benchmark|}}
{{#each benchmark.comments as |comment|}}
// {{comment}}
{{/each}}
fn {{benchmark.name~}}
(
{{~#each benchmark.components as |c| ~}}
{{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}}
) -> Weight {
({{underscore benchmark.base_weight}} as Weight)
{{#each benchmark.component_weight as |cw|}}
// Standard Error: {{underscore cw.error}}
.saturating_add(({{underscore cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight))
{{/each}}
{{#if (ne benchmark.base_reads "0")}}
.saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}} as Weight))
{{/if}}
{{#each benchmark.component_reads as |cr|}}
.saturating_add(RocksDbWeight::get().reads(({{cr.slope}} as Weight).saturating_mul({{cr.name}} as Weight)))
{{/each}}
{{#if (ne benchmark.base_writes "0")}}
.saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}} as Weight))
{{/if}}
{{#each benchmark.component_writes as |cw|}}
.saturating_add(RocksDbWeight::get().writes(({{cw.slope}} as Weight).saturating_mul({{cw.name}} as Weight)))
{{/each}}
}
{{/each}}
}
1 change: 1 addition & 0 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::too_many_arguments)]

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;

#[cfg(test)]
Expand Down
40 changes: 40 additions & 0 deletions frame/hotfix-sufficients/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[package]
name = "pallet-hotfix-sufficients"
version = "1.0.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/frontier/"
description = "Hotfix zero-value account sufficients with non-zero-value nonces"

[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] }
scale-info = { version = "2.0.0", default-features = false, features = ["derive"] }
sp-core = { version = "6.0.0", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-io = { version = "6.0.0", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-runtime = { version = "6.0.0", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
sp-std = { version = "4.0.0", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }

frame-benchmarking = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
frame-support = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
frame-system = { version = "4.0.0-dev", git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
pallet-evm = { path = "../evm", default-features = false }

[features]
default = ["std"]
std = [
"codec/std",
"scale-info/std",
"sp-core/std",
"sp-runtime/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
]
runtime-benchmarks = [
"frame-benchmarking",
]
16 changes: 16 additions & 0 deletions frame/hotfix-sufficients/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Hotfix Sufficients Module

The Hotfix Sufficients module allows hotfixing account inconsistency to patch existing accounts that have a non-zero `nonce` but a zero `sufficients` value.
The accounts' `sufficients` values also need to be non-zero to be consistent.

## Description

This pallet can be used to hotfix the account state where a previous bug in EVM create account, lead to accounts being created with `0` references (consumers + providers + sufficients)
but a non-zero nonce.

The dispatchable `hotfix_inc_account_sufficients` fixes this by taking a list of account addresses that have zero reference counts (consumers, providers, sufficients are all `0`),
and incrementing their `sufficients` reference counter.

Any addresses that do not have a zero reference count, will be unaffected.

License: Apache-2.0
62 changes: 62 additions & 0 deletions frame/hotfix-sufficients/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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.

#![cfg(feature = "runtime-benchmarks")]

use frame_benchmarking::{benchmarks, impl_benchmark_test_suite};

use super::*;

benchmarks! {
hotfix_inc_account_sufficients {
// This benchmark tests the resource utilization by hotfixing N number of accounts
// by incrementing their `sufficients` if `nonce` is > 0.

let n in 0 .. 1000;

use frame_benchmarking::{whitelisted_caller};
use sp_core::H160;
use frame_system::RawOrigin;

// The caller account is whitelisted for DB reads/write by the benchmarking macro.
let caller: T::AccountId = whitelisted_caller();
let addresses = (0..n as u64)
.map(H160::from_low_u64_le)
.collect::<Vec<H160>>();
let accounts = addresses
.iter()
.cloned()
.map(|addr| {
let account_id = T::AddressMapping::into_account_id(addr);
frame_system::Pallet::<T>::inc_account_nonce(&account_id);
assert_eq!(frame_system::Pallet::<T>::sufficients(&account_id), 0);

account_id
})
.collect::<Vec<_>>();

}: _(RawOrigin::Signed(caller), addresses)
verify {
accounts
.iter()
.for_each(|id| {
assert_eq!(frame_system::Pallet::<T>::sufficients(&id), 1);
});
}
}

impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::tests::Test);
102 changes: 102 additions & 0 deletions frame/hotfix-sufficients/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-License-Identifier: Apache-2.0
// This file is part of Frontier.
//
// Copyright (c) 2021-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.

#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(test)]
mod tests;

#[cfg(test)]
mod mock;

#[cfg(feature = "runtime-benchmarks")]
pub mod benchmarking;
pub mod weights;
pub use weights::WeightInfo;

pub use pallet_evm::AddressMapping;
use sp_runtime::traits::Zero;
use sp_std::vec::Vec;

pub use self::pallet::*;

#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::{dispatch::PostDispatchInfo, pallet_prelude::*};
use frame_system::pallet_prelude::*;
use sp_core::H160;

#[pallet::config]
pub trait Config: frame_system::Config {
/// Mapping from address to account id.
type AddressMapping: AddressMapping<Self::AccountId>;
/// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}

#[pallet::error]
pub enum Error<T> {
/// Maximum address count exceeded
MaxAddressCountExceeded,
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Increment `sufficients` for existing accounts having a nonzero `nonce` but zero `sufficients`, `consumers` and `providers` value.
/// This state was caused by a previous bug in EVM create account dispatchable.
///
/// Any accounts in the input list not satisfying the above condition will remain unaffected.
#[pallet::weight(
<T as pallet::Config>::WeightInfo::hotfix_inc_account_sufficients(addresses.len().try_into().unwrap_or(u32::MAX))
)]
pub fn hotfix_inc_account_sufficients(
origin: OriginFor<T>,
addresses: Vec<H160>,
) -> DispatchResultWithPostInfo {
const MAX_ADDRESS_COUNT: usize = 1000;

frame_system::ensure_signed(origin)?;
ensure!(
addresses.len() <= MAX_ADDRESS_COUNT,
Error::<T>::MaxAddressCountExceeded
);

for address in addresses {
let account_id = T::AddressMapping::into_account_id(address);
let nonce = frame_system::Pallet::<T>::account_nonce(&account_id);
let refs = frame_system::Pallet::<T>::consumers(&account_id)
.saturating_add(frame_system::Pallet::<T>::providers(&account_id))
.saturating_add(frame_system::Pallet::<T>::sufficients(&account_id));

if !nonce.is_zero() && refs.is_zero() {
frame_system::Pallet::<T>::inc_sufficients(&account_id);
}
}

Ok(PostDispatchInfo {
actual_weight: None,
pays_fee: Pays::Yes,
})
}
}
}
Loading

0 comments on commit 2ceaa68

Please sign in to comment.