ethrex_levm/opcode_handlers/
frame_tx.rs1use crate::{
15 errors::{ExceptionalHalt, InternalError, OpcodeResult, VMError},
16 gas_cost,
17 memory::calculate_memory_size,
18 opcode_handlers::OpcodeHandler,
19 utils::size_offset_to_usize,
20 vm::VM,
21};
22use ethrex_common::{Address, U256, types::FrameMode, types::Log};
23
24fn index_to_usize(val: u64) -> Result<usize, VMError> {
26 usize::try_from(val).map_err(|_| ExceptionalHalt::InvalidOpcode.into())
27}
28
29pub fn u256_to_offset(value: U256) -> Option<usize> {
35 if value.0[1] != 0 || value.0[2] != 0 || value.0[3] != 0 {
36 return None;
37 }
38 usize::try_from(value.0[0]).ok()
39}
40
41pub(crate) fn compute_tx_max_cost(ctx: &crate::vm::FrameTxContext) -> Result<U256, VMError> {
53 let gas_cost = U256::from(ctx.tx.max_fee_per_gas)
54 .checked_mul(U256::from(ctx.total_gas_limit))
55 .ok_or(ExceptionalHalt::InvalidOpcode)?;
56 let blob_cost = U256::from(ctx.tx.blob_versioned_hashes.len())
57 .checked_mul(U256::from(131072u64))
58 .ok_or(ExceptionalHalt::InvalidOpcode)?
59 .checked_mul(ctx.tx.max_fee_per_blob_gas)
60 .ok_or(ExceptionalHalt::InvalidOpcode)?;
61 gas_cost
62 .checked_add(blob_cost)
63 .ok_or(ExceptionalHalt::InvalidOpcode.into())
64}
65
66pub fn apply_approve(
69 vm: &mut VM<'_>,
70 scope: u64,
71 frame_target: ethrex_common::Address,
72) -> Result<(), VMError> {
73 match scope {
74 0x1 => {
75 let ctx = vm
80 .frame_tx_context
81 .as_ref()
82 .ok_or(ExceptionalHalt::InvalidOpcode)?;
83 if ctx.payer_address.is_some() {
84 return Err(ExceptionalHalt::InvalidOpcode.into());
85 }
86 let tx_cost = compute_tx_max_cost(ctx)?;
89 let sender = ctx.tx.sender;
90
91 vm.increment_account_nonce(sender)?;
92 match vm.decrease_account_balance(frame_target, tx_cost) {
96 Ok(()) => {}
97 Err(InternalError::Underflow) => return Err(VMError::RevertOpcode),
98 Err(e) => return Err(VMError::Internal(e)),
99 }
100
101 let ctx = vm
102 .frame_tx_context
103 .as_mut()
104 .ok_or(ExceptionalHalt::InvalidOpcode)?;
105 ctx.payer_address = Some(frame_target);
106 }
107 0x2 => {
108 let ctx = vm
110 .frame_tx_context
111 .as_ref()
112 .ok_or(ExceptionalHalt::InvalidOpcode)?;
113 if ctx.sender_approved {
114 return Err(ExceptionalHalt::InvalidOpcode.into());
115 }
116 if frame_target != ctx.tx.sender {
117 return Err(VMError::RevertOpcode);
118 }
119 let ctx = vm
120 .frame_tx_context
121 .as_mut()
122 .ok_or(ExceptionalHalt::InvalidOpcode)?;
123 ctx.sender_approved = true;
124 }
125 0x3 => {
126 let ctx = vm
128 .frame_tx_context
129 .as_ref()
130 .ok_or(ExceptionalHalt::InvalidOpcode)?;
131 if ctx.sender_approved || ctx.payer_address.is_some() {
132 return Err(ExceptionalHalt::InvalidOpcode.into());
133 }
134 if frame_target != ctx.tx.sender {
135 return Err(VMError::RevertOpcode);
136 }
137 let tx_cost = compute_tx_max_cost(ctx)?;
138 let sender = ctx.tx.sender;
139
140 vm.increment_account_nonce(sender)?;
141 match vm.decrease_account_balance(frame_target, tx_cost) {
143 Ok(()) => {}
144 Err(InternalError::Underflow) => return Err(VMError::RevertOpcode),
145 Err(e) => return Err(VMError::Internal(e)),
146 }
147
148 let ctx = vm
149 .frame_tx_context
150 .as_mut()
151 .ok_or(ExceptionalHalt::InvalidOpcode)?;
152 ctx.sender_approved = true;
153 ctx.payer_address = Some(frame_target);
154 }
155 _ => {
156 return Err(ExceptionalHalt::InvalidOpcode.into());
158 }
159 }
160 Ok(())
161}
162
163pub struct OpApproveHandler;
178impl OpcodeHandler for OpApproveHandler {
179 #[inline(always)]
180 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
181 let [offset, length, scope] = *vm.current_call_frame.stack.pop()?;
182 let (length, offset) = size_offset_to_usize(length, offset)?;
183
184 let ctx = vm
186 .frame_tx_context
187 .as_ref()
188 .ok_or(ExceptionalHalt::InvalidOpcode)?;
189
190 let current_frame = ctx
192 .tx
193 .frames
194 .get(ctx.current_frame_index)
195 .ok_or(ExceptionalHalt::InvalidOpcode)?;
196 let frame_target = current_frame.target.unwrap_or(ctx.tx.sender);
197 if vm.current_call_frame.to != frame_target {
198 return Err(VMError::RevertOpcode);
199 }
200
201 let allowed_scope = current_frame.scope_restriction();
205 let scope_val = u64::try_from(scope).unwrap_or(u64::MAX);
206 if scope_val == 0 || scope_val > 3 || (scope_val & u64::from(allowed_scope)) != scope_val {
208 return Err(ExceptionalHalt::InvalidOpcode.into());
209 }
210
211 vm.current_call_frame
213 .increase_consumed_gas(gas_cost::exit_opcode(
214 calculate_memory_size(offset, length)?,
215 vm.current_call_frame.memory.len(),
216 )?)?;
217
218 apply_approve(vm, scope_val, frame_target)?;
219
220 let ctx = vm
221 .frame_tx_context
222 .as_mut()
223 .ok_or(ExceptionalHalt::InvalidOpcode)?;
224 ctx.approve_called_in_current_frame = true;
225
226 if length != 0 {
228 vm.current_call_frame.output =
229 vm.current_call_frame.memory.load_range(offset, length)?;
230 }
231
232 Ok(OpcodeResult::Halt)
233 }
234}
235
236pub struct OpTxParamHandler;
239impl OpcodeHandler for OpTxParamHandler {
240 #[inline(always)]
241 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
242 let [param_id] = *vm.current_call_frame.stack.pop()?;
243
244 vm.current_call_frame
245 .increase_consumed_gas(gas_cost::TXPARAM)?;
246
247 let ctx = vm
248 .frame_tx_context
249 .as_ref()
250 .ok_or(ExceptionalHalt::InvalidOpcode)?;
251
252 let param_id = u64::try_from(param_id).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
253 let result = load_tx_param(ctx, param_id)?;
254 vm.current_call_frame.stack.push(result)?;
255
256 Ok(OpcodeResult::Continue)
257 }
258}
259
260pub struct OpFrameDataLoadHandler;
264impl OpcodeHandler for OpFrameDataLoadHandler {
265 #[inline(always)]
266 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
267 let [offset, frame_index] = *vm.current_call_frame.stack.pop()?;
268
269 vm.current_call_frame
270 .increase_consumed_gas(gas_cost::FRAMEDATALOAD)?;
271
272 let ctx = vm
273 .frame_tx_context
274 .as_ref()
275 .ok_or(ExceptionalHalt::InvalidOpcode)?;
276
277 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
278 let idx = index_to_usize(frame_index)?;
279 let frame = ctx
280 .tx
281 .frames
282 .get(idx)
283 .ok_or(ExceptionalHalt::InvalidOpcode)?;
284
285 let mut word = [0u8; 32];
287 if let Some(byte_offset) = u256_to_offset(offset) {
288 let data = &frame.data;
289 let available = data.len().saturating_sub(byte_offset);
290 let copy_len = available.min(32);
291 if copy_len > 0
292 && let Some(src) = data.get(byte_offset..byte_offset.saturating_add(copy_len))
293 {
294 if let Some(dst) = word.get_mut(..copy_len) {
296 dst.copy_from_slice(src);
297 }
298 }
299 }
300
301 vm.current_call_frame
302 .stack
303 .push(U256::from_big_endian(&word))?;
304
305 Ok(OpcodeResult::Continue)
306 }
307}
308
309pub struct OpFrameDataCopyHandler;
313impl OpcodeHandler for OpFrameDataCopyHandler {
314 #[inline(always)]
315 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
316 let [mem_offset, data_offset, length, frame_index] = *vm.current_call_frame.stack.pop()?;
317 let (length, mem_offset) = size_offset_to_usize(length, mem_offset)?;
318 let data_offset_opt = u256_to_offset(data_offset);
320
321 let new_memory_size = calculate_memory_size(mem_offset, length)?;
322 let current_memory_size = vm.current_call_frame.memory.len();
323 vm.current_call_frame
327 .increase_consumed_gas(gas_cost::framedatacopy(
328 new_memory_size,
329 current_memory_size,
330 length,
331 )?)?;
332
333 let ctx = vm
337 .frame_tx_context
338 .as_ref()
339 .ok_or(ExceptionalHalt::InvalidOpcode)?;
340
341 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
342 let idx = index_to_usize(frame_index)?;
343 let frame = ctx
344 .tx
345 .frames
346 .get(idx)
347 .ok_or(ExceptionalHalt::InvalidOpcode)?;
348
349 if length == 0 {
350 return Ok(OpcodeResult::Continue);
351 }
352
353 let data = &frame.data;
354 let mut buf = vec![0u8; length];
355 if let Some(data_offset) = data_offset_opt {
356 let available = data.len().saturating_sub(data_offset);
357 let copy_len = length.min(available);
358 if let (Some(dst), Some(src)) = (
359 buf.get_mut(..copy_len),
360 data.get(data_offset..data_offset.saturating_add(copy_len)),
361 ) {
362 dst.copy_from_slice(src);
363 }
364 }
365
366 vm.current_call_frame.memory.store_data(mem_offset, &buf)?;
367
368 Ok(OpcodeResult::Continue)
369 }
370}
371
372pub struct OpFrameParamHandler;
375impl OpcodeHandler for OpFrameParamHandler {
376 #[inline(always)]
377 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
378 let [frame_index, param_id] = *vm.current_call_frame.stack.pop()?;
379
380 vm.current_call_frame
381 .increase_consumed_gas(gas_cost::FRAMEPARAM)?;
382
383 let ctx = vm
384 .frame_tx_context
385 .as_ref()
386 .ok_or(ExceptionalHalt::InvalidOpcode)?;
387
388 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
389 let idx = index_to_usize(frame_index)?;
390 let frame = ctx
391 .tx
392 .frames
393 .get(idx)
394 .ok_or(ExceptionalHalt::InvalidOpcode)?;
395
396 let param_id = u64::try_from(param_id).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
397 let result: U256 = match param_id {
398 0x00 => {
399 address_to_u256(frame.target.unwrap_or(ctx.tx.sender))
401 }
402 0x01 => {
403 U256::from(frame.gas_limit)
405 }
406 0x02 => {
407 U256::from(frame.mode)
409 }
410 0x03 => {
411 U256::from(frame.flags)
413 }
414 0x04 => {
415 U256::from(frame.data.len())
417 }
418 0x05 => {
419 if idx >= ctx.current_frame_index {
423 return Err(ExceptionalHalt::InvalidOpcode.into());
424 }
425 let (status, _, _) = ctx
426 .frame_results
427 .get(idx)
428 .ok_or(ExceptionalHalt::InvalidOpcode)?;
429 U256::from(*status)
430 }
431 0x06 => {
432 U256::from(frame.scope_restriction())
434 }
435 0x07 => {
436 U256::from(u8::from(frame.is_atomic_batch()))
438 }
439 0x08 => {
440 frame.value
442 }
443 _ => return Err(ExceptionalHalt::InvalidOpcode.into()),
444 };
445
446 vm.current_call_frame.stack.push(result)?;
447
448 Ok(OpcodeResult::Continue)
449 }
450}
451
452pub struct OpSigParamHandler;
456impl OpcodeHandler for OpSigParamHandler {
457 #[inline(always)]
458 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
459 let [signature_index, param] = *vm.current_call_frame.stack.pop()?;
460
461 vm.current_call_frame
462 .increase_consumed_gas(gas_cost::SIGPARAM)?;
463
464 let ctx = vm
465 .frame_tx_context
466 .as_ref()
467 .ok_or(ExceptionalHalt::InvalidOpcode)?;
468
469 let signature_index =
470 u64::try_from(signature_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
471 let idx = index_to_usize(signature_index)?;
472 let sig = ctx
473 .tx
474 .signatures
475 .get(idx)
476 .ok_or(ExceptionalHalt::InvalidOpcode)?;
477
478 let param = u64::try_from(param).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
479 let result = match param {
480 0x00 => address_to_u256(sig.signer), 0x01 => U256::from(sig.scheme),
482 0x02 => {
483 if sig.msg.is_empty() {
485 U256::zero()
486 } else {
487 U256::from_big_endian(&sig.msg)
488 }
489 }
490 0x03 => U256::from(sig.signature.len()),
491 _ => return Err(ExceptionalHalt::InvalidOpcode.into()),
492 };
493 vm.current_call_frame.stack.push(result)?;
494 Ok(OpcodeResult::Continue)
495 }
496}
497
498pub fn load_tx_param(ctx: &crate::vm::FrameTxContext, param_id: u64) -> Result<U256, VMError> {
501 match param_id {
502 0x00 => Ok(U256::from(0x06u8)), 0x01 => Ok(U256::from(ctx.tx.nonce)),
504 0x02 => Ok(address_to_u256(ctx.tx.sender)),
505 0x03 => Ok(U256::from(ctx.tx.max_priority_fee_per_gas)),
506 0x04 => Ok(U256::from(ctx.tx.max_fee_per_gas)),
507 0x05 => Ok(ctx.tx.max_fee_per_blob_gas),
508 0x06 => compute_tx_max_cost(ctx),
509 0x07 => Ok(U256::from(ctx.tx.blob_versioned_hashes.len())),
510 0x08 => {
511 let mut bytes = [0u8; 32];
512 bytes.copy_from_slice(ctx.sig_hash.as_bytes());
513 Ok(U256::from_big_endian(&bytes))
514 }
515 0x09 => Ok(U256::from(ctx.tx.frames.len())),
516 0x0A => Ok(U256::from(ctx.current_frame_index)),
517 0x0B => Ok(U256::from(ctx.tx.signatures.len())),
518 _ => Err(ExceptionalHalt::InvalidOpcode.into()),
519 }
520}
521
522pub fn address_to_u256(addr: ethrex_common::Address) -> U256 {
523 let mut bytes = [0u8; 32];
524 bytes[12..].copy_from_slice(addr.as_bytes());
525 U256::from_big_endian(&bytes)
526}
527
528pub fn execute_default_code(
539 vm: &mut VM<'_>,
540 frame: ðrex_common::types::Frame,
541 target: Address,
542) -> Result<(bool, u64, Vec<Log>), VMError> {
543 match frame.execution_mode() {
544 FrameMode::Verify => execute_default_verify(vm, frame, target),
545 FrameMode::Sender | FrameMode::Default => Ok((true, 0, Vec::new())),
552 }
553}
554
555fn execute_default_verify(
556 vm: &mut VM<'_>,
557 frame: ðrex_common::types::Frame,
558 target: Address,
559) -> Result<(bool, u64, Vec<Log>), VMError> {
560 let ctx = vm
561 .frame_tx_context
562 .as_ref()
563 .ok_or(ExceptionalHalt::InvalidOpcode)?;
564
565 let allowed_scope = u64::from(frame.scope_restriction());
567 if allowed_scope == 0 {
568 return Ok((false, 0, Vec::new()));
569 }
570
571 if (allowed_scope & 0x02) != 0 && target != ctx.tx.sender {
573 return Ok((false, 0, Vec::new()));
574 }
575
576 let has_sender_sig = ctx.tx.signatures.iter().any(|s| {
582 s.scheme == ethrex_common::types::FRAME_SIG_SCHEME_SECP256K1
583 && s.msg.is_empty()
584 && s.signer == target
585 });
586 if !has_sender_sig {
587 return Ok((false, 0, Vec::new()));
588 }
589
590 apply_approve(vm, allowed_scope, target)?;
591
592 let ctx = vm
593 .frame_tx_context
594 .as_mut()
595 .ok_or(ExceptionalHalt::InvalidOpcode)?;
596 ctx.approve_called_in_current_frame = true;
597
598 Ok((true, 0, Vec::new()))
599}
600
601#[cfg(test)]
602mod max_cost_tests {
603 use super::{compute_tx_max_cost, load_tx_param};
604 use crate::vm::FrameTxContext;
605 use ethrex_common::{H256, U256, types::FrameTransaction};
606
607 fn ctx(max_fee: u64, blobs: usize, max_blob_fee: u64, total_gas_limit: u64) -> FrameTxContext {
608 let tx = FrameTransaction {
609 max_fee_per_gas: max_fee,
610 max_fee_per_blob_gas: U256::from(max_blob_fee),
611 blob_versioned_hashes: vec![H256::zero(); blobs],
612 ..Default::default()
613 };
614 FrameTxContext {
615 sender_approved: false,
616 payer_address: None,
617 frame_results: Vec::new(),
618 current_frame_index: 0,
619 sig_hash: H256::zero(),
620 tx,
621 approve_called_in_current_frame: false,
622 total_gas_limit,
623 }
624 }
625
626 #[test]
627 fn max_cost_is_max_fee_times_limit_plus_max_blob_cost() {
628 let c = ctx(10, 2, 5, 100_000);
630 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(2_310_720u64));
631 let c = ctx(7, 0, 999, 21_000);
633 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(147_000u64));
634 }
635
636 #[test]
637 fn txparam_0x06_reports_the_same_maximum_cost_approve_debits() {
638 let c = ctx(10, 2, 5, 100_000);
641 assert_eq!(
642 load_tx_param(&c, 0x06).unwrap(),
643 compute_tx_max_cost(&c).unwrap()
644 );
645 }
646}