1use revm_interpreter::{SStoreResult, StateLoad};
4use revm_primitives::{hardfork::SpecId, U256};
5
6pub use revm_context_interface::journaled_state::AccountLoad;
7pub use revm_interpreter::gas::*;
8
9#[allow(unused_macros)]
11macro_rules! tri {
12 ($e:expr) => {
13 match $e {
14 Some(v) => v,
15 None => return None,
16 }
17 };
18}
19
20pub const SELFDESTRUCT: i64 = SELFDESTRUCT_REFUND;
22
23pub const MIN_CALLEE_GAS: u64 = 2300;
25
26#[inline]
28pub const fn cost_per_word(len: u64, per_word_cost: u64) -> Option<u64> {
29 let words = len.div_ceil(32);
30 words.checked_mul(per_word_cost)
31}
32
33#[inline]
35pub const fn warm_cold_cost(is_cold: bool) -> u64 {
36 if is_cold {
37 COLD_ACCOUNT_ACCESS_COST
38 } else {
39 WARM_STORAGE_READ_COST
40 }
41}
42
43#[inline]
45pub fn warm_cold_cost_with_delegation(state: StateLoad<bool>) -> u64 {
46 if state.is_cold {
47 COLD_ACCOUNT_ACCESS_COST
48 } else {
49 WARM_STORAGE_READ_COST
50 }
51}
52
53#[inline]
55pub fn extcodecopy_cost(spec_id: SpecId, len: u64, is_cold: bool) -> Option<u64> {
56 let base = if spec_id.is_enabled_in(SpecId::BERLIN) {
57 warm_cold_cost(is_cold)
58 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
59 700
60 } else {
61 20
62 };
63 cost_per_word(len, COPY).map(|word_cost| base.saturating_add(word_cost))
64}
65
66#[inline]
68pub const fn sload_cost(spec_id: SpecId, is_cold: bool) -> u64 {
69 if spec_id.is_enabled_in(SpecId::BERLIN) {
70 if is_cold {
71 COLD_SLOAD_COST
72 } else {
73 WARM_STORAGE_READ_COST
74 }
75 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
76 ISTANBUL_SLOAD_GAS
77 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
78 200
79 } else {
80 50
81 }
82}
83
84#[inline]
86pub fn sstore_cost(
87 spec_id: SpecId,
88 result: &SStoreResult,
89 remaining_gas: u64,
90 is_cold: bool,
91) -> Option<u64> {
92 if spec_id.is_enabled_in(SpecId::BERLIN) {
93 let base = if is_cold { COLD_SLOAD_COST } else { 0 };
94 let cost = if result.original_value == result.new_value {
95 WARM_STORAGE_READ_COST
96 } else if result.original_value == result.present_value {
97 if result.original_value.is_zero() {
98 SSTORE_SET
99 } else {
100 WARM_SSTORE_RESET
101 }
102 } else {
103 WARM_STORAGE_READ_COST
104 };
105 Some(base.saturating_add(cost))
106 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
107 let stipend = 2300;
108 if remaining_gas <= stipend {
109 return None;
110 }
111 let cost = if result.original_value == result.new_value {
112 ISTANBUL_SLOAD_GAS
113 } else if result.original_value == result.present_value {
114 if result.original_value.is_zero() {
115 SSTORE_SET
116 } else {
117 SSTORE_RESET
118 }
119 } else {
120 ISTANBUL_SLOAD_GAS
121 };
122 Some(cost)
123 } else {
124 let cost = if result.present_value.is_zero() && !result.new_value.is_zero() {
125 SSTORE_SET
126 } else {
127 SSTORE_RESET
128 };
129 Some(cost)
130 }
131}
132
133#[inline]
135pub fn sstore_refund(spec_id: SpecId, result: &SStoreResult) -> i64 {
136 if spec_id.is_enabled_in(SpecId::BERLIN) {
137 let mut refund = 0i64;
138 if result.original_value != result.present_value
139 && result.original_value == result.new_value
140 {
141 if result.original_value.is_zero() {
142 refund += (SSTORE_SET - WARM_STORAGE_READ_COST) as i64;
143 } else {
144 refund += (WARM_SSTORE_RESET - WARM_STORAGE_READ_COST) as i64;
145 }
146 }
147 if !result.present_value.is_zero() && result.new_value.is_zero() {
148 refund += REFUND_SSTORE_CLEARS;
149 }
150 if !result.original_value.is_zero()
151 && result.present_value.is_zero()
152 && result.new_value == result.original_value
153 {
154 refund -= REFUND_SSTORE_CLEARS;
155 }
156 refund
157 } else if spec_id.is_enabled_in(SpecId::ISTANBUL) {
158 let mut refund = 0i64;
159 if result.original_value != result.present_value
160 && result.original_value == result.new_value
161 {
162 if result.original_value.is_zero() {
163 refund += (SSTORE_SET - ISTANBUL_SLOAD_GAS) as i64;
164 } else {
165 refund += (SSTORE_RESET - ISTANBUL_SLOAD_GAS) as i64;
166 }
167 }
168 if !result.present_value.is_zero() && result.new_value.is_zero() {
169 refund += REFUND_SSTORE_CLEARS;
170 }
171 if !result.original_value.is_zero()
172 && result.present_value.is_zero()
173 && result.new_value == result.original_value
174 {
175 refund -= REFUND_SSTORE_CLEARS;
176 }
177 refund
178 } else if !result.present_value.is_zero() && result.new_value.is_zero() {
179 REFUND_SSTORE_CLEARS
180 } else {
181 0
182 }
183}
184
185#[inline]
187pub fn call_cost(
188 spec_id: SpecId,
189 transfers_value: bool,
190 account_load: StateLoad<AccountLoad>,
191) -> u64 {
192 let mut gas = if spec_id.is_enabled_in(SpecId::BERLIN) {
193 warm_cold_cost(account_load.is_cold)
194 + if let Some(is_cold) = account_load.data.is_delegate_account_cold {
195 warm_cold_cost(is_cold)
196 } else {
197 0
198 }
199 } else if spec_id.is_enabled_in(SpecId::TANGERINE) {
200 700
201 } else {
202 40
203 };
204
205 if transfers_value {
206 gas += CALLVALUE;
207 }
208
209 if account_load.data.is_empty
210 && (transfers_value || !spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON))
211 {
212 gas += NEWACCOUNT;
213 }
214
215 gas
216}
217
218#[inline]
220pub fn create2_cost(len: u64) -> Option<u64> {
221 cost_per_word(len, KECCAK256WORD).map(|x| CREATE.saturating_add(x))
222}
223
224#[inline]
226pub const fn initcode_cost(len: u64) -> u64 {
227 let words = len.div_ceil(32);
228 words.saturating_mul(INITCODE_WORD_COST)
229}
230
231#[inline]
235pub fn dyn_exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
236 #[inline]
237 const fn log2floor(value: U256) -> u64 {
238 let mut l: u64 = 256;
239 let mut i = 3;
240 loop {
241 if value.as_limbs()[i] == 0u64 {
242 l -= 64;
243 } else {
244 l -= value.as_limbs()[i].leading_zeros() as u64;
245 if l == 0 {
246 return l;
247 } else {
248 return l - 1;
249 }
250 }
251 if i == 0 {
252 break;
253 }
254 i -= 1;
255 }
256 l
257 }
258
259 if power == U256::ZERO {
260 Some(0)
261 } else {
262 let gas_byte =
264 U256::from(if spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) { 50 } else { 10 });
265 let gas = gas_byte.checked_mul(U256::from(log2floor(power) / 8 + 1))?;
266 u64::try_from(gas).ok()
267 }
268}
269
270#[inline]
272pub const fn dyn_log_cost(len: u64) -> Option<u64> {
273 LOGDATA.checked_mul(len)
274}
275
276#[inline]
278pub const fn dyn_keccak256_cost(len: u64) -> Option<u64> {
279 cost_per_word(len, KECCAK256WORD)
280}
281
282#[inline]
284pub const fn dyn_verylowcopy_cost(len: u64) -> Option<u64> {
285 cost_per_word(len, COPY)
286}
287
288#[inline]
290pub fn exp_cost(spec_id: SpecId, power: U256) -> Option<u64> {
291 dyn_exp_cost(spec_id, power).map(|dyn_cost| EXP.saturating_add(dyn_cost))
292}
293
294#[inline]
296pub const fn log_cost(n_topics: u8, len: u64) -> Option<u64> {
297 let base = LOG + (LOGTOPIC * n_topics as u64);
298 match dyn_log_cost(len) {
299 Some(dyn_cost) => Some(base.saturating_add(dyn_cost)),
300 None => None,
301 }
302}
303
304#[inline]
306pub const fn keccak256_cost(len: u64) -> Option<u64> {
307 match dyn_keccak256_cost(len) {
308 Some(dyn_cost) => Some(KECCAK256.saturating_add(dyn_cost)),
309 None => None,
310 }
311}
312
313#[inline]
315pub const fn verylowcopy_cost(len: u64) -> Option<u64> {
316 match dyn_verylowcopy_cost(len) {
317 Some(dyn_cost) => Some(VERYLOW.saturating_add(dyn_cost)),
318 None => None,
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn exp_cost() {
328 for (spec_id, power) in [
329 (SpecId::CANCUN, U256::from(0)),
330 (SpecId::CANCUN, U256::from(1)),
331 (SpecId::CANCUN, U256::from(69)),
332 ] {
333 assert_eq!(
334 super::exp_cost(spec_id, power).unwrap(),
335 EXP + dyn_exp_cost(spec_id, power).unwrap(),
336 );
337 }
338 }
339
340 #[test]
341 fn log_cost() {
342 for n_topics in [0, 1, 2] {
343 for len in [0, 1, 69] {
344 assert_eq!(
345 super::log_cost(n_topics, len).unwrap(),
346 LOG + (LOGTOPIC * n_topics as u64) + dyn_log_cost(len).unwrap(),
347 );
348 }
349 }
350 }
351
352 #[test]
353 fn keccak256_cost() {
354 for len in [0, 1, 69] {
355 assert_eq!(
356 super::keccak256_cost(len).unwrap(),
357 KECCAK256 + dyn_keccak256_cost(len).unwrap(),
358 );
359 }
360 }
361
362 #[test]
363 fn verylowcopy_cost() {
364 for len in [0, 1, 69] {
365 assert_eq!(
366 super::verylowcopy_cost(len).unwrap(),
367 VERYLOW + dyn_verylowcopy_cost(len).unwrap(),
368 );
369 }
370 }
371}