Skip to main content

revm_interpreter/instructions/
host.rs

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