Skip to main content

bobcat_entry/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3#[cfg(feature = "alloc")]
4extern crate alloc;
5
6#[cfg(feature = "alloc")]
7use alloc::vec::Vec;
8
9pub use bobcat_maths::U;
10
11type Address = [u8; 20];
12
13pub use bobcat_cd::{EvmCdDeserialise, EvmCdError, read_words};
14
15pub use bobcat_host as host;
16
17use array_concat::concat_arrays;
18
19pub fn balance(addr: Address) -> U {
20    let mut out = U::ZERO;
21    unsafe { host::account_balance(addr.as_ptr(), out.as_mut_ptr()) }
22    out
23}
24
25#[unsafe(no_mangle)]
26#[cfg(all(
27    target_family = "wasm",
28    target_os = "unknown",
29    not(feature = "dont-define-symbols")
30))]
31pub unsafe fn mark_used() {
32    unsafe { host::pay_for_memory_grow(0) }
33    panic!();
34}
35
36/// Write a result slice.
37pub fn write_result_slice(s: &[u8]) {
38    unsafe { host::write_result(s.as_ptr(), s.len()) }
39}
40
41pub fn write_result_word(s: &U) {
42    write_result_slice(&s.0)
43}
44
45pub fn write_result_bool(v: bool) {
46    write_result_slice(&U::from(v).0)
47}
48
49const OFFSET_ARR: [u8; 32] = U::from_u32(32).0;
50
51/// Helper function that create a fresh array with the length and offset
52/// by concatinating arrays.
53pub fn write_result_array_slice<const ARR_LEN: usize, const CD_LEN: usize>(arr: [u8; ARR_LEN]) {
54    assert!(
55        ARR_LEN + 32 * 2 == CD_LEN,
56        "bad array length, need: {}",
57        ARR_LEN + 32 * 2
58    );
59    let U(len_arr) = U::from_usize(ARR_LEN);
60    let x: [u8; ARR_LEN] = concat_arrays!(OFFSET_ARR, len_arr, arr);
61    write_result_slice(&x)
62}
63
64#[cfg(feature = "alloc")]
65pub fn write_result_array_vec(arr: Vec<u8>) {
66    let mut v = Vec::with_capacity(32 * 2 + arr.len());
67    v.extend_from_slice(&U::from_usize(32).0);
68    v.extend_from_slice(&U::from_usize(arr.len()).0);
69    v.extend(arr);
70    write_result_slice(&v)
71}
72
73pub fn return_data_size() -> usize {
74    unsafe { host::return_data_size() }
75}
76
77pub use bobcat_cd::leftpad_addr;
78
79/// Like write_result_exit_call, except it only reverts with the
80/// returndata if the underlying call reverted. If it doesn't, then it
81/// just returns the slice.
82#[macro_export]
83macro_rules! revert_if_bad_call_vec {
84    ($e:expr) => {{
85        let (rc, rd) = $e;
86        if !rc {
87            $crate::write_result_slice(&rd);
88            return 1;
89        }
90        rd
91    }};
92}
93
94/// Reverts if the underlying call failed, using the vector that was
95/// returned as the third argument as slice.
96#[macro_export]
97macro_rules! revert_if_bad_call_unit_vec {
98    ($e:expr) => {{
99        let (rc, revertdata) = $e;
100        match (rc, revertdata) {
101            (true, _) => (),
102            (false, Some(v)) => {
103                $crate::write_result_slice(&v);
104                return 1;
105            }
106            (false, _) => return 1,
107        }
108    }};
109}
110
111/// Reverts with a message if the revertdata is Some, and if the rc is false.
112#[macro_export]
113macro_rules! revert_if_bad_call_slice_vec {
114    ($e:expr) => {{
115        let (rc, returndata, revertdata) = $e;
116        match (rc, revertdata) {
117            (true, _) => returndata,
118            (false, Some(v)) => {
119                $crate::write_result_slice(&v);
120                return 1;
121            }
122            (false, _) => return 1,
123        }
124    }};
125}
126
127#[macro_export]
128macro_rules! write_result_exit_res {
129    ($ident:expr) => {{
130        match $ident {
131            Ok(v) => {
132                $crate::write_result_slice(&v);
133                0
134            }
135            Err(v) => {
136                $crate::write_result_slice(&v);
137                1
138            }
139        }
140    }};
141}
142
143#[macro_export]
144macro_rules! write_result_exit_create {
145    ($ident:expr) => {{
146        let (addr, b, i) = $ident;
147        if addr != [0u8; 20] {
148            $crate::write_result_slice(&leftpad_addr(addr));
149            0
150        } else {
151            $crate::write_result_slice(&b[..i]);
152            1
153        }
154    }};
155}
156
157#[macro_export]
158macro_rules! write_result_exit_call {
159    ($ident:expr) => {{
160        let (rc, l, v) = $ident;
161        $crate::write_result_slice(&v[..l]);
162        if rc { 0 } else { 1 }
163    }};
164}
165
166/// Read arguments from the Stylus VM (calldata), using unsafe
167/// MaybeUninit code if we're on the wasm host. If we're on a non wasm
168/// host, zero it out then return it. We also return the length.
169pub fn read_args<const CAP: usize>(len: usize) -> ([u8; CAP], usize) {
170    assert!(CAP >= len, "cap not enough");
171    // SAFETY: This is safe since the host will write over this, and on
172    // the Stylus host this will always be zeroed out anyway.
173    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
174    let mut b = unsafe { core::mem::MaybeUninit::<[u8; CAP]>::uninit().assume_init() };
175    // On other hosts, we don't make the assumption about this and zero it out:
176    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
177    let mut b = [0u8; CAP];
178    unsafe { host::read_args(b.as_mut_ptr()) };
179    (b, len)
180}
181
182#[cfg(all(target_arch = "riscv32", target_os = "none"))]
183pub fn args_len() -> usize {
184    unsafe { host::args_len() }
185}
186
187/// Read args and deserialise them into W, returning an error when the
188/// generated or built-in buffer cannot hold the calldata.
189pub fn read_cd_res<W: EvmCdDeserialise>(len: usize) -> Result<W, EvmCdError> {
190    let mut args = W::new_buffer(len)?;
191    unsafe { host::read_args(args.as_mut().as_mut_ptr()) };
192    W::deserialise(&args.as_ref()[..len])
193}
194
195/// Read args and deserialise them into W, panicking on invalid calldata.
196pub fn read_cd<W: EvmCdDeserialise>(len: usize) -> W {
197    read_cd_res::<W>(len).unwrap()
198}
199
200#[macro_export]
201macro_rules! read_args_safe {
202    ($len:expr, $max_len:expr) => {{
203        core::assert!($max_len >= $len, "{} < {}", $max_len, $len);
204        $crate::read_args::<$max_len>($len).0
205    }};
206}
207
208#[cfg(feature = "alloc")]
209pub fn read_args_vec(len: usize) -> Vec<u8> {
210    let mut b = Vec::with_capacity(len);
211    unsafe {
212        host::read_args(b.as_mut_ptr());
213        b.set_len(len);
214    };
215    b
216}
217
218pub fn msg_sender() -> Address {
219    let mut b = [0u8; 20];
220    unsafe { host::msg_sender(b.as_mut_ptr()) }
221    b
222}
223
224pub fn contract_address() -> Address {
225    let mut b = [0u8; 20];
226    unsafe { host::contract_address(b.as_mut_ptr()) }
227    b
228}
229
230pub fn msg_value() -> U {
231    let mut b = [0u8; 32];
232    unsafe { host::msg_value(b.as_mut_ptr()) }
233    U(b)
234}
235
236pub fn code_size(addr: Address) -> usize {
237    unsafe { host::account_code_size(addr.as_ptr()) }
238}
239
240pub fn code_slice<const CAP: usize>(
241    addr: Address,
242    size: usize,
243    offset: usize,
244) -> ([u8; CAP], usize) {
245    let mut b = [0u8; CAP];
246    assert!(CAP >= size, "not enough size: {size}, capacity: {CAP}");
247    let rd = unsafe { host::account_code(addr.as_ptr(), offset, size, b.as_mut_ptr()) };
248    (b, rd)
249}
250
251#[cfg(feature = "alloc")]
252pub fn code_vec_size(addr: Address, offset: usize, size: usize) -> Vec<u8> {
253    let mut b = Vec::with_capacity(size);
254    let rd = unsafe { host::account_code(addr.as_ptr(), offset, size, b.as_mut_ptr()) };
255    unsafe { b.set_len(rd) };
256    b
257}
258
259#[cfg(feature = "alloc")]
260pub fn code_vec(addr: Address, offset: usize) -> Vec<u8> {
261    code_vec_size(addr, offset, code_size(addr))
262}
263
264pub fn code_hash(addr: Address) -> U {
265    let mut b = U::ZERO;
266    unsafe { host::account_codehash(addr.as_ptr(), b.as_mut_ptr()) };
267    b
268}
269
270pub fn chain_id() -> u64 {
271    unsafe { host::chainid() }
272}
273
274pub fn block_timestamp() -> u64 {
275    unsafe { host::block_timestamp() }
276}
277
278pub fn block_basefee() -> U {
279    let mut out = U::ZERO;
280    unsafe { host::block_basefee(out.as_mut_ptr()) }
281    out
282}
283
284pub fn evm_gas_left() -> u64 {
285    unsafe { host::evm_gas_left() }
286}
287
288pub fn evm_ink_left() -> u64 {
289    unsafe { host::evm_ink_left() }
290}
291
292pub unsafe fn exit_early(code: usize) -> ! {
293    unsafe { host::exit_early(code as i32) }
294}
295
296pub unsafe fn storage_flush_cache(clear: bool) {
297    unsafe { host::storage_flush_cache(clear) }
298}
299
300#[cfg(all(test, feature = "std"))]
301mod tests {
302    use super::*;
303
304    #[test]
305    fn read_cd_uses_the_deserialiser_buffer() {
306        let mut args = vec![0u8; 32];
307        args[28..].copy_from_slice(&7u32.to_be_bytes());
308        host::set_args(args);
309
310        assert_eq!(read_cd_res::<u32>(32).unwrap(), 7);
311        assert!(read_cd_res::<u32>(33).is_err());
312    }
313}