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, offset: {offset}, rd len: {rd_len}, contract: {contract:?}, calldata: {calldata:?}"
131                );
132                let size = rd_len - offset;
133                panic_on_err_bad_decoding_bool!(
134                    DATA_CAP >= size;
135                    "not enough slice capacity, contract: {contract:?}, capacity: {DATA_CAP}, requested: {size}, calldata: {calldata:?}"
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, contract: {contract:?}, rd len: {rd_len}, offset: {offset}, calldata: {calldata:?}"
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, contract: {contract:?}, data cap: {DATA_CAP}, size: {size}, calldata: {calldata:?}"
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!(
191                    len == 32;
192                    "response didn't write 32 length, length: {len}, contract: {contract:?}, calldata: {calldata:?}"
193                );
194                (rc, U::from(v))
195            }
196
197            /// Same as the other slice function, though returning Option if error.
198            pub fn [<$base_fn _slice_opt>]<const DATA_CAP: usize>(
199                contract: Address,
200                calldata: &[u8],
201                $($value_param: $value_ty,)?
202                gas: u64,
203                offset: usize,
204            ) -> Option<(usize, [u8; DATA_CAP])> {
205                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
206                    contract,
207                    calldata,
208                    $($value_param,)?
209                    gas,
210                    offset,
211                );
212                if rc {
213                    Some((len, c))
214                } else {
215                    None
216                }
217            }
218
219            /// Same as the other slice function, returning Result depending on
220            /// return or revert.
221            pub fn [<$base_fn _slice_res>]<const DATA_CAP: usize>(
222                contract: Address,
223                calldata: &[u8],
224                $($value_param: $value_ty,)?
225                gas: u64,
226                offset: usize,
227            ) -> Result<(usize, [u8; DATA_CAP]), (usize, [u8; DATA_CAP])> {
228                let (rc, len, c) = [<$base_fn _slice>]::<DATA_CAP>(
229                    contract,
230                    calldata,
231                    $($value_param,)?
232                    gas,
233                    offset,
234                );
235                if rc {
236                    Ok((len, c))
237                } else {
238                    Err((len, c))
239                }
240            }
241
242            /// Same as the slice variant, except check the length of the code for the
243            /// address first. If code doesn't exist, then we return None.
244            pub fn [<safe_ $base_fn _slice>]<const DATA_CAP: usize>(
245                contract: Address,
246                calldata: &[u8],
247                $($value_param: $value_ty,)?
248                gas: u64,
249                offset: usize,
250            ) -> Option<(bool, usize, [u8; DATA_CAP])> {
251                if addr_has_code(contract) {
252                    Some([<$base_fn _slice>]::<DATA_CAP>(
253                        contract, calldata, $($value_param,)? gas, offset,
254                    ))
255                } else {
256                    None
257                }
258            }
259
260            /// Invoke call, only reading a single byte at the first word for a
261            /// check.
262            pub fn [<$base_fn _bool>](
263                contract: Address,
264                calldata: &[u8],
265                $($value_param: $value_ty,)?
266                gas: u64,
267            ) -> bool {
268                let (rc, _, v) = [<$base_fn _slice>]::<1>(
269                    contract, calldata, $($value_param,)? gas, 31,
270                );
271                rc && v[0] == 1
272            }
273
274            /// Invoke call, only reading a single byte at the first word for a
275            /// check. Returns Some if success.
276            pub fn [<$base_fn _bool_opt>](
277                contract: Address,
278                calldata: &[u8],
279                $($value_param: $value_ty,)?
280                gas: u64,
281            ) -> Option<()> {
282                if [<$base_fn _bool>](contract, calldata, $($value_param,)? gas) {
283                    Some(())
284                } else {
285                    None
286                }
287            }
288
289            /// Check the codesize before invoking call, only reading a single byte
290            /// at the location for a bool check. If the contract doesn't return anything,
291            /// then we assume everything went okay. If it does, then we check for true.
292            /// We don't return anything.
293            pub fn [<safe_ $base_fn _bool>](
294                contract: Address,
295                calldata: &[u8],
296                $($value_param: $value_ty,)?
297                gas: u64,
298            ) -> bool {
299                if addr_has_code(contract) {
300                    match [<$base_fn _partial>](contract, calldata, $($value_param,)? gas) {
301                        (true, 32) => {
302                            let mut b = [0u8; 1];
303                            unsafe { host::read_return_data(b.as_mut_ptr(), 31, 1) };
304                            b[0] == 1
305                        }
306                        (true, 0) => true,
307                        (true, _) => panic_on_err_bad_decoding_bool!(
308                            "word not returned, contract: {contract:?}, calldata: {calldata:?}"
309                        ),
310                        _ => false
311                    }
312                } else {
313                    false
314                }
315            }
316
317            /// Check the codesize before invoking call, returning a Option<()> if
318            /// the contract call worked, and the return value is true.
319            pub fn [<safe_ $base_fn _bool_opt>](
320                contract: Address,
321                calldata: &[u8],
322                $($value_param: $value_ty,)?
323                gas: u64,
324            ) -> Option<()> {
325                if [<safe_ $base_fn _bool>](
326                    contract,
327                    calldata,
328                    $($value_param,)?
329                    gas
330                ) {
331                    Some(())
332                } else {
333                    None
334                }
335            }
336
337            /// Call a contract, writing its returndata to the vector given. Behaves
338            /// the same way as the slice function.
339            #[cfg(feature = "alloc")]
340            pub fn [<$base_fn _vec>](
341                contract: Address,
342                calldata: &[u8],
343                $($value_param: $value_ty,)?
344                gas: u64,
345                offset: usize,
346            ) -> (bool, Vec<u8>) {
347                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
348                let size = rd_len - offset;
349                let mut b = Vec::with_capacity(size);
350                unsafe { b.set_len(size) }
351                unsafe { host::read_return_data(b.as_mut_ptr(), offset, size) };
352                (rc, b)
353            }
354
355            /// Same as the other vec function, returning Option if error.
356            #[cfg(feature = "alloc")]
357            pub fn [<$base_fn _vec_opt>](
358                contract: Address,
359                calldata: &[u8],
360                $($value_param: $value_ty,)?
361                gas: u64,
362                offset: usize,
363            ) -> Option<Vec<u8>> {
364                let (rc, v) = [<$base_fn _vec>](
365                    contract,
366                    calldata,
367                    $($value_param,)?
368                    gas,
369                    offset,
370                );
371                if rc {
372                    Some(v)
373                } else {
374                    None
375                }
376            }
377
378            /// Same as the other vec function, returning Result depending on
379            /// return or revert.
380            #[cfg(feature = "alloc")]
381            pub fn [<$base_fn _vec_res>](
382                contract: Address,
383                calldata: &[u8],
384                $($value_param: $value_ty,)?
385                gas: u64,
386                offset: usize,
387            ) -> Result<Vec<u8>, Vec<u8>> {
388                let (rc, rd) = [<$base_fn _vec>](
389                    contract,
390                    calldata,
391                    $($value_param,)?
392                    gas,
393                    offset,
394                );
395                if rc {
396                    Ok(rd)
397                } else {
398                    Err(rd)
399                }
400            }
401
402            /// Return a word if successful, a vector if a revert.
403            #[cfg(feature = "alloc")]
404            pub fn [<$base_fn _word_err_vec>](
405                contract: Address,
406                calldata: &[u8],
407                $($value_param: $value_ty,)?
408                gas: u64,
409            ) -> (bool, U, Option<Vec<u8>>) {
410                // Why use a vector for this entirely? Normally, you'd prefer stack space
411                // for data that might be used in a way with a performance context, like
412                // a word for an addition. For locality reasons. But, we want to save
413                // codesize by reducing complexity! So, we prefer to just depend on the
414                // allocator if that's what's in use here.
415                let (rc, v) = [<$base_fn _vec>](
416                    contract,
417                    calldata,
418                    $($value_param,)?
419                    gas,
420                    0
421                );
422                if rc {
423                    panic_on_err_bad_decoding_bool!(
424                        v.len() == 32;
425                        "not return for word, len: {}, contract: {}, calldata: {}",
426                        v.len(),
427                        const_hex::const_encode::<20, false>(&contract).as_str(),
428                        const_hex::encode(calldata)
429                    );
430                    let v: [u8; 32] = v.try_into().unwrap();
431                    (rc, U::from(v), None)
432                } else {
433                    (rc, U::ZERO, Some(v))
434                }
435            }
436
437            /// Allocates a slice with a single u8 if a word was returned, or
438            /// returns a Vec<u8> if something went wrong.
439            #[cfg(feature = "alloc")]
440            pub fn [<$base_fn _bool_err_vec>](
441                contract: Address,
442                calldata: &[u8],
443                $($value_param: $value_ty,)?
444                gas: u64
445            ) -> (bool, Option<Vec<u8>>) {
446                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
447                if rc {
448                    panic_on_err_bad_decoding_bool!(
449                        rd_len == 32;
450                        "bad length for bool word error vec, len: {rd_len}, contract: {}, calldata: {}",
451                        const_hex::const_encode::<20, false>(&contract).as_str(),
452                        const_hex::encode(calldata)
453                    );
454                    let mut b = [0u8; 1];
455                    unsafe {
456                        host::read_return_data(b.as_mut_ptr(), 31, 1);
457                    }
458                    (b[0] == 1, None)
459                } else {
460                    let mut b = Vec::with_capacity(rd_len);
461                    unsafe {
462                        host::read_return_data(b.as_mut_ptr(), 0, rd_len);
463                        b.set_len(rd_len);
464                    }
465                    (false, Some(b))
466                }
467            }
468
469            /// Call a function, returning whether the call returned true if the
470            /// contract returned something, or true if the contract returned nothing
471            /// but does exist. The vector contains revertdata if the call was
472            /// unsuccessful. Does not read returndata.
473            #[cfg(feature = "alloc")]
474            pub fn [<safe_ $base_fn _bool_err_vec>](
475                contract: Address,
476                calldata: &[u8],
477                $($value_param: $value_ty,)?
478                gas: u64
479            ) -> (bool, Option<Vec<u8>>) {
480                if !addr_has_code(contract) {
481                    return (false, None)
482                }
483                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
484                match (rc, rd_len) {
485                    (true, 32) => {
486                        let mut b = [0u8; 1];
487                        unsafe {
488                            host::read_return_data(b.as_mut_ptr(), 31, 1);
489                        }
490                        (b[0] == 1, None)
491                    }
492                    (true, 0) => (true, None),
493                    (true, _) => panic_on_err_bad_decoding_bool!(
494                        "word not returned for safe_ _bool_err_vec, contract: {}, calldata: {}",
495                        const_hex::const_encode::<20, false>(&contract).as_str(),
496                        const_hex::encode(calldata)
497                    ),
498                    (false, rd_len) => {
499                        let mut b = Vec::with_capacity(rd_len);
500                        unsafe {
501                            host::read_return_data(b.as_mut_ptr(), 0, rd_len);
502                            b.set_len(rd_len);
503                        }
504                        (false, Some(b))
505                    }
506                }
507            }
508
509            /// Return if the call was successful, and the vector of the revertdata.
510            #[cfg(feature = "alloc")]
511            pub fn [<$base_fn _unit_err_vec>](
512                contract: Address,
513                calldata: &[u8],
514                $($value_param: $value_ty,)?
515                gas: u64
516            ) -> (bool, Option<Vec<u8>>) {
517                let (rc, rd_len) = [<$base_fn _partial>](contract, calldata, $($value_param,)? gas);
518                if rc {
519                    (true, None)
520                } else {
521                    let mut b = Vec::with_capacity(rd_len);
522                    unsafe {
523                        host::read_return_data(b.as_mut_ptr(), 0, rd_len);
524                        b.set_len(rd_len);
525                    }
526                    (false, Some(b))
527                }
528            }
529
530            /// Result equivalent of _unit_err_vec.
531            #[cfg(feature = "alloc")]
532            pub fn [<$base_fn _unit_err_res>](
533                contract: Address,
534                calldata: &[u8],
535                $($value_param: $value_ty,)?
536                gas: u64
537            ) -> Result<(), Vec<u8>> {
538                match [<$base_fn _unit_err_vec>](
539                    contract,
540                    calldata,
541                    $($value_param,)?
542                    gas
543                )
544                {
545                    (false, Some(e)) => Err(e),
546                    (false, None) => {
547                        // How did this happen?
548                        unimplemented!()
549                    }
550                    (true, None) | (true, Some(_)) => Ok(())
551                }
552            }
553
554            /// Call a function, returning whether the call was successful. The vector contains
555            /// revertdata if the call was unsuccessful. Does not read returndata.
556            #[cfg(feature = "alloc")]
557            pub fn [<safe_ $base_fn _unit_err_vec>](
558                contract: Address,
559                calldata: &[u8],
560                $($value_param: $value_ty,)?
561                gas: u64
562            ) -> (bool, Option<Vec<u8>>) {
563                if addr_has_code(contract) {
564                    [<$base_fn _unit_err_vec>](contract, calldata, $($value_param,)? gas)
565                } else {
566                    (false, None)
567                }
568            }
569
570            /// Return a U word using a Result, or the vector for an error.
571            #[cfg(feature = "alloc")]
572            pub fn [<$base_fn _word_err_vec_res>](
573                contract: Address,
574                calldata: &[u8],
575                $($value_param: $value_ty,)?
576                gas: u64,
577            ) -> Result<U, Vec<u8>> {
578                let (rc, w, v) = [<$base_fn _word_err_vec>](
579                    contract,
580                    calldata,
581                    $($value_param,)?
582                    gas
583                );
584                if rc {
585                    Ok(w)
586                } else {
587                    Err(v.expect("vec not containing anything"))
588                }
589            }
590
591            /// Safely call a contract, with the same checks as the safe vec variant.
592            #[cfg(feature = "alloc")]
593            pub fn [<safe_ $base_fn _vec>](
594                contract: Address,
595                calldata: &[u8],
596                $($value_param: $value_ty,)?
597                gas: u64,
598                offset: usize,
599            ) -> Option<(bool, Vec<u8>)> {
600                if addr_has_code(contract) {
601                    Some([<$base_fn _vec>](contract, calldata, $($value_param,)? gas, offset))
602                } else {
603                    None
604                }
605            }
606        }
607    };
608}
609
610generate_call_variants!(call, has_value);
611generate_call_variants!(static_call);
612generate_call_variants!(delegate_call);