Skip to main content

stylus_sdk/abi/
mod.rs

1// Copyright 2023-2024, Offchain Labs, Inc.
2// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/main/licenses/COPYRIGHT.md
3
4//! Solidity ABIs for Rust types.
5//!
6//! Alloy provides a 1-way mapping of Solidity types to Rust ones via [`SolType`].
7//! This module provides the inverse mapping, forming a bijective, 2-way relationship between Rust
8//! and Solidity.
9//!
10//! This allows the [`prelude`][prelude] macros to generate method selectors, export
11//! Solidity interfaces, and otherwise facilitate inter-op between Rust and Solidity contracts.
12//!
13//! Notably, the SDK treats `Vec<u8>` as a Solidity `uint8[]`.
14//! For a Solidity `bytes`, see [`alloy_primitives::Bytes`].
15//!
16//! [prelude]: crate::prelude
17
18use alloc::vec::Vec;
19use core::borrow::BorrowMut;
20
21pub use alloy_primitives::Bytes;
22use alloy_primitives::U256;
23use alloy_sol_types::{abi::TokenSeq, private::SolTypeValue, SolType};
24pub use const_string::ConstString;
25#[cfg(feature = "export-abi")]
26pub use export::GenerateAbi;
27use stylus_core::{storage::TopLevelStorage, ValueDenier};
28
29use crate::{console, host::VM, storage::StorageType, ArbResult};
30
31#[cfg(feature = "export-abi")]
32pub mod export;
33
34mod const_string;
35mod impls;
36mod ints;
37
38#[doc(hidden)]
39pub mod internal;
40
41/// Executes a method given a selector and calldata.
42/// This trait can be automatically implemented via `#[public]`.
43/// Composition with other routers is possible via `#[inherit]`.
44pub trait Router<S, I = Self>
45where
46    S: TopLevelStorage + BorrowMut<Self::Storage> + ValueDenier,
47    I: ?Sized,
48{
49    /// The type the [`TopLevelStorage`] borrows into. Usually just `Self`.
50    type Storage;
51
52    /// Tries to find and execute a method for the given selector, returning `None` if none is
53    /// found. Routes add via `#[inherit]` will only execute if no match is found among `Self`.
54    /// This means that it is possible to override a method by redefining it in `Self`.
55    fn route(storage: &mut S, selector: u32, input: &[u8]) -> Option<ArbResult>;
56
57    /// Receive function for this contract. Called when no calldata is provided.
58    /// A receive function may not be defined, in which case this method will return None.
59    /// Receive functions are always payable, take in no inputs, and return no outputs.
60    /// If defined, they will always be called when a transaction does not send any
61    /// calldata, regardless of the transaction having a value attached.
62    fn receive(storage: &mut S) -> Option<Result<(), Vec<u8>>>;
63
64    /// Called when no receive function is defined or when the transaction has calldata but it
65    /// doesn't match any function selector.
66    /// If no #[fallback] function is defined in the contract, then any transactions with calldata
67    /// that do not match a selector will revert.
68    /// A fallback function may have two different implementations. It can be either declared
69    /// without any input or output, or with bytes input calldata and bytes output. If a user
70    /// defines a fallback function with no input or output, then this method will be called
71    /// and the underlying user-defined function will simply be invoked with no input.
72    /// A fallback function can be declared as payable. If not payable, then any transactions
73    /// that trigger a fallback with value attached will revert.
74    fn fallback(storage: &mut S, calldata: &[u8]) -> Option<ArbResult>;
75
76    /// The router_entrypoint calls the constructor when the selector is CONSTRUCTOR_SELECTOR.
77    /// The implementation should: decode the calldata and pass the parameters to the user-defined
78    /// constructor; and call internal::constructor_guard to ensure it is only executed once.
79    /// Since each constructor has its own set of parameters, this function won't call the
80    /// constructors for inherited structs automatically. Instead, the user-defined function should
81    /// call the base classes constructors.
82    /// A constructor function can be declared as payable. If not payable, then any transactions
83    /// that trigger the constructor with value attached will revert.
84    fn constructor(storage: &mut S, calldata: &[u8]) -> Option<ArbResult>;
85}
86
87/// Entrypoint used when `#[entrypoint]` is used on a contract struct.
88/// Solidity requires specific routing logic for situations in which no function selector
89/// matches the input calldata in the form of two different functions named "receive" and
90/// "fallback". The purity and type definitions are as follows:
91///
92/// - receive takes no input data, returns no data, and is always payable.
93/// - fallback offers two possible implementations. It can be either declared without input or
94///   return
95//    parameters, or with input bytes calldata and return bytes memory.
96//
97//  The fallback function MAY be payable. If not payable, then any transactions not matching any
98//  other function which send value will revert.
99//
100//  The order of routing semantics for receive and fallback work as follows:
101//
102//  - If a receive function exists, it is always called whenever calldata is empty, even if no value
103//    is received in the transaction. It is implicitly payable.
104//  - Fallback is called when no other function matches a selector. If a receive function is not
105//    defined, then calls with no input calldata will be routed to the fallback function.
106pub fn router_entrypoint<R, S>(input: alloc::vec::Vec<u8>, host: VM) -> ArbResult
107where
108    R: Router<S>,
109    S: StorageType + TopLevelStorage + BorrowMut<R::Storage> + ValueDenier,
110{
111    let mut storage = unsafe { S::new(U256::ZERO, 0, host) };
112
113    if input.is_empty() {
114        console!("no calldata provided");
115        if let Some(res) = R::receive(&mut storage) {
116            return res.map(|_| Vec::new());
117        }
118        // Try fallback function with no inputs if defined.
119        if let Some(res) = R::fallback(&mut storage, &[]) {
120            return res;
121        }
122        // Revert as no receive or fallback were defined.
123        return Err(Vec::new());
124    }
125
126    if input.len() >= 4 {
127        let selector = u32::from_be_bytes(TryInto::try_into(&input[..4]).unwrap());
128        if selector == CONSTRUCTOR_SELECTOR {
129            if let Some(res) = R::constructor(&mut storage, &input[4..]) {
130                return res;
131            }
132        } else if let Some(res) = R::route(&mut storage, selector, &input[4..]) {
133            return res;
134        } else {
135            console!("unknown method selector: {selector:08x}");
136        }
137    }
138
139    // Try fallback function.
140    if let Some(res) = R::fallback(&mut storage, &input) {
141        return res;
142    }
143
144    Err(Vec::new())
145}
146
147/// Provides a mapping of Rust to Solidity types.
148/// When combined with alloy, which provides the reverse direction, a two-way relationship is
149/// formed.
150///
151/// Additionally, `AbiType` provides a `const` equivalent to alloy's [`SolType::sol_type_name`].
152pub trait AbiType {
153    /// The associated Solidity type.
154    type SolType: SolType<RustType = Self>;
155
156    /// Equivalent to [`SolType::sol_type_name`], but `const`.
157    const ABI: ConstString;
158
159    /// String used in the function selector.
160    const SELECTOR_ABI: ConstString = Self::ABI;
161
162    /// String to use when the type is an interface method argument.
163    const EXPORT_ABI_ARG: ConstString = Self::ABI;
164
165    /// String to use when the type is an interface method return value.
166    const EXPORT_ABI_RET: ConstString = Self::ABI;
167
168    /// Whether the type is allowed in calldata
169    const CAN_BE_CALLDATA: bool = true;
170
171    /// Encode the rust value as the return of a Stylus function.
172    fn abi_encode_return<RustTy: ?Sized + SolTypeValue<Self::SolType>>(rust: &RustTy) -> Vec<u8> {
173        Self::SolType::abi_encode(rust)
174    }
175}
176
177/// Generates a function selector for the given method and its args.
178#[macro_export]
179macro_rules! function_selector {
180    ($name:expr $(,)?) => {{
181        const DIGEST: [u8; 32] = $crate::keccak_const::Keccak256::new()
182            .update($name.as_bytes())
183            .update(b"()")
184            .finalize();
185        $crate::abi::internal::digest_to_selector(DIGEST)
186    }};
187
188    ($name:expr, $first:ty $(, $ty:ty)* $(,)?) => {{
189        const DIGEST: [u8; 32] = $crate::keccak_const::Keccak256::new()
190            .update($name.as_bytes())
191            .update(b"(")
192            .update(<$first as $crate::abi::AbiType>::SELECTOR_ABI.as_bytes())
193            $(
194                .update(b",")
195                .update(<$ty as $crate::abi::AbiType>::SELECTOR_ABI.as_bytes())
196            )*
197            .update(b")")
198            .finalize();
199        $crate::abi::internal::digest_to_selector(DIGEST)
200    }};
201}
202
203/// The function selector for Stylus constructors.
204pub const CONSTRUCTOR_SELECTOR: u32 =
205    u32::from_be_bytes(function_selector!(internal::CONSTRUCTOR_BASE_NAME));
206
207/// ABI decode a tuple of parameters
208pub fn decode_params<T>(data: &[u8]) -> alloy_sol_types::Result<T>
209where
210    T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
211    for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
212{
213    T::SolType::abi_decode_params_validate(data)
214}
215
216/// ABI encode a value
217pub fn encode<T>(value: &T) -> Vec<u8>
218where
219    T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
220{
221    T::SolType::abi_encode(value)
222}
223
224/// ABI encode a tuple of parameters
225pub fn encode_params<T>(value: &T) -> Vec<u8>
226where
227    T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
228    for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
229{
230    T::SolType::abi_encode_params(value)
231}
232
233/// Encoded size of some sol type
234pub fn encoded_size<T>(value: &T) -> usize
235where
236    T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
237{
238    T::SolType::abi_encoded_size(value)
239}
240
241/// Parform a test of both the encode and decode functions for a given type
242///
243/// This is intended for use within unit tests.
244#[cfg(test)]
245fn test_encode_decode_params<T, B>(value: T, buffer: B)
246where
247    T: core::fmt::Debug + PartialEq + AbiType + SolTypeValue<<T as AbiType>::SolType>,
248    for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
249    B: core::fmt::Debug + AsRef<[u8]>,
250{
251    let encoded = encode_params(&value);
252    assert_eq!(encoded, buffer.as_ref());
253
254    let decoded = decode_params::<T>(buffer.as_ref()).unwrap();
255    assert_eq!(decoded, value);
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_function_selector() {
264        use alloy_primitives::{Address, U256};
265        assert_eq!(u32::from_be_bytes(function_selector!("foo")), 0xc2985578);
266        assert_eq!(function_selector!("foo", Address), [0xfd, 0xf8, 0x0b, 0xda]);
267
268        const TEST_SELECTOR: [u8; 4] = function_selector!("foo", Address, U256);
269        assert_eq!(TEST_SELECTOR, 0xbd0d639f_u32.to_be_bytes());
270    }
271
272    #[test]
273    fn test_decode_params_validate_rejects_dirty_padding() {
274        use alloy_primitives::Address;
275        // An ABI-encoded address is 32 bytes: 12 zero-padding bytes + 20 address bytes.
276        // Construct one with non-zero padding — valid for non-validate decode, but
277        // abi_decode_params_validate should reject it via type_check.
278        let mut dirty = [0u8; 32];
279        dirty[0] = 0xff; // dirty high byte in padding region
280        dirty[12..].copy_from_slice(&[0x01; 20]); // address bytes
281        let result = decode_params::<(Address,)>(&dirty);
282        assert!(
283            result.is_err(),
284            "decode_params should reject non-canonical address padding"
285        );
286    }
287}