From 5b8860fcb32045af4efc95e6e4711ca83c909304 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 31 Jul 2023 22:17:50 +0300 Subject: [PATCH 01/42] A new trait for recording foreign metrics --- primitives/evm/src/lib.rs | 1 + primitives/evm/src/metric.rs | 67 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 primitives/evm/src/metric.rs diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 4a4902ba27..138ae72536 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -20,6 +20,7 @@ mod precompile; mod validation; +mod metric; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; use scale_codec::{Decode, Encode}; diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs new file mode 100644 index 0000000000..e60e6d5252 --- /dev/null +++ b/primitives/evm/src/metric.rs @@ -0,0 +1,67 @@ +use evm::ExitError; +use sp_runtime::{ + traits::{CheckedAdd, Zero}, + Saturating, +}; + +/// A trait for metering different foreign metrics. +pub trait Metric { + fn try_consume(&self, cost: T) -> Result; + fn refund(&mut self, amount: T); + fn record(&mut self, cost: T) -> Result<(), ExitError>; +} + +pub struct BasicMetric { + limit: T, + usage: T, +} + +impl BasicMetric +where + T: Zero, +{ + pub fn new(limit: T) -> Self { + Self { + limit, + usage: Zero::zero(), + } + } +} + +impl Metric for BasicMetric +where + T: CheckedAdd + Saturating + PartialOrd + Copy, +{ + fn try_consume(&self, cost: T) -> Result { + let usage = self.usage.checked_add(&cost).ok_or(ExitError::OutOfGas)?; + if usage > self.limit { + return Err(ExitError::OutOfGas); + } + Ok(usage) + } + + fn refund(&mut self, amount: T) { + self.usage = self.usage.saturating_sub(amount); + } + + fn record(&mut self, cost: T) -> Result<(), ExitError> { + + Ok(()) + } +} + +pub struct StorageMetric(pub BasicMetric); + +impl Metric for StorageMetric { + fn try_consume(&self, cost: u64) -> Result { + self.0.try_consume(cost) + } + + fn refund(&mut self, amount: u64) { + self.0.refund(amount) + } + + fn record(&mut self, cost: u64) -> Result<(), ExitError> { + Ok(()) + } +} From d692104cd7ec780a9e308502a2f46a6bbcab0417 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 2 Aug 2023 13:41:57 +0300 Subject: [PATCH 02/42] Remove Metric trait --- primitives/evm/src/metric.rs | 44 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index e60e6d5252..3a3009404b 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -1,16 +1,9 @@ -use evm::ExitError; +use evm::{gasometer::GasCost, ExitError, Opcode}; use sp_runtime::{ traits::{CheckedAdd, Zero}, Saturating, }; -/// A trait for metering different foreign metrics. -pub trait Metric { - fn try_consume(&self, cost: T) -> Result; - fn refund(&mut self, amount: T); - fn record(&mut self, cost: T) -> Result<(), ExitError>; -} - pub struct BasicMetric { limit: T, usage: T, @@ -28,40 +21,45 @@ where } } -impl Metric for BasicMetric +impl BasicMetric where T: CheckedAdd + Saturating + PartialOrd + Copy, { - fn try_consume(&self, cost: T) -> Result { + fn try_consume(&mut self, cost: T) -> Result<(), ExitError> { let usage = self.usage.checked_add(&cost).ok_or(ExitError::OutOfGas)?; if usage > self.limit { return Err(ExitError::OutOfGas); } - Ok(usage) + self.usage = usage; + Ok(()) } fn refund(&mut self, amount: T) { self.usage = self.usage.saturating_sub(amount); } - fn record(&mut self, cost: T) -> Result<(), ExitError> { - - Ok(()) - } + fn record(&mut self, opcode: Opcode, gas_cost: GasCost) -> Result<(), ExitError> { + Ok(()) + } } -pub struct StorageMetric(pub BasicMetric); - -impl Metric for StorageMetric { - fn try_consume(&self, cost: u64) -> Result { - self.0.try_consume(cost) - } +pub struct StorageMetric(BasicMetric); +impl StorageMetric { fn refund(&mut self, amount: u64) { self.0.refund(amount) } - fn record(&mut self, cost: u64) -> Result<(), ExitError> { - Ok(()) + fn record_dynamic_opcode_cost( + &mut self, + _opcode: Opcode, + gas_cost: GasCost, + ) -> Result<(), ExitError> { + let cost = match gas_cost { + GasCost::Create => 0, + GasCost::Create2 { len } => len.try_into().map_err(|_| ExitError::OutOfGas)?, + _ => return Ok(()), + }; + self.0.try_consume(cost) } } From 523b18c73d0a5ee342562b104fe73ca65b0697d0 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 2 Aug 2023 13:42:55 +0300 Subject: [PATCH 03/42] rename BasicMetric to Metric --- primitives/evm/src/metric.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index 3a3009404b..4a1a095a19 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -4,12 +4,12 @@ use sp_runtime::{ Saturating, }; -pub struct BasicMetric { +pub struct Metric { limit: T, usage: T, } -impl BasicMetric +impl Metric where T: Zero, { @@ -21,7 +21,7 @@ where } } -impl BasicMetric +impl Metric where T: CheckedAdd + Saturating + PartialOrd + Copy, { @@ -43,7 +43,7 @@ where } } -pub struct StorageMetric(BasicMetric); +pub struct StorageMetric(Metric); impl StorageMetric { fn refund(&mut self, amount: u64) { From b168105be6e828a3012a9aa9010254f0884b5c43 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 2 Aug 2023 14:13:34 +0300 Subject: [PATCH 04/42] add unit tests and a new MetricError enum --- primitives/evm/src/metric.rs | 101 +++++++++++++++++++++++++++++++---- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index 4a1a095a19..838142abdd 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -1,9 +1,17 @@ -use evm::{gasometer::GasCost, ExitError, Opcode}; +use evm::{gasometer::GasCost, Opcode}; use sp_runtime::{ traits::{CheckedAdd, Zero}, Saturating, }; +#[derive(Debug, PartialEq)] +/// Metric error. +enum MetricError { + /// The metric usage exceeds the limit. + LimitExceeded, +} + +/// A struct that keeps track of metric usage and limit. pub struct Metric { limit: T, usage: T, @@ -13,6 +21,7 @@ impl Metric where T: Zero, { + /// Creates a new `Metric` instance with the given limit. pub fn new(limit: T) -> Self { Self { limit, @@ -25,41 +34,111 @@ impl Metric where T: CheckedAdd + Saturating + PartialOrd + Copy, { - fn try_consume(&mut self, cost: T) -> Result<(), ExitError> { - let usage = self.usage.checked_add(&cost).ok_or(ExitError::OutOfGas)?; + /// Records the cost of an operation and updates the usage. + /// + /// # Errors + /// + /// Returns `MetricError::LimitExceeded` if the metric usage exceeds the limit. + fn record_cost(&mut self, cost: T) -> Result<(), MetricError> { + let usage = self + .usage + .checked_add(&cost) + .ok_or(MetricError::LimitExceeded)?; if usage > self.limit { - return Err(ExitError::OutOfGas); + return Err(MetricError::LimitExceeded); } self.usage = usage; Ok(()) } + /// Refunds the given amount. fn refund(&mut self, amount: T) { self.usage = self.usage.saturating_sub(amount); } - - fn record(&mut self, opcode: Opcode, gas_cost: GasCost) -> Result<(), ExitError> { - Ok(()) - } } +/// A struct that keeps track of new storage usage and limit. pub struct StorageMetric(Metric); impl StorageMetric { + /// Creates a new `StorageMetric` instance with the given limit. + pub fn new(limit: u64) -> Self { + Self(Metric::new(limit)) + } + + /// Refunds the given amount of storage gas. fn refund(&mut self, amount: u64) { self.0.refund(amount) } + /// Records the dynamic opcode cost and updates the storage usage. + /// + /// # Errors + /// + /// Returns `MetricError::LimitExceeded` if the storage gas usage exceeds the storage gas limit. fn record_dynamic_opcode_cost( &mut self, _opcode: Opcode, gas_cost: GasCost, - ) -> Result<(), ExitError> { + ) -> Result<(), MetricError> { let cost = match gas_cost { GasCost::Create => 0, - GasCost::Create2 { len } => len.try_into().map_err(|_| ExitError::OutOfGas)?, + GasCost::Create2 { len } => len.try_into().map_err(|_| MetricError::LimitExceeded)?, _ => return Ok(()), }; - self.0.try_consume(cost) + self.0.record_cost(cost) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init() { + let metric = Metric::::new(100); + assert_eq!(metric.limit, 100); + assert_eq!(metric.usage, 0); + } + + #[test] + fn test_try_consume() { + let mut metric = Metric::::new(100); + assert_eq!(metric.record_cost(10), Ok(())); + assert_eq!(metric.usage, 10); + assert_eq!(metric.record_cost(90), Ok(())); + assert_eq!(metric.usage, 100); + assert_eq!(metric.record_cost(1), Err(MetricError::LimitExceeded)); + assert_eq!(metric.usage, 100); + } + + #[test] + fn test_refund() { + let mut metric = Metric::::new(100); + assert_eq!(metric.record_cost(10), Ok(())); + assert_eq!(metric.usage, 10); + metric.refund(10); + assert_eq!(metric.usage, 0); + + // refund more than usage + metric.refund(10); + assert_eq!(metric.usage, 0); + } + + #[test] + fn test_storage_metric() { + let mut metric = StorageMetric::new(100); + assert_eq!(metric.0.usage, 0); + assert_eq!(metric.0.limit, 100); + assert_eq!(metric.0.record_cost(10), Ok(())); + assert_eq!(metric.0.usage, 10); + assert_eq!(metric.0.record_cost(90), Ok(())); + assert_eq!(metric.0.usage, 100); + assert_eq!(metric.0.record_cost(1), Err(MetricError::LimitExceeded)); + assert_eq!(metric.0.usage, 100); + metric.0.refund(10); + assert_eq!(metric.0.usage, 90); + metric.refund(10); + assert_eq!(metric.0.usage, 80); } } From ce75d154b6c98254816e92fff2242b282ad09eee Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 3 Aug 2023 09:20:17 +0300 Subject: [PATCH 05/42] Add ProofSizeMeter --- primitives/evm/src/metric.rs | 107 +++++++++++++++++++++++++---------- 1 file changed, 76 insertions(+), 31 deletions(-) diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index 838142abdd..15f8b43e03 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -1,14 +1,21 @@ -use evm::{gasometer::GasCost, Opcode}; -use sp_runtime::{ - traits::{CheckedAdd, Zero}, - Saturating, -}; +use evm::{gasometer::GasCost, ExitError, Opcode}; +use sp_runtime::{traits::CheckedAdd, Saturating}; #[derive(Debug, PartialEq)] /// Metric error. -enum MetricError { +pub enum MetricError { /// The metric usage exceeds the limit. LimitExceeded, + /// Invalid Base Cost. + InvalidBaseCost, +} + +impl Into for MetricError { + fn into(self) -> ExitError { + match self { + MetricError::LimitExceeded | MetricError::InvalidBaseCost => ExitError::OutOfGas, + } + } } /// A struct that keeps track of metric usage and limit. @@ -19,21 +26,23 @@ pub struct Metric { impl Metric where - T: Zero, + T: CheckedAdd + Saturating + PartialOrd + Copy, { - /// Creates a new `Metric` instance with the given limit. - pub fn new(limit: T) -> Self { - Self { - limit, - usage: Zero::zero(), + /// Creates a new `Metric` instance with the given base cost and limit. + /// + /// # Errors + /// + /// Returns `MetricError::InvalidBaseCost` if the base cost is greater than the limit. + pub fn new(base_cost: T, limit: T) -> Result { + if base_cost > limit { + return Err(MetricError::InvalidBaseCost); } + Ok(Self { + limit, + usage: base_cost, + }) } -} -impl Metric -where - T: CheckedAdd + Saturating + PartialOrd + Copy, -{ /// Records the cost of an operation and updates the usage. /// /// # Errors @@ -44,6 +53,7 @@ where .usage .checked_add(&cost) .ok_or(MetricError::LimitExceeded)?; + if usage > self.limit { return Err(MetricError::LimitExceeded); } @@ -57,16 +67,35 @@ where } } -/// A struct that keeps track of new storage usage and limit. -pub struct StorageMetric(Metric); +/// A struct that keeps track of the size of the proof. +pub struct ProofSizeMeter(Metric); + +impl ProofSizeMeter { + /// Creates a new `ProofSizeMetric` instance with the given limit. + pub fn new(base_cost: u64, limit: u64) -> Result { + Ok(Self(Metric::new(base_cost, limit)?)) + } -impl StorageMetric { + /// Records the size of the proof and updates the usage. + /// + /// # Errors + /// + /// Returns `MetricError::LimitExceeded` if the proof size exceeds the limit. + fn record_proof_size(&mut self, size: u64) -> Result<(), MetricError> { + self.0.record_cost(size) + } +} + +/// A struct that keeps track of storage usage (newly created storage) and limit. +pub struct StorageMeter(Metric); + +impl StorageMeter { /// Creates a new `StorageMetric` instance with the given limit. - pub fn new(limit: u64) -> Self { - Self(Metric::new(limit)) + pub fn new(limit: u64) -> Result { + Ok(Self(Metric::new(0, limit)?)) } - /// Refunds the given amount of storage gas. + /// Refunds the given amount of storage. fn refund(&mut self, amount: u64) { self.0.refund(amount) } @@ -75,15 +104,25 @@ impl StorageMetric { /// /// # Errors /// - /// Returns `MetricError::LimitExceeded` if the storage gas usage exceeds the storage gas limit. + /// Returns `MetricError::LimitExceeded` if the storage usage exceeds the storage limit. fn record_dynamic_opcode_cost( &mut self, _opcode: Opcode, gas_cost: GasCost, ) -> Result<(), MetricError> { let cost = match gas_cost { - GasCost::Create => 0, - GasCost::Create2 { len } => len.try_into().map_err(|_| MetricError::LimitExceeded)?, + GasCost::Create => { + // TODO record cost for create + 0 + } + GasCost::Create2 { len } => { + // len in bytes ?? + len.try_into().map_err(|_| MetricError::LimitExceeded)? + } + GasCost::SStore { .. } => { + // TODO record cost for sstore + 0 + } _ => return Ok(()), }; self.0.record_cost(cost) @@ -96,25 +135,31 @@ mod tests { #[test] fn test_init() { - let metric = Metric::::new(100); + let metric = Metric::::new(0, 100).unwrap(); assert_eq!(metric.limit, 100); assert_eq!(metric.usage, 0); + + // base cost > limit + let metric = Metric::::new(100, 0).err(); + assert_eq!(metric, Some(MetricError::InvalidBaseCost)); } #[test] - fn test_try_consume() { - let mut metric = Metric::::new(100); + fn test_record_cost() { + let mut metric = Metric::::new(0, 100).unwrap(); assert_eq!(metric.record_cost(10), Ok(())); assert_eq!(metric.usage, 10); assert_eq!(metric.record_cost(90), Ok(())); assert_eq!(metric.usage, 100); + + // exceed limit assert_eq!(metric.record_cost(1), Err(MetricError::LimitExceeded)); assert_eq!(metric.usage, 100); } #[test] fn test_refund() { - let mut metric = Metric::::new(100); + let mut metric = Metric::::new(0, 100).unwrap(); assert_eq!(metric.record_cost(10), Ok(())); assert_eq!(metric.usage, 10); metric.refund(10); @@ -127,7 +172,7 @@ mod tests { #[test] fn test_storage_metric() { - let mut metric = StorageMetric::new(100); + let mut metric = StorageMeter::new(100).unwrap(); assert_eq!(metric.0.usage, 0); assert_eq!(metric.0.limit, 100); assert_eq!(metric.0.record_cost(10), Ok(())); From 26a85ad0cf514ec753d2b6ab711394105a2ae5f9 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 3 Aug 2023 13:30:04 +0300 Subject: [PATCH 06/42] Add ProofSizeMeter to WeightInfo --- frame/ethereum/src/lib.rs | 4 +-- frame/evm/src/lib.rs | 12 ++++---- frame/evm/src/runner/stack.rs | 7 ++--- frame/evm/src/tests.rs | 25 ++++++----------- primitives/evm/src/lib.rs | 40 ++++++++++++++------------- primitives/evm/src/metric.rs | 52 +++++++++++++++++++++++++++-------- 6 files changed, 81 insertions(+), 59 deletions(-) diff --git a/frame/ethereum/src/lib.rs b/frame/ethereum/src/lib.rs index 56eb6b3fd4..f5d4740c30 100644 --- a/frame/ethereum/src/lib.rs +++ b/frame/ethereum/src/lib.rs @@ -690,8 +690,8 @@ impl Pallet { true, ); if let Some(weight_info) = weight_info { - if let Some(proof_size_usage) = weight_info.proof_size_usage { - *gas_to_weight.proof_size_mut() = proof_size_usage; + if let Some(proof_size_meter) = weight_info.proof_size_meter { + *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); } } Some(gas_to_weight) diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index fb3a0388f8..569642068c 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -268,8 +268,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_usage) = weight_info.proof_size_usage { - *gas_to_weight.proof_size_mut() = proof_size_usage; + if let Some(proof_size_meter) = weight_info.proof_size_meter { + *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); } } Some(gas_to_weight) @@ -355,8 +355,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_usage) = weight_info.proof_size_usage { - *gas_to_weight.proof_size_mut() = proof_size_usage; + if let Some(proof_size_meter) = weight_info.proof_size_meter { + *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); } } Some(gas_to_weight) @@ -443,8 +443,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_usage) = weight_info.proof_size_usage { - *gas_to_weight.proof_size_mut() = proof_size_usage; + if let Some(proof_size_meter) = weight_info.proof_size_meter { + *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); } } Some(gas_to_weight) diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 7af212593a..9eeece6297 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -254,8 +254,7 @@ where Some(weight_info) => U256::from(sp_std::cmp::max( used_gas, weight_info - .proof_size_usage - .unwrap_or_default() + .proof_size_usage() .saturating_mul(T::GasLimitPovSizeRatio::get()), )), _ => used_gas.into(), @@ -1057,8 +1056,8 @@ where // Record proof_size // Return if proof size recording is disabled - let proof_size_limit = if let Some(proof_size_limit) = weight_info.proof_size_limit { - proof_size_limit + let proof_size_limit = if let Some(proof_size_meter) = weight_info.proof_size_meter { + proof_size_meter.limit() } else { return Ok(()); }; diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index bb1850708c..4e3d5a7416 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -184,8 +184,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -245,8 +244,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -302,8 +300,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -351,8 +348,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -401,8 +397,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -458,9 +453,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); - + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); } @@ -528,8 +521,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -615,8 +607,7 @@ mod proof_size_test { let actual_proof_size = result .weight_info .expect("weight info") - .proof_size_usage - .expect("proof size usage"); + .proof_size_usage(); assert_eq!(used_gas.standard, U256::from(21_000)); assert_eq!(used_gas.effective, U256::from(actual_proof_size * ratio)); diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 138ae72536..9e2765ff77 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -18,11 +18,12 @@ #![cfg_attr(not(feature = "std"), no_std)] #![deny(unused_crate_dependencies)] +mod metric; mod precompile; mod validation; -mod metric; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; +use metric::ProofSizeMeter; use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] @@ -78,9 +79,8 @@ pub enum AccessedStorage { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightInfo { pub ref_time_limit: Option, - pub proof_size_limit: Option, pub ref_time_usage: Option, - pub proof_size_usage: Option, + pub proof_size_meter: Option, } impl WeightInfo { @@ -95,16 +95,17 @@ impl WeightInfo { { Some(WeightInfo { ref_time_limit: Some(weight_limit.ref_time()), - proof_size_limit: Some(weight_limit.proof_size()), ref_time_usage: Some(0u64), - proof_size_usage: Some(proof_size_base_cost), + proof_size_meter: Some( + ProofSizeMeter::new(proof_size_base_cost, weight_limit.proof_size()) + .map_err(|_| "invalid proof size base cost")?, + ), }) } (Some(weight_limit), None) => Some(WeightInfo { ref_time_limit: Some(weight_limit.ref_time()), - proof_size_limit: None, ref_time_usage: Some(0u64), - proof_size_usage: None, + proof_size_meter: None, }), _ => return Err("must provide Some valid weight limit or None"), }) @@ -129,22 +130,23 @@ impl WeightInfo { Ok(()) } pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let (Some(proof_size_usage), Some(proof_size_limit)) = - (self.proof_size_usage, self.proof_size_limit) - { - let proof_size_usage = self.try_consume(cost, proof_size_limit, proof_size_usage)?; - if proof_size_usage > proof_size_limit { - return Err(ExitError::OutOfGas); - } - self.proof_size_usage = Some(proof_size_usage); + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter + .record_proof_size(cost) + .map_err(|_| ExitError::OutOfGas)?; } + Ok(()) } pub fn refund_proof_size(&mut self, amount: u64) { - if let Some(proof_size_usage) = self.proof_size_usage { - let proof_size_usage = proof_size_usage.saturating_sub(amount); - self.proof_size_usage = Some(proof_size_usage); - } + self.proof_size_meter.as_mut().map(|proof_size_meter| { + proof_size_meter.refund(amount); + }); + } + + pub fn proof_size_usage(&self) -> u64 { + self.proof_size_meter + .map_or(0, |proof_size_meter| proof_size_meter.usage()) } pub fn refund_ref_time(&mut self, amount: u64) { if let Some(ref_time_usage) = self.ref_time_usage { diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index 15f8b43e03..885e1df9b1 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -1,6 +1,13 @@ -use evm::{gasometer::GasCost, ExitError, Opcode}; +use evm::{gasometer::GasCost, Opcode}; +use scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; +use sp_core::U256; use sp_runtime::{traits::CheckedAdd, Saturating}; +use crate::AccessedStorage; + #[derive(Debug, PartialEq)] /// Metric error. pub enum MetricError { @@ -10,14 +17,8 @@ pub enum MetricError { InvalidBaseCost, } -impl Into for MetricError { - fn into(self) -> ExitError { - match self { - MetricError::LimitExceeded | MetricError::InvalidBaseCost => ExitError::OutOfGas, - } - } -} - +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] /// A struct that keeps track of metric usage and limit. pub struct Metric { limit: T, @@ -65,12 +66,26 @@ where fn refund(&mut self, amount: T) { self.usage = self.usage.saturating_sub(amount); } + + /// Returns the usage. + fn usage(&self) -> T { + self.usage + } } -/// A struct that keeps track of the size of the proof. +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +/// A struct that keeps track of the proof size and limit. pub struct ProofSizeMeter(Metric); impl ProofSizeMeter { + /// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) + pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; + /// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). + pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; + /// Account basic proof size + 5 bytes max of `decode_len` call. + pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; + /// Creates a new `ProofSizeMetric` instance with the given limit. pub fn new(base_cost: u64, limit: u64) -> Result { Ok(Self(Metric::new(base_cost, limit)?)) @@ -81,9 +96,24 @@ impl ProofSizeMeter { /// # Errors /// /// Returns `MetricError::LimitExceeded` if the proof size exceeds the limit. - fn record_proof_size(&mut self, size: u64) -> Result<(), MetricError> { + pub fn record_proof_size(&mut self, size: u64) -> Result<(), MetricError> { self.0.record_cost(size) } + + /// Refunds the given amount of proof size. + pub fn refund(&mut self, amount: u64) { + self.0.refund(amount) + } + + /// Returns the proof size usage. + pub fn usage(&self) -> u64 { + self.0.usage() + } + + /// Returns the proof size limit. + pub fn limit(&self) -> u64 { + self.0.limit + } } /// A struct that keeps track of storage usage (newly created storage) and limit. From 78273fbe5608091d4930fff5397e7ca395a2b36d Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 3 Aug 2023 13:34:13 +0300 Subject: [PATCH 07/42] formatting --- frame/evm/src/tests.rs | 40 ++++++++-------------------------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 4e3d5a7416..b1c5de19c0 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -181,10 +181,7 @@ mod proof_size_test { let nonce_increases = ACCOUNT_BASIC_PROOF_SIZE * 2; let expected_proof_size = write_cost + is_empty_check + nonce_increases; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -241,10 +238,7 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -297,10 +291,7 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -345,10 +336,7 @@ mod proof_size_test { + IS_EMPTY_CHECK_PROOF_SIZE + (ACCOUNT_BASIC_PROOF_SIZE * 2); - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -394,10 +382,7 @@ mod proof_size_test { + IS_EMPTY_CHECK_PROOF_SIZE + (ACCOUNT_BASIC_PROOF_SIZE * 2); - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -450,10 +435,7 @@ mod proof_size_test { let expected_proof_size = overhead + (number_balance_reads * ACCOUNT_BASIC_PROOF_SIZE) as u64; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); } @@ -518,10 +500,7 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -604,10 +583,7 @@ mod proof_size_test { let ratio = <::GasLimitPovSizeRatio as Get>::get(); let used_gas = result.used_gas; - let actual_proof_size = result - .weight_info - .expect("weight info") - .proof_size_usage(); + let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); assert_eq!(used_gas.standard, U256::from(21_000)); assert_eq!(used_gas.effective, U256::from(actual_proof_size * ratio)); From df4f5cf54f41558f2a07c5a0a09c8d5156e0444c Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 3 Aug 2023 14:41:30 +0300 Subject: [PATCH 08/42] Add RefTimeMeter to WeightInfo --- primitives/evm/src/lib.rs | 47 ++++++++++++++++-------------------- primitives/evm/src/metric.rs | 26 +++++++++++++++++--- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 9e2765ff77..1f4a7722bb 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -23,7 +23,7 @@ mod precompile; mod validation; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; -use metric::ProofSizeMeter; +use metric::{ProofSizeMeter, RefTimeMeter}; use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] @@ -78,8 +78,7 @@ pub enum AccessedStorage { #[derive(Clone, Copy, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightInfo { - pub ref_time_limit: Option, - pub ref_time_usage: Option, + pub ref_time_meter: Option, pub proof_size_meter: Option, } @@ -94,8 +93,10 @@ impl WeightInfo { if weight_limit.proof_size() >= proof_size_base_cost => { Some(WeightInfo { - ref_time_limit: Some(weight_limit.ref_time()), - ref_time_usage: Some(0u64), + ref_time_meter: Some( + RefTimeMeter::new(weight_limit.ref_time()) + .map_err(|_| "invalid ref time base cost")?, + ), proof_size_meter: Some( ProofSizeMeter::new(proof_size_base_cost, weight_limit.proof_size()) .map_err(|_| "invalid proof size base cost")?, @@ -103,30 +104,23 @@ impl WeightInfo { }) } (Some(weight_limit), None) => Some(WeightInfo { - ref_time_limit: Some(weight_limit.ref_time()), - ref_time_usage: Some(0u64), + ref_time_meter: Some( + RefTimeMeter::new(weight_limit.ref_time()) + .map_err(|_| "invalid ref time base cost")?, + ), proof_size_meter: None, }), _ => return Err("must provide Some valid weight limit or None"), }) } - fn try_consume(&self, cost: u64, limit: u64, usage: u64) -> Result { - let usage = usage.checked_add(cost).ok_or(ExitError::OutOfGas)?; - if usage > limit { - return Err(ExitError::OutOfGas); - } - Ok(usage) - } + pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let (Some(ref_time_usage), Some(ref_time_limit)) = - (self.ref_time_usage, self.ref_time_limit) - { - let ref_time_usage = self.try_consume(cost, ref_time_limit, ref_time_usage)?; - if ref_time_usage > ref_time_limit { - return Err(ExitError::OutOfGas); - } - self.ref_time_usage = Some(ref_time_usage); + if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { + ref_time_meter + .record_ref_time(cost) + .map_err(|_| ExitError::OutOfGas)?; } + Ok(()) } pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { @@ -138,6 +132,7 @@ impl WeightInfo { Ok(()) } + pub fn refund_proof_size(&mut self, amount: u64) { self.proof_size_meter.as_mut().map(|proof_size_meter| { proof_size_meter.refund(amount); @@ -148,11 +143,11 @@ impl WeightInfo { self.proof_size_meter .map_or(0, |proof_size_meter| proof_size_meter.usage()) } + pub fn refund_ref_time(&mut self, amount: u64) { - if let Some(ref_time_usage) = self.ref_time_usage { - let ref_time_usage = ref_time_usage.saturating_sub(amount); - self.ref_time_usage = Some(ref_time_usage); - } + self.ref_time_meter.as_mut().map(|ref_time_meter| { + ref_time_meter.refund(amount); + }); } } diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index 885e1df9b1..fd3c36ed15 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -3,11 +3,8 @@ use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -use sp_core::U256; use sp_runtime::{traits::CheckedAdd, Saturating}; -use crate::AccessedStorage; - #[derive(Debug, PartialEq)] /// Metric error. pub enum MetricError { @@ -73,9 +70,9 @@ where } } +/// A struct that keeps track of the proof size and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// A struct that keeps track of the proof size and limit. pub struct ProofSizeMeter(Metric); impl ProofSizeMeter { @@ -116,6 +113,27 @@ impl ProofSizeMeter { } } +/// A struct that keeps track of the ref_time usage and limit. +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct RefTimeMeter(Metric); + +impl RefTimeMeter { + /// Creates a new `RefTimeMetric` instance with the given limit. + pub fn new(limit: u64) -> Result { + Ok(Self(Metric::new(0, limit)?)) + } + + /// Records the ref_time and updates the usage. + pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), MetricError> { + self.0.record_cost(ref_time) + } + + /// Refunds the given amount of ref_time. + pub fn refund(&mut self, amount: u64) { + self.0.refund(amount) + } +} /// A struct that keeps track of storage usage (newly created storage) and limit. pub struct StorageMeter(Metric); From 1f86da7f8e668a91a3f462862e2e34696ef6e149 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 4 Aug 2023 12:25:09 +0300 Subject: [PATCH 09/42] add storage meter to WeightInfo --- frame/evm/src/runner/stack.rs | 51 ++++++++++++++++++++++++++------ primitives/evm/src/lib.rs | 55 ++++++++++++++++------------------- primitives/evm/src/metric.rs | 20 ++++++++----- 3 files changed, 79 insertions(+), 47 deletions(-) diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 9eeece6297..055505348c 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -153,13 +153,46 @@ where R: Default, { // Used to record the external costs in the evm through the StackState implementation - let maybe_weight_info = - WeightInfo::new_from_weight_limit(weight_limit, proof_size_base_cost).map_err( - |_| RunnerError { - error: Error::::Undefined, - weight, - }, - )?; + + let mut weight_info = WeightInfo::new(); + match weight_limit { + Some(weight_limit) => { + weight_info + .add_ref_time_meter(weight_limit.ref_time()) + .map_err(|_| RunnerError { + error: Error::::Undefined, + weight, + })?; + if let Some(proof_size_base_cost) = proof_size_base_cost { + weight_info + .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) + .map_err(|_| RunnerError { + error: Error::::Undefined, + weight, + })?; + } + } + None => (), + } + + // TODO Compute the limit of storage per tx + let storage_limit = 0; + weight_info.add_storage_meter(storage_limit).map_err(|_| RunnerError { + error: Error::::Undefined, + weight, + })?; + + let weight_info = if weight_info.ref_time_meter.is_none() + && weight_info.proof_size_meter.is_none() + && weight_info.storage_meter.is_none() + { + None + } + else { + Some(weight_info) + }; + + // The precompile check is only used for transactional invocations. However, here we always // execute the check, because the check has side effects. match precompiles.is_precompile(source, gas_limit) { @@ -174,7 +207,7 @@ where standard: gas_limit.into(), effective: gas_limit.into(), }, - weight_info: maybe_weight_info, + weight_info, logs: Default::default(), }) } @@ -243,7 +276,7 @@ where }; let metadata = StackSubstateMetadata::new(gas_limit, config); - let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info); + let state = SubstrateStackState::new(&vicinity, metadata, weight_info); let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); let (reason, retv) = f(&mut executor); diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 1f4a7722bb..44a96bd6ec 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -23,7 +23,7 @@ mod precompile; mod validation; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; -use metric::{ProofSizeMeter, RefTimeMeter}; +use metric::{ProofSizeMeter, RefTimeMeter, StorageMeter}; use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] @@ -80,38 +80,33 @@ pub enum AccessedStorage { pub struct WeightInfo { pub ref_time_meter: Option, pub proof_size_meter: Option, + pub storage_meter: Option, } impl WeightInfo { - pub fn new_from_weight_limit( - weight_limit: Option, - proof_size_base_cost: Option, - ) -> Result, &'static str> { - Ok(match (weight_limit, proof_size_base_cost) { - (None, _) => None, - (Some(weight_limit), Some(proof_size_base_cost)) - if weight_limit.proof_size() >= proof_size_base_cost => - { - Some(WeightInfo { - ref_time_meter: Some( - RefTimeMeter::new(weight_limit.ref_time()) - .map_err(|_| "invalid ref time base cost")?, - ), - proof_size_meter: Some( - ProofSizeMeter::new(proof_size_base_cost, weight_limit.proof_size()) - .map_err(|_| "invalid proof size base cost")?, - ), - }) - } - (Some(weight_limit), None) => Some(WeightInfo { - ref_time_meter: Some( - RefTimeMeter::new(weight_limit.ref_time()) - .map_err(|_| "invalid ref time base cost")?, - ), - proof_size_meter: None, - }), - _ => return Err("must provide Some valid weight limit or None"), - }) + pub fn new() -> Self { + Self { + ref_time_meter: None, + proof_size_meter: None, + storage_meter: None, + } + } + + pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); + Ok(()) + } + + pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { + self.proof_size_meter = + Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); + Ok(()) + } + + pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.storage_meter = + Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); + Ok(()) } pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/metric.rs index fd3c36ed15..03c590f6e5 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/metric.rs @@ -14,9 +14,9 @@ pub enum MetricError { InvalidBaseCost, } +/// A struct that keeps track of metric usage and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -/// A struct that keeps track of metric usage and limit. pub struct Metric { limit: T, usage: T, @@ -76,13 +76,6 @@ where pub struct ProofSizeMeter(Metric); impl ProofSizeMeter { - /// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) - pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; - /// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). - pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; - /// Account basic proof size + 5 bytes max of `decode_len` call. - pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; - /// Creates a new `ProofSizeMetric` instance with the given limit. pub fn new(base_cost: u64, limit: u64) -> Result { Ok(Self(Metric::new(base_cost, limit)?)) @@ -135,6 +128,8 @@ impl RefTimeMeter { } } /// A struct that keeps track of storage usage (newly created storage) and limit. +#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct StorageMeter(Metric); impl StorageMeter { @@ -175,6 +170,15 @@ impl StorageMeter { }; self.0.record_cost(cost) } + + fn record_external_operation(&mut self, operation: evm::ExternalOperation) { + match operation { + evm::ExternalOperation::Write => { + // Todo record cost for write + } + _ => {} + } + } } #[cfg(test)] From 230879a436623cb5833d2b6433b3eb53e1a8f1c2 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 4 Aug 2023 14:17:52 +0300 Subject: [PATCH 10/42] rename metric to resource --- primitives/evm/src/lib.rs | 4 +- primitives/evm/src/{metric.rs => resource.rs} | 130 +++++++++--------- 2 files changed, 67 insertions(+), 67 deletions(-) rename primitives/evm/src/{metric.rs => resource.rs} (52%) diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 44a96bd6ec..4265a3b328 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -18,12 +18,12 @@ #![cfg_attr(not(feature = "std"), no_std)] #![deny(unused_crate_dependencies)] -mod metric; +mod resource; mod precompile; mod validation; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; -use metric::{ProofSizeMeter, RefTimeMeter, StorageMeter}; +use resource::{ProofSizeMeter, RefTimeMeter, StorageMeter}; use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] diff --git a/primitives/evm/src/metric.rs b/primitives/evm/src/resource.rs similarity index 52% rename from primitives/evm/src/metric.rs rename to primitives/evm/src/resource.rs index 03c590f6e5..4d80f60b08 100644 --- a/primitives/evm/src/metric.rs +++ b/primitives/evm/src/resource.rs @@ -6,34 +6,34 @@ use serde::{Deserialize, Serialize}; use sp_runtime::{traits::CheckedAdd, Saturating}; #[derive(Debug, PartialEq)] -/// Metric error. -pub enum MetricError { - /// The metric usage exceeds the limit. +/// Resource error. +pub enum ResourceError { + /// The Resource usage exceeds the limit. LimitExceeded, /// Invalid Base Cost. InvalidBaseCost, } -/// A struct that keeps track of metric usage and limit. +/// A struct that keeps track of resource usage and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Metric { +pub struct Resource { limit: T, usage: T, } -impl Metric +impl Resource where T: CheckedAdd + Saturating + PartialOrd + Copy, { - /// Creates a new `Metric` instance with the given base cost and limit. + /// Creates a new `Resource` instance with the given base cost and limit. /// /// # Errors /// - /// Returns `MetricError::InvalidBaseCost` if the base cost is greater than the limit. - pub fn new(base_cost: T, limit: T) -> Result { + /// Returns `ResourceError::InvalidBaseCost` if the base cost is greater than the limit. + pub fn new(base_cost: T, limit: T) -> Result { if base_cost > limit { - return Err(MetricError::InvalidBaseCost); + return Err(ResourceError::InvalidBaseCost); } Ok(Self { limit, @@ -45,15 +45,15 @@ where /// /// # Errors /// - /// Returns `MetricError::LimitExceeded` if the metric usage exceeds the limit. - fn record_cost(&mut self, cost: T) -> Result<(), MetricError> { + /// Returns `ResourceError::LimitExceeded` if the Resource usage exceeds the limit. + fn record_cost(&mut self, cost: T) -> Result<(), ResourceError> { let usage = self .usage .checked_add(&cost) - .ok_or(MetricError::LimitExceeded)?; + .ok_or(ResourceError::LimitExceeded)?; if usage > self.limit { - return Err(MetricError::LimitExceeded); + return Err(ResourceError::LimitExceeded); } self.usage = usage; Ok(()) @@ -73,20 +73,20 @@ where /// A struct that keeps track of the proof size and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct ProofSizeMeter(Metric); +pub struct ProofSizeMeter(Resource); impl ProofSizeMeter { - /// Creates a new `ProofSizeMetric` instance with the given limit. - pub fn new(base_cost: u64, limit: u64) -> Result { - Ok(Self(Metric::new(base_cost, limit)?)) + /// Creates a new `ProofSizeResource` instance with the given limit. + pub fn new(base_cost: u64, limit: u64) -> Result { + Ok(Self(Resource::new(base_cost, limit)?)) } /// Records the size of the proof and updates the usage. /// /// # Errors /// - /// Returns `MetricError::LimitExceeded` if the proof size exceeds the limit. - pub fn record_proof_size(&mut self, size: u64) -> Result<(), MetricError> { + /// Returns `ResourceError::LimitExceeded` if the proof size exceeds the limit. + pub fn record_proof_size(&mut self, size: u64) -> Result<(), ResourceError> { self.0.record_cost(size) } @@ -109,16 +109,16 @@ impl ProofSizeMeter { /// A struct that keeps track of the ref_time usage and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct RefTimeMeter(Metric); +pub struct RefTimeMeter(Resource); impl RefTimeMeter { - /// Creates a new `RefTimeMetric` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Metric::new(0, limit)?)) + /// Creates a new `RefTimeResource` instance with the given limit. + pub fn new(limit: u64) -> Result { + Ok(Self(Resource::new(0, limit)?)) } /// Records the ref_time and updates the usage. - pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), MetricError> { + pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), ResourceError> { self.0.record_cost(ref_time) } @@ -130,12 +130,12 @@ impl RefTimeMeter { /// A struct that keeps track of storage usage (newly created storage) and limit. #[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct StorageMeter(Metric); +pub struct StorageMeter(Resource); impl StorageMeter { - /// Creates a new `StorageMetric` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Metric::new(0, limit)?)) + /// Creates a new `StorageResource` instance with the given limit. + pub fn new(limit: u64) -> Result { + Ok(Self(Resource::new(0, limit)?)) } /// Refunds the given amount of storage. @@ -147,12 +147,12 @@ impl StorageMeter { /// /// # Errors /// - /// Returns `MetricError::LimitExceeded` if the storage usage exceeds the storage limit. + /// Returns `ResourceError::LimitExceeded` if the storage usage exceeds the storage limit. fn record_dynamic_opcode_cost( &mut self, _opcode: Opcode, gas_cost: GasCost, - ) -> Result<(), MetricError> { + ) -> Result<(), ResourceError> { let cost = match gas_cost { GasCost::Create => { // TODO record cost for create @@ -160,7 +160,7 @@ impl StorageMeter { } GasCost::Create2 { len } => { // len in bytes ?? - len.try_into().map_err(|_| MetricError::LimitExceeded)? + len.try_into().map_err(|_| ResourceError::LimitExceeded)? } GasCost::SStore { .. } => { // TODO record cost for sstore @@ -187,55 +187,55 @@ mod tests { #[test] fn test_init() { - let metric = Metric::::new(0, 100).unwrap(); - assert_eq!(metric.limit, 100); - assert_eq!(metric.usage, 0); + let resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.limit, 100); + assert_eq!(resource.usage, 0); // base cost > limit - let metric = Metric::::new(100, 0).err(); - assert_eq!(metric, Some(MetricError::InvalidBaseCost)); + let resource = Resource::::new(100, 0).err(); + assert_eq!(resource, Some(ResourceError::InvalidBaseCost)); } #[test] fn test_record_cost() { - let mut metric = Metric::::new(0, 100).unwrap(); - assert_eq!(metric.record_cost(10), Ok(())); - assert_eq!(metric.usage, 10); - assert_eq!(metric.record_cost(90), Ok(())); - assert_eq!(metric.usage, 100); + let mut resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.record_cost(10), Ok(())); + assert_eq!(resource.usage, 10); + assert_eq!(resource.record_cost(90), Ok(())); + assert_eq!(resource.usage, 100); // exceed limit - assert_eq!(metric.record_cost(1), Err(MetricError::LimitExceeded)); - assert_eq!(metric.usage, 100); + assert_eq!(resource.record_cost(1), Err(ResourceError::LimitExceeded)); + assert_eq!(resource.usage, 100); } #[test] fn test_refund() { - let mut metric = Metric::::new(0, 100).unwrap(); - assert_eq!(metric.record_cost(10), Ok(())); - assert_eq!(metric.usage, 10); - metric.refund(10); - assert_eq!(metric.usage, 0); + let mut resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.record_cost(10), Ok(())); + assert_eq!(resource.usage, 10); + resource.refund(10); + assert_eq!(resource.usage, 0); // refund more than usage - metric.refund(10); - assert_eq!(metric.usage, 0); + resource.refund(10); + assert_eq!(resource.usage, 0); } #[test] - fn test_storage_metric() { - let mut metric = StorageMeter::new(100).unwrap(); - assert_eq!(metric.0.usage, 0); - assert_eq!(metric.0.limit, 100); - assert_eq!(metric.0.record_cost(10), Ok(())); - assert_eq!(metric.0.usage, 10); - assert_eq!(metric.0.record_cost(90), Ok(())); - assert_eq!(metric.0.usage, 100); - assert_eq!(metric.0.record_cost(1), Err(MetricError::LimitExceeded)); - assert_eq!(metric.0.usage, 100); - metric.0.refund(10); - assert_eq!(metric.0.usage, 90); - metric.refund(10); - assert_eq!(metric.0.usage, 80); + fn test_storage_resource() { + let mut resource = StorageMeter::new(100).unwrap(); + assert_eq!(resource.0.usage, 0); + assert_eq!(resource.0.limit, 100); + assert_eq!(resource.0.record_cost(10), Ok(())); + assert_eq!(resource.0.usage, 10); + assert_eq!(resource.0.record_cost(90), Ok(())); + assert_eq!(resource.0.usage, 100); + assert_eq!(resource.0.record_cost(1), Err(ResourceError::LimitExceeded)); + assert_eq!(resource.0.usage, 100); + resource.0.refund(10); + assert_eq!(resource.0.usage, 90); + resource.refund(10); + assert_eq!(resource.0.usage, 80); } } From 54b7c01c5cb205c173ef023704bb46edfb61b6b9 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 7 Aug 2023 18:22:46 +0300 Subject: [PATCH 11/42] code refactor --- frame/ethereum/src/lib.rs | 4 +- frame/evm/src/lib.rs | 13 +- frame/evm/src/resource.rs | 628 +++++++++++++++++++++++++++++++++ frame/evm/src/runner/stack.rs | 307 +++------------- frame/evm/src/tests.rs | 62 +++- primitives/evm/src/lib.rs | 89 +---- primitives/evm/src/resource.rs | 241 ------------- 7 files changed, 730 insertions(+), 614 deletions(-) create mode 100644 frame/evm/src/resource.rs delete mode 100644 primitives/evm/src/resource.rs diff --git a/frame/ethereum/src/lib.rs b/frame/ethereum/src/lib.rs index f5d4740c30..56eb6b3fd4 100644 --- a/frame/ethereum/src/lib.rs +++ b/frame/ethereum/src/lib.rs @@ -690,8 +690,8 @@ impl Pallet { true, ); if let Some(weight_info) = weight_info { - if let Some(proof_size_meter) = weight_info.proof_size_meter { - *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); + if let Some(proof_size_usage) = weight_info.proof_size_usage { + *gas_to_weight.proof_size_mut() = proof_size_usage; } } Some(gas_to_weight) diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index 569642068c..1e207f2a5d 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -60,6 +60,7 @@ pub mod benchmarking; #[cfg(test)] mod mock; +pub mod resource; pub mod runner; #[cfg(test)] mod tests; @@ -268,8 +269,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_meter) = weight_info.proof_size_meter { - *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); + if let Some(proof_size_usage) = weight_info.proof_size_usage { + *gas_to_weight.proof_size_mut() = proof_size_usage; } } Some(gas_to_weight) @@ -355,8 +356,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_meter) = weight_info.proof_size_meter { - *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); + if let Some(proof_size_usage) = weight_info.proof_size_usage { + *gas_to_weight.proof_size_mut() = proof_size_usage; } } Some(gas_to_weight) @@ -443,8 +444,8 @@ pub mod pallet { true, ); if let Some(weight_info) = info.weight_info { - if let Some(proof_size_meter) = weight_info.proof_size_meter { - *gas_to_weight.proof_size_mut() = proof_size_meter.usage(); + if let Some(proof_size_usage) = weight_info.proof_size_usage { + *gas_to_weight.proof_size_mut() = proof_size_usage; } } Some(gas_to_weight) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs new file mode 100644 index 0000000000..db07f775df --- /dev/null +++ b/frame/evm/src/resource.rs @@ -0,0 +1,628 @@ +use crate::{AccountCodes, AccountCodesMetadata, Config, Pallet}; + +use core::marker::PhantomData; +use evm::{ + gasometer::{GasCost, StorageTarget}, + ExitError, Opcode, +}; +use fp_evm::WeightInfo; +use sp_core::{Get, H160, H256, U256}; +use sp_runtime::{traits::CheckedAdd, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +/// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) +pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; +/// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). +pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; +/// 16 (hash1) + 20 (key1) + 16 (hash2) + 32 (key2) + 32 (value) +pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; +/// Fixed trie 32 byte hash. +pub const WRITE_PROOF_SIZE: u64 = 32; +/// Account basic proof size + 5 bytes max of `decode_len` call. +pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; + +#[derive(Debug, PartialEq)] +/// Resource error. +pub enum ResourceError { + /// The Resource usage exceeds the limit. + LimitExceeded, + /// Invalid Base Cost. + InvalidBaseCost, + ///Used to indicate that the code should be unreachable. + Unreachable, +} + +/// A struct that keeps track of resource usage and limit. +pub struct Resource { + limit: T, + usage: T, +} + +impl Resource +where + T: CheckedAdd + Saturating + PartialOrd + Copy, +{ + /// Creates a new `Resource` instance with the given base cost and limit. + /// + /// # Errors + /// + /// Returns `ResourceError::InvalidBaseCost` if the base cost is greater than the limit. + pub fn new(base_cost: T, limit: T) -> Result { + if base_cost > limit { + return Err(ResourceError::InvalidBaseCost); + } + Ok(Self { + limit, + usage: base_cost, + }) + } + + /// Records the cost of an operation and updates the usage. + /// + /// # Errors + /// + /// Returns `ResourceError::LimitExceeded` if the Resource usage exceeds the limit. + fn record_cost(&mut self, cost: T) -> Result<(), ResourceError> { + let usage = self + .usage + .checked_add(&cost) + .ok_or(ResourceError::LimitExceeded)?; + + if usage > self.limit { + return Err(ResourceError::LimitExceeded); + } + self.usage = usage; + Ok(()) + } + + /// Refunds the given amount. + fn refund(&mut self, amount: T) { + self.usage = self.usage.saturating_sub(amount); + } + + /// Returns the usage. + fn usage(&self) -> T { + self.usage + } +} + +pub enum AccessedStorage { + AccountCodes(H160), + AccountStorages((H160, H256)), +} + +#[derive(Default, Clone, Eq, PartialEq)] +pub struct Recorded { + account_codes: Vec, + account_storages: BTreeMap<(H160, H256), bool>, +} + +/// A struct that keeps track of the proof size and limit. +pub struct ProofSizeMeter { + resource: Resource, + recorded: Recorded, + _marker: PhantomData, +} + +impl ProofSizeMeter { + /// Creates a new `ProofSizeResource` instance with the given limit. + pub fn new(base_cost: u64, limit: u64) -> Result { + Ok(Self { + resource: Resource::new(base_cost, limit)?, + recorded: Recorded::default(), + _marker: PhantomData, + }) + } + + /// Records the size of the proof and updates the usage. + /// + /// # Errors + /// + /// Returns `ResourceError::LimitExceeded` if the proof size exceeds the limit. + pub fn record_proof_size(&mut self, size: u64) -> Result<(), ResourceError> { + self.resource.record_cost(size) + } + + /// Refunds the given amount of proof size. + pub fn refund(&mut self, amount: u64) { + self.resource.refund(amount) + } + + /// Returns the proof size usage. + pub fn usage(&self) -> u64 { + self.resource.usage() + } + + /// Returns the proof size limit. + pub fn limit(&self) -> u64 { + self.resource.limit + } + + pub fn record_external_operation( + &mut self, + op: &evm::ExternalOperation, + contract_size_limit: u64, + ) -> Result<(), ResourceError> { + match op { + evm::ExternalOperation::AccountBasicRead => { + self.record_proof_size(ACCOUNT_BASIC_PROOF_SIZE)? + } + evm::ExternalOperation::AddressCodeRead(address) => { + let maybe_record = !self.recorded.account_codes.contains(&address); + // Skip if the address has been already recorded this block + if maybe_record { + // First we record account emptiness check. + // Transfers to EOAs with standard 21_000 gas limit are able to + // pay for this pov size. + self.record_proof_size(IS_EMPTY_CHECK_PROOF_SIZE)?; + + if >::decode_len(address).unwrap_or(0) == 0 { + return Ok(()); + } + // Try to record fixed sized `AccountCodesMetadata` read + // Tentatively 16 + 20 + 40 + self.record_proof_size(ACCOUNT_CODES_METADATA_PROOF_SIZE)?; + if let Some(meta) = >::get(address) { + self.record_proof_size(meta.size)?; + } else { + // If it does not exist, try to record `create_contract_limit` first. + self.record_proof_size(contract_size_limit)?; + let meta = Pallet::::account_code_metadata(*address); + let actual_size = meta.size; + // Refund if applies + self.refund(contract_size_limit.saturating_sub(actual_size)); + } + self.recorded.account_codes.push(*address); + } + } + evm::ExternalOperation::IsEmpty => self.record_proof_size(IS_EMPTY_CHECK_PROOF_SIZE)?, + evm::ExternalOperation::Write => self.record_proof_size(WRITE_PROOF_SIZE)?, + }; + Ok(()) + } + + pub fn record_external_dynamic_opcode_cost( + &mut self, + opcode: Opcode, + target: evm::gasometer::StorageTarget, + contract_size_limit: u64, + ) -> Result<(), ResourceError> { + // If account code or storage slot is in the overlay it is already accounted for and early exit + let mut accessed_storage: Option = match target { + StorageTarget::Address(address) => { + if self.recorded.account_codes.contains(&address) { + return Ok(()); + } else { + Some(AccessedStorage::AccountCodes(address)) + } + } + StorageTarget::Slot(address, index) => { + if self + .recorded + .account_storages + .contains_key(&(address, index)) + { + return Ok(()); + } else { + Some(AccessedStorage::AccountStorages((address, index))) + } + } + _ => None, + }; + + let mut maybe_record_and_refund = |with_empty_check: bool| -> Result<(), ResourceError> { + let address = if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage { + address + } else { + // This must be unreachable, a valid target must be set. + // TODO decide how do we want to gracefully handle. + return Err(ResourceError::Unreachable); + }; + // First try to record fixed sized `AccountCodesMetadata` read + // Tentatively 20 + 8 + 32 + let mut base_cost = ACCOUNT_CODES_METADATA_PROOF_SIZE; + if with_empty_check { + base_cost = base_cost.saturating_add(IS_EMPTY_CHECK_PROOF_SIZE); + } + self.record_proof_size(base_cost)?; + if let Some(meta) = >::get(address) { + self.record_proof_size(meta.size)?; + } else { + // If it does not exist, try to record `create_contract_limit` first. + self.record_proof_size(contract_size_limit)?; + let meta = Pallet::::account_code_metadata(address); + let actual_size = meta.size; + // Refund if applies + self.refund(contract_size_limit.saturating_sub(actual_size)); + } + self.recorded.account_codes.push(address); + // Already recorded, return + Ok(()) + }; + + // Proof size is fixed length for writes (a 32-byte hash in a merkle trie), and + // the full key/value for reads. For read and writes over the same storage, the full value + // is included. + // For cold reads involving code (call, callcode, staticcall and delegatecall): + // - We depend on https://github.com/paritytech/frontier/pull/893 + // - Try to get the cached size or compute it on the fly + // - We record the actual size after caching, refunding the difference between it and the initially deducted + // contract size limit. + let opcode_proof_size = match opcode { + // Basic account fixed length + Opcode::BALANCE => { + accessed_storage = None; + U256::from(ACCOUNT_BASIC_PROOF_SIZE) + } + Opcode::EXTCODESIZE | Opcode::EXTCODECOPY | Opcode::EXTCODEHASH => { + return maybe_record_and_refund(false) + } + Opcode::CALLCODE | Opcode::CALL | Opcode::DELEGATECALL | Opcode::STATICCALL => { + return maybe_record_and_refund(true) + } + // (H160, H256) double map blake2 128 concat key size (68) + value 32 + Opcode::SLOAD => U256::from(ACCOUNT_STORAGE_PROOF_SIZE), + Opcode::SSTORE => { + let (address, index) = + if let Some(AccessedStorage::AccountStorages((address, index))) = + accessed_storage + { + (address, index) + } else { + // This must be unreachable, a valid target must be set. + // TODO decide how do we want to gracefully handle. + return Err(ResourceError::Unreachable); + }; + let mut cost = WRITE_PROOF_SIZE; + let maybe_record = !self + .recorded + .account_storages + .contains_key(&(address, index)); + // If the slot is yet to be accessed we charge for it, as the evm reads + // it prior to the opcode execution. + // Skip if the address and index has been already recorded this block. + if maybe_record { + cost = cost.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE); + } + U256::from(cost) + } + // Fixed trie 32 byte hash + Opcode::CREATE | Opcode::CREATE2 => U256::from(WRITE_PROOF_SIZE), + // When calling SUICIDE a target account will receive the self destructing + // address's balance. We need to account for both: + // - Target basic account read + // - 5 bytes of `decode_len` + Opcode::SUICIDE => { + accessed_storage = None; + U256::from(IS_EMPTY_CHECK_PROOF_SIZE) + } + // Rest of dynamic opcodes that do not involve proof size recording, do nothing + _ => return Ok(()), + }; + + if opcode_proof_size > U256::from(u64::MAX) { + self.record_proof_size(self.limit())?; + return Err(ResourceError::LimitExceeded); + } + + // Cache the storage access + match accessed_storage { + Some(AccessedStorage::AccountStorages((address, index))) => { + self.recorded + .account_storages + .insert((address, index), true); + } + Some(AccessedStorage::AccountCodes(address)) => { + self.recorded.account_codes.push(address); + } + _ => {} + } + + // Record cost + self.record_proof_size(opcode_proof_size.low_u64())?; + Ok(()) + } + + pub fn record_external_static_opcode_cost( + &mut self, + _opcode: Opcode, + _gas_cost: GasCost, + ) -> Result<(), ResourceError> { + Ok(()) + } +} + +/// A struct that keeps track of the ref_time usage and limit. +pub struct RefTimeMeter(Resource); + +impl RefTimeMeter { + /// Creates a new `RefTimeResource` instance with the given limit. + pub fn new(limit: u64) -> Result { + Ok(Self(Resource::new(0, limit)?)) + } + + /// Records the ref_time and updates the usage. + pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), ResourceError> { + self.0.record_cost(ref_time) + } + + /// Returns the ref time usage. + pub fn usage(&self) -> u64 { + self.0.usage() + } + + /// Returns the ref time limit. + pub fn limit(&self) -> u64 { + self.0.limit + } + + /// Refunds the given amount of ref_time. + pub fn refund(&mut self, amount: u64) { + self.0.refund(amount) + } +} + +/// A struct that keeps track of storage usage (newly created storage) and limit. +pub struct StorageMeter(Resource); + +impl StorageMeter { + /// Creates a new `StorageResource` instance with the given limit. + pub fn new(limit: u64) -> Result { + Ok(Self(Resource::new(0, limit)?)) + } + + /// Refunds the given amount of storage. + fn _refund(&mut self, amount: u64) { + self.0.refund(amount) + } + + /// Returns the storage usage. + pub fn usage(&self) -> u64 { + self.0.usage() + } + + /// Records the dynamic opcode cost and updates the storage usage. + /// + /// # Errors + /// + /// Returns `ResourceError::LimitExceeded` if the storage usage exceeds the storage limit. + fn _record_dynamic_opcode_cost( + &mut self, + _opcode: Opcode, + gas_cost: GasCost, + ) -> Result<(), ResourceError> { + let cost = match gas_cost { + GasCost::Create => { + // TODO record cost for create + 0 + } + GasCost::Create2 { len } => { + // len in bytes ?? + len.try_into().map_err(|_| ResourceError::LimitExceeded)? + } + GasCost::SStore { .. } => { + // TODO record cost for sstore + 0 + } + _ => return Ok(()), + }; + self.0.record_cost(cost) + } + + fn record_external_operation( + &mut self, + operation: &evm::ExternalOperation, + _contract_size_limit: u64, + ) { + match operation { + evm::ExternalOperation::Write => { + // Todo record cost for write + } + _ => {} + } + } +} + +pub struct ResourceInfo { + pub ref_time_meter: Option, + pub proof_size_meter: Option>, + pub storage_meter: Option, +} + +impl ResourceInfo { + pub fn new() -> Self { + Self { + ref_time_meter: None, + proof_size_meter: None, + storage_meter: None, + } + } + + pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); + Ok(()) + } + + pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { + self.proof_size_meter = + Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); + Ok(()) + } + + pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.storage_meter = Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); + Ok(()) + } + + pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { + if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { + ref_time_meter + .record_ref_time(cost) + .map_err(|_| ExitError::OutOfGas)?; + } + + Ok(()) + } + + pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter + .record_proof_size(cost) + .map_err(|_| ExitError::OutOfGas)?; + } + + Ok(()) + } + + pub fn refund_proof_size(&mut self, amount: u64) { + self.proof_size_meter.as_mut().map(|proof_size_meter| { + proof_size_meter.refund(amount); + }); + } + + pub fn refund_ref_time(&mut self, amount: u64) { + self.ref_time_meter.as_mut().map(|ref_time_meter| { + ref_time_meter.refund(amount); + }); + } + + /// Returns WeightInfo for the resource. + pub fn weight_info(&self) -> WeightInfo { + macro_rules! usage_and_limit { + ($x:expr) => { + ( + $x.as_ref().map(|x| x.usage()), + $x.as_ref().map(|x| x.limit()), + ) + }; + } + + let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_meter); + let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_meter); + + WeightInfo { + proof_size_usage, + proof_size_limit, + ref_time_usage, + ref_time_limit, + } + } + + pub fn record_external_operation( + &mut self, + operation: evm::ExternalOperation, + contract_size_limit: u64, + ) -> Result<(), ResourceError> { + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter.record_external_operation(&operation, contract_size_limit)?; + } + + if let Some(storage_meter) = self.storage_meter.as_mut() { + storage_meter.record_external_operation(&operation, contract_size_limit) + } + + Ok(()) + } + + pub fn record_external_dynamic_opcode_cost( + &mut self, + opcode: Opcode, + target: evm::gasometer::StorageTarget, + contract_size_limit: u64, + ) -> Result<(), ResourceError> { + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter.record_external_dynamic_opcode_cost( + opcode, + target, + contract_size_limit, + )?; + } + // Record ref_time + // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time + + Ok(()) + } + + /// Computes the effective gas for the transaction. Effective gas is the maximum between the + /// gas used and resource usage. + pub fn effective_gas(&self, gas: u64) -> U256 { + let proof_size_usage = self + .proof_size_meter + .as_ref() + .map_or(0, |meter| meter.usage()) + .saturating_mul(T::GasLimitPovSizeRatio::get()); + + // TODO: use the actual ref time usage + // let ref_time_usage = self + // .ref_time_meter + // .map_or(0, |meter| meter.usage()) + // .saturating_mul(T::GasLimitPovRefTimeRatio::get()); + + // TODO get the Storage Gas ratio + let storage_usage = self.storage_meter.as_ref().map_or(0, |meter| meter.usage()); + + let effective_gas = + sp_std::cmp::max(sp_std::cmp::max(proof_size_usage, storage_usage), gas); + + U256::from(effective_gas) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_init() { + let resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.limit, 100); + assert_eq!(resource.usage, 0); + + // base cost > limit + let resource = Resource::::new(100, 0).err(); + assert_eq!(resource, Some(ResourceError::InvalidBaseCost)); + } + + #[test] + fn test_record_cost() { + let mut resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.record_cost(10), Ok(())); + assert_eq!(resource.usage, 10); + assert_eq!(resource.record_cost(90), Ok(())); + assert_eq!(resource.usage, 100); + + // exceed limit + assert_eq!(resource.record_cost(1), Err(ResourceError::LimitExceeded)); + assert_eq!(resource.usage, 100); + } + + #[test] + fn test_refund() { + let mut resource = Resource::::new(0, 100).unwrap(); + assert_eq!(resource.record_cost(10), Ok(())); + assert_eq!(resource.usage, 10); + resource.refund(10); + assert_eq!(resource.usage, 0); + + // refund more than usage + resource.refund(10); + assert_eq!(resource.usage, 0); + } + + #[test] + fn test_storage_resource() { + let mut resource = StorageMeter::new(100).unwrap(); + assert_eq!(resource.0.usage, 0); + assert_eq!(resource.0.limit, 100); + assert_eq!(resource.0.record_cost(10), Ok(())); + assert_eq!(resource.0.usage, 10); + assert_eq!(resource.0.record_cost(90), Ok(())); + assert_eq!(resource.0.usage, 100); + assert_eq!(resource.0.record_cost(1), Err(ResourceError::LimitExceeded)); + assert_eq!(resource.0.usage, 100); + resource.0.refund(10); + assert_eq!(resource.0.usage, 90); + resource._refund(10); + assert_eq!(resource.0.usage, 80); + } +} diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 055505348c..8a2ac5ce2a 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -20,7 +20,7 @@ use evm::{ backend::Backend as BackendT, executor::stack::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata}, - gasometer::{GasCost, StorageTarget}, + gasometer::GasCost, ExitError, ExitReason, Opcode, Transfer, }; // Substrate @@ -42,15 +42,13 @@ use sp_std::{ }; // Frontier use fp_evm::{ - AccessedStorage, CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet, - Vicinity, WeightInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, - ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, + CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet, Vicinity, }; use crate::{ - runner::Runner as RunnerT, AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, - BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, - OnCreate, Pallet, RunnerError, + resource::ResourceInfo, runner::Runner as RunnerT, AccountCodes, AccountStorages, + AddressMapping, BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, + OnChargeEVMTransaction, OnCreate, Pallet, RunnerError, }; #[cfg(feature = "forbid-evm-reentrancy")] @@ -154,17 +152,17 @@ where { // Used to record the external costs in the evm through the StackState implementation - let mut weight_info = WeightInfo::new(); + let mut resource_info = ResourceInfo::new(); match weight_limit { Some(weight_limit) => { - weight_info + resource_info .add_ref_time_meter(weight_limit.ref_time()) .map_err(|_| RunnerError { error: Error::::Undefined, weight, })?; if let Some(proof_size_base_cost) = proof_size_base_cost { - weight_info + resource_info .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) .map_err(|_| RunnerError { error: Error::::Undefined, @@ -177,22 +175,22 @@ where // TODO Compute the limit of storage per tx let storage_limit = 0; - weight_info.add_storage_meter(storage_limit).map_err(|_| RunnerError { - error: Error::::Undefined, - weight, - })?; + resource_info + .add_storage_meter(storage_limit) + .map_err(|_| RunnerError { + error: Error::::Undefined, + weight, + })?; - let weight_info = if weight_info.ref_time_meter.is_none() - && weight_info.proof_size_meter.is_none() - && weight_info.storage_meter.is_none() + let resource_info = if resource_info.ref_time_meter.is_none() + && resource_info.proof_size_meter.is_none() + && resource_info.storage_meter.is_none() { None - } - else { - Some(weight_info) + } else { + Some(resource_info) }; - // The precompile check is only used for transactional invocations. However, here we always // execute the check, because the check has side effects. match precompiles.is_precompile(source, gas_limit) { @@ -207,7 +205,7 @@ where standard: gas_limit.into(), effective: gas_limit.into(), }, - weight_info, + weight_info: resource_info.map(|r| r.weight_info()), logs: Default::default(), }) } @@ -276,20 +274,15 @@ where }; let metadata = StackSubstateMetadata::new(gas_limit, config); - let state = SubstrateStackState::new(&vicinity, metadata, weight_info); + let state = SubstrateStackState::new(&vicinity, metadata, resource_info); let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); let (reason, retv) = f(&mut executor); // Post execution. let used_gas = executor.used_gas(); - let effective_gas = match executor.state().weight_info() { - Some(weight_info) => U256::from(sp_std::cmp::max( - used_gas, - weight_info - .proof_size_usage() - .saturating_mul(T::GasLimitPovSizeRatio::get()), - )), + let effective_gas = match executor.state().resource_info() { + Some(resource_info) => resource_info.effective_gas(used_gas), _ => used_gas.into(), }; let actual_fee = effective_gas.saturating_mul(total_fee_per_gas); @@ -378,7 +371,7 @@ where standard: used_gas.into(), effective: effective_gas, }, - weight_info: state.weight_info(), + weight_info: state.resource_info.as_ref().map(|r| r.weight_info()), logs: state.substate.logs, }) } @@ -700,19 +693,12 @@ impl<'config> SubstrateStackSubstate<'config> { } } -#[derive(Default, Clone, Eq, PartialEq)] -pub struct Recorded { - account_codes: Vec, - account_storages: BTreeMap<(H160, H256), bool>, -} - /// Substrate backend for EVM. pub struct SubstrateStackState<'vicinity, 'config, T> { vicinity: &'vicinity Vicinity, substate: SubstrateStackSubstate<'config>, original_storage: BTreeMap<(H160, H256), H256>, - recorded: Recorded, - weight_info: Option, + resource_info: Option>, _marker: PhantomData, } @@ -721,7 +707,7 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { pub fn new( vicinity: &'vicinity Vicinity, metadata: StackSubstateMetadata<'config>, - weight_info: Option, + resource_info: Option>, ) -> Self { Self { vicinity, @@ -733,21 +719,12 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { }, _marker: PhantomData, original_storage: BTreeMap::new(), - recorded: Default::default(), - weight_info, + resource_info, } } - pub fn weight_info(&self) -> Option { - self.weight_info - } - - pub fn recorded(&self) -> &Recorded { - &self.recorded - } - - pub fn info_mut(&mut self) -> (&mut Option, &mut Recorded) { - (&mut self.weight_info, &mut self.recorded) + pub fn resource_info(&self) -> &Option> { + &self.resource_info } } @@ -992,50 +969,12 @@ where .create_contract_limit .unwrap_or_default() as u64; - let (weight_info, recorded) = self.info_mut(); - - if let Some(weight_info) = weight_info { - match op { - evm::ExternalOperation::AccountBasicRead => { - weight_info.try_record_proof_size_or_fail(ACCOUNT_BASIC_PROOF_SIZE)? - } - evm::ExternalOperation::AddressCodeRead(address) => { - let maybe_record = !recorded.account_codes.contains(&address); - // Skip if the address has been already recorded this block - if maybe_record { - // First we record account emptiness check. - // Transfers to EOAs with standard 21_000 gas limit are able to - // pay for this pov size. - weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)?; - - if >::decode_len(address).unwrap_or(0) == 0 { - return Ok(()); - } - // Try to record fixed sized `AccountCodesMetadata` read - // Tentatively 16 + 20 + 40 - weight_info - .try_record_proof_size_or_fail(ACCOUNT_CODES_METADATA_PROOF_SIZE)?; - if let Some(meta) = >::get(address) { - weight_info.try_record_proof_size_or_fail(meta.size)?; - } else { - // If it does not exist, try to record `create_contract_limit` first. - weight_info.try_record_proof_size_or_fail(size_limit)?; - let meta = Pallet::::account_code_metadata(address); - let actual_size = meta.size; - // Refund if applies - weight_info.refund_proof_size(size_limit.saturating_sub(actual_size)); - } - recorded.account_codes.push(address); - } - } - evm::ExternalOperation::IsEmpty => { - weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)? - } - evm::ExternalOperation::Write => { - weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)? - } - }; + if let Some(resource_info) = self.resource_info.as_mut() { + resource_info + .record_external_operation(op, size_limit) + .map_err(|_| ExitError::OutOfGas)?; } + Ok(()) } @@ -1045,29 +984,6 @@ where _gas_cost: GasCost, target: evm::gasometer::StorageTarget, ) -> Result<(), ExitError> { - // If account code or storage slot is in the overlay it is already accounted for and early exit - let mut accessed_storage: Option = match target { - StorageTarget::Address(address) => { - if self.recorded().account_codes.contains(&address) { - return Ok(()); - } else { - Some(AccessedStorage::AccountCodes(address)) - } - } - StorageTarget::Slot(address, index) => { - if self - .recorded() - .account_storages - .contains_key(&(address, index)) - { - return Ok(()); - } else { - Some(AccessedStorage::AccountStorages((address, index))) - } - } - _ => None, - }; - let size_limit: u64 = self .metadata() .gasometer() @@ -1075,162 +991,21 @@ where .create_contract_limit .unwrap_or_default() as u64; - let (weight_info, recorded) = { - let (weight_info, recorded) = self.info_mut(); - if let Some(weight_info) = weight_info { - (weight_info, recorded) - } else { - return Ok(()); - } - }; - - // Record ref_time first - // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time - - // Record proof_size - // Return if proof size recording is disabled - let proof_size_limit = if let Some(proof_size_meter) = weight_info.proof_size_meter { - proof_size_meter.limit() - } else { - return Ok(()); - }; - - let mut maybe_record_and_refund = |with_empty_check: bool| -> Result<(), ExitError> { - let address = if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage { - address - } else { - // This must be unreachable, a valid target must be set. - // TODO decide how do we want to gracefully handle. - return Err(ExitError::OutOfGas); - }; - // First try to record fixed sized `AccountCodesMetadata` read - // Tentatively 20 + 8 + 32 - let mut base_cost = ACCOUNT_CODES_METADATA_PROOF_SIZE; - if with_empty_check { - base_cost = base_cost.saturating_add(IS_EMPTY_CHECK_PROOF_SIZE); - } - weight_info.try_record_proof_size_or_fail(base_cost)?; - if let Some(meta) = >::get(address) { - weight_info.try_record_proof_size_or_fail(meta.size)?; - } else { - // If it does not exist, try to record `create_contract_limit` first. - weight_info.try_record_proof_size_or_fail(size_limit)?; - let meta = Pallet::::account_code_metadata(address); - let actual_size = meta.size; - // Refund if applies - weight_info.refund_proof_size(size_limit.saturating_sub(actual_size)); - } - recorded.account_codes.push(address); - // Already recorded, return - Ok(()) - }; - - // Proof size is fixed length for writes (a 32-byte hash in a merkle trie), and - // the full key/value for reads. For read and writes over the same storage, the full value - // is included. - // For cold reads involving code (call, callcode, staticcall and delegatecall): - // - We depend on https://github.com/paritytech/frontier/pull/893 - // - Try to get the cached size or compute it on the fly - // - We record the actual size after caching, refunding the difference between it and the initially deducted - // contract size limit. - let opcode_proof_size = match opcode { - // Basic account fixed length - Opcode::BALANCE => { - accessed_storage = None; - U256::from(ACCOUNT_BASIC_PROOF_SIZE) - } - Opcode::EXTCODESIZE | Opcode::EXTCODECOPY | Opcode::EXTCODEHASH => { - return maybe_record_and_refund(false) - } - Opcode::CALLCODE | Opcode::CALL | Opcode::DELEGATECALL | Opcode::STATICCALL => { - return maybe_record_and_refund(true) - } - // (H160, H256) double map blake2 128 concat key size (68) + value 32 - Opcode::SLOAD => U256::from(ACCOUNT_STORAGE_PROOF_SIZE), - Opcode::SSTORE => { - let (address, index) = - if let Some(AccessedStorage::AccountStorages((address, index))) = - accessed_storage - { - (address, index) - } else { - // This must be unreachable, a valid target must be set. - // TODO decide how do we want to gracefully handle. - return Err(ExitError::OutOfGas); - }; - let mut cost = WRITE_PROOF_SIZE; - let maybe_record = !recorded.account_storages.contains_key(&(address, index)); - // If the slot is yet to be accessed we charge for it, as the evm reads - // it prior to the opcode execution. - // Skip if the address and index has been already recorded this block. - if maybe_record { - cost = cost.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE); - } - U256::from(cost) - } - // Fixed trie 32 byte hash - Opcode::CREATE | Opcode::CREATE2 => U256::from(WRITE_PROOF_SIZE), - // When calling SUICIDE a target account will receive the self destructing - // address's balance. We need to account for both: - // - Target basic account read - // - 5 bytes of `decode_len` - Opcode::SUICIDE => { - accessed_storage = None; - U256::from(IS_EMPTY_CHECK_PROOF_SIZE) - } - // Rest of dynamic opcodes that do not involve proof size recording, do nothing - _ => return Ok(()), - }; - - if opcode_proof_size > U256::from(u64::MAX) { - weight_info.try_record_proof_size_or_fail(proof_size_limit)?; - return Err(ExitError::OutOfGas); - } - - // Cache the storage access - match accessed_storage { - Some(AccessedStorage::AccountStorages((address, index))) => { - recorded.account_storages.insert((address, index), true); - } - Some(AccessedStorage::AccountCodes(address)) => { - recorded.account_codes.push(address); - } - _ => {} - } - - // Record cost - self.record_external_cost(None, Some(opcode_proof_size.low_u64()))?; - Ok(()) - } - - fn record_external_cost( - &mut self, - ref_time: Option, - proof_size: Option, - ) -> Result<(), ExitError> { - let weight_info = if let (Some(weight_info), _) = self.info_mut() { - weight_info - } else { - return Ok(()); - }; - // Record ref_time first - // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time - if let Some(amount) = ref_time { - weight_info.try_record_ref_time_or_fail(amount)?; - } - if let Some(amount) = proof_size { - weight_info.try_record_proof_size_or_fail(amount)?; + if let Some(resource_info) = self.resource_info.as_mut() { + resource_info + .record_external_dynamic_opcode_cost(opcode, target, size_limit) + .map_err(|_| ExitError::OutOfGas)?; } Ok(()) } fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { - if let Some(mut weight_info) = self.weight_info { + if let Some(resource_info) = self.resource_info.as_mut() { if let Some(amount) = ref_time { - weight_info.refund_ref_time(amount); + resource_info.refund_ref_time(amount); } if let Some(amount) = proof_size { - weight_info.refund_proof_size(amount); + resource_info.refund_proof_size(amount); } } } diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index b1c5de19c0..29b06dce53 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -18,8 +18,13 @@ #![cfg(test)] use super::*; -use crate::mock::*; - +use crate::{ + mock::*, + resource::{ + ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, + IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, + }, +}; use frame_support::{ assert_ok, traits::{GenesisBuild, LockIdentifier, LockableCurrency, WithdrawReasons}, @@ -28,10 +33,7 @@ use std::{collections::BTreeMap, str::FromStr}; mod proof_size_test { use super::*; - use fp_evm::{ - CreateInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, - ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, - }; + use fp_evm::CreateInfo; use frame_support::traits::StorageInfoTrait; // pragma solidity ^0.8.2; // contract Callee { @@ -181,7 +183,11 @@ mod proof_size_test { let nonce_increases = ACCOUNT_BASIC_PROOF_SIZE * 2; let expected_proof_size = write_cost + is_empty_check + nonce_increases; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -238,7 +244,11 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -291,7 +301,11 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -336,7 +350,11 @@ mod proof_size_test { + IS_EMPTY_CHECK_PROOF_SIZE + (ACCOUNT_BASIC_PROOF_SIZE * 2); - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -382,7 +400,11 @@ mod proof_size_test { + IS_EMPTY_CHECK_PROOF_SIZE + (ACCOUNT_BASIC_PROOF_SIZE * 2); - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -435,7 +457,11 @@ mod proof_size_test { let expected_proof_size = overhead + (number_balance_reads * ACCOUNT_BASIC_PROOF_SIZE) as u64; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); } @@ -500,7 +526,11 @@ mod proof_size_test { + reading_main_contract_len + is_empty_check + increase_nonce) as u64; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(expected_proof_size, actual_proof_size); }); @@ -583,7 +613,11 @@ mod proof_size_test { let ratio = <::GasLimitPovSizeRatio as Get>::get(); let used_gas = result.used_gas; - let actual_proof_size = result.weight_info.expect("weight info").proof_size_usage(); + let actual_proof_size = result + .weight_info + .expect("weight info") + .proof_size_usage + .expect("proof size usage"); assert_eq!(used_gas.standard, U256::from(21_000)); assert_eq!(used_gas.effective, U256::from(actual_proof_size * ratio)); diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 4265a3b328..5c34350a96 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -17,13 +17,10 @@ #![cfg_attr(not(feature = "std"), no_std)] #![deny(unused_crate_dependencies)] - -mod resource; mod precompile; mod validation; use frame_support::weights::{constants::WEIGHT_REF_TIME_PER_MILLIS, Weight}; -use resource::{ProofSizeMeter, RefTimeMeter, StorageMeter}; use scale_codec::{Decode, Encode}; use scale_info::TypeInfo; #[cfg(feature = "serde")] @@ -59,91 +56,13 @@ pub struct Vicinity { pub origin: H160, } -/// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) -pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; -/// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). -pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; -/// 16 (hash1) + 20 (key1) + 16 (hash2) + 32 (key2) + 32 (value) -pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; -/// Fixed trie 32 byte hash. -pub const WRITE_PROOF_SIZE: u64 = 32; -/// Account basic proof size + 5 bytes max of `decode_len` call. -pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; - -pub enum AccessedStorage { - AccountCodes(H160), - AccountStorages((H160, H256)), -} - #[derive(Clone, Copy, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightInfo { - pub ref_time_meter: Option, - pub proof_size_meter: Option, - pub storage_meter: Option, -} - -impl WeightInfo { - pub fn new() -> Self { - Self { - ref_time_meter: None, - proof_size_meter: None, - storage_meter: None, - } - } - - pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { - self.proof_size_meter = - Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.storage_meter = - Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { - ref_time_meter - .record_ref_time(cost) - .map_err(|_| ExitError::OutOfGas)?; - } - - Ok(()) - } - pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter - .record_proof_size(cost) - .map_err(|_| ExitError::OutOfGas)?; - } - - Ok(()) - } - - pub fn refund_proof_size(&mut self, amount: u64) { - self.proof_size_meter.as_mut().map(|proof_size_meter| { - proof_size_meter.refund(amount); - }); - } - - pub fn proof_size_usage(&self) -> u64 { - self.proof_size_meter - .map_or(0, |proof_size_meter| proof_size_meter.usage()) - } - - pub fn refund_ref_time(&mut self, amount: u64) { - self.ref_time_meter.as_mut().map(|ref_time_meter| { - ref_time_meter.refund(amount); - }); - } + pub ref_time_usage: Option, + pub ref_time_limit: Option, + pub proof_size_usage: Option, + pub proof_size_limit: Option, } #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] diff --git a/primitives/evm/src/resource.rs b/primitives/evm/src/resource.rs deleted file mode 100644 index 4d80f60b08..0000000000 --- a/primitives/evm/src/resource.rs +++ /dev/null @@ -1,241 +0,0 @@ -use evm::{gasometer::GasCost, Opcode}; -use scale_codec::{Decode, Encode}; -use scale_info::TypeInfo; -#[cfg(feature = "serde")] -use serde::{Deserialize, Serialize}; -use sp_runtime::{traits::CheckedAdd, Saturating}; - -#[derive(Debug, PartialEq)] -/// Resource error. -pub enum ResourceError { - /// The Resource usage exceeds the limit. - LimitExceeded, - /// Invalid Base Cost. - InvalidBaseCost, -} - -/// A struct that keeps track of resource usage and limit. -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct Resource { - limit: T, - usage: T, -} - -impl Resource -where - T: CheckedAdd + Saturating + PartialOrd + Copy, -{ - /// Creates a new `Resource` instance with the given base cost and limit. - /// - /// # Errors - /// - /// Returns `ResourceError::InvalidBaseCost` if the base cost is greater than the limit. - pub fn new(base_cost: T, limit: T) -> Result { - if base_cost > limit { - return Err(ResourceError::InvalidBaseCost); - } - Ok(Self { - limit, - usage: base_cost, - }) - } - - /// Records the cost of an operation and updates the usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the Resource usage exceeds the limit. - fn record_cost(&mut self, cost: T) -> Result<(), ResourceError> { - let usage = self - .usage - .checked_add(&cost) - .ok_or(ResourceError::LimitExceeded)?; - - if usage > self.limit { - return Err(ResourceError::LimitExceeded); - } - self.usage = usage; - Ok(()) - } - - /// Refunds the given amount. - fn refund(&mut self, amount: T) { - self.usage = self.usage.saturating_sub(amount); - } - - /// Returns the usage. - fn usage(&self) -> T { - self.usage - } -} - -/// A struct that keeps track of the proof size and limit. -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct ProofSizeMeter(Resource); - -impl ProofSizeMeter { - /// Creates a new `ProofSizeResource` instance with the given limit. - pub fn new(base_cost: u64, limit: u64) -> Result { - Ok(Self(Resource::new(base_cost, limit)?)) - } - - /// Records the size of the proof and updates the usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the proof size exceeds the limit. - pub fn record_proof_size(&mut self, size: u64) -> Result<(), ResourceError> { - self.0.record_cost(size) - } - - /// Refunds the given amount of proof size. - pub fn refund(&mut self, amount: u64) { - self.0.refund(amount) - } - - /// Returns the proof size usage. - pub fn usage(&self) -> u64 { - self.0.usage() - } - - /// Returns the proof size limit. - pub fn limit(&self) -> u64 { - self.0.limit - } -} - -/// A struct that keeps track of the ref_time usage and limit. -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct RefTimeMeter(Resource); - -impl RefTimeMeter { - /// Creates a new `RefTimeResource` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Resource::new(0, limit)?)) - } - - /// Records the ref_time and updates the usage. - pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), ResourceError> { - self.0.record_cost(ref_time) - } - - /// Refunds the given amount of ref_time. - pub fn refund(&mut self, amount: u64) { - self.0.refund(amount) - } -} -/// A struct that keeps track of storage usage (newly created storage) and limit. -#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, Debug, TypeInfo)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct StorageMeter(Resource); - -impl StorageMeter { - /// Creates a new `StorageResource` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Resource::new(0, limit)?)) - } - - /// Refunds the given amount of storage. - fn refund(&mut self, amount: u64) { - self.0.refund(amount) - } - - /// Records the dynamic opcode cost and updates the storage usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the storage usage exceeds the storage limit. - fn record_dynamic_opcode_cost( - &mut self, - _opcode: Opcode, - gas_cost: GasCost, - ) -> Result<(), ResourceError> { - let cost = match gas_cost { - GasCost::Create => { - // TODO record cost for create - 0 - } - GasCost::Create2 { len } => { - // len in bytes ?? - len.try_into().map_err(|_| ResourceError::LimitExceeded)? - } - GasCost::SStore { .. } => { - // TODO record cost for sstore - 0 - } - _ => return Ok(()), - }; - self.0.record_cost(cost) - } - - fn record_external_operation(&mut self, operation: evm::ExternalOperation) { - match operation { - evm::ExternalOperation::Write => { - // Todo record cost for write - } - _ => {} - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_init() { - let resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.limit, 100); - assert_eq!(resource.usage, 0); - - // base cost > limit - let resource = Resource::::new(100, 0).err(); - assert_eq!(resource, Some(ResourceError::InvalidBaseCost)); - } - - #[test] - fn test_record_cost() { - let mut resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.record_cost(10), Ok(())); - assert_eq!(resource.usage, 10); - assert_eq!(resource.record_cost(90), Ok(())); - assert_eq!(resource.usage, 100); - - // exceed limit - assert_eq!(resource.record_cost(1), Err(ResourceError::LimitExceeded)); - assert_eq!(resource.usage, 100); - } - - #[test] - fn test_refund() { - let mut resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.record_cost(10), Ok(())); - assert_eq!(resource.usage, 10); - resource.refund(10); - assert_eq!(resource.usage, 0); - - // refund more than usage - resource.refund(10); - assert_eq!(resource.usage, 0); - } - - #[test] - fn test_storage_resource() { - let mut resource = StorageMeter::new(100).unwrap(); - assert_eq!(resource.0.usage, 0); - assert_eq!(resource.0.limit, 100); - assert_eq!(resource.0.record_cost(10), Ok(())); - assert_eq!(resource.0.usage, 10); - assert_eq!(resource.0.record_cost(90), Ok(())); - assert_eq!(resource.0.usage, 100); - assert_eq!(resource.0.record_cost(1), Err(ResourceError::LimitExceeded)); - assert_eq!(resource.0.usage, 100); - resource.0.refund(10); - assert_eq!(resource.0.usage, 90); - resource.refund(10); - assert_eq!(resource.0.usage, 80); - } -} From d9f04e2460c7a0d95eccf55b6375aeeb93053d0f Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 8 Aug 2023 10:41:36 +0300 Subject: [PATCH 12/42] remove unused functions --- frame/evm/src/resource.rs | 20 -------------------- primitives/evm/src/lib.rs | 4 ++-- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index db07f775df..1c1066b21e 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -454,26 +454,6 @@ impl ResourceInfo { Ok(()) } - pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { - ref_time_meter - .record_ref_time(cost) - .map_err(|_| ExitError::OutOfGas)?; - } - - Ok(()) - } - - pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter - .record_proof_size(cost) - .map_err(|_| ExitError::OutOfGas)?; - } - - Ok(()) - } - pub fn refund_proof_size(&mut self, amount: u64) { self.proof_size_meter.as_mut().map(|proof_size_meter| { proof_size_meter.refund(amount); diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 5c34350a96..a442c9fa8e 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -59,10 +59,10 @@ pub struct Vicinity { #[derive(Clone, Copy, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightInfo { - pub ref_time_usage: Option, pub ref_time_limit: Option, - pub proof_size_usage: Option, pub proof_size_limit: Option, + pub ref_time_usage: Option, + pub proof_size_usage: Option, } #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] From af358ad544555a79d31c6a017b62b92869f42333 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 8 Aug 2023 10:52:15 +0300 Subject: [PATCH 13/42] make clippy happy --- frame/evm/src/resource.rs | 20 +++++++++----------- frame/evm/src/runner/stack.rs | 23 +++++++++++------------ frame/evm/src/tests.rs | 1 + 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index 1c1066b21e..24a1a2c80b 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -3,7 +3,7 @@ use crate::{AccountCodes, AccountCodesMetadata, Config, Pallet}; use core::marker::PhantomData; use evm::{ gasometer::{GasCost, StorageTarget}, - ExitError, Opcode, + Opcode, }; use fp_evm::WeightInfo; use sp_core::{Get, H160, H256, U256}; @@ -148,7 +148,7 @@ impl ProofSizeMeter { self.record_proof_size(ACCOUNT_BASIC_PROOF_SIZE)? } evm::ExternalOperation::AddressCodeRead(address) => { - let maybe_record = !self.recorded.account_codes.contains(&address); + let maybe_record = !self.recorded.account_codes.contains(address); // Skip if the address has been already recorded this block if maybe_record { // First we record account emptiness check. @@ -414,15 +414,13 @@ impl StorageMeter { operation: &evm::ExternalOperation, _contract_size_limit: u64, ) { - match operation { - evm::ExternalOperation::Write => { - // Todo record cost for write - } - _ => {} + if let evm::ExternalOperation::Write = operation { + // Todo record cost for write } } } +#[derive(Default)] pub struct ResourceInfo { pub ref_time_meter: Option, pub proof_size_meter: Option>, @@ -455,15 +453,15 @@ impl ResourceInfo { } pub fn refund_proof_size(&mut self, amount: u64) { - self.proof_size_meter.as_mut().map(|proof_size_meter| { + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { proof_size_meter.refund(amount); - }); + } } pub fn refund_ref_time(&mut self, amount: u64) { - self.ref_time_meter.as_mut().map(|ref_time_meter| { + if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { ref_time_meter.refund(amount); - }); + } } /// Returns WeightInfo for the resource. diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 8a2ac5ce2a..29fd014678 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -153,24 +153,23 @@ where // Used to record the external costs in the evm through the StackState implementation let mut resource_info = ResourceInfo::new(); - match weight_limit { - Some(weight_limit) => { + + if let Some(weight_limit) = weight_limit { + resource_info + .add_ref_time_meter(weight_limit.ref_time()) + .map_err(|_| RunnerError { + error: Error::::Undefined, + weight, + })?; + + if let Some(proof_size_base_cost) = proof_size_base_cost { resource_info - .add_ref_time_meter(weight_limit.ref_time()) + .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) .map_err(|_| RunnerError { error: Error::::Undefined, weight, })?; - if let Some(proof_size_base_cost) = proof_size_base_cost { - resource_info - .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) - .map_err(|_| RunnerError { - error: Error::::Undefined, - weight, - })?; - } } - None => (), } // TODO Compute the limit of storage per tx diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 29b06dce53..3f3824ef75 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -462,6 +462,7 @@ mod proof_size_test { .expect("weight info") .proof_size_usage .expect("proof size usage"); + assert_eq!(expected_proof_size, actual_proof_size); }); } From 8fa4dd2a00f5c935d49c045d43e855957e79fd1f Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 9 Aug 2023 12:48:51 +0300 Subject: [PATCH 14/42] minor improvements --- frame/evm/src/resource.rs | 33 ++++++++++++++++--------------- frame/evm/src/runner/stack.rs | 37 ++++++++++++----------------------- primitives/evm/src/lib.rs | 1 + 3 files changed, 30 insertions(+), 41 deletions(-) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index 24a1a2c80b..e1c63e4651 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -3,7 +3,7 @@ use crate::{AccountCodes, AccountCodesMetadata, Config, Pallet}; use core::marker::PhantomData; use evm::{ gasometer::{GasCost, StorageTarget}, - Opcode, + ExitError, Opcode, }; use fp_evm::WeightInfo; use sp_core::{Get, H160, H256, U256}; @@ -32,6 +32,12 @@ pub enum ResourceError { Unreachable, } +impl From for ExitError { + fn from(_error: ResourceError) -> Self { + ExitError::OutOfGas + } +} + /// A struct that keeps track of resource usage and limit. pub struct Resource { limit: T, @@ -521,22 +527,11 @@ impl ResourceInfo { Ok(()) } - /// Computes the effective gas for the transaction. Effective gas is the maximum between the - /// gas used and resource usage. pub fn effective_gas(&self, gas: u64) -> U256 { - let proof_size_usage = self - .proof_size_meter - .as_ref() - .map_or(0, |meter| meter.usage()) - .saturating_mul(T::GasLimitPovSizeRatio::get()); - - // TODO: use the actual ref time usage - // let ref_time_usage = self - // .ref_time_meter - // .map_or(0, |meter| meter.usage()) - // .saturating_mul(T::GasLimitPovRefTimeRatio::get()); - - // TODO get the Storage Gas ratio + let proof_size_usage = self.proof_size_meter.as_ref().map_or(0, |meter| { + meter.usage().saturating_mul(T::GasLimitPovSizeRatio::get()) + }); + let storage_usage = self.storage_meter.as_ref().map_or(0, |meter| meter.usage()); let effective_gas = @@ -544,6 +539,12 @@ impl ResourceInfo { U256::from(effective_gas) } + + pub fn is_empty(&self) -> bool { + self.ref_time_meter.is_none() + && self.proof_size_meter.is_none() + && self.storage_meter.is_none() + } } #[cfg(test)] diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 29fd014678..e1fe150023 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -154,37 +154,28 @@ where let mut resource_info = ResourceInfo::new(); + let map_error = |_e| RunnerError { + error: Error::::Undefined, + weight, + }; + if let Some(weight_limit) = weight_limit { resource_info .add_ref_time_meter(weight_limit.ref_time()) - .map_err(|_| RunnerError { - error: Error::::Undefined, - weight, - })?; + .map_err(map_error)?; if let Some(proof_size_base_cost) = proof_size_base_cost { resource_info .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) - .map_err(|_| RunnerError { - error: Error::::Undefined, - weight, - })?; + .map_err(map_error)?; } } - // TODO Compute the limit of storage per tx - let storage_limit = 0; resource_info - .add_storage_meter(storage_limit) - .map_err(|_| RunnerError { - error: Error::::Undefined, - weight, - })?; + .add_storage_meter(0) // TODO Compute the limit of storage per tx + .map_err(map_error)?; - let resource_info = if resource_info.ref_time_meter.is_none() - && resource_info.proof_size_meter.is_none() - && resource_info.storage_meter.is_none() - { + let resource_info = if resource_info.is_empty() { None } else { Some(resource_info) @@ -969,9 +960,7 @@ where .unwrap_or_default() as u64; if let Some(resource_info) = self.resource_info.as_mut() { - resource_info - .record_external_operation(op, size_limit) - .map_err(|_| ExitError::OutOfGas)?; + resource_info.record_external_operation(op, size_limit)? } Ok(()) @@ -991,9 +980,7 @@ where .unwrap_or_default() as u64; if let Some(resource_info) = self.resource_info.as_mut() { - resource_info - .record_external_dynamic_opcode_cost(opcode, target, size_limit) - .map_err(|_| ExitError::OutOfGas)?; + resource_info.record_external_dynamic_opcode_cost(opcode, target, size_limit)? } Ok(()) } diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index a442c9fa8e..0b02fecb5b 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -17,6 +17,7 @@ #![cfg_attr(not(feature = "std"), no_std)] #![deny(unused_crate_dependencies)] + mod precompile; mod validation; From 65d8b97e7c83dd83f1140919ad6d2fce3dcc5b58 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 9 Aug 2023 15:54:58 +0300 Subject: [PATCH 15/42] rename meter to resource --- frame/evm/src/resource.rs | 76 +++++++++++++++++------------------ frame/evm/src/runner/stack.rs | 6 +-- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index e1c63e4651..049613a06f 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -104,13 +104,13 @@ pub struct Recorded { } /// A struct that keeps track of the proof size and limit. -pub struct ProofSizeMeter { +pub struct ProofSizeResource { resource: Resource, recorded: Recorded, _marker: PhantomData, } -impl ProofSizeMeter { +impl ProofSizeResource { /// Creates a new `ProofSizeResource` instance with the given limit. pub fn new(base_cost: u64, limit: u64) -> Result { Ok(Self { @@ -339,9 +339,9 @@ impl ProofSizeMeter { } /// A struct that keeps track of the ref_time usage and limit. -pub struct RefTimeMeter(Resource); +pub struct RefTimeResource(Resource); -impl RefTimeMeter { +impl RefTimeResource { /// Creates a new `RefTimeResource` instance with the given limit. pub fn new(limit: u64) -> Result { Ok(Self(Resource::new(0, limit)?)) @@ -369,9 +369,9 @@ impl RefTimeMeter { } /// A struct that keeps track of storage usage (newly created storage) and limit. -pub struct StorageMeter(Resource); +pub struct StorageGrowthResource(Resource); -impl StorageMeter { +impl StorageGrowthResource { /// Creates a new `StorageResource` instance with the given limit. pub fn new(limit: u64) -> Result { Ok(Self(Resource::new(0, limit)?)) @@ -428,45 +428,45 @@ impl StorageMeter { #[derive(Default)] pub struct ResourceInfo { - pub ref_time_meter: Option, - pub proof_size_meter: Option>, - pub storage_meter: Option, + pub ref_time_resource: Option, + pub proof_size_resource: Option>, + pub storage_resource: Option, } impl ResourceInfo { pub fn new() -> Self { Self { - ref_time_meter: None, - proof_size_meter: None, - storage_meter: None, + ref_time_resource: None, + proof_size_resource: None, + storage_resource: None, } } - pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); + pub fn add_ref_time_resource(&mut self, limit: u64) -> Result<(), &'static str> { + self.ref_time_resource = Some(RefTimeResource::new(limit).map_err(|_| "Invalid pararesources")?); Ok(()) } - pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { - self.proof_size_meter = - Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); + pub fn add_proof_size_resource(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { + self.proof_size_resource = + Some(ProofSizeResource::new(base_cost, limit).map_err(|_| "Invalid pararesources")?); Ok(()) } - pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.storage_meter = Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); + pub fn add_storage_growth_resource(&mut self, limit: u64) -> Result<(), &'static str> { + self.storage_resource = Some(StorageGrowthResource::new(limit).map_err(|_| "Invalid pararesources")?); Ok(()) } pub fn refund_proof_size(&mut self, amount: u64) { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.refund(amount); + if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { + proof_size_resource.refund(amount); } } pub fn refund_ref_time(&mut self, amount: u64) { - if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { - ref_time_meter.refund(amount); + if let Some(ref_time_resource) = self.ref_time_resource.as_mut() { + ref_time_resource.refund(amount); } } @@ -481,8 +481,8 @@ impl ResourceInfo { }; } - let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_meter); - let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_meter); + let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_resource); + let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_resource); WeightInfo { proof_size_usage, @@ -497,12 +497,12 @@ impl ResourceInfo { operation: evm::ExternalOperation, contract_size_limit: u64, ) -> Result<(), ResourceError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.record_external_operation(&operation, contract_size_limit)?; + if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { + proof_size_resource.record_external_operation(&operation, contract_size_limit)?; } - if let Some(storage_meter) = self.storage_meter.as_mut() { - storage_meter.record_external_operation(&operation, contract_size_limit) + if let Some(storage_resource) = self.storage_resource.as_mut() { + storage_resource.record_external_operation(&operation, contract_size_limit) } Ok(()) @@ -514,8 +514,8 @@ impl ResourceInfo { target: evm::gasometer::StorageTarget, contract_size_limit: u64, ) -> Result<(), ResourceError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.record_external_dynamic_opcode_cost( + if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { + proof_size_resource.record_external_dynamic_opcode_cost( opcode, target, contract_size_limit, @@ -528,11 +528,11 @@ impl ResourceInfo { } pub fn effective_gas(&self, gas: u64) -> U256 { - let proof_size_usage = self.proof_size_meter.as_ref().map_or(0, |meter| { - meter.usage().saturating_mul(T::GasLimitPovSizeRatio::get()) + let proof_size_usage = self.proof_size_resource.as_ref().map_or(0, |resource| { + resource.usage().saturating_mul(T::GasLimitPovSizeRatio::get()) }); - let storage_usage = self.storage_meter.as_ref().map_or(0, |meter| meter.usage()); + let storage_usage = self.storage_resource.as_ref().map_or(0, |resource| resource.usage()); let effective_gas = sp_std::cmp::max(sp_std::cmp::max(proof_size_usage, storage_usage), gas); @@ -541,9 +541,9 @@ impl ResourceInfo { } pub fn is_empty(&self) -> bool { - self.ref_time_meter.is_none() - && self.proof_size_meter.is_none() - && self.storage_meter.is_none() + self.ref_time_resource.is_none() + && self.proof_size_resource.is_none() + && self.storage_resource.is_none() } } @@ -590,7 +590,7 @@ mod tests { #[test] fn test_storage_resource() { - let mut resource = StorageMeter::new(100).unwrap(); + let mut resource = StorageGrowthResource::new(100).unwrap(); assert_eq!(resource.0.usage, 0); assert_eq!(resource.0.limit, 100); assert_eq!(resource.0.record_cost(10), Ok(())); diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index e1fe150023..a78c2c52ed 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -161,18 +161,18 @@ where if let Some(weight_limit) = weight_limit { resource_info - .add_ref_time_meter(weight_limit.ref_time()) + .add_ref_time_resource(weight_limit.ref_time()) .map_err(map_error)?; if let Some(proof_size_base_cost) = proof_size_base_cost { resource_info - .add_proof_size_meter(proof_size_base_cost, weight_limit.proof_size()) + .add_proof_size_resource(proof_size_base_cost, weight_limit.proof_size()) .map_err(map_error)?; } } resource_info - .add_storage_meter(0) // TODO Compute the limit of storage per tx + .add_storage_growth_resource(0) // TODO Compute the limit of storage per tx .map_err(map_error)?; let resource_info = if resource_info.is_empty() { From 6a344db922e639c0c8d89d41e52f8a1a28edf660 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 10 Aug 2023 15:35:04 +0300 Subject: [PATCH 16/42] compute new storage created by SSTORE --- frame/evm/src/resource.rs | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index 049613a06f..f914b049df 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -20,6 +20,8 @@ pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; pub const WRITE_PROOF_SIZE: u64 = 32; /// Account basic proof size + 5 bytes max of `decode_len` call. pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; +/// Storage: Set a new value. (32 bytes) +pub const STORAGE_NEW_COST: u64 = 32; #[derive(Debug, PartialEq)] /// Resource error. @@ -406,9 +408,16 @@ impl StorageGrowthResource { // len in bytes ?? len.try_into().map_err(|_| ResourceError::LimitExceeded)? } - GasCost::SStore { .. } => { - // TODO record cost for sstore - 0 + GasCost::SStore { + current, + new, + .. + } => { + if current.is_zero() && !new.is_zero() { + STORAGE_NEW_COST + } else { + 0 + } } _ => return Ok(()), }; @@ -443,18 +452,24 @@ impl ResourceInfo { } pub fn add_ref_time_resource(&mut self, limit: u64) -> Result<(), &'static str> { - self.ref_time_resource = Some(RefTimeResource::new(limit).map_err(|_| "Invalid pararesources")?); + self.ref_time_resource = + Some(RefTimeResource::new(limit).map_err(|_| "Invalid parameters")?); Ok(()) } - pub fn add_proof_size_resource(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { + pub fn add_proof_size_resource( + &mut self, + base_cost: u64, + limit: u64, + ) -> Result<(), &'static str> { self.proof_size_resource = - Some(ProofSizeResource::new(base_cost, limit).map_err(|_| "Invalid pararesources")?); + Some(ProofSizeResource::new(base_cost, limit).map_err(|_| "Invalid parameters")?); Ok(()) } pub fn add_storage_growth_resource(&mut self, limit: u64) -> Result<(), &'static str> { - self.storage_resource = Some(StorageGrowthResource::new(limit).map_err(|_| "Invalid pararesources")?); + self.storage_resource = + Some(StorageGrowthResource::new(limit).map_err(|_| "Invalid parameters")?); Ok(()) } @@ -529,10 +544,15 @@ impl ResourceInfo { pub fn effective_gas(&self, gas: u64) -> U256 { let proof_size_usage = self.proof_size_resource.as_ref().map_or(0, |resource| { - resource.usage().saturating_mul(T::GasLimitPovSizeRatio::get()) + resource + .usage() + .saturating_mul(T::GasLimitPovSizeRatio::get()) }); - let storage_usage = self.storage_resource.as_ref().map_or(0, |resource| resource.usage()); + let storage_usage = self + .storage_resource + .as_ref() + .map_or(0, |resource| resource.usage()); let effective_gas = sp_std::cmp::max(sp_std::cmp::max(proof_size_usage, storage_usage), gas); From dea63f63f84e263553033ce72b1ddd140273b74a Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 14 Aug 2023 14:53:23 +0300 Subject: [PATCH 17/42] revert changes --- frame/evm/src/lib.rs | 3 +- frame/evm/src/resource.rs | 100 ++++++----- frame/evm/src/runner/stack.rs | 313 ++++++++++++++++++++++++++++------ frame/evm/src/tests.rs | 14 +- primitives/evm/src/lib.rs | 87 ++++++++++ 5 files changed, 403 insertions(+), 114 deletions(-) diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index 1e207f2a5d..64cda889e4 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -60,7 +60,6 @@ pub mod benchmarking; #[cfg(test)] mod mock; -pub mod resource; pub mod runner; #[cfg(test)] mod tests; @@ -1047,4 +1046,4 @@ impl OnCreate for Tuple { Tuple::on_create(owner, contract); )*) } -} +} \ No newline at end of file diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs index f914b049df..415d590821 100644 --- a/frame/evm/src/resource.rs +++ b/frame/evm/src/resource.rs @@ -106,13 +106,13 @@ pub struct Recorded { } /// A struct that keeps track of the proof size and limit. -pub struct ProofSizeResource { +pub struct ProofSizeMeter { resource: Resource, recorded: Recorded, _marker: PhantomData, } -impl ProofSizeResource { +impl ProofSizeMeter { /// Creates a new `ProofSizeResource` instance with the given limit. pub fn new(base_cost: u64, limit: u64) -> Result { Ok(Self { @@ -341,9 +341,9 @@ impl ProofSizeResource { } /// A struct that keeps track of the ref_time usage and limit. -pub struct RefTimeResource(Resource); +pub struct RefTimeMeter(Resource); -impl RefTimeResource { +impl RefTimeMeter { /// Creates a new `RefTimeResource` instance with the given limit. pub fn new(limit: u64) -> Result { Ok(Self(Resource::new(0, limit)?)) @@ -371,12 +371,15 @@ impl RefTimeResource { } /// A struct that keeps track of storage usage (newly created storage) and limit. -pub struct StorageGrowthResource(Resource); +pub struct StorageMeter { + usage: u64, + limit: u64, +} -impl StorageGrowthResource { +impl StorageMeter { /// Creates a new `StorageResource` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Resource::new(0, limit)?)) + pub fn new(limit: u64) -> Self { + Self(Resource::new(0, limit)) } /// Refunds the given amount of storage. @@ -389,6 +392,11 @@ impl StorageGrowthResource { self.0.usage() } + /// Increments the storage usage by the given amount. + pub fn record(&mut self, cost: u64) -> Result<(), ResourceError> { + self.0.record_cost(cost) + } + /// Records the dynamic opcode cost and updates the storage usage. /// /// # Errors @@ -408,11 +416,7 @@ impl StorageGrowthResource { // len in bytes ?? len.try_into().map_err(|_| ResourceError::LimitExceeded)? } - GasCost::SStore { - current, - new, - .. - } => { + GasCost::SStore { current, new, .. } => { if current.is_zero() && !new.is_zero() { STORAGE_NEW_COST } else { @@ -437,51 +441,45 @@ impl StorageGrowthResource { #[derive(Default)] pub struct ResourceInfo { - pub ref_time_resource: Option, - pub proof_size_resource: Option>, - pub storage_resource: Option, + pub ref_time_meter: Option, + pub proof_size_meter: Option>, + pub storage_meter: Option, } impl ResourceInfo { pub fn new() -> Self { Self { - ref_time_resource: None, - proof_size_resource: None, - storage_resource: None, + ref_time_meter: None, + proof_size_meter: None, + storage_meter: None, } } - pub fn add_ref_time_resource(&mut self, limit: u64) -> Result<(), &'static str> { - self.ref_time_resource = - Some(RefTimeResource::new(limit).map_err(|_| "Invalid parameters")?); + pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); Ok(()) } - pub fn add_proof_size_resource( - &mut self, - base_cost: u64, - limit: u64, - ) -> Result<(), &'static str> { - self.proof_size_resource = - Some(ProofSizeResource::new(base_cost, limit).map_err(|_| "Invalid parameters")?); + pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { + self.proof_size_meter = + Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); Ok(()) } - pub fn add_storage_growth_resource(&mut self, limit: u64) -> Result<(), &'static str> { - self.storage_resource = - Some(StorageGrowthResource::new(limit).map_err(|_| "Invalid parameters")?); + pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { + self.storage_meter = Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); Ok(()) } pub fn refund_proof_size(&mut self, amount: u64) { - if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { - proof_size_resource.refund(amount); + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter.refund(amount); } } pub fn refund_ref_time(&mut self, amount: u64) { - if let Some(ref_time_resource) = self.ref_time_resource.as_mut() { - ref_time_resource.refund(amount); + if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { + ref_time_meter.refund(amount); } } @@ -496,8 +494,8 @@ impl ResourceInfo { }; } - let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_resource); - let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_resource); + let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_meter); + let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_meter); WeightInfo { proof_size_usage, @@ -512,12 +510,12 @@ impl ResourceInfo { operation: evm::ExternalOperation, contract_size_limit: u64, ) -> Result<(), ResourceError> { - if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { - proof_size_resource.record_external_operation(&operation, contract_size_limit)?; + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter.record_external_operation(&operation, contract_size_limit)?; } - if let Some(storage_resource) = self.storage_resource.as_mut() { - storage_resource.record_external_operation(&operation, contract_size_limit) + if let Some(storage_meter) = self.storage_meter.as_mut() { + storage_meter.record_external_operation(&operation, contract_size_limit) } Ok(()) @@ -529,8 +527,8 @@ impl ResourceInfo { target: evm::gasometer::StorageTarget, contract_size_limit: u64, ) -> Result<(), ResourceError> { - if let Some(proof_size_resource) = self.proof_size_resource.as_mut() { - proof_size_resource.record_external_dynamic_opcode_cost( + if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { + proof_size_meter.record_external_dynamic_opcode_cost( opcode, target, contract_size_limit, @@ -543,14 +541,14 @@ impl ResourceInfo { } pub fn effective_gas(&self, gas: u64) -> U256 { - let proof_size_usage = self.proof_size_resource.as_ref().map_or(0, |resource| { + let proof_size_usage = self.proof_size_meter.as_ref().map_or(0, |resource| { resource .usage() .saturating_mul(T::GasLimitPovSizeRatio::get()) }); let storage_usage = self - .storage_resource + .storage_meter .as_ref() .map_or(0, |resource| resource.usage()); @@ -561,9 +559,9 @@ impl ResourceInfo { } pub fn is_empty(&self) -> bool { - self.ref_time_resource.is_none() - && self.proof_size_resource.is_none() - && self.storage_resource.is_none() + self.ref_time_meter.is_none() + && self.proof_size_meter.is_none() + && self.storage_meter.is_none() } } @@ -609,8 +607,8 @@ mod tests { } #[test] - fn test_storage_resource() { - let mut resource = StorageGrowthResource::new(100).unwrap(); + fn test_storage_meter() { + let mut resource = StorageMeter::new(100).unwrap(); assert_eq!(resource.0.usage, 0); assert_eq!(resource.0.limit, 100); assert_eq!(resource.0.record_cost(10), Ok(())); diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index a78c2c52ed..7af212593a 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -20,7 +20,7 @@ use evm::{ backend::Backend as BackendT, executor::stack::{Accessed, StackExecutor, StackState as StackStateT, StackSubstateMetadata}, - gasometer::GasCost, + gasometer::{GasCost, StorageTarget}, ExitError, ExitReason, Opcode, Transfer, }; // Substrate @@ -42,13 +42,15 @@ use sp_std::{ }; // Frontier use fp_evm::{ - CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet, Vicinity, + AccessedStorage, CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet, + Vicinity, WeightInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, + ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, }; use crate::{ - resource::ResourceInfo, runner::Runner as RunnerT, AccountCodes, AccountStorages, - AddressMapping, BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, - OnChargeEVMTransaction, OnCreate, Pallet, RunnerError, + runner::Runner as RunnerT, AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, + BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, + OnCreate, Pallet, RunnerError, }; #[cfg(feature = "forbid-evm-reentrancy")] @@ -151,36 +153,13 @@ where R: Default, { // Used to record the external costs in the evm through the StackState implementation - - let mut resource_info = ResourceInfo::new(); - - let map_error = |_e| RunnerError { - error: Error::::Undefined, - weight, - }; - - if let Some(weight_limit) = weight_limit { - resource_info - .add_ref_time_resource(weight_limit.ref_time()) - .map_err(map_error)?; - - if let Some(proof_size_base_cost) = proof_size_base_cost { - resource_info - .add_proof_size_resource(proof_size_base_cost, weight_limit.proof_size()) - .map_err(map_error)?; - } - } - - resource_info - .add_storage_growth_resource(0) // TODO Compute the limit of storage per tx - .map_err(map_error)?; - - let resource_info = if resource_info.is_empty() { - None - } else { - Some(resource_info) - }; - + let maybe_weight_info = + WeightInfo::new_from_weight_limit(weight_limit, proof_size_base_cost).map_err( + |_| RunnerError { + error: Error::::Undefined, + weight, + }, + )?; // The precompile check is only used for transactional invocations. However, here we always // execute the check, because the check has side effects. match precompiles.is_precompile(source, gas_limit) { @@ -195,7 +174,7 @@ where standard: gas_limit.into(), effective: gas_limit.into(), }, - weight_info: resource_info.map(|r| r.weight_info()), + weight_info: maybe_weight_info, logs: Default::default(), }) } @@ -264,15 +243,21 @@ where }; let metadata = StackSubstateMetadata::new(gas_limit, config); - let state = SubstrateStackState::new(&vicinity, metadata, resource_info); + let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info); let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); let (reason, retv) = f(&mut executor); // Post execution. let used_gas = executor.used_gas(); - let effective_gas = match executor.state().resource_info() { - Some(resource_info) => resource_info.effective_gas(used_gas), + let effective_gas = match executor.state().weight_info() { + Some(weight_info) => U256::from(sp_std::cmp::max( + used_gas, + weight_info + .proof_size_usage + .unwrap_or_default() + .saturating_mul(T::GasLimitPovSizeRatio::get()), + )), _ => used_gas.into(), }; let actual_fee = effective_gas.saturating_mul(total_fee_per_gas); @@ -361,7 +346,7 @@ where standard: used_gas.into(), effective: effective_gas, }, - weight_info: state.resource_info.as_ref().map(|r| r.weight_info()), + weight_info: state.weight_info(), logs: state.substate.logs, }) } @@ -683,12 +668,19 @@ impl<'config> SubstrateStackSubstate<'config> { } } +#[derive(Default, Clone, Eq, PartialEq)] +pub struct Recorded { + account_codes: Vec, + account_storages: BTreeMap<(H160, H256), bool>, +} + /// Substrate backend for EVM. pub struct SubstrateStackState<'vicinity, 'config, T> { vicinity: &'vicinity Vicinity, substate: SubstrateStackSubstate<'config>, original_storage: BTreeMap<(H160, H256), H256>, - resource_info: Option>, + recorded: Recorded, + weight_info: Option, _marker: PhantomData, } @@ -697,7 +689,7 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { pub fn new( vicinity: &'vicinity Vicinity, metadata: StackSubstateMetadata<'config>, - resource_info: Option>, + weight_info: Option, ) -> Self { Self { vicinity, @@ -709,12 +701,21 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { }, _marker: PhantomData, original_storage: BTreeMap::new(), - resource_info, + recorded: Default::default(), + weight_info, } } - pub fn resource_info(&self) -> &Option> { - &self.resource_info + pub fn weight_info(&self) -> Option { + self.weight_info + } + + pub fn recorded(&self) -> &Recorded { + &self.recorded + } + + pub fn info_mut(&mut self) -> (&mut Option, &mut Recorded) { + (&mut self.weight_info, &mut self.recorded) } } @@ -959,10 +960,50 @@ where .create_contract_limit .unwrap_or_default() as u64; - if let Some(resource_info) = self.resource_info.as_mut() { - resource_info.record_external_operation(op, size_limit)? - } + let (weight_info, recorded) = self.info_mut(); + if let Some(weight_info) = weight_info { + match op { + evm::ExternalOperation::AccountBasicRead => { + weight_info.try_record_proof_size_or_fail(ACCOUNT_BASIC_PROOF_SIZE)? + } + evm::ExternalOperation::AddressCodeRead(address) => { + let maybe_record = !recorded.account_codes.contains(&address); + // Skip if the address has been already recorded this block + if maybe_record { + // First we record account emptiness check. + // Transfers to EOAs with standard 21_000 gas limit are able to + // pay for this pov size. + weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)?; + + if >::decode_len(address).unwrap_or(0) == 0 { + return Ok(()); + } + // Try to record fixed sized `AccountCodesMetadata` read + // Tentatively 16 + 20 + 40 + weight_info + .try_record_proof_size_or_fail(ACCOUNT_CODES_METADATA_PROOF_SIZE)?; + if let Some(meta) = >::get(address) { + weight_info.try_record_proof_size_or_fail(meta.size)?; + } else { + // If it does not exist, try to record `create_contract_limit` first. + weight_info.try_record_proof_size_or_fail(size_limit)?; + let meta = Pallet::::account_code_metadata(address); + let actual_size = meta.size; + // Refund if applies + weight_info.refund_proof_size(size_limit.saturating_sub(actual_size)); + } + recorded.account_codes.push(address); + } + } + evm::ExternalOperation::IsEmpty => { + weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)? + } + evm::ExternalOperation::Write => { + weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)? + } + }; + } Ok(()) } @@ -972,6 +1013,29 @@ where _gas_cost: GasCost, target: evm::gasometer::StorageTarget, ) -> Result<(), ExitError> { + // If account code or storage slot is in the overlay it is already accounted for and early exit + let mut accessed_storage: Option = match target { + StorageTarget::Address(address) => { + if self.recorded().account_codes.contains(&address) { + return Ok(()); + } else { + Some(AccessedStorage::AccountCodes(address)) + } + } + StorageTarget::Slot(address, index) => { + if self + .recorded() + .account_storages + .contains_key(&(address, index)) + { + return Ok(()); + } else { + Some(AccessedStorage::AccountStorages((address, index))) + } + } + _ => None, + }; + let size_limit: u64 = self .metadata() .gasometer() @@ -979,19 +1043,162 @@ where .create_contract_limit .unwrap_or_default() as u64; - if let Some(resource_info) = self.resource_info.as_mut() { - resource_info.record_external_dynamic_opcode_cost(opcode, target, size_limit)? + let (weight_info, recorded) = { + let (weight_info, recorded) = self.info_mut(); + if let Some(weight_info) = weight_info { + (weight_info, recorded) + } else { + return Ok(()); + } + }; + + // Record ref_time first + // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time + + // Record proof_size + // Return if proof size recording is disabled + let proof_size_limit = if let Some(proof_size_limit) = weight_info.proof_size_limit { + proof_size_limit + } else { + return Ok(()); + }; + + let mut maybe_record_and_refund = |with_empty_check: bool| -> Result<(), ExitError> { + let address = if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage { + address + } else { + // This must be unreachable, a valid target must be set. + // TODO decide how do we want to gracefully handle. + return Err(ExitError::OutOfGas); + }; + // First try to record fixed sized `AccountCodesMetadata` read + // Tentatively 20 + 8 + 32 + let mut base_cost = ACCOUNT_CODES_METADATA_PROOF_SIZE; + if with_empty_check { + base_cost = base_cost.saturating_add(IS_EMPTY_CHECK_PROOF_SIZE); + } + weight_info.try_record_proof_size_or_fail(base_cost)?; + if let Some(meta) = >::get(address) { + weight_info.try_record_proof_size_or_fail(meta.size)?; + } else { + // If it does not exist, try to record `create_contract_limit` first. + weight_info.try_record_proof_size_or_fail(size_limit)?; + let meta = Pallet::::account_code_metadata(address); + let actual_size = meta.size; + // Refund if applies + weight_info.refund_proof_size(size_limit.saturating_sub(actual_size)); + } + recorded.account_codes.push(address); + // Already recorded, return + Ok(()) + }; + + // Proof size is fixed length for writes (a 32-byte hash in a merkle trie), and + // the full key/value for reads. For read and writes over the same storage, the full value + // is included. + // For cold reads involving code (call, callcode, staticcall and delegatecall): + // - We depend on https://github.com/paritytech/frontier/pull/893 + // - Try to get the cached size or compute it on the fly + // - We record the actual size after caching, refunding the difference between it and the initially deducted + // contract size limit. + let opcode_proof_size = match opcode { + // Basic account fixed length + Opcode::BALANCE => { + accessed_storage = None; + U256::from(ACCOUNT_BASIC_PROOF_SIZE) + } + Opcode::EXTCODESIZE | Opcode::EXTCODECOPY | Opcode::EXTCODEHASH => { + return maybe_record_and_refund(false) + } + Opcode::CALLCODE | Opcode::CALL | Opcode::DELEGATECALL | Opcode::STATICCALL => { + return maybe_record_and_refund(true) + } + // (H160, H256) double map blake2 128 concat key size (68) + value 32 + Opcode::SLOAD => U256::from(ACCOUNT_STORAGE_PROOF_SIZE), + Opcode::SSTORE => { + let (address, index) = + if let Some(AccessedStorage::AccountStorages((address, index))) = + accessed_storage + { + (address, index) + } else { + // This must be unreachable, a valid target must be set. + // TODO decide how do we want to gracefully handle. + return Err(ExitError::OutOfGas); + }; + let mut cost = WRITE_PROOF_SIZE; + let maybe_record = !recorded.account_storages.contains_key(&(address, index)); + // If the slot is yet to be accessed we charge for it, as the evm reads + // it prior to the opcode execution. + // Skip if the address and index has been already recorded this block. + if maybe_record { + cost = cost.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE); + } + U256::from(cost) + } + // Fixed trie 32 byte hash + Opcode::CREATE | Opcode::CREATE2 => U256::from(WRITE_PROOF_SIZE), + // When calling SUICIDE a target account will receive the self destructing + // address's balance. We need to account for both: + // - Target basic account read + // - 5 bytes of `decode_len` + Opcode::SUICIDE => { + accessed_storage = None; + U256::from(IS_EMPTY_CHECK_PROOF_SIZE) + } + // Rest of dynamic opcodes that do not involve proof size recording, do nothing + _ => return Ok(()), + }; + + if opcode_proof_size > U256::from(u64::MAX) { + weight_info.try_record_proof_size_or_fail(proof_size_limit)?; + return Err(ExitError::OutOfGas); + } + + // Cache the storage access + match accessed_storage { + Some(AccessedStorage::AccountStorages((address, index))) => { + recorded.account_storages.insert((address, index), true); + } + Some(AccessedStorage::AccountCodes(address)) => { + recorded.account_codes.push(address); + } + _ => {} + } + + // Record cost + self.record_external_cost(None, Some(opcode_proof_size.low_u64()))?; + Ok(()) + } + + fn record_external_cost( + &mut self, + ref_time: Option, + proof_size: Option, + ) -> Result<(), ExitError> { + let weight_info = if let (Some(weight_info), _) = self.info_mut() { + weight_info + } else { + return Ok(()); + }; + // Record ref_time first + // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time + if let Some(amount) = ref_time { + weight_info.try_record_ref_time_or_fail(amount)?; + } + if let Some(amount) = proof_size { + weight_info.try_record_proof_size_or_fail(amount)?; } Ok(()) } fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { - if let Some(resource_info) = self.resource_info.as_mut() { + if let Some(mut weight_info) = self.weight_info { if let Some(amount) = ref_time { - resource_info.refund_ref_time(amount); + weight_info.refund_ref_time(amount); } if let Some(amount) = proof_size { - resource_info.refund_proof_size(amount); + weight_info.refund_proof_size(amount); } } } diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 3f3824ef75..bb1850708c 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -18,13 +18,8 @@ #![cfg(test)] use super::*; -use crate::{ - mock::*, - resource::{ - ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, - IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, - }, -}; +use crate::mock::*; + use frame_support::{ assert_ok, traits::{GenesisBuild, LockIdentifier, LockableCurrency, WithdrawReasons}, @@ -33,7 +28,10 @@ use std::{collections::BTreeMap, str::FromStr}; mod proof_size_test { use super::*; - use fp_evm::CreateInfo; + use fp_evm::{ + CreateInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, + ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, + }; use frame_support::traits::StorageInfoTrait; // pragma solidity ^0.8.2; // contract Callee { diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 0b02fecb5b..4a4902ba27 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -57,6 +57,22 @@ pub struct Vicinity { pub origin: H160, } +/// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) +pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; +/// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). +pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; +/// 16 (hash1) + 20 (key1) + 16 (hash2) + 32 (key2) + 32 (value) +pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; +/// Fixed trie 32 byte hash. +pub const WRITE_PROOF_SIZE: u64 = 32; +/// Account basic proof size + 5 bytes max of `decode_len` call. +pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; + +pub enum AccessedStorage { + AccountCodes(H160), + AccountStorages((H160, H256)), +} + #[derive(Clone, Copy, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct WeightInfo { @@ -66,6 +82,77 @@ pub struct WeightInfo { pub proof_size_usage: Option, } +impl WeightInfo { + pub fn new_from_weight_limit( + weight_limit: Option, + proof_size_base_cost: Option, + ) -> Result, &'static str> { + Ok(match (weight_limit, proof_size_base_cost) { + (None, _) => None, + (Some(weight_limit), Some(proof_size_base_cost)) + if weight_limit.proof_size() >= proof_size_base_cost => + { + Some(WeightInfo { + ref_time_limit: Some(weight_limit.ref_time()), + proof_size_limit: Some(weight_limit.proof_size()), + ref_time_usage: Some(0u64), + proof_size_usage: Some(proof_size_base_cost), + }) + } + (Some(weight_limit), None) => Some(WeightInfo { + ref_time_limit: Some(weight_limit.ref_time()), + proof_size_limit: None, + ref_time_usage: Some(0u64), + proof_size_usage: None, + }), + _ => return Err("must provide Some valid weight limit or None"), + }) + } + fn try_consume(&self, cost: u64, limit: u64, usage: u64) -> Result { + let usage = usage.checked_add(cost).ok_or(ExitError::OutOfGas)?; + if usage > limit { + return Err(ExitError::OutOfGas); + } + Ok(usage) + } + pub fn try_record_ref_time_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { + if let (Some(ref_time_usage), Some(ref_time_limit)) = + (self.ref_time_usage, self.ref_time_limit) + { + let ref_time_usage = self.try_consume(cost, ref_time_limit, ref_time_usage)?; + if ref_time_usage > ref_time_limit { + return Err(ExitError::OutOfGas); + } + self.ref_time_usage = Some(ref_time_usage); + } + Ok(()) + } + pub fn try_record_proof_size_or_fail(&mut self, cost: u64) -> Result<(), ExitError> { + if let (Some(proof_size_usage), Some(proof_size_limit)) = + (self.proof_size_usage, self.proof_size_limit) + { + let proof_size_usage = self.try_consume(cost, proof_size_limit, proof_size_usage)?; + if proof_size_usage > proof_size_limit { + return Err(ExitError::OutOfGas); + } + self.proof_size_usage = Some(proof_size_usage); + } + Ok(()) + } + pub fn refund_proof_size(&mut self, amount: u64) { + if let Some(proof_size_usage) = self.proof_size_usage { + let proof_size_usage = proof_size_usage.saturating_sub(amount); + self.proof_size_usage = Some(proof_size_usage); + } + } + pub fn refund_ref_time(&mut self, amount: u64) { + if let Some(ref_time_usage) = self.ref_time_usage { + let ref_time_usage = ref_time_usage.saturating_sub(amount); + self.ref_time_usage = Some(ref_time_usage); + } + } +} + #[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, TypeInfo)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct UsedGas { From 4c45d4f932088344e0aff2ae7957ba1d8eb29b22 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Thu, 17 Aug 2023 12:23:55 +0200 Subject: [PATCH 18/42] compute storage growth for opcodes --- frame/evm/src/resource.rs | 625 ---------------------------------- frame/evm/src/runner/meter.rs | 136 ++++++++ frame/evm/src/runner/mod.rs | 1 + frame/evm/src/runner/stack.rs | 60 +++- 4 files changed, 191 insertions(+), 631 deletions(-) delete mode 100644 frame/evm/src/resource.rs create mode 100644 frame/evm/src/runner/meter.rs diff --git a/frame/evm/src/resource.rs b/frame/evm/src/resource.rs deleted file mode 100644 index 415d590821..0000000000 --- a/frame/evm/src/resource.rs +++ /dev/null @@ -1,625 +0,0 @@ -use crate::{AccountCodes, AccountCodesMetadata, Config, Pallet}; - -use core::marker::PhantomData; -use evm::{ - gasometer::{GasCost, StorageTarget}, - ExitError, Opcode, -}; -use fp_evm::WeightInfo; -use sp_core::{Get, H160, H256, U256}; -use sp_runtime::{traits::CheckedAdd, Saturating}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; - -/// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) -pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; -/// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). -pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; -/// 16 (hash1) + 20 (key1) + 16 (hash2) + 32 (key2) + 32 (value) -pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; -/// Fixed trie 32 byte hash. -pub const WRITE_PROOF_SIZE: u64 = 32; -/// Account basic proof size + 5 bytes max of `decode_len` call. -pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; -/// Storage: Set a new value. (32 bytes) -pub const STORAGE_NEW_COST: u64 = 32; - -#[derive(Debug, PartialEq)] -/// Resource error. -pub enum ResourceError { - /// The Resource usage exceeds the limit. - LimitExceeded, - /// Invalid Base Cost. - InvalidBaseCost, - ///Used to indicate that the code should be unreachable. - Unreachable, -} - -impl From for ExitError { - fn from(_error: ResourceError) -> Self { - ExitError::OutOfGas - } -} - -/// A struct that keeps track of resource usage and limit. -pub struct Resource { - limit: T, - usage: T, -} - -impl Resource -where - T: CheckedAdd + Saturating + PartialOrd + Copy, -{ - /// Creates a new `Resource` instance with the given base cost and limit. - /// - /// # Errors - /// - /// Returns `ResourceError::InvalidBaseCost` if the base cost is greater than the limit. - pub fn new(base_cost: T, limit: T) -> Result { - if base_cost > limit { - return Err(ResourceError::InvalidBaseCost); - } - Ok(Self { - limit, - usage: base_cost, - }) - } - - /// Records the cost of an operation and updates the usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the Resource usage exceeds the limit. - fn record_cost(&mut self, cost: T) -> Result<(), ResourceError> { - let usage = self - .usage - .checked_add(&cost) - .ok_or(ResourceError::LimitExceeded)?; - - if usage > self.limit { - return Err(ResourceError::LimitExceeded); - } - self.usage = usage; - Ok(()) - } - - /// Refunds the given amount. - fn refund(&mut self, amount: T) { - self.usage = self.usage.saturating_sub(amount); - } - - /// Returns the usage. - fn usage(&self) -> T { - self.usage - } -} - -pub enum AccessedStorage { - AccountCodes(H160), - AccountStorages((H160, H256)), -} - -#[derive(Default, Clone, Eq, PartialEq)] -pub struct Recorded { - account_codes: Vec, - account_storages: BTreeMap<(H160, H256), bool>, -} - -/// A struct that keeps track of the proof size and limit. -pub struct ProofSizeMeter { - resource: Resource, - recorded: Recorded, - _marker: PhantomData, -} - -impl ProofSizeMeter { - /// Creates a new `ProofSizeResource` instance with the given limit. - pub fn new(base_cost: u64, limit: u64) -> Result { - Ok(Self { - resource: Resource::new(base_cost, limit)?, - recorded: Recorded::default(), - _marker: PhantomData, - }) - } - - /// Records the size of the proof and updates the usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the proof size exceeds the limit. - pub fn record_proof_size(&mut self, size: u64) -> Result<(), ResourceError> { - self.resource.record_cost(size) - } - - /// Refunds the given amount of proof size. - pub fn refund(&mut self, amount: u64) { - self.resource.refund(amount) - } - - /// Returns the proof size usage. - pub fn usage(&self) -> u64 { - self.resource.usage() - } - - /// Returns the proof size limit. - pub fn limit(&self) -> u64 { - self.resource.limit - } - - pub fn record_external_operation( - &mut self, - op: &evm::ExternalOperation, - contract_size_limit: u64, - ) -> Result<(), ResourceError> { - match op { - evm::ExternalOperation::AccountBasicRead => { - self.record_proof_size(ACCOUNT_BASIC_PROOF_SIZE)? - } - evm::ExternalOperation::AddressCodeRead(address) => { - let maybe_record = !self.recorded.account_codes.contains(address); - // Skip if the address has been already recorded this block - if maybe_record { - // First we record account emptiness check. - // Transfers to EOAs with standard 21_000 gas limit are able to - // pay for this pov size. - self.record_proof_size(IS_EMPTY_CHECK_PROOF_SIZE)?; - - if >::decode_len(address).unwrap_or(0) == 0 { - return Ok(()); - } - // Try to record fixed sized `AccountCodesMetadata` read - // Tentatively 16 + 20 + 40 - self.record_proof_size(ACCOUNT_CODES_METADATA_PROOF_SIZE)?; - if let Some(meta) = >::get(address) { - self.record_proof_size(meta.size)?; - } else { - // If it does not exist, try to record `create_contract_limit` first. - self.record_proof_size(contract_size_limit)?; - let meta = Pallet::::account_code_metadata(*address); - let actual_size = meta.size; - // Refund if applies - self.refund(contract_size_limit.saturating_sub(actual_size)); - } - self.recorded.account_codes.push(*address); - } - } - evm::ExternalOperation::IsEmpty => self.record_proof_size(IS_EMPTY_CHECK_PROOF_SIZE)?, - evm::ExternalOperation::Write => self.record_proof_size(WRITE_PROOF_SIZE)?, - }; - Ok(()) - } - - pub fn record_external_dynamic_opcode_cost( - &mut self, - opcode: Opcode, - target: evm::gasometer::StorageTarget, - contract_size_limit: u64, - ) -> Result<(), ResourceError> { - // If account code or storage slot is in the overlay it is already accounted for and early exit - let mut accessed_storage: Option = match target { - StorageTarget::Address(address) => { - if self.recorded.account_codes.contains(&address) { - return Ok(()); - } else { - Some(AccessedStorage::AccountCodes(address)) - } - } - StorageTarget::Slot(address, index) => { - if self - .recorded - .account_storages - .contains_key(&(address, index)) - { - return Ok(()); - } else { - Some(AccessedStorage::AccountStorages((address, index))) - } - } - _ => None, - }; - - let mut maybe_record_and_refund = |with_empty_check: bool| -> Result<(), ResourceError> { - let address = if let Some(AccessedStorage::AccountCodes(address)) = accessed_storage { - address - } else { - // This must be unreachable, a valid target must be set. - // TODO decide how do we want to gracefully handle. - return Err(ResourceError::Unreachable); - }; - // First try to record fixed sized `AccountCodesMetadata` read - // Tentatively 20 + 8 + 32 - let mut base_cost = ACCOUNT_CODES_METADATA_PROOF_SIZE; - if with_empty_check { - base_cost = base_cost.saturating_add(IS_EMPTY_CHECK_PROOF_SIZE); - } - self.record_proof_size(base_cost)?; - if let Some(meta) = >::get(address) { - self.record_proof_size(meta.size)?; - } else { - // If it does not exist, try to record `create_contract_limit` first. - self.record_proof_size(contract_size_limit)?; - let meta = Pallet::::account_code_metadata(address); - let actual_size = meta.size; - // Refund if applies - self.refund(contract_size_limit.saturating_sub(actual_size)); - } - self.recorded.account_codes.push(address); - // Already recorded, return - Ok(()) - }; - - // Proof size is fixed length for writes (a 32-byte hash in a merkle trie), and - // the full key/value for reads. For read and writes over the same storage, the full value - // is included. - // For cold reads involving code (call, callcode, staticcall and delegatecall): - // - We depend on https://github.com/paritytech/frontier/pull/893 - // - Try to get the cached size or compute it on the fly - // - We record the actual size after caching, refunding the difference between it and the initially deducted - // contract size limit. - let opcode_proof_size = match opcode { - // Basic account fixed length - Opcode::BALANCE => { - accessed_storage = None; - U256::from(ACCOUNT_BASIC_PROOF_SIZE) - } - Opcode::EXTCODESIZE | Opcode::EXTCODECOPY | Opcode::EXTCODEHASH => { - return maybe_record_and_refund(false) - } - Opcode::CALLCODE | Opcode::CALL | Opcode::DELEGATECALL | Opcode::STATICCALL => { - return maybe_record_and_refund(true) - } - // (H160, H256) double map blake2 128 concat key size (68) + value 32 - Opcode::SLOAD => U256::from(ACCOUNT_STORAGE_PROOF_SIZE), - Opcode::SSTORE => { - let (address, index) = - if let Some(AccessedStorage::AccountStorages((address, index))) = - accessed_storage - { - (address, index) - } else { - // This must be unreachable, a valid target must be set. - // TODO decide how do we want to gracefully handle. - return Err(ResourceError::Unreachable); - }; - let mut cost = WRITE_PROOF_SIZE; - let maybe_record = !self - .recorded - .account_storages - .contains_key(&(address, index)); - // If the slot is yet to be accessed we charge for it, as the evm reads - // it prior to the opcode execution. - // Skip if the address and index has been already recorded this block. - if maybe_record { - cost = cost.saturating_add(ACCOUNT_STORAGE_PROOF_SIZE); - } - U256::from(cost) - } - // Fixed trie 32 byte hash - Opcode::CREATE | Opcode::CREATE2 => U256::from(WRITE_PROOF_SIZE), - // When calling SUICIDE a target account will receive the self destructing - // address's balance. We need to account for both: - // - Target basic account read - // - 5 bytes of `decode_len` - Opcode::SUICIDE => { - accessed_storage = None; - U256::from(IS_EMPTY_CHECK_PROOF_SIZE) - } - // Rest of dynamic opcodes that do not involve proof size recording, do nothing - _ => return Ok(()), - }; - - if opcode_proof_size > U256::from(u64::MAX) { - self.record_proof_size(self.limit())?; - return Err(ResourceError::LimitExceeded); - } - - // Cache the storage access - match accessed_storage { - Some(AccessedStorage::AccountStorages((address, index))) => { - self.recorded - .account_storages - .insert((address, index), true); - } - Some(AccessedStorage::AccountCodes(address)) => { - self.recorded.account_codes.push(address); - } - _ => {} - } - - // Record cost - self.record_proof_size(opcode_proof_size.low_u64())?; - Ok(()) - } - - pub fn record_external_static_opcode_cost( - &mut self, - _opcode: Opcode, - _gas_cost: GasCost, - ) -> Result<(), ResourceError> { - Ok(()) - } -} - -/// A struct that keeps track of the ref_time usage and limit. -pub struct RefTimeMeter(Resource); - -impl RefTimeMeter { - /// Creates a new `RefTimeResource` instance with the given limit. - pub fn new(limit: u64) -> Result { - Ok(Self(Resource::new(0, limit)?)) - } - - /// Records the ref_time and updates the usage. - pub fn record_ref_time(&mut self, ref_time: u64) -> Result<(), ResourceError> { - self.0.record_cost(ref_time) - } - - /// Returns the ref time usage. - pub fn usage(&self) -> u64 { - self.0.usage() - } - - /// Returns the ref time limit. - pub fn limit(&self) -> u64 { - self.0.limit - } - - /// Refunds the given amount of ref_time. - pub fn refund(&mut self, amount: u64) { - self.0.refund(amount) - } -} - -/// A struct that keeps track of storage usage (newly created storage) and limit. -pub struct StorageMeter { - usage: u64, - limit: u64, -} - -impl StorageMeter { - /// Creates a new `StorageResource` instance with the given limit. - pub fn new(limit: u64) -> Self { - Self(Resource::new(0, limit)) - } - - /// Refunds the given amount of storage. - fn _refund(&mut self, amount: u64) { - self.0.refund(amount) - } - - /// Returns the storage usage. - pub fn usage(&self) -> u64 { - self.0.usage() - } - - /// Increments the storage usage by the given amount. - pub fn record(&mut self, cost: u64) -> Result<(), ResourceError> { - self.0.record_cost(cost) - } - - /// Records the dynamic opcode cost and updates the storage usage. - /// - /// # Errors - /// - /// Returns `ResourceError::LimitExceeded` if the storage usage exceeds the storage limit. - fn _record_dynamic_opcode_cost( - &mut self, - _opcode: Opcode, - gas_cost: GasCost, - ) -> Result<(), ResourceError> { - let cost = match gas_cost { - GasCost::Create => { - // TODO record cost for create - 0 - } - GasCost::Create2 { len } => { - // len in bytes ?? - len.try_into().map_err(|_| ResourceError::LimitExceeded)? - } - GasCost::SStore { current, new, .. } => { - if current.is_zero() && !new.is_zero() { - STORAGE_NEW_COST - } else { - 0 - } - } - _ => return Ok(()), - }; - self.0.record_cost(cost) - } - - fn record_external_operation( - &mut self, - operation: &evm::ExternalOperation, - _contract_size_limit: u64, - ) { - if let evm::ExternalOperation::Write = operation { - // Todo record cost for write - } - } -} - -#[derive(Default)] -pub struct ResourceInfo { - pub ref_time_meter: Option, - pub proof_size_meter: Option>, - pub storage_meter: Option, -} - -impl ResourceInfo { - pub fn new() -> Self { - Self { - ref_time_meter: None, - proof_size_meter: None, - storage_meter: None, - } - } - - pub fn add_ref_time_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.ref_time_meter = Some(RefTimeMeter::new(limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn add_proof_size_meter(&mut self, base_cost: u64, limit: u64) -> Result<(), &'static str> { - self.proof_size_meter = - Some(ProofSizeMeter::new(base_cost, limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn add_storage_meter(&mut self, limit: u64) -> Result<(), &'static str> { - self.storage_meter = Some(StorageMeter::new(limit).map_err(|_| "Invalid parameters")?); - Ok(()) - } - - pub fn refund_proof_size(&mut self, amount: u64) { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.refund(amount); - } - } - - pub fn refund_ref_time(&mut self, amount: u64) { - if let Some(ref_time_meter) = self.ref_time_meter.as_mut() { - ref_time_meter.refund(amount); - } - } - - /// Returns WeightInfo for the resource. - pub fn weight_info(&self) -> WeightInfo { - macro_rules! usage_and_limit { - ($x:expr) => { - ( - $x.as_ref().map(|x| x.usage()), - $x.as_ref().map(|x| x.limit()), - ) - }; - } - - let (proof_size_usage, proof_size_limit) = usage_and_limit!(self.proof_size_meter); - let (ref_time_usage, ref_time_limit) = usage_and_limit!(self.ref_time_meter); - - WeightInfo { - proof_size_usage, - proof_size_limit, - ref_time_usage, - ref_time_limit, - } - } - - pub fn record_external_operation( - &mut self, - operation: evm::ExternalOperation, - contract_size_limit: u64, - ) -> Result<(), ResourceError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.record_external_operation(&operation, contract_size_limit)?; - } - - if let Some(storage_meter) = self.storage_meter.as_mut() { - storage_meter.record_external_operation(&operation, contract_size_limit) - } - - Ok(()) - } - - pub fn record_external_dynamic_opcode_cost( - &mut self, - opcode: Opcode, - target: evm::gasometer::StorageTarget, - contract_size_limit: u64, - ) -> Result<(), ResourceError> { - if let Some(proof_size_meter) = self.proof_size_meter.as_mut() { - proof_size_meter.record_external_dynamic_opcode_cost( - opcode, - target, - contract_size_limit, - )?; - } - // Record ref_time - // TODO benchmark opcodes, until this is done we do used_gas to weight conversion for ref_time - - Ok(()) - } - - pub fn effective_gas(&self, gas: u64) -> U256 { - let proof_size_usage = self.proof_size_meter.as_ref().map_or(0, |resource| { - resource - .usage() - .saturating_mul(T::GasLimitPovSizeRatio::get()) - }); - - let storage_usage = self - .storage_meter - .as_ref() - .map_or(0, |resource| resource.usage()); - - let effective_gas = - sp_std::cmp::max(sp_std::cmp::max(proof_size_usage, storage_usage), gas); - - U256::from(effective_gas) - } - - pub fn is_empty(&self) -> bool { - self.ref_time_meter.is_none() - && self.proof_size_meter.is_none() - && self.storage_meter.is_none() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_init() { - let resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.limit, 100); - assert_eq!(resource.usage, 0); - - // base cost > limit - let resource = Resource::::new(100, 0).err(); - assert_eq!(resource, Some(ResourceError::InvalidBaseCost)); - } - - #[test] - fn test_record_cost() { - let mut resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.record_cost(10), Ok(())); - assert_eq!(resource.usage, 10); - assert_eq!(resource.record_cost(90), Ok(())); - assert_eq!(resource.usage, 100); - - // exceed limit - assert_eq!(resource.record_cost(1), Err(ResourceError::LimitExceeded)); - assert_eq!(resource.usage, 100); - } - - #[test] - fn test_refund() { - let mut resource = Resource::::new(0, 100).unwrap(); - assert_eq!(resource.record_cost(10), Ok(())); - assert_eq!(resource.usage, 10); - resource.refund(10); - assert_eq!(resource.usage, 0); - - // refund more than usage - resource.refund(10); - assert_eq!(resource.usage, 0); - } - - #[test] - fn test_storage_meter() { - let mut resource = StorageMeter::new(100).unwrap(); - assert_eq!(resource.0.usage, 0); - assert_eq!(resource.0.limit, 100); - assert_eq!(resource.0.record_cost(10), Ok(())); - assert_eq!(resource.0.usage, 10); - assert_eq!(resource.0.record_cost(90), Ok(())); - assert_eq!(resource.0.usage, 100); - assert_eq!(resource.0.record_cost(1), Err(ResourceError::LimitExceeded)); - assert_eq!(resource.0.usage, 100); - resource.0.refund(10); - assert_eq!(resource.0.usage, 90); - resource._refund(10); - assert_eq!(resource.0.usage, 80); - } -} diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs new file mode 100644 index 0000000000..cf4b582751 --- /dev/null +++ b/frame/evm/src/runner/meter.rs @@ -0,0 +1,136 @@ +/// The size of a storage key and storage value in bytes. +pub const STORAGE_SIZE: u64 = 64; + +/// A meter for tracking the storage growth. +#[derive(Clone, Copy)] +pub struct StorageMeter { + usage: u64, + limit: u64, +} + +/// An error that is returned when the storage limit has been exceeded. +#[derive(Debug, PartialEq)] +pub enum MeterError { + LimitExceeded, +} + +impl StorageMeter { + /// Creates a new storage meter with the given limit. + pub fn new(limit: u64) -> Self { + Self { usage: 0, limit } + } + + /// Records the given amount of storage usage. The amount is added to the current usage. + /// The usage will saturate at `u64::MAX`. + pub fn record(&mut self, amount: u64) { + self.usage = self.usage.saturating_add(amount); + } + + /// Returns the current usage of storage. + pub fn usage(&self) -> u64 { + self.usage + } + + /// Returns the amount of storage that is available before the limit is reached. + pub fn available(&self) -> u64 { + self.limit.saturating_sub(self.usage) + } + + /// Merge the given storage meter into the current one. + pub fn merge(&mut self, other: Option) { + self.usage = self + .usage + .saturating_add(other.map_or(0, |meter| meter.usage)); + } + + /// Map storage usage to the gas cost. + pub fn gas_cost(&self) -> u64 { + 0 + } + + /// Checks if the current usage of storage is within the limit. + /// + /// # Errors + /// + /// Returns `MeterError::ResourceLimitExceeded` if the limit has been exceeded. + pub fn check_limit(&self) -> Result<(), MeterError> { + if self.usage > self.limit { + Err(MeterError::LimitExceeded) + } else { + Ok(()) + } + } +} + +// Generate a comprehensive unit test suite for the StorageMeter: +// - Make sure to cover all the edge cases. +// - Group the tests into a module so that they are not included in the crate documentation. +// - Group similar or related tests in seperate functions. +// - Document the different tests explaining the use case +#[cfg(test)] +mod test { + use super::*; + + #[cfg(test)] + mod tests { + use super::*; + + /// Tests the basic functionality of StorageMeter. + #[test] + fn test_basic_functionality() { + let limit = 100; + let mut meter = StorageMeter::new(limit); + + assert_eq!(meter.usage(), 0); + assert_eq!(meter.available(), limit); + + let amount = 10; + meter.record(amount); + assert_eq!(meter.usage(), amount); + assert_eq!(meter.available(), limit - amount); + + assert!(meter.check_limit().is_ok()); + } + + /// Tests the behavior of StorageMeter when reaching the limit. + #[test] + fn test_reaching_limit() { + let limit = 100; + let mut meter = StorageMeter::new(limit); + + // Approaching the limit without exceeding + meter.record(limit - 1); + assert_eq!(meter.usage(), limit - 1); + assert!(meter.check_limit().is_ok()); + + // Reaching the limit exactly + meter.record(1); + assert_eq!(meter.usage(), limit); + assert!(meter.check_limit().is_ok()); + + // Exceeding the limit + meter.record(1); + assert_eq!(meter.usage(), limit + 1); + assert_eq!(meter.check_limit(), Err(MeterError::LimitExceeded)); + } + + /// Tests the behavior of StorageMeter with saturation. + #[test] + fn test_saturation_behavior() { + let limit = u64::MAX; + let mut meter = StorageMeter::new(limit); + + // Reaching the limit + meter.record(limit); + assert_eq!(meter.usage(), limit); + assert_eq!(meter.available(), 0); + assert!(meter.check_limit().is_ok()); + + // Exceeding the limit using saturating_add + meter.record(1); + assert_eq!(meter.usage(), limit); + assert_eq!(meter.available(), 0); + assert!(meter.check_limit().is_ok()); + } + } +} diff --git a/frame/evm/src/runner/mod.rs b/frame/evm/src/runner/mod.rs index bda922d39c..bd1304e01e 100644 --- a/frame/evm/src/runner/mod.rs +++ b/frame/evm/src/runner/mod.rs @@ -16,6 +16,7 @@ // limitations under the License. pub mod stack; +pub mod meter; use crate::{Config, Weight}; use fp_evm::{CallInfo, CreateInfo}; diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 7af212593a..c2a4bd80b0 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -32,7 +32,7 @@ use frame_support::{ weights::Weight, }; use sp_core::{H160, H256, U256}; -use sp_runtime::traits::UniqueSaturatedInto; +use sp_runtime::{traits::UniqueSaturatedInto, DispatchError}; use sp_std::{ boxed::Box, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, @@ -47,10 +47,12 @@ use fp_evm::{ ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, }; +use super::meter::StorageMeter; use crate::{ - runner::Runner as RunnerT, AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, - BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, - OnCreate, Pallet, RunnerError, + runner::{meter::STORAGE_SIZE, Runner as RunnerT}, + AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, BalanceOf, + BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, OnCreate, + Pallet, RunnerError, }; #[cfg(feature = "forbid-evm-reentrancy")] @@ -242,12 +244,21 @@ where origin: source, }; + // TODO get Storage Limit Quota for the transaction + let storage_limit = Some(u64::MAX); + let metadata = StackSubstateMetadata::new(gas_limit, config); - let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info); + let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info, storage_limit); let mut executor = StackExecutor::new_with_precompiles(state, config, precompiles); let (reason, retv) = f(&mut executor); + let storage_usage = if let Some(storage_meter) = executor.state().substate.storage_meter { + storage_meter.usage() + } else { + 0 + }; + // Post execution. let used_gas = executor.used_gas(); let effective_gas = match executor.state().weight_info() { @@ -576,6 +587,7 @@ struct SubstrateStackSubstate<'config> { deletes: BTreeSet, logs: Vec, parent: Option>>, + storage_meter: Option, } impl<'config> SubstrateStackSubstate<'config> { @@ -588,11 +600,18 @@ impl<'config> SubstrateStackSubstate<'config> { } pub fn enter(&mut self, gas_limit: u64, is_static: bool) { + let storage_meter = if let Some(meter) = self.storage_meter { + Some(StorageMeter::new(meter.available())) + } else { + None + }; + let mut entering = Self { metadata: self.metadata.spit_child(gas_limit, is_static), parent: None, deletes: BTreeSet::new(), logs: Vec::new(), + storage_meter, }; mem::swap(&mut entering, self); @@ -609,6 +628,14 @@ impl<'config> SubstrateStackSubstate<'config> { self.logs.append(&mut exited.logs); self.deletes.append(&mut exited.deletes); + if let Some(storage_meter) = self.storage_meter.as_mut() { + storage_meter.merge(exited.storage_meter); + // TODO Check for storage limit here ?? + storage_meter + .check_limit() + .map_err(|_| ExitError::OutOfGas)?; + } + sp_io::storage::commit_transaction(); Ok(()) } @@ -690,7 +717,9 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { vicinity: &'vicinity Vicinity, metadata: StackSubstateMetadata<'config>, weight_info: Option, + storage_limit: Option, ) -> Self { + let storage_meter = storage_limit.map(|limit| StorageMeter::new(limit)); Self { vicinity, substate: SubstrateStackSubstate { @@ -698,6 +727,7 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { deletes: BTreeSet::new(), logs: Vec::new(), parent: None, + storage_meter, }, _marker: PhantomData, original_storage: BTreeMap::new(), @@ -851,8 +881,9 @@ where // We cache the current value if this is the first time we modify it // in the transaction. use sp_std::collections::btree_map::Entry::Vacant; + let original = >::get(address, index); + if let Vacant(e) = self.original_storage.entry((address, index)) { - let original = >::get(address, index); // No need to cache if same value. if original != value { e.insert(original); @@ -877,6 +908,15 @@ where value, ); >::insert(address, index, value); + + // If the original value was zero, and the new value is non-zero, + // then this is a new storage entry. We need to record the storage + // size. + if original.is_zero() { + if let Some(storage_meter) = self.substate.storage_meter.as_mut() { + storage_meter.record(STORAGE_SIZE); + } + } } } @@ -900,6 +940,14 @@ where code.len(), address ); + + if let Some(storage_meter) = self.substate.storage_meter.as_mut() { + let storage_usage = code.len() as u64; + + // TODO record storage usage for new account code creation + storage_meter.record(storage_usage); + } + Pallet::::create_account(address, code); } From cea9817372c6ec2f40c48471a410750e04ee0d40 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 18 Aug 2023 08:50:17 +0200 Subject: [PATCH 19/42] compute the storage quota per tx --- frame/ethereum/src/mock.rs | 4 +++ frame/evm/precompile/dispatch/src/mock.rs | 1 + frame/evm/src/lib.rs | 3 +++ frame/evm/src/mock.rs | 4 +++ frame/evm/src/runner/meter.rs | 4 +-- frame/evm/src/runner/stack.rs | 33 ++++++++++++++--------- template/runtime/src/lib.rs | 4 +++ 7 files changed, 39 insertions(+), 14 deletions(-) diff --git a/frame/ethereum/src/mock.rs b/frame/ethereum/src/mock.rs index fd611838f6..97616a3671 100644 --- a/frame/ethereum/src/mock.rs +++ b/frame/ethereum/src/mock.rs @@ -140,6 +140,8 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +/// The maximum storage growth per block in bytes (40 Kb). +const MAX_STORAGE_GROWTH: u64 = 40 * 1024; parameter_types! { pub const TransactionByteFee: u64 = 1; @@ -147,6 +149,7 @@ parameter_types! { pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa"); pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub const WeightPerGas: Weight = Weight::from_parts(20_000, 0); } @@ -178,6 +181,7 @@ impl pallet_evm::Config for Test { type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; type Timestamp = Timestamp; type WeightInfo = (); } diff --git a/frame/evm/precompile/dispatch/src/mock.rs b/frame/evm/precompile/dispatch/src/mock.rs index 81cf81ac48..f1f68f111d 100644 --- a/frame/evm/precompile/dispatch/src/mock.rs +++ b/frame/evm/precompile/dispatch/src/mock.rs @@ -164,6 +164,7 @@ impl pallet_evm::Config for Test { type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = (); + type GasLimitStorageGrowthRatio = (); type Timestamp = Timestamp; type WeightInfo = (); } diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index 64cda889e4..aad65b0338 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -167,6 +167,9 @@ pub mod pallet { /// Gas limit Pov size ratio. type GasLimitPovSizeRatio: Get; + /// Gas limit storage growth ratio. + type GasLimitStorageGrowthRatio: Get; + /// Get the timestamp for the current block. type Timestamp: Time; diff --git a/frame/evm/src/mock.rs b/frame/evm/src/mock.rs index ddf9f9062a..2664495be6 100644 --- a/frame/evm/src/mock.rs +++ b/frame/evm/src/mock.rs @@ -131,10 +131,13 @@ impl FindAuthor for FindAuthorTruncated { } const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +/// The maximum storage growth per block in bytes (40 Kb). +const MAX_STORAGE_GROWTH: u64 = 40 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; } @@ -160,6 +163,7 @@ impl crate::Config for Test { type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; type Timestamp = Timestamp; type WeightInfo = (); } diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index cf4b582751..9f36dd7819 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -44,8 +44,8 @@ impl StorageMeter { } /// Map storage usage to the gas cost. - pub fn gas_cost(&self) -> u64 { - 0 + pub fn storage_to_gas(&self, ratio: u64) -> u64 { + self.usage.saturating_mul(ratio) } /// Checks if the current usage of storage is within the limit. diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index c2a4bd80b0..a2f3da30d1 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -32,7 +32,7 @@ use frame_support::{ weights::Weight, }; use sp_core::{H160, H256, U256}; -use sp_runtime::{traits::UniqueSaturatedInto, DispatchError}; +use sp_runtime::traits::UniqueSaturatedInto; use sp_std::{ boxed::Box, collections::{btree_map::BTreeMap, btree_set::BTreeSet}, @@ -244,8 +244,14 @@ where origin: source, }; - // TODO get Storage Limit Quota for the transaction - let storage_limit = Some(u64::MAX); + // Compute the storage limit based on the gas limit and the storage growth ratio. + let storage_growth_ratio = T::GasLimitStorageGrowthRatio::get(); + let storage_limit = if storage_growth_ratio > 0 { + let storage_limit = gas_limit.saturating_div(storage_growth_ratio); + Some(storage_limit) + } else { + None + }; let metadata = StackSubstateMetadata::new(gas_limit, config); let state = SubstrateStackState::new(&vicinity, metadata, maybe_weight_info, storage_limit); @@ -253,21 +259,24 @@ where let (reason, retv) = f(&mut executor); - let storage_usage = if let Some(storage_meter) = executor.state().substate.storage_meter { - storage_meter.usage() - } else { - 0 + // Compute the storage gas cost based on the storage growth. + let storage_gas = match executor.state().substate.storage_meter { + Some(storage_meter) => storage_meter.storage_to_gas(storage_growth_ratio), + None => 0, }; // Post execution. let used_gas = executor.used_gas(); let effective_gas = match executor.state().weight_info() { Some(weight_info) => U256::from(sp_std::cmp::max( - used_gas, - weight_info - .proof_size_usage - .unwrap_or_default() - .saturating_mul(T::GasLimitPovSizeRatio::get()), + sp_std::cmp::max( + used_gas, + weight_info + .proof_size_usage + .unwrap_or_default() + .saturating_mul(T::GasLimitPovSizeRatio::get()), + ), + storage_gas, )), _ => used_gas.into(), }; diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index f10d675654..8745997faa 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -319,10 +319,13 @@ impl> FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 75_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; +/// The maximum storage growth per block in bytes (40 Kb). +const MAX_STORAGE_GROWTH: u64 = 40 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); } @@ -346,6 +349,7 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type FindAuthor = FindAuthorTruncated; type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type GasLimitStorageGrowthRatio = GasLimitStorageGrowthRatio; type Timestamp = Timestamp; type WeightInfo = pallet_evm::weights::SubstrateWeight; } From e41264396e0722935e02bdd84b2d55738174602a Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Sun, 20 Aug 2023 14:38:10 +0200 Subject: [PATCH 20/42] pin evm temporarily --- Cargo.lock | 8 ++++---- Cargo.toml | 3 ++- frame/evm/src/runner/stack.rs | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b19d96af0..36099a9025 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,7 +2055,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" version = "0.39.1" -source = "git+https://github.com/rust-blockchain/evm?rev=b7b82c7e1fc57b7449d6dfa6826600de37cc1e65#b7b82c7e1fc57b7449d6dfa6826600de37cc1e65" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" dependencies = [ "auto_impl", "environmental", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=b7b82c7e1fc57b7449d6dfa6826600de37cc1e65#b7b82c7e1fc57b7449d6dfa6826600de37cc1e65" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2086,7 +2086,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=b7b82c7e1fc57b7449d6dfa6826600de37cc1e65#b7b82c7e1fc57b7449d6dfa6826600de37cc1e65" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" dependencies = [ "environmental", "evm-core", @@ -2097,7 +2097,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=b7b82c7e1fc57b7449d6dfa6826600de37cc1e65#b7b82c7e1fc57b7449d6dfa6826600de37cc1e65" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" dependencies = [ "auto_impl", "environmental", diff --git a/Cargo.toml b/Cargo.toml index 9cdd1b7c4a..97387ef734 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,8 @@ clap = { version = "4.3", features = ["derive", "deprecated"] } environmental = { version = "1.1.4", default-features = false } ethereum = { version = "0.14.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -evm = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false } +# evm = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false } +evm = { git = "https://github.com/moonbeam-foundation/evm.git", branch = "ahmad-update-external-operation", default-features = false } futures = "0.3.28" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex-literal = "0.4.1" diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index a2f3da30d1..8592244471 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -1056,8 +1056,8 @@ where evm::ExternalOperation::IsEmpty => { weight_info.try_record_proof_size_or_fail(IS_EMPTY_CHECK_PROOF_SIZE)? } - evm::ExternalOperation::Write => { - weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)? + evm::ExternalOperation::Write(len) => { + weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)?; } }; } From dbf03709ebbfa23d3652d6fcbe2f8e6deed81714 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Sun, 20 Aug 2023 14:43:07 +0200 Subject: [PATCH 21/42] record storage growth for opcodes --- frame/evm/src/runner/meter.rs | 195 +++++++++++++++++++--------------- frame/evm/src/runner/stack.rs | 73 +++++-------- primitives/evm/src/lib.rs | 2 + 3 files changed, 137 insertions(+), 133 deletions(-) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index 9f36dd7819..fa047d471f 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -1,5 +1,6 @@ -/// The size of a storage key and storage value in bytes. -pub const STORAGE_SIZE: u64 = 64; +use evm::{gasometer::GasCost, Opcode}; +use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE; +use sp_core::H256; /// A meter for tracking the storage growth. #[derive(Clone, Copy)] @@ -21,9 +22,33 @@ impl StorageMeter { } /// Records the given amount of storage usage. The amount is added to the current usage. - /// The usage will saturate at `u64::MAX`. - pub fn record(&mut self, amount: u64) { - self.usage = self.usage.saturating_add(amount); + /// If the limit is reached, an error is returned. + pub fn record(&mut self, amount: u64) -> Result<(), MeterError> { + self.usage = self.usage.checked_add(amount).ok_or_else(|| { + self.usage = self.limit; + MeterError::LimitExceeded + })?; + + if self.usage > self.limit { + return Err(MeterError::LimitExceeded); + } + Ok(()) + } + + /// Records the storage growth for the given Opcode. + pub fn record_dynamic_opcode_cost( + &mut self, + _opcode: Opcode, + gas_cost: GasCost, + ) -> Result<(), MeterError> { + match gas_cost { + GasCost::SStore { original, new, .. } + if original == H256::default() && !new.is_zero() => + { + self.record(ACCOUNT_STORAGE_PROOF_SIZE) + } + _ => Ok(()), + } } /// Returns the current usage of storage. @@ -31,106 +56,100 @@ impl StorageMeter { self.usage } + /// Returns the limit of storage. + pub fn limit(&self) -> u64 { + self.limit + } + /// Returns the amount of storage that is available before the limit is reached. pub fn available(&self) -> u64 { self.limit.saturating_sub(self.usage) } - /// Merge the given storage meter into the current one. - pub fn merge(&mut self, other: Option) { - self.usage = self - .usage - .saturating_add(other.map_or(0, |meter| meter.usage)); - } - /// Map storage usage to the gas cost. pub fn storage_to_gas(&self, ratio: u64) -> u64 { self.usage.saturating_mul(ratio) } - - /// Checks if the current usage of storage is within the limit. - /// - /// # Errors - /// - /// Returns `MeterError::ResourceLimitExceeded` if the limit has been exceeded. - pub fn check_limit(&self) -> Result<(), MeterError> { - if self.usage > self.limit { - Err(MeterError::LimitExceeded) - } else { - Ok(()) - } - } } - -// Generate a comprehensive unit test suite for the StorageMeter: -// - Make sure to cover all the edge cases. -// - Group the tests into a module so that they are not included in the crate documentation. -// - Group similar or related tests in seperate functions. -// - Document the different tests explaining the use case #[cfg(test)] mod test { use super::*; - #[cfg(test)] - mod tests { - use super::*; - - /// Tests the basic functionality of StorageMeter. - #[test] - fn test_basic_functionality() { - let limit = 100; - let mut meter = StorageMeter::new(limit); - - assert_eq!(meter.usage(), 0); - assert_eq!(meter.available(), limit); + /// Tests the basic functionality of StorageMeter. + #[test] + fn test_basic_functionality() { + let limit = 100; + let mut meter = StorageMeter::new(limit); - let amount = 10; - meter.record(amount); - assert_eq!(meter.usage(), amount); - assert_eq!(meter.available(), limit - amount); + assert_eq!(meter.usage(), 0); + assert_eq!(meter.limit(), limit); - assert!(meter.check_limit().is_ok()); - } + let amount = 10; + meter.record(amount).unwrap(); + assert_eq!(meter.usage(), amount); + } - /// Tests the behavior of StorageMeter when reaching the limit. - #[test] - fn test_reaching_limit() { - let limit = 100; - let mut meter = StorageMeter::new(limit); - - // Approaching the limit without exceeding - meter.record(limit - 1); - assert_eq!(meter.usage(), limit - 1); - assert!(meter.check_limit().is_ok()); - - // Reaching the limit exactly - meter.record(1); - assert_eq!(meter.usage(), limit); - assert!(meter.check_limit().is_ok()); - - // Exceeding the limit - meter.record(1); - assert_eq!(meter.usage(), limit + 1); - assert_eq!(meter.check_limit(), Err(MeterError::LimitExceeded)); - } + /// Tests the behavior of StorageMeter when reaching the limit. + #[test] + fn test_reaching_limit() { + let limit = 100; + let mut meter = StorageMeter::new(limit); + + // Approaching the limit without exceeding + meter.record(limit - 1).unwrap(); + assert_eq!(meter.usage(), limit - 1); + + // Reaching the limit exactly + meter.record(1).unwrap(); + assert_eq!(meter.usage(), limit); + + // Exceeding the limit + let res = meter.record(1); + assert_eq!(meter.usage(), limit + 1); + assert!(res.is_err()); + assert_eq!(res, Err(MeterError::LimitExceeded)); + } - /// Tests the behavior of StorageMeter with saturation. - #[test] - fn test_saturation_behavior() { - let limit = u64::MAX; - let mut meter = StorageMeter::new(limit); - - // Reaching the limit - meter.record(limit); - assert_eq!(meter.usage(), limit); - assert_eq!(meter.available(), 0); - assert!(meter.check_limit().is_ok()); - - // Exceeding the limit using saturating_add - meter.record(1); - assert_eq!(meter.usage(), limit); - assert_eq!(meter.available(), 0); - assert!(meter.check_limit().is_ok()); - } + /// Tests the record of dynamic opcode cost. + #[test] + fn test_record_dynamic_opcode_cost() { + let limit = 200; + let mut meter = StorageMeter::new(limit); + + // Existing storage entry is updated. No change in storage growth. + let gas_cost = GasCost::SStore { + original: H256::from_low_u64_be(1), + current: Default::default(), + new: H256::from_low_u64_be(2), + target_is_cold: false, + }; + meter + .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost) + .unwrap(); + assert_eq!(meter.usage(), 0); + + // New storage entry is created. Storage growth is recorded. + let gas_cost = GasCost::SStore { + original: H256::default(), + current: Default::default(), + new: H256::from_low_u64_be(1), + target_is_cold: false, + }; + meter + .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost) + .unwrap(); + assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE); + + // New storage entry is created. Storage growth is recorded. The limit is reached. + let gas_cost = GasCost::SStore { + original: H256::default(), + current: Default::default(), + new: H256::from_low_u64_be(2), + target_is_cold: false, + }; + let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost); + assert!(res.is_err()); + assert_eq!(res, Err(MeterError::LimitExceeded)); + assert_eq!(meter.usage(), 232); } } diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 8592244471..fef697335a 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -43,16 +43,16 @@ use sp_std::{ // Frontier use fp_evm::{ AccessedStorage, CallInfo, CreateInfo, ExecutionInfoV2, IsPrecompileResult, Log, PrecompileSet, - Vicinity, WeightInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, - ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, WRITE_PROOF_SIZE, + Vicinity, WeightInfo, ACCOUNT_BASIC_PROOF_SIZE, ACCOUNT_CODES_KEY_SIZE, + ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, IS_EMPTY_CHECK_PROOF_SIZE, + WRITE_PROOF_SIZE, }; use super::meter::StorageMeter; use crate::{ - runner::{meter::STORAGE_SIZE, Runner as RunnerT}, - AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, BalanceOf, - BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, OnCreate, - Pallet, RunnerError, + runner::Runner as RunnerT, AccountCodes, AccountCodesMetadata, AccountStorages, AddressMapping, + BalanceOf, BlockHashMapping, Config, Error, Event, FeeCalculator, OnChargeEVMTransaction, + OnCreate, Pallet, RunnerError, }; #[cfg(feature = "forbid-evm-reentrancy")] @@ -260,7 +260,7 @@ where let (reason, retv) = f(&mut executor); // Compute the storage gas cost based on the storage growth. - let storage_gas = match executor.state().substate.storage_meter { + let storage_gas = match executor.state().storage_meter { Some(storage_meter) => storage_meter.storage_to_gas(storage_growth_ratio), None => 0, }; @@ -596,7 +596,6 @@ struct SubstrateStackSubstate<'config> { deletes: BTreeSet, logs: Vec, parent: Option>>, - storage_meter: Option, } impl<'config> SubstrateStackSubstate<'config> { @@ -609,18 +608,11 @@ impl<'config> SubstrateStackSubstate<'config> { } pub fn enter(&mut self, gas_limit: u64, is_static: bool) { - let storage_meter = if let Some(meter) = self.storage_meter { - Some(StorageMeter::new(meter.available())) - } else { - None - }; - let mut entering = Self { metadata: self.metadata.spit_child(gas_limit, is_static), parent: None, deletes: BTreeSet::new(), logs: Vec::new(), - storage_meter, }; mem::swap(&mut entering, self); @@ -637,14 +629,6 @@ impl<'config> SubstrateStackSubstate<'config> { self.logs.append(&mut exited.logs); self.deletes.append(&mut exited.deletes); - if let Some(storage_meter) = self.storage_meter.as_mut() { - storage_meter.merge(exited.storage_meter); - // TODO Check for storage limit here ?? - storage_meter - .check_limit() - .map_err(|_| ExitError::OutOfGas)?; - } - sp_io::storage::commit_transaction(); Ok(()) } @@ -717,6 +701,7 @@ pub struct SubstrateStackState<'vicinity, 'config, T> { original_storage: BTreeMap<(H160, H256), H256>, recorded: Recorded, weight_info: Option, + storage_meter: Option, _marker: PhantomData, } @@ -728,7 +713,7 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { weight_info: Option, storage_limit: Option, ) -> Self { - let storage_meter = storage_limit.map(|limit| StorageMeter::new(limit)); + let storage_meter = storage_limit.map(StorageMeter::new); Self { vicinity, substate: SubstrateStackSubstate { @@ -736,12 +721,12 @@ impl<'vicinity, 'config, T: Config> SubstrateStackState<'vicinity, 'config, T> { deletes: BTreeSet::new(), logs: Vec::new(), parent: None, - storage_meter, }, _marker: PhantomData, original_storage: BTreeMap::new(), recorded: Default::default(), weight_info, + storage_meter, } } @@ -890,9 +875,8 @@ where // We cache the current value if this is the first time we modify it // in the transaction. use sp_std::collections::btree_map::Entry::Vacant; - let original = >::get(address, index); - if let Vacant(e) = self.original_storage.entry((address, index)) { + let original = >::get(address, index); // No need to cache if same value. if original != value { e.insert(original); @@ -917,15 +901,6 @@ where value, ); >::insert(address, index, value); - - // If the original value was zero, and the new value is non-zero, - // then this is a new storage entry. We need to record the storage - // size. - if original.is_zero() { - if let Some(storage_meter) = self.substate.storage_meter.as_mut() { - storage_meter.record(STORAGE_SIZE); - } - } } } @@ -949,14 +924,6 @@ where code.len(), address ); - - if let Some(storage_meter) = self.substate.storage_meter.as_mut() { - let storage_usage = code.len() as u64; - - // TODO record storage usage for new account code creation - storage_meter.record(storage_usage); - } - Pallet::::create_account(address, code); } @@ -1058,6 +1025,16 @@ where } evm::ExternalOperation::Write(len) => { weight_info.try_record_proof_size_or_fail(WRITE_PROOF_SIZE)?; + + if let Some(storage_meter) = self.storage_meter.as_mut() { + // Record the number of bytes written to storage when deploying a contract. + let storage_growth = ACCOUNT_CODES_KEY_SIZE + + ACCOUNT_CODES_METADATA_PROOF_SIZE + + len.as_u64(); + storage_meter + .record(storage_growth) + .map_err(|_| ExitError::OutOfGas)?; + } } }; } @@ -1067,9 +1044,15 @@ where fn record_external_dynamic_opcode_cost( &mut self, opcode: Opcode, - _gas_cost: GasCost, + gas_cost: GasCost, target: evm::gasometer::StorageTarget, ) -> Result<(), ExitError> { + if let Some(storage_meter) = self.storage_meter.as_mut() { + storage_meter + .record_dynamic_opcode_cost(opcode, gas_cost) + .map_err(|_| ExitError::OutOfGas)?; + } + // If account code or storage slot is in the overlay it is already accounted for and early exit let mut accessed_storage: Option = match target { StorageTarget::Address(address) => { diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 4a4902ba27..6468ece781 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -67,6 +67,8 @@ pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; pub const WRITE_PROOF_SIZE: u64 = 32; /// Account basic proof size + 5 bytes max of `decode_len` call. pub const IS_EMPTY_CHECK_PROOF_SIZE: u64 = 93; +/// `AccountCodes` key size. 16 (hash) + 20 (key) +pub const ACCOUNT_CODES_KEY_SIZE: u64 = 36; pub enum AccessedStorage { AccountCodes(H160), From 84b487f7158af69b0eb33666323b56b5e5b1c563 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 21 Aug 2023 15:53:39 +0200 Subject: [PATCH 22/42] update GAS_LIMIT_STORAGE_GROWTH_RATIO for tests --- frame/ethereum/src/mock.rs | 6 +++--- frame/evm/src/mock.rs | 5 +++-- template/runtime/src/lib.rs | 5 +++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/frame/ethereum/src/mock.rs b/frame/ethereum/src/mock.rs index 97616a3671..87c0444f01 100644 --- a/frame/ethereum/src/mock.rs +++ b/frame/ethereum/src/mock.rs @@ -141,15 +141,15 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; /// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 40 * 1024; - +const MAX_STORAGE_GROWTH: u64 = 80 * 1024; +const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { pub const TransactionByteFee: u64 = 1; pub const ChainId: u64 = 42; pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa"); pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); + pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; pub const WeightPerGas: Weight = Weight::from_parts(20_000, 0); } diff --git a/frame/evm/src/mock.rs b/frame/evm/src/mock.rs index 2664495be6..84bd9452d2 100644 --- a/frame/evm/src/mock.rs +++ b/frame/evm/src/mock.rs @@ -132,12 +132,13 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; /// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 40 * 1024; +const MAX_STORAGE_GROWTH: u64 = 80 * 1024; +const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); + pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; } diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 8745997faa..022d3b2b2d 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -320,12 +320,13 @@ impl> FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 75_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; /// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 40 * 1024; +const MAX_STORAGE_GROWTH: u64 = 80 * 1024; +const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); + pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); } From cce7cf54cc288692bcf560fc6d6750e5e5e73233 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 21 Aug 2023 16:16:18 +0200 Subject: [PATCH 23/42] add support to record storage growth for precompiles --- Cargo.lock | 8 +-- frame/evm/precompile/dispatch/src/lib.rs | 2 +- frame/evm/src/res/erc20_contract_bytecode.txt | 1 + frame/evm/src/runner/stack.rs | 10 ++- frame/evm/src/tests.rs | 62 +++++++++++++++++++ frame/evm/test-vector-support/src/lib.rs | 2 +- 6 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 frame/evm/src/res/erc20_contract_bytecode.txt diff --git a/Cargo.lock b/Cargo.lock index 36099a9025..30905044bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,7 +2055,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" version = "0.39.1" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" dependencies = [ "auto_impl", "environmental", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2086,7 +2086,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" dependencies = [ "environmental", "evm-core", @@ -2097,7 +2097,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#8ea64228cd02da080c2b3053a77a21bdd3871b36" +source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" dependencies = [ "auto_impl", "environmental", diff --git a/frame/evm/precompile/dispatch/src/lib.rs b/frame/evm/precompile/dispatch/src/lib.rs index f73349bf31..4763dbf4e9 100644 --- a/frame/evm/precompile/dispatch/src/lib.rs +++ b/frame/evm/precompile/dispatch/src/lib.rs @@ -82,7 +82,7 @@ where } handle - .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()))?; + .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()), None)?; match call.dispatch(Some(origin).into()) { Ok(post_info) => { diff --git a/frame/evm/src/res/erc20_contract_bytecode.txt b/frame/evm/src/res/erc20_contract_bytecode.txt new file mode 100644 index 0000000000..1e31f671dd --- /dev/null +++ b/frame/evm/src/res/erc20_contract_bytecode.txt @@ -0,0 +1 @@ +608060405234801561001057600080fd5b50610041337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61004660201b60201c565b610291565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156100e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b6101028160025461020960201b610c7c1790919060201c565b60028190555061015d816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461020960201b610c7c1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b600080828401905083811015610287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b610e3a806102a06000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a08231146101fd578063a457c2d714610255578063a9059cbb146102bb578063dd62ed3e1461032157610088565b8063095ea7b31461008d57806318160ddd146100f357806323b872dd146101115780633950935114610197575b600080fd5b6100d9600480360360408110156100a357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610399565b604051808215151515815260200191505060405180910390f35b6100fb6103b7565b6040518082815260200191505060405180910390f35b61017d6004803603606081101561012757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506103c1565b604051808215151515815260200191505060405180910390f35b6101e3600480360360408110156101ad57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061049a565b604051808215151515815260200191505060405180910390f35b61023f6004803603602081101561021357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061054d565b6040518082815260200191505060405180910390f35b6102a16004803603604081101561026b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610595565b604051808215151515815260200191505060405180910390f35b610307600480360360408110156102d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610662565b604051808215151515815260200191505060405180910390f35b6103836004803603604081101561033757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610680565b6040518082815260200191505060405180910390f35b60006103ad6103a6610707565b848461070f565b6001905092915050565b6000600254905090565b60006103ce848484610906565b61048f846103da610707565b61048a85604051806060016040528060288152602001610d7060289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610440610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b600190509392505050565b60006105436104a7610707565b8461053e85600160006104b8610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b61070f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60006106586105a2610707565b8461065385604051806060016040528060258152602001610de160259139600160006105cc610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b6001905092915050565b600061067661066f610707565b8484610906565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610795576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180610dbd6024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561081b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610d286022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180610d986025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610d056023913960400191505060405180910390fd5b610a7d81604051806060016040528060268152602001610d4a602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b10816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610c69576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610c2e578082015181840152602081019050610c13565b50505050905090810190601f168015610c5b5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610cfa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b809150509291505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a72315820c7a5ffabf642bda14700b2de42f8c57b36621af020441df825de45fd2b3e1c5c64736f6c63430005100032 \ No newline at end of file diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index fef697335a..502529e1ca 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -1207,7 +1207,7 @@ where } // Record cost - self.record_external_cost(None, Some(opcode_proof_size.low_u64()))?; + self.record_external_cost(None, Some(opcode_proof_size.low_u64()), None)?; Ok(()) } @@ -1215,6 +1215,7 @@ where &mut self, ref_time: Option, proof_size: Option, + storage_growth: Option, ) -> Result<(), ExitError> { let weight_info = if let (Some(weight_info), _) = self.info_mut() { weight_info @@ -1229,6 +1230,13 @@ where if let Some(amount) = proof_size { weight_info.try_record_proof_size_or_fail(amount)?; } + if let Some(storage_meter) = self.storage_meter.as_mut() { + if let Some(amount) = storage_growth { + storage_meter + .record(amount) + .map_err(|_| ExitError::OutOfGas)?; + } + } Ok(()) } diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index bb1850708c..b0fc823283 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -624,6 +624,68 @@ mod proof_size_test { } } +mod storage_growth_test { + use super::*; + use std::env; + + pub const ERC20_CONTRACT_BYTECODE: &str = include_str!("./res/proof_size_test_contract_bytecode.txt"); + + fn setup_logger() { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "debug"); // Set log level here + } + let _ = env_logger::builder().is_test(true).try_init(); + } + + fn create_erc20_test_contract( + gas_limit: u64, + ) -> Result>> { + ::Runner::create( + H160::default(), + hex::decode(ERC20_CONTRACT_BYTECODE.trim_end()).unwrap(), + U256::zero(), + gas_limit, + Some(FixedGasPrice::min_gas_price().0), + None, + None, + Vec::new(), + true, // transactional + true, // must be validated + Some(FixedGasWeightMapping::::gas_to_weight( + gas_limit, true, + )), + Some(0), + &::config().clone(), + ) + } + + // This test is to ensure that the storage growth is accounted for correctly when deploying a + // a new contract. In this scenario, the contract is deployed with a gas limit that is not enough + // to cover the storage growth. + #[test] + fn contract_deployement_should_fail_oog() { + setup_logger(); + new_test_ext().execute_with(|| { + let gas_limit: u64 = 4_700_000; + + let result = create_erc20_test_contract(gas_limit).expect("create succeeds"); + // The contract is deployed with a gas limit that is not enough to cover the storage + // growth. The contract creation should fail. + // assert_eq!( + // result.exit_reason, + // crate::ExitReason::Error(crate::ExitError::OutOfGas) + // ); + + // // The contract is deployed with a gas limit that is enough to cover the storage + // // growth. The contract creation should succeed. + // let gas_limit: u64 = 1_500_000; + // let result = create_erc20_test_contract(gas_limit).expect("create succeeds"); + + // assert_eq!(result.exit_reason, crate::ExitReason::Succeed(ExitSucceed::Returned)); + }); + } +} + type Balances = pallet_balances::Pallet; type EVM = Pallet; diff --git a/frame/evm/test-vector-support/src/lib.rs b/frame/evm/test-vector-support/src/lib.rs index 3566840969..8d71bf01be 100644 --- a/frame/evm/test-vector-support/src/lib.rs +++ b/frame/evm/test-vector-support/src/lib.rs @@ -80,7 +80,7 @@ impl PrecompileHandle for MockHandle { Ok(()) } - fn record_external_cost(&mut self, _: Option, _: Option) -> Result<(), ExitError> { + fn record_external_cost(&mut self, _: Option, _: Option, _: Option) -> Result<(), ExitError> { Ok(()) } From 4670129ca50f7cf6dc127532b9846e00724f1265 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 21 Aug 2023 16:25:53 +0200 Subject: [PATCH 24/42] fix tests --- frame/evm/precompile/dispatch/src/mock.rs | 1 + frame/evm/src/res/erc20_contract_bytecode.txt | 1 - frame/evm/src/tests.rs | 12 ++---------- 3 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 frame/evm/src/res/erc20_contract_bytecode.txt diff --git a/frame/evm/precompile/dispatch/src/mock.rs b/frame/evm/precompile/dispatch/src/mock.rs index f1f68f111d..07c575d638 100644 --- a/frame/evm/precompile/dispatch/src/mock.rs +++ b/frame/evm/precompile/dispatch/src/mock.rs @@ -195,6 +195,7 @@ impl PrecompileHandle for MockHandle { &mut self, _ref_time: Option, _proof_size: Option, + _storage_growth: Option, ) -> Result<(), ExitError> { Ok(()) } diff --git a/frame/evm/src/res/erc20_contract_bytecode.txt b/frame/evm/src/res/erc20_contract_bytecode.txt deleted file mode 100644 index 1e31f671dd..0000000000 --- a/frame/evm/src/res/erc20_contract_bytecode.txt +++ /dev/null @@ -1 +0,0 @@ -608060405234801561001057600080fd5b50610041337fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff61004660201b60201c565b610291565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156100e9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601f8152602001807f45524332303a206d696e7420746f20746865207a65726f20616464726573730081525060200191505060405180910390fd5b6101028160025461020960201b610c7c1790919060201c565b60028190555061015d816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461020960201b610c7c1790919060201c565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b600080828401905083811015610287576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b610e3a806102a06000396000f3fe608060405234801561001057600080fd5b50600436106100885760003560e01c806370a082311161005b57806370a08231146101fd578063a457c2d714610255578063a9059cbb146102bb578063dd62ed3e1461032157610088565b8063095ea7b31461008d57806318160ddd146100f357806323b872dd146101115780633950935114610197575b600080fd5b6100d9600480360360408110156100a357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610399565b604051808215151515815260200191505060405180910390f35b6100fb6103b7565b6040518082815260200191505060405180910390f35b61017d6004803603606081101561012757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506103c1565b604051808215151515815260200191505060405180910390f35b6101e3600480360360408110156101ad57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061049a565b604051808215151515815260200191505060405180910390f35b61023f6004803603602081101561021357600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061054d565b6040518082815260200191505060405180910390f35b6102a16004803603604081101561026b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610595565b604051808215151515815260200191505060405180910390f35b610307600480360360408110156102d157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610662565b604051808215151515815260200191505060405180910390f35b6103836004803603604081101561033757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610680565b6040518082815260200191505060405180910390f35b60006103ad6103a6610707565b848461070f565b6001905092915050565b6000600254905090565b60006103ce848484610906565b61048f846103da610707565b61048a85604051806060016040528060288152602001610d7060289139600160008b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000610440610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b600190509392505050565b60006105436104a7610707565b8461053e85600160006104b8610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b61070f565b6001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b60006106586105a2610707565b8461065385604051806060016040528060258152602001610de160259139600160006105cc610707565b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b61070f565b6001905092915050565b600061067661066f610707565b8484610906565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610795576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526024815260200180610dbd6024913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141561081b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526022815260200180610d286022913960400191505060405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925836040518082815260200191505060405180910390a3505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141561098c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526025815260200180610d986025913960400191505060405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610a12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401808060200182810382526023815260200180610d056023913960400191505060405180910390fd5b610a7d81604051806060016040528060268152602001610d4a602691396000808773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610bbc9092919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610b10816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610c7c90919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b6000838311158290610c69576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015610c2e578082015181840152602081019050610c13565b50505050905090810190601f168015610c5b5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b5060008385039050809150509392505050565b600080828401905083811015610cfa576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b809150509291505056fe45524332303a207472616e7366657220746f20746865207a65726f206164647265737345524332303a20617070726f766520746f20746865207a65726f206164647265737345524332303a207472616e7366657220616d6f756e7420657863656564732062616c616e636545524332303a207472616e7366657220616d6f756e74206578636565647320616c6c6f77616e636545524332303a207472616e736665722066726f6d20746865207a65726f206164647265737345524332303a20617070726f76652066726f6d20746865207a65726f206164647265737345524332303a2064656372656173656420616c6c6f77616e63652062656c6f77207a65726fa265627a7a72315820c7a5ffabf642bda14700b2de42f8c57b36621af020441df825de45fd2b3e1c5c64736f6c63430005100032 \ No newline at end of file diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index b0fc823283..99023155f2 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -628,21 +628,14 @@ mod storage_growth_test { use super::*; use std::env; - pub const ERC20_CONTRACT_BYTECODE: &str = include_str!("./res/proof_size_test_contract_bytecode.txt"); - - fn setup_logger() { - if env::var("RUST_LOG").is_err() { - env::set_var("RUST_LOG", "debug"); // Set log level here - } - let _ = env_logger::builder().is_test(true).try_init(); - } + pub const STORAGE_GROWTH_TEST_CONTRACT: &str = include_str!("./res/proof_size_test_contract_bytecode.txt"); fn create_erc20_test_contract( gas_limit: u64, ) -> Result>> { ::Runner::create( H160::default(), - hex::decode(ERC20_CONTRACT_BYTECODE.trim_end()).unwrap(), + hex::decode(STORAGE_GROWTH_TEST_CONTRACT.trim_end()).unwrap(), U256::zero(), gas_limit, Some(FixedGasPrice::min_gas_price().0), @@ -664,7 +657,6 @@ mod storage_growth_test { // to cover the storage growth. #[test] fn contract_deployement_should_fail_oog() { - setup_logger(); new_test_ext().execute_with(|| { let gas_limit: u64 = 4_700_000; From dee68d8cf796cabb35bde6d6c75e5749245c230b Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 21 Aug 2023 16:27:04 +0200 Subject: [PATCH 25/42] fix formatting --- frame/evm/precompile/dispatch/src/lib.rs | 7 +++++-- frame/evm/src/lib.rs | 2 +- frame/evm/src/runner/mod.rs | 2 +- frame/evm/src/tests.rs | 3 ++- frame/evm/test-vector-support/src/lib.rs | 7 ++++++- 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frame/evm/precompile/dispatch/src/lib.rs b/frame/evm/precompile/dispatch/src/lib.rs index 4763dbf4e9..d3427b692f 100644 --- a/frame/evm/precompile/dispatch/src/lib.rs +++ b/frame/evm/precompile/dispatch/src/lib.rs @@ -81,8 +81,11 @@ where return Err(err); } - handle - .record_external_cost(Some(info.weight.ref_time()), Some(info.weight.proof_size()), None)?; + handle.record_external_cost( + Some(info.weight.ref_time()), + Some(info.weight.proof_size()), + None, + )?; match call.dispatch(Some(origin).into()) { Ok(post_info) => { diff --git a/frame/evm/src/lib.rs b/frame/evm/src/lib.rs index aad65b0338..d693a29e62 100644 --- a/frame/evm/src/lib.rs +++ b/frame/evm/src/lib.rs @@ -1049,4 +1049,4 @@ impl OnCreate for Tuple { Tuple::on_create(owner, contract); )*) } -} \ No newline at end of file +} diff --git a/frame/evm/src/runner/mod.rs b/frame/evm/src/runner/mod.rs index bd1304e01e..3a95a3d763 100644 --- a/frame/evm/src/runner/mod.rs +++ b/frame/evm/src/runner/mod.rs @@ -15,8 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod stack; pub mod meter; +pub mod stack; use crate::{Config, Weight}; use fp_evm::{CallInfo, CreateInfo}; diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 99023155f2..dcdd14ad95 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -628,7 +628,8 @@ mod storage_growth_test { use super::*; use std::env; - pub const STORAGE_GROWTH_TEST_CONTRACT: &str = include_str!("./res/proof_size_test_contract_bytecode.txt"); + pub const STORAGE_GROWTH_TEST_CONTRACT: &str = + include_str!("./res/proof_size_test_contract_bytecode.txt"); fn create_erc20_test_contract( gas_limit: u64, diff --git a/frame/evm/test-vector-support/src/lib.rs b/frame/evm/test-vector-support/src/lib.rs index 8d71bf01be..beb87c6f98 100644 --- a/frame/evm/test-vector-support/src/lib.rs +++ b/frame/evm/test-vector-support/src/lib.rs @@ -80,7 +80,12 @@ impl PrecompileHandle for MockHandle { Ok(()) } - fn record_external_cost(&mut self, _: Option, _: Option, _: Option) -> Result<(), ExitError> { + fn record_external_cost( + &mut self, + _: Option, + _: Option, + _: Option, + ) -> Result<(), ExitError> { Ok(()) } From f110333d9595a04b601aa3ce02570168e2cdf9f6 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 22 Aug 2023 17:37:30 +0200 Subject: [PATCH 26/42] fix storage growth parameters for tests --- frame/ethereum/src/mock.rs | 2 +- frame/evm/src/mock.rs | 2 +- template/runtime/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/ethereum/src/mock.rs b/frame/ethereum/src/mock.rs index 87c0444f01..de96847c40 100644 --- a/frame/ethereum/src/mock.rs +++ b/frame/ethereum/src/mock.rs @@ -140,7 +140,7 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes (40 Kb). +/// The maximum storage growth per block in bytes (80 Kb). const MAX_STORAGE_GROWTH: u64 = 80 * 1024; const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { diff --git a/frame/evm/src/mock.rs b/frame/evm/src/mock.rs index 84bd9452d2..12ab5260b8 100644 --- a/frame/evm/src/mock.rs +++ b/frame/evm/src/mock.rs @@ -132,7 +132,7 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; /// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 80 * 1024; +const MAX_STORAGE_GROWTH: u64 = 40 * 1024; const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 022d3b2b2d..e2d46ead20 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -319,8 +319,8 @@ impl> FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 75_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 80 * 1024; +/// The maximum storage growth per block in bytes (3 Mb). +const MAX_STORAGE_GROWTH: u64 = 3 * 1024 * 1024; const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); parameter_types! { From 332bde7f1c1229a9fd998d649f84f08980e455c8 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 23 Aug 2023 12:29:54 +0200 Subject: [PATCH 27/42] add rust integration tests --- frame/evm/src/res/StorageGrowthTest.sol | 27 ++ .../storage_growth_test_contract_bytecode.txt | 1 + frame/evm/src/runner/stack.rs | 26 +- frame/evm/src/tests.rs | 236 ++++++++++++++++-- 4 files changed, 251 insertions(+), 39 deletions(-) create mode 100644 frame/evm/src/res/StorageGrowthTest.sol create mode 100644 frame/evm/src/res/storage_growth_test_contract_bytecode.txt diff --git a/frame/evm/src/res/StorageGrowthTest.sol b/frame/evm/src/res/StorageGrowthTest.sol new file mode 100644 index 0000000000..7a66d33369 --- /dev/null +++ b/frame/evm/src/res/StorageGrowthTest.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.2; + +contract StorageGrowthTest { + mapping(uint256 => uint256) public map; + uint256 foo; + uint256 bar; + uint256 baz; + + constructor() { + foo = 1; + bar = 2; + baz = 3; + } + + function store() public { + map[0] = 1; + map[1] = 2; + map[2] = 3; + } + + function update() public { + foo = 2; + bar = 3; + baz = 4; + } +} diff --git a/frame/evm/src/res/storage_growth_test_contract_bytecode.txt b/frame/evm/src/res/storage_growth_test_contract_bytecode.txt new file mode 100644 index 0000000000..e0ba9113b0 --- /dev/null +++ b/frame/evm/src/res/storage_growth_test_contract_bytecode.txt @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506001808190555060028081905550600380819055506101c7806100356000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c8063975057e714610046578063a2e6204514610050578063b8dda9c71461005a575b600080fd5b61004e61008a565b005b6100586100d6565b005b610074600480360381019061006f919061011d565b6100f0565b6040516100819190610155565b60405180910390f35b6001600080808152602001908152602001600020819055506002600080600181526020019081526020016000208190555060036000806002815260200190815260200160002081905550565b600260018190555060036002819055506004600381905550565b60006020528060005260406000206000915090505481565b6000813590506101178161017a565b92915050565b60006020828403121561012f57600080fd5b600061013d84828501610108565b91505092915050565b61014f81610170565b82525050565b600060208201905061016a6000830184610146565b92915050565b6000819050919050565b61018381610170565b811461018e57600080fd5b5056fea2646970667358221220b25685afab962e465f0b43f6c4de10c82a565f50f6995c1cd444b002bcdd43e964736f6c63430008020033 \ No newline at end of file diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index 502529e1ca..b6c4eb2842 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -265,21 +265,21 @@ where None => 0, }; + let pov_gas = match executor.state().weight_info() { + Some(weight_info) => weight_info + .proof_size_usage + .unwrap_or_default() + .saturating_mul(T::GasLimitPovSizeRatio::get()), + None => 0, + }; + // Post execution. let used_gas = executor.used_gas(); - let effective_gas = match executor.state().weight_info() { - Some(weight_info) => U256::from(sp_std::cmp::max( - sp_std::cmp::max( - used_gas, - weight_info - .proof_size_usage - .unwrap_or_default() - .saturating_mul(T::GasLimitPovSizeRatio::get()), - ), - storage_gas, - )), - _ => used_gas.into(), - }; + let effective_gas = U256::from(sp_std::cmp::max( + sp_std::cmp::max(used_gas, pov_gas), + storage_gas, + )); + let actual_fee = effective_gas.saturating_mul(total_fee_per_gas); let actual_base_fee = effective_gas.saturating_mul(base_fee); diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index dcdd14ad95..0edbddccbf 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -626,17 +626,24 @@ mod proof_size_test { mod storage_growth_test { use super::*; - use std::env; + use crate::tests::proof_size_test::PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE; + use fp_evm::{ + ACCOUNT_CODES_KEY_SIZE, ACCOUNT_CODES_METADATA_PROOF_SIZE, ACCOUNT_STORAGE_PROOF_SIZE, + }; - pub const STORAGE_GROWTH_TEST_CONTRACT: &str = - include_str!("./res/proof_size_test_contract_bytecode.txt"); + const PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN: u64 = 116; + // The contract bytecode stored on chain. + const STORAGE_GROWTH_TEST_CONTRACT: &str = + include_str!("./res/storage_growth_test_contract_bytecode.txt"); + const STORAGE_GROWTH_TEST_CONTRACT_BYTECODE_LEN: u64 = 455; - fn create_erc20_test_contract( + fn create_test_contract( + contract: &str, gas_limit: u64, ) -> Result>> { ::Runner::create( H160::default(), - hex::decode(STORAGE_GROWTH_TEST_CONTRACT.trim_end()).unwrap(), + hex::decode(contract.trim_end()).expect("Failed to decode contract"), U256::zero(), gas_limit, Some(FixedGasPrice::min_gas_price().0), @@ -649,32 +656,209 @@ mod storage_growth_test { gas_limit, true, )), Some(0), - &::config().clone(), + &::config(), ) } - // This test is to ensure that the storage growth is accounted for correctly when deploying a - // a new contract. In this scenario, the contract is deployed with a gas limit that is not enough - // to cover the storage growth. + // Calls the given contract + fn call_test_contract( + contract_addr: H160, + call_data: &[u8], + value: U256, + gas_limit: u64, + ) -> Result>> { + ::Runner::call( + H160::default(), + contract_addr, + call_data.to_vec(), + value, + gas_limit, + Some(FixedGasPrice::min_gas_price().0), + None, + None, + Vec::new(), + true, // transactional + true, // must be validated + None, + Some(0), + &::config(), + ) + } + + // Computes the expected gas for contract creation (related to storage growth). + // `byte_code_len` represents the length of the contract bytecode stored on-chain. + fn expected_contract_create_storage_growth_gas(bytecode_len: u64) -> u64 { + let ratio = <::GasLimitStorageGrowthRatio as Get>::get(); + (ACCOUNT_CODES_KEY_SIZE + ACCOUNT_CODES_METADATA_PROOF_SIZE + bytecode_len) * ratio + } + + // Verifies that contract deployment fails when the necessary storage growth gas isn't + // provided, even if the gas limit surpasses the standard gas usage measured by the + // gasometer. + #[test] + fn contract_deployment_should_fail_oog() { + new_test_ext().execute_with(|| { + let gas_limit: u64 = 80_000; + + let result = create_test_contract(PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE, gas_limit) + .expect("create succeeds"); + + // Assert that the legacy gas is lower than the gas limit. + assert!(result.used_gas.standard < U256::from(gas_limit)); + assert_eq!( + result.exit_reason, + crate::ExitReason::Error(crate::ExitError::OutOfGas) + ); + assert_eq!( + result.used_gas.effective.as_u64(), + expected_contract_create_storage_growth_gas( + PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN + ) + ); + // Assert that the contract entry does not exists in the storage. + assert!(!AccountCodes::::contains_key(result.value)); + }); + } + + /// Test that contract deployment succeeds when the necessary storage growth gas is provided. + #[test] + fn contract_deployment_should_succeed() { + new_test_ext().execute_with(|| { + let gas_limit: u64 = 85_000; + + let result = create_test_contract(PROOF_SIZE_TEST_CALLEE_CONTRACT_BYTECODE, gas_limit) + .expect("create succeeds"); + + assert_eq!( + result.used_gas.effective.as_u64(), + expected_contract_create_storage_growth_gas( + PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN + ) + ); + assert_eq!( + result.exit_reason, + crate::ExitReason::Succeed(ExitSucceed::Returned) + ); + // Assert that the contract entry exists in the storage. + assert!(AccountCodes::::contains_key(result.value)); + }); + } + + // Test that contract creation with code initialization that results in new storage entries + // succeeds when the necessary storage growth gas is provided. + #[test] + fn contract_creation_with_code_initialization_should_succeed() { + new_test_ext().execute_with(|| { + let gas_limit: u64 = 863_394; + let ratio = <::GasLimitStorageGrowthRatio as Get>::get(); + // The constructor of the contract creates 3 new storage entries (uint256). So, + // the expected gas is the gas for contract creation + 3 * ACCOUNT_STORAGE_PROOF_SIZE. + let expected_storage_growth_gas = expected_contract_create_storage_growth_gas( + STORAGE_GROWTH_TEST_CONTRACT_BYTECODE_LEN, + ) + (3 * ACCOUNT_STORAGE_PROOF_SIZE * ratio); + + // Deploy the contract. + let result = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit) + .expect("create succeeds"); + + assert_eq!( + result.used_gas.effective.as_u64(), + expected_storage_growth_gas + ); + assert_eq!( + result.exit_reason, + crate::ExitReason::Succeed(ExitSucceed::Returned) + ); + }); + } + + // Verify that saving new entries fails when insufficient storage growth gas is supplied. + #[test] + fn store_new_entries_should_fail_oog() { + new_test_ext().execute_with(|| { + let gas_limit: u64 = 863_394; + // Deploy the contract. + let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit) + .expect("create succeeds"); + let contract_addr = res.value; + + let gas_limit = 120_000; + // Call the contract method store to store new entries. + let result = call_test_contract( + contract_addr, + &hex::decode("975057e7").unwrap(), + U256::zero(), + gas_limit, + ) + .expect("call should succeed"); + + assert_eq!( + result.exit_reason, + crate::ExitReason::Error(crate::ExitError::OutOfGas) + ); + }); + } + + // Verify that saving new entries succeeds when sufficient storage growth gas is supplied. #[test] - fn contract_deployement_should_fail_oog() { + fn store_new_entries_should_succeeds() { new_test_ext().execute_with(|| { - let gas_limit: u64 = 4_700_000; - - let result = create_erc20_test_contract(gas_limit).expect("create succeeds"); - // The contract is deployed with a gas limit that is not enough to cover the storage - // growth. The contract creation should fail. - // assert_eq!( - // result.exit_reason, - // crate::ExitReason::Error(crate::ExitError::OutOfGas) - // ); - - // // The contract is deployed with a gas limit that is enough to cover the storage - // // growth. The contract creation should succeed. - // let gas_limit: u64 = 1_500_000; - // let result = create_erc20_test_contract(gas_limit).expect("create succeeds"); - - // assert_eq!(result.exit_reason, crate::ExitReason::Succeed(ExitSucceed::Returned)); + let gas_limit: u64 = 863_394; + // Deploy the contract. + let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit) + .expect("create succeeds"); + let contract_addr = res.value; + + let gas_limit = 128_000; + // Call the contract method store to store new entries. + let result = call_test_contract( + contract_addr, + &hex::decode("975057e7").unwrap(), + U256::zero(), + gas_limit, + ) + .expect("call should succeed"); + + let expected_storage_growth_gas = 3 + * ACCOUNT_STORAGE_PROOF_SIZE + * <::GasLimitStorageGrowthRatio as Get>::get(); + assert_eq!( + result.exit_reason, + crate::ExitReason::Succeed(ExitSucceed::Stopped) + ); + assert_eq!( + result.used_gas.effective.as_u64(), + expected_storage_growth_gas + ); + }); + } + + // Verify that updating existing storage entries does not incur any storage growth charges. + #[test] + fn update_exisiting_entries_succeeds() { + new_test_ext().execute_with(|| { + let gas_limit: u64 = 863_394; + // Deploy the contract. + let res = create_test_contract(STORAGE_GROWTH_TEST_CONTRACT, gas_limit) + .expect("create succeeds"); + let contract_addr = res.value; + + // Providing gas limit of 37_000 is enough to update existing entries, but not enough + // to store new entries. + let gas_limit = 37_000; + // Call the contract method update to update existing entries. + let result = call_test_contract( + contract_addr, + &hex::decode("a2e62045").unwrap(), + U256::zero(), + gas_limit, + ) + .expect("call should succeed"); + + assert_eq!( + result.exit_reason, + crate::ExitReason::Succeed(ExitSucceed::Stopped) + ); }); } } From 1623f9ca849e6a256e187d99abecdcf7449599c2 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Wed, 23 Aug 2023 19:19:50 +0200 Subject: [PATCH 28/42] fix typo --- primitives/evm/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/primitives/evm/src/lib.rs b/primitives/evm/src/lib.rs index 6468ece781..fb2da2884b 100644 --- a/primitives/evm/src/lib.rs +++ b/primitives/evm/src/lib.rs @@ -59,7 +59,7 @@ pub struct Vicinity { /// `System::Account` 16(hash) + 20 (key) + 60 (AccountInfo::max_encoded_len) pub const ACCOUNT_BASIC_PROOF_SIZE: u64 = 96; -/// `AccountCodesMetadata` read, temptatively 16 (hash) + 20 (key) + 40 (CodeMetadata). +/// `AccountCodesMetadata` read, temtatively 16 (hash) + 20 (key) + 40 (CodeMetadata). pub const ACCOUNT_CODES_METADATA_PROOF_SIZE: u64 = 76; /// 16 (hash1) + 20 (key1) + 16 (hash2) + 32 (key2) + 32 (value) pub const ACCOUNT_STORAGE_PROOF_SIZE: u64 = 116; From 19ba6297f99435e3d8e807dca3710857da514d47 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 02:10:14 +0200 Subject: [PATCH 29/42] fix recording storage growth of a special use case of SSTORE --- frame/evm/src/runner/meter.rs | 75 ++++++++++++++++++++++++++--------- frame/evm/src/runner/stack.rs | 4 +- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index fa047d471f..df229d2a73 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -1,13 +1,10 @@ -use evm::{gasometer::GasCost, Opcode}; +use evm::{ + gasometer::{GasCost, StorageTarget}, + Opcode, +}; use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE; -use sp_core::H256; - -/// A meter for tracking the storage growth. -#[derive(Clone, Copy)] -pub struct StorageMeter { - usage: u64, - limit: u64, -} +use sp_core::{H160, H256}; +use sp_std::collections::btree_map::BTreeMap; /// An error that is returned when the storage limit has been exceeded. #[derive(Debug, PartialEq)] @@ -15,10 +12,22 @@ pub enum MeterError { LimitExceeded, } +/// A meter for tracking the storage growth. +#[derive(Clone)] +pub struct StorageMeter { + usage: u64, + limit: u64, + recorded_new_entries: BTreeMap<(H160, H256), ()>, +} + impl StorageMeter { /// Creates a new storage meter with the given limit. pub fn new(limit: u64) -> Self { - Self { usage: 0, limit } + Self { + usage: 0, + limit, + recorded_new_entries: BTreeMap::new(), + } } /// Records the given amount of storage usage. The amount is added to the current usage. @@ -40,15 +49,29 @@ impl StorageMeter { &mut self, _opcode: Opcode, gas_cost: GasCost, + target: StorageTarget, ) -> Result<(), MeterError> { match gas_cost { - GasCost::SStore { original, new, .. } - if original == H256::default() && !new.is_zero() => - { - self.record(ACCOUNT_STORAGE_PROOF_SIZE) + GasCost::SStore { original, new, .. } => { + // Validate if storage growth for the current slot has been accounted for within this transaction. + // Comparing Original and new to determine if a new entry is being created is not sufficient, because + // 'original' updates only at the end of the transaction. So, if a new entry + // is created and updated multiple times within the same transaction, the storage growth is + // accounted for multiple times, because 'original' is always zero for the subsequent updates. + // To avoid this, we keep track of the new entries that are created within the transaction. + let (address, index) = match target { + StorageTarget::Slot(address, index) => (address, index), + _ => return Ok(()), + }; + let recorded = self.recorded_new_entries.contains_key(&(address, index)); + if !recorded && original == H256::default() && !new.is_zero() { + self.record(ACCOUNT_STORAGE_PROOF_SIZE)?; + self.recorded_new_entries.insert((address, index), ()); + } } - _ => Ok(()), + _ => {} } + Ok(()) } /// Returns the current usage of storage. @@ -123,8 +146,10 @@ mod test { new: H256::from_low_u64_be(2), target_is_cold: false, }; + let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(1)); + meter - .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost) + .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target) .unwrap(); assert_eq!(meter.usage(), 0); @@ -136,7 +161,19 @@ mod test { target_is_cold: false, }; meter - .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost) + .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target) + .unwrap(); + assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE); + + // Try to record the same storage growth again. No change in storage growth. + let gas_cost = GasCost::SStore { + original: H256::default(), + current: Default::default(), + new: H256::from_low_u64_be(1), + target_is_cold: false, + }; + meter + .record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target) .unwrap(); assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE); @@ -147,7 +184,9 @@ mod test { new: H256::from_low_u64_be(2), target_is_cold: false, }; - let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost); + let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(2)); + + let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target); assert!(res.is_err()); assert_eq!(res, Err(MeterError::LimitExceeded)); assert_eq!(meter.usage(), 232); diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index b6c4eb2842..f725d4207c 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -260,7 +260,7 @@ where let (reason, retv) = f(&mut executor); // Compute the storage gas cost based on the storage growth. - let storage_gas = match executor.state().storage_meter { + let storage_gas = match &executor.state().storage_meter { Some(storage_meter) => storage_meter.storage_to_gas(storage_growth_ratio), None => 0, }; @@ -1049,7 +1049,7 @@ where ) -> Result<(), ExitError> { if let Some(storage_meter) = self.storage_meter.as_mut() { storage_meter - .record_dynamic_opcode_cost(opcode, gas_cost) + .record_dynamic_opcode_cost(opcode, gas_cost, target) .map_err(|_| ExitError::OutOfGas)?; } From 75781756bb8f6eb91de3300eba2065857006c0d0 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 02:12:44 +0200 Subject: [PATCH 30/42] use saturating add --- frame/evm/src/runner/stack.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/evm/src/runner/stack.rs b/frame/evm/src/runner/stack.rs index f725d4207c..a53d93b5b1 100644 --- a/frame/evm/src/runner/stack.rs +++ b/frame/evm/src/runner/stack.rs @@ -1029,8 +1029,8 @@ where if let Some(storage_meter) = self.storage_meter.as_mut() { // Record the number of bytes written to storage when deploying a contract. let storage_growth = ACCOUNT_CODES_KEY_SIZE - + ACCOUNT_CODES_METADATA_PROOF_SIZE - + len.as_u64(); + .saturating_add(ACCOUNT_CODES_METADATA_PROOF_SIZE) + .saturating_add(len.as_u64()); storage_meter .record(storage_growth) .map_err(|_| ExitError::OutOfGas)?; From d0304fe889d2fc332a4dca1a61c06d2f2235c80f Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 03:22:22 +0200 Subject: [PATCH 31/42] minor improvements --- frame/ethereum/src/mock.rs | 7 +++---- frame/evm/src/mock.rs | 7 +++---- template/runtime/src/lib.rs | 7 +++---- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/frame/ethereum/src/mock.rs b/frame/ethereum/src/mock.rs index de96847c40..f489195f4b 100644 --- a/frame/ethereum/src/mock.rs +++ b/frame/ethereum/src/mock.rs @@ -140,16 +140,15 @@ impl FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes (80 Kb). -const MAX_STORAGE_GROWTH: u64 = 80 * 1024; -const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); +/// The maximum storage growth per block in bytes. +const MAX_STORAGE_GROWTH: u64 = 800 * 1024; parameter_types! { pub const TransactionByteFee: u64 = 1; pub const ChainId: u64 = 42; pub const EVMModuleId: PalletId = PalletId(*b"py/evmpa"); pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub const WeightPerGas: Weight = Weight::from_parts(20_000, 0); } diff --git a/frame/evm/src/mock.rs b/frame/evm/src/mock.rs index 12ab5260b8..a653246076 100644 --- a/frame/evm/src/mock.rs +++ b/frame/evm/src/mock.rs @@ -131,14 +131,13 @@ impl FindAuthor for FindAuthorTruncated { } const BLOCK_GAS_LIMIT: u64 = 150_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes (40 Kb). -const MAX_STORAGE_GROWTH: u64 = 40 * 1024; -const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); +/// The maximum storage growth per block in bytes. +const MAX_STORAGE_GROWTH: u64 = 400 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub WeightPerGas: Weight = Weight::from_parts(20_000, 0); pub MockPrecompiles: MockPrecompileSet = MockPrecompileSet; } diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index e2d46ead20..065f301436 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -319,14 +319,13 @@ impl> FindAuthor for FindAuthorTruncated { const BLOCK_GAS_LIMIT: u64 = 75_000_000; const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; -/// The maximum storage growth per block in bytes (3 Mb). -const MAX_STORAGE_GROWTH: u64 = 3 * 1024 * 1024; -const GAS_LIMIT_STORAGE_GROWTH_RATIO: u64 = 15_000_000u64.saturating_div(MAX_STORAGE_GROWTH); +/// The maximum storage growth per block in bytes. +const MAX_STORAGE_GROWTH: u64 = 400 * 1024; parameter_types! { pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); - pub const GasLimitStorageGrowthRatio: u64 = GAS_LIMIT_STORAGE_GROWTH_RATIO; + pub const GasLimitStorageGrowthRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_STORAGE_GROWTH); pub PrecompilesValue: FrontierPrecompiles = FrontierPrecompiles::<_>::new(); pub WeightPerGas: Weight = Weight::from_parts(weight_per_gas(BLOCK_GAS_LIMIT, NORMAL_DISPATCH_RATIO, WEIGHT_MILLISECS_PER_BLOCK), 0); } From fb31dacdcebcf18d345207bb30d6f0c486d5010b Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 03:22:46 +0200 Subject: [PATCH 32/42] add license to meter.rs --- frame/evm/src/runner/meter.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index df229d2a73..5a6a880995 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// This file is part of Frontier. +// +// Copyright (c) 2021-2022 Parity Technologies (UK) Ltd. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use evm::{ gasometer::{GasCost, StorageTarget}, Opcode, From a6da7efb443cb7c3872fabac5d3c4e223dd428c3 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 09:31:46 +0200 Subject: [PATCH 33/42] fix clippy warnings --- frame/evm/src/runner/meter.rs | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index 5a6a880995..927a3f2879 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -69,25 +69,22 @@ impl StorageMeter { gas_cost: GasCost, target: StorageTarget, ) -> Result<(), MeterError> { - match gas_cost { - GasCost::SStore { original, new, .. } => { - // Validate if storage growth for the current slot has been accounted for within this transaction. - // Comparing Original and new to determine if a new entry is being created is not sufficient, because - // 'original' updates only at the end of the transaction. So, if a new entry - // is created and updated multiple times within the same transaction, the storage growth is - // accounted for multiple times, because 'original' is always zero for the subsequent updates. - // To avoid this, we keep track of the new entries that are created within the transaction. - let (address, index) = match target { - StorageTarget::Slot(address, index) => (address, index), - _ => return Ok(()), - }; - let recorded = self.recorded_new_entries.contains_key(&(address, index)); - if !recorded && original == H256::default() && !new.is_zero() { - self.record(ACCOUNT_STORAGE_PROOF_SIZE)?; - self.recorded_new_entries.insert((address, index), ()); - } + if let GasCost::SStore { original, new, .. } = gas_cost { + // Validate if storage growth for the current slot has been accounted for within this transaction. + // Comparing Original and new to determine if a new entry is being created is not sufficient, because + // 'original' updates only at the end of the transaction. So, if a new entry + // is created and updated multiple times within the same transaction, the storage growth is + // accounted for multiple times, because 'original' is always zero for the subsequent updates. + // To avoid this, we keep track of the new entries that are created within the transaction. + let (address, index) = match target { + StorageTarget::Slot(address, index) => (address, index), + _ => return Ok(()), + }; + let recorded = self.recorded_new_entries.contains_key(&(address, index)); + if !recorded && original == H256::default() && !new.is_zero() { + self.record(ACCOUNT_STORAGE_PROOF_SIZE)?; + self.recorded_new_entries.insert((address, index), ()); } - _ => {} } Ok(()) } From 4a6ea480cc79449278ced262120b07963056ff2e Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Mon, 28 Aug 2023 16:59:07 +0200 Subject: [PATCH 34/42] pin evm to master --- Cargo.lock | 8 ++++---- Cargo.toml | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 30905044bb..cd4605e62f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,7 +2055,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" version = "0.39.1" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" +source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" dependencies = [ "auto_impl", "environmental", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" +source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2086,7 +2086,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" +source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" dependencies = [ "environmental", "evm-core", @@ -2097,7 +2097,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.39.0" -source = "git+https://github.com/moonbeam-foundation/evm.git?branch=ahmad-update-external-operation#6bd1f61d38e33aab44b6b52cb14deaf9668755b5" +source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" dependencies = [ "auto_impl", "environmental", diff --git a/Cargo.toml b/Cargo.toml index 97387ef734..4b3e94ac6d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,7 @@ clap = { version = "4.3", features = ["derive", "deprecated"] } environmental = { version = "1.1.4", default-features = false } ethereum = { version = "0.14.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -# evm = { git = "https://github.com/rust-blockchain/evm", rev = "b7b82c7e1fc57b7449d6dfa6826600de37cc1e65", default-features = false } -evm = { git = "https://github.com/moonbeam-foundation/evm.git", branch = "ahmad-update-external-operation", default-features = false } +evm = { git = "https://github.com/rust-blockchain/evm", rev = "44bb77c27be9ee615eaa576ad914fbb461537276", default-features = false } futures = "0.3.28" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex-literal = "0.4.1" From ba9552d72988eb381364f6dc7df65cc20a6f17c6 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 29 Aug 2023 10:14:40 +0200 Subject: [PATCH 35/42] update evm to the latest commit --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd4605e62f..71b53c7183 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2055,7 +2055,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" version = "0.39.1" -source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" +source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" dependencies = [ "auto_impl", "environmental", @@ -2075,7 +2075,7 @@ dependencies = [ [[package]] name = "evm-core" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" +source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2086,7 +2086,7 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" +source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" dependencies = [ "environmental", "evm-core", @@ -2097,7 +2097,7 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=44bb77c27be9ee615eaa576ad914fbb461537276#44bb77c27be9ee615eaa576ad914fbb461537276" +source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" dependencies = [ "auto_impl", "environmental", diff --git a/Cargo.toml b/Cargo.toml index 4b3e94ac6d..19379acb04 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,7 +45,7 @@ clap = { version = "4.3", features = ["derive", "deprecated"] } environmental = { version = "1.1.4", default-features = false } ethereum = { version = "0.14.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -evm = { git = "https://github.com/rust-blockchain/evm", rev = "44bb77c27be9ee615eaa576ad914fbb461537276", default-features = false } +evm = { git = "https://github.com/rust-blockchain/evm", rev = "a33ac87ad7462b7e7029d12c385492b2a8311d1c", default-features = false } futures = "0.3.28" hex = { version = "0.4.3", default-features = false, features = ["alloc"] } hex-literal = "0.4.1" From f14d3e233d44bca96a75e4552d88595d15140d2c Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 12 Sep 2023 12:31:45 +0200 Subject: [PATCH 36/42] check limit exceedance before updating the usage --- frame/evm/src/runner/meter.rs | 15 ++++++++------- frame/evm/src/tests.rs | 7 +------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index 927a3f2879..8d681de56d 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -51,14 +51,15 @@ impl StorageMeter { /// Records the given amount of storage usage. The amount is added to the current usage. /// If the limit is reached, an error is returned. pub fn record(&mut self, amount: u64) -> Result<(), MeterError> { - self.usage = self.usage.checked_add(amount).ok_or_else(|| { - self.usage = self.limit; - MeterError::LimitExceeded - })?; + let usage = self + .usage + .checked_add(amount) + .ok_or_else(|| MeterError::LimitExceeded)?; - if self.usage > self.limit { + if usage > self.limit { return Err(MeterError::LimitExceeded); } + self.usage = usage; Ok(()) } @@ -143,7 +144,7 @@ mod test { // Exceeding the limit let res = meter.record(1); - assert_eq!(meter.usage(), limit + 1); + assert_eq!(meter.usage(), limit); assert!(res.is_err()); assert_eq!(res, Err(MeterError::LimitExceeded)); } @@ -204,6 +205,6 @@ mod test { let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target); assert!(res.is_err()); assert_eq!(res, Err(MeterError::LimitExceeded)); - assert_eq!(meter.usage(), 232); + assert_eq!(meter.usage(), 116); } } diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index 0edbddccbf..47fa5f15a6 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -709,12 +709,7 @@ mod storage_growth_test { result.exit_reason, crate::ExitReason::Error(crate::ExitError::OutOfGas) ); - assert_eq!( - result.used_gas.effective.as_u64(), - expected_contract_create_storage_growth_gas( - PROOF_SIZE_CALLEE_CONTRACT_BYTECODE_LEN - ) - ); + assert_eq!(result.used_gas.effective.as_u64(), 78485); // Assert that the contract entry does not exists in the storage. assert!(!AccountCodes::::contains_key(result.value)); }); From ca9e4d3d908b9a05bc18a2c685bba6787e6a925e Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 15 Sep 2023 09:06:16 +0200 Subject: [PATCH 37/42] Use BtreeSet --- frame/evm/src/runner/meter.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/evm/src/runner/meter.rs b/frame/evm/src/runner/meter.rs index 8d681de56d..5ad50cd025 100644 --- a/frame/evm/src/runner/meter.rs +++ b/frame/evm/src/runner/meter.rs @@ -22,7 +22,7 @@ use evm::{ }; use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE; use sp_core::{H160, H256}; -use sp_std::collections::btree_map::BTreeMap; +use sp_std::collections::btree_set::BTreeSet; /// An error that is returned when the storage limit has been exceeded. #[derive(Debug, PartialEq)] @@ -35,7 +35,7 @@ pub enum MeterError { pub struct StorageMeter { usage: u64, limit: u64, - recorded_new_entries: BTreeMap<(H160, H256), ()>, + recorded_new_entries: BTreeSet<(H160, H256)>, } impl StorageMeter { @@ -44,7 +44,7 @@ impl StorageMeter { Self { usage: 0, limit, - recorded_new_entries: BTreeMap::new(), + recorded_new_entries: BTreeSet::new(), } } @@ -54,7 +54,7 @@ impl StorageMeter { let usage = self .usage .checked_add(amount) - .ok_or_else(|| MeterError::LimitExceeded)?; + .ok_or(MeterError::LimitExceeded)?; if usage > self.limit { return Err(MeterError::LimitExceeded); @@ -81,10 +81,10 @@ impl StorageMeter { StorageTarget::Slot(address, index) => (address, index), _ => return Ok(()), }; - let recorded = self.recorded_new_entries.contains_key(&(address, index)); + let recorded = self.recorded_new_entries.contains(&(address, index)); if !recorded && original == H256::default() && !new.is_zero() { self.record(ACCOUNT_STORAGE_PROOF_SIZE)?; - self.recorded_new_entries.insert((address, index), ()); + self.recorded_new_entries.insert((address, index)); } } Ok(()) From 4a343e69d3f73e47705dda56cc418c0a48011454 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 15 Sep 2023 10:25:47 +0200 Subject: [PATCH 38/42] add support for storage growth in precompiles --- precompiles/src/evm/handle.rs | 3 ++- precompiles/src/precompile_set.rs | 4 +++- precompiles/src/substrate.rs | 9 ++++++--- precompiles/src/testing/handle.rs | 1 + precompiles/tests-external/lib.rs | 2 ++ 5 files changed, 14 insertions(+), 5 deletions(-) diff --git a/precompiles/src/evm/handle.rs b/precompiles/src/evm/handle.rs index d3d26bbe57..74254025f6 100644 --- a/precompiles/src/evm/handle.rs +++ b/precompiles/src/evm/handle.rs @@ -60,7 +60,7 @@ impl PrecompileHandleExt for T { ) -> Result<(), evm::ExitError> { self.record_cost(crate::prelude::RuntimeHelper::::db_read_gas_cost())?; // TODO: record ref time when precompile will be benchmarked - self.record_external_cost(None, Some(data_max_encoded_len as u64)) + self.record_external_cost(None, Some(data_max_encoded_len as u64), None) } /// Record cost of a log manualy. @@ -190,6 +190,7 @@ mod tests { &mut self, _ref_time: Option, _proof_size: Option, + _storage_growth: Option, ) -> Result<(), fp_evm::ExitError> { Ok(()) } diff --git a/precompiles/src/precompile_set.rs b/precompiles/src/precompile_set.rs index 2f9bb0816a..ebe2d11cfd 100644 --- a/precompiles/src/precompile_set.rs +++ b/precompiles/src/precompile_set.rs @@ -477,8 +477,10 @@ impl<'a, H: PrecompileHandle> PrecompileHandle for RestrictiveHandle<'a, H> { &mut self, ref_time: Option, proof_size: Option, + storage_growth: Option, ) -> Result<(), ExitError> { - self.handle.record_external_cost(ref_time, proof_size) + self.handle + .record_external_cost(ref_time, proof_size, storage_growth) } fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { diff --git a/precompiles/src/substrate.rs b/precompiles/src/substrate.rs index a5c91447b7..7db249661d 100644 --- a/precompiles/src/substrate.rs +++ b/precompiles/src/substrate.rs @@ -59,9 +59,10 @@ where Runtime::RuntimeCall: Dispatchable + GetDispatchInfo, { #[inline(always)] - pub fn record_weight_v2_cost( + pub fn record_external_cost( handle: &mut impl PrecompileHandle, weight: Weight, + storage_growth: u64, ) -> Result<(), ExitError> { // Make sure there is enough gas. let remaining_gas = handle.remaining_gas(); @@ -72,7 +73,7 @@ where // Make sure there is enough remaining weight // TODO: record ref time when precompile will be benchmarked - handle.record_external_cost(None, Some(weight.proof_size())) + handle.record_external_cost(None, Some(weight.proof_size()), Some(storage_growth)) } #[inline(always)] @@ -102,6 +103,7 @@ where handle: &mut impl PrecompileHandle, origin: ::RuntimeOrigin, call: Call, + storage_growth: u64, ) -> Result where Runtime::RuntimeCall: From, @@ -109,7 +111,8 @@ where let call = Runtime::RuntimeCall::from(call); let dispatch_info = call.get_dispatch_info(); - Self::record_weight_v2_cost(handle, dispatch_info.weight).map_err(TryDispatchError::Evm)?; + Self::record_external_cost(handle, dispatch_info.weight, storage_growth) + .map_err(TryDispatchError::Evm)?; // Dispatch call. // It may be possible to not record gas cost if the call returns Pays::No. diff --git a/precompiles/src/testing/handle.rs b/precompiles/src/testing/handle.rs index f521954034..0e869700f2 100644 --- a/precompiles/src/testing/handle.rs +++ b/precompiles/src/testing/handle.rs @@ -208,6 +208,7 @@ impl PrecompileHandle for MockHandle { &mut self, _ref_time: Option, _proof_size: Option, + _storage_growth: Option, ) -> Result<(), ExitError> { Ok(()) } diff --git a/precompiles/tests-external/lib.rs b/precompiles/tests-external/lib.rs index bdf46e2932..994bc32de7 100644 --- a/precompiles/tests-external/lib.rs +++ b/precompiles/tests-external/lib.rs @@ -162,6 +162,7 @@ impl PrecompileHandle for MockPrecompileHandle { &mut self, _ref_time: Option, _proof_size: Option, + _storage_growth: Option, ) -> Result<(), fp_evm::ExitError> { Ok(()) } @@ -240,6 +241,7 @@ impl pallet_evm::Config for Runtime { type OnCreate = (); type FindAuthor = (); type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type GasLimitStorageGrowthRatio = (); type Timestamp = Timestamp; type WeightInfo = pallet_evm::weights::SubstrateWeight; } From e72809dfd41cfd707e865bdf6a870aaf03899d04 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Fri, 15 Sep 2023 12:09:55 +0200 Subject: [PATCH 39/42] clippy warnings --- frame/evm/src/tests.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/evm/src/tests.rs b/frame/evm/src/tests.rs index e81b37ad84..97d70303c1 100644 --- a/frame/evm/src/tests.rs +++ b/frame/evm/src/tests.rs @@ -656,7 +656,7 @@ mod storage_growth_test { gas_limit, true, )), Some(0), - &::config(), + ::config(), ) } @@ -681,7 +681,7 @@ mod storage_growth_test { true, // must be validated None, Some(0), - &::config(), + ::config(), ) } From 553984fd94f801d329bb384e8682ec5b14f8693f Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 21 Nov 2023 10:29:43 +0100 Subject: [PATCH 40/42] update to evm 0.41.0 --- Cargo.lock | 58 ++++++++++++++++++++---------------------------------- Cargo.toml | 4 ++-- 2 files changed, 23 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5e7dc44180..ce367a2353 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2328,20 +2328,20 @@ dependencies = [ [[package]] name = "ethereum" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a89fb87a9e103f71b903b80b670200b54cc67a07578f070681f1fffb7396fb7" +checksum = "2e04d24d20b8ff2235cffbf242d5092de3aa45f77c5270ddbfadd2778ca13fea" dependencies = [ "bytes", "ethereum-types", - "hash-db 0.15.2", + "hash-db", "hash256-std-hasher", "parity-scale-codec", "rlp", "scale-info", "serde", "sha3", - "triehash", + "trie-root", ] [[package]] @@ -2368,8 +2368,8 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" -version = "0.39.1" -source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" +version = "0.41.0" +source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" dependencies = [ "auto_impl", "environmental", @@ -2388,8 +2388,8 @@ dependencies = [ [[package]] name = "evm-core" -version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" +version = "0.41.0" +source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2399,8 +2399,8 @@ dependencies = [ [[package]] name = "evm-gasometer" -version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" +version = "0.41.0" +source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" dependencies = [ "environmental", "evm-core", @@ -2410,8 +2410,8 @@ dependencies = [ [[package]] name = "evm-runtime" -version = "0.39.0" -source = "git+https://github.com/rust-blockchain/evm?rev=a33ac87ad7462b7e7029d12c385492b2a8311d1c#a33ac87ad7462b7e7029d12c385492b2a8311d1c" +version = "0.41.0" +source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" dependencies = [ "auto_impl", "environmental", @@ -3630,12 +3630,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "hash-db" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" - [[package]] name = "hash-db" version = "0.16.0" @@ -5116,7 +5110,7 @@ version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" dependencies = [ - "hash-db 0.16.0", + "hash-db", ] [[package]] @@ -5813,7 +5807,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", - "hash-db 0.16.0", + "hash-db", "hex", "hex-literal", "impl-trait-for-tuples", @@ -7566,7 +7560,7 @@ name = "sc-client-db" version = "0.10.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#dc28df0b278f2ca0f69d14363e08668a9ecc2fac" dependencies = [ - "hash-db 0.16.0", + "hash-db", "kvdb", "kvdb-memorydb", "kvdb-rocksdb", @@ -8874,7 +8868,7 @@ name = "sp-api" version = "4.0.0-dev" source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#dc28df0b278f2ca0f69d14363e08668a9ecc2fac" dependencies = [ - "hash-db 0.16.0", + "hash-db", "log", "parity-scale-codec", "scale-info", @@ -9056,7 +9050,7 @@ dependencies = [ "dyn-clonable", "ed25519-zebra", "futures", - "hash-db 0.16.0", + "hash-db", "hash256-std-hasher", "impl-serde", "lazy_static", @@ -9349,7 +9343,7 @@ name = "sp-state-machine" version = "0.28.0" source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#dc28df0b278f2ca0f69d14363e08668a9ecc2fac" dependencies = [ - "hash-db 0.16.0", + "hash-db", "log", "parity-scale-codec", "parking_lot 0.12.1", @@ -9462,7 +9456,7 @@ version = "22.0.0" source = "git+https://github.com/paritytech/polkadot-sdk?branch=master#dc28df0b278f2ca0f69d14363e08668a9ecc2fac" dependencies = [ "ahash 0.8.3", - "hash-db 0.16.0", + "hash-db", "hashbrown 0.13.2", "lazy_static", "memory-db", @@ -10451,7 +10445,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "767abe6ffed88a1889671a102c2861ae742726f52e0a5a425b92c9fbfa7e9c85" dependencies = [ - "hash-db 0.16.0", + "hash-db", "hashbrown 0.13.2", "log", "rustc-hex", @@ -10464,17 +10458,7 @@ version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" dependencies = [ - "hash-db 0.16.0", -] - -[[package]] -name = "triehash" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1631b201eb031b563d2e85ca18ec8092508e262a3196ce9bd10a67ec87b9f5c" -dependencies = [ - "hash-db 0.15.2", - "rlp", + "hash-db", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 6670915eb2..066a00fd41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,9 +48,9 @@ bn = { package = "substrate-bn", version = "0.6", default-features = false } clap = { version = "4.4.3", features = ["derive", "deprecated"] } derive_more = "0.99" environmental = { version = "1.1.4", default-features = false } -ethereum = { version = "0.14.0", default-features = false } +ethereum = { version = "0.15.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -evm = { git = "https://github.com/rust-blockchain/evm", rev = "a33ac87ad7462b7e7029d12c385492b2a8311d1c", default-features = false } +evm = { git = "https://github.com/rust-blockchain/evm", rev = "d543f105c2ce61494f9cedd6011f7f14989fd611", default-features = false } futures = "0.3.28" hash-db = { version = "0.16.0", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } From 063ea9d01c950ba255ce4510cf8860e0b2918e37 Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com> Date: Tue, 21 Nov 2023 11:28:35 +0100 Subject: [PATCH 41/42] Update Cargo.toml Co-authored-by: Qinxuan Chen --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 066a00fd41..d7bdb23e45 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ derive_more = "0.99" environmental = { version = "1.1.4", default-features = false } ethereum = { version = "0.15.0", default-features = false } ethereum-types = { version = "0.14.1", default-features = false } -evm = { git = "https://github.com/rust-blockchain/evm", rev = "d543f105c2ce61494f9cedd6011f7f14989fd611", default-features = false } +evm = { version = "0.41.0", default-features = false } futures = "0.3.28" hash-db = { version = "0.16.0", default-features = false } hex = { version = "0.4.3", default-features = false, features = ["alloc"] } From 74f321ce06be8ed910e19ae396cdc29f15bb6bcc Mon Sep 17 00:00:00 2001 From: Ahmad Kaouk Date: Tue, 21 Nov 2023 11:29:59 +0100 Subject: [PATCH 42/42] update Cargo.lock --- Cargo.lock | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ce367a2353..b297ae470c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2369,7 +2369,8 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "evm" version = "0.41.0" -source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba01dea39965418e1a072fbe9e1651bd726f0e617611c0f24ab96613425272d1" dependencies = [ "auto_impl", "environmental", @@ -2389,7 +2390,8 @@ dependencies = [ [[package]] name = "evm-core" version = "0.41.0" -source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1da6cedc5cedb4208e59467106db0d1f50db01b920920589f8e672c02fdc04f" dependencies = [ "parity-scale-codec", "primitive-types", @@ -2400,7 +2402,8 @@ dependencies = [ [[package]] name = "evm-gasometer" version = "0.41.0" -source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dc0eb591abc5cd7b05bef6a036c2bb6c66ab6c5e0c5ce94bfe377ab670b1fd7" dependencies = [ "environmental", "evm-core", @@ -2411,7 +2414,8 @@ dependencies = [ [[package]] name = "evm-runtime" version = "0.41.0" -source = "git+https://github.com/rust-blockchain/evm?rev=d543f105c2ce61494f9cedd6011f7f14989fd611#d543f105c2ce61494f9cedd6011f7f14989fd611" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84bbe09b64ae13a29514048c1bb6fda6374ac0b4f6a1f15a443348ab88ef42cd" dependencies = [ "auto_impl", "environmental",