feat: generate rust structs from solidity JSON ABI (#378)
* add raw abi model * feat: simplify struct representation * feat: add struct generation * use structs as function input * fix: failing test * add example * rustfmt
This commit is contained in:
parent
b38685c574
commit
ba5f650dec
|
@ -846,6 +846,7 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
"quote",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
"url",
|
||||
|
|
|
@ -19,6 +19,7 @@ quote = "1.0"
|
|||
syn = "1.0.12"
|
||||
url = "2.1"
|
||||
serde_json = "1.0.61"
|
||||
serde = { version = "1.0.124", features = ["derive"] }
|
||||
hex = { version = "0.4.2", default-features = false, features = ["std"] }
|
||||
reqwest = { version = "0.11.3", features = ["blocking"] }
|
||||
once_cell = { version = "1.8.0", default-features = false }
|
||||
|
|
|
@ -7,6 +7,8 @@ mod types;
|
|||
|
||||
use super::util;
|
||||
use super::Abigen;
|
||||
use crate::contract::structs::InternalStructs;
|
||||
use crate::rawabi::RawAbi;
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use ethers_core::abi::AbiParser;
|
||||
use ethers_core::{
|
||||
|
@ -30,6 +32,9 @@ pub(crate) struct Context {
|
|||
/// The parser used for human readable format
|
||||
abi_parser: AbiParser,
|
||||
|
||||
/// Contains all the solidity structs extracted from the JSON ABI.
|
||||
internal_structs: InternalStructs,
|
||||
|
||||
/// Was the ABI in human readable format?
|
||||
human_readable: bool,
|
||||
|
||||
|
@ -118,6 +123,14 @@ impl Context {
|
|||
(abi_parser.parse_str(&abi_str)?, true)
|
||||
};
|
||||
|
||||
// try to extract all the solidity structs from the normal JSON ABI
|
||||
// we need to parse the json abi again because we need the internalType fields which are omitted by ethabi.
|
||||
let internal_structs = (!human_readable)
|
||||
.then(|| serde_json::from_str::<RawAbi>(&abi_str).ok())
|
||||
.flatten()
|
||||
.map(InternalStructs::new)
|
||||
.unwrap_or_default();
|
||||
|
||||
let contract_name = util::ident(&args.contract_name);
|
||||
|
||||
// NOTE: We only check for duplicate signatures here, since if there are
|
||||
|
@ -146,6 +159,7 @@ impl Context {
|
|||
human_readable,
|
||||
abi_str: Literal::string(&abi_str),
|
||||
abi_parser,
|
||||
internal_structs,
|
||||
contract_name,
|
||||
method_aliases,
|
||||
event_derives,
|
||||
|
|
|
@ -22,27 +22,93 @@ impl Context {
|
|||
.flatten()
|
||||
.map(|function| {
|
||||
let signature = function.abi_signature();
|
||||
expand_function(function, aliases.remove(&signature))
|
||||
self.expand_function(function, aliases.remove(&signature))
|
||||
.with_context(|| format!("error expanding function '{}'", signature))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(quote! { #( #functions )* })
|
||||
}
|
||||
|
||||
fn expand_inputs_call_arg_with_structs(
|
||||
&self,
|
||||
fun: &Function,
|
||||
) -> Result<(TokenStream, TokenStream)> {
|
||||
let mut args = Vec::with_capacity(fun.inputs.len());
|
||||
let mut call_args = Vec::with_capacity(fun.inputs.len());
|
||||
for (i, param) in fun.inputs.iter().enumerate() {
|
||||
let name = util::expand_input_name(i, ¶m.name);
|
||||
let ty = self.expand_input_param(fun, ¶m.name, ¶m.kind)?;
|
||||
args.push(quote! { #name: #ty });
|
||||
let call_arg = match param.kind {
|
||||
// this is awkward edge case where the function inputs are a single struct
|
||||
// we need to force this argument into a tuple so it gets expanded to `((#name,))`
|
||||
// this is currently necessary because internally `flatten_tokens` is called which removes the outermost `tuple` level
|
||||
// and since `((#name))` is not a rust tuple it doesn't get wrapped into another tuple that will be peeled off by `flatten_tokens`
|
||||
ParamType::Tuple(_) if fun.inputs.len() == 1 => {
|
||||
// make sure the tuple gets converted to `Token::Tuple`
|
||||
quote! {(#name,)}
|
||||
}
|
||||
_ => name,
|
||||
};
|
||||
call_args.push(call_arg);
|
||||
}
|
||||
let args = quote! { #( , #args )* };
|
||||
let call_args = match call_args.len() {
|
||||
0 => quote! { () },
|
||||
1 => quote! { #( #call_args )* },
|
||||
_ => quote! { ( #(#call_args, )* ) },
|
||||
};
|
||||
|
||||
Ok((args, call_args))
|
||||
}
|
||||
|
||||
fn expand_input_param(
|
||||
&self,
|
||||
fun: &Function,
|
||||
param: &str,
|
||||
kind: &ParamType,
|
||||
) -> Result<TokenStream> {
|
||||
match kind {
|
||||
ParamType::Array(ty) => {
|
||||
let ty = self.expand_input_param(fun, param, ty)?;
|
||||
Ok(quote! {
|
||||
::std::vec::Vec<#ty>
|
||||
})
|
||||
}
|
||||
ParamType::FixedArray(ty, size) => {
|
||||
let ty = self.expand_input_param(fun, param, ty)?;
|
||||
let size = *size;
|
||||
Ok(quote! {[#ty; #size]})
|
||||
}
|
||||
ParamType::Tuple(_) => {
|
||||
let ty = if let Some(rust_struct_name) = self
|
||||
.internal_structs
|
||||
.get_function_input_struct_type(&fun.name, param)
|
||||
{
|
||||
let ident = util::ident(rust_struct_name);
|
||||
quote! {#ident}
|
||||
} else {
|
||||
types::expand(kind)?
|
||||
};
|
||||
Ok(ty)
|
||||
}
|
||||
_ => types::expand(kind),
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
|
||||
fn expand_function(&self, function: &Function, alias: Option<Ident>) -> Result<TokenStream> {
|
||||
let name = alias.unwrap_or_else(|| util::safe_ident(&function.name.to_snake_case()));
|
||||
let selector = expand_selector(function.selector());
|
||||
|
||||
let input = expand_inputs(&function.inputs)?;
|
||||
|
||||
// TODO use structs
|
||||
let outputs = expand_fn_outputs(&function.outputs)?;
|
||||
|
||||
let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> };
|
||||
|
||||
let arg = expand_inputs_call_arg(&function.inputs);
|
||||
let (input, arg) = self.expand_inputs_call_arg_with_structs(function)?;
|
||||
|
||||
let doc = util::expand_doc(&format!(
|
||||
"Calls the contract's `{}` (0x{}) function",
|
||||
function.name,
|
||||
|
@ -57,6 +123,7 @@ fn expand_function(function: &Function, alias: Option<Ident>) -> Result<TokenStr
|
|||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// converts the function params to name/type pairs
|
||||
pub(crate) fn expand_inputs(inputs: &[Param]) -> Result<TokenStream> {
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
//! Methods for expanding structs
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use anyhow::{Context as _, Result};
|
||||
use inflector::Inflector;
|
||||
use proc_macro2::{Literal, TokenStream};
|
||||
use quote::quote;
|
||||
|
||||
use ethers_core::abi::{struct_def::FieldType, ParamType};
|
||||
use ethers_core::abi::{
|
||||
param_type::Reader,
|
||||
struct_def::{FieldDeclaration, FieldType, StructFieldType, StructType},
|
||||
ParamType, SolStruct,
|
||||
};
|
||||
|
||||
use crate::contract::{types, Context};
|
||||
use crate::rawabi::{Component, RawAbi};
|
||||
use crate::util;
|
||||
use std::any::Any;
|
||||
|
||||
impl Context {
|
||||
/// Generate corresponding types for structs parsed from a human readable ABI
|
||||
|
@ -16,8 +24,102 @@ impl Context {
|
|||
/// in fact present in the `AbiParser`, this is sound because `AbiParser::parse` would have
|
||||
/// failed already
|
||||
pub fn abi_structs(&self) -> Result<TokenStream> {
|
||||
let mut structs = Vec::with_capacity(self.abi_parser.structs.len());
|
||||
for (name, sol_struct) in &self.abi_parser.structs {
|
||||
if self.human_readable {
|
||||
self.gen_human_readable_structs()
|
||||
} else {
|
||||
self.gen_internal_structs()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the `TokenStream` with all the internal structs extracted form the JSON ABI
|
||||
fn gen_internal_structs(&self) -> Result<TokenStream> {
|
||||
let mut structs = TokenStream::new();
|
||||
let mut ids: Vec<_> = self.internal_structs.structs.keys().collect();
|
||||
ids.sort();
|
||||
|
||||
for id in ids {
|
||||
let sol_struct = &self.internal_structs.structs[id];
|
||||
let struct_name = self
|
||||
.internal_structs
|
||||
.rust_type_names
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?;
|
||||
let tuple = self
|
||||
.internal_structs
|
||||
.struct_tuples
|
||||
.get(id)
|
||||
.context(format!("No types found for {}", id))?
|
||||
.clone();
|
||||
structs.extend(self.expand_internal_struct(struct_name, sol_struct, tuple)?);
|
||||
}
|
||||
Ok(structs)
|
||||
}
|
||||
|
||||
/// Expand all structs parsed from the internal types of the JSON ABI
|
||||
fn expand_internal_struct(
|
||||
&self,
|
||||
name: &str,
|
||||
sol_struct: &SolStruct,
|
||||
tuple: ParamType,
|
||||
) -> Result<TokenStream> {
|
||||
let mut fields = Vec::with_capacity(sol_struct.fields().len());
|
||||
for field in sol_struct.fields() {
|
||||
let field_name = util::ident(&field.name().to_snake_case());
|
||||
match field.r#type() {
|
||||
FieldType::Elementary(ty) => {
|
||||
let ty = types::expand(ty)?;
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
}
|
||||
FieldType::Struct(struct_ty) => {
|
||||
let ty = expand_struct_type(struct_ty);
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
}
|
||||
FieldType::Mapping(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Mapping types in struct `{}` are not supported {:?}",
|
||||
name,
|
||||
field
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sig = if let ParamType::Tuple(ref tokens) = tuple {
|
||||
tokens
|
||||
.iter()
|
||||
.map(|kind| kind.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(",")
|
||||
} else {
|
||||
"".to_string()
|
||||
};
|
||||
|
||||
let abi_signature = format!("{}({})", name, sig,);
|
||||
|
||||
let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
|
||||
|
||||
let name = util::ident(name);
|
||||
|
||||
// use the same derives as for events
|
||||
let derives = &self.event_derives;
|
||||
let derives = quote! {#(#derives),*};
|
||||
|
||||
Ok(quote! {
|
||||
#abi_signature_doc
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)]
|
||||
pub struct #name {
|
||||
#( #fields ),*
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Expand all structs parsed from the human readable ABI
|
||||
fn gen_human_readable_structs(&self) -> Result<TokenStream> {
|
||||
let mut structs = TokenStream::new();
|
||||
let mut names: Vec<_> = self.abi_parser.structs.keys().collect();
|
||||
names.sort();
|
||||
for name in names {
|
||||
let sol_struct = &self.abi_parser.structs[name];
|
||||
let mut fields = Vec::with_capacity(sol_struct.fields().len());
|
||||
let mut param_types = Vec::with_capacity(sol_struct.fields().len());
|
||||
for field in sol_struct.fields() {
|
||||
|
@ -29,44 +131,19 @@ impl Context {
|
|||
fields.push(quote! { pub #field_name: #ty });
|
||||
}
|
||||
FieldType::Struct(struct_ty) => {
|
||||
let ty = util::ident(struct_ty.name());
|
||||
let ty = expand_struct_type(struct_ty);
|
||||
fields.push(quote! { pub #field_name: #ty });
|
||||
|
||||
let name = struct_ty.name();
|
||||
let tuple = self
|
||||
.abi_parser
|
||||
.struct_tuples
|
||||
.get(struct_ty.name())
|
||||
.context(format!("No types found for {}", struct_ty.name()))?
|
||||
.get(name)
|
||||
.context(format!("No types found for {}", name))?
|
||||
.clone();
|
||||
param_types.push(ParamType::Tuple(tuple));
|
||||
}
|
||||
FieldType::StructArray(struct_ty) => {
|
||||
let ty = util::ident(struct_ty.name());
|
||||
fields.push(quote! { pub #field_name: ::std::vec::Vec<#ty> });
|
||||
let tuple = ParamType::Tuple(tuple);
|
||||
|
||||
let tuple = self
|
||||
.abi_parser
|
||||
.struct_tuples
|
||||
.get(struct_ty.name())
|
||||
.context(format!("No types found for {}", struct_ty.name()))?
|
||||
.clone();
|
||||
param_types.push(ParamType::Array(Box::new(ParamType::Tuple(tuple))));
|
||||
}
|
||||
FieldType::FixedStructArray(struct_ty, len) => {
|
||||
let ty = util::ident(struct_ty.name());
|
||||
let size = Literal::usize_unsuffixed(*len);
|
||||
fields.push(quote! { pub #field_name: [#ty; #size] });
|
||||
|
||||
let tuple = self
|
||||
.abi_parser
|
||||
.struct_tuples
|
||||
.get(struct_ty.name())
|
||||
.context(format!("No types found for {}", struct_ty.name()))?
|
||||
.clone();
|
||||
param_types.push(ParamType::FixedArray(
|
||||
Box::new(ParamType::Tuple(tuple)),
|
||||
*len,
|
||||
));
|
||||
param_types.push(struct_ty.as_param(tuple));
|
||||
}
|
||||
FieldType::Mapping(_) => {
|
||||
return Err(anyhow::anyhow!(
|
||||
|
@ -96,7 +173,7 @@ impl Context {
|
|||
let derives = &self.event_derives;
|
||||
let derives = quote! {#(#derives),*};
|
||||
|
||||
structs.push(quote! {
|
||||
structs.extend(quote! {
|
||||
#abi_signature_doc
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, ethers::contract::EthAbiType, #derives)]
|
||||
pub struct #name {
|
||||
|
@ -104,6 +181,341 @@ impl Context {
|
|||
}
|
||||
});
|
||||
}
|
||||
Ok(quote! {#( #structs )*})
|
||||
Ok(structs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to match `ethabi::Param`s with structs and nested structs
|
||||
///
|
||||
/// This is currently used to get access to all the unique solidity structs used as function in/output until `ethabi` supports it as well.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct InternalStructs {
|
||||
/// All unique internal types that are function inputs or outputs
|
||||
top_level_internal_types: HashMap<String, Component>,
|
||||
|
||||
/// (function name, param name) -> struct which are the identifying properties we get the name from ethabi.
|
||||
function_params: HashMap<(String, String), String>,
|
||||
|
||||
/// (function name) -> Vec<structs> all structs the function returns
|
||||
outputs: HashMap<String, Vec<String>>,
|
||||
|
||||
/// All the structs extracted from the abi with their identifier as key
|
||||
structs: HashMap<String, SolStruct>,
|
||||
|
||||
/// solidity structs as tuples
|
||||
struct_tuples: HashMap<String, ParamType>,
|
||||
|
||||
/// Contains the names for the rust types (id -> rust type name)
|
||||
rust_type_names: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl InternalStructs {
|
||||
pub fn new(abi: RawAbi) -> Self {
|
||||
let mut top_level_internal_types = HashMap::new();
|
||||
let mut function_params = HashMap::new();
|
||||
let mut outputs = HashMap::new();
|
||||
let mut structs = HashMap::new();
|
||||
for item in abi
|
||||
.into_iter()
|
||||
.filter(|item| item.type_field == "constructor" || item.type_field == "function")
|
||||
{
|
||||
if let Some(name) = item.name {
|
||||
for input in item.inputs {
|
||||
if let Some(ty) = input
|
||||
.internal_type
|
||||
.as_deref()
|
||||
.filter(|ty| ty.starts_with("struct "))
|
||||
.map(struct_type_identifier)
|
||||
{
|
||||
function_params.insert((name.clone(), input.name.clone()), ty.to_string());
|
||||
top_level_internal_types.insert(ty.to_string(), input);
|
||||
}
|
||||
}
|
||||
let mut output_structs = Vec::new();
|
||||
for output in item.outputs {
|
||||
if let Some(ty) = output
|
||||
.internal_type
|
||||
.as_deref()
|
||||
.filter(|ty| ty.starts_with("struct "))
|
||||
.map(struct_type_identifier)
|
||||
{
|
||||
output_structs.push(ty.to_string());
|
||||
top_level_internal_types.insert(ty.to_string(), output);
|
||||
}
|
||||
}
|
||||
outputs.insert(name, output_structs);
|
||||
}
|
||||
}
|
||||
|
||||
// turn each top level internal type (function input/output) and their nested types
|
||||
// into a struct will create all structs
|
||||
for component in top_level_internal_types.values() {
|
||||
insert_structs(&mut structs, component);
|
||||
}
|
||||
|
||||
// determine the `ParamType` representation of each struct
|
||||
let struct_tuples = resolve_struct_tuples(&structs);
|
||||
|
||||
// name -> (id, projections)
|
||||
let mut type_names: HashMap<String, (String, Vec<String>)> =
|
||||
HashMap::with_capacity(structs.len());
|
||||
for id in structs.keys() {
|
||||
let name = struct_type_name(id).to_pascal_case();
|
||||
let projections = struct_type_projections(id);
|
||||
insert_rust_type_name(&mut type_names, name, projections, id.clone());
|
||||
}
|
||||
|
||||
Self {
|
||||
top_level_internal_types,
|
||||
function_params,
|
||||
outputs,
|
||||
structs,
|
||||
struct_tuples,
|
||||
rust_type_names: type_names
|
||||
.into_iter()
|
||||
.map(|(rust_name, (id, _))| (id, rust_name))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the rust type that will be generated if the given input is a struct
|
||||
/// NOTE: this does not account for arrays or fixed arrays
|
||||
pub fn get_function_input_struct_type(&self, function: &str, input: &str) -> Option<&str> {
|
||||
let key = (function.to_string(), input.to_string());
|
||||
self.function_params
|
||||
.get(&key)
|
||||
.and_then(|id| self.rust_type_names.get(id))
|
||||
.map(String::as_str)
|
||||
}
|
||||
}
|
||||
|
||||
/// This will determine the name of the rust type and will make sure that possible collisions are resolved by adjusting the actual Rust name of the structure, e.g. `LibraryA.Point` and `LibraryB.Point` to `LibraryAPoint` and `LibraryBPoint`.
|
||||
fn insert_rust_type_name(
|
||||
type_names: &mut HashMap<String, (String, Vec<String>)>,
|
||||
mut name: String,
|
||||
mut projections: Vec<String>,
|
||||
id: String,
|
||||
) {
|
||||
if let Some((other_id, mut other_projections)) = type_names.remove(&name) {
|
||||
let mut other_name = name.clone();
|
||||
// name collision `A.name` `B.name`, rename to `AName`, `BName`
|
||||
if !other_projections.is_empty() {
|
||||
other_name = format!(
|
||||
"{}{}",
|
||||
other_projections.remove(0).to_pascal_case(),
|
||||
other_name
|
||||
);
|
||||
}
|
||||
insert_rust_type_name(type_names, other_name, other_projections, other_id);
|
||||
|
||||
if !projections.is_empty() {
|
||||
name = format!("{}{}", projections.remove(0).to_pascal_case(), name);
|
||||
}
|
||||
insert_rust_type_name(type_names, name, projections, id);
|
||||
} else {
|
||||
type_names.insert(name, (id, projections));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tries to determine the `ParamType::Tuple` for every struct.
|
||||
///
|
||||
/// If a structure has nested structures, these must be determined first, essentially starting with structures consisting of only elementary types before moving on to higher level structures, for example `Proof {point: Point}, Point {x:int, y:int}` start by converting Point into a tuple of `x` and `y` and then substituting `point` with this within `Proof`.
|
||||
fn resolve_struct_tuples(all_structs: &HashMap<String, SolStruct>) -> HashMap<String, ParamType> {
|
||||
let mut params = HashMap::new();
|
||||
let mut structs: VecDeque<_> = all_structs.iter().collect();
|
||||
|
||||
// keep track of how often we retried nested structs
|
||||
let mut sequential_retries = 0;
|
||||
'outer: while let Some((id, ty)) = structs.pop_front() {
|
||||
if sequential_retries > structs.len() {
|
||||
break;
|
||||
}
|
||||
if let Some(tuple) = ty.as_tuple() {
|
||||
params.insert(id.to_string(), tuple);
|
||||
} else {
|
||||
// try to substitute all nested struct types with their `ParamTypes`
|
||||
let mut struct_params = Vec::with_capacity(ty.fields.len());
|
||||
for field in ty.fields() {
|
||||
match field.ty {
|
||||
FieldType::Elementary(ref param) => {
|
||||
struct_params.push(param.clone());
|
||||
}
|
||||
FieldType::Struct(ref field_ty) => {
|
||||
// nested struct
|
||||
let ty_id = field_ty.identifier();
|
||||
if let Some(nested) = params.get(&ty_id).cloned() {
|
||||
match field_ty {
|
||||
StructFieldType::Type(_) => struct_params.push(nested),
|
||||
StructFieldType::Array(_) => {
|
||||
struct_params.push(ParamType::Array(Box::new(nested)));
|
||||
}
|
||||
StructFieldType::FixedArray(_, size) => {
|
||||
struct_params
|
||||
.push(ParamType::FixedArray(Box::new(nested), *size));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// struct field needs to be resolved first
|
||||
structs.push_back((id, ty));
|
||||
sequential_retries += 1;
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
unreachable!("mapping types are unsupported")
|
||||
}
|
||||
}
|
||||
}
|
||||
params.insert(id.to_string(), ParamType::Tuple(struct_params));
|
||||
}
|
||||
|
||||
// we resolved a new param, so we can try all again
|
||||
sequential_retries = 0;
|
||||
}
|
||||
params
|
||||
}
|
||||
|
||||
/// turns the tuple component into a struct if it's still missing in the map, including all inner structs
|
||||
fn insert_structs(structs: &mut HashMap<String, SolStruct>, tuple: &Component) {
|
||||
if let Some(internal_ty) = tuple.internal_type.as_ref() {
|
||||
let ident = struct_type_identifier(internal_ty);
|
||||
if structs.contains_key(ident) {
|
||||
return;
|
||||
}
|
||||
if let Some(fields) = tuple
|
||||
.components
|
||||
.iter()
|
||||
.map(|f| {
|
||||
Reader::read(&f.type_field)
|
||||
.ok()
|
||||
.and_then(|kind| field(structs, f, kind))
|
||||
})
|
||||
.collect::<Option<Vec<_>>>()
|
||||
{
|
||||
let s = SolStruct {
|
||||
name: ident.to_string(),
|
||||
fields,
|
||||
};
|
||||
structs.insert(ident.to_string(), s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the type of the field component
|
||||
fn field(
|
||||
structs: &mut HashMap<String, SolStruct>,
|
||||
field_component: &Component,
|
||||
kind: ParamType,
|
||||
) -> Option<FieldDeclaration> {
|
||||
match kind {
|
||||
ParamType::Array(ty) => {
|
||||
let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
|
||||
match ty {
|
||||
FieldType::Elementary(kind) => {
|
||||
// this arm represents all the elementary types like address, uint...
|
||||
Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Elementary(ParamType::Array(Box::new(kind))),
|
||||
))
|
||||
}
|
||||
FieldType::Struct(ty) => Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Struct(StructFieldType::Array(Box::new(ty))),
|
||||
)),
|
||||
_ => {
|
||||
unreachable!("no mappings types support as function inputs or outputs")
|
||||
}
|
||||
}
|
||||
}
|
||||
ParamType::FixedArray(ty, size) => {
|
||||
let FieldDeclaration { ty, .. } = field(structs, field_component, *ty)?;
|
||||
match ty {
|
||||
FieldType::Elementary(kind) => {
|
||||
// this arm represents all the elementary types like address, uint...
|
||||
Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Elementary(ParamType::FixedArray(Box::new(kind), size)),
|
||||
))
|
||||
}
|
||||
FieldType::Struct(ty) => Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Struct(StructFieldType::FixedArray(Box::new(ty), size)),
|
||||
)),
|
||||
_ => {
|
||||
unreachable!("no mappings types support as function inputs or outputs")
|
||||
}
|
||||
}
|
||||
}
|
||||
ParamType::Tuple(_) => {
|
||||
insert_structs(structs, field_component);
|
||||
let internal_type = field_component.internal_type.as_ref()?;
|
||||
let ty = struct_type_identifier(internal_type);
|
||||
// split the identifier into the name and all projections:
|
||||
// `A.B.C.name` -> name, [A,B,C]
|
||||
let mut idents = ty.rsplit('.');
|
||||
let name = idents.next().unwrap().to_string();
|
||||
let projections = idents.rev().map(str::to_string).collect();
|
||||
Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Struct(StructFieldType::Type(StructType::new(name, projections))),
|
||||
))
|
||||
}
|
||||
elementary => Some(FieldDeclaration::new(
|
||||
field_component.name.clone(),
|
||||
FieldType::Elementary(elementary),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// `struct Pairing.G2Point[]` -> `G2Point`
|
||||
fn struct_type_name(name: &str) -> &str {
|
||||
struct_type_identifier(name).rsplit('.').next().unwrap()
|
||||
}
|
||||
|
||||
/// `Pairing.G2Point` -> `Pairing.G2Point`
|
||||
fn struct_type_identifier(name: &str) -> &str {
|
||||
name.trim_start_matches("struct ")
|
||||
.split('[')
|
||||
.next()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// `struct Pairing.Nested.G2Point[]` -> `[Pairing, Nested]`
|
||||
fn struct_type_projections(name: &str) -> Vec<String> {
|
||||
let id = struct_type_identifier(name);
|
||||
let mut iter = id.rsplit('.');
|
||||
iter.next();
|
||||
iter.rev().map(str::to_string).collect()
|
||||
}
|
||||
|
||||
/// Expands to the rust struct type
|
||||
fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
|
||||
match struct_ty {
|
||||
StructFieldType::Type(ty) => {
|
||||
let ty = util::ident(ty.name());
|
||||
quote! {#ty}
|
||||
}
|
||||
StructFieldType::Array(ty) => {
|
||||
let ty = expand_struct_type(&*ty);
|
||||
quote! {::std::vec::Vec<#ty>}
|
||||
}
|
||||
StructFieldType::FixedArray(ty, size) => {
|
||||
let ty = expand_struct_type(&*ty);
|
||||
quote! { [#ty; #size]}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn can_determine_structs() {
|
||||
const VERIFIER_ABI: &str =
|
||||
include_str!("../../../tests/solidity-contracts/verifier_abi.json");
|
||||
let abi = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
|
||||
|
||||
let internal = InternalStructs::new(abi);
|
||||
dbg!(internal.rust_type_names);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ mod test_macros;
|
|||
mod contract;
|
||||
use contract::Context;
|
||||
|
||||
pub mod rawabi;
|
||||
mod rustfmt;
|
||||
mod source;
|
||||
mod util;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
//! This is a basic representation of a contract ABI that does no post processing but contains the raw content of the ABI.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Contract ABI as a list of items where each item can be a function, constructor or event
|
||||
pub type RawAbi = Vec<Item>;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Item {
|
||||
#[serde(default)]
|
||||
pub inputs: Vec<Component>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub state_mutability: Option<String>,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub outputs: Vec<Component>,
|
||||
}
|
||||
|
||||
/// Either
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Component {
|
||||
#[serde(
|
||||
rename = "internalType",
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none"
|
||||
)]
|
||||
pub internal_type: Option<String>,
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub type_field: String,
|
||||
#[serde(default)]
|
||||
pub components: Vec<Component>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn can_parse_raw_abi() {
|
||||
const VERIFIER_ABI: &str = include_str!("../../tests/solidity-contracts/verifier_abi.json");
|
||||
let _ = serde_json::from_str::<RawAbi>(VERIFIER_ABI).unwrap();
|
||||
}
|
||||
}
|
|
@ -43,24 +43,33 @@ fn can_gen_structs_readable() {
|
|||
);
|
||||
}
|
||||
|
||||
// NOTE(mattsse): There is currently a limitation with the `ethabi` crate's `Reader`
|
||||
// that doesn't support arrays of tuples; https://github.com/gakonst/ethabi/pull/1 should fix this
|
||||
// See also https://github.com/rust-ethereum/ethabi/issues/178 and
|
||||
// https://github.com/rust-ethereum/ethabi/pull/186
|
||||
#[test]
|
||||
fn can_gen_structs_with_arrays_readable() {
|
||||
abigen!(
|
||||
SimpleContract,
|
||||
r#"[
|
||||
struct Value {address addr; string value;}
|
||||
struct Addresses {address[] addr; string s;}
|
||||
event ValueChanged(Value indexed old, Value newValue, Addresses[] _a)
|
||||
]"#,
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
assert_eq!(
|
||||
"ValueChanged((address,string),(address,string),(address[],string)[])",
|
||||
ValueChangedFilter::abi_signature()
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn can_gen_structs_with_arrays_readable() {
|
||||
// abigen!(
|
||||
// SimpleContract,
|
||||
// r#"[
|
||||
// struct Value {address addr; string value;}
|
||||
// struct Addresses {address[] addr; string s;}
|
||||
// event ValueChanged(Value indexed old, Value newValue, Addresses[] _a)
|
||||
// ]"#,
|
||||
// event_derives(serde::Deserialize, serde::Serialize)
|
||||
// );
|
||||
// assert_eq!(
|
||||
// "ValueChanged((address,string),(address,string),(address[],string)[])",
|
||||
// ValueChangedFilter::abi_signature()
|
||||
// );
|
||||
// }
|
||||
fn assert_tokenizeable<T: Tokenizable>() {}
|
||||
|
||||
#[test]
|
||||
fn can_generate_internal_structs() {
|
||||
abigen!(
|
||||
VerifierContract,
|
||||
"ethers-contract/tests/solidity-contracts/verifier_abi.json",
|
||||
event_derives(serde::Deserialize, serde::Serialize)
|
||||
);
|
||||
assert_tokenizeable::<VerifyingKey>();
|
||||
assert_tokenizeable::<G1Point>();
|
||||
assert_tokenizeable::<G2Point>();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
[
|
||||
{
|
||||
"inputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint256[]",
|
||||
"name": "input",
|
||||
"type": "uint256[]"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "X",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "Y",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G1Point",
|
||||
"name": "A",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "X",
|
||||
"type": "uint256[2]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "Y",
|
||||
"type": "uint256[2]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G2Point",
|
||||
"name": "B",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "X",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "Y",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G1Point",
|
||||
"name": "C",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Verifier.Proof",
|
||||
"name": "proof",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "X",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "Y",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G1Point",
|
||||
"name": "alfa1",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "X",
|
||||
"type": "uint256[2]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "Y",
|
||||
"type": "uint256[2]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G2Point",
|
||||
"name": "beta2",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "X",
|
||||
"type": "uint256[2]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "Y",
|
||||
"type": "uint256[2]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G2Point",
|
||||
"name": "gamma2",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "X",
|
||||
"type": "uint256[2]"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256[2]",
|
||||
"name": "Y",
|
||||
"type": "uint256[2]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G2Point",
|
||||
"name": "delta2",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "X",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"internalType": "uint256",
|
||||
"name": "Y",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Pairing.G1Point[]",
|
||||
"name": "IC",
|
||||
"type": "tuple[]"
|
||||
}
|
||||
],
|
||||
"internalType": "struct Verifier.VerifyingKey",
|
||||
"name": "vk",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "verify",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -110,26 +110,7 @@ impl AbiParser {
|
|||
FieldType::Elementary(param) => tuple.push(param.clone()),
|
||||
FieldType::Struct(ty) => {
|
||||
if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
|
||||
tuple.push(ParamType::Tuple(param))
|
||||
} else {
|
||||
resolved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FieldType::StructArray(ty) => {
|
||||
if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
|
||||
tuple.push(ParamType::Array(Box::new(ParamType::Tuple(param))))
|
||||
} else {
|
||||
resolved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
FieldType::FixedStructArray(ty, size) => {
|
||||
if let Some(param) = self.struct_tuples.get(ty.name()).cloned() {
|
||||
tuple.push(ParamType::FixedArray(
|
||||
Box::new(ParamType::Tuple(param)),
|
||||
*size,
|
||||
))
|
||||
tuple.push(ty.as_param(ParamType::Tuple(param)))
|
||||
} else {
|
||||
resolved = false;
|
||||
break;
|
||||
|
@ -331,13 +312,10 @@ impl AbiParser {
|
|||
.map(ParamType::Tuple)
|
||||
.ok_or_else(|| format_err!("Unknown struct `{}`", struct_ty.name()))?;
|
||||
|
||||
match field {
|
||||
FieldType::Struct(_) => Ok(tuple),
|
||||
FieldType::StructArray(_) => Ok(ParamType::Array(Box::new(tuple))),
|
||||
FieldType::FixedStructArray(_, size) => {
|
||||
Ok(ParamType::FixedArray(Box::new(tuple), size))
|
||||
}
|
||||
_ => bail!("Expected struct type"),
|
||||
if let Some(field) = field.as_struct() {
|
||||
Ok(field.as_param(tuple))
|
||||
} else {
|
||||
bail!("Expected struct type")
|
||||
}
|
||||
} else {
|
||||
bail!("Failed determine event type `{}`", type_str)
|
||||
|
|
|
@ -6,11 +6,15 @@ use crate::abi::{param_type::Reader, ParamType};
|
|||
/// A field declaration inside a struct
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FieldDeclaration {
|
||||
name: String,
|
||||
ty: FieldType,
|
||||
pub name: String,
|
||||
pub ty: FieldType,
|
||||
}
|
||||
|
||||
impl FieldDeclaration {
|
||||
pub fn new(name: String, ty: FieldType) -> Self {
|
||||
Self { name, ty }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
@ -24,13 +28,11 @@ impl FieldDeclaration {
|
|||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum FieldType {
|
||||
/// Represents elementary types, see [`ParamType`]
|
||||
///
|
||||
/// Note: tuples will be treated as rust tuples
|
||||
Elementary(ParamType),
|
||||
/// A non elementary type field, treated as user defined struct
|
||||
Struct(StructFieldType),
|
||||
// Array of user defined type
|
||||
StructArray(StructFieldType),
|
||||
// Array with fixed size of user defined type
|
||||
FixedStructArray(StructFieldType, usize),
|
||||
/// Mapping
|
||||
Mapping(Box<MappingType>),
|
||||
}
|
||||
|
@ -42,9 +44,7 @@ impl FieldType {
|
|||
|
||||
pub(crate) fn as_struct(&self) -> Option<&StructFieldType> {
|
||||
match self {
|
||||
FieldType::Struct(s)
|
||||
| FieldType::StructArray(s)
|
||||
| FieldType::FixedStructArray(s, _) => Some(s),
|
||||
FieldType::Struct(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
@ -72,8 +72,8 @@ pub struct StructFieldDeclaration {
|
|||
|
||||
/// How the type of a struct field is referenced
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct StructFieldType {
|
||||
/// The name of the struct
|
||||
pub struct StructType {
|
||||
/// The name of the struct (or rather the name of the rust type)
|
||||
name: String,
|
||||
/// All previous projections up until the name
|
||||
///
|
||||
|
@ -81,10 +81,63 @@ pub struct StructFieldType {
|
|||
projections: Vec<String>,
|
||||
}
|
||||
|
||||
impl StructFieldType {
|
||||
impl StructType {
|
||||
pub fn new(name: String, projections: Vec<String>) -> Self {
|
||||
Self { name, projections }
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the type of a field in a struct
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StructFieldType {
|
||||
/// A non elementary type field, represents a user defined struct
|
||||
Type(StructType),
|
||||
// Array of user defined type
|
||||
Array(Box<StructFieldType>),
|
||||
// Array with fixed size of user defined type
|
||||
FixedArray(Box<StructFieldType>, usize),
|
||||
}
|
||||
|
||||
impl StructFieldType {
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
StructFieldType::Type(ty) => &ty.name,
|
||||
StructFieldType::Array(ty) => ty.name(),
|
||||
StructFieldType::FixedArray(ty, _) => ty.name(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn projections(&self) -> &[String] {
|
||||
match self {
|
||||
StructFieldType::Type(ty) => &ty.projections,
|
||||
StructFieldType::Array(ty) => ty.projections(),
|
||||
StructFieldType::FixedArray(ty, _) => ty.projections(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identifier(&self) -> String {
|
||||
let name = self.name();
|
||||
let path = self.projections().join(".");
|
||||
if path.is_empty() {
|
||||
name.to_string()
|
||||
} else {
|
||||
format!("{}.{}", path, name)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_param(&self, tuple: ParamType) -> ParamType {
|
||||
match self {
|
||||
StructFieldType::Type(_) => tuple,
|
||||
StructFieldType::Array(ty) => ty.as_param(ParamType::Array(Box::new(tuple))),
|
||||
StructFieldType::FixedArray(ty, size) => {
|
||||
ty.as_param(ParamType::FixedArray(Box::new(tuple), *size))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a struct field declaration
|
||||
///
|
||||
|
@ -97,10 +150,10 @@ impl StructFieldType {
|
|||
let mut chars = input.chars();
|
||||
match chars.next() {
|
||||
None => {
|
||||
return Ok(FieldType::Struct(StructFieldType {
|
||||
return Ok(FieldType::Struct(StructFieldType::Type(StructType {
|
||||
name: ty,
|
||||
projections,
|
||||
}))
|
||||
})))
|
||||
}
|
||||
Some(' ') | Some('\t') | Some('[') => {
|
||||
// array
|
||||
|
@ -118,18 +171,23 @@ impl StructFieldType {
|
|||
}
|
||||
}
|
||||
Some(']') => {
|
||||
let ty = StructFieldType {
|
||||
let ty = StructType {
|
||||
name: ty,
|
||||
projections,
|
||||
};
|
||||
|
||||
return if size.is_empty() {
|
||||
Ok(FieldType::StructArray(ty))
|
||||
Ok(FieldType::Struct(StructFieldType::Array(Box::new(
|
||||
StructFieldType::Type(ty),
|
||||
))))
|
||||
} else {
|
||||
let size = size.parse().map_err(|_| {
|
||||
format_err!("Illegal array size `{}` at `{}`", size, input)
|
||||
})?;
|
||||
Ok(FieldType::FixedStructArray(ty, size))
|
||||
Ok(FieldType::Struct(StructFieldType::FixedArray(
|
||||
Box::new(StructFieldType::Type(ty)),
|
||||
size,
|
||||
)))
|
||||
};
|
||||
}
|
||||
Some(c) => {
|
||||
|
@ -157,8 +215,8 @@ impl StructFieldType {
|
|||
/// Represents a solidity struct
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct SolStruct {
|
||||
name: String,
|
||||
fields: Vec<FieldDeclaration>,
|
||||
pub name: String,
|
||||
pub fields: Vec<FieldDeclaration>,
|
||||
}
|
||||
|
||||
impl SolStruct {
|
||||
|
@ -223,6 +281,19 @@ impl SolStruct {
|
|||
pub fn fields(&self) -> &Vec<FieldDeclaration> {
|
||||
&self.fields
|
||||
}
|
||||
|
||||
/// If the struct only consists of elementary fields, this will return `ParamType::Tuple` with all those fields
|
||||
pub fn as_tuple(&self) -> Option<ParamType> {
|
||||
let mut params = Vec::with_capacity(self.fields.len());
|
||||
for field in self.fields() {
|
||||
if let FieldType::Elementary(ref param) = field.ty {
|
||||
params.push(param.clone())
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(ParamType::Tuple(params))
|
||||
}
|
||||
}
|
||||
|
||||
/// Strips the identifier of field declaration from the input and returns it
|
||||
|
@ -386,10 +457,10 @@ mod tests {
|
|||
},
|
||||
FieldDeclaration {
|
||||
name: "_other".to_string(),
|
||||
ty: FieldType::Struct(StructFieldType {
|
||||
ty: FieldType::Struct(StructFieldType::Type(StructType {
|
||||
name: "Inner".to_string(),
|
||||
projections: vec!["Some".to_string(), "Other".to_string()]
|
||||
}),
|
||||
})),
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
//! Main entry point for ContractMonitor
|
||||
|
||||
use ethers::{prelude::*, utils::Ganache};
|
||||
use std::{convert::TryFrom, sync::Arc, time::Duration};
|
||||
|
||||
abigen!(
|
||||
VerifierContract,
|
||||
"ethers-contract/tests/solidity-contracts/verifier_abi.json"
|
||||
);
|
||||
|
||||
/// This example only demonstrates how to use generated structs for solidity functions that
|
||||
/// have structs as input.
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let ganache = Ganache::new().spawn();
|
||||
let provider =
|
||||
Provider::<Http>::try_from(ganache.endpoint())?.interval(Duration::from_millis(10u64));
|
||||
let wallet: LocalWallet = ganache.keys()[0].clone().into();
|
||||
|
||||
let client = SignerMiddleware::new(provider, wallet);
|
||||
let client = Arc::new(client);
|
||||
|
||||
let contract = VerifierContract::new(Address::zero(), client);
|
||||
|
||||
// NOTE: this is all just dummy data
|
||||
let g1 = G1Point {
|
||||
x: U256::zero(),
|
||||
y: U256::zero(),
|
||||
};
|
||||
let g2 = G2Point {
|
||||
x: [U256::zero(), U256::zero()],
|
||||
y: [U256::zero(), U256::zero()],
|
||||
};
|
||||
let vk = VerifyingKey {
|
||||
alfa_1: g1.clone(),
|
||||
beta_2: g2.clone(),
|
||||
gamma_2: g2.clone(),
|
||||
delta_2: g2.clone(),
|
||||
ic: vec![g1.clone()],
|
||||
};
|
||||
let proof = Proof {
|
||||
a: g1.clone(),
|
||||
b: g2,
|
||||
c: g1,
|
||||
};
|
||||
|
||||
let _ = contract.verify(vec![], proof, vk);
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in New Issue