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