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
552    ecx.gas.record_refund(gp.sstore_refund(is_istanbul, &state_load.data));
553    Ok(())
554}
555
556#[unsafe(no_mangle)]
557pub unsafe extern "C" fn __revmc_builtin_tload(ecx: &mut EvmContext<'_>, key: &mut EvmWord) {
558    *key = ecx.host.tload(ecx.input.target_address, key.to_u256()).into();
559}
560
561#[unsafe(no_mangle)]
562pub unsafe extern "C" fn __revmc_builtin_tstore(
563    ecx: &mut EvmContext<'_>,
564    sp: &mut [EvmWord; 2],
565) -> BuiltinResult {
566    let rev![key, value] = sp;
567    ensure_non_staticcall!(ecx);
568    ecx.host.tstore(ecx.input.target_address, key.to_u256(), value.to_u256());
569    Ok(())
570}
571
572#[unsafe(no_mangle)]
573pub unsafe extern "C" fn __revmc_builtin_mcopy(
574    ecx: &mut EvmContext<'_>,
575    sp: &mut [EvmWord; 3],
576) -> BuiltinResult {
577    let rev![dst, src, len] = sp;
578    let len = try_into_usize!(len);
579    gas!(ecx, ecx.gas_params.mcopy_cost(len));
580    if len != 0 {
581        let dst = try_into_usize!(dst);
582        let src = try_into_usize!(src);
583        ensure_memory(ecx, dst.max(src), len)?;
584        ecx.memory.copy(dst, src, len);
585    }
586    Ok(())
587}
588
589#[unsafe(no_mangle)]
590pub unsafe extern "C" fn __revmc_builtin_log(
591    ecx: &mut EvmContext<'_>,
592    sp: *mut EvmWord,
593    n: u8,
594) -> BuiltinResult {
595    ensure_non_staticcall!(ecx);
596    assume!(n <= 4, "invalid log topic count: {n}");
597    let sp = sp.add(n as usize);
598    read_words!(sp, offset, len);
599    let len = try_into_usize!(len);
600    gas!(ecx, ecx.gas_params.log_cost(n, len as u64));
601    let data = if len != 0 {
602        let offset = try_into_usize!(offset);
603        ensure_memory(ecx, offset, len)?;
604        Bytes::copy_from_slice(&ecx.memory.slice(offset..offset + len))
605    } else {
606        Bytes::new()
607    };
608
609    let mut topics = Vec::with_capacity(n as usize);
610    for i in 1..=n {
611        topics.push(sp.sub(i as usize).read().to_be_bytes());
612    }
613
614    let log = Log {
615        address: ecx.input.target_address,
616        data: LogData::new(topics, data).expect("too many topics"),
617    };
618    if let Some(on_log) = &mut ecx.on_log {
619        ecx.host.log(log.clone());
620        on_log(&log);
621    } else {
622        ecx.host.log(log);
623    }
624    Ok(())
625}
626
627#[unsafe(no_mangle)]
628pub unsafe extern "C" fn __revmc_builtin_create(
629    ecx: &mut EvmContext<'_>,
630    sp: *mut EvmWord,
631    create_kind: CreateKind,
632) -> BuiltinResult {
633    ensure_non_staticcall!(ecx);
634
635    let len = match create_kind {
636        CreateKind::Create => 3,
637        CreateKind::Create2 => 4,
638    };
639    let mut sp = sp.add(len);
640    pop!(sp; value, code_offset, len);
641
642    let len = try_into_usize!(len);
643    let code = if len != 0 {
644        if ecx.spec_id.is_enabled_in(SpecId::SHANGHAI) {
645            // Limit is set as double of max contract bytecode size
646            let max_initcode_size = ecx.host.max_initcode_size();
647            if len > max_initcode_size {
648                return Err(InstructionResult::CreateInitCodeSizeLimit.into());
649            }
650            gas!(ecx, ecx.gas_params.initcode_cost(len));
651        }
652
653        let code_offset = try_into_usize!(code_offset);
654        ensure_memory(ecx, code_offset, len)?;
655        Bytes::copy_from_slice(&ecx.memory.slice(code_offset..code_offset + len))
656    } else {
657        Bytes::new()
658    };
659
660    let is_create2 = create_kind == CreateKind::Create2;
661    let gp = &ecx.gas_params;
662    gas!(ecx, if is_create2 { gp.create2_cost(len) } else { gp.create_cost() });
663
664    let scheme = if is_create2 {
665        pop!(sp; salt);
666        CreateScheme::Create2 { salt: salt.to_u256() }
667    } else {
668        CreateScheme::Create
669    };
670
671    // State gas for account creation + contract metadata (EIP-8037).
672    if ecx.host.is_amsterdam_eip8037_enabled() {
673        state_gas!(ecx, ecx.gas_params.create_state_gas());
674    }
675
676    let mut gas_limit = ecx.gas.remaining();
677    if ecx.spec_id.is_enabled_in(SpecId::TANGERINE) {
678        gas_limit = ecx.gas_params.call_stipend_reduction(gas_limit);
679    }
680    gas!(ecx, gas_limit);
681
682    *ecx.next_action =
683        Some(InterpreterAction::NewFrame(FrameInput::Create(Box::new(CreateInputs::new(
684            ecx.input.target_address,
685            scheme,
686            value.to_u256(),
687            code,
688            gas_limit,
689            ecx.gas.reservoir(),
690        )))));
691
692    Ok(())
693}
694
695#[unsafe(no_mangle)]
696pub unsafe extern "C" fn __revmc_builtin_call(
697    ecx: &mut EvmContext<'_>,
698    sp: *mut EvmWord,
699    call_kind: CallKind,
700) -> BuiltinResult {
701    let len = match call_kind {
702        CallKind::Call | CallKind::CallCode => 7,
703        CallKind::DelegateCall | CallKind::StaticCall => 6,
704    };
705    let mut sp = sp.add(len);
706
707    pop!(sp; local_gas_limit, to);
708    let local_gas_limit = local_gas_limit.to_u256();
709    let to = to.to_address();
710
711    // max gas limit is not possible in real ethereum situation.
712    // But for tests we would not like to fail on this.
713    // Gas limit for subcall is taken as min of this value and current gas limit.
714    let local_gas_limit = as_u64_saturated!(local_gas_limit);
715
716    let value = match call_kind {
717        CallKind::Call | CallKind::CallCode => {
718            pop!(sp; value);
719            let value = value.to_u256();
720            if call_kind == CallKind::Call && ecx.is_static && value != U256::ZERO {
721                return Err(InstructionResult::CallNotAllowedInsideStatic.into());
722            }
723            value
724        }
725        CallKind::DelegateCall | CallKind::StaticCall => U256::ZERO,
726    };
727    let transfers_value = value != U256::ZERO;
728
729    pop!(sp; in_offset, in_len, out_offset, out_len);
730
731    let in_len = try_into_usize!(in_len);
732    let input = if in_len != 0 {
733        let in_offset = try_into_usize!(in_offset);
734        ensure_memory(ecx, in_offset, in_len)?;
735        let local_offset = ecx.memory.local_memory_offset();
736        let start = in_offset.saturating_add(local_offset);
737        start..start.saturating_add(in_len)
738    } else {
739        usize::MAX..usize::MAX
740    };
741
742    let out_len = try_into_usize!(out_len);
743    let out_offset = if out_len != 0 {
744        let out_offset = try_into_usize!(out_offset);
745        ensure_memory(ecx, out_offset, out_len)?;
746        out_offset
747    } else {
748        usize::MAX // unrealistic value so we are sure it is not used
749    };
750
751    if transfers_value {
752        gas!(ecx, ecx.gas_params.transfer_value_cost());
753    }
754
755    // Match interpreter call path: load delegated account and pass resolved bytecode/hash
756    // through CallInputs::known_bytecode (covers EIP-7702 delegation and EOF execution).
757    let (dynamic_gas, state_gas_cost, bytecode, code_hash) =
758        revm_interpreter::instructions::contract::load_account_delegated(
759            ecx.host,
760            ecx.spec_id,
761            ecx.gas.remaining(),
762            to,
763            transfers_value,
764            call_kind == CallKind::Call,
765        )?;
766
767    gas!(ecx, dynamic_gas);
768
769    // Deduct state gas (EIP-8037) if any.
770    state_gas!(ecx, state_gas_cost);
771
772    // EIP-150: Gas cost changes for IO-heavy operations
773    let mut gas_limit = if ecx.spec_id.is_enabled_in(SpecId::TANGERINE) {
774        let gas = ecx.gas.remaining();
775        ecx.gas_params.call_stipend_reduction(gas).min(local_gas_limit)
776    } else {
777        local_gas_limit
778    };
779
780    gas!(ecx, gas_limit);
781
782    // Add call stipend if there is value to be transferred.
783    if matches!(call_kind, CallKind::Call | CallKind::CallCode) && transfers_value {
784        gas_limit = gas_limit.saturating_add(ecx.gas_params.call_stipend());
785    }
786
787    *ecx.next_action = Some(InterpreterAction::NewFrame(revm_interpreter::FrameInput::Call(
788        Box::new(CallInputs {
789            input: CallInput::SharedBuffer(input),
790            return_memory_offset: out_offset..out_offset + out_len,
791            gas_limit,
792            bytecode_address: to,
793            known_bytecode: (code_hash, bytecode),
794            target_address: if matches!(call_kind, CallKind::DelegateCall | CallKind::CallCode) {
795                ecx.input.target_address
796            } else {
797                to
798            },
799            caller: if call_kind == CallKind::DelegateCall {
800                ecx.input.caller_address
801            } else {
802                ecx.input.target_address
803            },
804            value: if call_kind == CallKind::DelegateCall {
805                CallValue::Apparent(ecx.input.call_value)
806            } else {
807                CallValue::Transfer(value)
808            },
809            scheme: call_kind.into(),
810            is_static: ecx.is_static || call_kind == CallKind::StaticCall,
811            reservoir: ecx.gas.reservoir(),
812        }),
813    )));
814
815    Ok(())
816}
817
818#[unsafe(no_mangle)]
819pub unsafe extern "C" fn __revmc_builtin_do_return(
820    ecx: &mut EvmContext<'_>,
821    sp: &mut [EvmWord; 2],
822    result: InstructionResult,
823) -> BuiltinResult {
824    let rev![offset, len] = sp;
825    let len = try_into_usize!(len);
826    let output = if len != 0 {
827        let offset = try_into_usize!(offset);
828        ensure_memory(ecx, offset, len)?;
829        ecx.memory.slice(offset..offset + len).to_vec().into()
830    } else {
831        Bytes::new()
832    };
833    *ecx.next_action =
834        Some(InterpreterAction::Return(InterpreterResult { output, gas: ecx.gas, result }));
835    Err(result.into())
836}
837
838#[unsafe(no_mangle)]
839pub unsafe extern "C" fn __revmc_builtin_do_return_cc(
840    ecx: &mut EvmContext<'_>,
841    offset: u64,
842    len: u64,
843    result: InstructionResult,
844) -> BuiltinResult {
845    let offset = offset as usize;
846    let len = len as usize;
847    let output = if len != 0 {
848        ensure_memory(ecx, offset, len)?;
849        ecx.memory.slice(offset..offset + len).to_vec().into()
850    } else {
851        Bytes::new()
852    };
853    *ecx.next_action =
854        Some(InterpreterAction::Return(InterpreterResult { output, gas: ecx.gas, result }));
855    Err(result.into())
856}
857
858#[unsafe(no_mangle)]
859pub unsafe extern "C" fn __revmc_builtin_selfdestruct(
860    ecx: &mut EvmContext<'_>,
861    target: &mut EvmWord,
862) -> BuiltinResult {
863    ensure_non_staticcall!(ecx);
864
865    let cold_load_gas = ecx.gas_params.selfdestruct_cold_cost();
866    let skip_cold_load = ecx.gas.remaining() < cold_load_gas;
867    let res =
868        ecx.host.selfdestruct(ecx.input.target_address, target.to_address(), skip_cold_load)?;
869
870    // EIP-161: State trie clearing (invariant-preserving alternative)
871    let should_charge_topup = if ecx.spec_id.is_enabled_in(SpecId::SPURIOUS_DRAGON) {
872        res.had_value && !res.target_exists
873    } else {
874        !res.target_exists
875    };
876
877    gas!(ecx, ecx.gas_params.selfdestruct_cost(should_charge_topup, res.is_cold));
878
879    // State gas for new account creation (EIP-8037).
880    if ecx.host.is_amsterdam_eip8037_enabled() && should_charge_topup {
881        state_gas!(ecx, ecx.gas_params.new_account_state_gas());
882    }
883
884    if !res.previously_destroyed {
885        ecx.gas.record_refund(ecx.gas_params.selfdestruct_refund());
886    }
887
888    Err(InstructionResult::SelfDestruct.into())
889}
890
891}