From 0b04ffe7875630ebb58fcc1e842026b98dd52d4a Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Sun, 28 Aug 2022 21:17:48 +0200 Subject: [PATCH] feat: add uint8 type (#1639) * feat: add uint8 type * update changelog * derive default * fix: failing test --- CHANGELOG.md | 1 + .../src/contract/methods.rs | 14 ++- .../src/contract/types.rs | 12 +- ethers-contract/tests/it/abigen.rs | 10 ++ ethers-core/src/abi/codec.rs | 23 +++- ethers-core/src/abi/human_readable/lexer.rs | 7 ++ ethers-core/src/abi/mod.rs | 7 +- ethers-core/src/types/mod.rs | 3 + ethers-core/src/types/uint8.rs | 109 ++++++++++++++++++ 9 files changed, 178 insertions(+), 8 deletions(-) create mode 100644 ethers-core/src/types/uint8.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 65ed438c..30ed61aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- Add `Unit8` helper type [#1639](https://github.com/gakonst/ethers-rs/pull/1639) - Add `evm.deployedBytecode.immutableReferences` output selector [#1523](https://github.com/gakonst/ethers-rs/pull/1523) - Added `get_erc1155_token_transfer_events` function for etherscan client [#1503](https://github.com/gakonst/ethers-rs/pull/1503) - Add support for Geth `debug_traceTransaction` [#1469](https://github.com/gakonst/ethers-rs/pull/1469) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index 314d03df..58a2aaff 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -356,6 +356,7 @@ impl Context { param: &str, kind: &ParamType, ) -> Result { + let ethers_core = ethers_core_crate(); match kind { ParamType::Array(ty) => { let ty = self.expand_input_param_type(fun, param, ty)?; @@ -364,7 +365,18 @@ impl Context { }) } ParamType::FixedArray(ty, size) => { - let ty = self.expand_input_param_type(fun, param, ty)?; + let ty = match **ty { + ParamType::Uint(size) => { + if size / 8 == 1 { + // this prevents type ambiguity with `FixedBytes` + quote! { #ethers_core::types::Uint8} + } else { + self.expand_input_param_type(fun, param, ty)? + } + } + _ => self.expand_input_param_type(fun, param, ty)?, + }; + let size = *size; Ok(quote! {[#ty; #size]}) } diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs index acde39b3..78d66501 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs @@ -41,7 +41,17 @@ pub(crate) fn expand(kind: &ParamType) -> Result { } ParamType::FixedArray(t, n) => { // TODO(nlordell): see above - let inner = expand(t)?; + let inner = match **t { + ParamType::Uint(size) => { + if size / 8 == 1 { + // this prevents type ambiguity with `FixedBytes` + quote! { #ethers_core::types::Uint8} + } else { + expand(t)? + } + } + _ => expand(t)?, + }; let size = Literal::usize_unsuffixed(*n); Ok(quote! { [#inner; #size] }) } diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs index e8d2c441..77922d1a 100644 --- a/ethers-contract/tests/it/abigen.rs +++ b/ethers-contract/tests/it/abigen.rs @@ -679,3 +679,13 @@ fn can_generate_large_output_struct() { let r = GetByIdReturn(Info::default()); } + +#[test] +fn gen_complex_function() { + abigen!( + WyvernExchangeV1, + r#"[ + function atomicMatch_(address[14] addrs, uint[18] uints, uint8[8] feeMethodsSidesKindsHowToCalls, bytes calldataBuy, bytes calldataSell, bytes replacementPatternBuy, bytes replacementPatternSell, bytes staticExtradataBuy, bytes staticExtradataSell, uint8[2] vs, bytes32[5] rssMetadata) public payable + ]"#, + ); +} diff --git a/ethers-core/src/abi/codec.rs b/ethers-core/src/abi/codec.rs index 45b0ad2d..47970e0e 100644 --- a/ethers-core/src/abi/codec.rs +++ b/ethers-core/src/abi/codec.rs @@ -2,7 +2,7 @@ use crate::{ abi::{ AbiArrayType, AbiError, AbiType, Detokenize, Token, Tokenizable, TokenizableItem, Tokenize, }, - types::{Address, Bytes, H256, I256, U128, U256}, + types::{Address, Bytes, Uint8, H256, I256, U128, U256}, }; /// Trait for ABI encoding @@ -65,6 +65,7 @@ impl_abi_codec!( U128, U256, I256, + Uint8, u8, u16, u32, @@ -279,4 +280,24 @@ mod tests { let encoded = value.encode(); assert_eq!(value, String::decode(encoded).unwrap()); } + + #[test] + fn should_decode_array_of_fixed_uint8() { + // uint8[8] + let tokens = vec![Token::FixedArray(vec![ + Token::Uint(1.into()), + Token::Uint(2.into()), + Token::Uint(3.into()), + Token::Uint(4.into()), + Token::Uint(5.into()), + Token::Uint(6.into()), + Token::Uint(7.into()), + Token::Uint(8.into()), + ])]; + let data: [Uint8; 8] = Detokenize::from_tokens(tokens).unwrap(); + assert_eq!(data[0], 1); + assert_eq!(data[1], 2); + assert_eq!(data[2], 3); + assert_eq!(data[7], 8); + } } diff --git a/ethers-core/src/abi/human_readable/lexer.rs b/ethers-core/src/abi/human_readable/lexer.rs index 4c7fea58..6347565a 100644 --- a/ethers-core/src/abi/human_readable/lexer.rs +++ b/ethers-core/src/abi/human_readable/lexer.rs @@ -1218,4 +1218,11 @@ mod tests { event ); } + + #[test] + fn parse_large_function() { + let f = "function atomicMatch_(address[14] addrs, uint[18] uints, uint8[8] feeMethodsSidesKindsHowToCalls, bytes calldataBuy, bytes calldataSell, bytes replacementPatternBuy, bytes replacementPatternSell, bytes staticExtradataBuy, bytes staticExtradataSell, uint8[2] vs, bytes32[5] rssMetadata) public payable"; + + let _fun = HumanReadableParser::parse_function(f).unwrap(); + } } diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs index bf31b833..a13e729b 100644 --- a/ethers-core/src/abi/mod.rs +++ b/ethers-core/src/abi/mod.rs @@ -1,10 +1,9 @@ //! This module implements extensions to the [`ethabi`](https://docs.rs/ethabi) API. // Adapted from [Gnosis' ethcontract](https://github.com/gnosis/ethcontract-rs/blob/master/common/src/abiext.rs) use crate::{ - types::{Bytes, Selector}, + types::{Bytes, Selector, Uint8, H256, H512, I256, U128, U256, U64}, utils::id, }; - pub use ethabi::{self, Contract as Abi, *}; mod tokens; @@ -23,9 +22,6 @@ mod human_readable; pub use human_readable::{ lexer::HumanReadableParser, parse as parse_abi, parse_str as parse_abi_str, AbiParser, }; - -use crate::types::{H256, H512, I256, U128, U256, U64}; - mod sealed { use ethabi::{Event, Function}; @@ -166,6 +162,7 @@ impl_abi_type!( str => String, H256 => FixedBytes(32), H512 => FixedBytes(64), + Uint8 => Uint(8), U64 => Uint(64), U128 => Uint(128), U256 => Uint(256), diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs index 9c10f25f..c97dfef3 100644 --- a/ethers-core/src/types/mod.rs +++ b/ethers-core/src/types/mod.rs @@ -24,6 +24,9 @@ pub use path_or_string::PathOrString; mod u256; pub use u256::*; +mod uint8; +pub use uint8::*; + mod i256; pub use i256::{Sign, I256}; diff --git a/ethers-core/src/types/uint8.rs b/ethers-core/src/types/uint8.rs new file mode 100644 index 00000000..3c4ca0e7 --- /dev/null +++ b/ethers-core/src/types/uint8.rs @@ -0,0 +1,109 @@ +//! This module contains a helper type for `uint8` +//! +//! The reason this exists is to circumvent ambiguity with fixed bytes arrays + +use crate::abi::{InvalidOutputType, Tokenizable, TokenizableItem}; +use ethabi::{ethereum_types::U256, Token}; +use serde::{Deserialize, Serialize}; +use std::ops::{Add, Sub}; + +/// A wrapper for `u8` +/// +/// Note: this type is only necessary in conjunction with `FixedBytes` so that `[Uint8; 8]` is +/// recognized as `uint8[8]` and not fixed bytes. +/// +/// See also +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default, Ord, PartialOrd)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Uint8(u8); + +impl From for Uint8 { + fn from(val: u8) -> Self { + Uint8(val) + } +} + +impl From for u8 { + fn from(val: Uint8) -> Self { + val.0 + } +} + +impl From for U256 { + fn from(val: Uint8) -> Self { + U256::from(val.0) + } +} + +impl PartialEq for Uint8 { + fn eq(&self, other: &u8) -> bool { + self.0 == *other + } +} + +impl Add for Uint8 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Uint8(self.0 + rhs.0) + } +} + +impl Sub for Uint8 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Uint8(self.0 - rhs.0) + } +} + +impl Add for Uint8 { + type Output = Self; + + fn add(self, rhs: u8) -> Self::Output { + Uint8(self.0 + rhs) + } +} + +impl Sub for Uint8 { + type Output = Self; + + fn sub(self, rhs: u8) -> Self::Output { + Uint8(self.0 - rhs) + } +} + +impl Tokenizable for Uint8 { + fn from_token(token: Token) -> Result { + match token { + Token::Int(data) | Token::Uint(data) => { + if data > U256::from(u8::MAX) { + return Err(InvalidOutputType("Integer overflow when casting to u8".to_string())) + } + Ok(Uint8(data.low_u32() as u8)) + } + other => Err(InvalidOutputType(format!("Expected `uint8`, got {:?}", other))), + } + } + fn into_token(self) -> Token { + Token::Uint(self.into()) + } +} + +impl TokenizableItem for Uint8 {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::abi::AbiType; + use ethabi::ParamType; + + #[test] + fn uint8_array() { + assert_eq!( + <[Uint8; 8usize]>::param_type(), + ParamType::FixedArray(Box::new(ParamType::Uint(8),), 8) + ); + } +}