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 let accessed_slots = vm.substate.get_accessed_storage_slots(&to);
735 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
736 recorder.record_touched_address(beneficiary);
737 recorder.record_touched_address(to);
738 if balance > U256::zero() {
739 recorder.set_initial_balance(to, balance);
740 }
741 for key in &accessed_slots {
742 let slot = U256::from_big_endian(key.as_bytes());
743 recorder.record_storage_read(to, slot);
744 }
745 }
746
747 vm.current_call_frame
749 .increase_consumed_gas(gas_cost::selfdestruct(
750 target_account_is_cold,
751 target_account_is_empty,
752 balance,
753 vm.env.config.fork,
754 )?)?;
755
756 if target_account_is_empty && balance > U256::zero() {
758 vm.increase_state_gas(vm.state_gas_new_account)?;
759 }
760 } else {
761 vm.current_call_frame
762 .increase_consumed_gas(gas_cost::selfdestruct(
763 target_account_is_cold,
764 target_account_is_empty,
765 balance,
766 vm.env.config.fork,
767 )?)?;
768
769 let accessed_slots = vm.substate.get_accessed_storage_slots(&to);
771 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
772 recorder.record_touched_address(beneficiary);
773 recorder.record_touched_address(to);
774 if balance > U256::zero() {
775 recorder.set_initial_balance(to, balance);
776 }
777 for key in &accessed_slots {
778 let slot = U256::from_big_endian(key.as_bytes());
779 recorder.record_storage_read(to, slot);
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 if self.env.config.fork >= Fork::Amsterdam {
888 self.increase_state_gas(self.state_gas_new_account)?;
889 }
890
891 let current_call_frame = &mut self.current_call_frame;
892
893 if self.env.config.fork < Fork::Amsterdam && current_call_frame.is_static {
895 return Err(ExceptionalHalt::OpcodeNotAllowedInStaticContext.into());
896 }
897
898 current_call_frame.sub_return_data = Bytes::new();
900
901 let gas_limit = gas_cost::max_message_call_gas(current_call_frame)?;
903 current_call_frame.increase_consumed_gas(gas_limit)?;
904
905 let code = self
907 .current_call_frame
908 .memory
909 .load_range(code_offset_in_memory, code_size_in_memory)?;
910
911 let deployer = self.current_call_frame.to;
913 let (deployer_balance, deployer_nonce) = {
914 let deployer_account = self.db.get_account(deployer)?;
915 (deployer_account.info.balance, deployer_account.info.nonce)
916 };
917
918 let new_address = match salt {
920 Some(salt) => calculate_create2_address(deployer, &code, salt)?,
921 None => calculate_create_address(deployer, deployer_nonce),
922 };
923
924 let call_type = match salt {
926 Some(_) => CallType::CREATE2,
927 None => CallType::CREATE,
928 };
929 self.tracer
930 .enter(call_type, deployer, new_address, value, gas_limit, &code);
931
932 let new_depth = self
933 .current_call_frame
934 .depth
935 .checked_add(1)
936 .ok_or(InternalError::Overflow)?;
937
938 let checks = [
944 (deployer_balance < value, "OutOfFund"),
945 (new_depth > 1024, "MaxDepth"),
946 (deployer_nonce == u64::MAX, "MaxNonce"),
947 ];
948 for (condition, reason) in checks {
949 if condition {
950 if self.env.config.fork >= Fork::Amsterdam {
954 self.credit_state_gas_refund(self.state_gas_new_account)?;
955 }
956 self.early_revert_message_call(gas_limit, reason.to_string())?;
957 return Ok(OpcodeResult::Continue);
958 }
959 }
960
961 self.substate.add_accessed_address(new_address);
963
964 if let Some(recorder) = self.db.bal_recorder.as_mut() {
966 recorder.record_touched_address(new_address);
967 }
968
969 self.increment_account_nonce(deployer)?;
971
972 let new_account = self.get_account_mut(new_address)?;
974 if new_account.create_would_collide() {
975 if self.env.config.fork >= Fork::Amsterdam {
978 self.credit_state_gas_refund(self.state_gas_new_account)?;
979 }
980 self.current_call_frame.stack.push(FAIL)?;
981 self.tracer
982 .exit_early(gas_limit, Some("CreateAccExists".to_string()))?;
983 return Ok(OpcodeResult::Continue);
984 }
985 let target_alive = !self.get_account_mut(new_address)?.is_empty();
995
996 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
998
999 let mut stack = self.stack_pool.pop().unwrap_or_default();
1000 stack.clear();
1001
1002 let next_memory = self.current_call_frame.memory.next_memory();
1003
1004 let mut new_call_frame = CallFrame::new(
1005 deployer,
1006 new_address,
1007 new_address,
1008 Code::from_bytecode_unchecked(code, H256::zero()),
1010 value,
1011 Bytes::new(),
1012 false,
1013 gas_limit,
1014 new_depth,
1015 true,
1016 true,
1017 0,
1018 0,
1019 stack,
1020 next_memory,
1021 );
1022 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1024 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1028 new_call_frame.target_alive = target_alive;
1031
1032 self.add_callframe(new_call_frame);
1033
1034 self.increment_account_nonce(new_address)?; self.transfer(deployer, new_address, value)?;
1037
1038 self.substate.push_backup();
1039 self.substate.add_created_account(new_address); if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() {
1044 let log = create_eth_transfer_log(deployer, new_address, value);
1045 self.substate.add_log(log);
1046 }
1047
1048 Ok(OpcodeResult::Continue)
1049 }
1050
1051 fn check_call_static_gas(
1057 &mut self,
1058 args_offset: usize,
1059 args_len: usize,
1060 return_offset: usize,
1061 return_len: usize,
1062 address: Address,
1063 value_cost: u64,
1064 ) -> Result<(usize, bool, u64), VMError> {
1065 let new_memory_size = calculate_memory_size(args_offset, args_len)?
1066 .max(calculate_memory_size(return_offset, return_len)?);
1067 let address_was_cold = !self.substate.is_address_accessed(&address);
1068 let memory_expansion_cost =
1069 memory::expansion_cost(new_memory_size, self.current_call_frame.memory.len())?;
1070 let access_gas_cost = if address_was_cold {
1071 gas_cost::cold_account_access_cost(self.env.config.fork)
1072 } else {
1073 gas_cost::WARM_ADDRESS_ACCESS_COST
1074 };
1075 let static_cost = memory_expansion_cost
1076 .checked_add(access_gas_cost)
1077 .ok_or(ExceptionalHalt::OutOfGas)?
1078 .checked_add(value_cost)
1079 .ok_or(ExceptionalHalt::OutOfGas)?;
1080 self.current_call_frame.check_gas(static_cost)?;
1081 Ok((new_memory_size, address_was_cold, static_cost))
1082 }
1083
1084 #[expect(
1087 clippy::too_many_arguments,
1088 reason = "matches EIP-7928 EELS reference parameters"
1089 )]
1090 fn record_bal_call_touch(
1091 &mut self,
1092 target: Address,
1093 code_address: Address,
1094 is_delegation_7702: bool,
1095 eip7702_gas_consumed: u64,
1096 new_memory_size: usize,
1097 current_memory_size: usize,
1098 address_was_cold: bool,
1099 value_cost: u64,
1100 create_cost: u64,
1101 ) {
1102 let Some(recorder) = self.db.bal_recorder.as_mut() else {
1103 return;
1104 };
1105 let mem_cost =
1109 memory::expansion_cost(new_memory_size, current_memory_size).unwrap_or(u64::MAX);
1110 let access_cost = if address_was_cold {
1111 gas_cost::cold_account_access_cost(self.env.config.fork)
1112 } else {
1113 gas_cost::WARM_ADDRESS_ACCESS_COST
1114 };
1115 let basic_cost = mem_cost
1116 .saturating_add(access_cost)
1117 .saturating_add(value_cost);
1118 let gas_remaining = self.current_call_frame.gas_remaining;
1119
1120 if gas_remaining >= i64::try_from(basic_cost).unwrap_or(i64::MAX) {
1121 recorder.record_touched_address(target);
1122
1123 if is_delegation_7702 {
1124 let delegation_check = basic_cost
1125 .saturating_add(create_cost)
1126 .saturating_add(eip7702_gas_consumed);
1127 if gas_remaining >= i64::try_from(delegation_check).unwrap_or(i64::MAX) {
1128 recorder.record_touched_address(code_address);
1129 }
1130 }
1131 }
1132 }
1133
1134 #[expect(
1141 clippy::too_many_arguments,
1142 reason = "inlined for performance, many args needed"
1143 )]
1144 #[inline(always)]
1145 pub fn generic_call(
1146 &mut self,
1147 gas_limit: u64,
1148 value: U256,
1149 msg_sender: Address,
1150 to: Address,
1151 code_address: Address,
1152 should_transfer_value: bool,
1153 is_static: bool,
1154 calldata: Bytes,
1155 ret_offset: usize,
1156 ret_size: usize,
1157 bytecode: Code,
1158 is_delegation_7702: bool,
1159 new_account_charged: bool,
1160 ) -> Result<OpcodeResult, VMError> {
1161 self.current_call_frame.sub_return_data.clear();
1163
1164 if should_transfer_value && !value.is_zero() {
1166 let sender_balance = self.db.get_account(msg_sender)?.info.balance;
1167 if sender_balance < value {
1168 self.refund_new_account_state_gas(new_account_charged)?;
1170 self.early_revert_message_call(gas_limit, "OutOfFund".to_string())?;
1171 return Ok(OpcodeResult::Continue);
1172 }
1173 }
1174
1175 let new_depth = self
1177 .current_call_frame
1178 .depth
1179 .checked_add(1)
1180 .ok_or(InternalError::Overflow)?;
1181 if new_depth > 1024 {
1182 self.refund_new_account_state_gas(new_account_charged)?;
1183 self.early_revert_message_call(gas_limit, "MaxDepth".to_string())?;
1184 return Ok(OpcodeResult::Continue);
1185 }
1186
1187 if precompiles::is_precompile(&code_address, self.env.config.fork, self.vm_type)
1188 && !is_delegation_7702
1189 {
1190 if let Some(recorder) = self.db.bal_recorder.as_mut() {
1192 recorder.record_touched_address(code_address);
1193 }
1194
1195 let mut gas_remaining = gas_limit;
1196 let ctx_result = Self::execute_precompile(
1197 code_address,
1198 &calldata,
1199 gas_limit,
1200 &mut gas_remaining,
1201 self.env.config.fork,
1202 self.db.store.precompile_cache(),
1203 self.crypto,
1204 )?;
1205
1206 let call_frame = &mut self.current_call_frame;
1207
1208 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1210 if ctx_result.is_success() {
1211 call_frame.gas_remaining = (call_frame.gas_remaining as u64)
1212 .checked_add(
1213 gas_limit
1214 .checked_sub(ctx_result.gas_used)
1215 .ok_or(InternalError::Underflow)?,
1216 )
1217 .ok_or(InternalError::Overflow)?
1218 as i64;
1219 }
1220
1221 call_frame.memory.store_data(
1223 ret_offset,
1224 if ctx_result.output.len() >= ret_size {
1225 ctx_result
1226 .output
1227 .get(..ret_size)
1228 .ok_or(ExceptionalHalt::OutOfBounds)?
1229 } else {
1230 &ctx_result.output
1231 },
1232 )?;
1233 call_frame.sub_return_data = ctx_result.output.clone();
1234
1235 call_frame.stack.push(match &ctx_result.result {
1237 TxResult::Success => SUCCESS,
1238 TxResult::Revert(_) => FAIL,
1239 })?;
1240
1241 self.refund_new_account_state_gas(new_account_charged && !ctx_result.is_success())?;
1245
1246 if should_transfer_value && ctx_result.is_success() {
1248 self.transfer(msg_sender, to, value)?;
1249
1250 if self.env.config.fork >= Fork::Amsterdam && !value.is_zero() && msg_sender != to {
1253 let log = create_eth_transfer_log(msg_sender, to, value);
1254 self.substate.add_log(log);
1255 }
1256 }
1257
1258 self.tracer.exit_context(&ctx_result, false)?;
1259 } else {
1260 let bal_checkpoint = self.db.bal_recorder.as_ref().map(|r| r.checkpoint());
1262
1263 let mut stack = self.stack_pool.pop().unwrap_or_default();
1264 stack.clear();
1265
1266 let next_memory = self.current_call_frame.memory.next_memory();
1267
1268 let mut new_call_frame = CallFrame::new(
1269 msg_sender,
1270 to,
1271 code_address,
1272 bytecode,
1273 value,
1274 calldata,
1275 is_static,
1276 gas_limit,
1277 new_depth,
1278 should_transfer_value,
1279 false,
1280 ret_offset,
1281 ret_size,
1282 stack,
1283 next_memory,
1284 );
1285 new_call_frame.call_frame_backup.bal_checkpoint = bal_checkpoint;
1287 new_call_frame.state_gas_used_at_entry = self.state_gas_used;
1288 new_call_frame.new_account_state_gas_charged = new_account_charged;
1289
1290 self.add_callframe(new_call_frame);
1291
1292 if should_transfer_value {
1294 self.transfer(msg_sender, to, value)?;
1295 }
1296
1297 self.substate.push_backup();
1298
1299 if should_transfer_value
1303 && self.env.config.fork >= Fork::Amsterdam
1304 && !value.is_zero()
1305 && msg_sender != to
1306 {
1307 let log = create_eth_transfer_log(msg_sender, to, value);
1308 self.substate.add_log(log);
1309 }
1310 }
1311
1312 Ok(OpcodeResult::Continue)
1313 }
1314
1315 pub fn handle_state_backup(
1323 &mut self,
1324 ctx_result: &ContextResult,
1325 consume_backup: bool,
1326 ) -> Result<(), VMError> {
1327 if ctx_result.is_success() {
1328 self.substate.commit_backup();
1329 } else {
1330 self.substate.revert_backup();
1331 if consume_backup {
1332 self.restore_cache_state_consuming()?;
1333 } else {
1334 self.restore_cache_state()?;
1335 }
1336 }
1337
1338 Ok(())
1339 }
1340
1341 pub fn handle_return(&mut self, ctx_result: &ContextResult) -> Result<(), VMError> {
1345 self.handle_state_backup(ctx_result, true)?;
1348 let executed_call_frame = self.pop_call_frame()?;
1349
1350 if executed_call_frame.is_create {
1352 self.handle_return_create(executed_call_frame, ctx_result)?;
1353 } else {
1354 self.handle_return_call(executed_call_frame, ctx_result)?;
1355 }
1356
1357 Ok(())
1358 }
1359
1360 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1361 pub fn handle_return_call(
1362 &mut self,
1363 executed_call_frame: CallFrame,
1364 ctx_result: &ContextResult,
1365 ) -> Result<(), VMError> {
1366 let CallFrame {
1367 gas_limit,
1368 ret_offset,
1369 ret_size,
1370 memory: old_callframe_memory,
1371 frame_state_gas_spilled: child_frame_state_gas_spilled,
1372 call_frame_backup,
1373 stack,
1374 new_account_state_gas_charged,
1375 ..
1376 } = executed_call_frame;
1377
1378 #[cfg(not(target_arch = "riscv64"))]
1379 old_callframe_memory.clean_from_base();
1380
1381 #[cfg(target_arch = "riscv64")]
1382 old_callframe_memory.truncate_to_base();
1383
1384 let parent_call_frame = &mut self.current_call_frame;
1385
1386 let child_unused_gas = gas_limit
1388 .checked_sub(ctx_result.gas_used)
1389 .ok_or(InternalError::Underflow)?;
1390 parent_call_frame.gas_remaining = parent_call_frame
1391 .gas_remaining
1392 .checked_add(child_unused_gas as i64)
1393 .ok_or(InternalError::Overflow)?;
1394
1395 parent_call_frame.memory.store_data(
1397 ret_offset,
1398 if ctx_result.output.len() >= ret_size {
1399 ctx_result
1400 .output
1401 .get(..ret_size)
1402 .ok_or(ExceptionalHalt::OutOfBounds)?
1403 } else {
1404 &ctx_result.output
1405 },
1406 )?;
1407
1408 parent_call_frame.sub_return_data = ctx_result.output.clone();
1409
1410 match &ctx_result.result {
1412 TxResult::Success => {
1413 self.current_call_frame.stack.push(SUCCESS)?;
1414 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1415 self.current_call_frame.frame_state_gas_spilled = self
1421 .current_call_frame
1422 .frame_state_gas_spilled
1423 .checked_add(child_frame_state_gas_spilled)
1424 .ok_or(InternalError::Overflow)?;
1425 }
1426 TxResult::Revert(_) => {
1427 self.refund_new_account_state_gas(new_account_state_gas_charged)?;
1433 self.current_call_frame.stack.push(FAIL)?;
1434 }
1435 };
1436
1437 self.tracer.exit_context(ctx_result, false)?;
1438
1439 let mut stack = stack;
1440 stack.clear();
1441 self.stack_pool.push(stack);
1442
1443 Ok(())
1444 }
1445
1446 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1447 pub fn handle_return_create(
1448 &mut self,
1449 executed_call_frame: CallFrame,
1450 ctx_result: &ContextResult,
1451 ) -> Result<(), VMError> {
1452 let CallFrame {
1453 gas_limit,
1454 to,
1455 call_frame_backup,
1456 memory: old_callframe_memory,
1457 frame_state_gas_spilled: child_frame_state_gas_spilled,
1458 target_alive,
1459 stack,
1460 ..
1461 } = executed_call_frame;
1462
1463 #[cfg(not(target_arch = "riscv64"))]
1464 old_callframe_memory.clean_from_base();
1465
1466 #[cfg(target_arch = "riscv64")]
1467 old_callframe_memory.truncate_to_base();
1468
1469 let unused_gas = gas_limit
1471 .checked_sub(ctx_result.gas_used)
1472 .ok_or(InternalError::Underflow)?;
1473 self.current_call_frame.gas_remaining = self
1474 .current_call_frame
1475 .gas_remaining
1476 .checked_add(unused_gas as i64)
1477 .ok_or(InternalError::Overflow)?;
1478
1479 match ctx_result.result.clone() {
1481 TxResult::Success => {
1482 self.current_call_frame.stack.push(address_to_word(to))?;
1483 self.merge_call_frame_backup_with_parent(&call_frame_backup)?;
1484 self.current_call_frame.frame_state_gas_spilled = self
1490 .current_call_frame
1491 .frame_state_gas_spilled
1492 .checked_add(child_frame_state_gas_spilled)
1493 .ok_or(InternalError::Overflow)?;
1494 if self.env.config.fork >= Fork::Amsterdam && target_alive {
1503 self.credit_state_gas_refund(self.state_gas_new_account)?;
1504 }
1505 }
1506 TxResult::Revert(err) => {
1507 if self.env.config.fork >= Fork::Amsterdam {
1515 self.credit_state_gas_refund(self.state_gas_new_account)?;
1516 }
1517
1518 if err.is_revert_opcode() {
1520 self.current_call_frame.sub_return_data = ctx_result.output.clone();
1521 }
1522
1523 self.current_call_frame.stack.push(FAIL)?;
1524 }
1525 };
1526
1527 self.tracer.exit_context(ctx_result, false)?;
1528
1529 let mut stack = stack;
1530 stack.clear();
1531 self.stack_pool.push(stack);
1532
1533 Ok(())
1534 }
1535
1536 fn get_calldata(&mut self, offset: usize, size: usize) -> Result<Bytes, VMError> {
1537 self.current_call_frame.memory.load_range(offset, size)
1538 }
1539
1540 #[expect(clippy::as_conversions, reason = "remaining gas conversion")]
1541 fn early_revert_message_call(&mut self, gas_limit: u64, reason: String) -> Result<(), VMError> {
1542 let callframe = &mut self.current_call_frame;
1543
1544 callframe.gas_remaining = callframe
1546 .gas_remaining
1547 .checked_add(gas_limit as i64)
1548 .ok_or(InternalError::Overflow)?;
1549 callframe.stack.push(FAIL)?; self.tracer.exit_early(0, Some(reason))?;
1552 Ok(())
1553 }
1554}