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