From eede86df41c8300a40b6b6061e10b3b78a510586 Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 28 Oct 2021 00:07:24 +0200 Subject: [PATCH] feat: add abi code trait impls (#531) * feat: use const generics for array tokenize * feat: add abi encode decode impls * test: add some tests * chore: move abi codec to core * update changelog --- CHANGELOG.md | 1 + .../src/contract/methods.rs | 10 +- .../ethers-contract-derive/src/call.rs | 19 +- ethers-contract/src/base.rs | 20 +- ethers-contract/src/call.rs | 29 +-- ethers-contract/src/codec.rs | 14 -- ethers-contract/src/lib.rs | 3 - ethers-contract/tests/abigen.rs | 26 +- ethers-contract/tests/common/derive.rs | 2 +- ethers-core/Cargo.toml | 1 + ethers-core/src/abi/codec.rs | 228 ++++++++++++++++++ ethers-core/src/abi/error.rs | 17 ++ ethers-core/src/abi/mod.rs | 8 +- ethers-core/src/abi/tokens.rs | 160 +++++------- 14 files changed, 362 insertions(+), 176 deletions(-) delete mode 100644 ethers-contract/src/codec.rs create mode 100644 ethers-core/src/abi/codec.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index fdf0b046..93974d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Unreleased +- move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types [#531](https://github.com/gakonst/ethers-rs/pull/531) - add `EthCall` trait and derive macro which generates matching structs for contract calls [#517](https://github.com/gakonst/ethers-rs/pull/517) - `abigen!` now generates `Display` for all events using the new `EthDisplay` macro [#513](https://github.com/gakonst/ethers-rs/pull/513) - `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501) diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs index f747ba87..b3ee6bfa 100644 --- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs +++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs @@ -111,10 +111,10 @@ impl Context { #(#variant_names(#struct_names)),* } - impl #ethers_contract::AbiDecode for #enum_name { - fn decode(data: impl AsRef<[u8]>) -> Result { + impl #ethers_core::abi::AbiDecode for #enum_name { + fn decode(data: impl AsRef<[u8]>) -> Result { #( - if let Ok(decoded) = <#struct_names as #ethers_contract::AbiDecode>::decode(data.as_ref()) { + if let Ok(decoded) = <#struct_names as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) { return Ok(#enum_name::#variant_names(decoded)) } )* @@ -122,8 +122,8 @@ impl Context { } } - impl #ethers_contract::AbiEncode for #enum_name { - fn encode(self) -> Result<#ethers_core::types::Bytes, #ethers_contract::AbiError> { + impl #ethers_core::abi::AbiEncode for #enum_name { + fn encode(self) -> Vec { match self { #( #enum_name::#variant_names(element) => element.encode() diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs index d41a080a..472335bd 100644 --- a/ethers-contract/ethers-contract-derive/src/call.rs +++ b/ethers-contract/ethers-contract-derive/src/call.rs @@ -95,14 +95,27 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream { fn abi_signature() -> ::std::borrow::Cow<'static, str> { #abi.into() } - } - impl #contract_crate::AbiDecode for #name { - fn decode(bytes: impl AsRef<[u8]>) -> Result { + impl #core_crate::abi::AbiDecode for #name { + fn decode(bytes: impl AsRef<[u8]>) -> Result { #decode_impl } } + + impl #core_crate::abi::AbiEncode for #name { + fn encode(self) -> ::std::vec::Vec { + let tokens = #core_crate::abi::Tokenize::into_tokens(self); + let selector = ::selector(); + let encoded = #core_crate::abi::encode(&tokens); + selector + .iter() + .copied() + .chain(encoded.into_iter()) + .collect() + } + } + }; let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input); diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs index a98b9b14..d05a94ea 100644 --- a/ethers-contract/src/base.rs +++ b/ethers-contract/src/base.rs @@ -1,9 +1,8 @@ use crate::Contract; +pub use ethers_core::abi::AbiError; use ethers_core::{ - abi::{ - Abi, Detokenize, Error, Event, Function, FunctionExt, InvalidOutputType, RawLog, Tokenize, - }, + abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Tokenize}, types::{Address, Bytes, Selector, H256}, }; use ethers_providers::Middleware; @@ -14,21 +13,6 @@ use std::{ hash::Hash, sync::Arc, }; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum AbiError { - /// Thrown when the ABI decoding fails - #[error(transparent)] - DecodingError(#[from] ethers_core::abi::Error), - - /// Thrown when detokenizing an argument - #[error(transparent)] - DetokenizationError(#[from] InvalidOutputType), - - #[error("missing or wrong function selector")] - WrongSelector, -} /// A reduced form of `Contract` which just takes the `abi` and produces /// ABI encoded data for its functions. diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs index e58e727c..fc84b24e 100644 --- a/ethers-contract/src/call.rs +++ b/ethers-contract/src/call.rs @@ -1,23 +1,20 @@ use super::base::{decode_function_data, AbiError}; use ethers_core::{ - abi::{Detokenize, Function, InvalidOutputType}, + abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable}, types::{ - transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, TransactionRequest, U256, + transaction::eip2718::TypedTransaction, Address, BlockId, Bytes, Selector, + TransactionRequest, U256, }, + utils::id, }; use ethers_providers::{Middleware, PendingTransaction, ProviderError}; -use std::borrow::Cow; -use std::{fmt::Debug, marker::PhantomData, sync::Arc}; +use std::{borrow::Cow, fmt::Debug, marker::PhantomData, sync::Arc}; -use crate::{AbiDecode, AbiEncode}; -use ethers_core::abi::{Tokenizable, Tokenize}; -use ethers_core::types::Selector; -use ethers_core::utils::id; use thiserror::Error as ThisError; /// A helper trait for types that represent all call input parameters of a specific function -pub trait EthCall: Tokenizable + AbiDecode + Send + Sync { +pub trait EthCall: Tokenizable + AbiDecode + AbiEncode + Send + Sync { /// The name of the function fn function_name() -> Cow<'static, str>; @@ -30,20 +27,6 @@ pub trait EthCall: Tokenizable + AbiDecode + Send + Sync { } } -impl AbiEncode for T { - fn encode(self) -> Result { - let tokens = self.into_tokens(); - let selector = Self::selector(); - let encoded = ethers_core::abi::encode(&tokens); - let encoded: Vec<_> = selector - .iter() - .copied() - .chain(encoded.into_iter()) - .collect(); - Ok(encoded.into()) - } -} - #[derive(ThisError, Debug)] /// An Error which is thrown when interacting with a smart contract pub enum ContractError { diff --git a/ethers-contract/src/codec.rs b/ethers-contract/src/codec.rs deleted file mode 100644 index 19060bac..00000000 --- a/ethers-contract/src/codec.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::AbiError; -use ethers_core::types::Bytes; - -/// Trait for ABI encoding -pub trait AbiEncode { - /// ABI encode the type - fn encode(self) -> Result; -} - -/// Trait for ABI decoding -pub trait AbiDecode: Sized { - /// Decodes the ABI encoded data - fn decode(bytes: impl AsRef<[u8]>) -> Result; -} diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs index e46fc305..b72174e0 100644 --- a/ethers-contract/src/lib.rs +++ b/ethers-contract/src/lib.rs @@ -31,9 +31,6 @@ pub use event::EthEvent; mod log; pub use log::{decode_logs, EthLogDecode, LogMeta}; -mod codec; -pub use codec::{AbiDecode, AbiEncode}; - mod stream; mod multicall; diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs index 49eb6811..6ec65770 100644 --- a/ethers-contract/tests/abigen.rs +++ b/ethers-contract/tests/abigen.rs @@ -1,7 +1,7 @@ #![cfg(feature = "abigen")] //! Test cases to validate the `abigen!` macro -use ethers_contract::{abigen, AbiDecode, AbiEncode, EthEvent}; -use ethers_core::abi::{Address, Tokenizable}; +use ethers_contract::{abigen, EthEvent}; +use ethers_core::abi::{AbiDecode, AbiEncode, Address, Tokenizable}; use ethers_core::types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256}; use ethers_core::utils::Solc; use ethers_providers::Provider; @@ -183,18 +183,18 @@ fn can_gen_human_readable_with_structs() { addr: Address::random(), }; let encoded_call = contract.encode("bar", (call.x, call.y, call.addr)).unwrap(); - assert_eq!(encoded_call, call.clone().encode().unwrap()); + assert_eq!(encoded_call, call.clone().encode().into()); let decoded_call = BarCall::decode(encoded_call.as_ref()).unwrap(); assert_eq!(call, decoded_call); let contract_call = SimpleContractCalls::Bar(call); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); assert_eq!(contract_call, decoded_enum); - assert_eq!(encoded_call, contract_call.encode().unwrap()); + assert_eq!(encoded_call, contract_call.encode().into()); let call = YeetCall(1u64.into(), 0u64.into(), Address::zero()); let encoded_call = contract.encode("yeet", (call.0, call.1, call.2)).unwrap(); - assert_eq!(encoded_call, call.clone().encode().unwrap()); + assert_eq!(encoded_call, call.clone().encode().into()); let decoded_call = YeetCall::decode(encoded_call.as_ref()).unwrap(); assert_eq!(call, decoded_call); @@ -202,7 +202,7 @@ fn can_gen_human_readable_with_structs() { let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); assert_eq!(contract_call, decoded_enum); assert_eq!(contract_call, call.into()); - assert_eq!(encoded_call, contract_call.encode().unwrap()); + assert_eq!(encoded_call, contract_call.encode().into()); } #[test] @@ -227,14 +227,14 @@ fn can_handle_overloaded_functions() { let call = GetValueCall; let encoded_call = contract.encode("getValue", ()).unwrap(); - assert_eq!(encoded_call, call.clone().encode().unwrap()); + assert_eq!(encoded_call, call.clone().encode().into()); let decoded_call = GetValueCall::decode(encoded_call.as_ref()).unwrap(); assert_eq!(call, decoded_call); let contract_call = SimpleContractCalls::GetValue(call); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); assert_eq!(contract_call, decoded_enum); - assert_eq!(encoded_call, contract_call.encode().unwrap()); + assert_eq!(encoded_call, contract_call.encode().into()); let call = GetValueWithOtherValueCall { other_value: 420u64.into(), @@ -243,14 +243,14 @@ fn can_handle_overloaded_functions() { let encoded_call = contract .encode_with_selector([15, 244, 201, 22], call.other_value) .unwrap(); - assert_eq!(encoded_call, call.clone().encode().unwrap()); + assert_eq!(encoded_call, call.clone().encode().into()); let decoded_call = GetValueWithOtherValueCall::decode(encoded_call.as_ref()).unwrap(); assert_eq!(call, decoded_call); let contract_call = SimpleContractCalls::GetValueWithOtherValue(call); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); assert_eq!(contract_call, decoded_enum); - assert_eq!(encoded_call, contract_call.encode().unwrap()); + assert_eq!(encoded_call, contract_call.encode().into()); let call = GetValueWithOtherValueAndAddrCall { other_value: 420u64.into(), @@ -266,7 +266,7 @@ fn can_handle_overloaded_functions() { let contract_call = SimpleContractCalls::GetValueWithOtherValueAndAddr(call); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); assert_eq!(contract_call, decoded_enum); - assert_eq!(encoded_call, contract_call.encode().unwrap()); + assert_eq!(encoded_call, contract_call.encode().into()); } #[tokio::test] @@ -283,7 +283,7 @@ async fn can_handle_underscore_functions() { // launcht the network & connect to it let ganache = ethers_core::utils::Ganache::new().spawn(); - let from = ganache.addresses()[0].clone(); + let from = ganache.addresses()[0]; let provider = Provider::try_from(ganache.endpoint()) .unwrap() .with_sender(from) @@ -330,7 +330,7 @@ async fn can_handle_underscore_functions() { // Manual call construction use ethers_providers::Middleware; // TODO: How do we handle underscores for calls here? - let data = simplestorage_mod::HashPuzzleCall.encode().unwrap(); + let data = simplestorage_mod::HashPuzzleCall.encode(); let tx = Eip1559TransactionRequest::new().data(data).to(addr); let tx = TypedTransaction::Eip1559(tx); let res5 = client.call(&tx, None).await.unwrap(); diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs index bcb529ec..4b438f41 100644 --- a/ethers-contract/tests/common/derive.rs +++ b/ethers-contract/tests/common/derive.rs @@ -1,5 +1,5 @@ use ethers_contract::EthLogDecode; -use ethers_contract::{abigen, AbiDecode, EthAbiType, EthCall, EthDisplay, EthEvent}; +use ethers_contract::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent}; use ethers_core::abi::{RawLog, Tokenizable}; use ethers_core::types::Address; use ethers_core::types::{H160, H256, I256, U128, U256}; diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml index c686801a..8c763ec7 100644 --- a/ethers-core/Cargo.toml +++ b/ethers-core/Cargo.toml @@ -50,6 +50,7 @@ bincode = { version = "1.3.3", default-features = false } once_cell = { version = "1.8.0" } hex-literal = "0.3.3" futures-util = { version = "0.3.17" } +rand = "0.8.4" [features] celo = ["legacy"] # celo support extends the transaction format with extra fields diff --git a/ethers-core/src/abi/codec.rs b/ethers-core/src/abi/codec.rs new file mode 100644 index 00000000..1479e263 --- /dev/null +++ b/ethers-core/src/abi/codec.rs @@ -0,0 +1,228 @@ +use crate::abi::{AbiArrayType, AbiError, AbiType, Detokenize, Tokenizable, TokenizableItem}; +use crate::types::{Address, H256, U128, U256}; + +/// Trait for ABI encoding +pub trait AbiEncode { + /// ABI encode the type + fn encode(self) -> Vec; +} + +/// Trait for ABI decoding +pub trait AbiDecode: Sized { + /// Decodes the ABI encoded data + fn decode(bytes: impl AsRef<[u8]>) -> Result; +} + +macro_rules! impl_abi_codec { + ($($name:ty),*) => { + $( + impl AbiEncode for $name { + fn encode(self) -> Vec { + let token = self.into_token(); + crate::abi::encode(&[token]).into() + } + } + impl AbiDecode for $name { + fn decode(bytes: impl AsRef<[u8]>) -> Result { + let tokens = crate::abi::decode( + &[Self::param_type()], bytes.as_ref() + )?; + Ok(::from_tokens(tokens)?) + } + } + )* + }; +} + +impl_abi_codec!( + Vec, + Address, + bool, + String, + H256, + U128, + U256, + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128 +); + +impl AbiEncode for [T; N] { + fn encode(self) -> Vec { + let token = self.into_token(); + crate::abi::encode(&[token]) + } +} + +impl AbiEncode for [u8; N] { + fn encode(self) -> Vec { + let token = self.into_token(); + crate::abi::encode(&[token]) + } +} + +impl AbiDecode for [u8; N] { + fn decode(bytes: impl AsRef<[u8]>) -> Result { + let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?; + Ok(::from_tokens(tokens)?) + } +} + +impl AbiDecode for [T; N] +where + T: TokenizableItem + AbiArrayType + Clone, +{ + fn decode(bytes: impl AsRef<[u8]>) -> Result { + let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?; + Ok(::from_tokens(tokens)?) + } +} + +impl AbiEncode for Vec { + fn encode(self) -> Vec { + let token = self.into_token(); + crate::abi::encode(&[token]) + } +} + +impl AbiDecode for Vec { + fn decode(bytes: impl AsRef<[u8]>) -> Result { + let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?; + Ok(::from_tokens(tokens)?) + } +} + +macro_rules! impl_abi_codec_tuple { + ($num: expr, $( $ty: ident),+) => { + impl<$($ty, )+> AbiEncode for ($($ty,)+) where + $( + $ty: Tokenizable, + )+ + { + fn encode(self) -> Vec { + let token = self.into_token(); + crate::abi::encode(&[token]).into() + } + } + + impl<$($ty, )+> AbiDecode for ($($ty,)+) where + $( + $ty: AbiType + Tokenizable, + )+ { + fn decode(bytes: impl AsRef<[u8]>) -> Result { + let tokens = crate::abi::decode( + &[Self::param_type()], bytes.as_ref() + )?; + Ok(::from_tokens(tokens)?) + } + } + } +} + +impl_abi_codec_tuple!(1, A); +impl_abi_codec_tuple!(2, A, B); +impl_abi_codec_tuple!(3, A, B, C); +impl_abi_codec_tuple!(4, A, B, C, D); +impl_abi_codec_tuple!(5, A, B, C, D, E); +impl_abi_codec_tuple!(6, A, B, C, D, E, F); +impl_abi_codec_tuple!(7, A, B, C, D, E, F, G); +impl_abi_codec_tuple!(8, A, B, C, D, E, F, G, H); +impl_abi_codec_tuple!(9, A, B, C, D, E, F, G, H, I); +impl_abi_codec_tuple!(10, A, B, C, D, E, F, G, H, I, J); +impl_abi_codec_tuple!(11, A, B, C, D, E, F, G, H, I, J, K); +impl_abi_codec_tuple!(12, A, B, C, D, E, F, G, H, I, J, K, L); +impl_abi_codec_tuple!(13, A, B, C, D, E, F, G, H, I, J, K, L, M); +impl_abi_codec_tuple!(14, A, B, C, D, E, F, G, H, I, J, K, L, M, N); +impl_abi_codec_tuple!(15, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O); +impl_abi_codec_tuple!(16, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P); + +#[cfg(test)] +mod tests { + use super::*; + use std::fmt::Debug; + + use crate::abi::{AbiArrayType, TokenizableItem}; + use rand::{ + distributions::{Alphanumeric, Distribution, Standard}, + random, thread_rng, Rng, + }; + + fn assert_codec(val: T) + where + T: AbiDecode + AbiEncode + Clone + PartialEq + Debug, + { + let encoded = val.clone().encode(); + assert_eq!(val, T::decode(encoded).unwrap()); + } + + macro_rules! roundtrip_alloc { + ($val:expr) => { + assert_codec($val); + assert_codec(($val, $val)); + assert_codec(std::iter::repeat_with(|| $val).take(10).collect::>()); + }; + } + + macro_rules! roundtrip_all { + ($val:expr) => { + roundtrip_alloc!($val); + assert_codec([$val; 10]); + }; + } + + fn test_codec_rng() + where + Standard: Distribution, + T: AbiDecode + AbiEncode + Copy + PartialEq + Debug + AbiArrayType + TokenizableItem, + { + roundtrip_all!(random::()); + } + + #[test] + fn address_codec() { + test_codec_rng::
(); + } + + #[test] + fn uint_codec() { + test_codec_rng::(); + test_codec_rng::(); + test_codec_rng::(); + test_codec_rng::(); + + test_codec_rng::(); + test_codec_rng::(); + test_codec_rng::(); + test_codec_rng::(); + test_codec_rng::(); + } + + #[test] + fn u8_codec() { + assert_codec(random::()); + assert_codec((random::(), random::())); + assert_codec( + std::iter::repeat_with(|| random::()) + .take(10) + .collect::>(), + ); + assert_codec([random::(); 10]); + } + + #[test] + fn string_codec() { + roundtrip_alloc! { thread_rng() + .sample_iter(&Alphanumeric) + .take(30) + .map(char::from) + .collect::() + }; + } +} diff --git a/ethers-core/src/abi/error.rs b/ethers-core/src/abi/error.rs index 2cb4a668..d9a1ae98 100644 --- a/ethers-core/src/abi/error.rs +++ b/ethers-core/src/abi/error.rs @@ -1,9 +1,11 @@ //! Boilerplate error definitions. +use crate::abi::InvalidOutputType; use thiserror::Error; /// A type alias for std's Result with the Error as our error type. pub type Result = std::result::Result; +/// Error that can occur during human readable parsing #[derive(Error, Debug)] pub enum ParseError { #[error("{0}")] @@ -23,3 +25,18 @@ macro_rules! _bail { ($($tt:tt)*) => { return Err($crate::abi::error::format_err!($($tt)*)) }; } pub(crate) use _bail as bail; + +/// ABI codec related errors +#[derive(Error, Debug)] +pub enum AbiError { + /// Thrown when the ABI decoding fails + #[error(transparent)] + DecodingError(#[from] crate::abi::Error), + + /// Thrown when detokenizing an argument + #[error(transparent)] + DetokenizationError(#[from] InvalidOutputType), + + #[error("missing or wrong function selector")] + WrongSelector, +} diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs index f898f823..e2f43b67 100644 --- a/ethers-core/src/abi/mod.rs +++ b/ethers-core/src/abi/mod.rs @@ -11,13 +11,16 @@ pub use tokens::{Detokenize, InvalidOutputType, Tokenizable, TokenizableItem, To pub mod struct_def; pub use struct_def::SolStruct; +mod codec; +pub use codec::{AbiDecode, AbiEncode}; + mod error; -pub use error::ParseError; +pub use error::{AbiError, ParseError}; mod human_readable; pub use human_readable::{parse as parse_abi, parse_str as parse_abi_str, AbiParser}; -use crate::types::{H256, H512, U128, U64}; +use crate::types::{H256, H512, U128, U256, U64}; /// Extension trait for `ethabi::Function`. pub trait FunctionExt { @@ -127,6 +130,7 @@ impl_abi_type!( H512 => FixedBytes(64), U64 => Uint(64), U128 => Uint(128), + U256 => Uint(256), u16 => Uint(16), u32 => Uint(32), u64 => Uint(64), diff --git a/ethers-core/src/abi/tokens.rs b/ethers-core/src/abi/tokens.rs index 3257f3dd..029cf655 100644 --- a/ethers-core/src/abi/tokens.rs +++ b/ethers-core/src/abi/tokens.rs @@ -369,107 +369,79 @@ impl Tokenizable for Vec { impl TokenizableItem for Vec {} -macro_rules! impl_fixed_types { - ($num: expr) => { - impl Tokenizable for [u8; $num] { - fn from_token(token: Token) -> Result { - match token { - Token::FixedBytes(bytes) => { - if bytes.len() != $num { - return Err(InvalidOutputType(format!( - "Expected `FixedBytes({})`, got FixedBytes({})", - $num, - bytes.len() - ))); - } - - let mut arr = [0; $num]; - arr.copy_from_slice(&bytes); - Ok(arr) - } - other => Err(InvalidOutputType(format!( - "Expected `FixedBytes({})`, got {:?}", - $num, other - )) - .into()), +impl Tokenizable for [u8; N] { + fn from_token(token: Token) -> Result { + match token { + Token::FixedBytes(bytes) => { + if bytes.len() != N { + return Err(InvalidOutputType(format!( + "Expected `FixedBytes({})`, got FixedBytes({})", + N, + bytes.len() + ))); } - } - fn into_token(self) -> Token { - Token::FixedBytes(self.to_vec()) + let mut arr = [0; N]; + arr.copy_from_slice(&bytes); + Ok(arr) } + other => Err(InvalidOutputType(format!( + "Expected `FixedBytes({})`, got {:?}", + N, other + )) + .into()), } + } - impl TokenizableItem for [u8; $num] {} - - impl Tokenizable for [T; $num] { - fn from_token(token: Token) -> Result { - match token { - Token::FixedArray(tokens) => { - if tokens.len() != $num { - return Err(InvalidOutputType(format!( - "Expected `FixedArray({})`, got FixedArray({})", - $num, - tokens.len() - ))); - } - - let mut arr = ArrayVec::::new(); - let mut it = tokens.into_iter().map(T::from_token); - for _ in 0..$num { - arr.push(it.next().expect("Length validated in guard; qed")?); - } - // Can't use expect here because [T; $num]: Debug is not satisfied. - match arr.into_inner() { - Ok(arr) => Ok(arr), - Err(_) => panic!("All elements inserted so the array is full; qed"), - } - } - other => Err(InvalidOutputType(format!( - "Expected `FixedArray({})`, got {:?}", - $num, other - )) - .into()), - } - } - - fn into_token(self) -> Token { - Token::FixedArray( - ArrayVec::from(self) - .into_iter() - .map(T::into_token) - .collect(), - ) - } - } - - impl TokenizableItem for [T; $num] {} - }; + fn into_token(self) -> Token { + Token::FixedBytes(self.to_vec()) + } } -impl_fixed_types!(1); -impl_fixed_types!(2); -impl_fixed_types!(3); -impl_fixed_types!(4); -impl_fixed_types!(5); -impl_fixed_types!(6); -impl_fixed_types!(7); -impl_fixed_types!(8); -impl_fixed_types!(9); -impl_fixed_types!(10); -impl_fixed_types!(11); -impl_fixed_types!(12); -impl_fixed_types!(13); -impl_fixed_types!(14); -impl_fixed_types!(15); -impl_fixed_types!(16); -impl_fixed_types!(18); -impl_fixed_types!(32); -impl_fixed_types!(64); -impl_fixed_types!(128); -impl_fixed_types!(256); -impl_fixed_types!(512); -impl_fixed_types!(1024); +impl TokenizableItem for [u8; N] {} + +impl Tokenizable for [T; N] { + fn from_token(token: Token) -> Result { + match token { + Token::FixedArray(tokens) => { + if tokens.len() != N { + return Err(InvalidOutputType(format!( + "Expected `FixedArray({})`, got FixedArray({})", + N, + tokens.len() + ))); + } + + let mut arr = ArrayVec::::new(); + let mut it = tokens.into_iter().map(T::from_token); + for _ in 0..N { + arr.push(it.next().expect("Length validated in guard; qed")?); + } + // Can't use expect here because [T; N]: Debug is not satisfied. + match arr.into_inner() { + Ok(arr) => Ok(arr), + Err(_) => panic!("All elements inserted so the array is full; qed"), + } + } + other => Err(InvalidOutputType(format!( + "Expected `FixedArray({})`, got {:?}", + N, other + )) + .into()), + } + } + + fn into_token(self) -> Token { + Token::FixedArray( + ArrayVec::from(self) + .into_iter() + .map(T::into_token) + .collect(), + ) + } +} + +impl TokenizableItem for [T; N] {} /// Helper for flattening non-nested tokens into their inner /// types, e.g. (A, B, C ) would get tokenized to Tuple([A, B, C])