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