revm_interpreter/instructions/
host.rs

1use crate::{
2    gas::{self, warm_cold_cost, CALL_STIPEND},
3    instructions::utility::{IntoAddress, IntoU256},
4    interpreter_types::{InputsTr, InterpreterTypes, MemoryTr, RuntimeFlag, StackTr},
5    Host, InstructionResult,
6};
7use core::cmp::min;
8use primitives::{hardfork::SpecId::*, Bytes, Log, LogData, B256, BLOCK_HASH_HISTORY, U256};
9
10use crate::InstructionContext;
11
12/// Implements the BALANCE instruction.
13///
14/// Gets the balance of the given account.
15pub fn balance<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
16    popn_top!([], top, context.interpreter);
17    let address = top.into_address();
18    let Some(balance) = context.host.balance(address) else {
19        context
20            .interpreter
21            .halt(InstructionResult::FatalExternalError);
22        return;
23    };
24    let spec_id = context.interpreter.runtime_flag.spec_id();
25    gas!(
26        context.interpreter,
27        if spec_id.is_enabled_in(BERLIN) {
28            warm_cold_cost(balance.is_cold)
29        } else if spec_id.is_enabled_in(ISTANBUL) {
30            // EIP-1884: Repricing for trie-size-dependent opcodes
31            700
32        } else if spec_id.is_enabled_in(TANGERINE) {
33            400
34        } else {
35            20
36        }
37    );
38    *top = balance.data;
39}
40
41/// EIP-1884: Repricing for trie-size-dependent opcodes
42pub fn selfbalance<WIRE: InterpreterTypes, H: Host + ?Sized>(
43    context: InstructionContext<'_, H, WIRE>,
44) {
45    check!(context.interpreter, ISTANBUL);
46    gas!(context.interpreter, gas::LOW);
47
48    let Some(balance) = context
49        .host
50        .balance(context.interpreter.input.target_address())
51    else {
52        context
53            .interpreter
54            .halt(InstructionResult::FatalExternalError);
55        return;
56    };
57    push!(context.interpreter, balance.data);
58}
59
60/// Implements the EXTCODESIZE instruction.
61///
62/// Gets the size of an account's code.
63pub fn extcodesize<WIRE: InterpreterTypes, H: Host + ?Sized>(
64    context: InstructionContext<'_, H, WIRE>,
65) {
66    popn_top!([], top, context.interpreter);
67    let address = top.into_address();
68    let Some(code) = context.host.load_account_code(address) else {
69        context
70            .interpreter
71            .halt(InstructionResult::FatalExternalError);
72        return;
73    };
74    let spec_id = context.interpreter.runtime_flag.spec_id();
75    if spec_id.is_enabled_in(BERLIN) {
76        gas!(context.interpreter, warm_cold_cost(code.is_cold));
77    } else if spec_id.is_enabled_in(TANGERINE) {
78        gas!(context.interpreter, 700);
79    } else {
80        gas!(context.interpreter, 20);
81    }
82
83    *top = U256::from(code.len());
84}
85
86/// EIP-1052: EXTCODEHASH opcode
87pub fn extcodehash<WIRE: InterpreterTypes, H: Host + ?Sized>(
88    context: InstructionContext<'_, H, WIRE>,
89) {
90    check!(context.interpreter, CONSTANTINOPLE);
91    popn_top!([], top, context.interpreter);
92    let address = top.into_address();
93    let Some(code_hash) = context.host.load_account_code_hash(address) else {
94        context
95            .interpreter
96            .halt(InstructionResult::FatalExternalError);
97        return;
98    };
99    let spec_id = context.interpreter.runtime_flag.spec_id();
100    if spec_id.is_enabled_in(BERLIN) {
101        gas!(context.interpreter, warm_cold_cost(code_hash.is_cold));
102    } else if spec_id.is_enabled_in(ISTANBUL) {
103        gas!(context.interpreter, 700);
104    } else {
105        gas!(context.interpreter, 400);
106    }
107    *top = code_hash.into_u256();
108}
109
110/// Implements the EXTCODECOPY instruction.
111///
112/// Copies a portion of an account's code to memory.
113pub fn extcodecopy<WIRE: InterpreterTypes, H: Host + ?Sized>(
114    context: InstructionContext<'_, H, WIRE>,
115) {
116    popn!(
117        [address, memory_offset, code_offset, len_u256],
118        context.interpreter
119    );
120    let address = address.into_address();
121    let Some(code) = context.host.load_account_code(address) else {
122        context
123            .interpreter
124            .halt(InstructionResult::FatalExternalError);
125        return;
126    };
127
128    let len = as_usize_or_fail!(context.interpreter, len_u256);
129    gas_or_fail!(
130        context.interpreter,
131        gas::extcodecopy_cost(
132            context.interpreter.runtime_flag.spec_id(),
133            len,
134            code.is_cold
135        )
136    );
137    if len == 0 {
138        return;
139    }
140    let memory_offset = as_usize_or_fail!(context.interpreter, memory_offset);
141    let code_offset = min(as_usize_saturated!(code_offset), code.len());
142    resize_memory!(context.interpreter, memory_offset, len);
143
144    // Note: This can't panic because we resized memory to fit.
145    context
146        .interpreter
147        .memory
148        .set_data(memory_offset, code_offset, len, &code);
149}
150
151/// Implements the BLOCKHASH instruction.
152///
153/// Gets the hash of one of the 256 most recent complete blocks.
154pub fn blockhash<WIRE: InterpreterTypes, H: Host + ?Sized>(
155    context: InstructionContext<'_, H, WIRE>,
156) {
157    gas!(context.interpreter, gas::BLOCKHASH);
158    popn_top!([], number, context.interpreter);
159
160    let requested_number = *number;
161    let block_number = context.host.block_number();
162
163    let Some(diff) = block_number.checked_sub(requested_number) else {
164        *number = U256::ZERO;
165        return;
166    };
167
168    let diff = as_u64_saturated!(diff);
169
170    // blockhash should push zero if number is same as current block number.
171    if diff == 0 {
172        *number = U256::ZERO;
173        return;
174    }
175
176    *number = if diff <= BLOCK_HASH_HISTORY {
177        let Some(hash) = context.host.block_hash(as_u64_saturated!(requested_number)) else {
178            context
179                .interpreter
180                .halt(InstructionResult::FatalExternalError);
181            return;
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
195    let Some(value) = context
196        .host
197        .sload(context.interpreter.input.target_address(), *index)
198    else {
199        context
200            .interpreter
201            .halt(InstructionResult::FatalExternalError);
202        return;
203    };
204
205    gas!(
206        context.interpreter,
207        gas::sload_cost(context.interpreter.runtime_flag.spec_id(), value.is_cold)
208    );
209    *index = value.data;
210}
211
212/// Implements the SSTORE instruction.
213///
214/// Stores a word to storage.
215pub fn sstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
216    require_non_staticcall!(context.interpreter);
217
218    popn!([index, value], context.interpreter);
219
220    let Some(state_load) =
221        context
222            .host
223            .sstore(context.interpreter.input.target_address(), index, value)
224    else {
225        context
226            .interpreter
227            .halt(InstructionResult::FatalExternalError);
228        return;
229    };
230
231    // EIP-1706 Disable SSTORE with gasleft lower than call stipend
232    if context
233        .interpreter
234        .runtime_flag
235        .spec_id()
236        .is_enabled_in(ISTANBUL)
237        && context.interpreter.gas.remaining() <= CALL_STIPEND
238    {
239        context
240            .interpreter
241            .halt(InstructionResult::ReentrancySentryOOG);
242        return;
243    }
244    gas!(
245        context.interpreter,
246        gas::sstore_cost(
247            context.interpreter.runtime_flag.spec_id(),
248            &state_load.data,
249            state_load.is_cold
250        )
251    );
252
253    context.interpreter.gas.record_refund(gas::sstore_refund(
254        context.interpreter.runtime_flag.spec_id(),
255        &state_load.data,
256    ));
257}
258
259/// EIP-1153: Transient storage opcodes
260/// Store value to transient storage
261pub fn tstore<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
262    check!(context.interpreter, CANCUN);
263    require_non_staticcall!(context.interpreter);
264    gas!(context.interpreter, gas::WARM_STORAGE_READ_COST);
265
266    popn!([index, value], context.interpreter);
267
268    context
269        .host
270        .tstore(context.interpreter.input.target_address(), index, value);
271}
272
273/// EIP-1153: Transient storage opcodes
274/// Load value from transient storage
275pub fn tload<WIRE: InterpreterTypes, H: Host + ?Sized>(context: InstructionContext<'_, H, WIRE>) {
276    check!(context.interpreter, CANCUN);
277    gas!(context.interpreter, gas::WARM_STORAGE_READ_COST);
278
279    popn_top!([], index, context.interpreter);
280
281    *index = context
282        .host
283        .tload(context.interpreter.input.target_address(), *index);
284}
285
286/// Implements the LOG0-LOG4 instructions.
287///
288/// Appends log record with N topics.
289pub fn log<const N: usize, H: Host + ?Sized>(
290    context: InstructionContext<'_, H, impl InterpreterTypes>,
291) {
292    require_non_staticcall!(context.interpreter);
293
294    popn!([offset, len], context.interpreter);
295    let len = as_usize_or_fail!(context.interpreter, len);
296    gas_or_fail!(context.interpreter, gas::log_cost(N as u8, len as u64));
297    let data = if len == 0 {
298        Bytes::new()
299    } else {
300        let offset = as_usize_or_fail!(context.interpreter, offset);
301        resize_memory!(context.interpreter, offset, len);
302        Bytes::copy_from_slice(context.interpreter.memory.slice_len(offset, len).as_ref())
303    };
304    if context.interpreter.stack.len() < N {
305        context.interpreter.halt(InstructionResult::StackUnderflow);
306        return;
307    }
308    let Some(topics) = context.interpreter.stack.popn::<N>() else {
309        context.interpreter.halt(InstructionResult::StackUnderflow);
310        return;
311    };
312
313    let log = Log {
314        address: context.interpreter.input.target_address(),
315        data: LogData::new(topics.into_iter().map(B256::from).collect(), data)
316            .expect("LogData should have <=4 topics"),
317    };
318
319    context.host.log(log);
320}
321
322/// Implements the SELFDESTRUCT instruction.
323///
324/// Halt execution and register account for later deletion.
325pub fn selfdestruct<WIRE: InterpreterTypes, H: Host + ?Sized>(
326    context: InstructionContext<'_, H, WIRE>,
327) {
328    require_non_staticcall!(context.interpreter);
329    popn!([target], context.interpreter);
330    let target = target.into_address();
331
332    let Some(res) = context
333        .host
334        .selfdestruct(context.interpreter.input.target_address(), target)
335    else {
336        context
337            .interpreter
338            .halt(InstructionResult::FatalExternalError);
339        return;
340    };
341
342    // EIP-3529: Reduction in refunds
343    if !context
344        .interpreter
345        .runtime_flag
346        .spec_id()
347        .is_enabled_in(LONDON)
348        && !res.previously_destroyed
349    {
350        context.interpreter.gas.record_refund(gas::SELFDESTRUCT)
351    }
352
353    gas!(
354        context.interpreter,
355        gas::selfdestruct_cost(context.interpreter.runtime_flag.spec_id(), res)
356    );
357
358    context.interpreter.halt(InstructionResult::SelfDestruct);
359}