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, CallData, 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, U256, evm::calculate_create_address, types::Fork};
26use ethrex_common::{
27 tracing::CallType,
28 types::{Code, InitcodeBuffer},
29};
30use std::mem;
31
32pub struct OpCallHandler;
33impl OpcodeHandler for OpCallHandler {
34 #[inline(always)]
35 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
36 let [
37 gas,
38 callee,
39 value,
40 args_offset,
41 args_len,
42 return_offset,
43 return_len,
44 ] = *vm.current_call_frame.stack.pop()?;
45 let callee = word_to_address(callee);
46 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
47 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
48
49 if vm.current_call_frame.is_static && !value.is_zero() {
51 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
52 }
53
54 let value_cost = if !value.is_zero() {
55 gas_cost::call_positive_value_cost(vm.env.config.fork)
56 } else {
57 0
58 };
59 let (new_memory_size, address_was_cold, static_cost) = vm.check_call_static_gas(
60 args_offset,
61 args_len,
62 return_offset,
63 return_len,
64 callee,
65 value_cost,
66 )?;
67
68 vm.substate.add_accessed_address(callee);
69 let address_is_empty = if value.is_zero() {
73 false
74 } else {
75 vm.db.get_account(callee)?.is_empty()
76 };
77 let (callee_code, delegation) =
81 eip7702_peek_delegation(vm.db, &vm.substate, callee, vm.env.config.fork)?;
82 let is_delegation_7702 = delegation.is_some();
83 let (eip7702_gas_consumed, code_address) = match delegation {
84 Some((auth_address, access_cost)) => (access_cost, auth_address),
85 None => (0, callee),
86 };
87
88 if vm.validation_observer.active {
91 vm.validation_check_call_target(callee, is_delegation_7702)?;
92 }
93
94 let create_cost = if address_is_empty {
95 gas_cost::CALL_TO_EMPTY_ACCOUNT
96 } else {
97 0
98 };
99
100 vm.record_bal_call_touch(
103 callee,
104 code_address,
105 is_delegation_7702,
106 eip7702_gas_consumed,
107 new_memory_size,
108 vm.current_call_frame.memory.len(),
109 address_was_cold,
110 value_cost,
111 create_cost,
112 );
113
114 let bytecode = if let Some((auth_address, access_cost)) = delegation {
117 vm.current_call_frame.check_gas(
118 static_cost
119 .checked_add(access_cost)
120 .ok_or(ExceptionalHalt::OutOfGas)?,
121 )?;
122 vm.substate.add_accessed_address(auth_address);
123 vm.db.get_account_code(auth_address)?.clone()
124 } else {
125 callee_code
126 };
127
128 let fork = vm.env.config.fork;
129
130 #[expect(clippy::as_conversions, reason = "safe")]
132 let gas_left = (vm.current_call_frame.gas_remaining as u64)
133 .checked_sub(eip7702_gas_consumed)
134 .ok_or(ExceptionalHalt::OutOfGas)?;
135
136 let needs_state_gas = fork >= Fork::Amsterdam && address_is_empty;
141 let gas_left = if needs_state_gas {
142 let state_gas_new_account = vm.state_gas_new_account;
143 let from_reservoir = vm.state_gas_reservoir.min(state_gas_new_account);
144 #[expect(
146 clippy::arithmetic_side_effects,
147 reason = "from_reservoir <= state_gas_new_account"
148 )]
149 let spill = state_gas_new_account - from_reservoir;
150 gas_left
151 .checked_sub(spill)
152 .ok_or(ExceptionalHalt::OutOfGas)?
153 } else {
154 gas_left
155 };
156
157 let (gas_cost, gas_limit) = gas_cost::call(
158 new_memory_size,
159 vm.current_call_frame.memory.len(),
160 address_was_cold,
161 address_is_empty,
162 value,
163 gas,
164 gas_left,
165 fork,
166 )?;
167
168 vm.current_call_frame.increase_consumed_gas(
170 gas_cost
171 .checked_add(eip7702_gas_consumed)
172 .ok_or(ExceptionalHalt::OutOfGas)?,
173 )?;
174
175 if needs_state_gas {
177 vm.increase_state_gas(vm.state_gas_new_account)?;
178 }
179
180 if vm.opcode_tracer.active {
186 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
187 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
188 }
189
190 vm.current_call_frame.memory.resize(new_memory_size)?;
195
196 let data = vm.get_calldata(args_offset, args_len)?;
198 vm.tracer.enter(
199 CallType::CALL,
200 vm.current_call_frame.to,
201 callee,
202 value,
203 gas_limit,
204 &data,
205 )?;
206
207 vm.generic_call(
209 gas_limit,
210 value,
211 vm.current_call_frame.to,
212 callee,
213 code_address,
214 true,
215 vm.current_call_frame.is_static,
216 data,
217 return_offset,
218 return_len,
219 bytecode,
220 is_delegation_7702,
221 needs_state_gas,
222 )
223 }
224}
225
226pub struct OpCallCodeHandler;
227impl OpcodeHandler for OpCallCodeHandler {
228 #[inline(always)]
229 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
230 let [
231 gas,
232 address,
233 value,
234 args_offset,
235 args_len,
236 return_offset,
237 return_len,
238 ] = *vm.current_call_frame.stack.pop()?;
239 let address = word_to_address(address);
240 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
241 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
242
243 let value_cost = if !value.is_zero() {
244 gas_cost::call_positive_value_cost(vm.env.config.fork)
245 } else {
246 0
247 };
248 let (new_memory_size, address_was_cold, static_cost) = vm.check_call_static_gas(
249 args_offset,
250 args_len,
251 return_offset,
252 return_len,
253 address,
254 value_cost,
255 )?;
256
257 vm.substate.add_accessed_address(address);
258 let (target_code, delegation) =
262 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
263 let is_delegation_7702 = delegation.is_some();
264 let (eip7702_gas_consumed, code_address) = match delegation {
265 Some((auth_address, access_cost)) => (access_cost, auth_address),
266 None => (0, address),
267 };
268
269 if vm.validation_observer.active {
273 vm.validation_check_call_target(address, is_delegation_7702)?;
274 }
275
276 vm.record_bal_call_touch(
278 address,
279 code_address,
280 is_delegation_7702,
281 eip7702_gas_consumed,
282 new_memory_size,
283 vm.current_call_frame.memory.len(),
284 address_was_cold,
285 value_cost,
286 0,
287 );
288
289 let bytecode = if let Some((auth_address, access_cost)) = delegation {
290 vm.current_call_frame.check_gas(
291 static_cost
292 .checked_add(access_cost)
293 .ok_or(ExceptionalHalt::OutOfGas)?,
294 )?;
295 vm.substate.add_accessed_address(auth_address);
296 vm.db.get_account_code(auth_address)?.clone()
297 } else {
298 target_code
299 };
300
301 #[expect(clippy::as_conversions, reason = "safe")]
302 let gas_left = (vm.current_call_frame.gas_remaining as u64)
303 .checked_sub(eip7702_gas_consumed)
304 .ok_or(ExceptionalHalt::OutOfGas)?;
305 let (gas_cost, gas_limit) = gas_cost::callcode(
306 new_memory_size,
307 vm.current_call_frame.memory.len(),
308 address_was_cold,
309 value,
310 gas,
311 gas_left,
312 vm.env.config.fork,
313 )?;
314 vm.current_call_frame.increase_consumed_gas(
315 gas_cost
316 .checked_add(eip7702_gas_consumed)
317 .ok_or(ExceptionalHalt::OutOfGas)?,
318 )?;
319
320 if vm.opcode_tracer.active {
322 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
323 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
324 }
325
326 vm.current_call_frame.memory.resize(new_memory_size)?;
331
332 let data = vm.get_calldata(args_offset, args_len)?;
334 vm.tracer.enter(
335 CallType::CALLCODE,
336 vm.current_call_frame.to,
337 code_address,
338 value,
339 gas_limit,
340 &data,
341 )?;
342
343 vm.generic_call(
345 gas_limit,
346 value,
347 vm.current_call_frame.to,
348 vm.current_call_frame.to,
349 code_address,
350 true,
351 vm.current_call_frame.is_static,
352 data,
353 return_offset,
354 return_len,
355 bytecode,
356 is_delegation_7702,
357 false,
358 )
359 }
360}
361
362pub struct OpDelegateCallHandler;
363impl OpcodeHandler for OpDelegateCallHandler {
364 #[inline(always)]
365 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
366 let [
367 gas,
368 address,
369 args_offset,
370 args_len,
371 return_offset,
372 return_len,
373 ] = *vm.current_call_frame.stack.pop()?;
374 let address = word_to_address(address);
375 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
376 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
377
378 let (new_memory_size, address_was_cold, static_cost) =
379 vm.check_call_static_gas(args_offset, args_len, return_offset, return_len, address, 0)?;
380
381 vm.substate.add_accessed_address(address);
382 let (target_code, delegation) =
386 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
387 let is_delegation_7702 = delegation.is_some();
388 let (eip7702_gas_consumed, code_address) = match delegation {
389 Some((auth_address, access_cost)) => (access_cost, auth_address),
390 None => (0, address),
391 };
392
393 if vm.validation_observer.active {
396 vm.validation_check_call_target(address, is_delegation_7702)?;
397 }
398
399 vm.record_bal_call_touch(
401 address,
402 code_address,
403 is_delegation_7702,
404 eip7702_gas_consumed,
405 new_memory_size,
406 vm.current_call_frame.memory.len(),
407 address_was_cold,
408 0,
409 0,
410 );
411
412 let bytecode = if let Some((auth_address, access_cost)) = delegation {
413 vm.current_call_frame.check_gas(
414 static_cost
415 .checked_add(access_cost)
416 .ok_or(ExceptionalHalt::OutOfGas)?,
417 )?;
418 vm.substate.add_accessed_address(auth_address);
419 vm.db.get_account_code(auth_address)?.clone()
420 } else {
421 target_code
422 };
423
424 #[expect(clippy::as_conversions, reason = "safe")]
425 let gas_left = (vm.current_call_frame.gas_remaining as u64)
426 .checked_sub(eip7702_gas_consumed)
427 .ok_or(ExceptionalHalt::OutOfGas)?;
428 let (gas_cost, gas_limit) = gas_cost::delegatecall(
429 new_memory_size,
430 vm.current_call_frame.memory.len(),
431 address_was_cold,
432 gas,
433 gas_left,
434 vm.env.config.fork,
435 )?;
436 vm.current_call_frame.increase_consumed_gas(
437 gas_cost
438 .checked_add(eip7702_gas_consumed)
439 .ok_or(ExceptionalHalt::OutOfGas)?,
440 )?;
441
442 if vm.opcode_tracer.active {
444 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
445 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
446 }
447
448 vm.current_call_frame.memory.resize(new_memory_size)?;
453
454 let data = vm.get_calldata(args_offset, args_len)?;
456 vm.tracer.enter(
459 CallType::DELEGATECALL,
460 vm.current_call_frame.to,
461 code_address,
462 vm.current_call_frame.msg_value,
463 gas_limit,
464 &data,
465 )?;
466
467 vm.generic_call(
469 gas_limit,
470 vm.current_call_frame.msg_value,
471 vm.current_call_frame.msg_sender,
472 vm.current_call_frame.to,
473 code_address,
474 false,
475 vm.current_call_frame.is_static,
476 data,
477 return_offset,
478 return_len,
479 bytecode,
480 is_delegation_7702,
481 false,
482 )
483 }
484}
485
486pub struct OpStaticCallHandler;
487impl OpcodeHandler for OpStaticCallHandler {
488 #[inline(always)]
489 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
490 let [
491 gas,
492 address,
493 args_offset,
494 args_len,
495 return_offset,
496 return_len,
497 ] = *vm.current_call_frame.stack.pop()?;
498 let address = word_to_address(address);
499 let (args_len, args_offset) = size_offset_to_usize(args_len, args_offset)?;
500 let (return_len, return_offset) = size_offset_to_usize(return_len, return_offset)?;
501
502 let (new_memory_size, address_was_cold, static_cost) =
503 vm.check_call_static_gas(args_offset, args_len, return_offset, return_len, address, 0)?;
504
505 vm.substate.add_accessed_address(address);
506 let (target_code, delegation) =
510 eip7702_peek_delegation(vm.db, &vm.substate, address, vm.env.config.fork)?;
511 let is_delegation_7702 = delegation.is_some();
512 let (eip7702_gas_consumed, code_address) = match delegation {
513 Some((auth_address, access_cost)) => (access_cost, auth_address),
514 None => (0, address),
515 };
516
517 if vm.validation_observer.active {
520 vm.validation_check_call_target(address, is_delegation_7702)?;
521 }
522
523 vm.record_bal_call_touch(
525 address,
526 code_address,
527 is_delegation_7702,
528 eip7702_gas_consumed,
529 new_memory_size,
530 vm.current_call_frame.memory.len(),
531 address_was_cold,
532 0,
533 0,
534 );
535
536 let bytecode = if let Some((auth_address, access_cost)) = delegation {
537 vm.current_call_frame.check_gas(
538 static_cost
539 .checked_add(access_cost)
540 .ok_or(ExceptionalHalt::OutOfGas)?,
541 )?;
542 vm.substate.add_accessed_address(auth_address);
543 vm.db.get_account_code(auth_address)?.clone()
544 } else {
545 target_code
546 };
547
548 #[expect(clippy::as_conversions, reason = "safe")]
549 let gas_left = (vm.current_call_frame.gas_remaining as u64)
550 .checked_sub(eip7702_gas_consumed)
551 .ok_or(ExceptionalHalt::OutOfGas)?;
552 let (gas_cost, gas_limit) = gas_cost::staticcall(
553 new_memory_size,
554 vm.current_call_frame.memory.len(),
555 address_was_cold,
556 gas,
557 gas_left,
558 vm.env.config.fork,
559 )?;
560 vm.current_call_frame.increase_consumed_gas(
561 gas_cost
562 .checked_add(eip7702_gas_consumed)
563 .ok_or(ExceptionalHalt::OutOfGas)?,
564 )?;
565
566 if vm.opcode_tracer.active {
568 let geth_cost = gas_cost.saturating_add(eip7702_gas_consumed);
569 vm.opcode_tracer.last_opcode_gas_cost = Some(geth_cost);
570 }
571
572 vm.current_call_frame.memory.resize(new_memory_size)?;
577
578 let data = vm.get_calldata(args_offset, args_len)?;
580 vm.tracer.enter(
581 CallType::STATICCALL,
582 vm.current_call_frame.to,
583 address,
584 U256::zero(),
585 gas_limit,
586 &data,
587 )?;
588
589 vm.generic_call(
591 gas_limit,
592 U256::zero(),
593 vm.current_call_frame.to,
594 address,
595 address,
596 true,
597 true,
598 data,
599 return_offset,
600 return_len,
601 bytecode,
602 is_delegation_7702,
603 false,
604 )
605 }
606}
607
608pub struct OpReturnHandler;
609impl OpcodeHandler for OpReturnHandler {
610 #[inline(always)]
611 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
612 let [offset, len] = *vm.current_call_frame.stack.pop()?;
613 let (len, offset) = size_offset_to_usize(len, offset)?;
614
615 vm.current_call_frame
616 .increase_consumed_gas(gas_cost::exit_opcode(
617 calculate_memory_size(offset, len)?,
618 vm.current_call_frame.memory.len(),
619 )?)?;
620
621 if len != 0 {
622 vm.current_call_frame.output = vm.current_call_frame.memory.load_range(offset, len)?;
623 }
624
625 Ok(OpcodeResult::Halt)
626 }
627}
628
629pub struct OpCreateHandler;
630impl OpcodeHandler for OpCreateHandler {
631 #[inline(always)]
632 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
633 if vm.env.config.fork >= Fork::Amsterdam && vm.current_call_frame.is_static {
636 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
637 }
638
639 let [value_in_wei, code_offset, code_len] = *vm.current_call_frame.stack.pop()?;
640 let (code_len, code_offset) = size_offset_to_usize(code_len, code_offset)?;
641
642 let create_gas = gas_cost::create(
643 calculate_memory_size(code_offset, code_len)?,
644 vm.current_call_frame.memory.len(),
645 code_len,
646 vm.env.config.fork,
647 )?;
648 vm.current_call_frame.increase_consumed_gas(create_gas)?;
649
650 if vm.opcode_tracer.active {
652 vm.opcode_tracer.last_opcode_gas_cost = Some(create_gas);
653 }
654
655 if vm.validation_observer.active {
658 vm.validation_check_create();
659 }
660
661 vm.generic_create(value_in_wei, code_offset, code_len, None)
662 }
663}
664
665pub struct OpCreate2Handler;
666impl OpcodeHandler for OpCreate2Handler {
667 #[inline(always)]
668 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
669 if vm.env.config.fork >= Fork::Amsterdam && vm.current_call_frame.is_static {
672 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
673 }
674
675 let [value_in_wei, code_offset, code_len, salt] = *vm.current_call_frame.stack.pop()?;
676 let (code_len, code_offset) = size_offset_to_usize(code_len, code_offset)?;
677
678 let create2_gas = gas_cost::create_2(
679 calculate_memory_size(code_offset, code_len)?,
680 vm.current_call_frame.memory.len(),
681 code_len,
682 vm.env.config.fork,
683 )?;
684 vm.current_call_frame.increase_consumed_gas(create2_gas)?;
685
686 if vm.opcode_tracer.active {
688 vm.opcode_tracer.last_opcode_gas_cost = Some(create2_gas);
689 }
690
691 if vm.validation_observer.active {
694 vm.validation_check_create();
695 }
696
697 vm.generic_create(value_in_wei, code_offset, code_len, Some(salt))
698 }
699}
700
701pub struct OpSelfDestructHandler;
702impl OpcodeHandler for OpSelfDestructHandler {
703 #[inline(always)]
704 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
705 if vm.current_call_frame.is_static {
706 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
707 }
708
709 let beneficiary = word_to_address(vm.current_call_frame.stack.pop1()?);
710 let to = vm.current_call_frame.to;
711
712 let target_account_is_cold = vm.substate.add_accessed_address(beneficiary);
713
714 if vm.env.config.fork >= Fork::Amsterdam {
718 let base_cost =
719 gas_cost::selfdestruct_base(target_account_is_cold, vm.env.config.fork)?;
720 #[expect(clippy::as_conversions, reason = "base_cost fits in i64")]
722 if vm.current_call_frame.gas_remaining < (base_cost as i64) {
723 return Err(ExceptionalHalt::OutOfGas.into());
724 }
725 }
726
727 let target_account_is_empty = vm.db.get_account(beneficiary)?.is_empty();
728 let balance = vm.db.get_account(to)?.info.balance;
729
730 if vm.env.config.fork >= Fork::Amsterdam {
736 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
745 recorder.record_touched_address(beneficiary);
746 recorder.record_touched_address(to);
747 if balance > U256::zero() {
748 recorder.set_initial_balance(to, balance);
749 }
750 }
751
752 vm.current_call_frame
754 .increase_consumed_gas(gas_cost::selfdestruct(
755 target_account_is_cold,
756 target_account_is_empty,
757 balance,
758 vm.env.config.fork,
759 )?)?;
760
761 if target_account_is_empty && balance > U256::zero() {
763 vm.increase_state_gas(vm.state_gas_new_account)?;
764 }
765 } else {
766 vm.current_call_frame
767 .increase_consumed_gas(gas_cost::selfdestruct(
768 target_account_is_cold,
769 target_account_is_empty,
770 balance,
771 vm.env.config.fork,
772 )?)?;
773
774 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
779 recorder.record_touched_address(beneficiary);
780 recorder.record_touched_address(to);
781 if balance > U256::zero() {
782 recorder.set_initial_balance(to, balance);
783 }
784 }
785 }
786
787 if vm.env.config.fork >= Fork::Cancun {
789 if !(vm.env.config.fork >= Fork::Amsterdam && to == beneficiary) {
794 vm.transfer(to, beneficiary, balance)?;
795 }
796
797 if vm.substate.is_account_created(&to) {
799 if vm.env.config.fork < Fork::Amsterdam {
803 vm.get_account_mut(to)?.info.balance = U256::zero();
804
805 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
807 recorder.record_balance_change(to, U256::zero());
808 }
809 }
810
811 vm.substate.add_selfdestruct(to);
812 }
813
814 if vm.env.config.fork >= Fork::Amsterdam && !balance.is_zero() && to != beneficiary {
818 let log = create_eth_transfer_log(to, beneficiary, balance);
819 vm.substate.add_log(log);
820 }
822 } else {
823 vm.increase_account_balance(beneficiary, balance)?;
824 vm.get_account_mut(to)?.info.balance = U256::zero();
825
826 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
828 recorder.record_balance_change(to, U256::zero());
829 }
830
831 vm.substate.add_selfdestruct(to);
832 }
833
834 vm.tracer.enter(
835 CallType::SELFDESTRUCT,
836 vm.current_call_frame.to,
837 beneficiary,
838 balance,
839 0,
840 &Default::default(),
841 )?;
842 vm.tracer.exit_early(0, None)?;
843
844 Ok(OpcodeResult::Halt)
845 }
846}
847
848pub struct OpRevertHandler;
849impl OpcodeHandler for OpRevertHandler {
850 #[inline(always)]
851 fn eval(vm: &mut VM<'_>) -> Result<OpcodeResult, VMError> {
852 let [offset, len] = *vm.current_call_frame.stack.pop()?;
853 let (len, offset) = size_offset_to_usize(len, offset)?;
854
855 vm.current_call_frame
856 .increase_consumed_gas(gas_cost::exit_opcode(
857 calculate_memory_size(offset, len)?,
858 vm.current_call_frame.memory.len(),
859 )?)?;
860
861 if len != 0 {
862 vm.current_call_frame.output = vm.current_call_frame.memory.load_range(offset, len)?;
863 }
864
865 Err(VMError::RevertOpcode)
866 }
867}
868
869impl<'a> VM<'a> {
870 pub fn generic_create(
872 &mut self,
873 value: U256,
874 code_offset_in_memory: usize,
875 code_size_in_memory: usize,
876 salt: Option<U256>,
877 ) -> Result<OpcodeResult, VMError> {
878 let mut initcode = self.initcode_pool.pop().unwrap_or_default();
882 let result = self.create_with_initcode_buffer(
883 &mut initcode,
884 value,
885 code_offset_in_memory,
886 code_size_in_memory,
887 salt,
888 );
889 self.pool_initcode(initcode);
890 result
891 }
892
893 fn create_with_initcode_buffer(
896 &mut self,
897 initcode: &mut InitcodeBuffer,
898 value: U256,
899 code_offset_in_memory: usize,
900 code_size_in_memory: usize,
901 salt: Option<U256>,
902 ) -> Result<OpcodeResult, VMError> {
903 let init_code_max = if self.env.config.fork >= Fork::Amsterdam {
905 AMSTERDAM_INIT_CODE_MAX_SIZE
906 } else {
907 INIT_CODE_MAX_SIZE
908 };
909 if code_size_in_memory > init_code_max && self.env.config.fork >= Fork::Shanghai {
910 return Err(ExceptionalHalt::OutOfGas.into());
911 }
912
913 let current_call_frame = &mut self.current_call_frame;
914
915 if self.env.config.fork < Fork::Amsterdam && current_call_frame.is_static {
917 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
918 }
919
920 current_call_frame.sub_return_data = Bytes::new();
922
923 self.current_call_frame.memory.with_range(
925 code_offset_in_memory,
926 code_size_in_memory,
927 |code| initcode.set(code),
928 )?;
929
930 let deployer = self.current_call_frame.to;
932 let (deployer_balance, deployer_nonce) = {
933 let deployer_account = self.db.get_account(deployer)?;
934 (deployer_account.info.balance, deployer_account.info.nonce)
935 };
936
937 let new_address = match salt {
939 Some(salt) => calculate_create2_address(deployer, initcode.initcode(), salt)?,
940 None => calculate_create_address(deployer, deployer_nonce),
941 };
942
943 let call_type = match salt {
944 Some(_) => CallType::CREATE2,
945 None => CallType::CREATE,
946 };
947
948 let new_depth = self
949 .current_call_frame
950 .depth
951 .checked_add(1)
952 .ok_or(InternalError::Overflow)?;
953
954 let checks = [
963 (deployer_balance < value, "OutOfFund"),
964 (new_depth > 1024, "MaxDepth"),
965 (deployer_nonce == u64::MAX, "MaxNonce"),
966 ];
967 for (condition, reason) in checks {
968 if condition {
969 let preview_gas = gas_cost::max_message_call_gas(&self.current_call_frame)?;
972 if self.tracer.active {
973 self.tracer.enter(
974 call_type,
975 deployer,
976 new_address,
977 value,
978 preview_gas,
979 &Bytes::copy_from_slice(initcode.initcode()).into(),
980 )?;
981 }
982 self.current_call_frame.stack.push(FAIL)?;
983 self.tracer.exit_early(0, Some(reason.to_string()))?;
984 return Ok(OpcodeResult::Continue);
985 }
986 }
987
988 self.substate.add_accessed_address(new_address);
990
991 if let Some(recorder) = self.db.bal_recorder.as_mut() {
996 recorder.record_touched_address(new_address);
997 }
998
999 let target_alive = !self.get_account_mut(new_address)?.is_empty();
1006
1007 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
1017 self.increase_state_gas(self.state_gas_new_account)?;
1018 }
1019
1020 let gas_limit = gas_cost::max_message_call_gas(&self.current_call_frame)?;
1022 self.current_call_frame.increase_consumed_gas(gas_limit)?;
1023
1024 if self.tracer.active {
1026 self.tracer.enter(
1027 call_type,
1028 deployer,
1029 new_address,
1030 value,
1031 gas_limit,
1032 &Bytes::copy_from_slice(initcode.initcode()).into(),
1033 )?;
1034 }
1035
1036 self.increment_account_nonce(deployer)?;
1038
1039 let new_account = self.get_account_mut(new_address)?;
1041 if new_account.create_would_collide() {
1042 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
1046 self.credit_state_gas_refund(self.state_gas_new_account)?;
1047 }
1048 self.current_call_frame.stack.push(FAIL)?;
1049 self.tracer
1050 .exit_early(gas_limit, Some("CreateAccExists".to_string()))?;
1051 return Ok(OpcodeResult::Continue);
1052 }
1053
1054 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
1056
1057 let mut stack = self.stack_pool.pop().unwrap_or_default();
1058 stack.clear();
1059
1060 let next_memory = self.current_call_frame.memory.next_memory();
1061
1062 let mut new_call_frame = CallFrame::new(
1063 deployer,
1064 new_address,
1065 new_address,
1066 Code::from_initcode_buffer(mem::take(initcode)),
1067 value,
1068 CallData::default(),
1069 false,
1070 gas_limit,
1071 new_depth,
1072 true,
1073 true,
1074 0,
1075 0,
1076 stack,
1077 next_memory,
1078 );
1079 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1081 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1085 new_call_frame.target_alive = target_alive;
1088
1089 self.add_callframe(new_call_frame);
1090
1091 self.increment_account_nonce(new_address)?; self.transfer(deployer, new_address, value)?;
1094
1095 self.substate.push_backup();
1096 self.substate.add_created_account(new_address); if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() {
1101 let log = create_eth_transfer_log(deployer, new_address, value);
1102 self.substate.add_log(log);
1103 }
1104
1105 Ok(OpcodeResult::Continue)
1106 }
1107
1108 fn check_call_static_gas(
1114 &mut self,
1115 args_offset: usize,
1116 args_len: usize,
1117 return_offset: usize,
1118 return_len: usize,
1119 address: Address,
1120 value_cost: u64,
1121 ) -> Result<(usize, bool, u64), VMError> {
1122 let new_memory_size = calculate_memory_size(args_offset, args_len)?
1123 .max(calculate_memory_size(return_offset, return_len)?);
1124 let address_was_cold = !self.substate.is_address_accessed(&address);
1125 let memory_expansion_cost =
1126 memory::expansion_cost(new_memory_size, self.current_call_frame.memory.len())?;
1127 let access_gas_cost = if address_was_cold {
1128 gas_cost::cold_account_access_cost(self.env.config.fork)
1129 } else {
1130 gas_cost::WARM_ADDRESS_ACCESS_COST
1131 };
1132 let static_cost = memory_expansion_cost
1133 .checked_add(access_gas_cost)
1134 .ok_or(ExceptionalHalt::OutOfGas)?
1135 .checked_add(value_cost)
1136 .ok_or(ExceptionalHalt::OutOfGas)?;
1137 self.current_call_frame.check_gas(static_cost)?;
1138 Ok((new_memory_size, address_was_cold, static_cost))
1139 }
1140
1141 #[expect(
1144 clippy::too_many_arguments,
1145 reason = "matches EIP-7928 EELS reference parameters"
1146 )]
1147 fn record_bal_call_touch(
1148 &mut self,
1149 target: Address,
1150 code_address: Address,
1151 is_delegation_7702: bool,
1152 eip7702_gas_consumed: u64,
1153 new_memory_size: usize,
1154 current_memory_size: usize,
1155 address_was_cold: bool,
1156 value_cost: u64,
1157 create_cost: u64,
1158 ) {
1159 let Some(recorder) = self.db.bal_recorder.as_mut() else {
1160 return;
1161 };
1162 let mem_cost =
1166 memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
1167 let access_cost = if address_was_cold {
1168 gas_cost::cold_account_access_cost(self.env.config.fork)
1169 } else {
1170 gas_cost::WARM_ADDRESS_ACCESS_COST
1171 };
1172 let basic_cost = mem_cost
1173 .saturating_add(access_cost)
1174 .saturating_add(value_cost);
1175 let gas_remaining = self.current_call_frame.gas_remaining;
1176
1177 if gas_remaining >= i64::try_from(basic_cost).unwrap_or(i64::MAX) {
1178 recorder.record_touched_address(target);
1179
1180 if is_delegation_7702 {
1181 let delegation_check = basic_cost
1182 .saturating_add(create_cost)
1183 .saturating_add(eip7702_gas_consumed);
1184 if gas_remaining >= i64::try_from(delegation_check).unwrap_or(i64::MAX) {
1185 recorder.record_touched_address(code_address);
1186 }
1187 }
1188 }
1189 }
1190
1191 #[expect(
1198 clippy::too_many_arguments,
1199 reason = "inlined for performance, many args needed"
1200 )]
1201 #[inline(always)]
1202 pub fn generic_call(
1203 &mut self,
1204 gas_limit: u64,
1205 value: U256,
1206 msg_sender: Address,
1207 to: Address,
1208 code_address: Address,
1209 should_transfer_value: bool,
1210 is_static: bool,
1211 calldata: CallData,
1212 ret_offset: usize,
1213 ret_size: usize,
1214 bytecode: Code,
1215 is_delegation_7702: bool,
1216 new_account_charged: bool,
1217 ) -> Result<OpcodeResult, VMError> {
1218 self.current_call_frame.sub_return_data.clear();
1220
1221 if should_transfer_value && !value.is_zero() {
1223 let sender_balance = self.db.get_account(msg_sender)?.info.balance;
1224 if sender_balance < value {
1225 self.refund_new_account_state_gas(new_account_charged)?;
1227 self.early_revert_message_call(gas_limit, "OutOfFund".to_string())?;
1228 return Ok(OpcodeResult::Continue);
1229 }
1230 }
1231
1232 let new_depth = self
1234 .current_call_frame
1235 .depth
1236 .checked_add(1)
1237 .ok_or(InternalError::Overflow)?;
1238 if new_depth > 1024 {
1239 self.refund_new_account_state_gas(new_account_charged)?;
1240 self.early_revert_message_call(gas_limit, "MaxDepth".to_string())?;
1241 return Ok(OpcodeResult::Continue);
1242 }
1243
1244 if self.address_is_precompile(&code_address) && !is_delegation_7702 {
1245 if let Some(recorder) = self.db.bal_recorder.as_mut() {
1247 recorder.record_touched_address(code_address);
1248 }
1249
1250 let mut gas_remaining = gas_limit;
1251 let ctx_result =
1253 self.execute_precompile(code_address, &calldata, gas_limit, &mut gas_remaining)?;
1254
1255 let call_frame = &mut self.current_call_frame;
1256
1257 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1259 if ctx_result.is_success() {
1260 call_frame.gas_remaining = (call_frame.gas_remaining as u64)
1261 .checked_add(
1262 gas_limit
1263 .checked_sub(ctx_result.gas_used)
1264 .ok_or(InternalError::Underflow)?,
1265 )
1266 .ok_or(InternalError::Overflow)?
1267 as i64;
1268 }
1269
1270 call_frame.memory.store_data(
1272 ret_offset,
1273 if ctx_result.output.len() >= ret_size {
1274 ctx_result
1275 .output
1276 .get(..ret_size)
1277 .ok_or(ExceptionalHalt::OutOfBounds)?
1278 } else {
1279 &ctx_result.output
1280 },
1281 )?;
1282 call_frame.sub_return_data = ctx_result.output.clone();
1283
1284 call_frame.stack.push(match &ctx_result.result {
1286 TxResult::Success => SUCCESS,
1287 TxResult::Revert(_) => FAIL,
1288 })?;
1289
1290 self.refund_new_account_state_gas(new_account_charged && !ctx_result.is_success())?;
1294
1295 if should_transfer_value && ctx_result.is_success() {
1297 self.transfer(msg_sender, to, value)?;
1298
1299 if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() && msg_sender != to {
1302 let log = create_eth_transfer_log(msg_sender, to, value);
1303 self.substate.add_log(log);
1304 }
1305 }
1306
1307 self.tracer.exit_context(&ctx_result, false)?;
1308 } else {
1309 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
1311
1312 let mut stack = self.stack_pool.pop().unwrap_or_default();
1313 stack.clear();
1314
1315 let next_memory = self.current_call_frame.memory.next_memory();
1316
1317 let mut new_call_frame = CallFrame::new(
1318 msg_sender,
1319 to,
1320 code_address,
1321 bytecode,
1322 value,
1323 calldata,
1324 is_static,
1325 gas_limit,
1326 new_depth,
1327 should_transfer_value,
1328 false,
1329 ret_offset,
1330 ret_size,
1331 stack,
1332 next_memory,
1333 );
1334 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1336 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1337 new_call_frame.new_account_state_gas_charged = new_account_charged;
1338
1339 self.add_callframe(new_call_frame);
1340
1341 if should_transfer_value {
1343 self.transfer(msg_sender, to, value)?;
1344 }
1345
1346 self.substate.push_backup();
1347
1348 if should_transfer_value
1352 && self.env.config.fork >= Fork::Amsterdam
1353 && !value.is_zero()
1354 && msg_sender != to
1355 {
1356 let log = create_eth_transfer_log(msg_sender, to, value);
1357 self.substate.add_log(log);
1358 }
1359 }
1360
1361 Ok(OpcodeResult::Continue)
1362 }
1363
1364 pub fn handle_state_backup(
1372 &mut self,
1373 ctx_result: &ContextResult,
1374 consume_backup: bool,
1375 ) -> Result<(), VMError> {
1376 if ctx_result.is_success() {
1377 self.substate.commit_backup();
1378 } else {
1379 self.substate.revert_backup();
1380 if consume_backup {
1381 self.restore_cache_state_consuming()?;
1382 } else {
1383 self.restore_cache_state()?;
1384 }
1385 }
1386
1387 Ok(())
1388 }
1389
1390 pub fn handle_return(&mut self, ctx_result: &ContextResult) -> Result<(), VMError> {
1394 self.handle_state_backup(ctx_result, true)?;
1397 let executed_call_frame = self.pop_call_frame()?;
1398
1399 if executed_call_frame.is_create {
1401 self.handle_return_create(executed_call_frame, ctx_result)?;
1402 } else {
1403 self.handle_return_call(executed_call_frame, ctx_result)?;
1404 }
1405
1406 Ok(())
1407 }
1408
1409 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1410 pub fn handle_return_call(
1411 &mut self,
1412 executed_call_frame: CallFrame,
1413 ctx_result: &ContextResult,
1414 ) -> Result<(), VMError> {
1415 let CallFrame {
1416 gas_limit,
1417 ret_offset,
1418 ret_size,
1419 memory: old_callframe_memory,
1420 frame_state_gas_spilled: child_frame_state_gas_spilled,
1421 call_frame_backup,
1422 stack,
1423 new_account_state_gas_charged,
1424 ..
1425 } = executed_call_frame;
1426
1427 #[cfg(not(target_arch = "riscv64"))]
1428 old_callframe_memory.clean_from_base();
1429
1430 #[cfg(target_arch = "riscv64")]
1431 old_callframe_memory.truncate_to_base();
1432
1433 let parent_call_frame = &mut self.current_call_frame;
1434
1435 let child_unused_gas = gas_limit
1437 .checked_sub(ctx_result.gas_used)
1438 .ok_or(InternalError::Underflow)?;
1439 parent_call_frame.gas_remaining = parent_call_frame
1440 .gas_remaining
1441 .checked_add(child_unused_gas as i64)
1442 .ok_or(InternalError::Overflow)?;
1443
1444 parent_call_frame.memory.store_data(
1446 ret_offset,
1447 if ctx_result.output.len() >= ret_size {
1448 ctx_result
1449 .output
1450 .get(..ret_size)
1451 .ok_or(ExceptionalHalt::OutOfBounds)?
1452 } else {
1453 &ctx_result.output
1454 },
1455 )?;
1456
1457 parent_call_frame.sub_return_data = ctx_result.output.clone();
1458
1459 match &ctx_result.result {
1461 TxResult::Success => {
1462 self.current_call_frame.stack.push(SUCCESS)?;
1463 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1464 self.current_call_frame.frame_state_gas_spilled = self
1470 .current_call_frame
1471 .frame_state_gas_spilled
1472 .checked_add(child_frame_state_gas_spilled)
1473 .ok_or(InternalError::Overflow)?;
1474 if self.env.config.fork >= Fork::Amsterdam {
1478 self.repay_state_gas_spill()?;
1479 }
1480 }
1481 TxResult::Revert(_) => {
1482 self.refund_new_account_state_gas(new_account_state_gas_charged)?;
1488 self.current_call_frame.stack.push(FAIL)?;
1489 }
1490 };
1491
1492 self.tracer.exit_context(ctx_result, false)?;
1493
1494 let mut stack = stack;
1495 stack.clear();
1496 self.stack_pool.push(stack);
1497
1498 Ok(())
1499 }
1500
1501 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1502 pub fn handle_return_create(
1503 &mut self,
1504 executed_call_frame: CallFrame,
1505 ctx_result: &ContextResult,
1506 ) -> Result<(), VMError> {
1507 let CallFrame {
1508 gas_limit,
1509 to,
1510 call_frame_backup,
1511 memory: old_callframe_memory,
1512 frame_state_gas_spilled: child_frame_state_gas_spilled,
1513 target_alive,
1514 stack,
1515 bytecode,
1516 ..
1517 } = executed_call_frame;
1518
1519 #[cfg(not(target_arch = "riscv64"))]
1520 old_callframe_memory.clean_from_base();
1521
1522 #[cfg(target_arch = "riscv64")]
1523 old_callframe_memory.truncate_to_base();
1524
1525 let unused_gas = gas_limit
1527 .checked_sub(ctx_result.gas_used)
1528 .ok_or(InternalError::Underflow)?;
1529 self.current_call_frame.gas_remaining = self
1530 .current_call_frame
1531 .gas_remaining
1532 .checked_add(unused_gas as i64)
1533 .ok_or(InternalError::Overflow)?;
1534
1535 match ctx_result.result.clone() {
1537 TxResult::Success => {
1538 self.current_call_frame.stack.push(address_to_word(to))?;
1539 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1540 self.current_call_frame.frame_state_gas_spilled = self
1546 .current_call_frame
1547 .frame_state_gas_spilled
1548 .checked_add(child_frame_state_gas_spilled)
1549 .ok_or(InternalError::Overflow)?;
1550 if self.env.config.fork >= Fork::Amsterdam {
1554 self.repay_state_gas_spill()?;
1555 }
1556 }
1563 TxResult::Revert(err) => {
1564 if self.env.config.fork >= Fork::Amsterdam && !target_alive {
1573 self.credit_state_gas_refund(self.state_gas_new_account)?;
1574 }
1575
1576 if err.is_revert_opcode() {
1578 self.current_call_frame.sub_return_data = ctx_result.output.clone();
1579 }
1580
1581 self.current_call_frame.stack.push(FAIL)?;
1582 }
1583 };
1584
1585 self.tracer.exit_context(ctx_result, false)?;
1586
1587 let mut stack = stack;
1588 stack.clear();
1589 self.stack_pool.push(stack);
1590 self.recycle_initcode(bytecode);
1591
1592 Ok(())
1593 }
1594
1595 fn get_calldata(&mut self, offset: usize, size: usize) -> Result<CallData, VMError> {
1597 self.current_call_frame.memory.calldata_view(offset, size)
1598 }
1599
1600 fn recycle_initcode(&mut self, code: Code) {
1603 self.pool_initcode(code.into_initcode_buffer());
1604 }
1605
1606 fn pool_initcode(&mut self, mut buffer: InitcodeBuffer) {
1609 if buffer.is_allocated() {
1610 buffer.clear();
1611 self.initcode_pool.push(buffer);
1612 }
1613 }
1614
1615 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1616 fn early_revert_message_call(&mut self, gas_limit: u64, reason: String) -> Result<(), VMError> {
1617 let callframe = &mut self.current_call_frame;
1618
1619 callframe.gas_remaining = callframe
1621 .gas_remaining
1622 .checked_add(gas_limit as i64)
1623 .ok_or(InternalError::Overflow)?;
1624 callframe.stack.push(FAIL)?; self.tracer.exit_early(0, Some(reason))?;
1627 Ok(())
1628 }
1629}