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 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;
465impl OpcodeHandler for OpSigParamHandler {
466 #[inline(always)]
467 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
468 let [signature_index, param] = *vm.current_call_frame.stack.pop()?;
469 let param = u64::try_from(param).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
470 let signature_index =
471 u64::try_from(signature_index).map_err(|_| ExceptionalHalt::InvalidOpcode)?;
472 let idx = index_to_usize(signature_index)?;
473
474 if param == 0x04 {
477 let [length, data_offset, mem_offset] = *vm.current_call_frame.stack.pop()?;
478 let (length, mem_offset) = size_offset_to_usize(length, mem_offset)?;
479 let data_offset_opt = u256_to_offset(data_offset);
480
481 let new_memory_size = calculate_memory_size(mem_offset, length)?;
482 let current_memory_size = vm.current_call_frame.memory.len();
483 vm.current_call_frame
486 .increase_consumed_gas(gas_cost::framedatacopy(
487 new_memory_size,
488 current_memory_size,
489 length,
490 )?)?;
491
492 let ctx = vm
493 .frame_tx_context
494 .as_ref()
495 .ok_or(ExceptionalHalt::InvalidOpcode)?;
496 let sig = ctx
497 .tx
498 .signatures
499 .get(idx)
500 .ok_or(ExceptionalHalt::InvalidOpcode)?;
501 if sig.scheme != ethrex_common::types::FRAME_SIG_SCHEME_ARBITRARY {
503 return Err(ExceptionalHalt::InvalidOpcode.into());
504 }
505 if length == 0 {
506 return Ok(OpcodeResult::Continue);
507 }
508 let data = &sig.signature;
509 let mut buf = vec![0u8; length];
510 if let Some(data_offset) = data_offset_opt {
511 let available = data.len().saturating_sub(data_offset);
512 let copy_len = length.min(available);
513 if let (Some(dst), Some(src)) = (
514 buf.get_mut(..copy_len),
515 data.get(data_offset..data_offset.saturating_add(copy_len)),
516 ) {
517 dst.copy_from_slice(src);
518 }
519 }
520 vm.current_call_frame.memory.store_data(mem_offset, &buf)?;
521 return Ok(OpcodeResult::Continue);
522 }
523
524 vm.current_call_frame
526 .increase_consumed_gas(gas_cost::SIGPARAM)?;
527 let ctx = vm
528 .frame_tx_context
529 .as_ref()
530 .ok_or(ExceptionalHalt::InvalidOpcode)?;
531 let sig = ctx
532 .tx
533 .signatures
534 .get(idx)
535 .ok_or(ExceptionalHalt::InvalidOpcode)?;
536 let result = match param {
537 0x00 => {
541 if sig.scheme == ethrex_common::types::FRAME_SIG_SCHEME_ARBITRARY {
542 return Err(ExceptionalHalt::InvalidOpcode.into());
543 }
544 address_to_u256(sig.signer.unwrap_or(ctx.tx.sender))
545 }
546 0x01 => U256::from(sig.scheme),
547 0x02 => {
548 if sig.msg.is_empty() {
550 U256::zero()
551 } else {
552 U256::from_big_endian(&sig.msg)
553 }
554 }
555 0x03 => U256::from(sig.signature.len()),
556 _ => return Err(ExceptionalHalt::InvalidOpcode.into()),
557 };
558 vm.current_call_frame.stack.push(result)?;
559 Ok(OpcodeResult::Continue)
560 }
561}
562
563pub fn load_tx_param(ctx: &crate::vm::FrameTxContext, param_id: u64) -> Result<U256, VMError> {
566 match param_id {
567 0x00 => Ok(U256::from(0x06u8)), 0x01 => Ok(U256::from(ctx.tx.nonce)),
569 0x02 => Ok(address_to_u256(ctx.tx.sender)),
570 0x03 => Ok(U256::from(ctx.tx.max_priority_fee_per_gas)),
571 0x04 => Ok(U256::from(ctx.tx.max_fee_per_gas)),
572 0x05 => Ok(ctx.tx.max_fee_per_blob_gas),
573 0x06 => compute_tx_max_cost(ctx),
574 0x07 => Ok(U256::from(ctx.tx.blob_versioned_hashes.len())),
575 0x08 => {
576 let mut bytes = [0u8; 32];
577 bytes.copy_from_slice(ctx.sig_hash.as_bytes());
578 Ok(U256::from_big_endian(&bytes))
579 }
580 0x09 => Ok(U256::from(ctx.tx.frames.len())),
581 0x0A => Ok(U256::from(ctx.current_frame_index)),
582 0x0B => Ok(U256::from(ctx.tx.signatures.len())),
583 _ => Err(ExceptionalHalt::InvalidOpcode.into()),
584 }
585}
586
587pub fn address_to_u256(addr: ethrex_common::Address) -> U256 {
588 let mut bytes = [0u8; 32];
589 bytes[12..].copy_from_slice(addr.as_bytes());
590 U256::from_big_endian(&bytes)
591}
592
593pub fn execute_default_code(
604 vm: &mut VM<'_>,
605 frame: ðrex_common::types::Frame,
606 target: Address,
607) -> Result<(bool, u64, Vec<Log>), VMError> {
608 match frame.execution_mode() {
609 FrameMode::Verify => execute_default_verify(vm, frame, target),
610 FrameMode::Sender | FrameMode::Default => Ok((true, 0, Vec::new())),
616 }
617}
618
619fn execute_default_verify(
620 vm: &mut VM<'_>,
621 frame: ðrex_common::types::Frame,
622 target: Address,
623) -> Result<(bool, u64, Vec<Log>), VMError> {
624 let ctx = vm
625 .frame_tx_context
626 .as_ref()
627 .ok_or(ExceptionalHalt::InvalidOpcode)?;
628
629 let allowed_scope = u64::from(frame.scope_restriction());
631 if allowed_scope == 0 {
632 return Ok((false, 0, Vec::new()));
633 }
634
635 if (allowed_scope & 0x02) != 0 && target != ctx.tx.sender {
637 return Ok((false, 0, Vec::new()));
638 }
639
640 let sig_index = if (allowed_scope & 0x02) != 0 { 0 } else { 1 };
648 let sender_sig_ok = ctx.tx.signatures.get(sig_index).is_some_and(|s| {
649 s.scheme == ethrex_common::types::FRAME_SIG_SCHEME_SECP256K1
650 && s.msg.is_empty()
651 && s.signer.unwrap_or(ctx.tx.sender) == target
652 });
653 if !sender_sig_ok {
654 return Ok((false, 0, Vec::new()));
655 }
656
657 apply_approve(vm, allowed_scope, target)?;
658
659 let ctx = vm
660 .frame_tx_context
661 .as_mut()
662 .ok_or(ExceptionalHalt::InvalidOpcode)?;
663 ctx.approve_called_in_current_frame = true;
664
665 Ok((true, 0, Vec::new()))
666}
667
668#[cfg(test)]
669mod max_cost_tests {
670 use super::{compute_tx_max_cost, load_tx_param};
671 use crate::vm::FrameTxContext;
672 use ethrex_common::{H256, U256, types::FrameTransaction};
673
674 fn ctx(max_fee: u64, blobs: usize, max_blob_fee: u64, total_gas_limit: u64) -> FrameTxContext {
675 let tx = FrameTransaction {
676 max_fee_per_gas: max_fee,
677 max_fee_per_blob_gas: U256::from(max_blob_fee),
678 blob_versioned_hashes: vec![H256::zero(); blobs],
679 ..Default::default()
680 };
681 FrameTxContext {
682 sender_approved: false,
683 payer_address: None,
684 frame_results: Vec::new(),
685 current_frame_index: 0,
686 sig_hash: H256::zero(),
687 tx,
688 approve_called_in_current_frame: false,
689 total_gas_limit,
690 }
691 }
692
693 #[test]
694 fn max_cost_is_max_fee_times_limit_plus_max_blob_cost() {
695 let c = ctx(10, 2, 5, 100_000);
697 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(2_310_720u64));
698 let c = ctx(7, 0, 999, 21_000);
700 assert_eq!(compute_tx_max_cost(&c).unwrap(), U256::from(147_000u64));
701 }
702
703 #[test]
704 fn txparam_0x06_reports_the_same_maximum_cost_approve_debits() {
705 let c = ctx(10, 2, 5, 100_000);
708 assert_eq!(
709 load_tx_param(&c, 0x06).unwrap(),
710 compute_tx_max_cost(&c).unwrap()
711 );
712 }
713}