diff --git a/stylus-sdk/src/abi/impls.rs b/stylus-sdk/src/abi/impls.rs index c98ddb4..0db1414 100644 --- a/stylus-sdk/src/abi/impls.rs +++ b/stylus-sdk/src/abi/impls.rs @@ -3,7 +3,7 @@ use super::{AbiType, ConstString}; use alloc::{string::String, vec::Vec}; -use alloy_primitives::{Address, FixedBytes, Signed, Uint}; +use alloy_primitives::{Address, FixedBytes}; use alloy_sol_types::sol_data::{self, ByteCount, IntBitCount, SupportedFixedBytes, SupportedInt}; /// Generates a test to ensure the two-way relationship between Rust Types and Sol Types is bijective. @@ -52,29 +52,11 @@ where test_type!(bytes, "bytes calldata", super::Bytes); -impl AbiType for Uint -where - IntBitCount: SupportedInt, -{ - type SolType = sol_data::Uint; - - const ABI: ConstString = append_dec!("uint", BITS); -} - -// test_type!(uint160, "uint160", Uint<160, 3>); TODO: audit alloy -test_type!(uint256, "uint256", Uint<256, 4>); - -impl AbiType for Signed -where - IntBitCount: SupportedInt, -{ - type SolType = sol_data::Int; - - const ABI: ConstString = append_dec!("int", BITS); -} +test_type!(uint160, "uint160", alloy_primitives::Uint<160, 3>); +test_type!(uint256, "uint256", alloy_primitives::Uint<256, 4>); -// test_type!(int160, "int160", Signed<160, 3>); TODO: audit alloy -test_type!(int256, "int256", Signed<256, 4>); +test_type!(int160, "int160", alloy_primitives::Signed<160, 3>); +test_type!(int256, "int256", alloy_primitives::Signed<256, 4>); macro_rules! impl_int { ($bits:literal, $as_arg:expr, $unsigned:ty, $signed:ty) => { diff --git a/stylus-sdk/src/abi/ints.rs b/stylus-sdk/src/abi/ints.rs new file mode 100644 index 0000000..f776176 --- /dev/null +++ b/stylus-sdk/src/abi/ints.rs @@ -0,0 +1,252 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md + +//! Support for generic integer types found in [alloy_primitives]. + +use alloy_primitives::{ruint::UintTryFrom, Signed, Uint}; +use alloy_sol_types::{ + abi::token::WordToken, + private::SolTypeValue, + sol_data::{self, IntBitCount, SupportedInt}, + SolType, +}; + +use super::{AbiType, ConstString}; + +/// Represents [`intX`] in Solidity +/// +/// A custom type is used here in lieu of [alloy_sol_types::sol_data::Int] in order to keep our +/// bijectivity constraint. +/// +/// [`intX`]: https://docs.soliditylang.org/en/latest/types.html#integers +pub struct SolInt; + +impl AbiType for Signed +where + for<'a> sol_data::Int: SolType = WordToken>, + Converted>: From< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Int>: From>, +{ + type SolType = SolInt; + + const ABI: ConstString = ConstString::new(Self::SolType::SOL_NAME); +} + +impl SolType for SolInt +where + for<'a> sol_data::Int: SolType = WordToken>, + Converted>: From< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Int>: From>, +{ + type RustType = Signed; + type Token<'a> = WordToken; + + const SOL_NAME: &'static str = ConstString::new("int") + .concat(ConstString::from_decimal_number(BITS)) + .as_str(); + const ENCODED_SIZE: Option = Some(32); + + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + sol_data::Int::::valid_token(token) + } + + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + let converted: Converted<_> = sol_data::Int::::detokenize(token).into(); + converted.0 + } +} + +impl SolTypeValue> + for Signed +where + for<'a> sol_data::Int: SolType = WordToken>, + Converted>: From< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Int>: From>, +{ + #[inline] + fn stv_to_tokens(&self) -> WordToken { + let stv: Converted< as SupportedInt>::Int> = (*self).into(); + SolTypeValue::>::stv_to_tokens(&stv.0) + } + + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let stv: Converted< as SupportedInt>::Int> = (*self).into(); + SolTypeValue::>::stv_abi_encode_packed_to(&stv.0, out) + } + + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + self.stv_to_tokens().0 + } +} + +/// Represents [`uintX`] in Solidity +/// +/// A custom type is used here in lieu of [alloy_sol_types::sol_data::Uint] in order to keep our +/// bijectivity constraint. +/// +/// [`uintX`]: https://docs.soliditylang.org/en/latest/types.html#integers +pub struct SolUint; + +impl AbiType for Uint +where + for<'a> sol_data::Uint: SolType = WordToken>, + Uint: UintTryFrom< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Uint>: From>, +{ + type SolType = SolUint; + + const ABI: ConstString = ConstString::new(Self::SolType::SOL_NAME); +} + +impl SolType for SolUint +where + for<'a> sol_data::Uint: SolType = WordToken>, + Uint: UintTryFrom< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Uint>: From>, +{ + type RustType = Uint; + type Token<'a> = WordToken; + + const SOL_NAME: &'static str = ConstString::new("uint") + .concat(ConstString::from_decimal_number(BITS)) + .as_str(); + const ENCODED_SIZE: Option = Some(32); + + #[inline] + fn valid_token(token: &Self::Token<'_>) -> bool { + sol_data::Uint::::valid_token(token) + } + + #[inline] + fn detokenize(token: Self::Token<'_>) -> Self::RustType { + Uint::from(sol_data::Uint::::detokenize(token)) + } +} + +impl SolTypeValue> for Uint +where + for<'a> sol_data::Uint: SolType = WordToken>, + Uint: UintTryFrom< as SolType>::RustType>, + IntBitCount: SupportedInt, + Converted< as SupportedInt>::Uint>: From>, +{ + #[inline] + fn stv_to_tokens(&self) -> WordToken { + let stv: Converted< as SupportedInt>::Uint> = (*self).into(); + SolTypeValue::>::stv_to_tokens(&stv.0) + } + + #[inline] + fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec) { + let stv: Converted< as SupportedInt>::Uint> = (*self).into(); + SolTypeValue::>::stv_abi_encode_packed_to(&stv.0, out) + } + + #[inline] + fn stv_eip712_data_word(&self) -> alloy_sol_types::Word { + self.stv_to_tokens().0 + } +} + +// Marker for built-in number types +trait Builtin {} + +impl Builtin for i8 {} +impl Builtin for i16 {} +impl Builtin for i32 {} +impl Builtin for i64 {} +impl Builtin for i128 {} +impl Builtin for u8 {} +impl Builtin for u16 {} +impl Builtin for u32 {} +impl Builtin for u64 {} +impl Builtin for u128 {} + +// Int or Uint that has been converted to the appropriate SupportedInt type +struct Converted(T); + +impl From for Converted> +where + Signed: TryFrom, + as TryFrom>::Error: core::fmt::Debug, +{ + fn from(int: T) -> Self { + Converted(int.try_into().unwrap()) + } +} + +impl From> for Converted +where + IntBitCount: SupportedInt, + T: TryFrom>, + >>::Error: core::fmt::Debug, +{ + fn from(int: Signed) -> Self { + Converted(int.try_into().unwrap()) + } +} + +impl From> + for Converted> +{ + fn from(int: Signed) -> Self { + let slice = int.as_limbs(); + if slice.len() < TL { + let mut limbs = [0; TL]; + limbs[..slice.len()].copy_from_slice(slice); + Converted(Signed::from_limbs(limbs)) + } else { + let (head, tail) = slice.split_at(TL); + let mut limbs = [0; TL]; + limbs.copy_from_slice(head); + let mut overflow = tail.iter().any(|&limb| limb != 0); + if TL > 0 { + overflow |= limbs[TL - 1] > mask(TB); + limbs[TL - 1] &= mask(TB); + } + if overflow { + // This should not happen. + panic!("overflow in int conversion"); + } + Converted(Signed::from_limbs(limbs)) + } + } +} + +const fn mask(bits: usize) -> u64 { + if bits == 0 { + return 0; + } + let bits = bits % 64; + if bits == 0 { + u64::MAX + } else { + (1 << bits) - 1 + } +} + +impl From> for Converted +where + IntBitCount: SupportedInt, + T: TryFrom>, + >>::Error: core::fmt::Debug, +{ + fn from(uint: Uint) -> Self { + Converted(uint.try_into().unwrap()) + } +} + +impl From> for Converted> { + fn from(uint: Uint) -> Self { + Converted(Uint::from_limbs_slice(uint.as_limbs())) + } +} diff --git a/stylus-sdk/src/abi/mod.rs b/stylus-sdk/src/abi/mod.rs index b5aad86..b608e2f 100644 --- a/stylus-sdk/src/abi/mod.rs +++ b/stylus-sdk/src/abi/mod.rs @@ -30,6 +30,7 @@ pub mod export; mod bytes; mod const_string; mod impls; +mod ints; #[doc(hidden)] pub mod internal;