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
This commit is contained in:
Matthias Seitz 2021-10-28 00:07:24 +02:00 committed by GitHub
parent e089ad7755
commit eede86df41
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 362 additions and 176 deletions

View File

@ -4,6 +4,7 @@
### Unreleased ### 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) - 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 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) - `abigen!` now supports overloaded functions natively [#501](https://github.com/gakonst/ethers-rs/pull/501)

View File

@ -111,10 +111,10 @@ impl Context {
#(#variant_names(#struct_names)),* #(#variant_names(#struct_names)),*
} }
impl #ethers_contract::AbiDecode for #enum_name { impl #ethers_core::abi::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_contract::AbiError> { fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_core::abi::AbiError> {
#( #(
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)) return Ok(#enum_name::#variant_names(decoded))
} }
)* )*
@ -122,8 +122,8 @@ impl Context {
} }
} }
impl #ethers_contract::AbiEncode for #enum_name { impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Result<#ethers_core::types::Bytes, #ethers_contract::AbiError> { fn encode(self) -> Vec<u8> {
match self { match self {
#( #(
#enum_name::#variant_names(element) => element.encode() #enum_name::#variant_names(element) => element.encode()

View File

@ -95,14 +95,27 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
fn abi_signature() -> ::std::borrow::Cow<'static, str> { fn abi_signature() -> ::std::borrow::Cow<'static, str> {
#abi.into() #abi.into()
} }
} }
impl #contract_crate::AbiDecode for #name { impl #core_crate::abi::AbiDecode for #name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #contract_crate::AbiError> { fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> {
#decode_impl #decode_impl
} }
} }
impl #core_crate::abi::AbiEncode for #name {
fn encode(self) -> ::std::vec::Vec<u8> {
let tokens = #core_crate::abi::Tokenize::into_tokens(self);
let selector = <Self as #contract_crate::EthCall>::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); let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);

View File

@ -1,9 +1,8 @@
use crate::Contract; use crate::Contract;
pub use ethers_core::abi::AbiError;
use ethers_core::{ use ethers_core::{
abi::{ abi::{Abi, Detokenize, Error, Event, Function, FunctionExt, RawLog, Tokenize},
Abi, Detokenize, Error, Event, Function, FunctionExt, InvalidOutputType, RawLog, Tokenize,
},
types::{Address, Bytes, Selector, H256}, types::{Address, Bytes, Selector, H256},
}; };
use ethers_providers::Middleware; use ethers_providers::Middleware;
@ -14,21 +13,6 @@ use std::{
hash::Hash, hash::Hash,
sync::Arc, 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 /// A reduced form of `Contract` which just takes the `abi` and produces
/// ABI encoded data for its functions. /// ABI encoded data for its functions.

View File

@ -1,23 +1,20 @@
use super::base::{decode_function_data, AbiError}; use super::base::{decode_function_data, AbiError};
use ethers_core::{ use ethers_core::{
abi::{Detokenize, Function, InvalidOutputType}, abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
types::{ 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 ethers_providers::{Middleware, PendingTransaction, ProviderError};
use std::borrow::Cow; use std::{borrow::Cow, fmt::Debug, marker::PhantomData, sync::Arc};
use std::{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; use thiserror::Error as ThisError;
/// A helper trait for types that represent all call input parameters of a specific function /// 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 /// The name of the function
fn function_name() -> Cow<'static, str>; fn function_name() -> Cow<'static, str>;
@ -30,20 +27,6 @@ pub trait EthCall: Tokenizable + AbiDecode + Send + Sync {
} }
} }
impl<T: EthCall> AbiEncode for T {
fn encode(self) -> Result<Bytes, AbiError> {
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)] #[derive(ThisError, Debug)]
/// An Error which is thrown when interacting with a smart contract /// An Error which is thrown when interacting with a smart contract
pub enum ContractError<M: Middleware> { pub enum ContractError<M: Middleware> {

View File

@ -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<Bytes, AbiError>;
}
/// Trait for ABI decoding
pub trait AbiDecode: Sized {
/// Decodes the ABI encoded data
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError>;
}

View File

@ -31,9 +31,6 @@ pub use event::EthEvent;
mod log; mod log;
pub use log::{decode_logs, EthLogDecode, LogMeta}; pub use log::{decode_logs, EthLogDecode, LogMeta};
mod codec;
pub use codec::{AbiDecode, AbiEncode};
mod stream; mod stream;
mod multicall; mod multicall;

View File

@ -1,7 +1,7 @@
#![cfg(feature = "abigen")] #![cfg(feature = "abigen")]
//! Test cases to validate the `abigen!` macro //! Test cases to validate the `abigen!` macro
use ethers_contract::{abigen, AbiDecode, AbiEncode, EthEvent}; use ethers_contract::{abigen, EthEvent};
use ethers_core::abi::{Address, Tokenizable}; use ethers_core::abi::{AbiDecode, AbiEncode, Address, Tokenizable};
use ethers_core::types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256}; use ethers_core::types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256};
use ethers_core::utils::Solc; use ethers_core::utils::Solc;
use ethers_providers::Provider; use ethers_providers::Provider;
@ -183,18 +183,18 @@ fn can_gen_human_readable_with_structs() {
addr: Address::random(), addr: Address::random(),
}; };
let encoded_call = contract.encode("bar", (call.x, call.y, call.addr)).unwrap(); 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(); let decoded_call = BarCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call); assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::Bar(call); let contract_call = SimpleContractCalls::Bar(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); 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 call = YeetCall(1u64.into(), 0u64.into(), Address::zero());
let encoded_call = contract.encode("yeet", (call.0, call.1, call.2)).unwrap(); 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(); let decoded_call = YeetCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call); 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(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); assert_eq!(contract_call, decoded_enum);
assert_eq!(contract_call, call.into()); assert_eq!(contract_call, call.into());
assert_eq!(encoded_call, contract_call.encode().unwrap()); assert_eq!(encoded_call, contract_call.encode().into());
} }
#[test] #[test]
@ -227,14 +227,14 @@ fn can_handle_overloaded_functions() {
let call = GetValueCall; let call = GetValueCall;
let encoded_call = contract.encode("getValue", ()).unwrap(); 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(); let decoded_call = GetValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call); assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::GetValue(call); let contract_call = SimpleContractCalls::GetValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); 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 { let call = GetValueWithOtherValueCall {
other_value: 420u64.into(), other_value: 420u64.into(),
@ -243,14 +243,14 @@ fn can_handle_overloaded_functions() {
let encoded_call = contract let encoded_call = contract
.encode_with_selector([15, 244, 201, 22], call.other_value) .encode_with_selector([15, 244, 201, 22], call.other_value)
.unwrap(); .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(); let decoded_call = GetValueWithOtherValueCall::decode(encoded_call.as_ref()).unwrap();
assert_eq!(call, decoded_call); assert_eq!(call, decoded_call);
let contract_call = SimpleContractCalls::GetValueWithOtherValue(call); let contract_call = SimpleContractCalls::GetValueWithOtherValue(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); 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 { let call = GetValueWithOtherValueAndAddrCall {
other_value: 420u64.into(), other_value: 420u64.into(),
@ -266,7 +266,7 @@ fn can_handle_overloaded_functions() {
let contract_call = SimpleContractCalls::GetValueWithOtherValueAndAddr(call); let contract_call = SimpleContractCalls::GetValueWithOtherValueAndAddr(call);
let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap(); let decoded_enum = SimpleContractCalls::decode(encoded_call.as_ref()).unwrap();
assert_eq!(contract_call, decoded_enum); assert_eq!(contract_call, decoded_enum);
assert_eq!(encoded_call, contract_call.encode().unwrap()); assert_eq!(encoded_call, contract_call.encode().into());
} }
#[tokio::test] #[tokio::test]
@ -283,7 +283,7 @@ async fn can_handle_underscore_functions() {
// launcht the network & connect to it // launcht the network & connect to it
let ganache = ethers_core::utils::Ganache::new().spawn(); 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()) let provider = Provider::try_from(ganache.endpoint())
.unwrap() .unwrap()
.with_sender(from) .with_sender(from)
@ -330,7 +330,7 @@ async fn can_handle_underscore_functions() {
// Manual call construction // Manual call construction
use ethers_providers::Middleware; use ethers_providers::Middleware;
// TODO: How do we handle underscores for calls here? // 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 = Eip1559TransactionRequest::new().data(data).to(addr);
let tx = TypedTransaction::Eip1559(tx); let tx = TypedTransaction::Eip1559(tx);
let res5 = client.call(&tx, None).await.unwrap(); let res5 = client.call(&tx, None).await.unwrap();

View File

@ -1,5 +1,5 @@
use ethers_contract::EthLogDecode; 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::abi::{RawLog, Tokenizable};
use ethers_core::types::Address; use ethers_core::types::Address;
use ethers_core::types::{H160, H256, I256, U128, U256}; use ethers_core::types::{H160, H256, I256, U128, U256};

View File

@ -50,6 +50,7 @@ bincode = { version = "1.3.3", default-features = false }
once_cell = { version = "1.8.0" } once_cell = { version = "1.8.0" }
hex-literal = "0.3.3" hex-literal = "0.3.3"
futures-util = { version = "0.3.17" } futures-util = { version = "0.3.17" }
rand = "0.8.4"
[features] [features]
celo = ["legacy"] # celo support extends the transaction format with extra fields celo = ["legacy"] # celo support extends the transaction format with extra fields

View File

@ -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<u8>;
}
/// Trait for ABI decoding
pub trait AbiDecode: Sized {
/// Decodes the ABI encoded data
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError>;
}
macro_rules! impl_abi_codec {
($($name:ty),*) => {
$(
impl AbiEncode for $name {
fn encode(self) -> Vec<u8> {
let token = self.into_token();
crate::abi::encode(&[token]).into()
}
}
impl AbiDecode for $name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = crate::abi::decode(
&[Self::param_type()], bytes.as_ref()
)?;
Ok(<Self as Detokenize>::from_tokens(tokens)?)
}
}
)*
};
}
impl_abi_codec!(
Vec<u8>,
Address,
bool,
String,
H256,
U128,
U256,
u8,
u16,
u32,
u64,
u128,
i8,
i16,
i32,
i64,
i128
);
impl<T: TokenizableItem + Clone, const N: usize> AbiEncode for [T; N] {
fn encode(self) -> Vec<u8> {
let token = self.into_token();
crate::abi::encode(&[token])
}
}
impl<const N: usize> AbiEncode for [u8; N] {
fn encode(self) -> Vec<u8> {
let token = self.into_token();
crate::abi::encode(&[token])
}
}
impl<const N: usize> AbiDecode for [u8; N] {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?;
Ok(<Self as Detokenize>::from_tokens(tokens)?)
}
}
impl<T, const N: usize> AbiDecode for [T; N]
where
T: TokenizableItem + AbiArrayType + Clone,
{
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?;
Ok(<Self as Detokenize>::from_tokens(tokens)?)
}
}
impl<T: TokenizableItem + AbiArrayType> AbiEncode for Vec<T> {
fn encode(self) -> Vec<u8> {
let token = self.into_token();
crate::abi::encode(&[token])
}
}
impl<T: TokenizableItem + AbiArrayType> AbiDecode for Vec<T> {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = crate::abi::decode(&[Self::param_type()], bytes.as_ref())?;
Ok(<Self as Detokenize>::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<u8> {
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<Self, AbiError> {
let tokens = crate::abi::decode(
&[Self::param_type()], bytes.as_ref()
)?;
Ok(<Self as Detokenize>::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<T>(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::<Vec<_>>());
};
}
macro_rules! roundtrip_all {
($val:expr) => {
roundtrip_alloc!($val);
assert_codec([$val; 10]);
};
}
fn test_codec_rng<T>()
where
Standard: Distribution<T>,
T: AbiDecode + AbiEncode + Copy + PartialEq + Debug + AbiArrayType + TokenizableItem,
{
roundtrip_all!(random::<T>());
}
#[test]
fn address_codec() {
test_codec_rng::<Address>();
}
#[test]
fn uint_codec() {
test_codec_rng::<u16>();
test_codec_rng::<u32>();
test_codec_rng::<u64>();
test_codec_rng::<u128>();
test_codec_rng::<i8>();
test_codec_rng::<i16>();
test_codec_rng::<i32>();
test_codec_rng::<i64>();
test_codec_rng::<i128>();
}
#[test]
fn u8_codec() {
assert_codec(random::<u8>());
assert_codec((random::<u8>(), random::<u8>()));
assert_codec(
std::iter::repeat_with(|| random::<u8>())
.take(10)
.collect::<Vec<_>>(),
);
assert_codec([random::<u8>(); 10]);
}
#[test]
fn string_codec() {
roundtrip_alloc! { thread_rng()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect::<String>()
};
}
}

View File

@ -1,9 +1,11 @@
//! Boilerplate error definitions. //! Boilerplate error definitions.
use crate::abi::InvalidOutputType;
use thiserror::Error; use thiserror::Error;
/// A type alias for std's Result with the Error as our error type. /// A type alias for std's Result with the Error as our error type.
pub type Result<T, E = ParseError> = std::result::Result<T, E>; pub type Result<T, E = ParseError> = std::result::Result<T, E>;
/// Error that can occur during human readable parsing
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum ParseError { pub enum ParseError {
#[error("{0}")] #[error("{0}")]
@ -23,3 +25,18 @@ macro_rules! _bail {
($($tt:tt)*) => { return Err($crate::abi::error::format_err!($($tt)*)) }; ($($tt:tt)*) => { return Err($crate::abi::error::format_err!($($tt)*)) };
} }
pub(crate) use _bail as bail; 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,
}

View File

@ -11,13 +11,16 @@ pub use tokens::{Detokenize, InvalidOutputType, Tokenizable, TokenizableItem, To
pub mod struct_def; pub mod struct_def;
pub use struct_def::SolStruct; pub use struct_def::SolStruct;
mod codec;
pub use codec::{AbiDecode, AbiEncode};
mod error; mod error;
pub use error::ParseError; pub use error::{AbiError, ParseError};
mod human_readable; mod human_readable;
pub use human_readable::{parse as parse_abi, parse_str as parse_abi_str, AbiParser}; 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`. /// Extension trait for `ethabi::Function`.
pub trait FunctionExt { pub trait FunctionExt {
@ -127,6 +130,7 @@ impl_abi_type!(
H512 => FixedBytes(64), H512 => FixedBytes(64),
U64 => Uint(64), U64 => Uint(64),
U128 => Uint(128), U128 => Uint(128),
U256 => Uint(256),
u16 => Uint(16), u16 => Uint(16),
u32 => Uint(32), u32 => Uint(32),
u64 => Uint(64), u64 => Uint(64),

View File

@ -369,107 +369,79 @@ impl<T: TokenizableItem> Tokenizable for Vec<T> {
impl<T: TokenizableItem> TokenizableItem for Vec<T> {} impl<T: TokenizableItem> TokenizableItem for Vec<T> {}
macro_rules! impl_fixed_types { impl<const N: usize> Tokenizable for [u8; N] {
($num: expr) => { fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
impl Tokenizable for [u8; $num] { match token {
fn from_token(token: Token) -> Result<Self, InvalidOutputType> { Token::FixedBytes(bytes) => {
match token { if bytes.len() != N {
Token::FixedBytes(bytes) => { return Err(InvalidOutputType(format!(
if bytes.len() != $num { "Expected `FixedBytes({})`, got FixedBytes({})",
return Err(InvalidOutputType(format!( N,
"Expected `FixedBytes({})`, got FixedBytes({})", bytes.len()
$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()),
} }
}
fn into_token(self) -> Token { let mut arr = [0; N];
Token::FixedBytes(self.to_vec()) arr.copy_from_slice(&bytes);
Ok(arr)
} }
other => Err(InvalidOutputType(format!(
"Expected `FixedBytes({})`, got {:?}",
N, other
))
.into()),
} }
}
impl TokenizableItem for [u8; $num] {} fn into_token(self) -> Token {
Token::FixedBytes(self.to_vec())
impl<T: TokenizableItem + Clone> Tokenizable for [T; $num] { }
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
match token {
Token::FixedArray(tokens) => {
if tokens.len() != $num {
return Err(InvalidOutputType(format!(
"Expected `FixedArray({})`, got FixedArray({})",
$num,
tokens.len()
)));
}
let mut arr = ArrayVec::<T, $num>::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<T: TokenizableItem + Clone> TokenizableItem for [T; $num] {}
};
} }
impl_fixed_types!(1); impl<const N: usize> TokenizableItem for [u8; N] {}
impl_fixed_types!(2);
impl_fixed_types!(3); impl<T: TokenizableItem + Clone, const N: usize> Tokenizable for [T; N] {
impl_fixed_types!(4); fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
impl_fixed_types!(5); match token {
impl_fixed_types!(6); Token::FixedArray(tokens) => {
impl_fixed_types!(7); if tokens.len() != N {
impl_fixed_types!(8); return Err(InvalidOutputType(format!(
impl_fixed_types!(9); "Expected `FixedArray({})`, got FixedArray({})",
impl_fixed_types!(10); N,
impl_fixed_types!(11); tokens.len()
impl_fixed_types!(12); )));
impl_fixed_types!(13); }
impl_fixed_types!(14);
impl_fixed_types!(15); let mut arr = ArrayVec::<T, N>::new();
impl_fixed_types!(16); let mut it = tokens.into_iter().map(T::from_token);
impl_fixed_types!(18); for _ in 0..N {
impl_fixed_types!(32); arr.push(it.next().expect("Length validated in guard; qed")?);
impl_fixed_types!(64); }
impl_fixed_types!(128); // Can't use expect here because [T; N]: Debug is not satisfied.
impl_fixed_types!(256); match arr.into_inner() {
impl_fixed_types!(512); Ok(arr) => Ok(arr),
impl_fixed_types!(1024); 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<T: TokenizableItem + Clone, const N: usize> TokenizableItem for [T; N] {}
/// Helper for flattening non-nested tokens into their inner /// Helper for flattening non-nested tokens into their inner
/// types, e.g. (A, B, C ) would get tokenized to Tuple([A, B, C]) /// types, e.g. (A, B, C ) would get tokenized to Tuple([A, B, C])