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
|
### 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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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;
|
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;
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
//! 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,
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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])
|
||||||
|
|
Loading…
Reference in New Issue