Skip to main content

yevm_core/ops/
calls.rs

1use yevm_base::math::lift;
2use yevm_base::{Acc, Int};
3use yevm_misc::keccak256;
4
5use crate::{
6    Call,
7    evm::{self, CallMode, Context, Evm, EvmResult, EvmYield, Fetch, HaltReason},
8    misc::{create_address, create2_address, is_precompile},
9    state::State,
10};
11
12/// Allocate gas for a child frame per EIP-150 (63/64 rule).
13fn sub_call_gas(evm: &Evm) -> u64 {
14    let remaining = evm.gas_remaining().max(0) as u64;
15    remaining - remaining / 64
16}
17
18pub fn create<S: State>(evm: &mut Evm, ctx: &Context, _: &Call, state: &mut S) -> EvmResult<()> {
19    if ctx.is_static {
20        return Err(EvmYield::Halt(HaltReason::NonStatic));
21    }
22    evm.gas_charge(32_000)?;
23    let [value, offset, size] = evm.peek()?;
24    evm::mem_check_int(offset, size)?;
25    let (offset, size) = (offset.as_usize(), size.as_usize());
26    let initcode_cost = 2 * ((size as i64 + 31) / 32);
27    evm.gas_charge(initcode_cost)?;
28
29    let Some(nonce) = state.nonce(&ctx.this).map(|x| x.as_u64()) else {
30        return Err(EvmYield::Fetch(Fetch::Nonce(ctx.this)));
31    };
32    let address = create_address(&ctx.this, nonce);
33
34    let data = evm.mem_get(offset, size)?;
35    let data: Vec<u8> = data.to_vec();
36    let gas = sub_call_gas(evm);
37    evm.gas_charge(gas as i64)?;
38
39    let call = Call {
40        by: ctx.this,
41        to: None,
42        gas,
43        eth: value,
44        data: data.into(),
45    };
46
47    Err(EvmYield::Call(call, CallMode::Create(address)))
48}
49
50pub fn call<S: State>(evm: &mut Evm, ctx: &Context, _: &Call, state: &mut S) -> EvmResult<()> {
51    let [
52        gas_arg,
53        address,
54        value,
55        args_offset,
56        args_size,
57        ret_offset,
58        ret_size,
59    ] = evm.peek()?;
60    evm::mem_check_int(args_offset, args_size)?;
61    evm::mem_check_int(ret_offset, ret_size)?;
62    let (args_offset, args_size) = (args_offset.as_usize(), args_size.as_usize());
63    let (ret_offset, ret_size) = (ret_offset.as_usize(), ret_size.as_usize());
64    let address: Acc = address.to();
65
66    if state.acc(&ctx.this).is_none() {
67        return Err(EvmYield::Fetch(Fetch::Account(ctx.this)));
68    }
69    if state.acc(&address).is_none() {
70        return Err(EvmYield::Fetch(Fetch::Account(address)));
71    }
72    if let Some(delegate) = state.auth(&address)
73        && state.acc(&address).is_none()
74    {
75        return Err(EvmYield::Fetch(Fetch::Account(delegate)));
76    }
77
78    // EIP-2929: warm/cold address access (use pending warm to survive Fetch+reset)
79    let access_cost: i64 = if state.is_cold_acc(&address) {
80        2600
81    } else {
82        100
83    };
84    evm.warm_acc(&address);
85    evm.gas_charge(access_cost)?;
86
87    if let Some(delegate) = state.auth(&address) {
88        let access_cost: i64 = if state.is_cold_acc(&delegate) {
89            2600
90        } else {
91            100
92        };
93        evm.warm_acc(&delegate);
94        evm.gas_charge(access_cost)?;
95    };
96
97    // Value transfer cost
98    let has_value = !value.is_zero();
99    if ctx.is_static && has_value {
100        return Err(EvmYield::Halt(HaltReason::NonStatic));
101    }
102    if has_value {
103        evm.gas_charge(9000)?;
104    }
105
106    // New account cost (sending value to dead account per EIP-161).
107    // Applies to ALL addresses including precompiles — if the precompile address
108    // is empty in the state, the 25000 cost is charged just like any other address.
109    if has_value {
110        if state.acc(&address).is_none() {
111            return Err(EvmYield::Fetch(Fetch::Account(address)));
112        }
113        let is_empty = state
114            .acc(&address)
115            .map(|a| a.value.is_zero() && a.nonce.is_zero() && a.code.0.0.is_empty())
116            .unwrap_or(true);
117        if is_empty {
118            evm.gas_charge(25000)?;
119        }
120    }
121
122    // Memory expansion for both args and return regions (AFTER all Fetch points so it survives reset)
123    evm.mem_expand_max(&[(args_offset, args_size), (ret_offset, ret_size)])?;
124
125    // 63/64 rule: cap the gas arg at available_gas * 63/64
126    let available = evm.gas_remaining().max(0) as u64;
127    let max_child = available - available / 64;
128    let mut gas = gas_arg.as_u64().min(max_child);
129    evm.gas_charge(gas as i64)?;
130
131    // Gas stipend: add 2300 to child when sending value
132    if has_value {
133        gas += 2300;
134    }
135
136    let data = evm.mem_get(args_offset, args_size)?;
137
138    let call = Call {
139        by: ctx.this,
140        to: Some(address),
141        gas,
142        eth: value,
143        data: data.to_vec().into(),
144    };
145
146    Err(EvmYield::Call(call, CallMode::Call(ret_offset, ret_size)))
147}
148
149pub fn callcode<S: State>(evm: &mut Evm, ctx: &Context, _: &Call, state: &mut S) -> EvmResult<()> {
150    let [
151        gas_arg,
152        address,
153        value,
154        args_offset,
155        args_size,
156        ret_offset,
157        ret_size,
158    ] = evm.peek()?;
159    evm::mem_check_int(args_offset, args_size)?;
160    evm::mem_check_int(ret_offset, ret_size)?;
161    let (args_offset, args_size) = (args_offset.as_usize(), args_size.as_usize());
162    let (ret_offset, ret_size) = (ret_offset.as_usize(), ret_size.as_usize());
163    let address: Acc = address.to();
164
165    let is_precompile = is_precompile(&address);
166    // Address 0 has no account but can be called (empty code returns success)
167    let needs_account = address != Acc::ZERO && !is_precompile;
168    if needs_account && state.acc(&address).is_none() {
169        return Err(EvmYield::Fetch(Fetch::Account(address)));
170    };
171    if let Some(delegate) = state.auth(&address)
172        && state.acc(&address).is_none()
173    {
174        return Err(EvmYield::Fetch(Fetch::Account(delegate)));
175    }
176
177    let access_cost: i64 = if state.is_cold_acc(&address) {
178        2600
179    } else {
180        100
181    };
182    evm.warm_acc(&address);
183    evm.gas_charge(access_cost)?;
184
185    if let Some(delegate) = state.auth(&address) {
186        let access_cost: i64 = if state.is_cold_acc(&delegate) {
187            2600
188        } else {
189            100
190        };
191        evm.warm_acc(&delegate);
192        evm.gas_charge(access_cost)?;
193    };
194
195    evm.mem_expand_max(&[(args_offset, args_size), (ret_offset, ret_size)])?;
196
197    let has_value = !value.is_zero();
198    if has_value {
199        evm.gas_charge(9000)?;
200    }
201
202    let available = evm.gas_remaining().max(0) as u64;
203    let max_child = available - available / 64;
204    let mut gas = gas_arg.as_u64().min(max_child);
205    evm.gas_charge(gas as i64)?;
206
207    if has_value {
208        gas += 2300;
209    }
210
211    let data = evm.mem_get(args_offset, args_size)?;
212
213    let call = Call {
214        by: ctx.this,
215        to: Some(address),
216        gas,
217        eth: value,
218        data: data.to_vec().into(),
219    };
220
221    Err(EvmYield::Call(
222        call,
223        CallMode::CallCode(ret_offset, ret_size),
224    ))
225}
226
227pub fn r#return(evm: &mut Evm, _: &Context, _: &Call, _: &mut dyn State) -> EvmResult<()> {
228    let [offset, size] = evm.peek()?;
229    evm::mem_check_int(offset, size)?;
230    let (offset, size) = (offset.as_usize(), size.as_usize());
231    let mem = evm.mem_get(offset, size)?;
232    Err(EvmYield::Return(mem.to_vec()))
233}
234
235pub fn delegatecall<S: State>(
236    evm: &mut Evm,
237    _ctx: &Context,
238    call: &Call,
239    state: &mut S,
240) -> EvmResult<()> {
241    let [
242        gas_arg,
243        address,
244        args_offset,
245        args_size,
246        ret_offset,
247        ret_size,
248    ] = evm.peek()?;
249    evm::mem_check_int(args_offset, args_size)?;
250    evm::mem_check_int(ret_offset, ret_size)?;
251    let (args_offset, args_size) = (args_offset.as_usize(), args_size.as_usize());
252    let (ret_offset, ret_size) = (ret_offset.as_usize(), ret_size.as_usize());
253    let address: Acc = address.to();
254
255    let is_precompile = is_precompile(&address);
256    // Address 0 has no account but can be called (empty code returns success)
257    let needs_account = address != Acc::ZERO && !is_precompile;
258    if needs_account && state.acc(&address).is_none() {
259        return Err(EvmYield::Fetch(Fetch::Account(address)));
260    };
261    if let Some(delegate) = state.auth(&address)
262        && state.acc(&address).is_none()
263    {
264        return Err(EvmYield::Fetch(Fetch::Account(delegate)));
265    }
266
267    let access_cost: i64 = if state.is_cold_acc(&address) {
268        2600
269    } else {
270        100
271    };
272    evm.warm_acc(&address);
273    evm.gas_charge(access_cost)?;
274
275    if let Some(delegate) = state.auth(&address) {
276        let access_cost: i64 = if state.is_cold_acc(&delegate) {
277            2600
278        } else {
279            100
280        };
281        evm.warm_acc(&delegate);
282        evm.gas_charge(access_cost)?;
283    };
284
285    evm.mem_expand_max(&[(args_offset, args_size), (ret_offset, ret_size)])?;
286
287    let available = evm.gas_remaining().max(0) as u64;
288    let max_child = available - available / 64;
289    let gas = gas_arg.as_u64().min(max_child);
290    evm.gas_charge(gas as i64)?;
291
292    let data = evm.mem_get(args_offset, args_size)?;
293
294    // DELEGATECALL preserves msg.sender and msg.value from the parent frame
295    let inner_call = Call {
296        by: call.by,
297        to: Some(address),
298        gas,
299        eth: call.eth,
300        data: data.to_vec().into(),
301    };
302
303    Err(EvmYield::Call(
304        inner_call,
305        CallMode::Delegate(ret_offset, ret_size),
306    ))
307}
308
309pub fn create2(evm: &mut Evm, ctx: &Context, _: &Call, _: &mut dyn State) -> EvmResult<()> {
310    if ctx.is_static {
311        return Err(EvmYield::Halt(HaltReason::NonStatic));
312    }
313    evm.gas_charge(32_000)?;
314    let [value, offset, size, salt] = evm.peek()?;
315    evm::mem_check_int(offset, size)?;
316    let (offset, size) = (offset.as_usize(), size.as_usize());
317    // EIP-3860 initcode word cost (2) + CREATE2 hash word cost (6) = 8 per word
318    let word_cost = 8 * ((size as i64 + 31) / 32);
319    evm.gas_charge(word_cost)?;
320
321    let data = evm.mem_get(offset, size)?;
322    let data: Vec<u8> = data.to_vec();
323    let init_code_hash = Int::from(keccak256(&data).as_ref());
324    let address = create2_address(&ctx.this, &salt, &init_code_hash);
325
326    let gas = sub_call_gas(evm);
327    evm.gas_charge(gas as i64)?;
328
329    let call = Call {
330        by: ctx.this,
331        to: None,
332        gas,
333        eth: value,
334        data: data.into(),
335    };
336
337    Err(EvmYield::Call(call, CallMode::Create2(address)))
338}
339
340pub fn staticcall<S: State>(
341    evm: &mut Evm,
342    ctx: &Context,
343    _: &Call,
344    state: &mut S,
345) -> EvmResult<()> {
346    let [
347        gas_arg,
348        address,
349        args_offset,
350        args_size,
351        ret_offset,
352        ret_size,
353    ] = evm.peek()?;
354    evm::mem_check_int(args_offset, args_size)?;
355    evm::mem_check_int(ret_offset, ret_size)?;
356    let (args_offset, args_size) = (args_offset.as_usize(), args_size.as_usize());
357    let (ret_offset, ret_size) = (ret_offset.as_usize(), ret_size.as_usize());
358    let address: Acc = address.to();
359    let is_precompile = is_precompile(&address);
360
361    let needs_account = address != Acc::ZERO && !is_precompile;
362    if needs_account && state.acc(&address).is_none() {
363        return Err(EvmYield::Fetch(Fetch::Account(address)));
364    };
365    if let Some(delegate) = state.auth(&address)
366        && state.acc(&address).is_none()
367    {
368        return Err(EvmYield::Fetch(Fetch::Account(delegate)));
369    }
370
371    // EIP-2929: warm/cold address access (applies to precompiles too)
372    let access_cost: i64 = if state.is_cold_acc(&address) {
373        2600
374    } else {
375        100
376    };
377    evm.warm_acc(&address);
378    evm.gas_charge(access_cost)?;
379
380    if let Some(delegate) = state.auth(&address) {
381        let access_cost: i64 = if state.is_cold_acc(&delegate) {
382            2600
383        } else {
384            100
385        };
386        evm.warm_acc(&delegate);
387        evm.gas_charge(access_cost)?;
388    };
389
390    // Fetch before mem_expand_max so expansion/charge survive reset
391    if !is_precompile && state.acc(&address).is_none() {
392        return Err(EvmYield::Fetch(Fetch::Account(address)));
393    };
394
395    evm.mem_expand_max(&[(args_offset, args_size), (ret_offset, ret_size)])?;
396
397    let available = evm.gas_remaining().max(0) as u64;
398    let max_child = available - available / 64;
399    let gas = gas_arg.as_u64().min(max_child);
400    evm.gas_charge(gas as i64)?;
401
402    let data = evm.mem_get(args_offset, args_size)?;
403
404    let call = Call {
405        by: ctx.this,
406        to: Some(address),
407        gas,
408        eth: Int::ZERO,
409        data: data.to_vec().into(),
410    };
411
412    Err(EvmYield::Call(call, CallMode::Static(ret_offset, ret_size)))
413}
414
415pub fn revert(evm: &mut Evm, _: &Context, _: &Call, _: &mut dyn State) -> EvmResult<()> {
416    let [offset, size] = evm.peek()?;
417    evm::mem_check_int(offset, size)?;
418    let (offset, size) = (offset.as_usize(), size.as_usize());
419    let mem = evm.mem_get(offset, size)?;
420    Err(EvmYield::Revert(mem.to_vec()))
421}
422
423pub fn selfdestruct<S: State>(
424    evm: &mut Evm,
425    ctx: &Context,
426    _: &Call,
427    state: &mut S,
428) -> EvmResult<()> {
429    // EIP-214: SELFDESTRUCT in static context is an exceptional halt
430    if ctx.is_static {
431        return Err(EvmYield::Halt(HaltReason::NonStatic));
432    }
433
434    let [beneficiary] = evm.peek()?;
435    let beneficiary: Acc = beneficiary.to();
436
437    // Fetch beneficiary before gas accounting so is_empty check is accurate.
438    if state.acc(&beneficiary).is_none() {
439        return Err(EvmYield::Fetch(crate::evm::Fetch::Account(beneficiary)));
440    }
441
442    evm.gas_charge(5_000)?;
443
444    // EIP-2929: cold address surcharge for beneficiary (no warm cost — covered by 5000 base)
445    if state.is_cold_acc(&beneficiary) {
446        evm.gas_charge(2600)?;
447    }
448    evm.warm_acc(&beneficiary);
449
450    let balance = state.balance(&ctx.this).unwrap_or(Int::ZERO);
451    if !balance.is_zero() && beneficiary != ctx.this {
452        // EIP-161: creating empty account costs 25000 when transferring value
453        let is_empty = state
454            .acc(&beneficiary)
455            .map(|a| a.value.is_zero() && a.nonce.is_zero() && a.code.0.0.is_empty())
456            .unwrap_or(true);
457        if is_empty {
458            evm.gas_charge(25000)?;
459        }
460        let add = lift(|[a, b]| a + b);
461        let to_bal = state.balance(&beneficiary).unwrap_or(Int::ZERO);
462        state.set_value(&beneficiary, add([to_bal, balance]));
463        state.set_value(&ctx.this, Int::ZERO);
464    }
465
466    // EIP-6780 (Cancun): only destroy if contract was created in same transaction
467    if state.created().contains(&ctx.this) {
468        state.destroy(&ctx.this);
469    }
470    Err(EvmYield::Return(vec![]))
471}