Generate structs for the return data of abigen-erated contracts (#1440)
* convert some helper functions * use said helpers * more * don't derive EthCall on return structs * move return structs to separate function * remove unused * remove duplicate code * reduce code duplication also use an iterator instead of a presized vector * comments * stuck * fix wrong field * rename * don't generate structs for no-output functions * cosmetic changes * test: decode and verify result * more testing unnamed output (tuple struct) no output (doesn't exist) -> can't verify this in code though * Update ethers-contract/ethers-contract-abigen/src/contract/methods.rs Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de> * remove dbg print oops :( Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
parent
3c1de64240
commit
b287fcca4d
|
@ -37,9 +37,15 @@ impl Context {
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let function_impls = quote! { #( #functions )* };
|
let function_impls = quote! { #( #functions )* };
|
||||||
let call_structs = self.expand_call_structs(aliases)?;
|
let call_structs = self.expand_call_structs(aliases.clone())?;
|
||||||
|
let return_structs = self.expand_return_structs(aliases)?;
|
||||||
|
|
||||||
Ok((function_impls, call_structs))
|
let all_structs = quote! {
|
||||||
|
#call_structs
|
||||||
|
#return_structs
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((function_impls, all_structs))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all deploy (constructor) implementations
|
/// Returns all deploy (constructor) implementations
|
||||||
|
@ -135,7 +141,47 @@ impl Context {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands all structs
|
/// Expands to the corresponding struct type based on the inputs of the given function
|
||||||
|
fn expand_return_struct(
|
||||||
|
&self,
|
||||||
|
function: &Function,
|
||||||
|
alias: Option<&MethodAlias>,
|
||||||
|
) -> Result<TokenStream> {
|
||||||
|
let struct_name = expand_return_struct_name(function, alias);
|
||||||
|
let fields = self.expand_output_params(function)?;
|
||||||
|
// no point in having structs when there is no data returned
|
||||||
|
if function.outputs.is_empty() {
|
||||||
|
return Ok(TokenStream::new())
|
||||||
|
}
|
||||||
|
// expand as a tuple if all fields are anonymous
|
||||||
|
let all_anonymous_fields = function.outputs.iter().all(|output| output.name.is_empty());
|
||||||
|
let return_type_definition = if all_anonymous_fields {
|
||||||
|
// expand to a tuple struct
|
||||||
|
expand_data_tuple(&struct_name, &fields)
|
||||||
|
} else {
|
||||||
|
// expand to a struct
|
||||||
|
expand_data_struct(&struct_name, &fields)
|
||||||
|
};
|
||||||
|
let abi_signature = function.abi_signature();
|
||||||
|
let doc = format!(
|
||||||
|
"Container type for all return fields from the `{}` function with signature `{}` and selector `{:?}`",
|
||||||
|
function.name,
|
||||||
|
abi_signature,
|
||||||
|
function.selector()
|
||||||
|
);
|
||||||
|
let abi_signature_doc = util::expand_doc(&doc);
|
||||||
|
let ethers_contract = ethers_contract_crate();
|
||||||
|
// use the same derives as for events
|
||||||
|
let derives = util::expand_derives(&self.event_derives);
|
||||||
|
|
||||||
|
Ok(quote! {
|
||||||
|
#abi_signature_doc
|
||||||
|
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
|
||||||
|
pub #return_type_definition
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands all call structs
|
||||||
fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
|
fn expand_call_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
|
||||||
let mut struct_defs = Vec::new();
|
let mut struct_defs = Vec::new();
|
||||||
let mut struct_names = Vec::new();
|
let mut struct_names = Vec::new();
|
||||||
|
@ -214,20 +260,52 @@ impl Context {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands all return structs
|
||||||
|
fn expand_return_structs(&self, aliases: BTreeMap<String, MethodAlias>) -> Result<TokenStream> {
|
||||||
|
let mut struct_defs = Vec::new();
|
||||||
|
for function in self.abi.functions.values().flatten() {
|
||||||
|
let signature = function.abi_signature();
|
||||||
|
let alias = aliases.get(&signature);
|
||||||
|
struct_defs.push(self.expand_return_struct(function, alias)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let struct_def_tokens = quote! {
|
||||||
|
#(#struct_defs)*
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(struct_def_tokens)
|
||||||
|
}
|
||||||
|
|
||||||
/// The name ident of the calls enum
|
/// The name ident of the calls enum
|
||||||
fn expand_calls_enum_name(&self) -> Ident {
|
fn expand_calls_enum_name(&self) -> Ident {
|
||||||
util::ident(&format!("{}Calls", self.contract_ident))
|
util::ident(&format!("{}Calls", self.contract_ident))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands to the `name : type` pairs of the function's parameters
|
||||||
|
fn expand_params(
|
||||||
|
&self,
|
||||||
|
fun: &Function,
|
||||||
|
params: &[Param],
|
||||||
|
) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||||
|
params
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(idx, param)| {
|
||||||
|
let name = util::expand_input_name(idx, ¶m.name);
|
||||||
|
let ty = self.expand_input_param_type(fun, ¶m.name, ¶m.kind)?;
|
||||||
|
Ok((name, ty))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// Expands to the `name : type` pairs of the function's inputs
|
/// Expands to the `name : type` pairs of the function's inputs
|
||||||
fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||||
let mut args = Vec::with_capacity(fun.inputs.len());
|
self.expand_params(fun, &fun.inputs)
|
||||||
for (idx, param) in fun.inputs.iter().enumerate() {
|
}
|
||||||
let name = util::expand_input_name(idx, ¶m.name);
|
|
||||||
let ty = self.expand_input_param_type(fun, ¶m.name, ¶m.kind)?;
|
/// Expands to the `name : type` pairs of the function's outputs
|
||||||
args.push((name, ty));
|
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||||
}
|
self.expand_params(fun, &fun.outputs)
|
||||||
Ok(args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands to the return type of a function
|
/// Expands to the return type of a function
|
||||||
|
@ -607,16 +685,30 @@ fn expand_function_name(function: &Function, alias: Option<&MethodAlias>) -> Ide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Expands to the name of the call struct
|
/// Expands the name of a struct by a postfix
|
||||||
fn expand_call_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
fn expand_struct_name_postfix(
|
||||||
|
function: &Function,
|
||||||
|
alias: Option<&MethodAlias>,
|
||||||
|
postfix: &str,
|
||||||
|
) -> Ident {
|
||||||
let name = if let Some(alias) = alias {
|
let name = if let Some(alias) = alias {
|
||||||
format!("{}Call", alias.struct_name)
|
format!("{}{}", alias.struct_name, postfix)
|
||||||
} else {
|
} else {
|
||||||
format!("{}Call", util::safe_pascal_case(&function.name))
|
format!("{}{}", util::safe_pascal_case(&function.name), postfix)
|
||||||
};
|
};
|
||||||
util::ident(&name)
|
util::ident(&name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expands to the name of the call struct
|
||||||
|
fn expand_call_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
||||||
|
expand_struct_name_postfix(function, alias, "Call")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands to the name of the return struct
|
||||||
|
fn expand_return_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
||||||
|
expand_struct_name_postfix(function, alias, "Return")
|
||||||
|
}
|
||||||
|
|
||||||
/// Expands to the name of the call struct
|
/// Expands to the name of the call struct
|
||||||
fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
||||||
if let Some(alias) = alias {
|
if let Some(alias) = alias {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![cfg(feature = "abigen")]
|
#![cfg(feature = "abigen")]
|
||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
//! Test cases to validate the `abigen!` macro
|
//! Test cases to validate the `abigen!` macro
|
||||||
use ethers_contract::{abigen, EthCall, EthEvent};
|
use ethers_contract::{abigen, Abigen, EthCall, EthEvent};
|
||||||
use ethers_core::{
|
use ethers_core::{
|
||||||
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
||||||
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
||||||
|
@ -167,6 +167,34 @@ fn can_generate_internal_structs_multiple() {
|
||||||
let _ = contract.verify(vec![], proof, vk);
|
let _ = contract.verify(vec![], proof, vk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn can_gen_return_struct() {
|
||||||
|
abigen!(MultiInputOutput, "ethers-contract/tests/solidity-contracts/MultiInputOutput.json");
|
||||||
|
|
||||||
|
fn verify<T: AbiEncode + AbiDecode + Clone + std::fmt::Debug + std::cmp::PartialEq>(
|
||||||
|
binding: T,
|
||||||
|
) {
|
||||||
|
let encoded = binding.clone().encode();
|
||||||
|
let decoded = T::decode(&encoded).unwrap();
|
||||||
|
assert_eq!(binding, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
// just make sure they are accessible and work
|
||||||
|
|
||||||
|
let dupe = DupeIntReturn { out_one: 5.into(), out_two: 1234.into() };
|
||||||
|
verify(dupe);
|
||||||
|
|
||||||
|
let array =
|
||||||
|
ArrayRelayerReturn { outputs: vec![4.into(), 9.into(), 2.into()], some_number: 42.into() };
|
||||||
|
verify(array);
|
||||||
|
|
||||||
|
let single = SingleUnnamedReturn { 0: 4321.into() };
|
||||||
|
verify(single);
|
||||||
|
|
||||||
|
// doesnt exist:
|
||||||
|
// let nonexistant = CallWithoutReturnDataReturn;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn can_gen_human_readable_with_structs() {
|
fn can_gen_human_readable_with_structs() {
|
||||||
abigen!(
|
abigen!(
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
{"abi":[{"inputs":[{"internalType":"uint256[]","name":"inputs","type":"uint256[]"}],"name":"arrayRelayer","outputs":[{"internalType":"uint256[]","name":"outputs","type":"uint256[]"},{"internalType":"uint256","name":"someNumber","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"input","type":"uint256"}],"name":"callWithoutReturnData","outputs":[],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"input","type":"uint256"}],"name":"dupeInt","outputs":[{"internalType":"uint256","name":"outOne","type":"uint256"},{"internalType":"uint256","name":"outTwo","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"singleUnnamed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"}]}
|
|
@ -0,0 +1,24 @@
|
||||||
|
pragma solidity >=0.6.0;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
contract MultiInputOutput {
|
||||||
|
function dupeInt(uint256 input) public pure returns (uint256 outOne, uint256 outTwo) {
|
||||||
|
return (input, input);
|
||||||
|
}
|
||||||
|
function arrayRelayer(uint256[] memory inputs) public pure returns (uint256[] memory outputs, uint someNumber) {
|
||||||
|
outputs = new uint[](inputs.length);
|
||||||
|
for(uint256 i = 0; i < inputs.length; i++) {
|
||||||
|
outputs[i] = inputs[i];
|
||||||
|
}
|
||||||
|
someNumber = 42;
|
||||||
|
}
|
||||||
|
function singleUnnamed() public pure returns (uint) {
|
||||||
|
return 0x45;
|
||||||
|
}
|
||||||
|
function callWithoutReturnData(uint256 input) public pure {
|
||||||
|
// silence unused errors
|
||||||
|
uint nothing = input;
|
||||||
|
input = nothing;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue