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}