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, true)
88    } else {
89        let Ok(account) = context
90            .host
91            .load_account_info_skip_cold_load(address, true, 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.interpreter.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!(context.interpreter, memory_offset_usize, len);
133    }
134
135    let code = if spec_id.is_enabled_in(BERLIN) {
136        let account = berlin_load_account!(context, address, true);
137        account.code.as_ref().unwrap().original_bytes()
138    } else {
139        let Some(code) = context.host.load_account_code(address) else {
140            return context.interpreter.halt_fatal();
141        };
142        code.data
143    };
144
145    let code_offset_usize = min(as_usize_saturated!(code_offset), code.len());
146
147    // Note: This can't panic because we resized memory to fit.
148    // len zero is handled in set_data
149    context
150        .interpreter
151        .memory
152        .set_data(memory_offset_usize, code_offset_usize, len, &code);
153}
154
155/// Implements the BLOCKHASH instruction.
156///
157/// Gets the hash of one of the 256 most recent complete blocks.
158pub fn blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(
159    context: InstructionContext<'_, H, WIRE>,
160) {
161    popn_top!([], number, context.interpreter);
162
163    let requested_number = *number;
164    let block_number = context.host.block_number();
165
166    let Some(diff) = block_number.checked_sub(requested_number) else {
167        *number = U256::ZERO;
168        return;
169    };
170
171    let diff = as_u64_saturated!(diff);
172
173    // blockhash should push zero if number is same as current block number.
174    if diff == 0 {
175        *number = U256::ZERO;
176        return;
177    }
178
179    *number = if diff <= BLOCK_HASH_HISTORY {
180        let Some(hash) = context.host.block_hash(as_u64_saturated!(requested_number)) else {
181            return context.interpreter.halt_fatal();
182        };
183        U256::from_be_bytes(hash.0)
184    } else {
185        U256::ZERO
186    }
187}
188
189/// Implements the SLOAD instruction.
190///
191/// Loads a word from storage.
192pub fn sload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
193    popn_top!([], index, context.interpreter);
194    let spec_id = context.interpreter.runtime_flag.spec_id();
195    let target = context.interpreter.input.target_address();
196
197    if spec_id.is_enabled_in(BERLIN) {
198        let additional_cold_cost = context
199            .interpreter
200            .gas_params
201            .cold_storage_additional_cost();
202        let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost;
203        let res = context.host.sload_skip_cold_load(target, *index, skip_cold);
204        match res {
205            Ok(storage) => {
206                if storage.is_cold {
207                    gas!(context.interpreter, additional_cold_cost);
208                }
209
210                *index = storage.data;
211            }
212            Err(LoadError::ColdLoadSkipped) => context.interpreter.halt_oog(),
213            Err(LoadError::DBError) => context.interpreter.halt_fatal(),
214        }
215    } else {
216        let Some(storage) = context.host.sload(target, *index) else {
217            return context.interpreter.halt_fatal();
218        };
219        *index = storage.data;
220    };
221}
222
223/// Implements the SSTORE instruction.
224///
225/// Stores a word to storage.
226pub fn sstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
227    require_non_staticcall!(context.interpreter);
228    popn!([index, value], context.interpreter);
229
230    let target = context.interpreter.input.target_address();
231    let spec_id = context.interpreter.runtime_flag.spec_id();
232
233    // EIP-2200: Structured Definitions for Net Gas Metering
234    // If gasleft is less than or equal to gas stipend, fail the current call frame with ‘out of gas’ exception.
235    if spec_id.is_enabled_in(ISTANBUL)
236        && context.interpreter.gas.remaining() <= context.interpreter.gas_params.call_stipend()
237    {
238        context
239            .interpreter
240            .halt(InstructionResult::ReentrancySentryOOG);
241        return;
242    }
243
244    gas!(
245        context.interpreter,
246        context.interpreter.gas_params.sstore_static_gas()
247    );
248
249    let state_load = if spec_id.is_enabled_in(BERLIN) {
250        let additional_cold_cost = context
251            .interpreter
252            .gas_params
253            .cold_storage_additional_cost();
254        let skip_cold = context.interpreter.gas.remaining() < additional_cold_cost;
255        let res = context
256            .host
257            .sstore_skip_cold_load(target, index, value, skip_cold);
258        match res {
259            Ok(load) => load,
260            Err(LoadError::ColdLoadSkipped) => return context.interpreter.halt_oog(),
261            Err(LoadError::DBError) => return context.interpreter.halt_fatal(),
262        }
263    } else {
264        let Some(load) = context.host.sstore(target, index, value) else {
265            return context.interpreter.halt_fatal();
266        };
267        load
268    };
269
270    let is_istanbul = spec_id.is_enabled_in(ISTANBUL);
271
272    // dynamic gas
273    gas!(
274        context.interpreter,
275        context.interpreter.gas_params.sstore_dynamic_gas(
276            is_istanbul,
277            &state_load.data,
278            state_load.is_cold
279        )
280    );
281
282    // refund
283    context.interpreter.gas.record_refund(
284        context
285            .interpreter
286            .gas_params
287            .sstore_refund(is_istanbul, &state_load.data),
288    );
289}
290
291/// EIP-1153: Transient storage opcodes
292/// Store value to transient storage
293pub fn tstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
294    check!(context.interpreter, CANCUN);
295    require_non_staticcall!(context.interpreter);
296    popn!([index, value], context.interpreter);
297
298    context
299        .host
300        .tstore(context.interpreter.input.target_address(), index, value);
301}
302
303/// EIP-1153: Transient storage opcodes
304/// Load value from transient storage
305pub fn tload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
306    check!(context.interpreter, CANCUN);
307    popn_top!([], index, context.interpreter);
308
309    *index = context
310        .host
311        .tload(context.interpreter.input.target_address(), *index);
312}
313
314/// Implements the LOG0-LOG4 instructions.
315///
316/// Appends log record with N topics.
317pub fn log<const N: usize, H: Host + ?Sized>(
318    context: InstructionContext<'_, H, impl InterpreterTypes>,
319) {
320    require_non_staticcall!(context.interpreter);
321
322    popn!([offset, len], context.interpreter);
323    let len = as_usize_or_fail!(context.interpreter, len);
324    gas!(
325        context.interpreter,
326        context.interpreter.gas_params.log_cost(N as u8, len as u64)
327    );
328    let data = if len == 0 {
329        Bytes::new()
330    } else {
331        let offset = as_usize_or_fail!(context.interpreter, offset);
332        // Resize memory to fit the data
333        resize_memory!(context.interpreter, offset, len);
334        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
335    };
336    let Some(topics) = context.interpreter.stack.popn::<N>() else {
337        context.interpreter.halt_underflow();
338        return;
339    };
340
341    let log = Log {
342        address: context.interpreter.input.target_address(),
343        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
344            .expect("LogData should have <=4 topics"),
345    };
346
347    context.host.log(log);
348}
349
350/// Implements the SELFDESTRUCT instruction.
351///
352/// Halt execution and register account for later deletion.
353pub fn selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
354    context: InstructionContext<'_, H, WIRE>,
355) {
356    require_non_staticcall!(context.interpreter);
357    popn!([target], context.interpreter);
358    let target = target.into_address();
359    let spec = context.interpreter.runtime_flag.spec_id();
360
361    let cold_load_gas = context
362        .interpreter
363        .gas_params
364        .cold_account_additional_cost();
365
366    let skip_cold_load = context.interpreter.gas.remaining() < cold_load_gas;
367    let res = match context.host.selfdestruct(
368        context.interpreter.input.target_address(),
369        target,
370        skip_cold_load,
371    ) {
372        Ok(res) => res,
373        Err(LoadError::ColdLoadSkipped) => return context.interpreter.halt_oog(),
374        Err(LoadError::DBError) => return context.interpreter.halt_fatal(),
375    };
376
377    // EIP-161: State trie clearing (invariant-preserving alternative)
378    let should_charge_topup = if spec.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
379        res.had_value && !res.target_exists
380    } else {
381        !res.target_exists
382    };
383
384    gas!(
385        context.interpreter,
386        context
387            .interpreter
388            .gas_params
389            .selfdestruct_cost(should_charge_topup, res.is_cold)
390    );
391
392    if !res.previously_destroyed {
393        context
394            .interpreter
395            .gas
396            .record_refund(context.interpreter.gas_params.selfdestruct_refund());
397    }
398
399    context.interpreter.halt(InstructionResult::SelfDestruct);
400}