Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: add abi code trait impls #531

Merged
merged 5 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ impl Context {
}

impl #ethers_contract::AbiEncode for #enum_name {
fn encode(self) -> Result<#ethers_core::types::Bytes, #ethers_contract::AbiError> {
fn encode(self) -> Vec<u8> {
match self {
#(
#enum_name::#variant_names(element) => element.encode()
Expand Down
7 changes: 3 additions & 4 deletions ethers-contract/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,15 @@ pub trait EthCall: Tokenizable + AbiDecode + Send + Sync {
}

impl<T: EthCall> AbiEncode for T {
fn encode(self) -> Result<Bytes, AbiError> {
fn encode(self) -> Vec<u8> {
let tokens = self.into_tokens();
let selector = Self::selector();
let encoded = ethers_core::abi::encode(&tokens);
let encoded: Vec<_> = selector
selector
.iter()
.copied()
.chain(encoded.into_iter())
.collect();
Ok(encoded.into())
.collect()
}
}

Expand Down
115 changes: 113 additions & 2 deletions ethers-contract/src/codec.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,125 @@
use crate::AbiError;
use ethers_core::types::Bytes;
use ethers_core::abi::{AbiArrayType, AbiType, Detokenize, Tokenizable, TokenizableItem};
use ethers_core::types::{Address, H256, U128, U256};

/// Trait for ABI encoding
pub trait AbiEncode {
/// ABI encode the type
fn encode(self) -> Result<Bytes, AbiError>;
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();
ethers_core::abi::encode(&[token]).into()
}
}
impl AbiDecode for $name {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = ethers_core::abi::decode(
&[Self::param_type()], bytes.as_ref()
)?;
Ok(<Self as Detokenize>::from_tokens(tokens)?)
}
}
)*
};
}

// TODO add Vec<u8>
impl_abi_codec!(
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();
ethers_core::abi::encode(&[token])
}
}

// TODO this must be moved to the same crate as AbiType
mattsse marked this conversation as resolved.
Show resolved Hide resolved
// impl<const N: usize> AbiDecode for [u8; N]
// {
// fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
// let tokens = ethers_core::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 = ethers_core::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();
ethers_core::abi::encode(&[token])
}
}

impl<T: TokenizableItem + AbiArrayType> AbiDecode for Vec<T> {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = ethers_core::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();
ethers_core::abi::encode(&[token]).into()
}
}

impl<$($ty, )+> AbiDecode for ($($ty,)+) where
$(
$ty: AbiType + Tokenizable,
)+ {
fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
let tokens = ethers_core::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);
22 changes: 11 additions & 11 deletions ethers-contract/tests/abigen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,26 +183,26 @@ 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);

let contract_call = SimpleContractCalls::Yeet(call.clone());
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]
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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]
Expand All @@ -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)
Expand Down Expand Up @@ -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();
Expand Down
85 changes: 85 additions & 0 deletions ethers-contract/tests/codec.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//! Test cases to validate the codec
use ethers_contract::{AbiDecode, AbiEncode};
use ethers_core::abi::{AbiArrayType, TokenizableItem};
use ethers_core::rand::distributions::Alphanumeric;
use ethers_core::rand::{thread_rng, Rng};
use ethers_core::{
rand::{
distributions::{Distribution, Standard},
random,
},
types::*,
};
use std::fmt::Debug;

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>()
};
}
3 changes: 2 additions & 1 deletion ethers-core/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ pub use error::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 {
Expand Down Expand Up @@ -127,6 +127,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),
Expand Down
Loading