bobcat_call/
lib.rs

1#![no_std]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::vec::Vec;
8
9use core::cmp::min;
10
11use bobcat_maths::U;
12
13pub type Address = [u8; 20];
14
15#[cfg(target_arch = "wasm32")]
16mod impls {
17    #[link(wasm_import_module = "vm_hooks")]
18    unsafe extern "C" {
19        pub(crate) fn call_contract(
20            contract: *const u8,
21            calldata: *const u8,
22            calldata_len: usize,
23            value: *const u8,
24            gas: u64,
25            return_data_len: *mut usize,
26        ) -> u8;
27
28        pub(crate) fn static_call_contract(
29            contract: *const u8,
30            calldata: *const u8,
31            calldata_len: usize,
32            gas: u64,
33            return_data_len: *mut usize,
34        ) -> u8;
35
36        pub(crate) fn delegate_call_contract(
37            contract: *const u8,
38            calldata: *const u8,
39            calldata_len: usize,
40            gas: u64,
41            return_data_len: *mut usize,
42        ) -> u8;
43
44        pub(crate) fn read_return_data(dest: *mut u8, offset: usize, size: usize) -> usize;
45
46        pub(crate) fn account_code_size(address: *const u8) -> usize;
47    }
48}
49
50#[cfg(not(target_arch = "wasm32"))]
51mod impls {
52    pub(crate) unsafe fn call_contract(
53        _contract: *const u8,
54        _calldata: *const u8,
55        _calldata_len: usize,
56        _value: *const u8,
57        _gas: u64,
58        _return_data_len: *mut usize,
59    ) -> u8 {
60        0
61    }
62
63    pub(crate) unsafe fn static_call_contract(
64        _contract: *const u8,
65        _calldata: *const u8,
66        _calldata_len: usize,
67        _gas: u64,
68        _return_data_len: *mut usize,
69    ) -> u8 {
70        0
71    }
72
73    pub(crate) unsafe fn delegate_call_contract(
74        _contract: *const u8,
75        _calldata: *const u8,
76        _calldata_len: usize,
77        _gas: u64,
78        _return_data_len: *mut usize,
79    ) -> u8 {
80        0
81    }
82
83    pub(crate) unsafe fn read_return_data(_: *mut u8, _: usize, _: usize) -> usize {
84        0
85    }
86
87    pub(crate) unsafe fn account_code_size(_: *const u8) -> usize {
88        0
89    }
90}
91
92use impls::{
93    call_contract as call, delegate_call_contract as delegate_call,
94    static_call_contract as static_call,
95};
96
97fn code_size(addr: Address) -> usize {
98    unsafe { impls::account_code_size(addr.as_ptr()) }
99}
100
101macro_rules! generate_call_variants {
102    ($base_fn:ident, has_value) => {
103        generate_call_variants!(@impl $base_fn, value: &U);
104    };
105    ($base_fn:ident) => {
106        generate_call_variants!(@impl $base_fn,);
107    };
108    (@impl $base_fn:ident, $($value_param:ident: $value_ty:ty)?) => {
109        paste::paste! {
110            /// Call a contract with the given parameters. Returns a tuple of
111            /// (success, return_data_length).
112            pub fn [<$base_fn _partial>](
113                contract: Address,
114                calldata: &[u8],
115                $($value_param: $value_ty,)?
116                gas: u64,
117            ) -> (bool, usize) {
118                let mut return_data_len = 0usize;
119                let status = unsafe {
120                    $base_fn(
121                        contract.as_ptr(),
122                        calldata.as_ptr(),
123                        calldata.len(),
124                        $($value_param.as_ptr(),)?
125                        gas,
126                        &mut return_data_len as *mut usize,
127                    )
128                };
129                (status == 0, return_data_len)
130            }
131
132            /// Call a contract, ignoring its returndata. Returns true if contract
133            /// invoked successfully.
134            pub fn [<$base_fn _unit>](
135                contract: Address,
136                calldata: &[u8],
137                $($value_param: $value_ty,)?
138                gas: u64
139            ) -> bool {
140                [<$base_fn _partial>](contract, calldata, $($value_param,)? gas).0
141            }
142
143            /// Call a contract, ignoring its returndata. Returns Some(()) if the
144            /// contract invoked successfully.
145            pub fn [<$base_fn _unit_opt>](
146                contract: Address,
147                calldata: &[u8],
148                $($value_param: $value_ty,)?
149                gas: u64,
150            ) -> Option<()> {
151                if [<$base_fn _unit>](
152                    contract,
153                    calldata,
154                    $($value_param,)?
155                    gas,
156                ) {
157                    Some(())
158                } else {
159                    None
160                }
161            }
162
163            /// Call a contract, writing its returndata to the slice given. Returns
164            /// true for if the contract ran without issue, or false if a revert
165            /// happened. An offset can be used to start reading the return data from,
166            /// writing to the buffer given. The function will panic if the returndata
167            /// exceeds the capacity. Enforces write control if static, and delegates if
168            /// delegatecall. The function will also panic if the offset is greater than
169            /// the size of the returndata. Programmers making this mistake must be
170            /// making an error with the decoding.
171            pub fn [<$base_fn _slice>]<const DATA_CAP: usize>(
172                contract: Address,
173                calldata: &[u8],
174                $($value_param: $value_ty,)?
175                gas: u64,
176                offset: usize,
177                size: usize,
178            ) -> (bool, usize, [u8; DATA_CAP]) {
179                assert!(DATA_CAP >= size, "not enough cap for size");
180                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
181                assert!(rd_len > offset, "not enough data for offset");
182                let mut b = [0u8; DATA_CAP];
183                let size = min(size, rd_len - offset);
184                unsafe { impls::read_return_data(b.as_mut_ptr(), offset, size) };
185                (rc, size, b)
186            }
187
188            /// Same as the other slice function, though returning Option if error.
189            pub fn [<$base_fn _slice_opt>]<const DATA_CAP: usize>(
190                contract: Address,
191                calldata: &[u8],
192                $($value_param: $value_ty,)?
193                gas: u64,
194                offset: usize,
195                size: usize,
196            ) -> Option<(usize, [u8; DATA_CAP])> {
197                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
198                    contract,
199                    calldata,
200                    $($value_param,)?
201                    gas,
202                    offset,
203                    size,
204                );
205                if rc {
206                    Some((len, c))
207                } else {
208                    None
209                }
210            }
211
212            /// Same as the other slice function, returning Result depending on
213            /// return or revert.
214            pub fn [<$base_fn _slice_res>]<const DATA_CAP: usize>(
215                contract: Address,
216                calldata: &[u8],
217                $($value_param: $value_ty,)?
218                gas: u64,
219                offset: usize,
220                size: usize,
221            ) -> Result<(usize, [u8; DATA_CAP]), (usize, [u8; DATA_CAP])> {
222                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
223                    contract,
224                    calldata,
225                    $($value_param,)?
226                    gas,
227                    offset,
228                    size,
229                );
230                if rc {
231                    Ok((len, c))
232                } else {
233                    Ok((len, c))
234                }
235            }
236
237            /// Same as the slice variant, except check the length of the code for the
238            /// address first. If code doesn't exist, then we return None. This might
239            /// be useful for implementing ERC20 when you relax the check on the
240            /// return value.
241            pub fn [<safe_ $base_fn _slice>]<const DATA_CAP: usize>(
242                contract: Address,
243                calldata: &[u8],
244                $($value_param: $value_ty,)?
245                gas: u64,
246                offset: usize,
247                size: usize,
248            ) -> Option<(bool, usize, [u8; DATA_CAP])> {
249                if code_size(contract) > 0 {
250                    Some([<$base_fn _slice>]::<DATA_CAP>(
251                        contract, calldata, $($value_param,)? gas, offset, size,
252                    ))
253                } else {
254                    None
255                }
256            }
257
258            /// Invoke call, only reading a single byte at the first word for a
259            /// check.
260            pub fn [<$base_fn _bool>](
261                contract: Address,
262                calldata: &[u8],
263                $($value_param: $value_ty,)?
264                gas: u64,
265            ) -> bool {
266                let (rc, _, v) = [<$base_fn _slice>]::<1>(
267                    contract, calldata, $($value_param,)? gas, 31, 1,
268                );
269                rc && v[0] == 1
270            }
271
272            /// Invoke call, only reading a single byte at the first word for a
273            /// check. Returns Some if success.
274            pub fn [<$base_fn _bool_opt>](
275                contract: Address,
276                calldata: &[u8],
277                $($value_param: $value_ty,)?
278                gas: u64,
279            ) -> Option<()> {
280                if [<$base_fn _bool>](contract, calldata, $($value_param,)? gas) {
281                    Some(())
282                } else {
283                    None
284                }
285            }
286
287            /// Check the codesize before invoking call, only reading a single byte
288            /// at the location for a bool check.
289            pub fn [<safe_ $base_fn _bool>](
290                contract: Address,
291                calldata: &[u8],
292                $($value_param: $value_ty,)?
293                gas: u64,
294            ) -> bool {
295                if code_size(contract) > 0 {
296                    let (rc, _, v) = [<$base_fn _slice>]::<1>(
297                        contract, calldata, $($value_param,)? gas, 31, 1,
298                    );
299                    rc && v[0] == 1
300                } else {
301                    false
302                }
303            }
304
305            /// Check the codesize before invoking call, returning a Option<()> if
306            /// the contract call worked, and the return value is true.
307            pub fn [<safe_ $base_fn _bool_opt>](
308                contract: Address,
309                calldata: &[u8],
310                $($value_param: $value_ty,)?
311                gas: u64,
312            ) -> Option<()> {
313                if [<safe_ $base_fn _bool>](
314                    contract,
315                    calldata,
316                    $($value_param,)?
317                    gas
318                ) {
319                    Some(())
320                } else {
321                    None
322                }
323            }
324
325            /// Call a contract, writing its returndata to the vector given. Behaves
326            /// the same way as the slice function.
327            #[cfg(feature = "alloc")]
328            pub fn [<$base_fn _vec>](
329                contract: Address,
330                calldata: &[u8],
331                $($value_param: $value_ty,)?
332                gas: u64,
333                offset: usize,
334                size: usize,
335            ) -> (bool, Vec<u8>) {
336                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
337                let size = min(size, rd_len - offset);
338                let mut b = Vec::with_capacity(size);
339                unsafe { b.set_len(size) }
340                unsafe { impls::read_return_data(b.as_mut_ptr(), offset, size) };
341                (rc, b)
342            }
343
344            /// Same as the other vec function, returning Option if error.
345            #[cfg(feature = "alloc")]
346            pub fn [<$base_fn _vec_opt>](
347                contract: Address,
348                calldata: &[u8],
349                $($value_param: $value_ty,)?
350                gas: u64,
351                offset: usize,
352                size: usize,
353            ) -> Option<Vec<u8>> {
354                let (rc, v) = [<$base_fn _vec>](
355                    contract,
356                    calldata,
357                    $($value_param,)?
358                    gas,
359                    offset,
360                    size,
361                );
362                if rc {
363                    Some(v)
364                } else {
365                    None
366                }
367            }
368
369            /// Same as the other vec function, returning Result depending on
370            /// return or revert.
371            #[cfg(feature = "alloc")]
372            pub fn [<$base_fn _vec_res>]<const DATA_CAP: usize>(
373                contract: Address,
374                calldata: &[u8],
375                $($value_param: $value_ty,)?
376                gas: u64,
377                offset: usize,
378                size: usize,
379            ) -> Result<Vec<u8>, Vec<u8>> {
380                let (rc, rd) = [<$base_fn _vec>](
381                    contract,
382                    calldata,
383                    $($value_param,)?
384                    gas,
385                    offset,
386                    size,
387                );
388                if rc {
389                    Ok(rd)
390                } else {
391                    Ok(rd)
392                }
393            }
394
395            /// Safely call a contract, with the same checks as the safe vec variant.
396            #[cfg(feature = "alloc")]
397            pub fn [<safe_ $base_fn _vec>](
398                contract: Address,
399                calldata: &[u8],
400                $($value_param: $value_ty,)?
401                gas: u64,
402                offset: usize,
403                size: usize,
404            ) -> Option<(bool, Vec<u8>)> {
405                if code_size(contract) > 0 {
406                    Some([<$base_fn _vec>](contract, calldata, $($value_param,)? gas, offset, size))
407                } else {
408                    None
409                }
410            }
411        }
412    };
413}
414
415generate_call_variants!(call, has_value);
416generate_call_variants!(static_call);
417generate_call_variants!(delegate_call);