rtvm_interpreter/gas/
calc.rs1use rtvm_primitives::Bytes;
2
3use super::constants::*;
4use crate::{
5 primitives::{
6 Address, SpecId,
7 SpecId::{BERLIN, SPURIOUS_DRAGON, TANGERINE},
8 U256,
9 },
10 SelfDestructResult,
11};
12use std::vec::Vec;
13
14macro_rules! tri {
16 ($e:expr) => {
17 match $e {
18 Some(v) => v,
19 None => return None,
20 }
21 };
22}
23
24#[allow(clippy::collapsible_else_if)]
26#[inline]
27pub fn sstore_refund(spec_id: SpecId, original: U256, current: U256, new: U256) -> i64 {
28 if spec_id.is_enabled_in(SpecId::ISTANBUL) {
29 let sstore_clears_schedule = if spec_id.is_enabled_in(SpecId::LONDON) {
31 (SSTORE_RESET - COLD_SLOAD_COST + ACCESS_LIST_STORAGE_KEY) as i64
32 } else {
33 REFUND_SSTORE_CLEARS
34 };
35 if current == new {
36 0
37 } else {
38 if original == current && new == U256::ZERO {
39 sstore_clears_schedule
40 } else {
41 let mut refund = 0;
42
43 if original != U256::ZERO {
44 if current == U256::ZERO {
45 refund -= sstore_clears_schedule;
46 } else if new == U256::ZERO {
47 refund += sstore_clears_schedule;
48 }
49 }
50
51 if original == new {
52 let (gas_sstore_reset, gas_sload) = if spec_id.is_enabled_in(SpecId::BERLIN) {
53 (SSTORE_RESET - COLD_SLOAD_COST, WARM_STORAGE_READ_COST)
54 } else {
55 (SSTORE_RESET, sload_cost(spec_id, false))
56 };
57 if original == U256::ZERO {
58 refund += (SSTORE_SET - gas_sload) as i64;
59 } else {
60 refund += (gas_sstore_reset - gas_sload) as i64;
61 }
62 }
63
64 refund
65 }
66 }
67 } else {
68 if current != U256::ZERO && new == U256::ZERO {
69 REFUND_SSTORE_CLEARS
70 } else {
71 0
72 }
73 }
74}
75
76#[inline]
78pub const fn create2_cost(len: u64) -> Option<u64> {
79 CREATE.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
80}
81
82#[inline]
83const fn log2floor(value: U256) -> u64 {
84 let mut l: u64 = 256;
85 let mut i = 3;
86 loop {
87 if value.as_limbs()[i] == 0u64 {
88 l -= 64;
89 } else {
90 l -= value.as_limbs()[i].leading_zeros() as u64;
91 if l == 0 {
92 return l;
93 } else {
94 return l - 1;
95 }
96 }
97 if i == 0 {
98 break;
99 }
100 i -= 1;
101 }
102 l
103}
104
105#[inline]
107pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
108 if power == U256::ZERO {
109 Some(EXP)
110 } else {
111 let gas_byte = U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
113 50
114 } else {
115 10
116 });
117 let gas = U256::from(EXP)
118 .checked_add(gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?)?;
119
120 u64::try_from(gas).ok()
121 }
122}
123
124#[inline]
126pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
127 VERYLOW.checked_add(tri!(cost_per_word(len, COPY)))
128}
129
130#[inline]
132pub const fn extcodecopy_cost(spec_id: SpecId, len: u64, is_cold: bool) -> Option<u64> {
133 let base_gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
134 warm_cold_cost(is_cold)
135 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
136 700
137 } else {
138 20
139 };
140 base_gas.checked_add(tri!(cost_per_word(len, COPY)))
141}
142
143#[inline]
145pub const fn log_cost(n: u8, len: u64) -> Option<u64> {
146 tri!(LOG.checked_add(tri!(LOGDATA.checked_mul(len)))).checked_add(LOGTOPIC * n as u64)
147}
148
149#[inline]
151pub const fn keccak256_cost(len: u64) -> Option<u64> {
152 KECCAK256.checked_add(tri!(cost_per_word(len, KECCAK256WORD)))
153}
154
155#[inline]
157pub const fn cost_per_word(len: u64, multiple: u64) -> Option<u64> {
158 multiple.checked_mul(len.div_ceil(32))
159}
160
161#[inline]
167pub const fn initcode_cost(len: u64) -> u64 {
168 let Some(cost) = cost_per_word(len, INITCODE_WORD_COST) else {
169 panic!("initcode cost overflow")
170 };
171 cost
172}
173
174#[inline]
176pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
177 if spec_id.is_enabled_in(SpecId::BERLIN) {
178 if is_cold {
179 COLD_SLOAD_COST
180 } else {
181 WARM_STORAGE_READ_COST
182 }
183 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
184 INSTANBUL_SLOAD_GAS
186 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
187 200
189 } else {
190 50
191 }
192}
193
194#[inline]
196pub fn sstore_cost(
197 spec_id: SpecId,
198 original: U256,
199 current: U256,
200 new: U256,
201 gas: u64,
202 is_cold: bool,
203) -> Option<u64> {
204 if spec_id.is_enabled_in(SpecId::ISTANBUL) && gas <= CALL_STIPEND {
206 return None;
207 }
208
209 if spec_id.is_enabled_in(SpecId::BERLIN) {
210 let mut gas_cost = istanbul_sstore_cost::<WARM_STORAGE_READ_COST, WARM_SSTORE_RESET>(
212 original, current, new,
213 );
214
215 if is_cold {
216 gas_cost += COLD_SLOAD_COST;
217 }
218 Some(gas_cost)
219 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
220 Some(istanbul_sstore_cost::<INSTANBUL_SLOAD_GAS, SSTORE_RESET>(
222 original, current, new,
223 ))
224 } else {
225 Some(frontier_sstore_cost(current, new))
227 }
228}
229
230#[inline]
232fn istanbul_sstore_cost<const SLOAD_GAS: u64, const SSTORE_RESET_GAS: u64>(
233 original: U256,
234 current: U256,
235 new: U256,
236) -> u64 {
237 if new == current {
238 SLOAD_GAS
239 } else if original == current && original == U256::ZERO {
240 SSTORE_SET
241 } else if original == current {
242 SSTORE_RESET_GAS
243 } else {
244 SLOAD_GAS
245 }
246}
247
248#[inline]
250fn frontier_sstore_cost(current: U256, new: U256) -> u64 {
251 if current == U256::ZERO && new != U256::ZERO {
252 SSTORE_SET
253 } else {
254 SSTORE_RESET
255 }
256}
257
258#[inline]
260pub const fn selfdestruct_cost(spec_id: SpecId, res: SelfDestructResult) -> u64 {
261 let should_charge_topup = if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
263 res.had_value && !res.target_exists
264 } else {
265 !res.target_exists
266 };
267
268 let selfdestruct_gas_topup = if spec_id.is_enabled_in(SpecId::TANGERINE) && should_charge_topup
270 {
271 25000
272 } else {
273 0
274 };
275
276 let selfdestruct_gas = if spec_id.is_enabled_in(SpecId::TANGERINE) {
278 5000
279 } else {
280 0
281 };
282
283 let mut gas = selfdestruct_gas + selfdestruct_gas_topup;
284 if spec_id.is_enabled_in(SpecId::BERLIN) && res.is_cold {
285 gas += COLD_ACCOUNT_ACCESS_COST
286 }
287 gas
288}
289
290#[inline]
298pub const fn call_cost(
299 spec_id: SpecId,
300 transfers_value: bool,
301 is_cold: bool,
302 new_account_accounting: bool,
303) -> u64 {
304 let mut gas = if spec_id.is_enabled_in(BERLIN) {
306 warm_cold_cost(is_cold)
307 } else if spec_id.is_enabled_in(TANGERINE) {
308 700
310 } else {
311 40
312 };
313
314 if transfers_value {
316 gas += CALLVALUE;
317 }
318
319 if new_account_accounting {
321 if spec_id.is_enabled_in(SPURIOUS_DRAGON) {
323 if transfers_value {
325 gas += NEWACCOUNT;
326 }
327 } else {
328 gas += NEWACCOUNT;
329 }
330 }
331
332 gas
333}
334
335#[inline]
337pub const fn warm_cold_cost(is_cold: bool) -> u64 {
338 if is_cold {
339 COLD_ACCOUNT_ACCESS_COST
340 } else {
341 WARM_STORAGE_READ_COST
342 }
343}
344
345#[inline]
347pub const fn memory_gas(a: usize) -> u64 {
348 let a = a as u64;
349 MEMORY
350 .saturating_mul(a)
351 .saturating_add(a.saturating_mul(a) / 512)
352}
353
354pub fn validate_initial_tx_gas(
357 spec_id: SpecId,
358 input: &[u8],
359 is_create: bool,
360 access_list: &[(Address, Vec<U256>)],
361 initcodes: &[Bytes],
362) -> u64 {
363 let mut initial_gas = 0;
364 let mut zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
365 let mut non_zero_data_len = input.len() as u64 - zero_data_len;
366
367 for initcode in initcodes {
369 let zeros = initcode.iter().filter(|v| **v == 0).count() as u64;
370 zero_data_len += zeros;
371 non_zero_data_len += initcode.len() as u64 - zeros;
372 }
373
374 initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
376 initial_gas += non_zero_data_len
378 * if spec_id.is_enabled_in(SpecId::ISTANBUL) {
379 16
380 } else {
381 68
382 };
383
384 if spec_id.is_enabled_in(SpecId::BERLIN) {
386 let accessed_slots = access_list
387 .iter()
388 .fold(0, |slot_count, (_, slots)| slot_count + slots.len() as u64);
389 initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
390 initial_gas += accessed_slots * ACCESS_LIST_STORAGE_KEY;
391 }
392
393 initial_gas += if is_create {
395 if spec_id.is_enabled_in(SpecId::HOMESTEAD) {
396 53000
398 } else {
399 21000
400 }
401 } else {
402 21000
403 };
404
405 if spec_id.is_enabled_in(SpecId::SHANGHAI) && is_create {
408 initial_gas += initcode_cost(input.len() as u64)
409 }
410
411 initial_gas
412}