revm_interpreter/instructions/
host.rs

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