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