Skip to main content

yevm_core/
exe.rs

1use yevm_base::math::U256;
2use yevm_base::math::lift;
3use yevm_misc::keccak256;
4
5use yevm_misc::buf::Buf;
6
7use crate::Fetch;
8use crate::Tx;
9use crate::evm::{CallMode, Context, Evm, Gas, StepResult};
10use crate::misc::{create_address, is_precompile};
11use crate::{Acc, Call, Error, Int, Result};
12use crate::{
13    call::Head,
14    chain::{Chain, fetch},
15    state::{Account, State},
16    trace::Event,
17};
18
19const MAX_CALL_DEPTH: usize = 1024;
20
21const BEACON_ROOTS: Acc = yevm_base::acc::acc("0x000f3df6d732807ef1319fb7b8bb8522d0beac02");
22const HISTORY_BUFFER_LENGTH: u64 = 8191;
23
24/// EIP-4788: write the parent beacon block root into the ring buffer before executing transactions.
25pub async fn pre_block(head: &Head, state: &mut impl State, chain: &impl Chain) -> Result<()> {
26    let Some(root) = head.parent_beacon_block_root else {
27        return Ok(());
28    };
29    fetch(Fetch::Account(BEACON_ROOTS), state, chain).await?;
30    let timestamp = head.timestamp.as_u64();
31    let slot = timestamp % HISTORY_BUFFER_LENGTH;
32    state.init(&BEACON_ROOTS, &Int::from(slot), Int::from(timestamp));
33    state.init(
34        &BEACON_ROOTS,
35        &Int::from(slot + HISTORY_BUFFER_LENGTH),
36        root,
37    );
38    Ok(())
39}
40const MAX_STEPS: u64 = 10_000_000;
41const MAX_CODE_SIZE: usize = 24_576;
42const CODE_DEPOSIT_GAS: i64 = 200;
43
44#[derive(Debug)]
45pub enum CallResult {
46    Done { status: Int, ret: Buf, gas: Gas },
47    Created { acc: Acc, code: Buf, gas: Gas },
48}
49
50impl CallResult {
51    pub fn gas(&self) -> &Gas {
52        match self {
53            Self::Done { gas, .. } => gas,
54            Self::Created { gas, .. } => gas,
55        }
56    }
57
58    pub fn gas_mut(&mut self) -> &mut Gas {
59        match self {
60            Self::Done { gas, .. } => gas,
61            Self::Created { gas, .. } => gas,
62        }
63    }
64}
65
66pub struct Executor {
67    pub call: Call,
68    pub callstack: Vec<CallFrame>,
69    /// Effective gas price for GASPRICE opcode (min(max_fee, base_fee + priority) for EIP-1559).
70    effective_gas_price: Int,
71    pub fetches: usize,
72
73    #[cfg(not(target_arch = "wasm32"))]
74    pub fetching: std::time::Duration,
75}
76
77pub struct CallFrame {
78    pub call: Call,
79    pub evm: Evm,
80    pub ctx: Context,
81    pub checkpoint: usize,
82    /// Return-data target (ret_offset, ret_size) for the parent frame's CALL/STATICCALL.
83    pub target: (usize, usize),
84    /// True when this frame is a CREATE/CREATE2, false for CALL/STATICCALL/etc.
85    /// Cannot rely on `call.to.is_zero()` because a plain CALL to address 0x0 is valid.
86    pub is_create: bool,
87}
88
89pub fn intrinsic(
90    call: &Call,
91    tx: &Tx,
92    head: &Head,
93    state: &mut impl State,
94) -> Result<(i64, i64, Int)> {
95    let mut total = 21_000i64;
96    let mut floor = 21_000i64;
97    let has_code = call
98        .to
99        .and_then(|to| state.code(&to))
100        .is_some_and(|(c, _)| !c.0.is_empty());
101    let is_create = call.is_create() && !has_code;
102    if is_create {
103        total += 32_000;
104        // EIP-3860: 2 gas per 32-byte word of initcode
105        let initcode_cost = 2 * ((call.data.0.len() as i64 + 31) / 32);
106        total += initcode_cost;
107    }
108    let zeroes = call.data.0.iter().filter(|b| **b == 0).count();
109    let non_zeroes = call.data.0.len() - zeroes;
110    total += (zeroes * 4 + non_zeroes * 16) as i64;
111    // EIP-7623: floor calldata cost (TOTAL_COST_FLOOR_PER_TOKEN = 10)
112    let tokens = (zeroes + non_zeroes * 4) as i64;
113    floor += 10 * tokens;
114
115    // EIP-2929: pre-warm sender, target, coinbase, and precompile addresses.
116    // For CREATE (to==0x0) there is no target; do not warm 0x0.
117    state.warm_acc(&call.by);
118    if let Some(to) = call.to {
119        state.warm_acc(&to);
120    }
121    state.warm_acc(&head.coinbase);
122    for i in 1u64..=0xa {
123        state.warm_acc(&Acc::from(i));
124    }
125    state.warm_acc(&Acc::from(0x100u64)); // p256verify precompile
126
127    // EIP-2930: access list gas (2400/address + 1900/storage key)
128    for (acc, keys) in tx
129        .access_list
130        .iter()
131        .map(|item| (item.address, &item.storage_keys))
132    {
133        let al_cost = 2_400 + 1_900 * keys.len() as i64;
134        total += al_cost;
135        state.warm_acc(&acc);
136        for key in keys {
137            state.warm_key(&acc, key);
138        }
139    }
140
141    // EIP-1559: gasPrice / maxFeePerGas must be >= baseFee.
142    let lt = lift(|[a, b]| if a < b { U256::ONE } else { U256::ZERO });
143    if tx.max_fee_per_gas.is_zero() {
144        // Legacy tx: gasPrice must be >= baseFee
145        if !lt([tx.gas_price, head.base_fee]).is_zero() {
146            return Err(Error::MaxFeeLessThanBaseFee);
147        }
148    } else {
149        // EIP-1559 tx: maxFeePerGas must be >= baseFee
150        if !lt([tx.max_fee_per_gas, head.base_fee]).is_zero() {
151            return Err(Error::MaxFeeLessThanBaseFee);
152        }
153    }
154
155    // EIP-1559: effective gas price = min(max_fee_per_gas, base_fee + max_priority_fee_per_gas).
156    // For legacy tx (max_fee_per_gas == 0) use gas_price directly.
157    let effective_gas_price = if tx.max_fee_per_gas.is_zero() {
158        tx.gas_price
159    } else {
160        let min = lift(|[a, b]| a.min(b));
161        let sum = lift(|[a, b]| a + b);
162        min([
163            tx.max_fee_per_gas,
164            sum([head.base_fee, tx.max_priority_fee_per_gas]),
165        ])
166    };
167
168    // Check for overflow in effective_gas_price * call.gas
169    let mul_overflows = lift(|[a, b]| {
170        if a.checked_mul(b).is_none() {
171            U256::ONE
172        } else {
173            U256::ZERO
174        }
175    });
176    if !mul_overflows([effective_gas_price, Int::from(call.gas)]).is_zero() {
177        return Err(Error::GasLimitPriceProductOverflow);
178    }
179
180    // Upfront gas deduction (YP §6.1): sender pays gas_limit × effective_gas_price.
181    // For EIP-1559 tx, balance check uses max_fee_per_gas (sender must afford worst case).
182    let mul = lift(|[a, b]| a * b);
183    let sub = lift(|[a, b]| a - b);
184    let add = lift(|[a, b]| a + b);
185    let gt = lift(|[a, b]| if a > b { U256::ONE } else { U256::ZERO });
186    let max_gas_price = if tx.max_fee_per_gas.is_zero() {
187        effective_gas_price
188    } else {
189        tx.max_fee_per_gas
190    };
191    let upfront_check = mul([Int::from(call.gas), max_gas_price]);
192    let upfront = mul([Int::from(call.gas), effective_gas_price]);
193
194    // EIP-4844: blob gas cost = num_blobs × GAS_PER_BLOB × actual_blob_base_fee (never refunded).
195    const GAS_PER_BLOB: u64 = 0x20000;
196    let blob_gas_cost = if let Some(_excess) = head.excess_blob_gas {
197        // TODO: proper blob handling
198        // let fee = crate::call::blob_base_fee(head.number.as_u64(), excess);
199        // mul([Int::from(tx.blob_versioned_hashes.len() as u64 * GAS_PER_BLOB), fee])
200        Int::from(tx.blob_versioned_hashes.len() as u64 * GAS_PER_BLOB)
201    } else {
202        Int::ZERO
203    };
204
205    let total_cost = add([upfront_check, call.eth]);
206    let balance = state.balance(&call.by).unwrap_or_default();
207    if !gt([total_cost, balance]).is_zero() {
208        // TODO: FIXME: false positives detected
209        // return Err(Error::InsufficientFunds);
210    }
211    state.set_value(&call.by, sub([balance, add([upfront, blob_gas_cost])]));
212
213    // EIP-7702: authorization list gas (PER_EMPTY_ACCOUNT_COST per tuple, matches revm).
214    // EIP-7623 floor is only 21_000 + 10 * tokens (calc_tx_floor_cost); it must NOT include
215    // authorization list gas — see revm `calculate_initial_tx_gas` (PRAGUE branch) and
216    // `eip7623_check_gas_floor` (floor_gas excludes auth, initial_gas includes it).
217    let auth_cost = 25_000 * tx.authorization_list.len() as i64;
218    total += auth_cost;
219    Ok((total, floor, effective_gas_price))
220}
221
222pub fn finalized(
223    call: &Call,
224    tx: &Tx,
225    head: &Head,
226    effective_gas_price: Int,
227    result: &CallResult,
228    state: &mut impl State,
229    floor: i64,
230) -> i64 {
231    // Settle gas: return unused gas to sender; coinbase receives the priority fee tip.
232    let gas = result.gas();
233
234    let effective_refund = gas.refund.min(gas.spent / 5);
235    // EIP-7623: floor gas cost for calldata-heavy transactions
236    let final_gas = (gas.spent - effective_refund).max(0).max(floor) as u64;
237    let returned_gas = (gas.limit.max(0) as u64).saturating_sub(final_gas);
238    let mul = lift(|[a, b]| a * b);
239    let sub = lift(|[a, b]| a - b);
240    let add = lift(|[a, b]| a + b);
241
242    // Return (gas_limit - net_gas) × effective_gas_price to sender.
243    let returned_cost = mul([Int::from(returned_gas), effective_gas_price]);
244    let balance = state.balance(&call.by).unwrap_or_default();
245    state.set_value(&call.by, add([balance, returned_cost]));
246
247    // Priority fee to coinbase: net_gas × min(max_priority_fee, effective_gas_price - base_fee).
248    let priority_fee = if tx.max_fee_per_gas.is_zero() {
249        sub([effective_gas_price, head.base_fee])
250    } else {
251        lift(|[a, b]| a.min(b))([
252            tx.max_priority_fee_per_gas,
253            sub([effective_gas_price, head.base_fee]),
254        ])
255    };
256
257    let tip = mul([Int::from(final_gas), priority_fee]);
258    if !tip.is_zero() {
259        let balance = state.balance(&head.coinbase).unwrap_or_default();
260        state.set_value(&head.coinbase, add([balance, tip]));
261    }
262
263    let base_burn = mul([Int::from(final_gas), head.base_fee]);
264    state.emit(Event::Fee(
265        call.by,
266        head.coinbase,
267        base_burn,
268        tip,
269        final_gas,
270    ));
271
272    final_gas as i64
273}
274
275pub fn transfer(call: &Call, mode: &CallMode, state: &mut impl State) {
276    if call.eth.is_zero() {
277        return;
278    }
279    let to = mode.created().or(call.to);
280    if let Some(to) = to {
281        let sub = lift(|[a, b]| a - b);
282        let add = lift(|[a, b]| a + b);
283        let by0 = state.balance(&call.by).unwrap_or_default();
284        state.set_value(&call.by, sub([by0, call.eth]));
285        let to0 = state.balance(&to).unwrap_or_default();
286        state.set_value(&to, add([to0, call.eth]));
287        state.emit(Event::Move(call.by, to, call.eth));
288    }
289}
290
291impl Executor {
292    pub fn new(call: Call) -> Self {
293        Self {
294            call,
295            callstack: vec![],
296            effective_gas_price: Int::ZERO,
297            fetches: 0,
298
299            #[cfg(not(target_arch = "wasm32"))]
300            fetching: std::time::Duration::ZERO,
301        }
302    }
303
304    pub async fn run(
305        &mut self,
306        mut tx: Tx,
307        head: Head,
308        state: &mut impl State,
309        chain: &impl Chain,
310    ) -> Result<CallResult> {
311        if !self.callstack.is_empty() {
312            return Err(eyre::eyre!("inconsistent state: call stack empty").into());
313        }
314        for acc in [&self.call.by, &head.coinbase] {
315            if state.acc(acc).is_none() {
316                fetch(Fetch::Account(*acc), state, chain).await?;
317                state.warm_acc(acc);
318            }
319        }
320        if let Some(acc) = self.call.to.as_ref()
321            && state.acc(acc).is_none()
322        {
323            fetch(Fetch::Account(*acc), state, chain).await?;
324            state.warm_acc(acc);
325        }
326
327        if tx.chain_id.is_zero() {
328            tx.chain_id = state.get_chain_id().into();
329            if tx.chain_id.is_zero() {
330                return Err(Error::UndefinedChainId);
331            }
332        }
333
334        // Pre-transaction validation checks
335
336        // EIP-1559: max_fee_per_gas must cover base_fee
337        if !tx.max_fee_per_gas.is_zero() && tx.max_fee_per_gas < head.base_fee {
338            return Err(Error::MaxFeeLessThanBaseFee);
339        }
340
341        // EIP-1559: max_priority_fee must not exceed max_fee
342        if !tx.max_fee_per_gas.is_zero() && tx.max_priority_fee_per_gas > tx.max_fee_per_gas {
343            return Err(Error::PriorityGreaterThanMaxFee);
344        }
345
346        // Gas limit must not exceed block gas limit
347        let gt = lift(|[a, b]| if a > b { U256::ONE } else { U256::ZERO });
348        if !gt([Int::from(self.call.gas), head.gas_limit]).is_zero() {
349            return Err(Error::GasAllowanceExceeded);
350        }
351
352        // EIP-3607: sender must be an EOA (no code)
353        if let Some(acc) = state.acc(&self.call.by)
354            && !(state.auth(&self.call.by).is_some() || acc.code.0.0.is_empty())
355        {
356            // TODO: FIXME: false positives
357            // return Err(Error::SenderNotEOA);
358        }
359
360        let has_code = self
361            .call
362            .to
363            .and_then(|to| state.code(&to))
364            .is_some_and(|(c, _)| !c.0.is_empty())
365            && self.call.to.and_then(|to| state.auth(&to)).is_none();
366
367        // CREATE address uses sender nonce *before* the tx-level increment (YP / EIP-161).
368        let mode = if self.call.is_create() && !has_code {
369            let nonce = state.nonce(&self.call.by).unwrap_or_default();
370            let created = create_address(&self.call.by, nonce.as_u64());
371            CallMode::Create(created)
372        } else {
373            CallMode::Call(0, 0)
374        };
375
376        // Tx consumes one sender nonce before execution; EIP-7702 auth checks need this first
377        // when authority == sender (post-increment on-chain nonce vs signed tuple nonce).
378        state.inc_nonce(&self.call.by, Int::ONE);
379
380        let eip7702_refund =
381            crate::eip7702::apply_authorization_list(&tx, tx.chain_id.as_u64(), state, chain)
382                .await?;
383
384        let (intrinsic, floor, effective_gas_price) = intrinsic(&self.call, &tx, &head, state)?;
385        self.effective_gas_price = effective_gas_price;
386        if (self.call.gas as i64) < intrinsic {
387            return Err(Error::GasTooLow {
388                have: self.call.gas,
389                want: intrinsic as u64,
390            });
391        }
392
393        // prepare() takes a checkpoint to be able to revert,
394        // so all state mutations must come AFTER that to be included.
395        let mut frame = prepare(
396            head.clone(),
397            self.call.clone(),
398            mode,
399            None,
400            tx.chain_id.to(),
401            effective_gas_price,
402            tx.blob_versioned_hashes.clone(),
403            state,
404            chain,
405        )
406        .await?;
407        // For top-level CREATE: collision check + initialize with nonce=1 (EIP-161).
408        // Done AFTER the checkpoint so it's reverted on init-code failure.
409        if let CallMode::Create(created) = mode {
410            let existing_nonce = state.nonce(&created).unwrap_or(Int::ZERO);
411            let has_code = state.code(&created).is_some_and(|(c, _)| !c.0.is_empty());
412            if !existing_nonce.is_zero() || has_code {
413                // Collision: drain gas, revert, return failure
414                frame.evm.gas.drain();
415                let gas = frame.evm.gas;
416                state.revert_to(frame.checkpoint);
417                let result = CallResult::Done {
418                    status: Int::ZERO,
419                    ret: vec![].into(),
420                    gas,
421                };
422                let mut result = result;
423                result.gas_mut().refund += eip7702_refund;
424                let gas_final = finalized(
425                    &self.call,
426                    &tx,
427                    &head,
428                    effective_gas_price,
429                    &result,
430                    state,
431                    floor,
432                );
433                result.gas_mut().finalized = gas_final;
434                state.apply();
435                return Ok(result);
436            }
437            // Preserve any pre-existing balance at the CREATE address
438            let existing_balance = state.balance(&created).unwrap_or(Int::ZERO);
439            state.create(
440                created,
441                Account {
442                    value: existing_balance,
443                    nonce: Int::ONE,
444                    code: (Buf::default(), Int::ZERO),
445                },
446            );
447            // EIP-2929: add newly created address to accessed_addresses
448            state.warm_acc(&created);
449        }
450        transfer(&self.call, &mode, state);
451        let _ = frame.evm.gas_charge(intrinsic);
452
453        // Top-level call to a precompile: run inline, skip the step loop.
454        let call_to_is_precompile = self
455            .call
456            .to
457            .map(|to| is_precompile(&to))
458            .unwrap_or_default();
459        if !frame.is_create && call_to_is_precompile {
460            let id = self.call.to.expect("verified precompile address").as_u64();
461            let (ok, out, gas_used) =
462                crate::pre::run(id, &self.call.data.0, frame.evm.gas_remaining());
463            let _ = frame.evm.gas_charge(gas_used);
464            frame.evm.apply(state);
465
466            let status = if ok { Int::ONE } else { Int::ZERO };
467            if ok {
468                state.emit(Event::Return(out.clone().into(), gas_used.max(0) as u64));
469            } else {
470                state.revert_to(frame.checkpoint);
471                state.emit(Event::Revert(vec![].into(), gas_used.max(0) as u64));
472            }
473
474            let mut result = CallResult::Done {
475                status,
476                ret: out.into(),
477                gas: frame.evm.gas,
478            };
479            result.gas_mut().refund += eip7702_refund;
480            let gas_final = finalized(
481                &self.call,
482                &tx,
483                &head,
484                effective_gas_price,
485                &result,
486                state,
487                floor,
488            );
489            result.gas_mut().finalized = gas_final;
490            state.apply();
491            return Ok(result);
492        }
493
494        state.emit(Event::Call(self.call.clone(), mode));
495        self.callstack.push(frame);
496
497        let mut result: Option<CallResult> = None;
498        let mut last_popped_checkpoint: Option<usize> = None;
499        let mut steps: u64 = 0;
500
501        while let Some(this) = self.callstack.last_mut() {
502            state.set_depth(this.ctx.depth + 1);
503            steps += 1;
504            if steps > MAX_STEPS {
505                this.evm.gas.drain();
506                let gas = this.evm.gas;
507                let checkpoint = this.checkpoint;
508                state.revert_to(checkpoint);
509                self.callstack.clear();
510                return Ok(CallResult::Done {
511                    status: Int::ZERO,
512                    ret: vec![].into(),
513                    gas,
514                });
515            }
516            // Process a result returned from a completed subcall
517            if let Some(call_result) = result.take() {
518                match call_result {
519                    CallResult::Done { status, ret, gas } => {
520                        // Revert failed child's state (value transfer, etc.) when call returns 0
521                        if status.is_zero() {
522                            if let Some(cp) = last_popped_checkpoint.take() {
523                                state.revert_to(cp);
524                            }
525                        } else {
526                            last_popped_checkpoint = None; // success, discard stale checkpoint
527                        }
528                        let _ = this.evm.push(status);
529                        this.evm.ret = ret.clone().into_vec();
530                        // EIP-211: return data (success or revert) is written to memory at ret_offset
531                        let (offset, size) = this.target;
532                        if size > 0 {
533                            let _ = this.evm.mem_put(offset, size, ret.as_slice());
534                        }
535                        // Return all unused child gas to the parent, regardless of
536                        // success or failure.  The 2300 stipend is a free subsidy —
537                        // any portion the child did not consume flows back to the
538                        // caller (this matches geth / revm behaviour).
539                        let return_gas = (gas.limit - gas.spent).max(0);
540                        this.evm.gas.spent -= return_gas;
541                        // Only propagate refund on success; reverted refunds are discarded.
542                        if !status.is_zero() {
543                            this.evm.gas.refund += gas.refund;
544                        }
545                        this.evm.apply(state);
546                        this.evm.pc += 1;
547                        this.target = (0, 0);
548                        result = None;
549                    }
550                    CallResult::Created {
551                        acc: addr,
552                        code,
553                        gas,
554                    } => {
555                        last_popped_checkpoint = None; // success
556
557                        let hash = Int::from(keccak256(code.as_slice()).as_ref());
558                        state.set_code(&addr, code, hash);
559
560                        let _ = this.evm.push(addr.to());
561                        let return_gas = (gas.limit - gas.spent).max(0);
562                        // eprintln!("CREATED: depth={} child_limit={} child_spent={} child_refund={} return_gas={} parent_spent_before={} parent_spent_after={}",
563                        //     this.ctx.depth, gas.limit, gas.spent, gas.refund, return_gas, this.evm.gas.spent, this.evm.gas.spent - return_gas);
564                        this.evm.gas.spent -= return_gas;
565                        this.evm.gas.refund += gas.refund;
566                        this.evm.apply(state);
567                        this.evm.pc += 1;
568                        this.evm.ret.clear();
569                    }
570                }
571            }
572
573            match this.evm.step(&this.ctx, &this.call, state)? {
574                StepResult::Ok => {
575                    continue;
576                }
577                StepResult::End => {
578                    this.evm.apply(state);
579
580                    // Do not emit synthetic STOP
581
582                    let is_create = this.is_create;
583                    let gas = this.evm.gas;
584                    result = Some(if is_create {
585                        CallResult::Created {
586                            acc: this.ctx.this,
587                            code: vec![].into(),
588                            gas,
589                        }
590                    } else {
591                        CallResult::Done {
592                            status: Int::ONE,
593                            ret: vec![].into(),
594                            gas,
595                        }
596                    });
597                    last_popped_checkpoint = Some(this.checkpoint);
598                    self.callstack.pop();
599                }
600                StepResult::Call(call, mode) => {
601                    state.emit(Event::Call(call.clone(), mode));
602                    this.evm.apply(state);
603                    let call_to_is_precompile =
604                        call.to.map(|to| is_precompile(&to)).unwrap_or_default();
605                    if call_to_is_precompile {
606                        // Load precompile account from chain so its on-chain balance
607                        // is reflected in state (mirrors what prepare() does for regular calls).
608                        if let Some(to) = call.to
609                            && state.acc(&to).is_none()
610                        {
611                            fetch(Fetch::Account(to), state, chain).await?;
612                        }
613
614                        // EIP-211: clear return data before new call
615                        this.evm.ret.clear();
616
617                        // Precompile runs inline. Replace child-gas reservation with actual used
618                        // (avoids OOG when child_gas > remaining); keep access cost.
619                        let id = call.to.expect("verified precompile address").as_u64();
620                        let (ok, out, gas_used) =
621                            crate::pre::run(id, &call.data.0, call.gas as i64);
622                        this.evm.ret = out.clone();
623                        this.evm.pending_gas_charge -= call.gas as i64;
624                        this.evm.pending_gas_charge += gas_used;
625
626                        // Value transfer only on success — failure reverts all child-frame
627                        // state changes, including the value transfer.
628                        if ok
629                            && !call.eth.is_zero()
630                            && matches!(mode, CallMode::Call(..))
631                            && let Some(call_to) = call.to
632                        {
633                            let sub = lift(|[a, b]| a - b);
634                            let add = lift(|[a, b]| a + b);
635                            let by0 = state.balance(&call.by).unwrap_or_default();
636                            let to0 = state.balance(&call_to).unwrap_or_default();
637                            if call.by != call_to {
638                                state.set_value(&call.by, sub([by0, call.eth]));
639                                state.set_value(&call_to, add([to0, call.eth]));
640                            }
641                        }
642
643                        let status = if ok { Int::ONE } else { Int::ZERO };
644                        if ok {
645                            let d = state.get_depth();
646                            state.set_depth(d + 1);
647                            state.emit(Event::Return(out.clone().into(), gas_used.max(0) as u64));
648                            state.set_depth(d);
649                        } else {
650                            let d = state.get_depth();
651                            state.set_depth(d + 1);
652                            state.emit(Event::Revert(vec![].into(), gas_used.max(0) as u64));
653                            state.set_depth(d);
654                        }
655                        let (ret_offset, ret_size) = mode.target().unwrap_or_default();
656                        this.evm.apply(state);
657                        let _ = this.evm.push(status);
658                        if !status.is_zero() && ret_size > 0 {
659                            let n = ret_size.min(out.len());
660                            let _ = this.evm.mem_put(ret_offset, n, &out[..n]);
661                        }
662                        this.evm.apply(state);
663                        this.evm.pc += 1;
664                        continue;
665                    }
666
667                    let is_create = matches!(mode, CallMode::Create(_) | CallMode::Create2(_));
668
669                    // For CREATE: perform pre-checkpoint checks, then increment nonce.
670                    // Per EVM spec, nonce is incremented before the snapshot so it survives
671                    // collision reverts, but NOT depth or insufficient-balance failures.
672                    if let Some(created) = mode.created() {
673                        let creator = call.by;
674
675                        // Depth check before nonce increment
676                        if this.ctx.depth + 1 > MAX_CALL_DEPTH {
677                            // Return child gas (not consumed on depth failure)
678                            this.evm.gas.spent -= call.gas as i64;
679                            this.evm.apply(state);
680                            let _ = this.evm.push(Int::ZERO);
681                            this.evm.apply(state);
682                            this.evm.ret = vec![];
683                            this.evm.pc += 1;
684                            this.target = (0, 0);
685                            continue;
686                        }
687
688                        // Balance check before nonce increment
689                        if !call.eth.is_zero() {
690                            let gte = lift(|[a, b]| if a >= b { U256::ONE } else { U256::ZERO });
691                            let by0 = state.balance(&creator).unwrap_or_default();
692                            if gte([by0, call.eth]).is_zero() {
693                                // Return child gas (not consumed on balance failure)
694                                this.evm.gas.spent -= call.gas as i64;
695                                this.evm.apply(state);
696                                let _ = this.evm.push(Int::ZERO);
697                                this.evm.apply(state);
698                                this.evm.ret = vec![];
699                                this.evm.pc += 1;
700                                this.target = (0, 0);
701                                continue;
702                            }
703                        }
704
705                        // EIP-2681: nonce overflow check — CREATE fails if nonce >= 2^64 - 1
706                        let nonce_max = Int::from(u64::MAX);
707                        let creator_nonce = state.nonce(&creator).unwrap_or(Int::ZERO);
708                        if creator_nonce >= nonce_max {
709                            this.evm.gas.spent -= call.gas as i64;
710                            this.evm.apply(state);
711                            let _ = this.evm.push(Int::ZERO);
712                            this.evm.apply(state);
713                            this.evm.ret = vec![];
714                            this.evm.pc += 1;
715                            this.target = (0, 0);
716                            continue;
717                        }
718
719                        // Increment nonce BEFORE checkpoint so collision-reverts don't undo it
720                        state.inc_nonce(&creator, Int::ONE);
721                        // EIP-2929: created address is warmed BEFORE checkpoint (survives revert)
722                        state.warm_acc(&created);
723                    }
724
725                    let checkpoint = state.checkpoint();
726                    this.target = mode.target().unwrap_or_default();
727
728                    if let Some(created) = mode.created() {
729                        let creator = call.by;
730
731                        // Fetch before collision check — account may exist on-chain but not in cache
732                        if state.acc(&created).is_none() {
733                            fetch(Fetch::Account(created), state, chain).await?;
734                        }
735
736                        // Collision check: existing nonce or code at derived address
737                        let existing_nonce = state.nonce(&created).unwrap_or(Int::ZERO);
738                        let has_code = state.code(&created).is_some_and(|(c, _)| !c.0.is_empty());
739                        if !existing_nonce.is_zero() || has_code {
740                            state.revert_to(checkpoint);
741                            this.evm.apply(state);
742                            let _ = this.evm.push(Int::ZERO);
743                            this.evm.apply(state);
744                            this.evm.ret = vec![];
745                            this.evm.pc += 1;
746                            this.target = (0, 0);
747                            continue;
748                        }
749
750                        // Create account with nonce=1 (EIP-161), preserving pre-existing balance
751                        let existing_balance = state.balance(&created).unwrap_or(Int::ZERO);
752                        state.create(
753                            created,
754                            Account {
755                                value: existing_balance,
756                                nonce: Int::ONE,
757                                code: (Buf::default(), Int::ZERO),
758                            },
759                        );
760
761                        // Value transfer (balance already verified above)
762                        if !call.eth.is_zero() {
763                            let sub = lift(|[a, b]| a - b);
764                            let add = lift(|[a, b]| a + b);
765                            let by0 = state.balance(&creator).unwrap_or_default();
766                            state.set_value(&creator, sub([by0, call.eth]));
767                            let to0 = state.balance(&created).unwrap_or_default();
768                            state.set_value(&created, add([to0, call.eth]));
769                        }
770                    }
771
772                    // EIP-211: clear return data buffer when making a new call
773                    this.evm.ret.clear();
774
775                    let mut frame = prepare(
776                        head.clone(),
777                        call.clone(),
778                        mode,
779                        Some(&this.ctx),
780                        tx.chain_id.to(),
781                        self.effective_gas_price,
782                        tx.blob_versioned_hashes.clone(),
783                        state,
784                        chain,
785                    )
786                    .await?;
787                    // Use the outer checkpoint (set before state.create / value transfer)
788                    // so that reverting the child frame undoes create + value transfer.
789                    frame.checkpoint = checkpoint;
790                    if frame.ctx.depth > MAX_CALL_DEPTH {
791                        state.revert_to(checkpoint);
792                        // Return child gas (not consumed on depth failure)
793                        this.evm.gas.spent -= call.gas as i64;
794                        this.evm.apply(state);
795                        let _ = this.evm.push(Int::ZERO);
796                        this.evm.apply(state);
797                        this.evm.ret = vec![];
798                        this.evm.pc += 1;
799                        this.target = (0, 0);
800                        continue;
801                    }
802
803                    // ETH value transfer for CALL and CALLCODE
804                    if !is_create
805                        && !call.eth.is_zero()
806                        && matches!(mode, CallMode::Call(..) | CallMode::CallCode(..))
807                    {
808                        let by = call.by;
809                        let by0 = state.balance(&by).unwrap_or_default();
810
811                        let gte = lift(|[a, b]| if a >= b { U256::ONE } else { U256::ZERO });
812                        if gte([by0, call.eth]).is_zero() {
813                            state.revert_to(checkpoint);
814                            // Return child gas (not consumed on balance failure)
815                            this.evm.gas.spent -= call.gas as i64;
816                            this.evm.apply(state);
817                            let _ = this.evm.push(Int::ZERO);
818                            this.evm.apply(state);
819                            this.evm.ret = vec![];
820                            this.evm.pc += 1;
821                            this.target = (0, 0);
822                            continue;
823                        }
824
825                        // CALLCODE: value stays with self (by == this), no actual transfer
826                        // CALL: value goes from caller to callee
827                        if matches!(mode, CallMode::Call(..))
828                            && let Some(to) = call.to
829                        {
830                            let add = lift(|[a, b]| a + b);
831                            let sub = lift(|[a, b]| a - b);
832                            let to0 = state.balance(&to).unwrap_or_default();
833                            if to != by {
834                                state.set_value(&by, sub([by0, call.eth]));
835                                state.set_value(&to, add([to0, call.eth]));
836                            }
837                        }
838                    }
839                    self.callstack.push(frame);
840                }
841                StepResult::Return(ret) => {
842                    state.emit(Event::Return(
843                        ret.clone().into(),
844                        this.evm.gas.spent.max(0) as u64,
845                    ));
846                    let is_create = this.is_create;
847                    result = Some(if is_create {
848                        let deploy_cost = CODE_DEPOSIT_GAS * ret.len() as i64;
849                        // EIP-3541: reject code starting with 0xEF
850                        let starts_with_ef = ret.first() == Some(&0xEF);
851                        if ret.len() > MAX_CODE_SIZE
852                            || starts_with_ef
853                            || this.evm.gas_remaining() < deploy_cost
854                        {
855                            this.evm.gas.drain();
856                            state.revert_to(this.checkpoint);
857                            CallResult::Done {
858                                status: Int::ZERO,
859                                ret: vec![].into(),
860                                gas: this.evm.gas,
861                            }
862                        } else {
863                            this.evm.gas.spent += deploy_cost;
864                            CallResult::Created {
865                                acc: this.ctx.this,
866                                code: ret.into(),
867                                gas: this.evm.gas,
868                            }
869                        }
870                    } else {
871                        CallResult::Done {
872                            status: Int::ONE,
873                            ret: ret.into(),
874                            gas: this.evm.gas,
875                        }
876                    });
877                    self.callstack.pop();
878                }
879                StepResult::Revert(ret) => {
880                    state.emit(Event::Revert(
881                        ret.clone().into(),
882                        this.evm.gas.spent.max(0) as u64,
883                    ));
884                    state.revert_to(this.checkpoint);
885                    let mut gas = this.evm.gas;
886                    gas.refund = 0;
887                    result = Some(CallResult::Done {
888                        status: Int::ZERO,
889                        ret: ret.into(),
890                        gas,
891                    });
892                    self.callstack.pop();
893                }
894                StepResult::Halt(reason) => {
895                    state.emit(Event::Halt(reason, this.evm.gas.limit.max(0) as u64));
896                    this.evm.apply(state);
897                    this.evm.gas.drain();
898                    state.revert_to(this.checkpoint);
899                    result = Some(CallResult::Done {
900                        status: Int::ZERO,
901                        ret: vec![].into(),
902                        gas: this.evm.gas,
903                    });
904                    self.callstack.pop();
905                }
906                StepResult::Fetch(f) => {
907                    #[cfg(not(target_arch = "wasm32"))]
908                    let now = std::time::Instant::now();
909
910                    fetch(f, state, chain).await?;
911
912                    #[cfg(not(target_arch = "wasm32"))]
913                    {
914                        let elapsed = now.elapsed();
915                        self.fetching += elapsed;
916                    }
917
918                    self.fetches += 1;
919                    this.evm.reset();
920                }
921            }
922        }
923
924        let mut result =
925            result.ok_or::<Error>(eyre::eyre!("inconsistent state: call result missing").into())?;
926
927        // Revert top-level state when call returns 0 or CREATE returns zero address
928        let should_revert = match &result {
929            CallResult::Done { status, .. } => status.is_zero(),
930            CallResult::Created { acc, .. } => acc == &Acc::ZERO,
931        };
932        if should_revert && let Some(cp) = last_popped_checkpoint.take() {
933            state.revert_to(cp);
934        }
935
936        // For top-level CREATE, store the deployed bytecode into the new account.
937        if let CallResult::Created {
938            acc: addr,
939            ref code,
940            ..
941        } = result
942            && !code.0.is_empty()
943        {
944            let hash = Int::from(keccak256(code.as_slice()).as_ref());
945            state.set_code(&addr, code.clone(), hash);
946        }
947
948        state.set_depth(0);
949
950        result.gas_mut().refund += eip7702_refund;
951        let gas_final = finalized(
952            &self.call,
953            &tx,
954            &head,
955            effective_gas_price,
956            &result,
957            state,
958            floor,
959        );
960        result.gas_mut().finalized = gas_final;
961
962        state.apply();
963        Ok(result)
964    }
965}
966
967#[allow(clippy::too_many_arguments)]
968async fn prepare(
969    head: Head,
970    mut call: Call,
971    mode: CallMode,
972    ctx: Option<&Context>,
973    chain_id: Int,
974    gas_price: Int,
975    blob_hashes: Vec<Int>,
976    state: &mut impl State,
977    chain: &impl Chain,
978) -> Result<CallFrame> {
979    let is_create = matches!(mode, CallMode::Create(_) | CallMode::Create2(_));
980    let code = if is_create {
981        std::mem::take(&mut call.data)
982    } else {
983        let call_to = call.to.expect("checked non-create call");
984        if let Some((code, _)) = state.code(&call_to) {
985            code
986        } else {
987            fetch(Fetch::Account(call_to), state, chain).await?;
988            if let Some(account) = state.acc(&call_to) {
989                account.code.0.clone()
990            } else {
991                Buf::default()
992            }
993        }
994    };
995    // EIP-7702: resolve delegation after code is loaded.
996    // Revm's `load_account_delegated` marks both the delegated account and the implementation
997    // address warm when resolving code for a frame; mirror that so *CALL does not charge cold
998    // access for an address already loaded for execution (see revm-context JournalInner).
999    let code = if let Some(delegate) = call.to.and_then(|to| state.auth(&to)) {
1000        if let Some((code, _)) = state.code(&delegate) {
1001            state.warm_acc(&delegate);
1002            code
1003        } else {
1004            fetch(Fetch::Account(delegate), state, chain).await?;
1005            if let Some(account) = state.acc(&delegate) {
1006                state.warm_acc(&delegate);
1007                account.code.0.clone()
1008            } else {
1009                Buf::default()
1010            }
1011        }
1012    } else {
1013        code
1014    };
1015    // GASPRICE opcode returns effective gas price (EIP-1559: min(max_fee, base_fee + priority))
1016    let evm = Evm::new(
1017        head,
1018        code.into_vec(),
1019        call.gas,
1020        chain_id,
1021        gas_price,
1022        blob_hashes,
1023    );
1024    let is_static = matches!(mode, CallMode::Static(_, _));
1025    let this = match mode {
1026        CallMode::Create(acc) => acc,
1027        CallMode::Create2(acc) => acc,
1028        CallMode::Call(_, _) | CallMode::Static(_, _) => call.to.expect("CALL must have 'to' set"),
1029        CallMode::CallCode(_, _) | CallMode::Delegate(_, _) => {
1030            ctx.map(|c| c.this).unwrap_or(call.by)
1031        }
1032    };
1033    let ctx = if let Some(ctx) = ctx {
1034        Context {
1035            origin: ctx.origin,
1036            is_static: ctx.is_static || is_static,
1037            depth: ctx.depth + 1,
1038            this,
1039        }
1040    } else {
1041        Context {
1042            origin: call.by,
1043            is_static,
1044            depth: 0,
1045            this,
1046        }
1047    };
1048    let is_create = matches!(mode, CallMode::Create(_) | CallMode::Create2(_));
1049    let checkpoint = state.checkpoint();
1050    Ok(CallFrame {
1051        call,
1052        evm,
1053        ctx,
1054        checkpoint,
1055        target: (0, 0),
1056        is_create,
1057    })
1058}