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::{string::String, 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_slice(s: &[u8]) {
38    unsafe { host::write_result(s.as_ptr(), s.len()) }
39}
40
41#[deprecated = "Replaced by write_slice"]
42pub fn write_result_slice(x: &[u8]) {
43    write_slice(x)
44}
45
46pub fn write_word(s: &U) {
47    write_slice(&s.0)
48}
49
50#[deprecated = "Renamed to write_word"]
51pub fn write_result_word(x: &U) {
52    write_word(x)
53}
54
55pub fn write_bool(v: bool) {
56    write_slice(&U::from(v).0)
57}
58
59const OFFSET_ARR: [u8; 32] = U::from_u32(32).0;
60
61/// Helper function that create a fresh array with the length and offset
62/// by concatinating arrays. Only writes in the end the array length
63/// given. CD_LEN is the total length, so must be 32 * 2 + ARR_LEN.
64pub fn write_array_slice_len<const ARR_LEN: usize, const CD_LEN: usize>(
65    arr: [u8; ARR_LEN],
66    len_arr: usize,
67) {
68    assert!(
69        ARR_LEN + 32 * 2 == CD_LEN,
70        "bad array length, need: {}",
71        ARR_LEN + 32 * 2
72    );
73    assert!(
74        ARR_LEN >= len_arr,
75        "array length {len_arr} larger than buffer {ARR_LEN}"
76    );
77    let U(len_arr) = U::from_usize(ARR_LEN);
78    let x: [u8; CD_LEN] = concat_arrays!(OFFSET_ARR, len_arr, arr);
79    write_slice(&x)
80}
81
82/// Helper function that create a fresh array with the length and offset
83/// by concatinating arrays. CD_LEN is the total length, so must be 32 * 2 + ARR_LEN.
84pub fn write_array_slice<const ARR_LEN: usize, const CD_LEN: usize>(arr: [u8; ARR_LEN]) {
85    write_array_slice_len::<ARR_LEN, CD_LEN>(arr, ARR_LEN)
86}
87
88#[cfg(feature = "alloc")]
89pub fn write_array_vec(arr: Vec<u8>) {
90    let mut v = Vec::with_capacity(32 * 2 + arr.len());
91    v.extend_from_slice(&U::from_usize(32).0);
92    v.extend_from_slice(&U::from_usize(arr.len()).0);
93    v.extend(arr);
94    write_slice(&v)
95}
96
97/// Write the slice given as Ethereum's String/Bytes type, with the length
98/// and offset prefixed.
99#[cfg(feature = "alloc")]
100pub fn write_bytes(x: &[u8]) {
101    let mut v = Vec::with_capacity(32 * 2 + x.len());
102    v.extend_from_slice(&U::from_usize(32).0);
103    v.extend_from_slice(&U::from_usize(x.len()).0);
104    v.extend_from_slice(x);
105    write_slice(&v)
106}
107
108#[cfg(feature = "alloc")]
109pub fn write_string(x: String) {
110    write_array_vec(x.into_bytes())
111}
112
113/// String buffer size we use for `write_str`.
114pub const STR_BUFFER_SIZE: usize = 128 + 32 * 2;
115
116/// Write a small string, allocating a small arena (192 bytes, 128
117/// characters max to use here) for the writing.
118pub fn write_str(x: &str) {
119    let len = 64 + x.len().div_ceil(32) * 32;
120    assert!(len <= STR_BUFFER_SIZE, "str too large");
121    let mut buf = [0u8; STR_BUFFER_SIZE];
122    // 0x20 for the first word:
123    buf[31] = 0x20;
124    buf[32..32 * 2].copy_from_slice(U::from(x.len()).as_slice());
125    buf[32 * 2..32 * 2 + x.len()].copy_from_slice(x.as_bytes());
126    write_slice(&buf[..len])
127}
128
129pub fn return_data_size() -> usize {
130    unsafe { host::return_data_size() }
131}
132
133pub use bobcat_cd::leftpad_addr;
134
135/// Like write_exit_call, except it only reverts with the
136/// returndata if the underlying call reverted. If it doesn't, then it
137/// just returns the slice.
138#[macro_export]
139macro_rules! revert_if_bad_call_vec {
140    ($e:expr) => {{
141        let (rc, rd) = $e;
142        if !rc {
143            $crate::write_slice(&rd);
144            return 1;
145        }
146        rd
147    }};
148}
149
150/// Reverts if the underlying call failed, using the vector that was
151/// returned as the third argument as slice.
152#[macro_export]
153macro_rules! revert_if_bad_call_unit_vec {
154    ($e:expr) => {{
155        let (rc, revertdata) = $e;
156        match (rc, revertdata) {
157            (true, _) => (),
158            (false, Some(v)) => {
159                $crate::write_slice(&v);
160                return 1;
161            }
162            (false, _) => return 1,
163        }
164    }};
165}
166
167/// Reverts with a message if the revertdata is Some, and if the rc is false.
168#[macro_export]
169macro_rules! revert_if_bad_call_slice_vec {
170    ($e:expr) => {{
171        let (rc, returndata, revertdata) = $e;
172        match (rc, revertdata) {
173            (true, _) => returndata,
174            (false, Some(v)) => {
175                $crate::write_slice(&v);
176                return 1;
177            }
178            (false, _) => return 1,
179        }
180    }};
181}
182
183#[macro_export]
184macro_rules! write_exit_res {
185    ($ident:expr) => {{
186        match $ident {
187            Ok(v) => {
188                $crate::write_slice(&v);
189                0
190            }
191            Err(v) => {
192                $crate::write_slice(&v);
193                1
194            }
195        }
196    }};
197}
198
199#[macro_export]
200macro_rules! write_exit_create {
201    ($ident:expr) => {{
202        let (addr, b, i) = $ident;
203        if addr != [0u8; 20] {
204            $crate::write_slice(&leftpad_addr(addr));
205            0
206        } else {
207            $crate::write_slice(&b[..i]);
208            1
209        }
210    }};
211}
212
213#[macro_export]
214macro_rules! write_exit_call {
215    ($ident:expr) => {{
216        let (rc, l, v) = $ident;
217        $crate::write_slice(&v[..l]);
218        if rc { 0 } else { 1 }
219    }};
220}
221
222/// Read arguments from the Stylus VM (calldata), using unsafe
223/// MaybeUninit code if we're on the wasm host. If we're on a non wasm
224/// host, zero it out then return it. We also return the length.
225pub fn read_args<const CAP: usize>(len: usize) -> ([u8; CAP], usize) {
226    assert!(CAP >= len, "cap not enough");
227    // SAFETY: This is safe since the host will write over this, and on
228    // the Stylus host this will always be zeroed out anyway.
229    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
230    let mut b = unsafe { core::mem::MaybeUninit::<[u8; CAP]>::uninit().assume_init() };
231    // On other hosts, we don't make the assumption about this and zero it out:
232    #[cfg(not(all(target_family = "wasm", target_os = "unknown")))]
233    let mut b = [0u8; CAP];
234    unsafe { host::read_args(b.as_mut_ptr()) };
235    (b, len)
236}
237
238#[cfg(all(target_arch = "riscv32", target_os = "none"))]
239pub fn args_len() -> usize {
240    unsafe { host::args_len() }
241}
242
243/// Read args and deserialise them into W, returning an error when the
244/// generated or built-in buffer cannot hold the calldata.
245pub fn read_cd_res<W: EvmCdDeserialise>(len: usize) -> Result<W, EvmCdError> {
246    let mut args = W::new_buffer(len)?;
247    unsafe { host::read_args(args.as_mut().as_mut_ptr()) };
248    W::deserialise(&args.as_ref()[..len])
249}
250
251/// Read args and deserialise them into W, panicking on invalid calldata.
252pub fn read_cd<W: EvmCdDeserialise>(len: usize) -> W {
253    read_cd_res::<W>(len).unwrap()
254}
255
256#[macro_export]
257macro_rules! read_args_safe {
258    ($len:expr, $max_len:expr) => {{
259        core::assert!($max_len >= $len, "{} < {}", $max_len, $len);
260        $crate::read_args::<$max_len>($len).0
261    }};
262}
263
264#[cfg(feature = "alloc")]
265pub fn read_args_vec(len: usize) -> Vec<u8> {
266    let mut b = Vec::with_capacity(len);
267    unsafe {
268        host::read_args(b.as_mut_ptr());
269        b.set_len(len);
270    };
271    b
272}
273
274pub fn msg_sender() -> Address {
275    let mut b = [0u8; 20];
276    unsafe { host::msg_sender(b.as_mut_ptr()) }
277    b
278}
279
280pub fn contract_address() -> Address {
281    let mut b = [0u8; 20];
282    unsafe { host::contract_address(b.as_mut_ptr()) }
283    b
284}
285
286pub fn msg_value() -> U {
287    let mut b = [0u8; 32];
288    unsafe { host::msg_value(b.as_mut_ptr()) }
289    U(b)
290}
291
292pub fn code_size(addr: Address) -> usize {
293    unsafe { host::account_code_size(addr.as_ptr()) }
294}
295
296pub fn code_slice<const CAP: usize>(
297    addr: Address,
298    size: usize,
299    offset: usize,
300) -> ([u8; CAP], usize) {
301    let mut b = [0u8; CAP];
302    assert!(CAP >= size, "not enough size: {size}, capacity: {CAP}");
303    let rd = unsafe { host::account_code(addr.as_ptr(), offset, size, b.as_mut_ptr()) };
304    (b, rd)
305}
306
307#[cfg(feature = "alloc")]
308pub fn code_vec_size(addr: Address, offset: usize, size: usize) -> Vec<u8> {
309    let mut b = Vec::with_capacity(size);
310    let rd = unsafe { host::account_code(addr.as_ptr(), offset, size, b.as_mut_ptr()) };
311    unsafe { b.set_len(rd) };
312    b
313}
314
315#[cfg(feature = "alloc")]
316pub fn code_vec(addr: Address, offset: usize) -> Vec<u8> {
317    code_vec_size(addr, offset, code_size(addr))
318}
319
320pub fn code_hash(addr: Address) -> U {
321    let mut b = U::ZERO;
322    unsafe { host::account_codehash(addr.as_ptr(), b.as_mut_ptr()) };
323    b
324}
325
326pub fn chain_id() -> u64 {
327    unsafe { host::chainid() }
328}
329
330pub fn block_timestamp() -> u64 {
331    unsafe { host::block_timestamp() }
332}
333
334pub fn block_basefee() -> U {
335    let mut out = U::ZERO;
336    unsafe { host::block_basefee(out.as_mut_ptr()) }
337    out
338}
339
340pub fn evm_gas_left() -> u64 {
341    unsafe { host::evm_gas_left() }
342}
343
344pub fn evm_ink_left() -> u64 {
345    unsafe { host::evm_ink_left() }
346}
347
348pub unsafe fn exit_early(code: usize) -> ! {
349    unsafe { host::exit_early(code as i32) }
350}
351
352pub unsafe fn storage_flush_cache(clear: bool) {
353    unsafe { host::storage_flush_cache(clear) }
354}
355
356/// Check if the contract interaction is reentrant.
357pub fn msg_reentrant() -> bool {
358    unsafe { host::msg_reentrant() }
359}
360
361/// Guard the function given, reverting if the contract call is reentrant,
362/// using the msg.reentrant message instead of transient storage.
363pub fn reentrancy_guard<R, F: FnOnce() -> R>(f: F) -> R {
364    assert!(!msg_reentrant(), "reentrancy is not allowed");
365    f()
366}
367
368#[cfg(all(test, feature = "std"))]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn read_cd_uses_the_deserialiser_buffer() {
374        let mut args = vec![0u8; 32];
375        args[28..].copy_from_slice(&7u32.to_be_bytes());
376        host::set_args(args);
377
378        assert_eq!(read_cd_res::<u32>(32).unwrap(), 7);
379        assert!(read_cd_res::<u32>(33).is_err());
380    }
381}