Skip to main content

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 bobcat_maths::U;
10
11use bobcat_entry::code_hash;
12
13use bobcat_host as host;
14
15use host::{
16    call_contract as call, delegate_call_contract as delegate_call,
17    static_call_contract as static_call,
18};
19
20#[allow(unused)]
21use bobcat_panic::panic_on_err_bad_decoding_bool;
22
23type Address = [u8; 20];
24
25pub fn read_return_data_slice<const CAP: usize>(offset: usize, size: usize) -> ([u8; CAP], usize) {
26    let mut b = [0u8; CAP];
27    let rd = unsafe { host::read_return_data(b.as_mut_ptr(), offset, size) };
28    (b, rd)
29}
30
31#[cfg(feature = "alloc")]
32pub fn read_return_data_vec(offset: usize, size: usize) -> Vec<u8> {
33    let mut b = Vec::with_capacity(size);
34    let rd = unsafe { host::read_return_data(b.as_mut_ptr(), offset, size) };
35    unsafe {
36        b.set_len(rd);
37    }
38    b
39}
40
41pub fn addr_has_code(addr: Address) -> bool {
42    // It costs to use the length instead of the codehash, so we do it this
43    // way for free. We compare it against the zero code hash:
44    code_hash(addr).0
45        != [
46            197, 210, 70, 1, 134, 247, 35, 60, 146, 126, 125, 178, 220, 199, 3, 192, 229, 0, 182,
47            83, 202, 130, 39, 59, 123, 250, 216, 4, 93, 133, 164, 112,
48        ]
49}
50
51macro_rules! generate_call_variants {
52    ($base_fn:ident, has_value) => {
53        generate_call_variants!(@impl $base_fn, value: &U);
54    };
55    ($base_fn:ident) => {
56        generate_call_variants!(@impl $base_fn,);
57    };
58    (@impl $base_fn:ident, $($value_param:ident: $value_ty:ty)?) => {
59        paste::paste! {
60            /// Call a contract with the given parameters. Returns a tuple of
61            /// (success, return_data_length).
62            pub fn [<$base_fn _partial>](
63                contract: Address,
64                calldata: &[u8],
65                $($value_param: $value_ty,)?
66                gas: u64,
67            ) -> (bool, usize) {
68                let mut return_data_len = 0usize;
69                let status = unsafe {
70                    $base_fn(
71                        contract.as_ptr(),
72                        calldata.as_ptr(),
73                        calldata.len(),
74                        $($value_param.as_ptr(),)?
75                        gas,
76                        &mut return_data_len as *mut usize,
77                    )
78                };
79                (status == 0, return_data_len)
80            }
81
82            /// Call a contract, ignoring its returndata. Returns true if contract
83            /// invoked successfully.
84            pub fn [<$base_fn _unit>](
85                contract: Address,
86                calldata: &[u8],
87                $($value_param: $value_ty,)?
88                gas: u64
89            ) -> bool {
90                [<$base_fn _partial>](contract, calldata, $($value_param,)? gas).0
91            }
92
93            /// Call a contract, ignoring its returndata. Returns Some(()) if the
94            /// contract invoked successfully.
95            pub fn [<$base_fn _unit_opt>](
96                contract: Address,
97                calldata: &[u8],
98                $($value_param: $value_ty,)?
99                gas: u64,
100            ) -> Option<()> {
101                if [<$base_fn _unit>](
102                    contract,
103                    calldata,
104                    $($value_param,)?
105                    gas,
106                ) {
107                    Some(())
108                } else {
109                    None
110                }
111            }
112
113            /// Call a contract, writing its returndata to the slice given. Returns
114            /// true for if the contract ran without issue, or false if a revert
115            /// happened, and the slice isn't read to if a revert happens.
116            pub fn [<$base_fn _slice>]<const DATA_CAP: usize>(
117                contract: Address,
118                calldata: &[u8],
119                $($value_param: $value_ty,)?
120                gas: u64,
121                offset: usize,
122            ) -> (bool, usize, [u8; DATA_CAP]) {
123                let mut b = [0u8; DATA_CAP];
124                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
125                if !rc {
126                    return (false, rd_len, b);
127                }
128                panic_on_err_bad_decoding_bool!(
129                    rd_len > offset,
130                    "offset greater than rd len ok"
131                );
132                let size = rd_len - offset;
133                panic_on_err_bad_decoding_bool!(
134                    DATA_CAP >= size,
135                    "not enough slice capacity"
136                );
137                unsafe { host::read_return_data(b.as_mut_ptr(), offset, DATA_CAP) };
138                (rc, rd_len, b)
139            }
140
141            /// Call a contract, writing its returndata to the slice given. Returns
142            /// true for if the contract ran without issue, or false if a revert
143            /// happened. An offset can be used to start reading the return data from,
144            /// writing to the buffer given. The code will not read from the offset
145            /// given if a revert has happened. The function will panic if the
146            /// returndata exceeds the capacity. Enforces write control if static, and
147            /// delegates if delegatecall. The function will also panic if the offset
148            /// is greater than the size of the returndata. Programmers making this
149            /// mistake must be making an error with the decoding.
150            pub fn [<$base_fn _err_slice>]<const DATA_CAP: usize>(
151                contract: Address,
152                calldata: &[u8],
153                $($value_param: $value_ty,)?
154                gas: u64,
155                offset: usize,
156            ) -> (bool, usize, [u8; DATA_CAP]) {
157                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
158                // When it comes to a revert, we don't cut it up like we do a normal slice.
159                let size = if rc {
160                    panic_on_err_bad_decoding_bool!(
161                        rd_len > offset,
162                        "offset greater than rd len ok"
163                    );
164                    rd_len - offset
165                } else {
166                    rd_len
167                };
168                // Capacity errors if the contract was in error where the revert value
169                // size isn't known and we want to show it to the user should use err_vec
170                // functions instead with the allocator.
171                panic_on_err_bad_decoding_bool!(
172                    DATA_CAP >= size,
173                    "not enough _slice capacity"
174                );
175                let mut b = [0u8; DATA_CAP];
176                unsafe { host::read_return_data(b.as_mut_ptr(), offset, DATA_CAP) };
177                (rc, size, b)
178            }
179
180            /// Call a contract, returning a word of the returndata/revertdata. Complains
181            /// if the other party does not write exactly a word, regardless of the reason!
182            pub fn [<$base_fn _word>](
183                contract: Address,
184                calldata: &[u8],
185                $($value_param: $value_ty,)?
186                gas: u64,
187                offset: usize,
188            ) -> (bool, U) {
189                let (rc, len, v) = [<$base_fn _slice>]::<32>(contract, calldata, $($value_param,)? gas, offset);
190                panic_on_err_bad_decoding_bool!(len == 32, "response didn't write 32");
191                (rc, U::from(v))
192            }
193
194            /// Same as the other slice function, though returning Option if error.
195            pub fn [<$base_fn _slice_opt>]<const DATA_CAP: usize>(
196                contract: Address,
197                calldata: &[u8],
198                $($value_param: $value_ty,)?
199                gas: u64,
200                offset: usize,
201            ) -> Option<(usize, [u8; DATA_CAP])> {
202                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
203                    contract,
204                    calldata,
205                    $($value_param,)?
206                    gas,
207                    offset,
208                );
209                if rc {
210                    Some((len, c))
211                } else {
212                    None
213                }
214            }
215
216            /// Same as the other slice function, returning Result depending on
217            /// return or revert.
218            pub fn [<$base_fn _slice_res>]<const DATA_CAP: usize>(
219                contract: Address,
220                calldata: &[u8],
221                $($value_param: $value_ty,)?
222                gas: u64,
223                offset: usize,
224            ) -> Result<(usize, [u8; DATA_CAP]), (usize, [u8; DATA_CAP])> {
225                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
226                    contract,
227                    calldata,
228                    $($value_param,)?
229                    gas,
230                    offset,
231                );
232                if rc {
233                    Ok((len, c))
234                } else {
235                    Err((len, c))
236                }
237            }
238
239            /// Same as the slice variant, except check the length of the code for the
240            /// address first. If code doesn't exist, then we return None.
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            ) -> Option<(bool, usize, [u8; DATA_CAP])> {
248                if addr_has_code(contract) {
249                    Some([<$base_fn _slice>]::<DATA_CAP>(
250                        contract, calldata, $($value_param,)? gas, offset,
251                    ))
252                } else {
253                    None
254                }
255            }
256
257            /// Invoke call, only reading a single byte at the first word for a
258            /// check.
259            pub fn [<$base_fn _bool>](
260                contract: Address,
261                calldata: &[u8],
262                $($value_param: $value_ty,)?
263                gas: u64,
264            ) -> bool {
265                let (rc, _, v) = [<$base_fn _slice>]::<1>(
266                    contract, calldata, $($value_param,)? gas, 31,
267                );
268                rc && v[0] == 1
269            }
270
271            /// Invoke call, only reading a single byte at the first word for a
272            /// check. Returns Some if success.
273            pub fn [<$base_fn _bool_opt>](
274                contract: Address,
275                calldata: &[u8],
276                $($value_param: $value_ty,)?
277                gas: u64,
278            ) -> Option<()> {
279                if [<$base_fn _bool>](contract, calldata, $($value_param,)? gas) {
280                    Some(())
281                } else {
282                    None
283                }
284            }
285
286            /// Check the codesize before invoking call, only reading a single byte
287            /// at the location for a bool check. If the contract doesn't return anything,
288            /// then we assume everything went okay. If it does, then we check for true.
289            /// We don't return anything.
290            pub fn [<safe_ $base_fn _bool>](
291                contract: Address,
292                calldata: &[u8],
293                $($value_param: $value_ty,)?
294                gas: u64,
295            ) -> bool {
296                if addr_has_code(contract) {
297                    match [<$base_fn _partial>](contract, calldata, $($value_param,)? gas) {
298                        (true, 32) => {
299                            let mut b = [0u8; 1];
300                            unsafe { host::read_return_data(b.as_mut_ptr(), 31, 1) };
301                            b[0] == 1
302                        }
303                        (true, 0) => true,
304                        (true, _) => panic_on_err_bad_decoding_bool!("word not returned"),
305                        _ => false
306                    }
307                } else {
308                    false
309                }
310            }
311
312            /// Check the codesize before invoking call, returning a Option<()> if
313            /// the contract call worked, and the return value is true.
314            pub fn [<safe_ $base_fn _bool_opt>](
315                contract: Address,
316                calldata: &[u8],
317                $($value_param: $value_ty,)?
318                gas: u64,
319            ) -> Option<()> {
320                if [<safe_ $base_fn _bool>](
321                    contract,
322                    calldata,
323                    $($value_param,)?
324                    gas
325                ) {
326                    Some(())
327                } else {
328                    None
329                }
330            }
331
332            /// Call a contract, writing its returndata to the vector given. Behaves
333            /// the same way as the slice function.
334            #[cfg(feature = "alloc")]
335            pub fn [<$base_fn _vec>](
336                contract: Address,
337                calldata: &[u8],
338                $($value_param: $value_ty,)?
339                gas: u64,
340                offset: usize,
341            ) -> (bool, Vec<u8>) {
342                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
343                let size = rd_len - offset;
344                let mut b = Vec::with_capacity(size);
345                unsafe { b.set_len(size) }
346                unsafe { host::read_return_data(b.as_mut_ptr(), offset, size) };
347                (rc, b)
348            }
349
350            /// Same as the other vec function, returning Option if error.
351            #[cfg(feature = "alloc")]
352            pub fn [<$base_fn _vec_opt>](
353                contract: Address,
354                calldata: &[u8],
355                $($value_param: $value_ty,)?
356                gas: u64,
357                offset: usize,
358            ) -> Option<Vec<u8>> {
359                let (rc, v) = [<$base_fn _vec>](
360                    contract,
361                    calldata,
362                    $($value_param,)?
363                    gas,
364                    offset,
365                );
366                if rc {
367                    Some(v)
368                } else {
369                    None
370                }
371            }
372
373            /// Same as the other vec function, returning Result depending on
374            /// return or revert.
375            #[cfg(feature = "alloc")]
376            pub fn [<$base_fn _vec_res>](
377                contract: Address,
378                calldata: &[u8],
379                $($value_param: $value_ty,)?
380                gas: u64,
381                offset: usize,
382            ) -> Result<Vec<u8>, Vec<u8>> {
383                let (rc, rd) = [<$base_fn _vec>](
384                    contract,
385                    calldata,
386                    $($value_param,)?
387                    gas,
388                    offset,
389                );
390                if rc {
391                    Ok(rd)
392                } else {
393                    Err(rd)
394                }
395            }
396
397            /// Return a word if successful, a vector if a revert.
398            #[cfg(feature = "alloc")]
399            pub fn [<$base_fn _word_err_vec>](
400                contract: Address,
401                calldata: &[u8],
402                $($value_param: $value_ty,)?
403                gas: u64,
404            ) -> (bool, U, Option<Vec<u8>>) {
405                // Why use a vector for this entirely? Normally, you'd prefer stack space
406                // for data that might be used in a way with a performance context, like
407                // a word for an addition. For locality reasons. But, we want to save
408                // codesize by reducing complexity! So, we prefer to just depend on the
409                // allocator if that's what's in use here.
410                let (rc, v) = [<$base_fn _vec>](
411                    contract,
412                    calldata,
413                    $($value_param,)?
414                    gas,
415                    0
416                );
417                if rc {
418                    panic_on_err_bad_decoding_bool!(v.len() == 32, "not return for word");
419                    let v: [u8; 32] = v.try_into().unwrap();
420                    (rc, U::from(v), None)
421                } else {
422                    (rc, U::ZERO, Some(v))
423                }
424            }
425
426            /// Allocates a slice with a single u8 if a word was returned, or
427            /// returns a Vec<u8> if something went wrong.
428            #[cfg(feature = "alloc")]
429            pub fn [<$base_fn _bool_err_vec>](
430                contract: Address,
431                calldata: &[u8],
432                $($value_param: $value_ty,)?
433                gas: u64
434            ) -> (bool, Option<Vec<u8>>) {
435                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
436                if rc {
437                    panic_on_err_bad_decoding_bool!(rd_len == 32, "not return for bool word");
438                    let mut b = [0u8; 1];
439                    unsafe {
440                        host::read_return_data(b.as_mut_ptr(), 31, 1);
441                    }
442                    (b[0] == 1, None)
443                } else {
444                    let mut b = Vec::with_capacity(rd_len);
445                    unsafe {
446                        host::read_return_data(b.as_mut_ptr(), 0, rd_len);
447                        b.set_len(rd_len);
448                    }
449                    (false, Some(b))
450                }
451            }
452
453            /// Call a function, returning whether the call returned true if the
454            /// contract returned something, or true if the contract returned nothing
455            /// but does exist. The vector contains revertdata if the call was
456            /// unsuccessful. Does not read returndata.
457            #[cfg(feature = "alloc")]
458            pub fn [<safe_ $base_fn _bool_err_vec>](
459                contract: Address,
460                calldata: &[u8],
461                $($value_param: $value_ty,)?
462                gas: u64
463            ) -> (bool, Option<Vec<u8>>) {
464                if !addr_has_code(contract) {
465                    return (false, None)
466                }
467                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
468                match (rc, rd_len) {
469                    (true, 32) => {
470                        let mut b = [0u8; 1];
471                        unsafe {
472                            host::read_return_data(b.as_mut_ptr(), 31, 1);
473                        }
474                        (b[0] == 1, None)
475                    }
476                    (true, 0) => (true, None),
477                    (true, _) => panic_on_err_bad_decoding_bool!(
478                        "word not returned for safe_ _bool_err_vec"
479                    ),
480                    (false, rd_len) => {
481                        let mut b = Vec::with_capacity(rd_len);
482                        unsafe {
483                            host::read_return_data(b.as_mut_ptr(), 0, rd_len);
484                            b.set_len(rd_len);
485                        }
486                        (false, Some(b))
487                    }
488                }
489            }
490
491            /// Return if the call was successful, and the vector of the revertdata.
492            #[cfg(feature = "alloc")]
493            pub fn [<$base_fn _unit_err_vec>](
494                contract: Address,
495                calldata: &[u8],
496                $($value_param: $value_ty,)?
497                gas: u64
498            ) -> (bool, Option<Vec<u8>>) {
499                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
500                if rc {
501                    (true, None)
502                } else {
503                    let mut b = Vec::with_capacity(rd_len);
504                    unsafe {
505                        host::read_return_data(b.as_mut_ptr(), 0, rd_len);
506                        b.set_len(rd_len);
507                    }
508                    (false, Some(b))
509                }
510            }
511
512            /// Result equivalent of _unit_err_vec.
513            #[cfg(feature = "alloc")]
514            pub fn [<$base_fn _unit_err_res>](
515                contract: Address,
516                calldata: &[u8],
517                $($value_param: $value_ty,)?
518                gas: u64
519            ) -> Result<(), Vec<u8>> {
520                match [<$base_fn _unit_err_vec>](
521                    contract,
522                    calldata,
523                    $($value_param,)?
524                    gas
525                )
526                {
527                    (false, Some(e)) => Err(e),
528                    (false, None) => {
529                        // How did this happen?
530                        unimplemented!()
531                    }
532                    (true, None) | (true, Some(_)) => Ok(())
533                }
534            }
535
536            /// Call a function, returning whether the call was successful. The vector contains
537            /// revertdata if the call was unsuccessful. Does not read returndata.
538            #[cfg(feature = "alloc")]
539            pub fn [<safe_ $base_fn _unit_err_vec>](
540                contract: Address,
541                calldata: &[u8],
542                $($value_param: $value_ty,)?
543                gas: u64
544            ) -> (bool, Option<Vec<u8>>) {
545                if addr_has_code(contract) {
546                    [<$base_fn _unit_err_vec>](contract, calldata, $($value_param,)? gas)
547                } else {
548                    (false, None)
549                }
550            }
551
552            /// Return a U word using a Result, or the vector for an error.
553            #[cfg(feature = "alloc")]
554            pub fn [<$base_fn _word_err_vec_res>](
555                contract: Address,
556                calldata: &[u8],
557                $($value_param: $value_ty,)?
558                gas: u64,
559            ) -> Result<U, Vec<u8>> {
560                let (rc, w, v) = [<$base_fn _word_err_vec>](
561                    contract,
562                    calldata,
563                    $($value_param,)?
564                    gas
565                );
566                if rc {
567                    Ok(w)
568                } else {
569                    Err(v.expect("vec not containing anything"))
570                }
571            }
572
573            /// Safely call a contract, with the same checks as the safe vec variant.
574            #[cfg(feature = "alloc")]
575            pub fn [<safe_ $base_fn _vec>](
576                contract: Address,
577                calldata: &[u8],
578                $($value_param: $value_ty,)?
579                gas: u64,
580                offset: usize,
581            ) -> Option<(bool, Vec<u8>)> {
582                if addr_has_code(contract) {
583                    Some([<$base_fn _vec>](contract, calldata, $($value_param,)? gas, offset))
584                } else {
585                    None
586                }
587            }
588        }
589    };
590}
591
592generate_call_variants!(call, has_value);
593generate_call_variants!(static_call);
594generate_call_variants!(delegate_call);