revm_interpreter/instructions/
host.rs

1use crate::{
2    gas::{
3        self, CALL_STIPEND, COLD_ACCOUNT_ACCESS_COST_ADDITIONAL, COLD_SLOAD_COST_ADDITIONAL,
4        ISTANBUL_SLOAD_GAS, WARM_STORAGE_READ_COST,
5    },
6    instructions::utility::{IntoAddress, IntoU256},
7    interpreter_types::{InputsTr, InterpreterTypes, MemoryTr, RuntimeFlag, StackTr},
8    Host, InstructionResult,
9};
10use context_interface::host::LoadError;
11use core::cmp::min;
12use primitives::{hardfork::SpecId::*, Bytes, Log, LogData, B256, BLOCK_HASH_HISTORY, U256};
13
14use crate::InstructionContext;
15
16/// Implements the BALANCE instruction.
17///
18/// Gets the balance of the given account.
19pub fn balance<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
20    popn_top!([], top, context.interpreter);
21    let address = top.into_address();
22    let spec_id = context.interpreter.runtime_flag.spec_id();
23    if spec_id.is_enabled_in(BERLIN) {
24        let account = berlin_load_account!(context, address, false);
25        *top = account.balance;
26    } else {
27        let gas = if spec_id.is_enabled_in(ISTANBUL) {
28            // EIP-1884: Repricing for trie-size-dependent opcodes
29            700
30        } else if spec_id.is_enabled_in(TANGERINE) {
31            400
32        } else {
33            20
34        };
35        gas!(context.interpreter, gas);
36        let Ok(account) = context
37            .host
38            .load_account_info_skip_cold_load(address, false, false)
39        else {
40            return context.interpreter.halt_fatal();
41        };
42        *top = account.balance;
43    };
44}
45
46/// EIP-1884: Repricing for trie-size-dependent opcodes
47pub fn selfbalance<WIRE: InterpreterTypes, H: Host + ?Sized>(
48    context: InstructionContext<'_, H, WIRE>,
49) {
50    check!(context.interpreter, ISTANBUL);
51    //gas!(context.interpreter, gas::LOW);
52
53    let Some(balance) = context
54        .host
55        .balance(context.interpreter.input.target_address())
56    else {
57        return context.interpreter.halt_fatal();
58    };
59    push!(context.interpreter, balance.data);
60}
61
62/// Implements the EXTCODESIZE instruction.
63///
64/// Gets the size of an account's code.
65pub fn extcodesize<WIRE: InterpreterTypes, H: Host + ?Sized>(
66    context: InstructionContext<'_, H, WIRE>,
67) {
68    popn_top!([], top, context.interpreter);
69    let address = top.into_address();
70    let spec_id = context.interpreter.runtime_flag.spec_id();
71    if spec_id.is_enabled_in(BERLIN) {
72        let account = berlin_load_account!(context, address, true);
73        // safe to unwrap because we are loading code
74        *top = U256::from(account.code.as_ref().unwrap().len());
75    } else {
76        let gas = if spec_id.is_enabled_in(TANGERINE) {
77            700
78        } else {
79            20
80        };
81        gas!(context.interpreter, gas);
82        let Ok(account) = context
83            .host
84            .load_account_info_skip_cold_load(address, true, false)
85        else {
86            return context.interpreter.halt_fatal();
87        };
88        // safe to unwrap because we are loading code
89        *top = U256::from(account.code.as_ref().unwrap().len());
90    }
91}
92
93/// EIP-1052: EXTCODEHASH opcode
94pub fn extcodehash<WIRE: InterpreterTypes, H: Host + ?Sized>(
95    context: InstructionContext<'_, H, WIRE>,
96) {
97    check!(context.interpreter, CONSTANTINOPLE);
98    popn_top!([], top, context.interpreter);
99    let address = top.into_address();
100
101    let spec_id = context.interpreter.runtime_flag.spec_id();
102    let account = if spec_id.is_enabled_in(BERLIN) {
103        berlin_load_account!(context, address, true)
104    } else {
105        let gas = if spec_id.is_enabled_in(ISTANBUL) {
106            700
107        } else {
108            400
109        };
110        gas!(context.interpreter, gas);
111        let Ok(account) = context
112            .host
113            .load_account_info_skip_cold_load(address, true, false)
114        else {
115            return context.interpreter.halt_fatal();
116        };
117        account
118    };
119    // if account is empty, code hash is zero
120    let code_hash = if account.is_empty() {
121        B256::ZERO
122    } else {
123        account.code_hash
124    };
125    *top = code_hash.into_u256();
126}
127
128/// Implements the EXTCODECOPY instruction.
129///
130/// Copies a portion of an account's code to memory.
131pub fn extcodecopy<WIRE: InterpreterTypes, H: Host + ?Sized>(
132    context: InstructionContext<'_, H, WIRE>,
133) {
134    popn!(
135        [address, memory_offset, code_offset, len_u256],
136        context.interpreter
137    );
138    let address = address.into_address();
139
140    let spec_id = context.interpreter.runtime_flag.spec_id();
141
142    let len = as_usize_or_fail!(context.interpreter, len_u256);
143    gas!(
144        context.interpreter,
145        gas::copy_cost(0, len).unwrap_or(u64::MAX)
146    );
147
148    let mut memory_offset_usize = 0;
149    // resize memory only if len is not zero
150    if len != 0 {
151        // fail on casting of memory_offset only if len is not zero.
152        memory_offset_usize = as_usize_or_fail!(context.interpreter, memory_offset);
153        resize_memory!(context.interpreter, memory_offset_usize, len);
154    }
155
156    let code = if spec_id.is_enabled_in(BERLIN) {
157        let account = berlin_load_account!(context, address, true);
158        account.code.as_ref().unwrap().original_bytes()
159    } else {
160        let gas = if spec_id.is_enabled_in(TANGERINE) {
161            700
162        } else {
163            20
164        };
165        gas!(context.interpreter, gas);
166
167        let Some(code) = context.host.load_account_code(address) else {
168            return context.interpreter.halt_fatal();
169        };
170        code.data
171    };
172
173    let code_offset_usize = min(as_usize_saturated!(code_offset), code.len());
174
175    // Note: This can't panic because we resized memory to fit.
176    // len zero is handled in set_data
177    context
178        .interpreter
179        .memory
180        .set_data(memory_offset_usize, code_offset_usize, len, &code);
181}
182
183/// Implements the BLOCKHASH instruction.
184///
185/// Gets the hash of one of the 256 most recent complete blocks.
186pub fn blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(
187    context: InstructionContext<'_, H, WIRE>,
188) {
189    //gas!(context.interpreter, gas::BLOCKHASH);
190    popn_top!([], number, context.interpreter);
191
192    let requested_number = *number;
193    let block_number = context.host.block_number();
194
195    let Some(diff) = block_number.checked_sub(requested_number) else {
196        *number = U256::ZERO;
197        return;
198    };
199
200    let diff = as_u64_saturated!(diff);
201
202    // blockhash should push zero if number is same as current block number.
203    if diff == 0 {
204        *number = U256::ZERO;
205        return;
206    }
207
208    *number = if diff <= BLOCK_HASH_HISTORY {
209        let Some(hash) = context.host.block_hash(as_u64_saturated!(requested_number)) else {
210            return context.interpreter.halt_fatal();
211        };
212        U256::from_be_bytes(hash.0)
213    } else {
214        U256::ZERO
215    }
216}
217
218/// Implements the SLOAD instruction.
219///
220/// Loads a word from storage.
221pub fn sload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
222    popn_top!([], index, context.interpreter);
223    let spec_id = context.interpreter.runtime_flag.spec_id();
224    let target = context.interpreter.input.target_address();
225
226    // `SLOAD` opcode cost calculation.
227    let gas = if spec_id.is_enabled_in(BERLIN) {
228        WARM_STORAGE_READ_COST
229    } else if spec_id.is_enabled_in(ISTANBUL) {
230        // EIP-1884: Repricing for trie-size-dependent opcodes
231        ISTANBUL_SLOAD_GAS
232    } else if spec_id.is_enabled_in(TANGERINE) {
233        // EIP-150: Gas cost changes for IO-heavy operations
234        200
235    } else {
236        50
237    };
238    gas!(context.interpreter, gas);
239    if spec_id.is_enabled_in(BERLIN) {
240        let skip_cold = context.interpreter.gas.remaining() < COLD_SLOAD_COST_ADDITIONAL;
241        let res = context.host.sload_skip_cold_load(target, *index, skip_cold);
242        match res {
243            Ok(storage) => {
244                if storage.is_cold {
245                    gas!(context.interpreter, COLD_SLOAD_COST_ADDITIONAL);
246                }
247
248                *index = storage.data;
249            }
250            Err(LoadError::ColdLoadSkipped) => context.interpreter.halt_oog(),
251            Err(LoadError::DBError) => context.interpreter.halt_fatal(),
252        }
253    } else {
254        let Some(storage) = context.host.sload(target, *index) else {
255            return context.interpreter.halt_fatal();
256        };
257        *index = storage.data;
258    };
259}
260
261/// Implements the SSTORE instruction.
262///
263/// Stores a word to storage.
264pub fn sstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
265    require_non_staticcall!(context.interpreter);
266    popn!([index, value], context.interpreter);
267
268    let target = context.interpreter.input.target_address();
269    let spec_id = context.interpreter.runtime_flag.spec_id();
270
271    // EIP-1706 Disable SSTORE with gasleft lower than call stipend
272    if context
273        .interpreter
274        .runtime_flag
275        .spec_id()
276        .is_enabled_in(ISTANBUL)
277        && context.interpreter.gas.remaining() <= CALL_STIPEND
278    {
279        context
280            .interpreter
281            .halt(InstructionResult::ReentrancySentryOOG);
282        return;
283    }
284
285    // static gas
286    gas!(
287        context.interpreter,
288        gas::static_sstore_cost(context.interpreter.runtime_flag.spec_id())
289    );
290
291    let state_load = if spec_id.is_enabled_in(BERLIN) {
292        let skip_cold = context.interpreter.gas.remaining() < COLD_SLOAD_COST_ADDITIONAL;
293        let res = context
294            .host
295            .sstore_skip_cold_load(target, index, value, skip_cold);
296        match res {
297            Ok(load) => load,
298            Err(LoadError::ColdLoadSkipped) => return context.interpreter.halt_oog(),
299            Err(LoadError::DBError) => return context.interpreter.halt_fatal(),
300        }
301    } else {
302        let Some(load) = context.host.sstore(target, index, value) else {
303            return context.interpreter.halt_fatal();
304        };
305        load
306    };
307
308    // dynamic gas
309    gas!(
310        context.interpreter,
311        gas::dyn_sstore_cost(
312            context.interpreter.runtime_flag.spec_id(),
313            &state_load.data,
314            state_load.is_cold
315        )
316    );
317
318    // refund
319    context.interpreter.gas.record_refund(gas::sstore_refund(
320        context.interpreter.runtime_flag.spec_id(),
321        &state_load.data,
322    ));
323}
324
325/// EIP-1153: Transient storage opcodes
326/// Store value to transient storage
327pub fn tstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
328    check!(context.interpreter, CANCUN);
329    require_non_staticcall!(context.interpreter);
330    //gas!(context.interpreter, gas::WARM_STORAGE_READ_COST);
331
332    popn!([index, value], context.interpreter);
333
334    context
335        .host
336        .tstore(context.interpreter.input.target_address(), index, value);
337}
338
339/// EIP-1153: Transient storage opcodes
340/// Load value from transient storage
341pub fn tload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
342    check!(context.interpreter, CANCUN);
343    //gas!(context.interpreter, gas::WARM_STORAGE_READ_COST);
344
345    popn_top!([], index, context.interpreter);
346
347    *index = context
348        .host
349        .tload(context.interpreter.input.target_address(), *index);
350}
351
352/// Implements the LOG0-LOG4 instructions.
353///
354/// Appends log record with N topics.
355pub fn log<const N: usize, H: Host + ?Sized>(
356    context: InstructionContext<'_, H, impl InterpreterTypes>,
357) {
358    require_non_staticcall!(context.interpreter);
359
360    popn!([offset, len], context.interpreter);
361    let len = as_usize_or_fail!(context.interpreter, len);
362    gas_or_fail!(context.interpreter, gas::log_cost(N as u8, len as u64));
363    let data = if len == 0 {
364        Bytes::new()
365    } else {
366        let offset = as_usize_or_fail!(context.interpreter, offset);
367        resize_memory!(context.interpreter, offset, len);
368        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
369    };
370    if context.interpreter.stack.len() < N {
371        context.interpreter.halt(InstructionResult::StackUnderflow);
372        return;
373    }
374    let Some(topics) = context.interpreter.stack.popn::<N>() else {
375        context.interpreter.halt(InstructionResult::StackUnderflow);
376        return;
377    };
378
379    let log = Log {
380        address: context.interpreter.input.target_address(),
381        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
382            .expect("LogData should have <=4 topics"),
383    };
384
385    context.host.log(log);
386}
387
388/// Implements the SELFDESTRUCT instruction.
389///
390/// Halt execution and register account for later deletion.
391pub fn selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
392    context: InstructionContext<'_, H, WIRE>,
393) {
394    require_non_staticcall!(context.interpreter);
395    popn!([target], context.interpreter);
396    let target = target.into_address();
397    let spec = context.interpreter.runtime_flag.spec_id();
398
399    // static gas
400    gas!(context.interpreter, gas::static_selfdestruct_cost(spec));
401
402    let Some(res) = context
403        .host
404        .selfdestruct(context.interpreter.input.target_address(), target)
405    else {
406        context
407            .interpreter
408            .halt(InstructionResult::FatalExternalError);
409        return;
410    };
411
412    gas!(context.interpreter, gas::dyn_selfdestruct_cost(spec, &res));
413
414    // EIP-3529: Reduction in refunds
415    if !context
416        .interpreter
417        .runtime_flag
418        .spec_id()
419        .is_enabled_in(LONDON)
420        && !res.previously_destroyed
421    {
422        context
423            .interpreter
424            .gas
425            .record_refund(gas::SELFDESTRUCT_REFUND);
426    }
427
428    context.interpreter.halt(InstructionResult::SelfDestruct);
429}