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}
173
174impl CallInputs {
175    /// Returns `true` if the call will transfer a non-zero value.
176    #[inline]
177    pub fn transfers_value(&self) -> bool {
178        self.value.transfer().is_some_and(|x| x > U256::ZERO)
179    }
180
181    /// Returns the transfer value.
182    ///
183    /// This is the value that is transferred from caller to callee, see [`CallValue`].
184    #[inline]
185    pub const fn transfer_value(&self) -> Option<U256> {
186        self.value.transfer()
187    }
188
189    /// Returns the **apparent** call value.
190    ///
191    /// This value is not actually transferred, see [`CallValue`].
192    #[inline]
193    pub const fn apparent_value(&self) -> Option<U256> {
194        self.value.apparent()
195    }
196
197    /// Returns the address of the transfer source account.
198    ///
199    /// This is only meaningful if `transfers_value` is `true`.
200    #[inline]
201    pub const fn transfer_from(&self) -> Address {
202        self.caller
203    }
204
205    /// Returns the address of the transfer target account.
206    ///
207    /// This is only meaningful if `transfers_value` is `true`.
208    #[inline]
209    pub const fn transfer_to(&self) -> Address {
210        self.target_address
211    }
212
213    /// Returns the call value, regardless of the transfer value type.
214    ///
215    /// **Note**: This value may not necessarily be transferred from caller to callee, see [`CallValue`].
216    #[inline]
217    pub const fn call_value(&self) -> U256 {
218        self.value.get()
219    }
220}
221
222/// Call scheme.
223#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
225pub enum CallScheme {
226    /// `CALL`.
227    Call,
228    /// `CALLCODE`
229    CallCode,
230    /// `DELEGATECALL`
231    DelegateCall,
232    /// `STATICCALL`
233    StaticCall,
234}
235
236impl CallScheme {
237    /// Returns true if it is `CALL`.
238    pub fn is_call(&self) -> bool {
239        matches!(self, Self::Call)
240    }
241
242    /// Returns true if it is `CALLCODE`.
243    pub fn is_call_code(&self) -> bool {
244        matches!(self, Self::CallCode)
245    }
246
247    /// Returns true if it is `DELEGATECALL`.
248    pub fn is_delegate_call(&self) -> bool {
249        matches!(self, Self::DelegateCall)
250    }
251
252    /// Returns true if it is `STATICCALL`.
253    pub fn is_static_call(&self) -> bool {
254        matches!(self, Self::StaticCall)
255    }
256}
257
258/// Call value.
259#[derive(Clone, Debug, PartialEq, Eq, Hash)]
260#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
261pub enum CallValue {
262    /// Concrete value, transferred from caller to callee at the end of the transaction.
263    Transfer(U256),
264    /// Apparent value, that is **not** actually transferred.
265    ///
266    /// Set when in a `DELEGATECALL` call type, and used by the `CALLVALUE` opcode.
267    Apparent(U256),
268}
269
270impl Default for CallValue {
271    #[inline]
272    fn default() -> Self {
273        CallValue::Transfer(U256::ZERO)
274    }
275}
276
277impl CallValue {
278    /// Returns the call value, regardless of the type.
279    #[inline]
280    pub const fn get(&self) -> U256 {
281        match *self {
282            Self::Transfer(value) | Self::Apparent(value) => value,
283        }
284    }
285
286    /// Returns the transferred value, if any.
287    #[inline]
288    pub const fn transfer(&self) -> Option<U256> {
289        match *self {
290            Self::Transfer(transfer) => Some(transfer),
291            Self::Apparent(_) => None,
292        }
293    }
294
295    /// Returns whether the call value will be transferred.
296    #[inline]
297    pub const fn is_transfer(&self) -> bool {
298        matches!(self, Self::Transfer(_))
299    }
300
301    /// Returns the apparent value, if any.
302    #[inline]
303    pub const fn apparent(&self) -> Option<U256> {
304        match *self {
305            Self::Transfer(_) => None,
306            Self::Apparent(apparent) => Some(apparent),
307        }
308    }
309
310    /// Returns whether the call value is apparent, and not actually transferred.
311    #[inline]
312    pub const fn is_apparent(&self) -> bool {
313        matches!(self, Self::Apparent(_))
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320    use core::cell::RefCell;
321    use std::rc::Rc;
322
323    struct TestLocalContext {
324        buffer: Rc<RefCell<Vec<u8>>>,
325    }
326
327    impl TestLocalContext {
328        fn new(data: Vec<u8>) -> Self {
329            Self {
330                buffer: Rc::new(RefCell::new(data)),
331            }
332        }
333    }
334
335    impl LocalContextTr for TestLocalContext {
336        fn shared_memory_buffer(&self) -> &Rc<RefCell<Vec<u8>>> {
337            &self.buffer
338        }
339
340        fn clear(&mut self) {
341            self.buffer.borrow_mut().clear();
342        }
343
344        fn set_precompile_error_context(&mut self, _output: String) {}
345
346        fn take_precompile_error_context(&mut self) -> Option<String> {
347            None
348        }
349    }
350
351    #[test]
352    fn as_bytes_local_with_bytes_variant() {
353        let input = CallInput::Bytes(Bytes::from_static(b"hello"));
354        let local = TestLocalContext::new(vec![]);
355        let result = input.as_bytes_local(&local);
356        assert_eq!(&*result, b"hello");
357    }
358
359    #[test]
360    fn as_bytes_local_with_shared_buffer() {
361        let input = CallInput::SharedBuffer(1..4);
362        let local = TestLocalContext::new(vec![0, 1, 2, 3, 4]);
363        let result = input.as_bytes_local(&local);
364        assert_eq!(&*result, &[1, 2, 3]);
365    }
366
367    #[test]
368    fn as_bytes_local_with_out_of_range_buffer() {
369        let input = CallInput::SharedBuffer(10..20);
370        let local = TestLocalContext::new(vec![0, 1, 2]);
371        let result = input.as_bytes_local(&local);
372        // Out of range returns empty via unwrap_or_default on the Option<Ref>
373        assert_eq!(&*result, &[] as &[u8]);
374    }
375
376    #[test]
377    fn bytes_local_with_bytes_variant() {
378        let input = CallInput::Bytes(Bytes::from_static(b"world"));
379        let local = TestLocalContext::new(vec![]);
380        let result = input.bytes_local(&local);
381        assert_eq!(result, Bytes::from_static(b"world"));
382    }
383
384    #[test]
385    fn bytes_local_with_shared_buffer() {
386        let input = CallInput::SharedBuffer(0..3);
387        let local = TestLocalContext::new(vec![10, 20, 30, 40]);
388        let result = input.bytes_local(&local);
389        assert_eq!(result, Bytes::from(vec![10, 20, 30]));
390    }
391
392    #[test]
393    fn bytes_local_with_out_of_range_buffer() {
394        let input = CallInput::SharedBuffer(5..10);
395        let local = TestLocalContext::new(vec![0]);
396        let result = input.bytes_local(&local);
397        assert_eq!(result, Bytes::new());
398    }
399
400    #[test]
401    fn bytes_local_with_empty_range() {
402        let input = CallInput::SharedBuffer(2..2);
403        let local = TestLocalContext::new(vec![0, 1, 2, 3]);
404        let result = input.bytes_local(&local);
405        assert_eq!(result, Bytes::new());
406    }
407}