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<_>>>()?;
|
||||
|
||||
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
|
||||
|
@ -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> {
|
||||
let mut struct_defs = 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
|
||||
fn expand_calls_enum_name(&self) -> 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
|
||||
fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||
let mut args = Vec::with_capacity(fun.inputs.len());
|
||||
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)?;
|
||||
args.push((name, ty));
|
||||
}
|
||||
Ok(args)
|
||||
self.expand_params(fun, &fun.inputs)
|
||||
}
|
||||
|
||||
/// Expands to the `name : type` pairs of the function's outputs
|
||||
fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
|
||||
self.expand_params(fun, &fun.outputs)
|
||||
}
|
||||
|
||||
/// 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
|
||||
fn expand_call_struct_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
||||
/// Expands the name of a struct by a postfix
|
||||
fn expand_struct_name_postfix(
|
||||
function: &Function,
|
||||
alias: Option<&MethodAlias>,
|
||||
postfix: &str,
|
||||
) -> Ident {
|
||||
let name = if let Some(alias) = alias {
|
||||
format!("{}Call", alias.struct_name)
|
||||
format!("{}{}", alias.struct_name, postfix)
|
||||
} else {
|
||||
format!("{}Call", util::safe_pascal_case(&function.name))
|
||||
format!("{}{}", util::safe_pascal_case(&function.name), postfix)
|
||||
};
|
||||
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
|
||||
fn expand_call_struct_variant_name(function: &Function, alias: Option<&MethodAlias>) -> Ident {
|
||||
if let Some(alias) = alias {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#![cfg(feature = "abigen")]
|
||||
#![allow(unused)]
|
||||
//! Test cases to validate the `abigen!` macro
|
||||
use ethers_contract::{abigen, EthCall, EthEvent};
|
||||
use ethers_contract::{abigen, Abigen, EthCall, EthEvent};
|
||||
use ethers_core::{
|
||||
abi::{AbiDecode, AbiEncode, Address, Tokenizable},
|
||||
types::{transaction::eip2718::TypedTransaction, Eip1559TransactionRequest, U256},
|
||||
|
@ -167,6 +167,34 @@ fn can_generate_internal_structs_multiple() {
|
|||
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]
|
||||
fn can_gen_human_readable_with_structs() {
|
||||
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