Skip to main content

ethrex_levm/opcode_handlers/
environment.rs

1//! # Environment operations
2//!
3//! Includes the following opcodes:
4//!   - `ADDRESS`
5//!   - `BALANCE`
6//!   - `ORIGIN`
7//!   - `GASPRICE`
8//!   - `CALLER`
9//!   - `CALLVALUE`
10//!   - `CALLDATALOAD`
11//!   - `CALLDATASIZE`
12//!   - `CALLDATACOPY`
13//!   - `CODESIZE`
14//!   - `CODECOPY`
15//!   - `EXTCODESIZE`
16//!   - `EXTCODECOPY`
17//!   - `EXTCODEHASH`
18//!   - `RETURNDATASIZE`
19//!   - `RETURNDATACOPY`
20
21use crate::{
22    errors::{ExceptionalHalt, OpcodeResult, VMError},
23    gas_cost::{self},
24    memory::calculate_memory_size,
25    opcode_handlers::OpcodeHandler,
26    utils::{size_offset_to_usize, u256_to_usize, word_to_address},
27    vm::VM,
28};
29use ethrex_common::U256;
30use std::mem;
31
32/// Implementation for the `ADDRESS` opcode.
33pub struct OpAddressHandler;
34impl OpcodeHandler for OpAddressHandler {
35    #[inline(always)]
36    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
37        vm.current_call_frame
38            .increase_consumed_gas(gas_cost::ADDRESS)?;
39
40        #[expect(unsafe_code, reason = "safe")]
41        vm.current_call_frame.stack.push(U256(unsafe {
42            let mut bytes: [u8; 32] = [0; 32];
43            bytes[12..].copy_from_slice(&vm.current_call_frame.to.0);
44            bytes.reverse();
45            mem::transmute_copy::<[u8; 32], [u64; 4]>(&bytes)
46        }))?;
47
48        Ok(OpcodeResult::Continue)
49    }
50}
51
52/// Implementation for the `BALANCE` opcode.
53pub struct OpBalanceHandler;
54impl OpcodeHandler for OpBalanceHandler {
55    #[inline(always)]
56    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
57        let address = word_to_address(vm.current_call_frame.stack.pop1()?);
58        vm.current_call_frame
59            .increase_consumed_gas(gas_cost::balance(
60                vm.substate.add_accessed_address(address),
61                vm.env.config.fork,
62            )?)?;
63
64        // State access AFTER gas check passes
65        let account_balance = vm.db.get_account(address)?.info.balance;
66
67        // Record address touch for BAL (after gas check passes)
68        if let Some(recorder) = vm.db.bal_recorder.as_mut() {
69            recorder.record_touched_address(address);
70        }
71
72        vm.current_call_frame.stack.push(account_balance)?;
73
74        Ok(OpcodeResult::Continue)
75    }
76}
77
78/// Implementation for the `ORIGIN` opcode.
79pub struct OpOriginHandler;
80impl OpcodeHandler for OpOriginHandler {
81    #[inline(always)]
82    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
83        vm.current_call_frame
84            .increase_consumed_gas(gas_cost::ORIGIN)?;
85
86        #[expect(unsafe_code, reason = "safe")]
87        vm.current_call_frame.stack.push(U256(unsafe {
88            let mut bytes: [u8; 32] = [0; 32];
89            bytes[12..].copy_from_slice(&vm.env.origin.0);
90            bytes.reverse();
91            mem::transmute_copy::<[u8; 32], [u64; 4]>(&bytes)
92        }))?;
93
94        Ok(OpcodeResult::Continue)
95    }
96}
97
98/// Implementation for the `GASPRICE` opcode.
99pub struct OpGasPriceHandler;
100impl OpcodeHandler for OpGasPriceHandler {
101    #[inline(always)]
102    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
103        vm.current_call_frame
104            .increase_consumed_gas(gas_cost::GASPRICE)?;
105
106        vm.current_call_frame.stack.push(vm.env.gas_price)?;
107
108        Ok(OpcodeResult::Continue)
109    }
110}
111
112/// Implementation for the `CALLER` opcode.
113pub struct OpCallerHandler;
114impl OpcodeHandler for OpCallerHandler {
115    #[inline(always)]
116    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
117        vm.current_call_frame
118            .increase_consumed_gas(gas_cost::CALLER)?;
119
120        #[expect(unsafe_code, reason = "safe")]
121        vm.current_call_frame.stack.push(U256(unsafe {
122            let mut bytes: [u8; 32] = [0; 32];
123            bytes[12..].copy_from_slice(&vm.current_call_frame.msg_sender.0);
124            bytes.reverse();
125            mem::transmute_copy::<[u8; 32], [u64; 4]>(&bytes)
126        }))?;
127
128        Ok(OpcodeResult::Continue)
129    }
130}
131
132/// Implementation for the `CALLVALUE` opcode.
133pub struct OpCallValueHandler;
134impl OpcodeHandler for OpCallValueHandler {
135    #[inline(always)]
136    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
137        vm.current_call_frame
138            .increase_consumed_gas(gas_cost::CALLVALUE)?;
139
140        vm.current_call_frame
141            .stack
142            .push(vm.current_call_frame.msg_value)?;
143
144        Ok(OpcodeResult::Continue)
145    }
146}
147
148/// Implementation for the `CALLDATALOAD` opcode.
149pub struct OpCallDataLoadHandler;
150impl OpcodeHandler for OpCallDataLoadHandler {
151    #[inline(always)]
152    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
153        vm.current_call_frame
154            .increase_consumed_gas(gas_cost::CALLDATALOAD)?;
155
156        let value = match usize::try_from(vm.current_call_frame.stack.pop1()?) {
157            Ok(offset) => vm.current_call_frame.calldata.load_word(offset)?,
158            Err(_) => U256::zero(),
159        };
160        vm.current_call_frame.stack.push(value)?;
161
162        Ok(OpcodeResult::Continue)
163    }
164}
165
166/// Implementation for the `CALLDATASIZE` opcode.
167pub struct OpCallDataSizeHandler;
168impl OpcodeHandler for OpCallDataSizeHandler {
169    #[inline(always)]
170    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
171        vm.current_call_frame
172            .increase_consumed_gas(gas_cost::CALLDATASIZE)?;
173
174        vm.current_call_frame
175            .stack
176            .push(U256::from(vm.current_call_frame.calldata.len()))?;
177
178        Ok(OpcodeResult::Continue)
179    }
180}
181
182/// Implementation for the `CALLDATACOPY` opcode.
183pub struct OpCallDataCopyHandler;
184impl OpcodeHandler for OpCallDataCopyHandler {
185    #[inline(always)]
186    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
187        let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
188        let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
189        let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
190
191        vm.current_call_frame
192            .increase_consumed_gas(gas_cost::calldatacopy(
193                calculate_memory_size(dst_offset, len)?,
194                vm.current_call_frame.memory.len(),
195                len,
196            )?)?;
197
198        let call_frame = &mut vm.current_call_frame;
199        call_frame
200            .memory
201            .store_calldata(dst_offset, &call_frame.calldata, src_offset, len)?;
202
203        Ok(OpcodeResult::Continue)
204    }
205}
206
207/// Implementation for the `CODESIZE` opcode.
208pub struct OpCodeSizeHandler;
209impl OpcodeHandler for OpCodeSizeHandler {
210    #[inline(always)]
211    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
212        vm.current_call_frame
213            .increase_consumed_gas(gas_cost::CODESIZE)?;
214
215        vm.current_call_frame
216            .stack
217            .push(vm.current_call_frame.bytecode.len().into())?;
218
219        Ok(OpcodeResult::Continue)
220    }
221}
222
223/// Implementation for the `CODECOPY` opcode.
224pub struct OpCodeCopyHandler;
225impl OpcodeHandler for OpCodeCopyHandler {
226    #[inline(always)]
227    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
228        let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
229        let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
230        let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
231
232        vm.current_call_frame
233            .increase_consumed_gas(gas_cost::codecopy(
234                calculate_memory_size(dst_offset, len)?,
235                vm.current_call_frame.memory.len(),
236                len,
237            )?)?;
238
239        if len > 0 {
240            let data = vm
241                .current_call_frame
242                .bytecode
243                .dispatch_buf()
244                .get(src_offset..)
245                .unwrap_or_default();
246            let data = data.get(..len).unwrap_or(data);
247
248            vm.current_call_frame.memory.store_data(dst_offset, data)?;
249            if data.len() < len {
250                #[expect(
251                    clippy::arithmetic_side_effects,
252                    reason = "data.len() < len guard ensures no underflow"
253                )]
254                vm.current_call_frame
255                    .memory
256                    .store_zeros(dst_offset + data.len(), len - data.len())?;
257            }
258        }
259
260        Ok(OpcodeResult::Continue)
261    }
262}
263
264/// Implementation for the `EXTCODESIZE` opcode.
265pub struct OpExtCodeSizeHandler;
266impl OpcodeHandler for OpExtCodeSizeHandler {
267    #[inline(always)]
268    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
269        let address = word_to_address(vm.current_call_frame.stack.pop1()?);
270        vm.current_call_frame
271            .increase_consumed_gas(gas_cost::extcodesize(
272                vm.substate.add_accessed_address(address),
273                vm.env.config.fork,
274            )?)?;
275
276        // EIP-8141 mempool validation-trace: EXTCODESIZE target must exist and
277        // not be EIP-7702-delegated (sender exempt).
278        if vm.validation_observer.active {
279            vm.validation_check_extcode_target(address)?;
280        }
281
282        // State access AFTER gas check passes (using optimized code length lookup)
283        let account_code_length = vm.db.get_code_length(address)?.into();
284
285        // Record address touch for BAL (after gas check passes)
286        if let Some(recorder) = vm.db.bal_recorder.as_mut() {
287            recorder.record_touched_address(address);
288        }
289
290        vm.current_call_frame.stack.push(account_code_length)?;
291
292        Ok(OpcodeResult::Continue)
293    }
294}
295
296/// Implementation for the `EXTCODECOPY` opcode.
297pub struct OpExtCodeCopyHandler;
298impl OpcodeHandler for OpExtCodeCopyHandler {
299    #[inline(always)]
300    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
301        let [address, dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
302        let address = word_to_address(address);
303        let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
304        let src_offset = u256_to_usize(src_offset).unwrap_or(usize::MAX);
305
306        vm.current_call_frame
307            .increase_consumed_gas(gas_cost::extcodecopy(
308                len,
309                calculate_memory_size(dst_offset, len)?,
310                vm.current_call_frame.memory.len(),
311                vm.substate.add_accessed_address(address),
312                vm.env.config.fork,
313            )?)?;
314
315        // Record address touch for BAL (after gas check passes)
316        if let Some(recorder) = vm.db.bal_recorder.as_mut() {
317            recorder.record_touched_address(address);
318        }
319
320        // EIP-8141 mempool validation-trace: EXTCODECOPY target must exist and
321        // not be EIP-7702-delegated (sender exempt).
322        if vm.validation_observer.active {
323            vm.validation_check_extcode_target(address)?;
324        }
325
326        // EELS reads the account's code unconditionally (even for size=0), so
327        // fetch the code — not just the account — to keep the read observable
328        // for execution witnesses (EIP-8025) and parallel-BAL access tracking.
329        let code = vm.db.get_account_code(address)?;
330
331        if len > 0 {
332            let data = code.dispatch_buf().get(src_offset..).unwrap_or_default();
333            let data = data.get(..len).unwrap_or(data);
334
335            vm.current_call_frame.memory.store_data(dst_offset, data)?;
336            if data.len() < len {
337                #[expect(
338                    clippy::arithmetic_side_effects,
339                    reason = "data.len() < len guard ensures no underflow"
340                )]
341                vm.current_call_frame
342                    .memory
343                    .store_zeros(dst_offset + data.len(), len - data.len())?;
344            }
345        }
346
347        Ok(OpcodeResult::Continue)
348    }
349}
350
351/// Implementation for the `EXTCODEHASH` opcode.
352pub struct OpExtCodeHashHandler;
353impl OpcodeHandler for OpExtCodeHashHandler {
354    #[inline(always)]
355    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
356        let address = word_to_address(vm.current_call_frame.stack.pop1()?);
357        vm.current_call_frame
358            .increase_consumed_gas(gas_cost::extcodehash(
359                vm.substate.add_accessed_address(address),
360                vm.env.config.fork,
361            )?)?;
362
363        // EIP-8141 mempool validation-trace: EXTCODEHASH target must exist and
364        // not be EIP-7702-delegated (sender exempt).
365        if vm.validation_observer.active {
366            vm.validation_check_extcode_target(address)?;
367        }
368
369        let account = vm.db.get_account(address)?;
370        let account_is_empty = account.is_empty();
371        let account_code_hash = account.info.code_hash.0;
372
373        // Record address touch for BAL (after gas check passes)
374        if let Some(recorder) = vm.db.bal_recorder.as_mut() {
375            recorder.record_touched_address(address);
376        }
377
378        if account_is_empty {
379            vm.current_call_frame.stack.push_zero()?;
380        } else {
381            #[expect(unsafe_code, reason = "safe")]
382            vm.current_call_frame.stack.push(U256(unsafe {
383                let mut bytes = account_code_hash;
384                bytes.reverse();
385                mem::transmute_copy::<[u8; 32], [u64; 4]>(&bytes)
386            }))?;
387        }
388
389        Ok(OpcodeResult::Continue)
390    }
391}
392
393/// Implementation for the `RETURNDATASIZE` opcode.
394pub struct OpReturnDataSizeHandler;
395impl OpcodeHandler for OpReturnDataSizeHandler {
396    #[inline(always)]
397    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
398        vm.current_call_frame
399            .increase_consumed_gas(gas_cost::RETURNDATASIZE)?;
400
401        vm.current_call_frame
402            .stack
403            .push(vm.current_call_frame.sub_return_data.len().into())?;
404
405        Ok(OpcodeResult::Continue)
406    }
407}
408
409/// Implementation for the `RETURNDATACOPY` opcode.
410pub struct OpReturnDataCopyHandler;
411impl OpcodeHandler for OpReturnDataCopyHandler {
412    #[inline(always)]
413    fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
414        let [dst_offset, src_offset, len] = *vm.current_call_frame.stack.pop()?;
415        let (len, dst_offset) = size_offset_to_usize(len, dst_offset)?;
416        let src_offset = u256_to_usize(src_offset)?;
417
418        vm.current_call_frame
419            .increase_consumed_gas(gas_cost::returndatacopy(
420                calculate_memory_size(dst_offset, len)?,
421                vm.current_call_frame.memory.len(),
422                len,
423            )?)?;
424
425        #[expect(
426            clippy::arithmetic_side_effects,
427            reason = "src_offset and len are validated by memory expansion"
428        )]
429        if src_offset + len > vm.current_call_frame.sub_return_data.len() {
430            return Err(ExceptionalHalt::OutOfBounds.into());
431        }
432
433        if len > 0 {
434            let data = vm
435                .current_call_frame
436                .sub_return_data
437                .get(src_offset..)
438                .unwrap_or_default();
439            let data = data.get(..len).unwrap_or(data);
440
441            vm.current_call_frame.memory.store_data(dst_offset, data)?;
442            if data.len() < len {
443                #[expect(
444                    clippy::arithmetic_side_effects,
445                    reason = "data.len() < len guard ensures no underflow"
446                )]
447                vm.current_call_frame
448                    .memory
449                    .store_zeros(dst_offset + data.len(), len - data.len())?;
450            }
451        }
452
453        Ok(OpcodeResult::Continue)
454    }
455}