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