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}