2020-05-26 18:57:59 +00:00
|
|
|
use super::{types, util, Context};
|
2021-02-19 06:34:56 +00:00
|
|
|
use anyhow::{anyhow, Context as _, Result};
|
2021-08-02 16:24:22 +00:00
|
|
|
use ethers_core::abi::ParamType;
|
2020-05-31 16:01:34 +00:00
|
|
|
use ethers_core::{
|
2020-06-16 12:08:42 +00:00
|
|
|
abi::{Function, FunctionExt, Param, StateMutability},
|
2020-05-31 16:01:34 +00:00
|
|
|
types::Selector,
|
2020-05-28 09:04:12 +00:00
|
|
|
};
|
2020-05-26 18:57:59 +00:00
|
|
|
use inflector::Inflector;
|
|
|
|
use proc_macro2::{Literal, TokenStream};
|
|
|
|
use quote::quote;
|
2021-02-19 06:34:56 +00:00
|
|
|
use std::collections::BTreeMap;
|
2020-05-26 18:57:59 +00:00
|
|
|
use syn::Ident;
|
|
|
|
|
|
|
|
/// Expands a context into a method struct containing all the generated bindings
|
|
|
|
/// to the Solidity contract methods.
|
|
|
|
impl Context {
|
|
|
|
pub(crate) fn methods(&self) -> Result<TokenStream> {
|
|
|
|
let mut aliases = self.method_aliases.clone();
|
2021-02-19 06:34:56 +00:00
|
|
|
let sorted_functions: BTreeMap<_, _> = self.abi.functions.clone().into_iter().collect();
|
|
|
|
let functions = sorted_functions
|
|
|
|
.values()
|
|
|
|
.flatten()
|
2020-05-26 18:57:59 +00:00
|
|
|
.map(|function| {
|
|
|
|
let signature = function.abi_signature();
|
|
|
|
expand_function(function, aliases.remove(&signature))
|
|
|
|
.with_context(|| format!("error expanding function '{}'", signature))
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
|
|
|
|
Ok(quote! { #( #functions )* })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(unused)]
|
|
|
|
fn expand_function(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)?;
|
|
|
|
|
|
|
|
let outputs = expand_fn_outputs(&function.outputs)?;
|
|
|
|
|
2021-03-19 15:44:59 +00:00
|
|
|
let result = quote! { ethers_contract::builders::ContractCall<M, #outputs> };
|
2020-05-26 18:57:59 +00:00
|
|
|
|
|
|
|
let arg = expand_inputs_call_arg(&function.inputs);
|
2020-05-27 08:46:16 +00:00
|
|
|
let doc = util::expand_doc(&format!(
|
|
|
|
"Calls the contract's `{}` (0x{}) function",
|
|
|
|
function.name,
|
2020-12-31 17:19:14 +00:00
|
|
|
hex::encode(function.selector())
|
2020-05-27 08:46:16 +00:00
|
|
|
));
|
2020-05-26 18:57:59 +00:00
|
|
|
Ok(quote! {
|
|
|
|
|
|
|
|
#doc
|
|
|
|
pub fn #name(&self #input) -> #result {
|
|
|
|
self.0.method_hash(#selector, #arg)
|
|
|
|
.expect("method not found (this should never happen)")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// converts the function params to name/type pairs
|
|
|
|
pub(crate) fn expand_inputs(inputs: &[Param]) -> Result<TokenStream> {
|
|
|
|
let params = inputs
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.map(|(i, param)| {
|
|
|
|
let name = util::expand_input_name(i, ¶m.name);
|
|
|
|
let kind = types::expand(¶m.kind)?;
|
|
|
|
Ok(quote! { #name: #kind })
|
|
|
|
})
|
|
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
Ok(quote! { #( , #params )* })
|
|
|
|
}
|
|
|
|
|
|
|
|
// packs the argument in a tuple to be used for the contract call
|
|
|
|
pub(crate) fn expand_inputs_call_arg(inputs: &[Param]) -> TokenStream {
|
|
|
|
let names = inputs
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
2021-08-02 16:24:22 +00:00
|
|
|
.map(|(i, param)| {
|
|
|
|
let name = util::expand_input_name(i, ¶m.name);
|
|
|
|
match param.kind {
|
|
|
|
ParamType::Tuple(_) => {
|
|
|
|
// make sure the tuple gets converted to `Token::Tuple`
|
|
|
|
quote! {(#name,)}
|
|
|
|
}
|
|
|
|
_ => name,
|
|
|
|
}
|
|
|
|
})
|
2020-07-03 16:52:09 +00:00
|
|
|
.collect::<Vec<TokenStream>>();
|
|
|
|
match names.len() {
|
|
|
|
0 => quote! { () },
|
|
|
|
1 => quote! { #( #names )* },
|
|
|
|
_ => quote! { ( #(#names, )* ) },
|
|
|
|
}
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn expand_fn_outputs(outputs: &[Param]) -> Result<TokenStream> {
|
|
|
|
match outputs.len() {
|
|
|
|
0 => Ok(quote! { () }),
|
|
|
|
1 => types::expand(&outputs[0].kind),
|
|
|
|
_ => {
|
|
|
|
let types = outputs
|
|
|
|
.iter()
|
|
|
|
.map(|param| types::expand(¶m.kind))
|
|
|
|
.collect::<Result<Vec<_>>>()?;
|
|
|
|
Ok(quote! { (#( #types ),*) })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn expand_selector(selector: Selector) -> TokenStream {
|
|
|
|
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
|
|
|
|
quote! { [#( #bytes ),*] }
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
2020-05-31 16:01:34 +00:00
|
|
|
use ethers_core::abi::ParamType;
|
2020-05-26 18:57:59 +00:00
|
|
|
|
2020-07-03 16:52:09 +00:00
|
|
|
#[test]
|
|
|
|
fn test_expand_inputs_call_arg() {
|
|
|
|
// no inputs
|
|
|
|
let params = vec![];
|
|
|
|
let token_stream = expand_inputs_call_arg(¶ms);
|
2020-10-01 08:02:21 +00:00
|
|
|
assert_eq!(token_stream.to_string(), "()");
|
2020-07-03 16:52:09 +00:00
|
|
|
|
|
|
|
// single input
|
|
|
|
let params = vec![Param {
|
|
|
|
name: "arg_a".to_string(),
|
|
|
|
kind: ParamType::Address,
|
|
|
|
}];
|
|
|
|
let token_stream = expand_inputs_call_arg(¶ms);
|
|
|
|
assert_eq!(token_stream.to_string(), "arg_a");
|
|
|
|
|
|
|
|
// two inputs
|
|
|
|
let params = vec![
|
|
|
|
Param {
|
|
|
|
name: "arg_a".to_string(),
|
|
|
|
kind: ParamType::Address,
|
|
|
|
},
|
|
|
|
Param {
|
|
|
|
name: "arg_b".to_string(),
|
|
|
|
kind: ParamType::Uint(256usize),
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let token_stream = expand_inputs_call_arg(¶ms);
|
2020-10-01 08:02:21 +00:00
|
|
|
assert_eq!(token_stream.to_string(), "(arg_a , arg_b ,)");
|
2020-07-03 16:52:09 +00:00
|
|
|
|
|
|
|
// three inputs
|
|
|
|
let params = vec![
|
|
|
|
Param {
|
|
|
|
name: "arg_a".to_string(),
|
|
|
|
kind: ParamType::Address,
|
|
|
|
},
|
|
|
|
Param {
|
|
|
|
name: "arg_b".to_string(),
|
|
|
|
kind: ParamType::Uint(128usize),
|
|
|
|
},
|
|
|
|
Param {
|
|
|
|
name: "arg_c".to_string(),
|
|
|
|
kind: ParamType::Bool,
|
|
|
|
},
|
|
|
|
];
|
|
|
|
let token_stream = expand_inputs_call_arg(¶ms);
|
2020-10-01 08:02:21 +00:00
|
|
|
assert_eq!(token_stream.to_string(), "(arg_a , arg_b , arg_c ,)");
|
2020-07-03 16:52:09 +00:00
|
|
|
}
|
|
|
|
|
2020-05-26 18:57:59 +00:00
|
|
|
#[test]
|
|
|
|
fn expand_inputs_empty() {
|
|
|
|
assert_quote!(expand_inputs(&[]).unwrap().to_string(), {},);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expand_inputs_() {
|
|
|
|
assert_quote!(
|
|
|
|
expand_inputs(
|
|
|
|
|
|
|
|
&[
|
|
|
|
Param {
|
|
|
|
name: "a".to_string(),
|
|
|
|
kind: ParamType::Bool,
|
|
|
|
},
|
|
|
|
Param {
|
|
|
|
name: "b".to_string(),
|
|
|
|
kind: ParamType::Address,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
)
|
|
|
|
.unwrap(),
|
2021-03-19 15:44:59 +00:00
|
|
|
{ , a: bool, b: ethers_core::types::Address },
|
2020-05-26 18:57:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expand_fn_outputs_empty() {
|
2020-05-27 11:55:09 +00:00
|
|
|
assert_quote!(expand_fn_outputs(&[],).unwrap(), { () });
|
2020-05-26 18:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn expand_fn_outputs_single() {
|
|
|
|
assert_quote!(
|
|
|
|
expand_fn_outputs(&[Param {
|
|
|
|
name: "a".to_string(),
|
|
|
|
kind: ParamType::Bool,
|
|
|
|
}])
|
|
|
|
.unwrap(),
|
|
|
|
{ bool },
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-07-03 16:52:09 +00:00
|
|
|
fn expand_fn_outputs_multiple() {
|
2020-05-26 18:57:59 +00:00
|
|
|
assert_quote!(
|
|
|
|
expand_fn_outputs(&[
|
|
|
|
Param {
|
|
|
|
name: "a".to_string(),
|
|
|
|
kind: ParamType::Bool,
|
|
|
|
},
|
|
|
|
Param {
|
|
|
|
name: "b".to_string(),
|
|
|
|
kind: ParamType::Address,
|
|
|
|
},
|
|
|
|
],)
|
|
|
|
.unwrap(),
|
2021-03-19 15:44:59 +00:00
|
|
|
{ (bool, ethers_core::types::Address) },
|
2020-05-26 18:57:59 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|