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