stylus_sdk/call/
context.rs

1// Copyright 2022-2024, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4use super::{CallContext, MutatingCallContext, NonPayableCallContext, StaticCallContext};
5use alloy_primitives::U256;
6use cfg_if::cfg_if;
7use stylus_core::storage::TopLevelStorage;
8
9/// Enables configurable calls to other contracts.
10#[derive(Debug, Clone)]
11#[deprecated(
12    since = "0.8.0",
13    note = "Use the Call struct defined in stylus_core::calls::context instead"
14)]
15pub struct Call<S, const HAS_VALUE: bool = false> {
16    gas: u64,
17    value: Option<U256>,
18    storage: S,
19}
20
21#[allow(deprecated)]
22impl<'a, S: TopLevelStorage> Call<&'a mut S, false>
23where
24    S: TopLevelStorage + 'a,
25{
26    /// Similar to [`new`], but intended for projects and libraries using reentrant patterns.
27    ///
28    /// [`new_in`] safeguards persistent storage by requiring a reference to a [`TopLevelStorage`] `struct`.
29    ///
30    /// Recall that [`TopLevelStorage`] is special in that a reference to it represents access to the entire
31    /// contract's state. So that it's sound to [`flush`] or [`clear`] the [`StorageCache`] when calling out
32    /// to other contracts, calls that may induce reentrancy require an `&` or `&mut` to one.
33    /// Although this reference to [`TopLevelStorage`] is not used, the lifetime is still required
34    /// to ensure safety of the storage cache.
35    ///
36    /// ```
37    /// use stylus_sdk::call::{Call, Error};
38    /// use stylus_sdk::{prelude::*, evm, msg, alloy_primitives::Address};
39    /// use stylus_core::storage::TopLevelStorage;
40    /// extern crate alloc;
41    ///
42    /// sol_interface! {
43    ///     interface IService {
44    ///         function makePayment(address user) external payable returns (string);
45    ///     }
46    /// }
47    ///
48    /// pub fn do_call(
49    ///     storage: &mut impl TopLevelStorage,  // can be generic, but often just &mut self
50    ///     account: IService,                   // serializes as an Address
51    ///     user: Address,
52    /// ) -> Result<String, Error> {
53    ///
54    ///     let config = Call::new_in(storage)
55    ///         .gas(evm::gas_left() / 2)        // limit to half the gas left
56    ///         .value(msg::value());            // set the callvalue
57    ///
58    ///     account.make_payment(config, user)   // note the snake case
59    /// }
60    /// ```
61    ///
62    /// [`StorageCache`]: crate::storage::StorageCache
63    /// [`flush`]: crate::storage::StorageCache::flush
64    /// [`clear`]: crate::storage::StorageCache::clear
65    /// [`new_in`]: Call::new_in
66    /// [`new`]: Call::new
67    pub fn new_in(storage: &'a mut S) -> Self {
68        Self {
69            gas: u64::MAX,
70            value: None,
71            storage,
72        }
73    }
74}
75
76#[allow(deprecated)]
77impl<S, const HAS_VALUE: bool> Call<S, HAS_VALUE> {
78    /// Amount of gas to supply the call.
79    /// Values greater than the amount provided will be clipped to all gas left.
80    pub fn gas(self, gas: u64) -> Self {
81        Self { gas, ..self }
82    }
83
84    /// Amount of ETH in wei to give the other contract.
85    /// Note: adding value will prevent calls to non-payable methods.
86    pub fn value(self, value: U256) -> Call<S, true> {
87        Call {
88            value: Some(value),
89            gas: self.gas,
90            storage: self.storage,
91        }
92    }
93}
94
95#[allow(deprecated)]
96impl<S, const HAS_VALUE: bool> CallContext for Call<S, HAS_VALUE> {
97    fn gas(&self) -> u64 {
98        self.gas
99    }
100}
101
102// allow &self as a context
103impl<T> CallContext for &T
104where
105    T: TopLevelStorage,
106{
107    fn gas(&self) -> u64 {
108        u64::MAX
109    }
110}
111
112// allow &mut self as a context
113impl<T> CallContext for &mut T
114where
115    T: TopLevelStorage,
116{
117    fn gas(&self) -> u64 {
118        u64::MAX
119    }
120}
121
122// allow &self to be a `pure` and `static` call context
123impl<T> StaticCallContext for &T where T: TopLevelStorage {}
124
125// allow &mut self to be a `pure` and `static` call context
126impl<T> StaticCallContext for &mut T where T: TopLevelStorage {}
127
128// allow &mut self to be a `write` and `payable` call context
129unsafe impl<T> MutatingCallContext for &mut T
130where
131    T: TopLevelStorage,
132{
133    fn value(&self) -> U256 {
134        U256::ZERO
135    }
136}
137
138// allow &mut self to be a `write`-only call context
139impl<T> NonPayableCallContext for &mut T where T: TopLevelStorage {}
140
141cfg_if! {
142    if #[cfg(feature = "reentrant")] {
143        // The following impls safeguard state during reentrancy scenarios
144
145        #[allow(deprecated)]
146        impl<S: TopLevelStorage> StaticCallContext for Call<&S, false> {}
147
148        #[allow(deprecated)]
149        impl<S: TopLevelStorage> StaticCallContext for Call<&mut S, false> {}
150
151        #[allow(deprecated)]
152        impl<S: TopLevelStorage> NonPayableCallContext for Call<&mut S, false> {}
153
154        #[allow(deprecated)]
155        unsafe impl<S: TopLevelStorage, const HAS_VALUE: bool> MutatingCallContext
156            for Call<&mut S, HAS_VALUE>
157        {
158            fn value(&self) -> U256 {
159                self.value.unwrap_or_default()
160            }
161        }
162    } else {
163        // If there's no reentrancy, all calls are storage safe
164
165        #[allow(deprecated)]
166        impl<S> StaticCallContext for Call<S, false> {}
167
168        #[allow(deprecated)]
169        impl<S> NonPayableCallContext for Call<S, false> {}
170
171        #[allow(deprecated)]
172        unsafe impl<S, const HAS_VALUE: bool> MutatingCallContext for Call<S, HAS_VALUE> {
173            fn value(&self) -> U256 {
174                self.value.unwrap_or_default()
175            }
176        }
177    }
178}
179
180cfg_if! {
181    if #[cfg(any(not(feature = "reentrant"), feature = "docs"))] {
182        #[allow(deprecated)]
183        impl Default for Call<(), false> {
184            fn default() -> Self {
185                Self::new()
186            }
187        }
188        #[allow(deprecated)]
189        impl Call<(), false> {
190            /// Begin configuring a call, similar to how [`RawCall`](super::RawCall) and
191            /// [`std::fs::OpenOptions`][OpenOptions] work.
192            ///
193            /// This is not available if `reentrant` feature is enabled, as it may lead to
194            /// vulnerability to reentrancy attacks. See [`Call::new_in`].
195            ///
196            /// ```no_compile
197            /// use stylus_sdk::call::{Call, Error};
198            /// use stylus_sdk::{prelude::*, evm, msg, alloy_primitives::Address};
199            /// extern crate alloc;
200            ///
201            /// sol_interface! {
202            ///     interface IService {
203            ///         function makePayment(address user) external payable returns (string);
204            ///     }
205            /// }
206            ///
207            /// pub fn do_call(account: IService, user: Address) -> Result<String, Error> {
208            ///     let config = Call::new()
209            ///         .gas(evm::gas_left() / 2)       // limit to half the gas left
210            ///         .value(msg::value());           // set the callvalue
211            ///
212            ///     account.make_payment(config, user)  // note the snake case
213            /// }
214            /// ```
215            ///
216            /// [OpenOptions]: https://doc.rust-lang.org/stable/std/fs/struct.OpenOptions.html
217            pub fn new() -> Self {
218                Self {
219                    gas: u64::MAX,
220                    value: None,
221                    storage: (),
222                }
223            }
224        }
225    }
226}