Skip to main content

revm_interpreter/instructions/
host.rs

1use crate::InstructionContext as Ictx;
2use crate::{
3    instructions::utility::{IntoAddress, IntoU256},
4    interpreter_types::{InputsTr, InterpreterTypes as ITy, MemoryTr, RuntimeFlag, StackTr},
5    Gas, Host, InstructionExecResult as Result, InstructionResult,
6};
7use context_interface::{
8    context::{SStoreResult, StateLoad},
9    host::LoadError,
10    journaled_state::AccountInfoLoad,
11};
12use core::cmp::min;
13use primitives::{
14    hardfork::SpecId::{self, *},
15    Address, Bytes, Log, LogData, B256, BLOCK_HASH_HISTORY, U256,
16};
17
18/// Loads an account, handling cold load gas accounting.
19///
20/// Pre-Berlin, `cold_account_additional_cost` is 0, so the cold load logic is a no-op.
21fn load_account<'a, H: Host + ?Sized>(
22    gas: &mut Gas,
23    host: &'a mut H,
24    address: primitives::Address,
25    load_code: bool,
26) -> core::result::Result<AccountInfoLoad<'a>, LoadError> {
27    let cold_load_gas = host.gas_params().cold_account_additional_cost();
28    let skip_cold_load = gas.remaining() < cold_load_gas;
29    let account = host.load_account_info_skip_cold_load(address, load_code, skip_cold_load)?;
30    if account.is_cold && !gas.record_regular_cost(cold_load_gas) {
31        return Err(LoadError::ColdLoadSkipped);
32    }
33    Ok(account)
34}
35
36/// Implements the BALANCE instruction.
37///
38/// Gets the balance of the given account.
39pub fn balance<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
40    popn_top!([], top, context.interpreter);
41    let address = top.into_address();
42    let account = load_account(&mut context.interpreter.gas, context.host, address, false)?;
43    *top = account.balance;
44    Ok(())
45}
46
47/// EIP-1884: Repricing for trie-size-dependent opcodes
48pub fn selfbalance<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
49    check!(context.interpreter, ISTANBUL);
50
51    let balance = context
52        .host
53        .balance(context.interpreter.input.target_address())
54        .ok_or(InstructionResult::FatalExternalError)?;
55    push!(context.interpreter, balance.data);
56    Ok(())
57}
58
59/// Implements the EXTCODESIZE instruction.
60///
61/// Gets the size of an account's code.
62pub fn extcodesize<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
63    popn_top!([], top, context.interpreter);
64    let address = top.into_address();
65    let account = load_account(&mut context.interpreter.gas, context.host, address, true)?;
66    // safe to unwrap because we are loading code
67    *top = U256::from(account.code.as_ref().unwrap().len());
68    Ok(())
69}
70
71/// EIP-1052: EXTCODEHASH opcode
72pub fn extcodehash<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
73    check!(context.interpreter, PETERSBURG);
74    popn_top!([], top, context.interpreter);
75    let address = top.into_address();
76    let account = load_account(&mut context.interpreter.gas, context.host, address, false)?;
77    // if account is empty, code hash is zero
78    let code_hash = if account.is_empty() {
79        B256::ZERO
80    } else {
81        account.code_hash
82    };
83    *top = code_hash.into_u256();
84    Ok(())
85}
86
87/// Implements the EXTCODECOPY instruction.
88///
89/// Copies a portion of an account's code to memory.
90pub fn extcodecopy<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
91    popn!(
92        [address, memory_offset, code_offset, len_u256],
93        context.interpreter
94    );
95    let address = address.into_address();
96
97    let len = as_usize_or_fail!(context.interpreter, len_u256);
98    gas!(
99        context.interpreter,
100        context.host.gas_params().extcodecopy(len)
101    );
102
103    let mut memory_offset_usize = 0;
104    // resize memory only if len is not zero
105    if len != 0 {
106        // fail on casting of memory_offset only if len is not zero.
107        memory_offset_usize = as_usize_or_fail!(context.interpreter, memory_offset);
108        // Resize memory to fit the code
109        context
110            .interpreter
111            .resize_memory(context.host.gas_params(), memory_offset_usize, len)?;
112    }
113
114    let account = load_account(&mut context.interpreter.gas, context.host, address, true)?;
115    let code = account.code.as_ref().unwrap().original_bytes();
116
117    let code_offset_usize = min(as_usize_saturated!(code_offset), code.len());
118
119    // Note: This can't panic because we resized memory to fit.
120    // len zero is handled in set_data
121    context
122        .interpreter
123        .memory
124        .set_data(memory_offset_usize, code_offset_usize, len, &code);
125    Ok(())
126}
127
128/// Implements the BLOCKHASH instruction.
129///
130/// Gets the hash of one of the 256 most recent complete blocks.
131pub fn blockhash<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
132    popn_top!([], number, context.interpreter);
133
134    let requested_number = *number;
135    let block_number = context.host.block_number();
136
137    let Some(diff) = block_number.checked_sub(requested_number) else {
138        *number = U256::ZERO;
139        return Ok(());
140    };
141
142    let diff = as_u64_saturated!(diff);
143
144    // blockhash should push zero if number is same as current block number.
145    if diff == 0 {
146        *number = U256::ZERO;
147        return Ok(());
148    }
149
150    *number = if diff <= BLOCK_HASH_HISTORY {
151        let hash = context
152            .host
153            .block_hash(as_u64_saturated!(requested_number))
154            .ok_or(InstructionResult::FatalExternalError)?;
155        U256::from_be_bytes(hash.0)
156    } else {
157        U256::ZERO
158    };
159    Ok(())
160}
161
162/// Implements the SLOAD instruction.
163///
164/// Loads a word from storage.
165pub fn sload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
166    popn_top!([], index, context.interpreter);
167    let spec_id = context.interpreter.runtime_flag.spec_id();
168    let target = context.interpreter.input.target_address();
169
170    if spec_id.is_enabled_in(BERLIN) {
171        let additional_cold_cost = context.host.gas_params().cold_storage_additional_cost();
172        let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost;
173        let storage = context
174            .host
175            .sload_skip_cold_load(target, *index, skip_cold)?;
176        if storage.is_cold {
177            gas!(context.interpreter, additional_cold_cost);
178        }
179        *index = storage.data;
180    } else {
181        let storage = context
182            .host
183            .sload(target, *index)
184            .ok_or(InstructionResult::FatalExternalError)?;
185        *index = storage.data;
186    };
187    Ok(())
188}
189
190/// Implements the SSTORE instruction.
191///
192/// Stores a word to storage.
193pub fn sstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
194    sstore_with_gas_accounting(context, sstore_default_gas_accounting)
195}
196
197/// Implements SSTORE, delegating dynamic gas and refund accounting to `gas_accounting`.
198///
199/// This helper performs the common SSTORE instruction flow: static-call checks,
200/// stack pops, stipend/static-gas charging, and the journaled storage write.
201/// Custom instruction sets can override the SSTORE opcode and call this helper
202/// with their own gas accounting closure.
203pub fn sstore_with_gas_accounting<'a, IT, H, F>(
204    mut context: Ictx<'a, H, IT>,
205    gas_accounting: F,
206) -> Result
207where
208    IT: ITy,
209    H: Host + ?Sized,
210    F: for<'ctx, 'load> FnOnce(
211        &'ctx mut Ictx<'a, H, IT>,
212        Address,
213        &'load StateLoad<SStoreResult>,
214    ) -> Result,
215{
216    require_non_staticcall!(context.interpreter);
217    popn!([index, value], context.interpreter);
218
219    let target = context.interpreter.input.target_address();
220    let spec_id = context.interpreter.runtime_flag.spec_id();
221
222    // EIP-2200: Structured Definitions for Net Gas Metering
223    // If gasleft is less than or equal to gas stipend, fail the current call frame with 'out of gas' exception.
224    if spec_id.is_enabled_in(ISTANBUL)
225        && context.interpreter.gas.remaining() <= context.host.gas_params().call_stipend()
226    {
227        return Err(InstructionResult::ReentrancySentryOOG);
228    }
229
230    gas!(
231        context.interpreter,
232        context.host.gas_params().sstore_static_gas()
233    );
234
235    let state_load = if spec_id.is_enabled_in(BERLIN) {
236        // Note: the slot is always cold-loaded (warmed), even when there is not
237        // enough gas to pay the cold cost. Skipping the load would avoid a DB
238        // read on a certain out-of-gas, but EIP-7928 (block access lists) records
239        // the slot access regardless of the subsequent OOG, so the warming must
240        // happen. The OOG outcome is unchanged: the cold charge below still fails.
241        context
242            .host
243            .sstore_skip_cold_load(target, index, value, false)?
244    } else {
245        context
246            .host
247            .sstore(target, index, value)
248            .ok_or(InstructionResult::FatalExternalError)?
249    };
250
251    gas_accounting(&mut context, target, &state_load)
252}
253
254/// Default dynamic gas and refund accounting for SSTORE.
255pub fn sstore_default_gas_accounting<IT, H>(
256    context: &mut Ictx<'_, H, IT>,
257    _target: Address,
258    state_load: &StateLoad<SStoreResult>,
259) -> Result
260where
261    IT: ITy,
262    H: Host + ?Sized,
263{
264    let spec_id = context.interpreter.runtime_flag.spec_id();
265    let is_istanbul = spec_id.is_enabled_in(ISTANBUL);
266
267    // dynamic gas
268    gas!(
269        context.interpreter,
270        context.host.gas_params().sstore_dynamic_gas(
271            is_istanbul,
272            &state_load.data,
273            state_load.is_cold
274        )
275    );
276
277    // state gas for new slot creation (EIP-8037)
278    if context.host.is_amsterdam_eip8037_enabled() {
279        state_gas!(
280            context.interpreter,
281            context.host.gas_params().sstore_state_gas(&state_load.data)
282        );
283
284        // EIP-8037 issue #2: 0→x→0 storage restoration refills the reservoir
285        // directly rather than routing the state gas through the capped refund
286        // counter. The regular-gas portion of the restoration still flows
287        // through `sstore_refund` below.
288        let refill = context
289            .host
290            .gas_params()
291            .sstore_state_gas_refill(&state_load.data);
292        if refill > 0 {
293            context.interpreter.gas.refill_reservoir(refill);
294        }
295    }
296
297    // refund
298    context.interpreter.gas.record_refund(
299        context
300            .host
301            .gas_params()
302            .sstore_refund(is_istanbul, &state_load.data),
303    );
304    Ok(())
305}
306
307/// EIP-1153: Transient storage opcodes
308/// Store value to transient storage
309pub fn tstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
310    check!(context.interpreter, CANCUN);
311    require_non_staticcall!(context.interpreter);
312    popn!([index, value], context.interpreter);
313
314    context
315        .host
316        .tstore(context.interpreter.input.target_address(), index, value);
317    Ok(())
318}
319
320/// EIP-1153: Transient storage opcodes
321/// Load value from transient storage
322pub fn tload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
323    check!(context.interpreter, CANCUN);
324    popn_top!([], index, context.interpreter);
325
326    *index = context
327        .host
328        .tload(context.interpreter.input.target_address(), *index);
329    Ok(())
330}
331
332/// Implements the LOG0-LOG4 instructions.
333///
334/// Appends log record with N topics.
335pub fn log<const N: usize, H: Host + ?Sized>(context: Ictx<'_, H, impl ITy>) -> Result {
336    require_non_staticcall!(context.interpreter);
337
338    popn!([offset, len], context.interpreter);
339    let len = as_usize_or_fail!(context.interpreter, len);
340    gas!(
341        context.interpreter,
342        context.host.gas_params().log_cost(N as u8, len as u64)
343    );
344    let data = if len == 0 {
345        Bytes::new()
346    } else {
347        let offset = as_usize_or_fail!(context.interpreter, offset);
348        // Resize memory to fit the data
349        context
350            .interpreter
351            .resize_memory(context.host.gas_params(), offset, len)?;
352        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
353    };
354    let Some(topics) = context.interpreter.stack.popn::<N>() else {
355        return Err(InstructionResult::StackUnderflow);
356    };
357
358    let log = Log {
359        address: context.interpreter.input.target_address(),
360        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
361            .expect("LogData should have <=4 topics"),
362    };
363
364    context.host.log(log);
365    Ok(())
366}
367
368/// Implements the SELFDESTRUCT instruction.
369///
370/// Halt execution and register account for later deletion.
371pub fn selfdestruct<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
372    require_non_staticcall!(context.interpreter);
373    popn!([target], context.interpreter);
374    let target = target.into_address();
375    let spec = context.interpreter.runtime_flag.spec_id();
376
377    let cold_load_gas = context.host.gas_params().selfdestruct_cold_cost();
378
379    let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas;
380    let res = context.host.selfdestruct(
381        context.interpreter.input.target_address(),
382        target,
383        skip_cold_load,
384    )?;
385
386    // EIP-161: State trie clearing (invariant-preserving alternative)
387    let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
388        res.had_value && !res.target_exists
389    } else {
390        !res.target_exists
391    };
392
393    gas!(
394        context.interpreter,
395        context
396            .host
397            .gas_params()
398            .selfdestruct_cost(should_charge_topup, res.is_cold)
399    );
400
401    // State gas for new account creation (EIP-8037)
402    if context.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
403        state_gas!(
404            context.interpreter,
405            context.host.gas_params().new_account_state_gas()
406        );
407    }
408
409    if !res.previously_destroyed {
410        context
411            .interpreter
412            .gas
413            .record_refund(context.host.gas_params().selfdestruct_refund());
414    }
415
416    Err(InstructionResult::SelfDestruct)
417}