Skip to content

Commit

Permalink
Merge pull request #137 from OffchainLabs/int-return-types
Browse files Browse the repository at this point in the history
Support for alloy_primitive int types
  • Loading branch information
rory-ocl authored Aug 6, 2024
2 parents 2d98314 + 0116de8 commit b42ad27
Show file tree
Hide file tree
Showing 3 changed files with 258 additions and 23 deletions.
28 changes: 5 additions & 23 deletions stylus-sdk/src/abi/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -52,29 +52,11 @@ where

test_type!(bytes, "bytes calldata", super::Bytes);

impl<const BITS: usize, const LIMBS: usize> AbiType for Uint<BITS, LIMBS>
where
IntBitCount<BITS>: SupportedInt<Uint = Self>,
{
type SolType = sol_data::Uint<BITS>;

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<const BITS: usize, const LIMBS: usize> AbiType for Signed<BITS, LIMBS>
where
IntBitCount<BITS>: SupportedInt<Int = Self>,
{
type SolType = sol_data::Int<BITS>;

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) => {
Expand Down
252 changes: 252 additions & 0 deletions stylus-sdk/src/abi/ints.rs
Original file line number Diff line number Diff line change
@@ -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<const BITS: usize, const LIMBS: usize>;

impl<const BITS: usize, const LIMBS: usize> AbiType for Signed<BITS, LIMBS>
where
for<'a> sol_data::Int<BITS>: SolType<Token<'a> = WordToken>,
Converted<Signed<BITS, LIMBS>>: From<<sol_data::Int<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Int>: From<Signed<BITS, LIMBS>>,
{
type SolType = SolInt<BITS, LIMBS>;

const ABI: ConstString = ConstString::new(Self::SolType::SOL_NAME);
}

impl<const BITS: usize, const LIMBS: usize> SolType for SolInt<BITS, LIMBS>
where
for<'a> sol_data::Int<BITS>: SolType<Token<'a> = WordToken>,
Converted<Signed<BITS, LIMBS>>: From<<sol_data::Int<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Int>: From<Signed<BITS, LIMBS>>,
{
type RustType = Signed<BITS, LIMBS>;
type Token<'a> = WordToken;

const SOL_NAME: &'static str = ConstString::new("int")
.concat(ConstString::from_decimal_number(BITS))
.as_str();
const ENCODED_SIZE: Option<usize> = Some(32);

#[inline]
fn valid_token(token: &Self::Token<'_>) -> bool {
sol_data::Int::<BITS>::valid_token(token)
}

#[inline]
fn detokenize(token: Self::Token<'_>) -> Self::RustType {
let converted: Converted<_> = sol_data::Int::<BITS>::detokenize(token).into();
converted.0
}
}

impl<const BITS: usize, const LIMBS: usize> SolTypeValue<SolInt<BITS, LIMBS>>
for Signed<BITS, LIMBS>
where
for<'a> sol_data::Int<BITS>: SolType<Token<'a> = WordToken>,
Converted<Signed<BITS, LIMBS>>: From<<sol_data::Int<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Int>: From<Signed<BITS, LIMBS>>,
{
#[inline]
fn stv_to_tokens(&self) -> WordToken {
let stv: Converted<<IntBitCount<BITS> as SupportedInt>::Int> = (*self).into();
SolTypeValue::<sol_data::Int<BITS>>::stv_to_tokens(&stv.0)
}

#[inline]
fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
let stv: Converted<<IntBitCount<BITS> as SupportedInt>::Int> = (*self).into();
SolTypeValue::<sol_data::Int<BITS>>::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<const BITS: usize, const LIMBS: usize>;

impl<const BITS: usize, const LIMBS: usize> AbiType for Uint<BITS, LIMBS>
where
for<'a> sol_data::Uint<BITS>: SolType<Token<'a> = WordToken>,
Uint<BITS, LIMBS>: UintTryFrom<<sol_data::Uint<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Uint>: From<Uint<BITS, LIMBS>>,
{
type SolType = SolUint<BITS, LIMBS>;

const ABI: ConstString = ConstString::new(Self::SolType::SOL_NAME);
}

impl<const BITS: usize, const LIMBS: usize> SolType for SolUint<BITS, LIMBS>
where
for<'a> sol_data::Uint<BITS>: SolType<Token<'a> = WordToken>,
Uint<BITS, LIMBS>: UintTryFrom<<sol_data::Uint<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Uint>: From<Uint<BITS, LIMBS>>,
{
type RustType = Uint<BITS, LIMBS>;
type Token<'a> = WordToken;

const SOL_NAME: &'static str = ConstString::new("uint")
.concat(ConstString::from_decimal_number(BITS))
.as_str();
const ENCODED_SIZE: Option<usize> = Some(32);

#[inline]
fn valid_token(token: &Self::Token<'_>) -> bool {
sol_data::Uint::<BITS>::valid_token(token)
}

#[inline]
fn detokenize(token: Self::Token<'_>) -> Self::RustType {
Uint::from(sol_data::Uint::<BITS>::detokenize(token))
}
}

impl<const BITS: usize, const LIMBS: usize> SolTypeValue<SolUint<BITS, LIMBS>> for Uint<BITS, LIMBS>
where
for<'a> sol_data::Uint<BITS>: SolType<Token<'a> = WordToken>,
Uint<BITS, LIMBS>: UintTryFrom<<sol_data::Uint<BITS> as SolType>::RustType>,
IntBitCount<BITS>: SupportedInt,
Converted<<IntBitCount<BITS> as SupportedInt>::Uint>: From<Uint<BITS, LIMBS>>,
{
#[inline]
fn stv_to_tokens(&self) -> WordToken {
let stv: Converted<<IntBitCount<BITS> as SupportedInt>::Uint> = (*self).into();
SolTypeValue::<sol_data::Uint<BITS>>::stv_to_tokens(&stv.0)
}

#[inline]
fn stv_abi_encode_packed_to(&self, out: &mut alloy_sol_types::private::Vec<u8>) {
let stv: Converted<<IntBitCount<BITS> as SupportedInt>::Uint> = (*self).into();
SolTypeValue::<sol_data::Uint<BITS>>::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>(T);

impl<T: Builtin, const BITS: usize, const LIMBS: usize> From<T> for Converted<Signed<BITS, LIMBS>>
where
Signed<BITS, LIMBS>: TryFrom<T>,
<Signed<BITS, LIMBS> as TryFrom<T>>::Error: core::fmt::Debug,
{
fn from(int: T) -> Self {
Converted(int.try_into().unwrap())
}
}

impl<T: Builtin, const BITS: usize, const LIMBS: usize> From<Signed<BITS, LIMBS>> for Converted<T>
where
IntBitCount<BITS>: SupportedInt<Int = T>,
T: TryFrom<Signed<BITS, LIMBS>>,
<T as TryFrom<Signed<BITS, LIMBS>>>::Error: core::fmt::Debug,
{
fn from(int: Signed<BITS, LIMBS>) -> Self {
Converted(int.try_into().unwrap())
}
}

impl<const FB: usize, const FL: usize, const TB: usize, const TL: usize> From<Signed<FB, FL>>
for Converted<Signed<TB, TL>>
{
fn from(int: Signed<FB, FL>) -> 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<T: Builtin, const BITS: usize, const LIMBS: usize> From<Uint<BITS, LIMBS>> for Converted<T>
where
IntBitCount<BITS>: SupportedInt<Uint = T>,
T: TryFrom<Uint<BITS, LIMBS>>,
<T as TryFrom<Uint<BITS, LIMBS>>>::Error: core::fmt::Debug,
{
fn from(uint: Uint<BITS, LIMBS>) -> Self {
Converted(uint.try_into().unwrap())
}
}

impl<const BITS: usize, const LIMBS: usize> From<Uint<BITS, LIMBS>> for Converted<Uint<256, 4>> {
fn from(uint: Uint<BITS, LIMBS>) -> Self {
Converted(Uint::from_limbs_slice(uint.as_limbs()))
}
}
1 change: 1 addition & 0 deletions stylus-sdk/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pub mod export;
mod bytes;
mod const_string;
mod impls;
mod ints;

#[doc(hidden)]
pub mod internal;
Expand Down

0 comments on commit b42ad27

Please sign in to comment.