revm_interpreter/instructions/
host.rs

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