Skip to main content

revm_interpreter/instructions/
host.rs

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