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:
parent
e089ad7755
commit
eede86df41
|
@ -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)
|
||||
|
|
|
@ -111,10 +111,10 @@ impl Context {
|
|||
#(#variant_names(#struct_names)),*
|
||||
}
|
||||
|
||||
impl #ethers_contract::AbiDecode for #enum_name {
|
||||
fn decode(data: impl AsRef<[u8]>) -> Result<Self, #ethers_contract::AbiError> {
|
||||
impl #ethers_core::abi::AbiDecode for #enum_name {
|
||||
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))
|
||||
}
|
||||
)*
|
||||
|
@ -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<u8> {
|
||||
match self {
|
||||
#(
|
||||
#enum_name::#variant_names(element) => element.encode()
|
||||
|
|
|
@ -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<Self, #contract_crate::AbiError> {
|
||||
impl #core_crate::abi::AbiDecode for #name {
|
||||
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> {
|
||||
#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);
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<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)]
|
||||
/// An Error which is thrown when interacting with a smart contract
|
||||
pub enum ContractError<M: Middleware> {
|
||||
|
|
|
@ -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>;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>()
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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<T, E = ParseError> = std::result::Result<T, E>;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -369,107 +369,79 @@ impl<T: TokenizableItem> Tokenizable for Vec<T> {
|
|||
|
||||
impl<T: TokenizableItem> TokenizableItem for Vec<T> {}
|
||||
|
||||
macro_rules! impl_fixed_types {
|
||||
($num: expr) => {
|
||||
impl Tokenizable for [u8; $num] {
|
||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||
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<const N: usize> Tokenizable for [u8; N] {
|
||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||
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<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] {}
|
||||
};
|
||||
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<const N: usize> TokenizableItem for [u8; N] {}
|
||||
|
||||
impl<T: TokenizableItem + Clone, const N: usize> Tokenizable for [T; N] {
|
||||
fn from_token(token: Token) -> Result<Self, InvalidOutputType> {
|
||||
match token {
|
||||
Token::FixedArray(tokens) => {
|
||||
if tokens.len() != N {
|
||||
return Err(InvalidOutputType(format!(
|
||||
"Expected `FixedArray({})`, got FixedArray({})",
|
||||
N,
|
||||
tokens.len()
|
||||
)));
|
||||
}
|
||||
|
||||
let mut arr = ArrayVec::<T, N>::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<T: TokenizableItem + Clone, const N: usize> 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])
|
||||
|
|
Loading…
Reference in New Issue