Skip to content

Commit

Permalink
feat(dyn-abi): add arbitrary impls and proptests (#175)
Browse files Browse the repository at this point in the history
* feat(dyn-abi): add arbitrary impls and proptests

* feat: array strategies

* feat: arbitrary `CustomStruct`s

* docs

* add examples and optimize

* chore

* chore: clippy

* fix: doctest

* typos
  • Loading branch information
DaniPopes authored Jul 6, 2023
1 parent 629b73a commit 1c2373e
Show file tree
Hide file tree
Showing 14 changed files with 1,105 additions and 174 deletions.
18 changes: 18 additions & 0 deletions crates/dyn-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,35 @@ derive_more = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }

# arbitrary
arbitrary = { workspace = true, optional = true }
derive_arbitrary = { workspace = true, optional = true }
proptest = { workspace = true, optional = true }

[dev-dependencies]
hex-literal.workspace = true
criterion.workspace = true
ethabi = "18"
rand = "0.8"

[features]
default = ["std"]
std = ["alloy-sol-types/std", "alloy-primitives/std", "hex/std", "serde?/std", "serde_json?/std"]
eip712 = ["alloy-sol-types/eip712-serde", "dep:derive_more", "dep:serde", "dep:serde_json"]
arbitrary = [
"std",
"alloy-sol-types/arbitrary",
"dep:arbitrary",
"dep:derive_arbitrary",
"dep:proptest",
]

[[bench]]
name = "abi"
path = "benches/abi.rs"
harness = false

[[bench]]
name = "types"
path = "benches/types.rs"
harness = false
40 changes: 27 additions & 13 deletions crates/dyn-abi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,42 @@ The dynamic encoder/decoder is significantly more expensive, especially for
complex types. It is also significantly more error prone, as the mapping
between solidity types and rust types is not enforced by the compiler.

[abi]: https://docs.rs/alloy-sol-types/latest/alloy_sol_types/
[abi]: https://docs.rs/alloy-sol-types

## Usage
## Examples

Basic usage:

```rust
use alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::hex;

// parse a type from a string
// limitation: custom structs cannot currently be parsed this way.
let my_type: DynSolType = "uint8[2][]".parse().unwrap();

// set values
let uints = DynSolValue::FixedArray(vec![0u8.into(), 1u8.into()]);
let my_values = DynSolValue::Array(vec![uints]);

// encode
let encoded = my_values.clone().encode_single();
// note: eip712 `CustomStruct`s cannot be parsed this way.
let my_type: DynSolType = "uint16[2][]".parse().unwrap();

// decode
let decoded = my_type.decode_single(&encoded).unwrap();
let my_data = hex!(
"0000000000000000000000000000000000000000000000000000000000000020" // offset
"0000000000000000000000000000000000000000000000000000000000000001" // length
"0000000000000000000000000000000000000000000000000000000000000002" // .[0][0]
"0000000000000000000000000000000000000000000000000000000000000003" // .[0][1]
);
let decoded = my_type.decode_single(&my_data)?;

let expected = DynSolValue::Array(vec![DynSolValue::FixedArray(vec![2u16.into(), 3u16.into()])]);
assert_eq!(decoded, expected);

// roundtrip
let encoded = decoded.encode_single();
assert_eq!(encoded, my_data);
# Ok::<(), alloy_dyn_abi::Error>(())
```

EIP-712:

assert_eq!(decoded, my_values);
```rust,ignore
todo!()
```

## How it works
Expand Down
4 changes: 2 additions & 2 deletions crates/dyn-abi/benches/abi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ fn ethabi_encode(c: &mut Criterion) {
g.bench_function("single", |b| {
let input = encode_single_input();
b.iter(|| {
let token = ethabi::Token::String(black_box(&input).clone());
ethabi::encode(&[token])
let token = ethabi::Token::String(input.clone());
ethabi::encode(&[black_box(token)])
});
});

Expand Down
79 changes: 79 additions & 0 deletions crates/dyn-abi/benches/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use alloy_dyn_abi::DynSolType;
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BenchmarkGroup, Criterion,
};
use rand::seq::SliceRandom;
use std::{hint::black_box, time::Duration};

static KEYWORDS: &[&str] = &[
"address", "bool", "string", "bytes", "bytes32", "uint", "uint256", "int", "int256",
];
static COMPLEX: &[&str] = &[
"((uint104,bytes,bytes8,bytes7,address,bool,address,int256,int32,bytes1,uint56,int136),uint80,uint104,address,bool,bytes14,int16,address,string,uint176,uint72,(uint120,uint192,uint256,int232,bool,bool,bool,bytes5,int56,address,uint224,int248,bytes10,int48,int8),string,string,bool,bool)",
"(address,string,(bytes,int48,bytes30,bool,address,bytes30,int48,address,bytes17,bool,uint32),bool,address,bytes28,bytes25,uint136)",
"(uint168,bytes21,address,(bytes,bool,string,address,bool,string,bytes,uint232,int128,int64,uint96,bytes7,int136),bool,uint200[5],bool,bytes,uint240,address,address,bytes15,bytes)"
];

fn parse(c: &mut Criterion) {
let mut g = group(c, "parse");
let rng = &mut rand::thread_rng();

g.bench_function("keywords", |b| {
b.iter(|| {
let kw = KEYWORDS.choose(rng).unwrap();
DynSolType::parse(black_box(*kw)).unwrap()
})
});
g.bench_function("complex", |b| {
b.iter(|| {
let complex = COMPLEX.choose(rng).unwrap();
DynSolType::parse(black_box(*complex)).unwrap()
})
});

g.finish();
}

fn format(c: &mut Criterion) {
let mut g = group(c, "format");
let rng = &mut rand::thread_rng();

g.bench_function("keywords", |b| {
let keyword_types = KEYWORDS
.iter()
.map(|s| DynSolType::parse(s).unwrap())
.collect::<Vec<_>>();
let keyword_types = keyword_types.as_slice();
assert!(!keyword_types.is_empty());
b.iter(|| {
let kw = unsafe { keyword_types.choose(rng).unwrap_unchecked() };
black_box(kw).sol_type_name()
})
});
g.bench_function("complex", |b| {
let complex_types = COMPLEX
.iter()
.map(|s| DynSolType::parse(s).unwrap())
.collect::<Vec<_>>();
let complex_types = complex_types.as_slice();
assert!(!complex_types.is_empty());
b.iter(|| {
let complex = unsafe { complex_types.choose(rng).unwrap_unchecked() };
black_box(complex).sol_type_name()
})
});

g.finish();
}

fn group<'a>(c: &'a mut Criterion, group_name: &str) -> BenchmarkGroup<'a, WallTime> {
let mut g = c.benchmark_group(group_name);
g.noise_threshold(0.03)
.warm_up_time(Duration::from_secs(1))
.measurement_time(Duration::from_secs(3))
.sample_size(200);
g
}

criterion_group!(benches, parse, format);
criterion_main!(benches);
Loading

0 comments on commit 1c2373e

Please sign in to comment.