1use crate::{
15 call_frame::CallFrame,
16 constants::{AMSTERDAM_INIT_CODE_MAX_SIZE, FAIL, INIT_CODE_MAX_SIZE, SUCCESS},
17 errors::{ContextResult, ExceptionalHalt, InternalError, OpcodeResult, TxResult, VMError},
18 gas_cost,
19 memory::{self, calculate_memory_size},
20 opcode_handlers::OpcodeHandler,
21 utils::{address_to_word, create_eth_transfer_log, word_to_address, *},
22 vm::VM,
23};
24use bytes::Bytes;
25use ethrex_common::{Address, H256, U256, evm::calculate_create_address, types::Fork};
26use ethrex_common::{tracing::CallType, types::Code};
27
28pub struct OpCallHandler;
29impl OpcodeHandler for OpCallHandler {
30 #[inline(always)]
31 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
32 let [
33 gas,
34 callee,
35 value,
36 args_offset,
37 args_len,
38 return_offset,
39 return_len,
40 ] = *vm.current_call_frame.stack.pop()?;
41 let callee = word_to_address(callee);
42 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
43 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
44
45 if vm.current_call_frame.is_static && !value.is_zero() {
47 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
48 }
49
50 let value_cost = if !value.is_zero() {
51 gas_cost::call_positive_value_cost(vm.env.config.fork)
52 } else {
53 0
54 };
55 let (new_memory_size, address_was_cold, static_cost) = vm.check_call_static_gas(
56 args_offset,
57 args_len,
58 return_offset,
59 return_len,
60 callee,
61 value_cost,
62 )?;
63
64 vm.substate.add_accessed_address(callee);
65 let address_is_empty = if value.is_zero() {
69 false
70 } else {
71 vm.db.get_account(callee)?.is_empty()
72 };
73 let (callee_code, delegation) =
77 eip7702_peek_delegation(vm.db, &vm.substate, callee, vm.env.config.fork)?;
78 let is_delegation_7702 = delegation.is_some();
79 let (eip7702_gas_consumed, code_address) = match delegation {
80 Some((auth_address, access_cost)) => (access_cost, auth_address),
81 None => (0, callee),
82 };
83
84 if vm.validation_observer.active {
87 vm.validation_check_call_target(callee, is_delegation_7702)?;
88 }
89
90 let create_cost = if address_is_empty {
91 gas_cost::CALL_TO_EMPTY_ACCOUNT
92 } else {
93 0
94 };
95
96 vm.record_bal_call_touch(
99 callee,
100 code_address,
101 is_delegation_7702,
102 eip7702_gas_consumed,
103 new_memory_size,
104 vm.current_call_frame.memory.len(),
105 address_was_cold,
106 value_cost,
107 create_cost,
108 );
109
110 let bytecode = if let Some((auth_address, access_cost)) = delegation {
113 vm.current_call_frame.check_gas(
114 static_cost
115 .checked_add(access_cost)
116 .ok_or(ExceptionalHalt::OutOfGas)?,
117 )?;
118 vm.substate.add_accessed_address(auth_address);
119 vm.db.get_account_code(auth_address)?.clone()
120 } else {
121 callee_code
122 };
123
124 let fork = vm.env.config.fork;
125
126 #[expect(clippy::as_conversions, reason = "safe")]
128 let gas_left = (vm.current_call_frame.gas_remaining as u64)
129 .checked_sub(eip7702_gas_consumed)
130 .ok_or(ExceptionalHalt::OutOfGas)?;
131
132 let needs_state_gas = fork >= Fork::Amsterdam && address_is_empty;
137 let gas_left = if needs_state_gas {
138 let state_gas_new_account = vm.state_gas_new_account;
139 let from_reservoir = vm.state_gas_reservoir.min(state_gas_new_account);
140 #[expect(
142 clippy::arithmetic_side_effects,
143 reason = "from_reservoir <= state_gas_new_account"
144 )]
145 let spill = state_gas_new_account - from_reservoir;
146 gas_left
147 .checked_sub(spill)
148 .ok_or(ExceptionalHalt::OutOfGas)?
149 } else {
150 gas_left
151 };
152
153 let (gas_cost, gas_limit) = gas_cost::call(
154 new_memory_size,
155 vm.current_call_frame.memory.len(),
156 address_was_cold,
157 address_is_empty,
158 value,
159 gas,
160 gas_left,
161 fork,
162 )?;
163
164 vm.current_call_frame.increase_consumed_gas(
166 gas_cost
167 .checked_add(eip7702_gas_consumed)
168 .ok_or(ExceptionalHalt::OutOfGas)?,
169 )?;
170
171 if needs_state_gas {
173 vm.increase_state_gas(vm.state_gas_new_account)?;
174 }
175
176 if vm.opcode_tracer.active {
182 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
183 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
184 }
185
186 vm.current_call_frame.memory.resize(new_memory_size)?;
191
192 let data = vm.get_calldata(args_offset, args_len)?;
194 vm.tracer.enter(
195 CallType::CALL,
196 vm.current_call_frame.to,
197 callee,
198 value,
199 gas_limit,
200 &data,
201 );
202
203 vm.generic_call(
205 gas_limit,
206 value,
207 vm.current_call_frame.to,
208 callee,
209 code_address,
210 true,
211 vm.current_call_frame.is_static,
212 data,
213 return_offset,
214 return_len,
215 bytecode,
216 is_delegation_7702,
217 needs_state_gas,
218 )
219 }
220}
221
222pub struct OpCallCodeHandler;
223impl OpcodeHandler for OpCallCodeHandler {
224 #[inline(always)]
225 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
226 let [
227 gas,
228 address,
229 value,
230 args_offset,
231 args_len,
232 return_offset,
233 return_len,
234 ] = *vm.current_call_frame.stack.pop()?;
235 let address = word_to_address(address);
236 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
237 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
238
239 let value_cost = if !value.is_zero() {
240 gas_cost::call_positive_value_cost(vm.env.config.fork)
241 } else {
242 0
243 };
244 let (new_memory_size, address_was_cold, static_cost) = vm.check_call_static_gas(
245 args_offset,
246 args_len,
247 return_offset,
248 return_len,
249 address,
250 value_cost,
251 )?;
252
253 vm.substate.add_accessed_address(address);
254 let (target_code, delegation) =
258 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
259 let is_delegation_7702 = delegation.is_some();
260 let (eip7702_gas_consumed, code_address) = match delegation {
261 Some((auth_address, access_cost)) => (access_cost, auth_address),
262 None => (0, address),
263 };
264
265 if vm.validation_observer.active {
269 vm.validation_check_call_target(address, is_delegation_7702)?;
270 }
271
272 vm.record_bal_call_touch(
274 address,
275 code_address,
276 is_delegation_7702,
277 eip7702_gas_consumed,
278 new_memory_size,
279 vm.current_call_frame.memory.len(),
280 address_was_cold,
281 value_cost,
282 0,
283 );
284
285 let bytecode = if let Some((auth_address, access_cost)) = delegation {
286 vm.current_call_frame.check_gas(
287 static_cost
288 .checked_add(access_cost)
289 .ok_or(ExceptionalHalt::OutOfGas)?,
290 )?;
291 vm.substate.add_accessed_address(auth_address);
292 vm.db.get_account_code(auth_address)?.clone()
293 } else {
294 target_code
295 };
296
297 #[expect(clippy::as_conversions, reason = "safe")]
298 let gas_left = (vm.current_call_frame.gas_remaining as u64)
299 .checked_sub(eip7702_gas_consumed)
300 .ok_or(ExceptionalHalt::OutOfGas)?;
301 let (gas_cost, gas_limit) = gas_cost::callcode(
302 new_memory_size,
303 vm.current_call_frame.memory.len(),
304 address_was_cold,
305 value,
306 gas,
307 gas_left,
308 vm.env.config.fork,
309 )?;
310 vm.current_call_frame.increase_consumed_gas(
311 gas_cost
312 .checked_add(eip7702_gas_consumed)
313 .ok_or(ExceptionalHalt::OutOfGas)?,
314 )?;
315
316 if vm.opcode_tracer.active {
318 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
319 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
320 }
321
322 vm.current_call_frame.memory.resize(new_memory_size)?;
327
328 let data = vm.get_calldata(args_offset, args_len)?;
330 vm.tracer.enter(
331 CallType::CALLCODE,
332 vm.current_call_frame.to,
333 code_address,
334 value,
335 gas_limit,
336 &data,
337 );
338
339 vm.generic_call(
341 gas_limit,
342 value,
343 vm.current_call_frame.to,
344 vm.current_call_frame.to,
345 code_address,
346 true,
347 vm.current_call_frame.is_static,
348 data,
349 return_offset,
350 return_len,
351 bytecode,
352 is_delegation_7702,
353 false,
354 )
355 }
356}
357
358pub struct OpDelegateCallHandler;
359impl OpcodeHandler for OpDelegateCallHandler {
360 #[inline(always)]
361 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
362 let [
363 gas,
364 address,
365 args_offset,
366 args_len,
367 return_offset,
368 return_len,
369 ] = *vm.current_call_frame.stack.pop()?;
370 let address = word_to_address(address);
371 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
372 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
373
374 let (new_memory_size, address_was_cold, static_cost) =
375 vm.check_call_static_gas(args_offset, args_len, return_offset, return_len, address, 0)?;
376
377 vm.substate.add_accessed_address(address);
378 let (target_code, delegation) =
382 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
383 let is_delegation_7702 = delegation.is_some();
384 let (eip7702_gas_consumed, code_address) = match delegation {
385 Some((auth_address, access_cost)) => (access_cost, auth_address),
386 None => (0, address),
387 };
388
389 if vm.validation_observer.active {
392 vm.validation_check_call_target(address, is_delegation_7702)?;
393 }
394
395 vm.record_bal_call_touch(
397 address,
398 code_address,
399 is_delegation_7702,
400 eip7702_gas_consumed,
401 new_memory_size,
402 vm.current_call_frame.memory.len(),
403 address_was_cold,
404 0,
405 0,
406 );
407
408 let bytecode = if let Some((auth_address, access_cost)) = delegation {
409 vm.current_call_frame.check_gas(
410 static_cost
411 .checked_add(access_cost)
412 .ok_or(ExceptionalHalt::OutOfGas)?,
413 )?;
414 vm.substate.add_accessed_address(auth_address);
415 vm.db.get_account_code(auth_address)?.clone()
416 } else {
417 target_code
418 };
419
420 #[expect(clippy::as_conversions, reason = "safe")]
421 let gas_left = (vm.current_call_frame.gas_remaining as u64)
422 .checked_sub(eip7702_gas_consumed)
423 .ok_or(ExceptionalHalt::OutOfGas)?;
424 let (gas_cost, gas_limit) = gas_cost::delegatecall(
425 new_memory_size,
426 vm.current_call_frame.memory.len(),
427 address_was_cold,
428 gas,
429 gas_left,
430 vm.env.config.fork,
431 )?;
432 vm.current_call_frame.increase_consumed_gas(
433 gas_cost
434 .checked_add(eip7702_gas_consumed)
435 .ok_or(ExceptionalHalt::OutOfGas)?,
436 )?;
437
438 if vm.opcode_tracer.active {
440 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
441 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
442 }
443
444 vm.current_call_frame.memory.resize(new_memory_size)?;
449
450 let data = vm.get_calldata(args_offset, args_len)?;
452 vm.tracer.enter(
455 CallType::DELEGATECALL,
456 vm.current_call_frame.to,
457 code_address,
458 vm.current_call_frame.msg_value,
459 gas_limit,
460 &data,
461 );
462
463 vm.generic_call(
465 gas_limit,
466 vm.current_call_frame.msg_value,
467 vm.current_call_frame.msg_sender,
468 vm.current_call_frame.to,
469 code_address,
470 false,
471 vm.current_call_frame.is_static,
472 data,
473 return_offset,
474 return_len,
475 bytecode,
476 is_delegation_7702,
477 false,
478 )
479 }
480}
481
482pub struct OpStaticCallHandler;
483impl OpcodeHandler for OpStaticCallHandler {
484 #[inline(always)]
485 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
486 let [
487 gas,
488 address,
489 args_offset,
490 args_len,
491 return_offset,
492 return_len,
493 ] = *vm.current_call_frame.stack.pop()?;
494 let address = word_to_address(address);
495 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
496 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
497
498 let (new_memory_size, address_was_cold, static_cost) =
499 vm.check_call_static_gas(args_offset, args_len, return_offset, return_len, address, 0)?;
500
501 vm.substate.add_accessed_address(address);
502 let (target_code, delegation) =
506 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
507 let is_delegation_7702 = delegation.is_some();
508 let (eip7702_gas_consumed, code_address) = match delegation {
509 Some((auth_address, access_cost)) => (access_cost, auth_address),
510 None => (0, address),
511 };
512
513 if vm.validation_observer.active {
516 vm.validation_check_call_target(address, is_delegation_7702)?;
517 }
518
519 vm.record_bal_call_touch(
521 address,
522 code_address,
523 is_delegation_7702,
524 eip7702_gas_consumed,
525 new_memory_size,
526 vm.current_call_frame.memory.len(),
527 address_was_cold,
528 0,
529 0,
530 );
531
532 let bytecode = if let Some((auth_address, access_cost)) = delegation {
533 vm.current_call_frame.check_gas(
534 static_cost
535 .checked_add(access_cost)
536 .ok_or(ExceptionalHalt::OutOfGas)?,
537 )?;
538 vm.substate.add_accessed_address(auth_address);
539 vm.db.get_account_code(auth_address)?.clone()
540 } else {
541 target_code
542 };
543
544 #[expect(clippy::as_conversions, reason = "safe")]
545 let gas_left = (vm.current_call_frame.gas_remaining as u64)
546 .checked_sub(eip7702_gas_consumed)
547 .ok_or(ExceptionalHalt::OutOfGas)?;
548 let (gas_cost, gas_limit) = gas_cost::staticcall(
549 new_memory_size,
550 vm.current_call_frame.memory.len(),
551 address_was_cold,
552 gas,
553 gas_left,
554 vm.env.config.fork,
555 )?;
556 vm.current_call_frame.increase_consumed_gas(
557 gas_cost
558 .checked_add(eip7702_gas_consumed)
559 .ok_or(ExceptionalHalt::OutOfGas)?,
560 )?;
561
562 if vm.opcode_tracer.active {
564 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
565 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
566 }
567
568 vm.current_call_frame.memory.resize(new_memory_size)?;
573
574 let data = vm.get_calldata(args_offset, args_len)?;
576 vm.tracer.enter(
577 CallType::STATICCALL,
578 vm.current_call_frame.to,
579 address,
580 U256::zero(),
581 gas_limit,
582 &data,
583 );
584
585 vm.generic_call(
587 gas_limit,
588 U256::zero(),
589 vm.current_call_frame.to,
590 address,
591 address,
592 true,
593 true,
594 data,
595 return_offset,
596 return_len,
597 bytecode,
598 is_delegation_7702,
599 false,
600 )
601 }
602}
603
604pub struct OpReturnHandler;
605impl OpcodeHandler for OpReturnHandler {
606 #[inline(always)]
607 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
608 let [offset, len] = *vm.current_call_frame.stack.pop()?;
609 let (len, offset) = size_offset_to_usize(len, offset)?;
610
611 vm.current_call_frame
612 .increase_consumed_gas(gas_cost::exit_opcode(
613 calculate_memory_size(offset, len)?,
614 vm.current_call_frame.memory.len(),
615 )?)?;
616
617 if len != 0 {
618 vm.current_call_frame.output = vm.current_call_frame.memory.load_range(offset, len)?;
619 }
620
621 Ok(OpcodeResult::Halt)
622 }
623}
624
625pub struct OpCreateHandler;
626impl OpcodeHandler for OpCreateHandler {
627 #[inline(always)]
628 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
629 if vm.env.config.fork >= Fork::Amsterdam && vm.current_call_frame.is_static {
632 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
633 }
634
635 let [value_in_wei, code_offset, code_len] = *vm.current_call_frame.stack.pop()?;
636 let (code_len, code_offset) = size_offset_to_usize(code_len, code_offset)?;
637
638 let create_gas = gas_cost::create(
639 calculate_memory_size(code_offset, code_len)?,
640 vm.current_call_frame.memory.len(),
641 code_len,
642 vm.env.config.fork,
643 )?;
644 vm.current_call_frame.increase_consumed_gas(create_gas)?;
645
646 if vm.opcode_tracer.active {
648 vm.opcode_tracer.last_opcode_gas_cost = Some(create_gas);
649 }
650
651 if vm.validation_observer.active {
654 vm.validation_check_create();
655 }
656
657 vm.generic_create(value_in_wei, code_offset, code_len, None)
658 }
659}
660
661pub struct OpCreate2Handler;
662impl OpcodeHandler for OpCreate2Handler {
663 #[inline(always)]
664 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
665 if vm.env.config.fork >= Fork::Amsterdam && vm.current_call_frame.is_static {
668 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
669 }
670
671 let [value_in_wei, code_offset, code_len, salt] = *vm.current_call_frame.stack.pop()?;
672 let (code_len, code_offset) = size_offset_to_usize(code_len, code_offset)?;
673
674 let create2_gas = gas_cost::create_2(
675 calculate_memory_size(code_offset, code_len)?,
676 vm.current_call_frame.memory.len(),
677 code_len,
678 vm.env.config.fork,
679 )?;
680 vm.current_call_frame.increase_consumed_gas(create2_gas)?;
681
682 if vm.opcode_tracer.active {
684 vm.opcode_tracer.last_opcode_gas_cost = Some(create2_gas);
685 }
686
687 if vm.validation_observer.active {
690 vm.validation_check_create();
691 }
692
693 vm.generic_create(value_in_wei, code_offset, code_len, Some(salt))
694 }
695}
696
697pub struct OpSelfDestructHandler;
698impl OpcodeHandler for OpSelfDestructHandler {
699 #[inline(always)]
700 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
701 if vm.current_call_frame.is_static {
702 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
703 }
704
705 let beneficiary = word_to_address(vm.current_call_frame.stack.pop1()?);
706 let to = vm.current_call_frame.to;
707
708 let target_account_is_cold = vm.substate.add_accessed_address(beneficiary);
709
710 if vm.env.config.fork >= Fork::Amsterdam {
714 let base_cost =
715 gas_cost::selfdestruct_base(target_account_is_cold, vm.env.config.fork)?;
716 #[expect(clippy::as_conversions, reason = "base_cost fits in i64")]
718 if vm.current_call_frame.gas_remaining < (base_cost as i64) {
719 return Err(ExceptionalHalt::OutOfGas.into());
720 }
721 }
722
723 let target_account_is_empty = vm.db.get_account(beneficiary)?.is_empty();
724 let balance = vm.db.get_account(to)?.info.balance;
725
726 if vm.env.config.fork >= Fork::Amsterdam {
732 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
741 recorder.record_touched_address(beneficiary);
742 recorder.record_touched_address(to);
743 if balance > U256::zero() {
744 recorder.set_initial_balance(to, balance);
745 }
746 }
747
748 vm.current_call_frame
750 .increase_consumed_gas(gas_cost::selfdestruct(
751 target_account_is_cold,
752 target_account_is_empty,
753 balance,
754 vm.env.config.fork,
755 )?)?;
756
757 if target_account_is_empty && balance > U256::zero() {
759 vm.increase_state_gas(vm.state_gas_new_account)?;
760 }
761 } else {
762 vm.current_call_frame
763 .increase_consumed_gas(gas_cost::selfdestruct(
764 target_account_is_cold,
765 target_account_is_empty,
766 balance,
767 vm.env.config.fork,
768 )?)?;
769
770 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
775 recorder.record_touched_address(beneficiary);
776 recorder.record_touched_address(to);
777 if balance > U256::zero() {
778 recorder.set_initial_balance(to, balance);
779 }
780 }
781 }
782
783 if vm.env.config.fork >= Fork::Cancun {
785 if !(vm.env.config.fork >= Fork::Amsterdam && to == beneficiary) {
790 vm.transfer(to, beneficiary, balance)?;
791 }
792
793 if vm.substate.is_account_created(&to) {
795 if vm.env.config.fork < Fork::Amsterdam {
799 vm.get_account_mut(to)?.info.balance = U256::zero();
800
801 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
803 recorder.record_balance_change(to, U256::zero());
804 }
805 }
806
807 vm.substate.add_selfdestruct(to);
808 }
809
810 if vm.env.config.fork >= Fork::Amsterdam && !balance.is_zero() && to != beneficiary {
814 let log = create_eth_transfer_log(to, beneficiary, balance);
815 vm.substate.add_log(log);
816 }
818 } else {
819 vm.increase_account_balance(beneficiary, balance)?;
820 vm.get_account_mut(to)?.info.balance = U256::zero();
821
822 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
824 recorder.record_balance_change(to, U256::zero());
825 }
826
827 vm.substate.add_selfdestruct(to);
828 }
829
830 vm.tracer.enter(
831 CallType::SELFDESTRUCT,
832 vm.current_call_frame.to,
833 beneficiary,
834 balance,
835 0,
836 &Default::default(),
837 );
838 vm.tracer.exit_early(0, None)?;
839
840 Ok(OpcodeResult::Halt)
841 }
842}
843
844pub struct OpRevertHandler;
845impl OpcodeHandler for OpRevertHandler {
846 #[inline(always)]
847 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
848 let [offset, len] = *vm.current_call_frame.stack.pop()?;
849 let (len, offset) = size_offset_to_usize(len, offset)?;
850
851 vm.current_call_frame
852 .increase_consumed_gas(gas_cost::exit_opcode(
853 calculate_memory_size(offset, len)?,
854 vm.current_call_frame.memory.len(),
855 )?)?;
856
857 if len != 0 {
858 vm.current_call_frame.output = vm.current_call_frame.memory.load_range(offset, len)?;
859 }
860
861 Err(VMError::RevertOpcode)
862 }
863}
864
865impl<'a> VM<'a> {
866 pub fn generic_create(
868 &mut self,
869 value: U256,
870 code_offset_in_memory: usize,
871 code_size_in_memory: usize,
872 salt: Option<U256>,
873 ) -> Result<OpcodeResult, VMError> {
874 let init_code_max = if self.env.config.fork >= Fork::Amsterdam {
876 AMSTERDAM_INIT_CODE_MAX_SIZE
877 } else {
878 INIT_CODE_MAX_SIZE
879 };
880 if code_size_in_memory > init_code_max && self.env.config.fork >= Fork::Shanghai {
881 return Err(ExceptionalHalt::OutOfGas.into());
882 }
883
884 let current_call_frame = &mut self.current_call_frame;
885
886 if self.env.config.fork < Fork::Amsterdam && current_call_frame.is_static {
888 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
889 }
890
891 current_call_frame.sub_return_data = Bytes::new();
893
894 let code = self
896 .current_call_frame
897 .memory
898 .load_range(code_offset_in_memory, code_size_in_memory)?;
899
900 let deployer = self.current_call_frame.to;
902 let (deployer_balance, deployer_nonce) = {
903 let deployer_account = self.db.get_account(deployer)?;
904 (deployer_account.info.balance, deployer_account.info.nonce)
905 };
906
907 let new_address = match salt {
909 Some(salt) => calculate_create2_address(deployer, &code, salt)?,
910 None => calculate_create_address(deployer, deployer_nonce),
911 };
912
913 let call_type = match salt {
914 Some(_) => CallType::CREATE2,
915 None => CallType::CREATE,
916 };
917
918 let new_depth = self
919 .current_call_frame
920 .depth
921 .checked_add(1)
922 .ok_or(InternalError::Overflow)?;
923
924 let checks = [
933 (deployer_balance < value, "OutOfFund"),
934 (new_depth > 1024, "MaxDepth"),
935 (deployer_nonce == u64::MAX, "MaxNonce"),
936 ];
937 for (condition, reason) in checks {
938 if condition {
939 let preview_gas = gas_cost::max_message_call_gas(&self.current_call_frame)?;
942 self.tracer
943 .enter(call_type, deployer, new_address, value, preview_gas, &code);
944 self.current_call_frame.stack.push(FAIL)?;
945 self.tracer.exit_early(0, Some(reason.to_string()))?;
946 return Ok(OpcodeResult::Continue);
947 }
948 }
949
950 self.substate.add_accessed_address(new_address);
952
953 if let Some(recorder) = self.db.bal_recorder.as_mut() {
958 recorder.record_touched_address(new_address);
959 }
960
961 let target_alive = !self.get_account_mut(new_address)?.is_empty();
968
969 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
979 self.increase_state_gas(self.state_gas_new_account)?;
980 }
981
982 let gas_limit = gas_cost::max_message_call_gas(&self.current_call_frame)?;
984 self.current_call_frame.increase_consumed_gas(gas_limit)?;
985
986 self.tracer
988 .enter(call_type, deployer, new_address, value, gas_limit, &code);
989
990 self.increment_account_nonce(deployer)?;
992
993 let new_account = self.get_account_mut(new_address)?;
995 if new_account.create_would_collide() {
996 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
1000 self.credit_state_gas_refund(self.state_gas_new_account)?;
1001 }
1002 self.current_call_frame.stack.push(FAIL)?;
1003 self.tracer
1004 .exit_early(gas_limit, Some("CreateAccExists".to_string()))?;
1005 return Ok(OpcodeResult::Continue);
1006 }
1007
1008 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
1010
1011 let mut stack = self.stack_pool.pop().unwrap_or_default();
1012 stack.clear();
1013
1014 let next_memory = self.current_call_frame.memory.next_memory();
1015
1016 let mut new_call_frame = CallFrame::new(
1017 deployer,
1018 new_address,
1019 new_address,
1020 Code::from_bytecode_unchecked(code, H256::zero()),
1022 value,
1023 Bytes::new(),
1024 false,
1025 gas_limit,
1026 new_depth,
1027 true,
1028 true,
1029 0,
1030 0,
1031 stack,
1032 next_memory,
1033 );
1034 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1036 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1040 new_call_frame.target_alive = target_alive;
1043
1044 self.add_callframe(new_call_frame);
1045
1046 self.increment_account_nonce(new_address)?; self.transfer(deployer, new_address, value)?;
1049
1050 self.substate.push_backup();
1051 self.substate.add_created_account(new_address); if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() {
1056 let log = create_eth_transfer_log(deployer, new_address, value);
1057 self.substate.add_log(log);
1058 }
1059
1060 Ok(OpcodeResult::Continue)
1061 }
1062
1063 fn check_call_static_gas(
1069 &mut self,
1070 args_offset: usize,
1071 args_len: usize,
1072 return_offset: usize,
1073 return_len: usize,
1074 address: Address,
1075 value_cost: u64,
1076 ) -> Result<(usize, bool, u64), VMError> {
1077 let new_memory_size = calculate_memory_size(args_offset, args_len)?
1078 .max(calculate_memory_size(return_offset, return_len)?);
1079 let address_was_cold = !self.substate.is_address_accessed(&address);
1080 let memory_expansion_cost =
1081 memory::expansion_cost(new_memory_size, self.current_call_frame.memory.len())?;
1082 let access_gas_cost = if address_was_cold {
1083 gas_cost::cold_account_access_cost(self.env.config.fork)
1084 } else {
1085 gas_cost::WARM_ADDRESS_ACCESS_COST
1086 };
1087 let static_cost = memory_expansion_cost
1088 .checked_add(access_gas_cost)
1089 .ok_or(ExceptionalHalt::OutOfGas)?
1090 .checked_add(value_cost)
1091 .ok_or(ExceptionalHalt::OutOfGas)?;
1092 self.current_call_frame.check_gas(static_cost)?;
1093 Ok((new_memory_size, address_was_cold, static_cost))
1094 }
1095
1096 #[expect(
1099 clippy::too_many_arguments,
1100 reason = "matches EIP-7928 EELS reference parameters"
1101 )]
1102 fn record_bal_call_touch(
1103 &mut self,
1104 target: Address,
1105 code_address: Address,
1106 is_delegation_7702: bool,
1107 eip7702_gas_consumed: u64,
1108 new_memory_size: usize,
1109 current_memory_size: usize,
1110 address_was_cold: bool,
1111 value_cost: u64,
1112 create_cost: u64,
1113 ) {
1114 let Some(recorder) = self.db.bal_recorder.as_mut() else {
1115 return;
1116 };
1117 let mem_cost =
1121 memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
1122 let access_cost = if address_was_cold {
1123 gas_cost::cold_account_access_cost(self.env.config.fork)
1124 } else {
1125 gas_cost::WARM_ADDRESS_ACCESS_COST
1126 };
1127 let basic_cost = mem_cost
1128 .saturating_add(access_cost)
1129 .saturating_add(value_cost);
1130 let gas_remaining = self.current_call_frame.gas_remaining;
1131
1132 if gas_remaining >= i64::try_from(basic_cost).unwrap_or(i64::MAX) {
1133 recorder.record_touched_address(target);
1134
1135 if is_delegation_7702 {
1136 let delegation_check = basic_cost
1137 .saturating_add(create_cost)
1138 .saturating_add(eip7702_gas_consumed);
1139 if gas_remaining >= i64::try_from(delegation_check).unwrap_or(i64::MAX) {
1140 recorder.record_touched_address(code_address);
1141 }
1142 }
1143 }
1144 }
1145
1146 #[expect(
1153 clippy::too_many_arguments,
1154 reason = "inlined for performance, many args needed"
1155 )]
1156 #[inline(always)]
1157 pub fn generic_call(
1158 &mut self,
1159 gas_limit: u64,
1160 value: U256,
1161 msg_sender: Address,
1162 to: Address,
1163 code_address: Address,
1164 should_transfer_value: bool,
1165 is_static: bool,
1166 calldata: Bytes,
1167 ret_offset: usize,
1168 ret_size: usize,
1169 bytecode: Code,
1170 is_delegation_7702: bool,
1171 new_account_charged: bool,
1172 ) -> Result<OpcodeResult, VMError> {
1173 self.current_call_frame.sub_return_data.clear();
1175
1176 if should_transfer_value && !value.is_zero() {
1178 let sender_balance = self.db.get_account(msg_sender)?.info.balance;
1179 if sender_balance < value {
1180 self.refund_new_account_state_gas(new_account_charged)?;
1182 self.early_revert_message_call(gas_limit, "OutOfFund".to_string())?;
1183 return Ok(OpcodeResult::Continue);
1184 }
1185 }
1186
1187 let new_depth = self
1189 .current_call_frame
1190 .depth
1191 .checked_add(1)
1192 .ok_or(InternalError::Overflow)?;
1193 if new_depth > 1024 {
1194 self.refund_new_account_state_gas(new_account_charged)?;
1195 self.early_revert_message_call(gas_limit, "MaxDepth".to_string())?;
1196 return Ok(OpcodeResult::Continue);
1197 }
1198
1199 if self.address_is_precompile(&code_address) && !is_delegation_7702 {
1200 if let Some(recorder) = self.db.bal_recorder.as_mut() {
1202 recorder.record_touched_address(code_address);
1203 }
1204
1205 let mut gas_remaining = gas_limit;
1206 let ctx_result = Self::execute_precompile(
1207 self.effective_precompile_address(code_address),
1209 &calldata,
1210 gas_limit,
1211 &mut gas_remaining,
1212 self.env.config.fork,
1213 self.db.store.precompile_cache(),
1214 self.crypto,
1215 self.stateless_validator,
1216 )?;
1217
1218 let call_frame = &mut self.current_call_frame;
1219
1220 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1222 if ctx_result.is_success() {
1223 call_frame.gas_remaining = (call_frame.gas_remaining as u64)
1224 .checked_add(
1225 gas_limit
1226 .checked_sub(ctx_result.gas_used)
1227 .ok_or(InternalError::Underflow)?,
1228 )
1229 .ok_or(InternalError::Overflow)?
1230 as i64;
1231 }
1232
1233 call_frame.memory.store_data(
1235 ret_offset,
1236 if ctx_result.output.len() >= ret_size {
1237 ctx_result
1238 .output
1239 .get(..ret_size)
1240 .ok_or(ExceptionalHalt::OutOfBounds)?
1241 } else {
1242 &ctx_result.output
1243 },
1244 )?;
1245 call_frame.sub_return_data = ctx_result.output.clone();
1246
1247 call_frame.stack.push(match &ctx_result.result {
1249 TxResult::Success => SUCCESS,
1250 TxResult::Revert(_) => FAIL,
1251 })?;
1252
1253 self.refund_new_account_state_gas(new_account_charged && !ctx_result.is_success())?;
1257
1258 if should_transfer_value && ctx_result.is_success() {
1260 self.transfer(msg_sender, to, value)?;
1261
1262 if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() && msg_sender != to {
1265 let log = create_eth_transfer_log(msg_sender, to, value);
1266 self.substate.add_log(log);
1267 }
1268 }
1269
1270 self.tracer.exit_context(&ctx_result, false)?;
1271 } else {
1272 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
1274
1275 let mut stack = self.stack_pool.pop().unwrap_or_default();
1276 stack.clear();
1277
1278 let next_memory = self.current_call_frame.memory.next_memory();
1279
1280 let mut new_call_frame = CallFrame::new(
1281 msg_sender,
1282 to,
1283 code_address,
1284 bytecode,
1285 value,
1286 calldata,
1287 is_static,
1288 gas_limit,
1289 new_depth,
1290 should_transfer_value,
1291 false,
1292 ret_offset,
1293 ret_size,
1294 stack,
1295 next_memory,
1296 );
1297 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1299 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1300 new_call_frame.new_account_state_gas_charged = new_account_charged;
1301
1302 self.add_callframe(new_call_frame);
1303
1304 if should_transfer_value {
1306 self.transfer(msg_sender, to, value)?;
1307 }
1308
1309 self.substate.push_backup();
1310
1311 if should_transfer_value
1315 && self.env.config.fork >= Fork::Amsterdam
1316 && !value.is_zero()
1317 && msg_sender != to
1318 {
1319 let log = create_eth_transfer_log(msg_sender, to, value);
1320 self.substate.add_log(log);
1321 }
1322 }
1323
1324 Ok(OpcodeResult::Continue)
1325 }
1326
1327 pub fn handle_state_backup(
1335 &mut self,
1336 ctx_result: &ContextResult,
1337 consume_backup: bool,
1338 ) -> Result<(), VMError> {
1339 if ctx_result.is_success() {
1340 self.substate.commit_backup();
1341 } else {
1342 self.substate.revert_backup();
1343 if consume_backup {
1344 self.restore_cache_state_consuming()?;
1345 } else {
1346 self.restore_cache_state()?;
1347 }
1348 }
1349
1350 Ok(())
1351 }
1352
1353 pub fn handle_return(&mut self, ctx_result: &ContextResult) -> Result<(), VMError> {
1357 self.handle_state_backup(ctx_result, true)?;
1360 let executed_call_frame = self.pop_call_frame()?;
1361
1362 if executed_call_frame.is_create {
1364 self.handle_return_create(executed_call_frame, ctx_result)?;
1365 } else {
1366 self.handle_return_call(executed_call_frame, ctx_result)?;
1367 }
1368
1369 Ok(())
1370 }
1371
1372 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1373 pub fn handle_return_call(
1374 &mut self,
1375 executed_call_frame: CallFrame,
1376 ctx_result: &ContextResult,
1377 ) -> Result<(), VMError> {
1378 let CallFrame {
1379 gas_limit,
1380 ret_offset,
1381 ret_size,
1382 memory: old_callframe_memory,
1383 frame_state_gas_spilled: child_frame_state_gas_spilled,
1384 call_frame_backup,
1385 stack,
1386 new_account_state_gas_charged,
1387 ..
1388 } = executed_call_frame;
1389
1390 #[cfg(not(target_arch = "riscv64"))]
1391 old_callframe_memory.clean_from_base();
1392
1393 #[cfg(target_arch = "riscv64")]
1394 old_callframe_memory.truncate_to_base();
1395
1396 let parent_call_frame = &mut self.current_call_frame;
1397
1398 let child_unused_gas = gas_limit
1400 .checked_sub(ctx_result.gas_used)
1401 .ok_or(InternalError::Underflow)?;
1402 parent_call_frame.gas_remaining = parent_call_frame
1403 .gas_remaining
1404 .checked_add(child_unused_gas as i64)
1405 .ok_or(InternalError::Overflow)?;
1406
1407 parent_call_frame.memory.store_data(
1409 ret_offset,
1410 if ctx_result.output.len() >= ret_size {
1411 ctx_result
1412 .output
1413 .get(..ret_size)
1414 .ok_or(ExceptionalHalt::OutOfBounds)?
1415 } else {
1416 &ctx_result.output
1417 },
1418 )?;
1419
1420 parent_call_frame.sub_return_data = ctx_result.output.clone();
1421
1422 match &ctx_result.result {
1424 TxResult::Success => {
1425 self.current_call_frame.stack.push(SUCCESS)?;
1426 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1427 self.current_call_frame.frame_state_gas_spilled = self
1433 .current_call_frame
1434 .frame_state_gas_spilled
1435 .checked_add(child_frame_state_gas_spilled)
1436 .ok_or(InternalError::Overflow)?;
1437 if self.env.config.fork >= Fork::Amsterdam {
1441 self.repay_state_gas_spill()?;
1442 }
1443 }
1444 TxResult::Revert(_) => {
1445 self.refund_new_account_state_gas(new_account_state_gas_charged)?;
1451 self.current_call_frame.stack.push(FAIL)?;
1452 }
1453 };
1454
1455 self.tracer.exit_context(ctx_result, false)?;
1456
1457 let mut stack = stack;
1458 stack.clear();
1459 self.stack_pool.push(stack);
1460
1461 Ok(())
1462 }
1463
1464 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1465 pub fn handle_return_create(
1466 &mut self,
1467 executed_call_frame: CallFrame,
1468 ctx_result: &ContextResult,
1469 ) -> Result<(), VMError> {
1470 let CallFrame {
1471 gas_limit,
1472 to,
1473 call_frame_backup,
1474 memory: old_callframe_memory,
1475 frame_state_gas_spilled: child_frame_state_gas_spilled,
1476 target_alive,
1477 stack,
1478 ..
1479 } = executed_call_frame;
1480
1481 #[cfg(not(target_arch = "riscv64"))]
1482 old_callframe_memory.clean_from_base();
1483
1484 #[cfg(target_arch = "riscv64")]
1485 old_callframe_memory.truncate_to_base();
1486
1487 let unused_gas = gas_limit
1489 .checked_sub(ctx_result.gas_used)
1490 .ok_or(InternalError::Underflow)?;
1491 self.current_call_frame.gas_remaining = self
1492 .current_call_frame
1493 .gas_remaining
1494 .checked_add(unused_gas as i64)
1495 .ok_or(InternalError::Overflow)?;
1496
1497 match ctx_result.result.clone() {
1499 TxResult::Success => {
1500 self.current_call_frame.stack.push(address_to_word(to))?;
1501 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1502 self.current_call_frame.frame_state_gas_spilled = self
1508 .current_call_frame
1509 .frame_state_gas_spilled
1510 .checked_add(child_frame_state_gas_spilled)
1511 .ok_or(InternalError::Overflow)?;
1512 if self.env.config.fork >= Fork::Amsterdam {
1516 self.repay_state_gas_spill()?;
1517 }
1518 }
1525 TxResult::Revert(err) => {
1526 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
1535 self.credit_state_gas_refund(self.state_gas_new_account)?;
1536 }
1537
1538 if err.is_revert_opcode() {
1540 self.current_call_frame.sub_return_data = ctx_result.output.clone();
1541 }
1542
1543 self.current_call_frame.stack.push(FAIL)?;
1544 }
1545 };
1546
1547 self.tracer.exit_context(ctx_result, false)?;
1548
1549 let mut stack = stack;
1550 stack.clear();
1551 self.stack_pool.push(stack);
1552
1553 Ok(())
1554 }
1555
1556 fn get_calldata(&mut self, offset: usize, size: usize) -> Result<Bytes, VMError> {
1557 self.current_call_frame.memory.load_range(offset, size)
1558 }
1559
1560 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1561 fn early_revert_message_call(&mut self, gas_limit: u64, reason: String) -> Result<(), VMError> {
1562 let callframe = &mut self.current_call_frame;
1563
1564 callframe.gas_remaining = callframe
1566 .gas_remaining
1567 .checked_add(gas_limit as i64)
1568 .ok_or(InternalError::Overflow)?;
1569 callframe.stack.push(FAIL)?; self.tracer.exit_early(0, Some(reason))?;
1572 Ok(())
1573 }
1574}