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        let skip_cold = context.interpreter.gas.remaining()
237            < context.host.gas_params().cold_storage_additional_cost();
238        context
239            .host
240            .sstore_skip_cold_load(target, index, value, skip_cold)?
241    } else {
242        context
243            .host
244            .sstore(target, index, value)
245            .ok_or(InstructionResult::FatalExternalError)?
246    };
247
248    gas_accounting(&mut context, target, &state_load)
249}
250
251/// Default dynamic gas and refund accounting for SSTORE.
252pub fn sstore_default_gas_accounting<IT, H>(
253    context: &mut Ictx<'_, H, IT>,
254    _target: Address,
255    state_load: &StateLoad<SStoreResult>,
256) -> Result
257where
258    IT: ITy,
259    H: Host + ?Sized,
260{
261    let spec_id = context.interpreter.runtime_flag.spec_id();
262    let is_istanbul = spec_id.is_enabled_in(ISTANBUL);
263
264    // dynamic gas
265    gas!(
266        context.interpreter,
267        context.host.gas_params().sstore_dynamic_gas(
268            is_istanbul,
269            &state_load.data,
270            state_load.is_cold
271        )
272    );
273
274    // state gas for new slot creation (EIP-8037)
275    if context.host.is_amsterdam_eip8037_enabled() {
276        state_gas!(
277            context.interpreter,
278            context.host.gas_params().sstore_state_gas(&state_load.data)
279        );
280
281        // EIP-8037 issue #2: 0→x→0 storage restoration refills the reservoir
282        // directly rather than routing the state gas through the capped refund
283        // counter. The regular-gas portion of the restoration still flows
284        // through `sstore_refund` below.
285        let refill = context
286            .host
287            .gas_params()
288            .sstore_state_gas_refill(&state_load.data);
289        if refill > 0 {
290            context.interpreter.gas.refill_reservoir(refill);
291        }
292    }
293
294    // refund
295    context.interpreter.gas.record_refund(
296        context
297            .host
298            .gas_params()
299            .sstore_refund(is_istanbul, &state_load.data),
300    );
301    Ok(())
302}
303
304/// EIP-1153: Transient storage opcodes
305/// Store value to transient storage
306pub fn tstore<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
307    check!(context.interpreter, CANCUN);
308    require_non_staticcall!(context.interpreter);
309    popn!([index, value], context.interpreter);
310
311    context
312        .host
313        .tstore(context.interpreter.input.target_address(), index, value);
314    Ok(())
315}
316
317/// EIP-1153: Transient storage opcodes
318/// Load value from transient storage
319pub fn tload<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
320    check!(context.interpreter, CANCUN);
321    popn_top!([], index, context.interpreter);
322
323    *index = context
324        .host
325        .tload(context.interpreter.input.target_address(), *index);
326    Ok(())
327}
328
329/// Implements the LOG0-LOG4 instructions.
330///
331/// Appends log record with N topics.
332pub fn log<const N: usize, H: Host + ?Sized>(context: Ictx<'_, H, impl ITy>) -> Result {
333    require_non_staticcall!(context.interpreter);
334
335    popn!([offset, len], context.interpreter);
336    let len = as_usize_or_fail!(context.interpreter, len);
337    gas!(
338        context.interpreter,
339        context.host.gas_params().log_cost(N as u8, len as u64)
340    );
341    let data = if len == 0 {
342        Bytes::new()
343    } else {
344        let offset = as_usize_or_fail!(context.interpreter, offset);
345        // Resize memory to fit the data
346        context
347            .interpreter
348            .resize_memory(context.host.gas_params(), offset, len)?;
349        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
350    };
351    let Some(topics) = context.interpreter.stack.popn::<N>() else {
352        return Err(InstructionResult::StackUnderflow);
353    };
354
355    let log = Log {
356        address: context.interpreter.input.target_address(),
357        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
358            .expect("LogData should have <=4 topics"),
359    };
360
361    context.host.log(log);
362    Ok(())
363}
364
365/// Implements the SELFDESTRUCT instruction.
366///
367/// Halt execution and register account for later deletion.
368pub fn selfdestruct<IT: ITy, H: Host + ?Sized>(context: Ictx<'_, H, IT>) -> Result {
369    require_non_staticcall!(context.interpreter);
370    popn!([target], context.interpreter);
371    let target = target.into_address();
372    let spec = context.interpreter.runtime_flag.spec_id();
373
374    let cold_load_gas = context.host.gas_params().selfdestruct_cold_cost();
375
376    let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas;
377    let res = context.host.selfdestruct(
378        context.interpreter.input.target_address(),
379        target,
380        skip_cold_load,
381    )?;
382
383    // EIP-161: State trie clearing (invariant-preserving alternative)
384    let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
385        res.had_value && !res.target_exists
386    } else {
387        !res.target_exists
388    };
389
390    gas!(
391        context.interpreter,
392        context
393            .host
394            .gas_params()
395            .selfdestruct_cost(should_charge_topup, res.is_cold)
396    );
397
398    // State gas for new account creation (EIP-8037)
399    if context.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
400        state_gas!(
401            context.interpreter,
402            context.host.gas_params().new_account_state_gas()
403        );
404    }
405
406    if !res.previously_destroyed {
407        context
408            .interpreter
409            .gas
410            .record_refund(context.host.gas_params().selfdestruct_refund());
411    }
412
413    Err(InstructionResult::SelfDestruct)
414}