pallet_evm/runner/
meter.rs

1// This file is part of Frontier.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use alloc::collections::btree_set::BTreeSet;
19use evm::{
20	gasometer::{GasCost, StorageTarget},
21	Opcode,
22};
23use fp_evm::ACCOUNT_STORAGE_PROOF_SIZE;
24use sp_core::{H160, H256};
25
26/// An error that is returned when the storage limit has been exceeded.
27#[derive(Debug, PartialEq)]
28pub enum MeterError {
29	LimitExceeded,
30}
31
32/// A meter for tracking the storage growth.
33#[derive(Clone)]
34pub struct StorageMeter {
35	usage: u64,
36	limit: u64,
37	recorded_new_entries: BTreeSet<(H160, H256)>,
38}
39
40impl StorageMeter {
41	/// Creates a new storage meter with the given limit.
42	pub fn new(limit: u64) -> Self {
43		Self {
44			usage: 0,
45			limit,
46			recorded_new_entries: BTreeSet::new(),
47		}
48	}
49
50	/// Records the given amount of storage usage. The amount is added to the current usage.
51	/// If the limit is reached, an error is returned.
52	pub fn record(&mut self, amount: u64) -> Result<(), MeterError> {
53		let usage = self.usage.checked_add(amount).ok_or_else(|| {
54			fp_evm::set_storage_oog();
55			MeterError::LimitExceeded
56		})?;
57
58		if usage > self.limit {
59			fp_evm::set_storage_oog();
60			return Err(MeterError::LimitExceeded);
61		}
62		self.usage = usage;
63		Ok(())
64	}
65
66	/// Records the storage growth for the given Opcode.
67	pub fn record_dynamic_opcode_cost(
68		&mut self,
69		_opcode: Opcode,
70		gas_cost: GasCost,
71		target: StorageTarget,
72	) -> Result<(), MeterError> {
73		if let GasCost::SStore { original, new, .. } = gas_cost {
74			// Validate if storage growth for the current slot has been accounted for within this transaction.
75			// Comparing Original and new to determine if a new entry is being created is not sufficient, because
76			// 'original' updates only at the end of the transaction. So, if a new entry
77			// is created and updated multiple times within the same transaction, the storage growth is
78			// accounted for multiple times, because 'original' is always zero for the subsequent updates.
79			// To avoid this, we keep track of the new entries that are created within the transaction.
80			let (address, index) = match target {
81				StorageTarget::Slot(address, index) => (address, index),
82				_ => return Ok(()),
83			};
84			let recorded = self.recorded_new_entries.contains(&(address, index));
85			if !recorded && original == H256::default() && !new.is_zero() {
86				self.record(ACCOUNT_STORAGE_PROOF_SIZE)?;
87				self.recorded_new_entries.insert((address, index));
88			}
89		}
90		Ok(())
91	}
92
93	/// Returns the current usage of storage.
94	pub fn usage(&self) -> u64 {
95		self.usage
96	}
97
98	/// Returns the limit of storage.
99	pub fn limit(&self) -> u64 {
100		self.limit
101	}
102
103	/// Returns the amount of storage that is available before the limit is reached.
104	pub fn available(&self) -> u64 {
105		self.limit.saturating_sub(self.usage)
106	}
107
108	/// Map storage usage to the gas cost.
109	pub fn storage_to_gas(&self, ratio: u64) -> u64 {
110		self.usage.saturating_mul(ratio)
111	}
112}
113#[cfg(test)]
114mod test {
115	use super::*;
116
117	/// Tests the basic functionality of StorageMeter.
118	#[test]
119	fn test_basic_functionality() {
120		let limit = 100;
121		let mut meter = StorageMeter::new(limit);
122
123		assert_eq!(meter.usage(), 0);
124		assert_eq!(meter.limit(), limit);
125
126		let amount = 10;
127		meter.record(amount).unwrap();
128		assert_eq!(meter.usage(), amount);
129	}
130
131	/// Tests the behavior of StorageMeter when reaching the limit.
132	#[test]
133	fn test_reaching_limit() {
134		let limit = 100;
135		let mut meter = StorageMeter::new(limit);
136
137		// Approaching the limit without exceeding
138		meter.record(limit - 1).unwrap();
139		assert_eq!(meter.usage(), limit - 1);
140
141		// Reaching the limit exactly
142		meter.record(1).unwrap();
143		assert_eq!(meter.usage(), limit);
144
145		// Exceeding the limit
146		let res = meter.record(1);
147		assert_eq!(meter.usage(), limit);
148		assert!(res.is_err());
149		assert_eq!(res, Err(MeterError::LimitExceeded));
150	}
151
152	/// Tests the record of dynamic opcode cost.
153	#[test]
154	fn test_record_dynamic_opcode_cost() {
155		let limit = 200;
156		let mut meter = StorageMeter::new(limit);
157
158		// Existing storage entry is updated. No change in storage growth.
159		let gas_cost = GasCost::SStore {
160			original: H256::from_low_u64_be(1),
161			current: Default::default(),
162			new: H256::from_low_u64_be(2),
163			target_is_cold: false,
164		};
165		let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(1));
166
167		meter
168			.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
169			.unwrap();
170		assert_eq!(meter.usage(), 0);
171
172		// New storage entry is created. Storage growth is recorded.
173		let gas_cost = GasCost::SStore {
174			original: H256::default(),
175			current: Default::default(),
176			new: H256::from_low_u64_be(1),
177			target_is_cold: false,
178		};
179		meter
180			.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
181			.unwrap();
182		assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);
183
184		// Try to record the same storage growth again. No change in storage growth.
185		let gas_cost = GasCost::SStore {
186			original: H256::default(),
187			current: Default::default(),
188			new: H256::from_low_u64_be(1),
189			target_is_cold: false,
190		};
191		meter
192			.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target)
193			.unwrap();
194		assert_eq!(meter.usage(), ACCOUNT_STORAGE_PROOF_SIZE);
195
196		// New storage entry is created. Storage growth is recorded. The limit is reached.
197		let gas_cost = GasCost::SStore {
198			original: H256::default(),
199			current: Default::default(),
200			new: H256::from_low_u64_be(2),
201			target_is_cold: false,
202		};
203		let target = StorageTarget::Slot(H160::default(), H256::from_low_u64_be(2));
204
205		let res = meter.record_dynamic_opcode_cost(Opcode::SSTORE, gas_cost, target);
206		assert!(res.is_err());
207		assert_eq!(res, Err(MeterError::LimitExceeded));
208		assert_eq!(meter.usage(), 116);
209	}
210}