Skip to main content

yevm_core/
evm.rs

1use crate::call::Head;
2use crate::trace::{Event, Step};
3use serde::{Deserialize, Serialize};
4use yevm_base::math::lift;
5
6use crate::{Acc, Call, Int, Result, ops, state::State};
7
8const K: usize = 1024;
9
10#[derive(Clone, Copy, Debug, Deserialize, Serialize, PartialEq, Eq)]
11pub enum HaltReason {
12    OutOfGas,
13    OutOfMemory,
14    BadCopyRange,
15    BadJump(usize),
16    BadOpcode(u8),
17    NonStatic,
18    StackUnderflow,
19    StackOverflow,
20    GasBelowStipend,
21}
22
23#[derive(Debug)]
24pub enum Fetch {
25    Code(Acc),
26    Nonce(Acc),
27    Balance(Acc),
28    Account(Acc),
29    BlockHash(u64),
30    StateCell(Acc, Int),
31}
32
33#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
34pub enum CallMode {
35    Call(usize, usize),
36    Static(usize, usize),
37    Delegate(usize, usize),
38    CallCode(usize, usize),
39    Create(Acc),
40    Create2(Acc),
41}
42
43impl CallMode {
44    pub fn target(&self) -> Option<(usize, usize)> {
45        match self {
46            Self::Call(offset, size) => Some((*offset, *size)),
47            Self::Static(offset, size) => Some((*offset, *size)),
48            Self::Delegate(offset, size) => Some((*offset, *size)),
49            Self::CallCode(offset, size) => Some((*offset, *size)),
50            _ => None,
51        }
52    }
53
54    pub fn created(&self) -> Option<Acc> {
55        match self {
56            Self::Create(acc) => Some(*acc),
57            Self::Create2(acc) => Some(*acc),
58            _ => None,
59        }
60    }
61}
62
63pub enum StepResult {
64    End,
65    Ok,
66    Call(Call, CallMode),
67    Return(Vec<u8>),
68    Revert(Vec<u8>),
69    Halt(HaltReason),
70    Fetch(Fetch),
71}
72
73impl From<HaltReason> for StepResult {
74    fn from(reason: HaltReason) -> Self {
75        StepResult::Halt(reason)
76    }
77}
78
79#[derive(Clone, Copy, Debug)]
80pub struct Gas {
81    pub limit: i64,
82    pub spent: i64,
83    pub refund: i64,
84    pub finalized: i64,
85}
86
87impl Gas {
88    pub fn new(gas: u64) -> Self {
89        Self {
90            limit: gas as i64,
91            spent: 0,
92            refund: 0,
93            finalized: 0,
94        }
95    }
96
97    pub fn remaining(&self) -> i64 {
98        self.limit - self.spent //+ self.refund
99    }
100
101    pub fn refund(&mut self, gas: i64) -> EvmResult<()> {
102        if self.refund + gas >= 0 {
103            self.refund += gas;
104            Ok(())
105        } else {
106            Err(EvmYield::Halt(HaltReason::OutOfGas))
107        }
108    }
109
110    pub fn charge(&mut self, gas: i64) -> EvmResult<i64> {
111        let rem = self.remaining();
112        if rem >= gas {
113            self.spent += gas;
114            Ok(rem - gas)
115        } else {
116            self.spent += rem;
117            Err(EvmYield::Halt(HaltReason::OutOfGas))
118        }
119    }
120
121    pub fn drain(&mut self) {
122        self.spent = self.limit;
123        self.refund = 0;
124    }
125}
126
127pub struct Context {
128    pub origin: Acc,
129    pub is_static: bool,
130    pub depth: usize,
131    pub this: Acc,
132}
133
134#[derive(Debug)]
135pub enum EvmYield {
136    Halt(HaltReason),
137    Fetch(Fetch),
138    Return(Vec<u8>),
139    Revert(Vec<u8>),
140    Call(Call, CallMode),
141}
142
143pub type EvmResult<T> = std::result::Result<T, EvmYield>;
144
145pub struct Evm {
146    pub pc: usize,
147    pub gas: Gas,
148    pub stack: Vec<Int>,
149    pub memory: Vec<u8>,
150    pub code: Vec<u8>,
151    pub head: Head,
152    pub ret: Vec<u8>,
153
154    pub chain_id: Int,
155    pub gas_price: Int,
156    pub blob_hashes: Vec<Int>,
157    pub(crate) pending_stack_pops: usize,
158    pub(crate) pending_stack_push: Vec<Int>,
159    pub(crate) pending_gas_charge: i64,
160    pub(crate) pending_gas_refund: i64,
161    pub(crate) pending_acc_warmup: [Acc; 2],
162    pub(crate) pending_acc_count: usize,
163    pub(crate) pending_key_warmup: Option<(Acc, Int)>,
164
165    pub(crate) step: Option<Step>,
166}
167
168impl Evm {
169    pub const STACK_SIZE_LIMIT: usize = 1024;
170    // Max memory size in bytes: (2^32 - 1) = 4Gb.
171    // Sanity check: (16 * 2^20) = 16Mb in practice.
172    pub const MEMORY_SIZE_LIMIT: usize = (1_usize << 24);
173
174    pub fn new(
175        head: Head,
176        code: Vec<u8>,
177        gas: u64,
178        chain_id: Int,
179        gas_price: Int,
180        blob_hashes: Vec<Int>,
181    ) -> Self {
182        Self {
183            pc: 0,
184            gas: Gas::new(gas),
185            stack: Vec::with_capacity(Self::STACK_SIZE_LIMIT),
186            memory: Vec::with_capacity(4 * K),
187            code,
188            head,
189            ret: Vec::new(),
190            chain_id,
191            gas_price,
192            blob_hashes,
193            pending_stack_pops: 0,
194            pending_stack_push: Vec::new(),
195            pending_gas_charge: 0,
196            pending_gas_refund: 0,
197            pending_acc_warmup: [Acc::ZERO; 2],
198            pending_acc_count: 0,
199            pending_key_warmup: None,
200            step: None,
201        }
202    }
203
204    pub fn peek_usize<const N: usize>(&mut self) -> EvmResult<[usize; N]> {
205        let mut ret = [0usize; N];
206        let pop = self.peek::<N>()?;
207        for (i, item) in ret.iter_mut().enumerate() {
208            *item = pop[i].as_usize();
209        }
210        Ok(ret)
211    }
212
213    pub fn peek<const N: usize>(&mut self) -> EvmResult<[Int; N]> {
214        let mut ret = [Int::ZERO; N];
215        if self.stack.len() < N {
216            return Err(EvmYield::Halt(HaltReason::StackUnderflow));
217        }
218        for (slot, value) in ret.iter_mut().zip(self.stack.iter().rev()) {
219            *slot = *value;
220        }
221        self.pending_stack_pops = N;
222        Ok(ret)
223    }
224
225    pub fn apply(&mut self, state: &mut impl State) {
226        for _ in 0..self.pending_stack_pops {
227            let _ = self.stack.pop();
228        }
229        self.pending_stack_pops = 0;
230
231        for int in self.pending_stack_push.drain(..) {
232            self.stack.push(int);
233        }
234        assert!(self.pending_stack_push.is_empty());
235
236        self.gas.spent += self.pending_gas_charge;
237        self.pending_gas_charge = 0;
238
239        self.gas.refund += self.pending_gas_refund;
240        self.pending_gas_refund = 0;
241
242        for i in 0..self.pending_acc_count {
243            state.warm_acc(&self.pending_acc_warmup[i]);
244        }
245        self.pending_acc_count = 0;
246
247        if let Some((acc, key)) = self.pending_key_warmup.take() {
248            state.warm_key(&acc, &key);
249        }
250    }
251
252    pub fn reset(&mut self) {
253        self.pending_stack_pops = 0;
254        self.pending_stack_push.clear();
255        self.pending_gas_charge = 0;
256        self.pending_gas_refund = 0;
257        self.pending_acc_count = 0;
258        self.pending_key_warmup = None;
259    }
260
261    pub fn push(&mut self, int: Int) -> EvmResult<()> {
262        let effective = self
263            .stack
264            .len()
265            .saturating_sub(self.pending_stack_pops)
266            .saturating_add(self.pending_stack_push.len());
267        if effective >= Self::STACK_SIZE_LIMIT {
268            return Err(EvmYield::Halt(HaltReason::StackOverflow));
269        }
270        self.pending_stack_push.push(int);
271        Ok(())
272    }
273
274    pub fn warm_acc(&mut self, acc: &Acc) {
275        self.pending_acc_warmup[self.pending_acc_count] = *acc;
276        self.pending_acc_count += 1;
277    }
278
279    pub fn warm_key(&mut self, acc: &Acc, key: &Int) {
280        self.pending_key_warmup = Some((*acc, *key));
281    }
282
283    pub fn gas_remaining(&self) -> i64 {
284        self.gas.remaining() - self.pending_gas_charge
285    }
286
287    pub fn gas_charge(&mut self, gas: i64) -> EvmResult<()> {
288        if gas > self.gas_remaining() {
289            self.pending_gas_charge += self.gas.remaining();
290            return Err(EvmYield::Halt(HaltReason::OutOfGas));
291        }
292        self.pending_gas_charge += gas;
293        Ok(())
294    }
295
296    pub fn gas_refund(&mut self, gas: i64) -> EvmResult<()> {
297        self.pending_gas_refund += gas;
298        Ok(())
299    }
300
301    pub fn mem_expand(&mut self, offset: usize, size: usize) -> EvmResult<()> {
302        if size == 0 {
303            return Ok(());
304        }
305        mem_check(offset, size)?;
306        let len = self.memory.len();
307        let end = (offset + size).div_ceil(32) * 32;
308        if end > len {
309            let old_words = (len / 32) as i64;
310            let new_words = (end / 32) as i64;
311            let cost = (new_words * new_words / 512 + 3 * new_words)
312                - (old_words * old_words / 512 + 3 * old_words);
313            self.gas_charge(cost)?;
314            self.memory.resize(end, 0);
315        }
316        Ok(())
317    }
318
319    /// Expand memory to cover all given regions and charge gas once for the combined expansion.
320    /// Per EVM spec, CALL/DELEGATECALL/etc. charge for expansion to max(args, ret) in one step.
321    pub fn mem_expand_max(&mut self, regions: &[(usize, usize)]) -> EvmResult<()> {
322        let mut max_end = self.memory.len();
323        for (offset, size) in regions {
324            if *size > 0 {
325                mem_check(*offset, *size)?;
326                let end = (offset + size).div_ceil(32) * 32;
327                max_end = max_end.max(end);
328            }
329        }
330        let len = self.memory.len();
331        if max_end > len {
332            let old_words = (len / 32) as i64;
333            let new_words = (max_end / 32) as i64;
334            let cost = (new_words * new_words / 512 + 3 * new_words)
335                - (old_words * old_words / 512 + 3 * old_words);
336            self.gas_charge(cost)?;
337            self.memory.resize(max_end, 0);
338        }
339        Ok(())
340    }
341
342    pub fn mem_put(&mut self, offset: usize, size: usize, source: &[u8]) -> EvmResult<()> {
343        self.mem_expand(offset, size)?;
344        if size > 0 && !source.is_empty() {
345            let len = source.len().min(size);
346            self.memory[offset..offset + len].copy_from_slice(&source[..len]);
347        }
348        Ok(())
349    }
350
351    pub fn mem_get(&mut self, offset: usize, size: usize) -> EvmResult<Vec<u8>> {
352        self.mem_expand(offset, size)?;
353        let lo = offset.min(self.memory.len());
354        let hi = (offset + size).min(self.memory.len());
355        let mut ret = vec![0u8; size];
356        ret[..hi - lo].copy_from_slice(&self.memory[lo..hi]);
357        Ok(ret)
358    }
359
360    pub fn data(&self, pc: usize) -> Vec<u8> {
361        let op = self.code[pc];
362        let len = match op {
363            0x60..0x80 => op as usize - 0x60 + 1, // PUSH{1..32}
364            _ => 0,
365        };
366        let lo = (pc + 1).min(self.code.len());
367        let hi = (pc + 1 + len).min(self.code.len());
368        let mut ret = vec![0; len];
369        let len = hi - lo;
370        ret[0..len].copy_from_slice(&self.code[lo..hi]);
371        ret
372    }
373
374    pub fn step(
375        &mut self,
376        ctx: &Context,
377        call: &Call,
378        state: &mut impl State,
379    ) -> Result<StepResult> {
380        let Some(op) = self.code.get(self.pc).copied() else {
381            return Ok(StepResult::End);
382        };
383        let name = ops::OPS[op as usize];
384
385        if state.is_tracing() {
386            let pc = self.pc;
387            let name = if name.starts_with("INVALID/") {
388                "INVALID".to_string()
389            } else {
390                name.to_string()
391            };
392            let data = self.data(pc);
393            let data = if data.is_empty() {
394                None
395            } else {
396                Some(data.into())
397            };
398            let gas = self.gas.remaining().max(0) as u64;
399            self.step = Some(Step {
400                pc,
401                op,
402                name,
403                data,
404                gas,
405                stack: self.stack.len(),
406                memory: self.memory.len(),
407                debug: vec![],
408            });
409        }
410
411        // let balance = state.balance(&ctx.this).unwrap_or_default();
412        // if let Some(step) = self.step.as_mut() {
413        //     step.debug
414        //         .push(format!("balance[{:?}]={:?}", ctx.this, balance.as_u128()));
415        // };
416
417        match ops::dispatch(op, self, ctx, call, state) {
418            Ok(()) => {
419                self.apply(state);
420                if !is_jump(op) {
421                    self.pc += 1;
422                }
423                if let Some(mut step) = self.step.take() {
424                    let cost = step.gas - self.gas.remaining().max(0) as u64;
425                    step.gas = self.gas.remaining().max(0) as u64;
426                    step.stack = self.stack.len();
427                    step.memory = self.memory.len();
428                    step.debug.push(format!("cost={cost}"));
429                    if self.gas.refund != 0 {
430                        step.debug.push(format!("gas_refund={}", self.gas.refund));
431                    }
432                    state.emit(Event::Step(step));
433                }
434                Ok(StepResult::Ok)
435            }
436            Err(EvmYield::Fetch(fetch)) => {
437                self.reset();
438                Ok(StepResult::Fetch(fetch))
439            }
440            Err(EvmYield::Halt(reason)) => {
441                self.apply(state);
442                if let Some(mut step) = self.step.take() {
443                    step.gas = self.gas.remaining().max(0) as u64;
444                    step.stack = self.stack.len();
445                    step.memory = self.memory.len();
446                    step.debug.push(format!("HALT:{:?}", reason));
447                    state.emit(Event::Step(step));
448                }
449                Ok(StepResult::Halt(reason))
450            }
451            Err(EvmYield::Return(ret)) => {
452                self.apply(state);
453                if let Some(mut step) = self.step.take() {
454                    step.gas = self.gas.remaining().max(0) as u64;
455                    step.stack = self.stack.len();
456                    step.memory = self.memory.len();
457                    step.debug.push(format!("RETURN:size={}", ret.len()));
458                    state.emit(Event::Step(step));
459                }
460                Ok(StepResult::Return(ret))
461            }
462            Err(EvmYield::Revert(ret)) => {
463                self.apply(state);
464                if let Some(mut step) = self.step.take() {
465                    step.gas = self.gas.remaining().max(0) as u64;
466                    step.stack = self.stack.len();
467                    step.memory = self.memory.len();
468                    step.debug.push(format!("REVERT:size={}", ret.len()));
469                    state.emit(Event::Step(step));
470                }
471                Ok(StepResult::Revert(ret))
472            }
473            Err(EvmYield::Call(call, mode)) => {
474                self.apply(state);
475                if let Some(mut step) = self.step.take() {
476                    step.gas = self.gas.remaining().max(0) as u64;
477                    step.stack = self.stack.len();
478                    step.memory = self.memory.len();
479                    step.debug.push(format!(
480                        "CALL:to={},gas={}",
481                        call.to.unwrap_or_default(),
482                        call.gas
483                    ));
484                    if !call.eth.is_zero() {
485                        step.debug.push(format!("CALL:eth={}", call.eth));
486                    }
487                    step.debug.push(format!("CALL:mode={mode:?}"));
488                    state.emit(Event::Step(step));
489                }
490                Ok(StepResult::Call(call, mode))
491            }
492        }
493    }
494}
495
496/// Check memory range using 256-bit values (avoids truncation before check).
497pub fn mem_check_int(offset: Int, size: Int) -> EvmResult<()> {
498    let limit = Int::from(Evm::MEMORY_SIZE_LIMIT);
499    let add = lift(|[a, b]| a + b);
500    let gt = lift(|[a, b]| {
501        if a > b {
502            yevm_base::math::U256::ONE
503        } else {
504            yevm_base::math::U256::ZERO
505        }
506    });
507    if !gt([size, limit]).is_zero() {
508        return Err(EvmYield::Halt(HaltReason::OutOfMemory));
509    }
510    let end = add([offset, size]);
511    if !gt([end, limit]).is_zero() {
512        return Err(EvmYield::Halt(HaltReason::OutOfMemory));
513    }
514    Ok(())
515}
516
517pub fn mem_check(offset: usize, size: usize) -> EvmResult<()> {
518    let limit = Evm::MEMORY_SIZE_LIMIT;
519    if size <= limit && offset <= limit.saturating_sub(size) {
520        return Ok(());
521    }
522    Err(EvmYield::Halt(HaltReason::OutOfMemory))
523}
524
525fn is_jump(op: u8) -> bool {
526    op == 0x56 || op == 0x57
527}