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.max_gas))
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.blob_base_fee)
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 if !ctx.sender_approved {
91 return Err(VMError::RevertOpcode);
92 }
93 let tx_cost = compute_tx_max_cost(ctx)?;
94 let sender = ctx.tx.sender;
95
96 vm.increment_account_nonce(sender)?;
97 match vm.decrease_account_balance(frame_target, tx_cost) {
101 Ok(()) => {}
102 Err(InternalError::Underflow) => return Err(VMError::RevertOpcode),
103 Err(e) => return Err(VMError::Internal(e)),
104 }
105
106 let ctx = vm
107 .frame_tx_context
108 .as_mut()
109 .ok_or(ExceptionalHalt::InvalidOpcode)?;
110 ctx.payer_address = Some(frame_target);
111 }
112 0x2 => {
113 let ctx = vm
115 .frame_tx_context
116 .as_ref()
117 .ok_or(ExceptionalHalt::InvalidOpcode)?;
118 if ctx.sender_approved {
119 return Err(ExceptionalHalt::InvalidOpcode.into());
120 }
121 if frame_target != ctx.tx.sender {
122 return Err(VMError::RevertOpcode);
123 }
124 let ctx = vm
125 .frame_tx_context
126 .as_mut()
127 .ok_or(ExceptionalHalt::InvalidOpcode)?;
128 ctx.sender_approved = true;
129 }
130 0x3 => {
131 let ctx = vm
133 .frame_tx_context
134 .as_ref()
135 .ok_or(ExceptionalHalt::InvalidOpcode)?;
136 if ctx.sender_approved || ctx.payer_address.is_some() {
137 return Err(ExceptionalHalt::InvalidOpcode.into());
138 }
139 if frame_target != ctx.tx.sender {
140 return Err(VMError::RevertOpcode);
141 }
142 let tx_cost = compute_tx_max_cost(ctx)?;
143 let sender = ctx.tx.sender;
144
145 vm.increment_account_nonce(sender)?;
146 match vm.decrease_account_balance(frame_target, tx_cost) {
148 Ok(()) => {}
149 Err(InternalError::Underflow) => return Err(VMError::RevertOpcode),
150 Err(e) => return Err(VMError::Internal(e)),
151 }
152
153 let ctx = vm
154 .frame_tx_context
155 .as_mut()
156 .ok_or(ExceptionalHalt::InvalidOpcode)?;
157 ctx.sender_approved = true;
158 ctx.payer_address = Some(frame_target);
159 }
160 _ => {
161 return Err(ExceptionalHalt::InvalidOpcode.into());
163 }
164 }
165 Ok(())
166}
167
168pub struct OpApproveHandler;
183impl OpcodeHandler for OpApproveHandler {
184 #[inline(always)]
185 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
186 let [offset, length, scope] = *vm.current_call_frame.stack.pop()?;
187 let (length, offset) = size_offset_to_usize(length, offset)?;
188
189 let ctx = vm
191 .frame_tx_context
192 .as_ref()
193 .ok_or(ExceptionalHalt::InvalidOpcode)?;
194
195 let current_frame = ctx
197 .tx
198 .frames
199 .get(ctx.current_frame_index)
200 .ok_or(ExceptionalHalt::InvalidOpcode)?;
201 let frame_target = current_frame.target.unwrap_or(ctx.tx.sender);
202 if vm.current_call_frame.to != frame_target {
203 return Err(VMError::RevertOpcode);
204 }
205
206 let allowed_scope = current_frame.scope_restriction();
210 let scope_val = u64::try_from(scope).unwrap_or(u64::MAX);
211 if scope_val == 0 || scope_val > 3 || (scope_val & u64::from(allowed_scope)) != scope_val {
213 return Err(ExceptionalHalt::InvalidOpcode.into());
214 }
215
216 vm.current_call_frame
218 .increase_consumed_gas(gas_cost::exit_opcode(
219 calculate_memory_size(offset, length)?,
220 vm.current_call_frame.memory.len(),
221 )?)?;
222
223 apply_approve(vm, scope_val, frame_target)?;
224
225 let ctx = vm
226 .frame_tx_context
227 .as_mut()
228 .ok_or(ExceptionalHalt::InvalidOpcode)?;
229 ctx.approve_called_in_current_frame = true;
230
231 if length != 0 {
233 vm.current_call_frame.output =
234 vm.current_call_frame.memory.load_range(offset, length)?;
235 }
236
237 Ok(OpcodeResult::Halt)
238 }
239}
240
241pub struct OpTxParamHandler;
244impl OpcodeHandler for OpTxParamHandler {
245 #[inline(always)]
246 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
247 let [param_id] = *vm.current_call_frame.stack.pop()?;
248
249 vm.current_call_frame
250 .increase_consumed_gas(gas_cost::TXPARAM)?;
251
252 let ctx = vm
253 .frame_tx_context
254 .as_ref()
255 .ok_or(ExceptionalHalt::InvalidOpcode)?;
256
257 let param_id = u64::try_from(param_id).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
258 let result = load_tx_param(ctx, param_id)?;
259 vm.current_call_frame.stack.push(result)?;
260
261 Ok(OpcodeResult::Continue)
262 }
263}
264
265pub struct OpFrameDataLoadHandler;
269impl OpcodeHandler for OpFrameDataLoadHandler {
270 #[inline(always)]
271 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
272 let [offset, frame_index] = *vm.current_call_frame.stack.pop()?;
273
274 vm.current_call_frame
275 .increase_consumed_gas(gas_cost::FRAMEDATALOAD)?;
276
277 let ctx = vm
278 .frame_tx_context
279 .as_ref()
280 .ok_or(ExceptionalHalt::InvalidOpcode)?;
281
282 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
283 let idx = index_to_usize(frame_index)?;
284 let frame = ctx
285 .tx
286 .frames
287 .get(idx)
288 .ok_or(ExceptionalHalt::InvalidOpcode)?;
289
290 let mut word = [0u8; 32];
292 if let Some(byte_offset) = u256_to_offset(offset) {
293 let data = &frame.data;
294 let available = data.len().saturating_sub(byte_offset);
295 let copy_len = available.min(32);
296 if copy_len > 0
297 && let Some(src) = data.get(byte_offset..byte_offset.saturating_add(copy_len))
298 {
299 if let Some(dst) = word.get_mut(..copy_len) {
301 dst.copy_from_slice(src);
302 }
303 }
304 }
305
306 vm.current_call_frame
307 .stack
308 .push(U256::from_big_endian(&word))?;
309
310 Ok(OpcodeResult::Continue)
311 }
312}
313
314pub struct OpFrameDataCopyHandler;
318impl OpcodeHandler for OpFrameDataCopyHandler {
319 #[inline(always)]
320 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
321 let [mem_offset, data_offset, length, frame_index] = *vm.current_call_frame.stack.pop()?;
322 let (length, mem_offset) = size_offset_to_usize(length, mem_offset)?;
323 let data_offset_opt = u256_to_offset(data_offset);
325
326 let new_memory_size = calculate_memory_size(mem_offset, length)?;
327 let current_memory_size = vm.current_call_frame.memory.len();
328 vm.current_call_frame
332 .increase_consumed_gas(gas_cost::framedatacopy(
333 new_memory_size,
334 current_memory_size,
335 length,
336 )?)?;
337
338 let ctx = vm
342 .frame_tx_context
343 .as_ref()
344 .ok_or(ExceptionalHalt::InvalidOpcode)?;
345
346 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
347 let idx = index_to_usize(frame_index)?;
348 let frame = ctx
349 .tx
350 .frames
351 .get(idx)
352 .ok_or(ExceptionalHalt::InvalidOpcode)?;
353
354 if length == 0 {
355 return Ok(OpcodeResult::Continue);
356 }
357
358 let data = &frame.data;
359 let mut buf = vec![0u8; length];
360 if let Some(data_offset) = data_offset_opt {
361 let available = data.len().saturating_sub(data_offset);
362 let copy_len = length.min(available);
363 if let (Some(dst), Some(src)) = (
364 buf.get_mut(..copy_len),
365 data.get(data_offset..data_offset.saturating_add(copy_len)),
366 ) {
367 dst.copy_from_slice(src);
368 }
369 }
370
371 vm.current_call_frame.memory.store_data(mem_offset, &buf)?;
372
373 Ok(OpcodeResult::Continue)
374 }
375}
376
377pub struct OpFrameParamHandler;
380impl OpcodeHandler for OpFrameParamHandler {
381 #[inline(always)]
382 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
383 let [frame_index, param_id] = *vm.current_call_frame.stack.pop()?;
384
385 vm.current_call_frame
386 .increase_consumed_gas(gas_cost::FRAMEPARAM)?;
387
388 let ctx = vm
389 .frame_tx_context
390 .as_ref()
391 .ok_or(ExceptionalHalt::InvalidOpcode)?;
392
393 let frame_index = u64::try_from(frame_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
394 let idx = index_to_usize(frame_index)?;
395 let frame = ctx
396 .tx
397 .frames
398 .get(idx)
399 .ok_or(ExceptionalHalt::InvalidOpcode)?;
400
401 let param_id = u64::try_from(param_id).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
402 let result: U256 = match param_id {
403 0x00 => {
404 address_to_u256(frame.target.unwrap_or(ctx.tx.sender))
406 }
407 0x01 => {
408 U256::from(frame.gas_limit)
410 }
411 0x02 => {
412 U256::from(frame.mode)
414 }
415 0x03 => {
416 U256::from(frame.flags)
418 }
419 0x04 => {
420 U256::from(frame.data.len())
422 }
423 0x05 => {
424 if idx >= ctx.current_frame_index {
428 return Err(ExceptionalHalt::InvalidOpcode.into());
429 }
430 let (status, _, _) = ctx
431 .frame_results
432 .get(idx)
433 .ok_or(ExceptionalHalt::InvalidOpcode)?;
434 U256::from(*status)
435 }
436 0x06 => {
437 U256::from(frame.scope_restriction())
439 }
440 0x07 => {
441 U256::from(u8::from(frame.is_atomic_batch()))
443 }
444 0x08 => {
445 frame.value
447 }
448 _ => return Err(ExceptionalHalt::InvalidOpcode.into()),
449 };
450
451 vm.current_call_frame.stack.push(result)?;
452
453 Ok(OpcodeResult::Continue)
454 }
455}
456
457pub struct OpSigParamHandler;
466impl OpcodeHandler for OpSigParamHandler {
467 #[inline(always)]
468 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
469 let [signature_index, param] = *vm.current_call_frame.stack.pop()?;
470 let param = u64::try_from(param).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
471 let signature_index =
472 u64::try_from(signature_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
473 let idx = index_to_usize(signature_index)?;
474
475 if param == 0x04 {
478 let [mem_offset, data_offset, length] = *vm.current_call_frame.stack.pop()?;
479 let (length, mem_offset) = size_offset_to_usize(length, mem_offset)?;
480 let data_offset_opt = u256_to_offset(data_offset);
481
482 let new_memory_size = calculate_memory_size(mem_offset, length)?;
483 let current_memory_size = vm.current_call_frame.memory.len();
484 vm.current_call_frame
487 .increase_consumed_gas(gas_cost::framedatacopy(
488 new_memory_size,
489 current_memory_size,
490 length,
491 )?)?;
492
493 let ctx = vm
494 .frame_tx_context
495 .as_ref()
496 .ok_or(ExceptionalHalt::InvalidOpcode)?;
497 let sig = ctx
498 .tx
499 .signatures
500 .get(idx)
501 .ok_or(ExceptionalHalt::InvalidOpcode)?;
502 if sig.scheme != ethrex_common::types::FRAME_SIG_SCHEME_ARBITRARY {
504 return Err(ExceptionalHalt::InvalidOpcode.into());
505 }
506 if length == 0 {
507 return Ok(OpcodeResult::Continue);
508 }
509 let data = &sig.signature;
510 let mut buf = vec![0u8; length];
511 if let Some(data_offset) = data_offset_opt {
512 let available = data.len().saturating_sub(data_offset);
513 let copy_len = length.min(available);
514 if let (Some(dst), Some(src)) = (
515 buf.get_mut(..copy_len),
516 data.get(data_offset..data_offset.saturating_add(copy_len)),
517 ) {
518 dst.copy_from_slice(src);
519 }
520 }
521 vm.current_call_frame.memory.store_data(mem_offset, &buf)?;
522 return Ok(OpcodeResult::Continue);
523 }
524
525 vm.current_call_frame
527 .increase_consumed_gas(gas_cost::SIGPARAM)?;
528 let ctx = vm
529 .frame_tx_context
530 .as_ref()
531 .ok_or(ExceptionalHalt::InvalidOpcode)?;
532 let sig = ctx
533 .tx
534 .signatures
535 .get(idx)
536 .ok_or(ExceptionalHalt::InvalidOpcode)?;
537 let result = match param {
538 0x00 => {
542 if sig.scheme == ethrex_common::types::FRAME_SIG_SCHEME_ARBITRARY {
543 return Err(ExceptionalHalt::InvalidOpcode.into());
544 }
545 address_to_u256(sig.signer.unwrap_or(ctx.tx.sender))
546 }
547 0x01 => U256::from(sig.scheme),
548 0x02 => {
549 if sig.msg.is_empty() {
551 U256::zero()
552 } else {
553 U256::from_big_endian(&sig.msg)
554 }
555 }
556 0x03 => U256::from(sig.signature.len()),
557 _ => return Err(ExceptionalHalt::InvalidOpcode.into()),
558 };
559 vm.current_call_frame.stack.push(result)?;
560 Ok(OpcodeResult::Continue)
561 }
562}
563
564pub fn load_tx_param(ctx: &crate::vm::FrameTxContext, param_id: u64) -> Result<U256, VMError> {
567 match param_id {
568 0x00 => Ok(U256::from(0x06u8)), 0x01 => Ok(U256::from(ctx.tx.nonce)),
570 0x02 => Ok(address_to_u256(ctx.tx.sender)),
571 0x03 => Ok(U256::from(ctx.tx.max_priority_fee_per_gas)),
572 0x04 => Ok(U256::from(ctx.tx.max_fee_per_gas)),
573 0x05 => Ok(ctx.tx.max_fee_per_blob_gas),
574 0x06 => compute_tx_max_cost(ctx),
575 0x07 => Ok(U256::from(ctx.tx.blob_versioned_hashes.len())),
576 0x08 => {
577 let mut bytes = [0u8; 32];
578 bytes.copy_from_slice(ctx.sig_hash.as_bytes());
579 Ok(U256::from_big_endian(&bytes))
580 }
581 0x09 => Ok(U256::from(ctx.tx.frames.len())),
582 0x0A => Ok(U256::from(ctx.current_frame_index)),
583 0x0B => Ok(U256::from(ctx.tx.signatures.len())),
584 _ => Err(ExceptionalHalt::InvalidOpcode.into()),
585 }
586}
587
588pub fn address_to_u256(addr: ethrex_common::Address) -> U256 {
589 let mut bytes = [0u8; 32];
590 bytes[12..].copy_from_slice(addr.as_bytes());
591 U256::from_big_endian(&bytes)
592}
593
594pub fn execute_default_code(
605 vm: &mut VM<'_>,
606 frame: ðrex_common::types::Frame,
607 target: Address,
608) -> Result<(bool, u64, Vec<Log>), VMError> {
609 match frame.execution_mode() {
610 FrameMode::Verify => execute_default_verify(vm, frame, target),
611 FrameMode::Sender | FrameMode::Default => Ok((true, 0, Vec::new())),
617 }
618}
619
620fn execute_default_verify(
621 vm: &mut VM<'_>,
622 frame: ðrex_common::types::Frame,
623 target: Address,
624) -> Result<(bool, u64, Vec<Log>), VMError> {
625 let ctx = vm
626 .frame_tx_context
627 .as_ref()
628 .ok_or(ExceptionalHalt::InvalidOpcode)?;
629
630 let allowed_scope = u64::from(frame.scope_restriction());
632 if allowed_scope == 0 {
633 return Ok((false, 0, Vec::new()));
634 }
635
636 if (allowed_scope & 0x02) != 0 && target != ctx.tx.sender {
638 return Ok((false, 0, Vec::new()));
639 }
640
641 let sig_index = if (allowed_scope & 0x02) != 0 { 0 } else { 1 };
649 let sender_sig_ok = ctx.tx.signatures.get(sig_index).is_some_and(|s| {
650 s.scheme == ethrex_common::types::FRAME_SIG_SCHEME_SECP256K1
651 && s.msg.is_empty()
652 && s.signer.unwrap_or(ctx.tx.sender) == target
653 });
654 if !sender_sig_ok {
655 return Ok((false, 0, Vec::new()));
656 }
657
658 apply_approve(vm, allowed_scope, target)?;
659
660 let ctx = vm
661 .frame_tx_context
662 .as_mut()
663 .ok_or(ExceptionalHalt::InvalidOpcode)?;
664 ctx.approve_called_in_current_frame = true;
665
666 Ok((true, 0, Vec::new()))
667}
668
669#[cfg(test)]
670mod max_cost_tests {
671 use super::{compute_tx_max_cost, load_tx_param};
672 use crate::vm::FrameTxContext;
673 use ethrex_common::{H256, U256, types::FrameTransaction};
674
675 fn ctx(max_fee: u64, blobs: usize, blob_base_fee: u64, max_gas: u64) -> FrameTxContext {
676 let tx = FrameTransaction {
677 max_fee_per_gas: max_fee,
678 max_fee_per_blob_gas: U256::from(blob_base_fee).saturating_mul(U256::from(1_000u64)),
681 blob_versioned_hashes: vec![H256::zero(); blobs],
682 ..Default::default()
683 };
684 FrameTxContext {
685 sender_approved: false,
686 payer_address: None,
687 frame_results: Vec::new(),
688 current_frame_index: 0,
689 sig_hash: H256::zero(),
690 tx,
691 approve_called_in_current_frame: false,
692 max_gas,
693 blob_base_fee: U256::from(blob_base_fee),
694 }
695 }
696
697 #[test]
698 fn max_cost_is_max_fee_times_limit_plus_base_rate_blob_cost() {
699 let c = ctx(10, 2, 5, 100_000);
701 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(2_310_720u64));
702 let c = ctx(7, 0, 999, 21_000);
704 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(147_000u64));
705 }
706
707 #[test]
708 fn txparam_0x06_reports_the_same_maximum_cost_approve_debits() {
709 let c = ctx(10, 2, 5, 100_000);
712 assert_eq!(
713 load_tx_param(&c, 0x06).unwrap(),
714 compute_tx_max_cost(&c).unwrap()
715 );
716 }
717}