Skip to main content

revmc_builtins/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(missing_docs, clippy::missing_safety_doc)]
3#![cfg_attr(not(test), warn(unused_extern_crates))]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![cfg_attr(not(feature = "std"), no_std)]
6
7extern crate alloc;
8
9#[macro_use]
10#[cfg(feature = "ir")]
11extern crate tracing;
12
13use alloc::{boxed::Box, vec::Vec};
14use revm_interpreter::{
15    CallInput, CallInputs, CallScheme, CallValue, CreateInputs, CreateScheme, FrameInput,
16    InstructionResult, InterpreterAction, InterpreterResult, as_u64_saturated, as_usize_saturated,
17    interpreter_types::{InputsTr, MemoryTr},
18};
19use revm_primitives::{B256, Bytes, KECCAK_EMPTY, Log, LogData, U256, hardfork::SpecId};
20use revmc_context::{EvmContext, EvmWord};
21
22pub mod gas;
23
24#[cfg(feature = "ir")]
25mod ir;
26#[cfg(feature = "ir")]
27pub use ir::*;
28
29#[macro_use]
30mod macros;
31
32mod utils;
33use utils::*;
34pub use utils::{BuiltinError, BuiltinResult};
35
36/// The kind of a `*CALL*` instruction.
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[repr(u8)]
39pub enum CallKind {
40    /// `CALL`.
41    Call,
42    /// `CALLCODE`.
43    CallCode,
44    /// `DELEGATECALL`.
45    DelegateCall,
46    /// `STATICCALL`.
47    StaticCall,
48}
49
50impl From<CallKind> for CallScheme {
51    fn from(kind: CallKind) -> Self {
52        match kind {
53            CallKind::Call => Self::Call,
54            CallKind::CallCode => Self::CallCode,
55            CallKind::DelegateCall => Self::DelegateCall,
56            CallKind::StaticCall => Self::StaticCall,
57        }
58    }
59}
60
61/// The kind of a `CREATE*` instruction.
62#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63#[repr(u8)]
64pub enum CreateKind {
65    /// `CREATE`.
66    Create,
67    /// `CREATE2`.
68    Create2,
69}
70
71// NOTE: All functions MUST be `extern "C"` and their parameters must match `Builtin` enum.
72//
73// The `sp` parameter always points to the last popped stack element.
74// If results are expected to be pushed back onto the stack, they must be written to the read
75// pointers in **reverse order**, meaning the last pointer is the first return value.
76
77#[inline(always)]
78#[cold]
79fn fail(ecx: &mut EvmContext<'_>, e: BuiltinError) -> ! {
80    ecx.exit_result = e.into();
81    unsafe { revmc_context::revmc_exit(ecx) }
82}
83
84macro_rules! builtins {
85    () => {};
86
87    // Fallible builtins: `-> BuiltinResult` is stripped and errors route through `fail()`.
88    ($(#[$attr:meta])* pub unsafe extern "C" fn $name:ident($ecx:ident : &mut EvmContext<'_> $(, $rest_i:ident : $rest_t:ty)* $(,)?) -> BuiltinResult $block:block $($more:tt)*) => {
89        $(#[$attr])*
90        pub unsafe extern "C" fn $name($ecx: &mut EvmContext<'_> $(, $rest_i : $rest_t)*) {
91            #[inline(always)]
92            unsafe fn imp($ecx: &mut EvmContext<'_> $(, $rest_i : $rest_t)*) -> BuiltinResult $block
93            match unsafe { imp($ecx $(, $rest_i)*) } {
94                Ok(()) => {}
95                Err(e) => fail($ecx, e),
96            }
97        }
98
99        builtins! { $($more)* }
100    };
101
102    // Infallible items pass through unchanged.
103    ($item:item $($rest:tt)*) => {
104        $item
105        builtins! { $($rest)* }
106    };
107}
108
109builtins! {
110
111#[unsafe(no_mangle)]
112pub unsafe extern "C" fn __revmc_builtin_panic(data: *const u8, len: usize) -> ! {
113    let msg = unsafe { core::str::from_utf8_unchecked(core::slice::from_raw_parts(data, len)) };
114    panic!("{msg}");
115}
116
117#[unsafe(no_mangle)]
118pub unsafe extern "C" fn __revmc_builtin_assert_spec_id(ecx: &EvmContext<'_>, expected: SpecId) {
119    assert_eq!(
120        ecx.spec_id, expected,
121        "revmc panic: runtime spec_id does not match compilation spec_id"
122    );
123}
124
125#[unsafe(no_mangle)]
126pub unsafe extern "C" fn __revmc_builtin_div(rev![a, b]: &mut [EvmWord; 2]) {
127    let divisor = b.to_u256();
128    *b = if divisor.is_zero() { U256::ZERO } else { a.to_u256().wrapping_div(divisor) }.into();
129}
130
131#[unsafe(no_mangle)]
132pub unsafe extern "C" fn __revmc_builtin_sdiv(rev![a, b]: &mut [EvmWord; 2]) {
133    *b = revm_interpreter::instructions::i256::i256_div(a.to_u256(), b.to_u256()).into();
134}
135
136#[unsafe(no_mangle)]
137pub unsafe extern "C" fn __revmc_builtin_mod(rev![a, b]: &mut [EvmWord; 2]) {
138    let divisor = b.to_u256();
139    *b = if divisor.is_zero() { U256::ZERO } else { a.to_u256().wrapping_rem(divisor) }.into();
140}
141
142#[unsafe(no_mangle)]
143pub unsafe extern "C" fn __revmc_builtin_smod(rev![a, b]: &mut [EvmWord; 2]) {
144    *b = revm_interpreter::instructions::i256::i256_mod(a.to_u256(), b.to_u256()).into();
145}
146
147#[unsafe(no_mangle)]
148pub unsafe extern "C" fn __revmc_builtin_addmod(rev![a, b, c]: &mut [EvmWord; 3]) {
149    *c = a.to_u256().add_mod(b.to_u256(), c.to_u256()).into();
150}
151
152#[unsafe(no_mangle)]
153pub unsafe extern "C" fn __revmc_builtin_mulmod(rev![a, b, c]: &mut [EvmWord; 3]) {
154    *c = a.to_u256().mul_mod(b.to_u256(), c.to_u256()).into();
155}
156
157#[unsafe(no_mangle)]
158pub unsafe extern "C" fn __revmc_builtin_exp(
159    ecx: &mut EvmContext<'_>,
160    sp: &mut [EvmWord; 2],
161) -> BuiltinResult {
162    let rev![base, exponent_ptr] = sp;
163    let exponent = exponent_ptr.to_u256();
164    gas!(ecx, ecx.gas_params.exp_cost(exponent));
165    *exponent_ptr = base.to_u256().pow(exponent).into();
166    Ok(())
167}
168
169#[unsafe(no_mangle)]
170pub unsafe extern "C" fn __revmc_builtin_exp_gas(
171    ecx: &mut EvmContext<'_>,
172    exponent: &EvmWord,
173) -> BuiltinResult {
174    gas!(ecx, ecx.gas_params.exp_cost(exponent.to_u256()));
175    Ok(())
176}
177
178#[unsafe(no_mangle)]
179pub unsafe extern "C" fn __revmc_builtin_keccak256(
180    ecx: &mut EvmContext<'_>,
181    sp: &mut [EvmWord; 2],
182) -> BuiltinResult {
183    let rev![offset, len_ptr] = sp;
184    let len = try_into_usize!(len_ptr);
185    do_keccak256(ecx, len_ptr, *offset, len)
186}
187
188#[unsafe(no_mangle)]
189pub unsafe extern "C" fn __revmc_builtin_keccak256_cc(
190    ecx: &mut EvmContext<'_>,
191    out: &mut EvmWord,
192    offset: u64,
193    len: u64,
194) -> BuiltinResult {
195    do_keccak256(ecx, out, U256::from(offset).into(), len as usize)
196}
197
198fn do_keccak256(
199    ecx: &mut EvmContext<'_>,
200    out: &mut EvmWord,
201    offset: EvmWord,
202    len: usize,
203) -> BuiltinResult {
204    *out = EvmWord::from_be_bytes(if len == 0 {
205        KECCAK_EMPTY
206    } else {
207        gas!(ecx, ecx.gas_params.keccak256_cost(len));
208        let offset = try_into_usize!(offset);
209        ensure_memory(ecx, offset, len)?;
210        let data = ecx.memory.slice(offset..offset + len);
211        revm_primitives::keccak256(&*data)
212    });
213    Ok(())
214}
215
216#[unsafe(no_mangle)]
217pub unsafe extern "C" fn __revmc_builtin_balance(
218    ecx: &mut EvmContext<'_>,
219    address: &mut EvmWord,
220) -> BuiltinResult {
221    let addr = address.to_address();
222    let account = load_account(ecx, addr, false)?;
223    *address = account.balance.into();
224    Ok(())
225}
226
227#[unsafe(no_mangle)]
228pub unsafe extern "C" fn __revmc_builtin_origin(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
229    *slot = EvmWord::from_be_bytes(ecx.host.caller().into_word());
230}
231
232#[unsafe(no_mangle)]
233pub unsafe extern "C" fn __revmc_builtin_calldataload(
234    ecx: &EvmContext<'_>,
235    offset_ptr: &mut EvmWord,
236) {
237    do_calldataload(ecx, offset_ptr, as_usize_saturated!(offset_ptr.to_u256()));
238}
239
240#[unsafe(no_mangle)]
241pub unsafe extern "C" fn __revmc_builtin_calldataload_c(
242    ecx: &EvmContext<'_>,
243    offset_ptr: &mut EvmWord,
244    offset: u64,
245) {
246    do_calldataload(ecx, offset_ptr, offset as usize);
247}
248
249fn do_calldataload(ecx: &EvmContext<'_>, out: &mut EvmWord, offset: usize) {
250    let mut word = B256::ZERO;
251    let input = ecx.input.input();
252    let input_len = input.len();
253    if offset < input_len {
254        let count = 32.min(input_len - offset);
255        let input = ecx.input.input().as_bytes_memory(ecx.memory);
256        // SAFETY: `count` is bounded by the calldata length.
257        // This is `word[..count].copy_from_slice(input[offset..offset + count])`, written using
258        // raw pointers as apparently the compiler cannot optimize the slice version, and using
259        // `get_unchecked` twice is uglier.
260        unsafe {
261            core::ptr::copy_nonoverlapping(input.as_ptr().add(offset), word.as_mut_ptr(), count)
262        };
263    }
264    *out = EvmWord::from_be_bytes(word);
265}
266
267#[unsafe(no_mangle)]
268pub unsafe extern "C" fn __revmc_builtin_calldatacopy(
269    ecx: &mut EvmContext<'_>,
270    sp: &mut [EvmWord; 3],
271) -> BuiltinResult {
272    let rev![memory_offset, data_offset, len] = sp;
273    let len = try_into_usize!(len);
274    if len != 0 {
275        gas!(ecx, ecx.gas_params.copy_cost(len));
276        let memory_offset = try_into_usize!(memory_offset);
277        ensure_memory(ecx, memory_offset, len)?;
278        let data_offset = as_usize_saturated!(data_offset.to_u256());
279
280        match ecx.input.input() {
281            CallInput::Bytes(bytes) => {
282                ecx.memory.set_data(memory_offset, data_offset, len, bytes.as_ref());
283            }
284            CallInput::SharedBuffer(range) => {
285                ecx.memory.set_data_from_global(memory_offset, data_offset, len, range.clone());
286            }
287        }
288    }
289    Ok(())
290}
291
292#[unsafe(no_mangle)]
293pub unsafe extern "C" fn __revmc_builtin_codecopy(
294    ecx: &mut EvmContext<'_>,
295    sp: &mut [EvmWord; 3],
296) -> BuiltinResult {
297    let bytecode = unsafe { &*ecx.bytecode };
298    copy_operation(ecx, sp, bytecode)
299}
300
301#[unsafe(no_mangle)]
302pub unsafe extern "C" fn __revmc_builtin_gas_price(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
303    *slot = ecx.host.effective_gas_price().into();
304}
305
306#[unsafe(no_mangle)]
307pub unsafe extern "C" fn __revmc_builtin_extcodesize(
308    ecx: &mut EvmContext<'_>,
309    address: &mut EvmWord,
310) -> BuiltinResult {
311    let addr = address.to_address();
312    let account = load_account(ecx, addr, true)?;
313    *address = U256::from(account.code.as_ref().unwrap().len()).into();
314    Ok(())
315}
316
317#[unsafe(no_mangle)]
318pub unsafe extern "C" fn __revmc_builtin_extcodecopy(
319    ecx: &mut EvmContext<'_>,
320    sp: &mut [EvmWord; 4],
321) -> BuiltinResult {
322    let rev![address, memory_offset, code_offset, len] = sp;
323    let addr = address.to_address();
324    let len = try_into_usize!(len);
325    gas!(ecx, ecx.gas_params.extcodecopy(len));
326
327    let mut memory_offset_usize = 0;
328    if len != 0 {
329        memory_offset_usize = try_into_usize!(memory_offset);
330        ensure_memory(ecx, memory_offset_usize, len)?;
331    }
332
333    let account = load_account(ecx, addr, true)?;
334    let code = account.code.as_ref().unwrap().original_bytes();
335
336    let code_offset_usize = core::cmp::min(as_usize_saturated!(code_offset.to_u256()), code.len());
337    ecx.memory.set_data(memory_offset_usize, code_offset_usize, len, &code);
338    Ok(())
339}
340
341#[unsafe(no_mangle)]
342pub unsafe extern "C" fn __revmc_builtin_returndatacopy(
343    ecx: &mut EvmContext<'_>,
344    sp: &mut [EvmWord; 3],
345) -> BuiltinResult {
346    let rev![memory_offset, offset, len] = sp;
347    let len = try_into_usize!(len);
348    let data_offset = as_usize_saturated!(offset.to_u256());
349
350    // Bounds check BEFORE charging gas, matching revm.
351    let data_end = data_offset.saturating_add(len);
352    if data_end > ecx.return_data.len() {
353        return Err(InstructionResult::OutOfOffset.into());
354    }
355
356    gas!(ecx, ecx.gas_params.copy_cost(len));
357    if len != 0 {
358        let memory_offset = try_into_usize!(memory_offset);
359        ensure_memory(ecx, memory_offset, len)?;
360        ecx.memory.set(memory_offset, &ecx.return_data[data_offset..data_end]);
361    }
362    Ok(())
363}
364
365#[unsafe(no_mangle)]
366pub unsafe extern "C" fn __revmc_builtin_extcodehash(
367    ecx: &mut EvmContext<'_>,
368    address: &mut EvmWord,
369) -> BuiltinResult {
370    let addr = address.to_address();
371    let account = load_account(ecx, addr, false)?;
372    let code_hash = if account.is_empty { revm_primitives::B256::ZERO } else { account.code_hash };
373    *address = EvmWord::from_be_bytes(code_hash);
374    Ok(())
375}
376
377#[unsafe(no_mangle)]
378pub unsafe extern "C" fn __revmc_builtin_blockhash(
379    ecx: &mut EvmContext<'_>,
380    number_ptr: &mut EvmWord,
381) -> BuiltinResult {
382    let requested_number = number_ptr.to_u256();
383    let block_number = ecx.host.block_number();
384
385    // Check if requested block is in the future
386    let Some(diff) = block_number.checked_sub(requested_number) else {
387        *number_ptr = EvmWord::ZERO;
388        return Ok(());
389    };
390
391    let diff = as_u64_saturated!(diff);
392
393    // Current block returns 0
394    if diff == 0 {
395        *number_ptr = EvmWord::ZERO;
396        return Ok(());
397    }
398
399    // BLOCK_HASH_HISTORY is 256
400    const BLOCK_HASH_HISTORY: u64 = 256;
401
402    if diff <= BLOCK_HASH_HISTORY {
403        let hash = ecx.host.block_hash(as_u64_saturated!(requested_number)).ok_or_fatal()?;
404        *number_ptr = EvmWord::from_be_bytes(hash);
405    } else {
406        // Too old, return 0
407        *number_ptr = EvmWord::ZERO;
408    }
409
410    Ok(())
411}
412
413#[unsafe(no_mangle)]
414pub unsafe extern "C" fn __revmc_builtin_coinbase(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
415    *slot = EvmWord::from_be_bytes(ecx.host.beneficiary().into_word());
416}
417
418#[unsafe(no_mangle)]
419pub unsafe extern "C" fn __revmc_builtin_timestamp(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
420    *slot = ecx.host.timestamp().into();
421}
422
423#[unsafe(no_mangle)]
424pub unsafe extern "C" fn __revmc_builtin_number(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
425    *slot = ecx.host.block_number().into();
426}
427
428#[unsafe(no_mangle)]
429pub unsafe extern "C" fn __revmc_builtin_difficulty(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
430    *slot = if ecx.spec_id.is_enabled_in(SpecId::MERGE) {
431        ecx.host.prevrandao().unwrap().into()
432    } else {
433        ecx.host.difficulty().into()
434    };
435}
436
437#[unsafe(no_mangle)]
438pub unsafe extern "C" fn __revmc_builtin_gaslimit(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
439    *slot = ecx.host.gas_limit().into();
440}
441
442#[unsafe(no_mangle)]
443pub unsafe extern "C" fn __revmc_builtin_chainid(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
444    *slot = ecx.host.chain_id().into();
445}
446
447#[unsafe(no_mangle)]
448pub unsafe extern "C" fn __revmc_builtin_self_balance(
449    ecx: &mut EvmContext<'_>,
450    slot: &mut EvmWord,
451) -> BuiltinResult {
452    let state = ecx.host.balance(ecx.input.target_address).ok_or_fatal()?;
453    *slot = state.data.into();
454    Ok(())
455}
456
457#[unsafe(no_mangle)]
458pub unsafe extern "C" fn __revmc_builtin_basefee(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
459    *slot = ecx.host.basefee().into();
460}
461
462#[unsafe(no_mangle)]
463pub unsafe extern "C" fn __revmc_builtin_blob_hash(ecx: &EvmContext<'_>, index_ptr: &mut EvmWord) {
464    let index = index_ptr.to_u256();
465    let index_usize = as_usize_saturated!(index);
466    *index_ptr = ecx.host.blob_hash(index_usize).unwrap_or_default().into();
467}
468
469#[unsafe(no_mangle)]
470pub unsafe extern "C" fn __revmc_builtin_blob_base_fee(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
471    *slot = ecx.host.blob_gasprice().into();
472}
473
474#[unsafe(no_mangle)]
475pub unsafe extern "C" fn __revmc_builtin_mresize(ecx: &mut EvmContext<'_>, min_size: u64) -> BuiltinResult {
476    ensure_memory(ecx, min_size as usize, 0)
477}
478
479#[unsafe(no_mangle)]
480pub unsafe extern "C" fn __revmc_builtin_slot_num(ecx: &EvmContext<'_>, slot: &mut EvmWord) {
481    *slot = ecx.host.slot_num().into();
482}
483
484#[unsafe(no_mangle)]
485pub unsafe extern "C" fn __revmc_builtin_sload(
486    ecx: &mut EvmContext<'_>,
487    index: &mut EvmWord,
488) -> BuiltinResult {
489    let address = ecx.input.target_address;
490    let key = index.to_u256();
491    if ecx.spec_id.is_enabled_in(SpecId::BERLIN) {
492        let additional_cold_cost = ecx.gas_params.cold_storage_additional_cost();
493        let skip_cold = ecx.gas.remaining() < additional_cold_cost;
494        let storage = ecx.host.sload_skip_cold_load(address, key, skip_cold)?;
495        if storage.is_cold {
496            gas!(ecx, additional_cold_cost);
497        }
498        *index = storage.data.into();
499    } else {
500        let storage = ecx.host.sload(address, key).ok_or_fatal()?;
501        *index = storage.data.into();
502    }
503
504    Ok(())
505}
506
507#[unsafe(no_mangle)]
508pub unsafe extern "C" fn __revmc_builtin_sload_c(
509    ecx: &mut EvmContext<'_>,
510    index: &mut EvmWord,
511    key: u64,
512) -> BuiltinResult {
513    *index = U256::from(key).into();
514    __revmc_builtin_sload(ecx, index);
515    Ok(())
516}
517
518#[unsafe(no_mangle)]
519pub unsafe extern "C" fn __revmc_builtin_sstore(
520    ecx: &mut EvmContext<'_>,
521    sp: &mut [EvmWord; 2],
522) -> BuiltinResult {
523    let rev![index, value] = sp;
524    ensure_non_staticcall!(ecx);
525
526    let target = ecx.input.target_address;
527    let is_istanbul = ecx.spec_id.is_enabled_in(SpecId::ISTANBUL);
528
529    // EIP-2200: If gasleft is less than or equal to gas stipend, fail with OOG.
530    if is_istanbul && ecx.gas.remaining() <= ecx.gas_params.call_stipend() {
531        return Err(InstructionResult::ReentrancySentryOOG.into());
532    }
533
534    gas!(ecx, ecx.gas_params.sstore_static_gas());
535
536    let state_load = if ecx.spec_id.is_enabled_in(SpecId::BERLIN) {
537        let additional_cold_cost = ecx.gas_params.cold_storage_additional_cost();
538        let skip_cold = ecx.gas.remaining() < additional_cold_cost;
539        ecx.host.sstore_skip_cold_load(target, index.to_u256(), value.to_u256(), skip_cold)?
540    } else {
541        ecx.host.sstore(target, index.to_u256(), value.to_u256()).ok_or_fatal()?
542    };
543
544    let gp = &ecx.gas_params;
545    gas!(ecx, gp.sstore_dynamic_gas(is_istanbul, &state_load.data, state_load.is_cold));
546
547    // State gas for new slot creation (EIP-8037).
548    if ecx.host.is_amsterdam_eip8037_enabled() {
549        state_gas!(ecx, gp.sstore_state_gas(&state_load.data));
550
551        let refill = gp.sstore_state_gas_refill(&state_load.data);
552        if refill > 0 {
553            ecx.gas.refill_reservoir(refill);
554        }
555    }
556
557    ecx.gas.record_refund(gp.sstore_refund(is_istanbul, &state_load.data));
558    Ok(())
559}
560
561#[unsafe(no_mangle)]
562pub unsafe extern "C" fn __revmc_builtin_tload(ecx: &mut EvmContext<'_>, key: &mut EvmWord) {
563    *key = ecx.host.tload(ecx.input.target_address, key.to_u256()).into();
564}
565
566#[unsafe(no_mangle)]
567pub unsafe extern "C" fn __revmc_builtin_tstore(
568    ecx: &mut EvmContext<'_>,
569    sp: &mut [EvmWord; 2],
570) -> BuiltinResult {
571    let rev![key, value] = sp;
572    ensure_non_staticcall!(ecx);
573    ecx.host.tstore(ecx.input.target_address, key.to_u256(), value.to_u256());
574    Ok(())
575}
576
577#[unsafe(no_mangle)]
578pub unsafe extern "C" fn __revmc_builtin_mcopy(
579    ecx: &mut EvmContext<'_>,
580    sp: &mut [EvmWord; 3],
581) -> BuiltinResult {
582    let rev![dst, src, len] = sp;
583    let len = try_into_usize!(len);
584    gas!(ecx, ecx.gas_params.mcopy_cost(len));
585    if len != 0 {
586        let dst = try_into_usize!(dst);
587        let src = try_into_usize!(src);
588        ensure_memory(ecx, dst.max(src), len)?;
589        ecx.memory.copy(dst, src, len);
590    }
591    Ok(())
592}
593
594#[unsafe(no_mangle)]
595pub unsafe extern "C" fn __revmc_builtin_log(
596    ecx: &mut EvmContext<'_>,
597    sp: *mut EvmWord,
598    n: u8,
599) -> BuiltinResult {
600    ensure_non_staticcall!(ecx);
601    assume!(n <= 4, "invalid log topic count: {n}");
602    let sp = sp.add(n as usize);
603    read_words!(sp, offset, len);
604    let len = try_into_usize!(len);
605    gas!(ecx, ecx.gas_params.log_cost(n, len as u64));
606    let data = if len != 0 {
607        let offset = try_into_usize!(offset);
608        ensure_memory(ecx, offset, len)?;
609        Bytes::copy_from_slice(&ecx.memory.slice(offset..offset + len))
610    } else {
611        Bytes::new()
612    };
613
614    let mut topics = Vec::with_capacity(n as usize);
615    for i in 1..=n {
616        topics.push(sp.sub(i as usize).read().to_be_bytes());
617    }
618
619    let log = Log {
620        address: ecx.input.target_address,
621        data: LogData::new(topics, data).expect("too many topics"),
622    };
623    if let Some(on_log) = &mut ecx.on_log {
624        ecx.host.log(log.clone());
625        on_log(&log);
626    } else {
627        ecx.host.log(log);
628    }
629    Ok(())
630}
631
632#[unsafe(no_mangle)]
633pub unsafe extern "C" fn __revmc_builtin_create(
634    ecx: &mut EvmContext<'_>,
635    sp: *mut EvmWord,
636    create_kind: CreateKind,
637) -> BuiltinResult {
638    ensure_non_staticcall!(ecx);
639
640    let len = match create_kind {
641        CreateKind::Create => 3,
642        CreateKind::Create2 => 4,
643    };
644    let mut sp = sp.add(len);
645    pop!(sp; value, code_offset, len);
646
647    let len = try_into_usize!(len);
648    let code = if len != 0 {
649        if ecx.spec_id.is_enabled_in(SpecId::SHANGHAI) {
650            // Limit is set as double of max contract bytecode size
651            let max_initcode_size = ecx.host.max_initcode_size();
652            if len > max_initcode_size {
653                return Err(InstructionResult::CreateInitCodeSizeLimit.into());
654            }
655            gas!(ecx, ecx.gas_params.initcode_cost(len));
656        }
657
658        let code_offset = try_into_usize!(code_offset);
659        ensure_memory(ecx, code_offset, len)?;
660        Bytes::copy_from_slice(&ecx.memory.slice(code_offset..code_offset + len))
661    } else {
662        Bytes::new()
663    };
664
665    let is_create2 = create_kind == CreateKind::Create2;
666    let gp = &ecx.gas_params;
667    gas!(ecx, if is_create2 { gp.create2_cost(len) } else { gp.create_cost() });
668
669    let scheme = if is_create2 {
670        pop!(sp; salt);
671        CreateScheme::Create2 { salt: salt.to_u256() }
672    } else {
673        CreateScheme::Create
674    };
675
676    // State gas for account creation + contract metadata (EIP-8037).
677    if ecx.host.is_amsterdam_eip8037_enabled() {
678        state_gas!(ecx, ecx.gas_params.create_state_gas());
679    }
680
681    let mut gas_limit = ecx.gas.remaining();
682    if ecx.spec_id.is_enabled_in(SpecId::TANGERINE) {
683        gas_limit = ecx.gas_params.call_stipend_reduction(gas_limit);
684    }
685    gas!(ecx, gas_limit);
686
687    *ecx.next_action =
688        Some(InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs::new(
689            ecx.input.target_address,
690            scheme,
691            value.to_u256(),
692            code,
693            gas_limit,
694            ecx.gas.reservoir(),
695        )))));
696
697    Ok(())
698}
699
700#[unsafe(no_mangle)]
701pub unsafe extern "C" fn __revmc_builtin_call(
702    ecx: &mut EvmContext<'_>,
703    sp: *mut EvmWord,
704    call_kind: CallKind,
705) -> BuiltinResult {
706    let len = match call_kind {
707        CallKind::Call | CallKind::CallCode => 7,
708        CallKind::DelegateCall | CallKind::StaticCall => 6,
709    };
710    let mut sp = sp.add(len);
711
712    pop!(sp; local_gas_limit, to);
713    let local_gas_limit = local_gas_limit.to_u256();
714    let to = to.to_address();
715
716    // max gas limit is not possible in real ethereum situation.
717    // But for tests we would not like to fail on this.
718    // Gas limit for subcall is taken as min of this value and current gas limit.
719    let local_gas_limit = as_u64_saturated!(local_gas_limit);
720
721    let value = match call_kind {
722        CallKind::Call | CallKind::CallCode => {
723            pop!(sp; value);
724            let value = value.to_u256();
725            if call_kind == CallKind::Call && ecx.is_static && value != U256::ZERO {
726                return Err(InstructionResult::CallNotAllowedInsideStatic.into());
727            }
728            value
729        }
730        CallKind::DelegateCall | CallKind::StaticCall => U256::ZERO,
731    };
732    let transfers_value = value != U256::ZERO;
733
734    pop!(sp; in_offset, in_len, out_offset, out_len);
735
736    let in_len = try_into_usize!(in_len);
737    let input = if in_len != 0 {
738        let in_offset = try_into_usize!(in_offset);
739        ensure_memory(ecx, in_offset, in_len)?;
740        let local_offset = ecx.memory.local_memory_offset();
741        let start = in_offset.saturating_add(local_offset);
742        start..start.saturating_add(in_len)
743    } else {
744        usize::MAX..usize::MAX
745    };
746
747    let out_len = try_into_usize!(out_len);
748    let out_offset = if out_len != 0 {
749        let out_offset = try_into_usize!(out_offset);
750        ensure_memory(ecx, out_offset, out_len)?;
751        out_offset
752    } else {
753        usize::MAX // unrealistic value so we are sure it is not used
754    };
755
756    if transfers_value {
757        gas!(ecx, ecx.gas_params.transfer_value_cost());
758    }
759
760    // Match interpreter call path: load delegated account and pass resolved bytecode/hash
761    // through CallInputs::known_bytecode (covers EIP-7702 delegation and EOF execution).
762    let (dynamic_gas, state_gas_cost, bytecode, code_hash) =
763        revm_interpreter::instructions::contract::load_account_delegated(
764            ecx.host,
765            ecx.spec_id,
766            ecx.gas.remaining(),
767            to,
768            transfers_value,
769            call_kind == CallKind::Call,
770        )?;
771    let charged_new_account_state_gas = state_gas_cost > 0;
772
773    gas!(ecx, dynamic_gas);
774
775    // Deduct state gas (EIP-8037) if any.
776    state_gas!(ecx, state_gas_cost);
777
778    // EIP-150: Gas cost changes for IO-heavy operations
779    let mut gas_limit = if ecx.spec_id.is_enabled_in(SpecId::TANGERINE) {
780        let gas = ecx.gas.remaining();
781        ecx.gas_params.call_stipend_reduction(gas).min(local_gas_limit)
782    } else {
783        local_gas_limit
784    };
785
786    gas!(ecx, gas_limit);
787
788    // Add call stipend if there is value to be transferred.
789    if matches!(call_kind, CallKind::Call | CallKind::CallCode) && transfers_value {
790        gas_limit = gas_limit.saturating_add(ecx.gas_params.call_stipend());
791    }
792
793    *ecx.next_action = Some(InterpreterAction::NewFrame(revm_interpreter::FrameInput::Call(
794        Box::new(CallInputs {
795            input: CallInput::SharedBuffer(input),
796            return_memory_offset: out_offset..out_offset + out_len,
797            gas_limit,
798            bytecode_address: to,
799            known_bytecode: (code_hash, bytecode),
800            target_address: if matches!(call_kind, CallKind::DelegateCall | CallKind::CallCode) {
801                ecx.input.target_address
802            } else {
803                to
804            },
805            caller: if call_kind == CallKind::DelegateCall {
806                ecx.input.caller_address
807            } else {
808                ecx.input.target_address
809            },
810            value: if call_kind == CallKind::DelegateCall {
811                CallValue::Apparent(ecx.input.call_value)
812            } else {
813                CallValue::Transfer(value)
814            },
815            scheme: call_kind.into(),
816            is_static: ecx.is_static || call_kind == CallKind::StaticCall,
817            reservoir: ecx.gas.reservoir(),
818            charged_new_account_state_gas,
819        }),
820    )));
821
822    Ok(())
823}
824
825#[unsafe(no_mangle)]
826pub unsafe extern "C" fn __revmc_builtin_do_return(
827    ecx: &mut EvmContext<'_>,
828    sp: &mut [EvmWord; 2],
829    result: InstructionResult,
830) -> BuiltinResult {
831    let rev![offset, len] = sp;
832    let len = try_into_usize!(len);
833    let output = if len != 0 {
834        let offset = try_into_usize!(offset);
835        ensure_memory(ecx, offset, len)?;
836        ecx.memory.slice(offset..offset + len).to_vec().into()
837    } else {
838        Bytes::new()
839    };
840    *ecx.next_action =
841        Some(InterpreterAction::Return(InterpreterResult { output, gas: ecx.gas, result }));
842    Err(result.into())
843}
844
845#[unsafe(no_mangle)]
846pub unsafe extern "C" fn __revmc_builtin_do_return_cc(
847    ecx: &mut EvmContext<'_>,
848    offset: u64,
849    len: u64,
850    result: InstructionResult,
851) -> BuiltinResult {
852    let offset = offset as usize;
853    let len = len as usize;
854    let output = if len != 0 {
855        ensure_memory(ecx, offset, len)?;
856        ecx.memory.slice(offset..offset + len).to_vec().into()
857    } else {
858        Bytes::new()
859    };
860    *ecx.next_action =
861        Some(InterpreterAction::Return(InterpreterResult { output, gas: ecx.gas, result }));
862    Err(result.into())
863}
864
865#[unsafe(no_mangle)]
866pub unsafe extern "C" fn __revmc_builtin_selfdestruct(
867    ecx: &mut EvmContext<'_>,
868    target: &mut EvmWord,
869) -> BuiltinResult {
870    ensure_non_staticcall!(ecx);
871
872    let cold_load_gas = ecx.gas_params.selfdestruct_cold_cost();
873    let skip_cold_load = ecx.gas.remaining() < cold_load_gas;
874    let res =
875        ecx.host.selfdestruct(ecx.input.target_address, target.to_address(), skip_cold_load)?;
876
877    // EIP-161: State trie clearing (invariant-preserving alternative)
878    let should_charge_topup = if ecx.spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
879        res.had_value && !res.target_exists
880    } else {
881        !res.target_exists
882    };
883
884    gas!(ecx, ecx.gas_params.selfdestruct_cost(should_charge_topup, res.is_cold));
885
886    // State gas for new account creation (EIP-8037).
887    if ecx.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
888        state_gas!(ecx, ecx.gas_params.new_account_state_gas());
889    }
890
891    if !res.previously_destroyed {
892        ecx.gas.record_refund(ecx.gas_params.selfdestruct_refund());
893    }
894
895    Err(InstructionResult::SelfDestruct.into())
896}
897
898}