use crate::abi_encoder::ABIEncoder;
use crate::code_gen::custom_types_gen::extract_struct_name_from_abi_property;
use crate::code_gen::docs_gen::expand_doc;
use crate::errors::Error;
use crate::json_abi::{parse_param, ABIParser};
use crate::types::expand_type;
use crate::utils::{ident, safe_ident};
use fuels_core::{ParamType, Selector};
use inflector::Inflector;
use sway_types::{Function, Property};
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use std::collections::HashMap;
pub fn expand_function(
function: &Function,
abi_parser: &ABIParser,
custom_enums: &HashMap<String, Property>,
custom_structs: &HashMap<String, Property>,
) -> Result<TokenStream, Error> {
let name = safe_ident(&function.name);
let fn_signature = abi_parser.build_fn_selector(&function.name, &function.inputs);
let encoded = ABIEncoder::encode_function_selector(fn_signature?.as_bytes());
let tokenized_signature = expand_selector(encoded);
let tokenized_output = expand_fn_outputs(&function.outputs)?;
let result = quote! { ContractCall<#tokenized_output> };
let (input, arg) = expand_function_arguments(function, custom_enums, custom_structs)?;
let doc = expand_doc(&format!(
"Calls the contract's `{}` (0x{}) function",
function.name,
hex::encode(encoded)
));
let mut output_params = vec![];
for output in &function.outputs {
let mut param_type_str: String = "ParamType::".to_owned();
let p = parse_param(output).unwrap();
param_type_str.push_str(&p.to_string());
let tok: proc_macro2::TokenStream = param_type_str.parse().unwrap();
output_params.push(tok);
}
let output_params_token = quote! { &[#( #output_params ),*] };
Ok(quote! {
#doc
pub fn #name(&self #input) -> #result {
Contract::method_hash(&self.fuel_client, &self.compiled,
#tokenized_signature, #output_params_token, #arg).expect("method not found (this should never happen)")
}
})
}
fn expand_selector(selector: Selector) -> TokenStream {
let bytes = selector.iter().copied().map(Literal::u8_unsuffixed);
quote! { [#( #bytes ),*] }
}
fn expand_fn_outputs(outputs: &[Property]) -> Result<TokenStream, Error> {
match outputs.len() {
0 => Ok(quote! { () }),
1 => {
if outputs[0].type_field.contains("struct ") {
let tok: proc_macro2::TokenStream =
extract_struct_name_from_abi_property(&outputs[0])
.parse()
.unwrap();
Ok(tok)
} else {
expand_type(&parse_param(&outputs[0])?)
}
}
_ => {
let types = outputs
.iter()
.map(|param| expand_type(&parse_param(param)?))
.collect::<Result<Vec<_>, Error>>()?;
Ok(quote! { (#( #types ),*) })
}
}
}
fn expand_function_arguments(
fun: &Function,
custom_enums: &HashMap<String, Property>,
custom_structs: &HashMap<String, Property>,
) -> Result<(TokenStream, TokenStream), Error> {
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() {
if param.name == "gas_" || param.name == "amount_" || param.name == "color_" {
continue;
}
let name = expand_input_name(i, ¶m.name);
let rust_enum_name = custom_enums.get(¶m.name);
let rust_struct_name = custom_structs.get(¶m.name);
let ty = expand_input_param(
fun,
¶m.name,
&parse_param(param)?,
&rust_enum_name,
&rust_struct_name,
)?;
args.push(quote! { #name: #ty });
call_args.push(name);
}
let args = quote! { #( , #args )* };
let call_args = match call_args.len() {
0 => quote! { () },
_ => quote! { &[ #(#call_args.into_token(), )* ] },
};
Ok((args, call_args))
}
pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
let name_str = match name {
"" => format!("p{}", index),
n => n.to_snake_case(),
};
let name = safe_ident(&name_str);
quote! { #name }
}
fn expand_input_param(
fun: &Function,
param: &str,
kind: &ParamType,
rust_enum_name: &Option<&Property>,
rust_struct_name: &Option<&Property>,
) -> Result<TokenStream, Error> {
match kind {
ParamType::Array(ty, _) => {
let ty = expand_input_param(fun, param, ty, rust_enum_name, rust_struct_name)?;
Ok(quote! {
::std::vec::Vec<#ty>
})
}
ParamType::Enum(_) => {
let ident = ident(
&extract_struct_name_from_abi_property(rust_enum_name.unwrap()).to_class_case(),
);
Ok(quote! { #ident })
}
ParamType::Struct(_) => {
let ident = ident(
&extract_struct_name_from_abi_property(rust_struct_name.unwrap()).to_class_case(),
);
Ok(quote! { #ident })
}
_ => expand_type(kind),
}
}