diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index a876fa545b4b0..4e16eeba4c10e 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -1214,6 +1214,7 @@ impl pallet_revive::Config for Runtime {
type FeeInfo = pallet_revive::evm::fees::Info
;
type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight;
type DebugEnabled = ConstBool;
+ type GasScale = ConstU32<1000>;
}
parameter_types! {
diff --git a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
index 7cfd9467592ae..01f6dd1700d0d 100644
--- a/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
+++ b/cumulus/parachains/runtimes/testing/penpal/src/lib.rs
@@ -842,6 +842,7 @@ impl pallet_revive::Config for Runtime {
type FeeInfo = pallet_revive::evm::fees::Info;
type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight;
type DebugEnabled = ConstBool;
+ type GasScale = ConstU32<1000>;
}
impl pallet_sudo::Config for Runtime {
diff --git a/prdoc/pr_10393.prdoc b/prdoc/pr_10393.prdoc
new file mode 100644
index 0000000000000..5f48ef937ae64
--- /dev/null
+++ b/prdoc/pr_10393.prdoc
@@ -0,0 +1,39 @@
+title: Add configuration to set Ethereum gas scale
+doc:
+- audience: Runtime Dev
+ description: |-
+ This PR adds a new configuration parameter (`GasScale`) to pallet-revive that allows to change the scale of the Ethereum gas and of the Ethereum gas price.
+
+ Before this PR, the Ethereum gas price is simply the next fee multiplier of pallet-transaction-payment multiplied by `NativeToEthRatio`. Thus, on Polkadot this is 100_000_000 when the multiplier has its default value of 1.
+
+ The required gas of a transaction is its total cost divided by the gas price, where the total cost is the sum of the transaction fee and the storage deposit.
+
+ This leads to a situation where the required gas for a transaction on revive is usually orders of magnitude larger than the required amount of gas on Ethereum. This can lead to issues with tools or systems that interact with revive and hard code expected gas amounts or upper limits of gas amounts.
+
+ Setting `GasScale` has two effects:
+ - revive's Ethereum gas price is scaled up by the factor `GasScale`
+ - resulting used/estimated gas amounts get scaled down by the factor `GasScale`.
+
+ ## Technical Details
+ Internally, revive uses exactly the same gas price and gas units as before. Only at the interface these amounts and prices get scaled by `GasScale`.
+
+ ## Recommended
+ This PR sets `GasScale` for the dev-node to 50_000.
+
+ This is motivated by the fact that storing a value in a contract storage slot costs `DepositPerChildTrieItem + DepositPerByte * 32`, which is `2_000_000_000 + 10_000_000 * 32` (= `2_320_000_000`) plancks. Before this change the gas price was 1_000_000 wei, so that this
+ equated to 2_320_000_000 gas units. In EVM this operation requires 22_100 gas only.
+
+ Thus, `GasScale` would need to be about 100_000 in order for `SSTORE` to have similar worst case gas requirements.
+
+ ## Resolved Issues
+
+ This PR addresses https://github.com/paritytech/contract-issues/issues/18 but we also need to find an appropriate `GasScale` for a mainnet installment of pallet-revive.
+crates:
+- name: revive-dev-runtime
+ bump: patch
+- name: pallet-revive
+ bump: major
+- name: asset-hub-westend-runtime
+ bump: patch
+- name: penpal-runtime
+ bump: patch
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 3a54a6bb09f13..8eb710dfa99e4 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -1539,6 +1539,7 @@ impl pallet_revive::Config for Runtime {
type FeeInfo = pallet_revive::evm::fees::Info;
type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight;
type DebugEnabled = ConstBool;
+ type GasScale = ConstU32<1000>;
}
impl pallet_sudo::Config for Runtime {
diff --git a/substrate/frame/revive/dev-node/runtime/src/lib.rs b/substrate/frame/revive/dev-node/runtime/src/lib.rs
index 43adcb24a04a1..046d16ea41083 100644
--- a/substrate/frame/revive/dev-node/runtime/src/lib.rs
+++ b/substrate/frame/revive/dev-node/runtime/src/lib.rs
@@ -71,7 +71,7 @@ pub mod genesis_config_presets {
use alloc::{vec, vec::Vec};
use serde_json::Value;
- pub const ENDOWMENT: Balance = 1_000_000_001 * DOLLARS;
+ pub const ENDOWMENT: Balance = 10_000_000_000_001 * DOLLARS;
fn well_known_accounts() -> Vec {
Sr25519Keyring::well_known()
@@ -356,6 +356,7 @@ impl pallet_revive::Config for Runtime {
type Time = Timestamp;
type FeeInfo = FeeInfo;
type DebugEnabled = ConstBool;
+ type GasScale = ConstU32<50000>;
}
pallet_revive::impl_runtime_apis_plus_revive_traits!(
diff --git a/substrate/frame/revive/src/lib.rs b/substrate/frame/revive/src/lib.rs
index e1cd26bfe0da3..b47caf6f98aff 100644
--- a/substrate/frame/revive/src/lib.rs
+++ b/substrate/frame/revive/src/lib.rs
@@ -347,6 +347,27 @@ pub mod pallet {
/// Allows debug-mode configuration, such as enabling unlimited contract size.
#[pallet::constant]
type DebugEnabled: Get;
+
+ /// This determines the relative scale of our gas price and gas estimates.
+ ///
+ /// By default, the gas price (in wei) is `FeeInfo::next_fee_multiplier()` multiplied by
+ /// `NativeToEthRatio`. `GasScale` allows to scale this value: the actual gas price is the
+ /// default gas price multiplied by `GasScale`.
+ ///
+ /// As a consequence, gas cost (gas estimates and actual gas usage during transaction) is
+ /// scaled down by the same factor. Thus, the total transaction cost is not affected by
+ /// `GasScale` – apart from rounding differences: the transaction cost is always a multiple
+ /// of the gas price and is derived by rounded up, so that with higher `GasScales` this can
+ /// lead to higher gas cost as the rounding difference would be larger.
+ ///
+ /// The main purpose of changing the `GasScale` is to tune the gas cost so that it is closer
+ /// to standard EVM gas cost and contracts will not run out of gas when tools or code
+ /// assume hard coded gas limits.
+ ///
+ /// Requirement: `GasScale` must not be 0
+ #[pallet::constant]
+ #[pallet::no_default_bounds]
+ type GasScale: Get;
}
/// Container for different types that implement [`DefaultConfig`]` of this pallet.
@@ -375,6 +396,7 @@ pub mod pallet {
pub const DepositPerByte: Balance = deposit(0, 1);
pub const CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(0);
pub const MaxEthExtrinsicWeight: FixedU128 = FixedU128::from_rational(9, 10);
+ pub const GasScale: u32 = 10u32;
}
/// A type providing default configurations for this pallet in testing environment.
@@ -429,6 +451,7 @@ pub mod pallet {
type FeeInfo = ();
type MaxEthExtrinsicWeight = MaxEthExtrinsicWeight;
type DebugEnabled = ConstBool;
+ type GasScale = GasScale;
}
}
@@ -892,6 +915,8 @@ pub mod pallet {
fn integrity_test() {
assert!(T::ChainId::get() > 0, "ChainId must be greater than 0");
+ assert!(T::GasScale::get() > 0u32.into(), "GasScale must not be 0");
+
T::FeeInfo::integrity_test();
// The memory available in the block building runtime
@@ -2114,8 +2139,12 @@ impl Pallet {
/// Get the base gas price.
pub fn evm_base_fee() -> U256 {
+ let gas_scale = ::GasScale::get();
let multiplier = T::FeeInfo::next_fee_multiplier();
- multiplier.saturating_mul_int::(T::NativeToEthRatio::get().into()).into()
+ multiplier
+ .saturating_mul_int::(T::NativeToEthRatio::get().into())
+ .saturating_mul(gas_scale.saturated_into())
+ .into()
}
/// Build an EVM tracer from the given tracer type.
diff --git a/substrate/frame/revive/src/metering/gas.rs b/substrate/frame/revive/src/metering/gas.rs
index 15b55affdc1fc..4e51c82c57ade 100644
--- a/substrate/frame/revive/src/metering/gas.rs
+++ b/substrate/frame/revive/src/metering/gas.rs
@@ -13,10 +13,11 @@
// 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.
+// limitations under the License
use crate::{evm::fees::InfoT, BalanceOf, Config, StorageDeposit};
use frame_support::DebugNoBound;
+use sp_core::Get;
use sp_runtime::{FixedPointNumber, Saturating};
/// The type for negative and positive gas amounts.
@@ -52,7 +53,8 @@ impl SignedGas {
/// Transform an Ethereum gas amount coming from outside the metering system and transform into
/// the internally used SignedGas.
pub fn from_ethereum_gas(gas: BalanceOf) -> Self {
- Self::Positive(gas)
+ let gas_scale = ::GasScale::get();
+ Self::Positive(gas.saturating_mul(gas_scale.into()))
}
/// Transform a storage deposit into a gas value. The value will be adjusted by dividing it
@@ -80,8 +82,11 @@ impl SignedGas {
/// Transform the gas amount to an Ethereum gas amount usable for external purposes
/// Returns None if the gas amount is negative.
pub fn to_ethereum_gas(&self) -> Option> {
+ let gas_scale: BalanceOf = ::GasScale::get().into();
+
match self {
- Positive(amount) => Some(*amount),
+ Positive(amount) =>
+ Some((amount.saturating_add(gas_scale.saturating_sub(1u32.into()))) / gas_scale),
Negative(..) => None,
}
}
diff --git a/substrate/frame/revive/src/metering/tests.rs b/substrate/frame/revive/src/metering/tests.rs
index 4b72024a5d6fc..35089c593cc04 100644
--- a/substrate/frame/revive/src/metering/tests.rs
+++ b/substrate/frame/revive/src/metering/tests.rs
@@ -18,8 +18,8 @@
use crate::{
test_utils::{builder::Contract, ALICE, ALICE_ADDR},
tests::{builder, ExtBuilder, Test},
- CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits, TransactionMeter,
- WeightToken,
+ BalanceOf, CallResources, Code, Config, EthTxInfo, StorageDeposit, TransactionLimits,
+ TransactionMeter, WeightToken,
};
use alloy_core::sol_types::SolCall;
use frame_support::traits::fungible::Mutate;
@@ -104,8 +104,15 @@ fn max_consumed_deposit_integration_refunds_subframes(
#[test]
fn substrate_metering_initialization_works() {
+ let gas_scale = ::GasScale::get().into();
+
let tests = vec![
- (5_000_000_000, 1_000_000_000, 2_000, Some((2999999500, 1499999750, 11107, 599999900))),
+ (
+ 5_000_000_000u64,
+ 1_000_000_000,
+ 2_000,
+ Some((2999999500u64, 1499999750, 11107, 599999900)),
+ ),
(6_000_000_000, 1_000_000_000, 2_000, Some((3999999500, 1999999750, 13728, 799999900))),
(6_000_000_000, 1_000_000_000, 10_000, Some((2185302235, 1999999750, 5728, 437060447))),
(2_000_000_000, 1_000_000_000, 2_000, None),
@@ -125,14 +132,17 @@ fn substrate_metering_initialization_works() {
EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof));
let transaction_meter =
TransactionMeter::::new(TransactionLimits::EthereumGas {
- eth_gas_limit,
+ eth_gas_limit: eth_gas_limit.div_ceil(gas_scale),
maybe_weight_limit: None,
eth_tx_info,
});
if let Some((gas_left, ref_time_left, proof_size_left, deposit_left)) = remaining {
let transaction_meter = transaction_meter.unwrap();
- assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap());
+ assert_eq!(
+ gas_left.div_ceil(gas_scale),
+ transaction_meter.eth_gas_left().unwrap()
+ );
assert_eq!(
Weight::from_parts(ref_time_left, proof_size_left),
transaction_meter.weight_left().unwrap()
@@ -160,7 +170,7 @@ fn substrate_metering_initialization_works() {
EthTxInfo::::new(100, Weight::from_parts(1_000_000_000, 2_000));
let transaction_meter =
TransactionMeter::::new(TransactionLimits::EthereumGas {
- eth_gas_limit: 5_000_000_000,
+ eth_gas_limit: 5_000_000_000 / gas_scale,
maybe_weight_limit: Some(Weight::from_parts(
ref_time_limit,
proof_size_limit,
@@ -181,10 +191,14 @@ fn substrate_metering_initialization_works() {
fn substrate_metering_charges_works() {
use Charge::{D, W};
+ let gas_scale = ::GasScale::get().into();
let tests = vec![
(
- (5_000_000_000, 1_000_000_000, 2_000),
- vec![(W(1000, 100), Some((2999997500, 1499998750, 11007, 599999500, 2000002500u64)))],
+ (5_000_000_000u64, 1_000_000_000, 2_000),
+ vec![(
+ W(1000, 100),
+ Some((2999997500u64, 1499998750, 11007, 599999500, 2000002500u64)),
+ )],
),
(
(5_000_000_000, 1_000_000_000, 2_000),
@@ -251,7 +265,7 @@ fn substrate_metering_charges_works() {
EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof));
let mut transaction_meter =
TransactionMeter::::new(TransactionLimits::EthereumGas {
- eth_gas_limit,
+ eth_gas_limit: eth_gas_limit.div_ceil(gas_scale),
maybe_weight_limit: None,
eth_tx_info,
})
@@ -282,13 +296,19 @@ fn substrate_metering_charges_works() {
)) = remaining
{
assert!(is_ok);
- assert_eq!(gas_left, transaction_meter.eth_gas_left().unwrap());
+ assert_eq!(
+ gas_left.div_ceil(gas_scale),
+ transaction_meter.eth_gas_left().unwrap()
+ );
assert_eq!(
Weight::from_parts(ref_time_left, proof_size_left),
transaction_meter.weight_left().unwrap()
);
assert_eq!(deposit_left, transaction_meter.deposit_left().unwrap());
- assert_eq!(gas_consumed, transaction_meter.total_consumed_gas());
+ assert_eq!(
+ gas_consumed.div_ceil(gas_scale),
+ transaction_meter.total_consumed_gas()
+ );
} else {
assert!(!is_ok);
}
@@ -301,10 +321,11 @@ fn substrate_metering_charges_works() {
fn substrate_nesting_works() {
use CallResources::{Ethereum, NoLimits, WeightDeposit};
+ let gas_scale = ::GasScale::get().into();
let tests = vec![
(
- ((5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits),
- Some((2999992500, 1499996250, 10107, 599998500, 2000007500)),
+ ((5_000_000_000u64, 1_000_000_000, 2_000, 1000, 1000, 1000i64), NoLimits),
+ Some((2999992500u64, 1499996250, 10107, 599998500, 2000007500u64)),
),
(
((5_000_000_000, 1_000_000_000, 2_000, 1000000000, 10000, 50000), NoLimits),
@@ -389,9 +410,9 @@ fn substrate_nesting_works() {
(
(
(5_000_000_000, 1_000_000_000, 2_000, 1000, 1000, 1000),
- Ethereum { gas: 2999992499, add_stipend: false },
+ Ethereum { gas: 2999992490, add_stipend: false },
),
- Some((2999992499, 1499996249, 10107, 599998499, 2000007500)),
+ Some((2999992490, 1499996245, 10107, 599998498, 2000007500)),
),
(
(
@@ -403,16 +424,9 @@ fn substrate_nesting_works() {
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000),
- Ethereum { gas: 708617664, add_stipend: false },
- ),
- Some((708617664, 18999997749, 1857, 141723532, 4291382335)),
- ),
- (
- (
- (5_000_000_000, 1_000_000_000, 3000, 2000, 100000, -7000000000),
- Ethereum { gas: 708617666, add_stipend: false },
+ Ethereum { gas: 708617660, add_stipend: false },
),
- Some((708617665, 18999997750, 1857, 141723533, 4291382335)),
+ Some((708617660, 18999997747, 1857, 141723532, 4291382335)),
),
(
(
@@ -424,37 +438,37 @@ fn substrate_nesting_works() {
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452),
- Ethereum { gas: 5, add_stipend: false },
+ Ethereum { gas: 500, add_stipend: false },
),
Some((4, 1499769120, 0, 0, 4999999996)),
),
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 10106, 91452),
- Ethereum { gas: 3, add_stipend: false },
+ Ethereum { gas: 300, add_stipend: false },
),
- Some((3, 1499769119, 0, 0, 4999999996)),
+ Some((4, 1499769120, 0, 0, 4999999996)),
),
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 1010, 91452),
- Ethereum { gas: 3, add_stipend: false },
+ Ethereum { gas: 300, add_stipend: false },
),
- Some((3, 1, 1232, 0, 2000461760)),
+ Some((300, 150, 1232, 60, 2000461760)),
),
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 2242, 91452),
- Ethereum { gas: 6, add_stipend: false },
+ Ethereum { gas: 600, add_stipend: false },
),
- Some((6, 3, 0, 1, 2000461760)),
+ Some((600, 300, 0, 120, 2000461760)),
),
(
(
(5_000_000_000, 1_000_000_000, 3000, 2000, 2243, 91452),
- Ethereum { gas: 6, add_stipend: false },
+ Ethereum { gas: 600, add_stipend: false },
),
- Some((6, 20891, 0, 1, 2000503536)),
+ Some((600, 21188, 0, 120, 2000503536)),
),
];
@@ -478,9 +492,9 @@ fn substrate_nesting_works() {
let eth_tx_info = EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof));
let mut transaction_meter =
TransactionMeter::::new(TransactionLimits::EthereumGas {
- eth_gas_limit,
+ eth_gas_limit: eth_gas_limit.div_ceil(gas_scale),
maybe_weight_limit: None,
- eth_tx_info,
+ eth_tx_info: eth_tx_info.clone(),
})
.unwrap();
@@ -498,7 +512,12 @@ fn substrate_nesting_works() {
.charge_weight_token(TestToken(ref_time_charge, proof_size_charge))
.unwrap();
- let nested = transaction_meter.new_nested(&call_resource);
+ let scaled_call_resource = match call_resource {
+ Ethereum { gas, add_stipend } =>
+ Ethereum { gas: (gas as BalanceOf).div_ceil(gas_scale), add_stipend },
+ _ => call_resource,
+ };
+ let nested = transaction_meter.new_nested(&scaled_call_resource);
if let Some((
gas_left,
@@ -509,13 +528,13 @@ fn substrate_nesting_works() {
)) = remaining
{
let nested = nested.unwrap();
- assert_eq!(gas_left, nested.eth_gas_left().unwrap());
+ assert_eq!(gas_left.div_ceil(gas_scale), nested.eth_gas_left().unwrap());
assert_eq!(
Weight::from_parts(ref_time_left, proof_size_left),
nested.weight_left().unwrap()
);
assert_eq!(deposit_left, nested.deposit_left().unwrap());
- assert_eq!(gas_consumed, nested.total_consumed_gas());
+ assert_eq!(gas_consumed.div_ceil(gas_scale), nested.total_consumed_gas());
} else {
assert!(nested.is_err());
}
@@ -527,11 +546,12 @@ fn substrate_nesting_works() {
fn substrate_nesting_charges_works() {
use Charge::{D, W};
+ let gas_scale = ::GasScale::get().into();
let tests = vec![
(
- (5_000_000_000, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000),
+ (5_000_000_000u64, 1_000_000_000, 2_000, 1000, 100, 1000i64, 1000u64),
vec![
- (W(100, 100), Some((800, 400, 3042, 160, 2000007700))),
+ (W(100, 100), Some((800u64, 400, 3042, 160, 2000007700u64))),
(D(100), Some((300, 150, 3042, 60, 2000008200))),
],
),
@@ -575,7 +595,7 @@ fn substrate_nesting_charges_works() {
EthTxInfo::::new(100, Weight::from_parts(extra_ref_time, extra_proof));
let mut transaction_meter =
TransactionMeter::::new(TransactionLimits::EthereumGas {
- eth_gas_limit,
+ eth_gas_limit: eth_gas_limit.div_ceil(gas_scale),
maybe_weight_limit: None,
eth_tx_info,
})
@@ -596,7 +616,10 @@ fn substrate_nesting_charges_works() {
.unwrap();
let mut nested = transaction_meter
- .new_nested(&CallResources::Ethereum { gas: gas_limit, add_stipend: false })
+ .new_nested(&CallResources::Ethereum {
+ gas: gas_limit.div_ceil(gas_scale),
+ add_stipend: false,
+ })
.unwrap();
for (charge, remaining) in charges {
@@ -624,13 +647,13 @@ fn substrate_nesting_charges_works() {
)) = remaining
{
assert!(is_ok);
- assert_eq!(gas_left, nested.eth_gas_left().unwrap());
+ assert_eq!(gas_left.div_ceil(gas_scale), nested.eth_gas_left().unwrap());
assert_eq!(
Weight::from_parts(ref_time_left, proof_size_left),
nested.weight_left().unwrap()
);
assert_eq!(deposit_left, nested.deposit_left().unwrap());
- assert_eq!(gas_consumed, nested.total_consumed_gas());
+ assert_eq!(gas_consumed.div_ceil(gas_scale), nested.total_consumed_gas());
} else {
assert!(!is_ok);
}