Skip to main content

revm_interpreter/interpreter_action/
call_inputs.rs

1use crate::interpreter_types::MemoryTr;
2use context_interface::{ContextTr, LocalContextTr};
3use core::ops::Range;
4use primitives::{Address, Bytes, B256, U256};
5use state::Bytecode;
6
7/// Input enum for a call.
8///
9/// As CallInput uses shared memory buffer it can get overridden if not used directly when call happens.
10#[derive(Clone, Debug, PartialEq, Eq)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub enum CallInput {
13    /// Bytes of the call data.
14    Bytes(Bytes),
15    /// The Range points to the SharedMemory buffer. Buffer can be found in [`context_interface::LocalContextTr::shared_memory_buffer_slice`] function.
16    /// And can be accessed with `evm.ctx().local().shared_memory_buffer()`
17    ///
18    /// # Warning
19    ///
20    /// Use it with caution, CallInput shared buffer can be overridden if context from child call is returned so
21    /// recommendation is to fetch buffer at first Inspector call and clone it from [`context_interface::LocalContextTr::shared_memory_buffer_slice`] function.
22    SharedBuffer(Range<usize>),
23}
24
25impl CallInput {
26    /// Returns the length of the call input.
27    #[inline]
28    pub fn len(&self) -> usize {
29        match self {
30            Self::Bytes(bytes) => bytes.len(),
31            Self::SharedBuffer(range) => range.len(),
32        }
33    }
34
35    /// Returns `true` if the call input is empty.
36    #[inline]
37    pub fn is_empty(&self) -> bool {
38        self.len() == 0
39    }
40
41    /// Returns the bytes of the call input from the given context.
42    #[inline]
43    pub fn as_bytes<'a, CTX: ContextTr>(
44        &'a self,
45        ctx: &'a CTX,
46    ) -> impl core::ops::Deref<Target = [u8]> + 'a {
47        self.as_bytes_local(ctx.local())
48    }
49
50    /// Returns the bytes of the call input from the given local context.
51    #[inline]
52    pub fn as_bytes_local<'a, L: LocalContextTr>(
53        &'a self,
54        local: &'a L,
55    ) -> impl core::ops::Deref<Target = [u8]> + 'a {
56        match self {
57            Self::Bytes(bytes) => CallInputRef::Bytes(bytes.as_ref()),
58            Self::SharedBuffer(range) => {
59                CallInputRef::SharedBuffer(local.shared_memory_buffer_slice(range.clone()))
60            }
61        }
62    }
63
64    /// Returns the bytes of the call input from the given memory.
65    #[inline]
66    pub fn as_bytes_memory<'a, M: MemoryTr>(
67        &'a self,
68        memory: &'a M,
69    ) -> impl core::ops::Deref<Target = [u8]> + 'a {
70        match self {
71            Self::Bytes(bytes) => CallInputRef::Bytes(bytes.as_ref()),
72            Self::SharedBuffer(range) => {
73                CallInputRef::SharedBuffer(Some(memory.global_slice(range.clone())))
74            }
75        }
76    }
77
78    /// Returns the bytes of the call input.
79    ///
80    /// SharedMemory buffer can be shrunked or overwritten if the child call returns the
81    /// shared memory context to its parent, the range in `CallInput::SharedBuffer` can show unexpected data.
82    ///
83    /// # Allocation
84    ///
85    /// If this `CallInput` is a `SharedBuffer`, the slice will be copied
86    /// into a fresh `Bytes` buffer, which can pose a performance penalty.
87    pub fn bytes<CTX: ContextTr>(&self, ctx: &CTX) -> Bytes {
88        self.bytes_local(ctx.local())
89    }
90
91    /// Returns the bytes of the call input from the given local context.
92    ///
93    /// # Allocation
94    ///
95    /// If the call input is a `SharedBuffer`, this function will allocate a new `Bytes` object.
96    #[inline]
97    pub fn bytes_local<L: LocalContextTr>(&self, local: &L) -> Bytes {
98        match self {
99            CallInput::Bytes(bytes) => bytes.clone(),
100            CallInput::SharedBuffer(range) => local
101                .shared_memory_buffer_slice(range.clone())
102                .map(|b| Bytes::from(b.to_vec()))
103                .unwrap_or_default(),
104        }
105    }
106}
107
108impl Default for CallInput {
109    #[inline]
110    fn default() -> Self {
111        CallInput::Bytes(Bytes::new())
112    }
113}
114
115enum CallInputRef<'a> {
116    Bytes(&'a [u8]),
117    SharedBuffer(Option<core::cell::Ref<'a, [u8]>>),
118}
119
120impl core::ops::Deref for CallInputRef<'_> {
121    type Target = [u8];
122
123    #[inline]
124    fn deref(&self) -> &Self::Target {
125        match self {
126            Self::Bytes(x) => x,
127            Self::SharedBuffer(x) => x.as_deref().unwrap_or_default(),
128        }
129    }
130}
131
132/// Inputs for a call.
133#[derive(Clone, Debug, PartialEq, Eq)]
134#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
135pub struct CallInputs {
136    /// The call data of the call.
137    pub input: CallInput,
138    /// The return memory offset where the output of the call is written.
139    pub return_memory_offset: Range<usize>,
140    /// The gas limit of the call.
141    pub gas_limit: u64,
142    /// State gas reservoir (EIP-8037). Passed from parent frame to child frame.
143    pub reservoir: u64,
144    /// The account address of bytecode that is going to be executed.
145    ///
146    /// Previously `context.code_address`.
147    pub bytecode_address: Address,
148    /// Known bytecode and its hash.
149    /// If None, bytecode will be loaded from the account at `bytecode_address`.
150    /// If Some((hash, bytecode)), the provided bytecode and hash will be used.
151    pub known_bytecode: (B256, Bytecode),
152    /// Target address, this account storage is going to be modified.
153    ///
154    /// Previously `context.address`.
155    pub target_address: Address,
156    /// This caller is invoking the call.
157    ///
158    /// Previously `context.caller`.
159    pub caller: Address,
160    /// Call value.
161    ///
162    /// **Note**: This value may not necessarily be transferred from caller to callee, see [`CallValue`].
163    ///
164    /// Previously `transfer.value` or `context.apparent_value`.
165    pub value: CallValue,
166    /// The call scheme.
167    ///
168    /// Previously `context.scheme`.
169    pub scheme: CallScheme,
170    /// Whether the call is a static call, or is initiated inside a static call.
171    pub is_static: bool,
172    /// EIP-8037: set when the CALL opcode upfront-charged `new_account_state_gas`
173    /// for creating an empty account at `target_address` via value transfer.
174    /// On revert/halt of the resulting frame the parent must refund this charge
175    /// to the reservoir, matching the CREATE pattern.
176    pub charged_new_account_state_gas: bool,
177}
178
179impl CallInputs {
180    /// Returns `true` if the call will transfer a non-zero value.
181    #[inline]
182    pub fn transfers_value(&self) -> bool {
183        self.value.transfer().is_some_and(|x| x > U256::ZERO)
184    }
185
186    /// Returns the transfer value.
187    ///
188    /// This is the value that is transferred from caller to callee, see [`CallValue`].
189    #[inline]
190    pub const fn transfer_value(&self) -> Option<U256> {
191        self.value.transfer()
192    }
193
194    /// Returns the **apparent** call value.
195    ///
196    /// This value is not actually transferred, see [`CallValue`].
197    #[inline]
198    pub const fn apparent_value(&self) -> Option<U256> {
199        self.value.apparent()
200    }
201
202    /// Returns the address of the transfer source account.
203    ///
204    /// This is only meaningful if `transfers_value` is `true`.
205    #[inline]
206    pub const fn transfer_from(&self) -> Address {
207        self.caller
208    }
209
210    /// Returns the address of the transfer target account.
211    ///
212    /// This is only meaningful if `transfers_value` is `true`.
213    #[inline]
214    pub const fn transfer_to(&self) -> Address {
215        self.target_address
216    }
217
218    /// Returns the call value, regardless of the transfer value type.
219    ///
220    /// **Note**: This value may not necessarily be transferred from caller to callee, see [`CallValue`].
221    #[inline]
222    pub const fn call_value(&self) -> U256 {
223        self.value.get()
224    }
225}
226
227/// Call scheme.
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
229#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
230pub enum CallScheme {
231    /// `CALL`.
232    Call,
233    /// `CALLCODE`
234    CallCode,
235    /// `DELEGATECALL`
236    DelegateCall,
237    /// `STATICCALL`
238    StaticCall,
239}
240
241impl CallScheme {
242    /// Returns true if it is `CALL`.
243    pub const fn is_call(&self) -> bool {
244        matches!(self, Self::Call)
245    }
246
247    /// Returns true if it is `CALLCODE`.
248    pub const fn is_call_code(&self) -> bool {
249        matches!(self, Self::CallCode)
250    }
251
252    /// Returns true if it is `DELEGATECALL`.
253    pub const fn is_delegate_call(&self) -> bool {
254        matches!(self, Self::DelegateCall)
255    }
256
257    /// Returns true if it is `STATICCALL`.
258    pub const fn is_static_call(&self) -> bool {
259        matches!(self, Self::StaticCall)
260    }
261}
262
263/// Call value.
264#[derive(Clone, Debug, PartialEq, Eq, Hash)]
265#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
266pub enum CallValue {
267    /// Concrete value, transferred from caller to callee at the end of the transaction.
268    Transfer(U256),
269    /// Apparent value, that is **not** actually transferred.
270    ///
271    /// Set when in a `DELEGATECALL` call type, and used by the `CALLVALUE` opcode.
272    Apparent(U256),
273}
274
275impl Default for CallValue {
276    #[inline]
277    fn default() -> Self {
278        CallValue::Transfer(U256::ZERO)
279    }
280}
281
282impl CallValue {
283    /// Returns the call value, regardless of the type.
284    #[inline]
285    pub const fn get(&self) -> U256 {
286        match *self {
287            Self::Transfer(value) | Self::Apparent(value) => value,
288        }
289    }
290
291    /// Returns the transferred value, if any.
292    #[inline]
293    pub const fn transfer(&self) -> Option<U256> {
294        match *self {
295            Self::Transfer(transfer) => Some(transfer),
296            Self::Apparent(_) => None,
297        }
298    }
299
300    /// Returns whether the call value will be transferred.
301    #[inline]
302    pub const fn is_transfer(&self) -> bool {
303        matches!(self, Self::Transfer(_))
304    }
305
306    /// Returns the apparent value, if any.
307    #[inline]
308    pub const fn apparent(&self) -> Option<U256> {
309        match *self {
310            Self::Transfer(_) => None,
311            Self::Apparent(apparent) => Some(apparent),
312        }
313    }
314
315    /// Returns whether the call value is apparent, and not actually transferred.
316    #[inline]
317    pub const fn is_apparent(&self) -> bool {
318        matches!(self, Self::Apparent(_))
319    }
320}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325    use core::cell::RefCell;
326    use std::rc::Rc;
327
328    struct TestLocalContext {
329        buffer: Rc<RefCell<Vec<u8>>>,
330    }
331
332    impl TestLocalContext {
333        fn new(data: Vec<u8>) -> Self {
334            Self {
335                buffer: Rc::new(RefCell::new(data)),
336            }
337        }
338    }
339
340    impl LocalContextTr for TestLocalContext {
341        fn shared_memory_buffer(&self) -> &Rc<RefCell<Vec<u8>>> {
342            &self.buffer
343        }
344
345        fn clear(&mut self) {
346            self.buffer.borrow_mut().clear();
347        }
348
349        fn set_precompile_error_context(&mut self, _output: String) {}
350
351        fn take_precompile_error_context(&mut self) -> Option<String> {
352            None
353        }
354
355        fn cpsb(&self) -> u64 {
356            0
357        }
358
359        fn set_cpsb(&mut self, _cpsb: u64) {}
360    }
361
362    #[test]
363    fn as_bytes_local_with_bytes_variant() {
364        let input = CallInput::Bytes(Bytes::from_static(b"hello"));
365        let local = TestLocalContext::new(vec![]);
366        let result = input.as_bytes_local(&local);
367        assert_eq!(&*result, b"hello");
368    }
369
370    #[test]
371    fn as_bytes_local_with_shared_buffer() {
372        let input = CallInput::SharedBuffer(1..4);
373        let local = TestLocalContext::new(vec![0, 1, 2, 3, 4]);
374        let result = input.as_bytes_local(&local);
375        assert_eq!(&*result, &[1, 2, 3]);
376    }
377
378    #[test]
379    fn as_bytes_local_with_out_of_range_buffer() {
380        let input = CallInput::SharedBuffer(10..20);
381        let local = TestLocalContext::new(vec![0, 1, 2]);
382        let result = input.as_bytes_local(&local);
383        // Out of range returns empty via unwrap_or_default on the Option<Ref>
384        assert_eq!(&*result, &[] as &[u8]);
385    }
386
387    #[test]
388    fn bytes_local_with_bytes_variant() {
389        let input = CallInput::Bytes(Bytes::from_static(b"world"));
390        let local = TestLocalContext::new(vec![]);
391        let result = input.bytes_local(&local);
392        assert_eq!(result, Bytes::from_static(b"world"));
393    }
394
395    #[test]
396    fn bytes_local_with_shared_buffer() {
397        let input = CallInput::SharedBuffer(0..3);
398        let local = TestLocalContext::new(vec![10, 20, 30, 40]);
399        let result = input.bytes_local(&local);
400        assert_eq!(result, Bytes::from(vec![10, 20, 30]));
401    }
402
403    #[test]
404    fn bytes_local_with_out_of_range_buffer() {
405        let input = CallInput::SharedBuffer(5..10);
406        let local = TestLocalContext::new(vec![0]);
407        let result = input.bytes_local(&local);
408        assert_eq!(result, Bytes::new());
409    }
410
411    #[test]
412    fn bytes_local_with_empty_range() {
413        let input = CallInput::SharedBuffer(2..2);
414        let local = TestLocalContext::new(vec![0, 1, 2, 3]);
415        let result = input.bytes_local(&local);
416        assert_eq!(result, Bytes::new());
417    }
418}