use alloc::vec::Vec;
use core::borrow::BorrowMut;
pub use alloy_primitives::Bytes;
use alloy_primitives::U256;
use alloy_sol_types::{abi::TokenSeq, private::SolTypeValue, SolType};
pub use const_string::ConstString;
#[cfg(feature = "export-abi")]
pub use export::GenerateAbi;
use stylus_core::{storage::TopLevelStorage, ValueDenier};
use crate::{console, host::VM, storage::StorageType, ArbResult};
#[cfg(feature = "export-abi")]
pub mod export;
mod const_string;
mod impls;
mod ints;
#[doc(hidden)]
pub mod internal;
pub trait Router<S, I = Self>
where
S: TopLevelStorage + BorrowMut<Self::Storage> + ValueDenier,
I: ?Sized,
{
type Storage;
fn route(storage: &mut S, selector: u32, input: &[u8]) -> Option<ArbResult>;
fn receive(storage: &mut S) -> Option<Result<(), Vec<u8>>>;
fn fallback(storage: &mut S, calldata: &[u8]) -> Option<ArbResult>;
fn constructor(storage: &mut S, calldata: &[u8]) -> Option<ArbResult>;
}
pub fn router_entrypoint<R, S>(input: alloc::vec::Vec<u8>, host: VM) -> ArbResult
where
R: Router<S>,
S: StorageType + TopLevelStorage + BorrowMut<R::Storage> + ValueDenier,
{
let mut storage = unsafe { S::new(U256::ZERO, 0, host) };
if input.is_empty() {
console!("no calldata provided");
if let Some(res) = R::receive(&mut storage) {
return res.map(|_| Vec::new());
}
if let Some(res) = R::fallback(&mut storage, &[]) {
return res;
}
return Err(Vec::new());
}
if input.len() >= 4 {
let selector = u32::from_be_bytes(TryInto::try_into(&input[..4]).unwrap());
if selector == CONSTRUCTOR_SELECTOR {
if let Some(res) = R::constructor(&mut storage, &input[4..]) {
return res;
}
} else if let Some(res) = R::route(&mut storage, selector, &input[4..]) {
return res;
} else {
console!("unknown method selector: {selector:08x}");
}
}
if let Some(res) = R::fallback(&mut storage, &input) {
return res;
}
Err(Vec::new())
}
pub trait AbiType {
type SolType: SolType<RustType = Self>;
const ABI: ConstString;
const SELECTOR_ABI: ConstString = Self::ABI;
const EXPORT_ABI_ARG: ConstString = Self::ABI;
const EXPORT_ABI_RET: ConstString = Self::ABI;
const CAN_BE_CALLDATA: bool = true;
fn abi_encode_return<RustTy: ?Sized + SolTypeValue<Self::SolType>>(rust: &RustTy) -> Vec<u8> {
Self::SolType::abi_encode(rust)
}
}
#[macro_export]
macro_rules! function_selector {
($name:expr $(,)?) => {{
const DIGEST: [u8; 32] = $crate::keccak_const::Keccak256::new()
.update($name.as_bytes())
.update(b"()")
.finalize();
$crate::abi::internal::digest_to_selector(DIGEST)
}};
($name:expr, $first:ty $(, $ty:ty)* $(,)?) => {{
const DIGEST: [u8; 32] = $crate::keccak_const::Keccak256::new()
.update($name.as_bytes())
.update(b"(")
.update(<$first as $crate::abi::AbiType>::SELECTOR_ABI.as_bytes())
$(
.update(b",")
.update(<$ty as $crate::abi::AbiType>::SELECTOR_ABI.as_bytes())
)*
.update(b")")
.finalize();
$crate::abi::internal::digest_to_selector(DIGEST)
}};
}
pub const CONSTRUCTOR_SELECTOR: u32 =
u32::from_be_bytes(function_selector!(internal::CONSTRUCTOR_BASE_NAME));
pub fn decode_params<T>(data: &[u8]) -> alloy_sol_types::Result<T>
where
T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
{
T::SolType::abi_decode_params_validate(data)
}
pub fn encode<T>(value: &T) -> Vec<u8>
where
T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
{
T::SolType::abi_encode(value)
}
pub fn encode_params<T>(value: &T) -> Vec<u8>
where
T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
{
T::SolType::abi_encode_params(value)
}
pub fn encoded_size<T>(value: &T) -> usize
where
T: AbiType + SolTypeValue<<T as AbiType>::SolType>,
{
T::SolType::abi_encoded_size(value)
}
#[cfg(test)]
fn test_encode_decode_params<T, B>(value: T, buffer: B)
where
T: core::fmt::Debug + PartialEq + AbiType + SolTypeValue<<T as AbiType>::SolType>,
for<'a> <<T as AbiType>::SolType as SolType>::Token<'a>: TokenSeq<'a>,
B: core::fmt::Debug + AsRef<[u8]>,
{
let encoded = encode_params(&value);
assert_eq!(encoded, buffer.as_ref());
let decoded = decode_params::<T>(buffer.as_ref()).unwrap();
assert_eq!(decoded, value);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_function_selector() {
use alloy_primitives::{Address, U256};
assert_eq!(u32::from_be_bytes(function_selector!("foo")), 0xc2985578);
assert_eq!(function_selector!("foo", Address), [0xfd, 0xf8, 0x0b, 0xda]);
const TEST_SELECTOR: [u8; 4] = function_selector!("foo", Address, U256);
assert_eq!(TEST_SELECTOR, 0xbd0d639f_u32.to_be_bytes());
}
#[test]
fn test_decode_params_validate_rejects_dirty_padding() {
use alloy_primitives::Address;
let mut dirty = [0u8; 32];
dirty[0] = 0xff; dirty[12..].copy_from_slice(&[0x01; 20]); let result = decode_params::<(Address,)>(&dirty);
assert!(
result.is_err(),
"decode_params should reject non-canonical address padding"
);
}
}