Skip to content

Commit

Permalink
feature: abi-json crate (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
prestwich authored Jun 8, 2023
1 parent bee92cc commit 0a00f01
Show file tree
Hide file tree
Showing 8 changed files with 740 additions and 0 deletions.
25 changes: 25 additions & 0 deletions crates/json-abi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
[package]
name = "alloy-json-abi"
description = "Ethereum ABI JSON file (de)serialization"
keywords = ["ethereum", "abi", "serialization"]
categories = ["encoding", "cryptography::cryptocurrencies"]
homepage = "https://github.com/alloy-rs/core/tree/main/crates/json-abi"

version.workspace = true
edition.workspace = true
rust-version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
exclude.workspace = true

[dependencies]
serde = { workspace = true, features = ["derive"] }
alloy-primitives.workspace = true

[dev-dependencies]
serde_json.workspace = true

[features]
default = ["std"]
std = ["serde/std", "alloy-primitives/std"]
147 changes: 147 additions & 0 deletions crates/json-abi/src/abi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
use crate::{AbiItem, Constructor, Error, Event, Fallback, Function, Receive};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use serde::{
de::{SeqAccess, Visitor},
ser::SerializeSeq,
Deserialize, Serialize,
};

/// The JSON contract ABI, as specified in the [Solidity documentation][ref].
///
/// [ref]: https://docs.soliditylang.org/en/latest/abi-spec.html#json
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AbiJson {
/// The constructor function.
pub constructor: Option<Constructor>,
/// The fallback function.
pub fallback: Option<Fallback>,
/// The receive function.
pub receive: Option<Receive>,
/// The functions, indexed by the function name
pub functions: BTreeMap<String, Vec<Function>>,
/// The events, indexed by the event name
pub events: BTreeMap<String, Vec<Event>>,
/// The errors, indexed by the error name
pub errors: BTreeMap<String, Vec<Error>>,
}

impl AbiJson {
/// The total number of items (of any type)
pub fn len(&self) -> usize {
self.constructor.is_some() as usize
+ self.fallback.is_some() as usize
+ self.receive.is_some() as usize
+ self.functions.values().map(Vec::len).sum::<usize>()
+ self.events.values().map(Vec::len).sum::<usize>()
+ self.errors.values().map(Vec::len).sum::<usize>()
}

/// True if the ABI contains no items
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}

impl<'de> Deserialize<'de> for AbiJson {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<AbiJson, D::Error> {
deserializer.deserialize_seq(AbiJsonVisitor)
}
}

impl Serialize for AbiJson {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.len()))?;

if let Some(constructor) = &self.constructor {
seq.serialize_element(constructor)?;
}
if let Some(fallback) = &self.fallback {
seq.serialize_element(fallback)?;
}
if let Some(receive) = &self.receive {
seq.serialize_element(receive)?;
}

self.functions
.values()
.flatten()
.try_for_each(|f| seq.serialize_element(f))?;

self.events
.values()
.flatten()
.try_for_each(|e| seq.serialize_element(&e))?;

self.errors
.values()
.flatten()
.try_for_each(|e| seq.serialize_element(&e))?;

seq.end()
}
}

struct AbiJsonVisitor;

impl<'de> Visitor<'de> for AbiJsonVisitor {
type Value = AbiJson;

fn expecting(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(formatter, "a valid ABI JSON file")
}

fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut json_file = AbiJson::default();

while let Some(item) = seq.next_element()? {
match item {
AbiItem::Constructor(c) => {
if json_file.constructor.is_some() {
return Err(serde::de::Error::duplicate_field("constructor"))
}
json_file.constructor = Some(c.into_owned());
}
AbiItem::Fallback(f) => {
if json_file.fallback.is_some() {
return Err(serde::de::Error::duplicate_field("fallback"))
}
json_file.fallback = Some(f.into_owned());
}
AbiItem::Receive(r) => {
if json_file.receive.is_some() {
return Err(serde::de::Error::duplicate_field("receive"))
}
json_file.receive = Some(r.into_owned());
}
AbiItem::Function(f) => {
json_file
.functions
.entry(f.name.clone())
.or_default()
.push(f.into_owned());
}
AbiItem::Event(e) => {
json_file
.events
.entry(e.name.clone())
.or_default()
.push(e.into_owned());
}
AbiItem::Error(e) => {
json_file
.errors
.entry(e.name.clone())
.or_default()
.push(e.into_owned());
}
}
}
Ok(json_file)
}
}
53 changes: 53 additions & 0 deletions crates/json-abi/src/event_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::param::Param;
use alloc::{string::String, vec::Vec};
use serde::{Deserialize, Serialize};

/// A Solidity Event parameter.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SimpleEventParam {
/// The name of the parameter.
pub name: String,
/// The Solidity type of the parameter.
#[serde(rename = "type")]
pub ty: String,
/// Whether the parameter is indexed. Indexed parameters have their
///value, or the hash of their value, stored in the log topics.
pub indexed: bool,
/// The internal type of the parameter. This type represents the type that
/// the author of the solidity contract specified. E.g. for a contract, this
/// will be `contract MyContract` while the `type` field will be `address`.
#[serde(rename = "internalType")]
pub internal_type: String,
}

/// JSON representation of a complex event parameter.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct ComplexEventParam {
/// The name of the parameter.
pub name: String,
/// The Solidity type of the parameter.
#[serde(rename = "type")]
pub ty: String,
/// Whether the parameter is indexed. Indexed parameters have their
/// value, or the hash of their value, stored in the log topics.
pub indexed: bool,
/// A list of the parameter's components, in order. This is a tuple
/// definition, and sub-components will NOT have an `indexed` field.
pub components: Vec<Param>,
/// The internal type of the parameter. This type represents the type that
/// the author of the solidity contract specified. E.g. for a contract, this
/// will be `contract MyContract` while the `type` field will be `address`.
#[serde(rename = "internalType")]
pub internal_type: String,
}

/// A Solidity Event parameter. Event parameters are distinct from function
/// parameters in that they have an `indexed` field.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum EventParam {
/// [`ComplexEventParam`] variant
Complex(ComplexEventParam),
/// [`SimpleEventParam`] variant
Simple(SimpleEventParam),
}
Loading

0 comments on commit 0a00f01

Please sign in to comment.