use crate::code_gen::custom_types_gen::extract_custom_type_name_from_abi_property;
use crate::code_gen::docs_gen::expand_doc;
use crate::types::expand_type;
use crate::utils::{first_four_bytes_of_sha256_hash, ident, safe_ident};
use crate::{ParamType, Selector};
use fuels_types::errors::Error;
use fuels_types::function_selector::build_fn_selector;
use fuels_types::{
ABIFunction, CustomType, TypeApplication, TypeDeclaration, ENUM_KEYWORD, STRUCT_KEYWORD,
};
use inflector::Inflector;
use proc_macro2::{Literal, TokenStream};
use quote::quote;
use regex::Regex;
use std::collections::HashMap;
pub fn expand_function(
function: &ABIFunction,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<TokenStream, Error> {
if function.name.is_empty() {
return Err(Error::InvalidData("Function name can not be empty".into()));
}
let fn_param_types = function
.inputs
.iter()
.map(|t| types.get(&t.type_field).unwrap().clone())
.collect::<Vec<TypeDeclaration>>();
let name = safe_ident(&function.name);
let fn_signature = build_fn_selector(&function.name, &fn_param_types, types)?;
let encoded = first_four_bytes_of_sha256_hash(&fn_signature);
let tokenized_signature = expand_selector(encoded);
let tokenized_output = expand_fn_output(&function.output, types)?;
let result = quote! { ContractCallHandler<#tokenized_output> };
let (input, arg) = expand_function_arguments(function, types)?;
let doc = expand_doc(&format!(
"Calls the contract's `{}` (0x{}) function",
function.name,
hex::encode(encoded)
));
let t = types
.get(&function.output.type_field)
.expect("couldn't find type");
let param_type = ParamType::from_type_declaration(t, types)?;
let tok: proc_macro2::TokenStream = format!("Some(ParamType::{})", param_type).parse().unwrap();
let output_param = tok;
Ok(quote! {
#doc
pub fn #name(&self #input) -> #result {
Contract::method_hash(&self.wallet.get_provider().expect("Provider not set up"), self.contract_id.clone(), &self.wallet,
#tokenized_signature, #output_param, #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_output(
output: &TypeApplication,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<TokenStream, Error> {
let output_type = types.get(&output.type_field).expect("couldn't find type");
if !output_type.is_custom_type(types) {
return expand_type(&ParamType::from_type_declaration(output_type, types)?);
}
match output_type.is_struct_type() {
true => {
let parsed_custom_type_name = extract_custom_type_name_from_abi_property(
output_type,
Some(CustomType::Struct),
types,
)?
.parse()
.expect("Custom type name should be a valid Rust identifier");
Ok(parsed_custom_type_name)
}
false => match output_type.is_enum_type() {
true => {
let parsed_custom_type_name = extract_custom_type_name_from_abi_property(
output_type,
Some(CustomType::Enum),
types,
)?
.parse()
.expect("Custom type name should be a valid Rust identifier");
Ok(parsed_custom_type_name)
}
false => match output_type.has_custom_type_in_array(types) {
true => {
let type_inside_array = types
.get(
&output_type
.components
.as_ref()
.expect("array should have components")[0]
.type_field,
)
.expect("couldn't find type");
let parsed_custom_type_name: TokenStream =
extract_custom_type_name_from_abi_property(
type_inside_array,
Some(
type_inside_array
.get_custom_type()
.expect("Custom type in array should be set"),
),
types,
)?
.parse()
.expect("couldn't parse custom type name");
Ok(quote! { ::std::vec::Vec<#parsed_custom_type_name> })
}
false => expand_tuple_w_custom_types(output_type, types),
},
},
}
}
fn expand_tuple_w_custom_types(
output: &TypeDeclaration,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<TokenStream, Error> {
if !output.has_custom_type_in_tuple(types) {
panic!("Output is of custom type, but not an enum, struct or enum/struct inside an array/tuple. This should never happen. Output received: {:?}", output);
}
let mut final_signature: String = "(".into();
let mut type_strings: Vec<String> = vec![];
for c in output
.components
.as_ref()
.expect("tuples should have components")
.iter()
{
let type_string = types.get(&c.type_field).unwrap().type_field.clone();
let keywords_removed = remove_words(&type_string, &[STRUCT_KEYWORD, ENUM_KEYWORD]);
let tuple_type_signature = expand_b256_into_array_form(&keywords_removed)
.parse()
.expect("could not parse tuple type signature");
type_strings.push(tuple_type_signature);
}
final_signature.push_str(&type_strings.join(", "));
final_signature.push(')');
Ok(final_signature.parse().unwrap())
}
fn expand_b256_into_array_form(type_field: &str) -> String {
let re = Regex::new(r"\bb256\b").unwrap();
re.replace_all(type_field, "[u8; 32]").to_string()
}
fn remove_words(from: &str, words: &[&str]) -> String {
words
.iter()
.fold(from.to_string(), |str_in_construction, word| {
str_in_construction.replace(word, "")
})
}
fn expand_function_arguments(
fun: &ABIFunction,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<(TokenStream, TokenStream), Error> {
let mut args = vec![];
let mut call_args = vec![];
for fn_type_application in &fun.inputs {
let name = expand_input_name(&fn_type_application.name)?;
let param = types
.get(&fn_type_application.type_field)
.expect("couldn't find type");
let kind = ParamType::from_type_declaration(param, types)?;
let tok = if let ParamType::Tuple(_tuple) = &kind {
let toks = build_expanded_tuple_params(param, types)
.expect("failed to build expanded tuple parameters");
toks.parse::<TokenStream>().unwrap()
} else {
expand_input_param(
fun,
fn_type_application,
&ParamType::from_type_declaration(param, types)?,
types,
)?
};
args.push(quote! { #name: #tok });
if let ParamType::String(len) = &kind {
call_args.push(quote! {Token::String(StringToken::new(#name, #len))});
} else {
call_args.push(name);
}
}
let args = quote! { #( , #args )* };
let call_args = quote! { &[ #(#call_args.into_token(), )* ] };
Ok((args, call_args))
}
fn build_expanded_tuple_params(
tuple_param: &TypeDeclaration,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<String, Error> {
let mut toks: String = "(".to_string();
for type_application in tuple_param
.components
.as_ref()
.expect("tuple parameter should have components")
{
let component = types
.get(&type_application.type_field)
.expect("couldn't find type");
if !component.is_custom_type(types) {
let p = ParamType::from_type_declaration(component, types)?;
let tok = expand_type(&p)?;
toks.push_str(&tok.to_string());
} else {
let tok = component
.type_field
.replace(STRUCT_KEYWORD, "")
.replace(ENUM_KEYWORD, "");
toks.push_str(&tok.to_string());
}
toks.push(',');
}
toks.push(')');
Ok(toks)
}
pub fn expand_input_name(name: &str) -> Result<TokenStream, Error> {
if name.is_empty() {
return Err(Error::InvalidData(
"Function arguments can not have empty names".into(),
));
}
let name = safe_ident(&name.to_snake_case());
Ok(quote! { #name })
}
fn expand_input_param(
fun: &ABIFunction,
type_application: &TypeApplication,
kind: &ParamType,
types: &HashMap<usize, TypeDeclaration>,
) -> Result<TokenStream, Error> {
match kind {
ParamType::Array(ty, _) => {
let ty = expand_input_param(fun, type_application, ty, types)?;
Ok(quote! {
::std::vec::Vec<#ty>
})
}
ParamType::Enum(_) => {
let t = types
.get(&type_application.type_field)
.expect("type not found");
let ident = ident(&extract_custom_type_name_from_abi_property(
t,
Some(CustomType::Enum),
types,
)?);
Ok(quote! { #ident })
}
ParamType::Struct(_) => {
let t = types
.get(&type_application.type_field)
.expect("type not found");
let ident = ident(&extract_custom_type_name_from_abi_property(
t,
Some(CustomType::Struct),
types,
)?);
Ok(quote! { #ident })
}
_ => expand_type(kind),
}
}
#[cfg(test)]
mod tests {
use super::*;
use fuels_types::ProgramABI;
use std::str::FromStr;
#[test]
fn test_expand_function_simpleabi() -> Result<(), Error> {
let s = r#"
{
"types": [
{
"typeId": 6,
"type": "u64",
"components": null,
"typeParameters": null
},
{
"typeId": 8,
"type": "b256",
"components": null,
"typeParameters": null
},
{
"typeId": 6,
"type": "u64",
"components": null,
"typeParameters": null
},
{
"typeId": 8,
"type": "b256",
"components": null,
"typeParameters": null
},
{
"typeId": 10,
"type": "bool",
"components": null,
"typeParameters": null
},
{
"typeId": 12,
"type": "struct MyStruct1",
"components": [
{
"name": "x",
"type": 6,
"typeArguments": null
},
{
"name": "y",
"type": 8,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 6,
"type": "u64",
"components": null,
"typeParameters": null
},
{
"typeId": 8,
"type": "b256",
"components": null,
"typeParameters": null
},
{
"typeId": 2,
"type": "struct MyStruct1",
"components": [
{
"name": "x",
"type": 6,
"typeArguments": null
},
{
"name": "y",
"type": 8,
"typeArguments": null
}
],
"typeParameters": null
},
{
"typeId": 3,
"type": "struct MyStruct2",
"components": [
{
"name": "x",
"type": 10,
"typeArguments": null
},
{
"name": "y",
"type": 12,
"typeArguments": []
}
],
"typeParameters": null
},
{
"typeId": 26,
"type": "struct MyStruct1",
"components": [
{
"name": "x",
"type": 6,
"typeArguments": null
},
{
"name": "y",
"type": 8,
"typeArguments": null
}
],
"typeParameters": null
}
],
"functions": [
{
"type": "function",
"inputs": [
{
"name": "s1",
"type": 2,
"typeArguments": []
},
{
"name": "s2",
"type": 3,
"typeArguments": []
}
],
"name": "some_abi_funct",
"output": {
"name": "",
"type": 26,
"typeArguments": []
}
}
]
}
"#;
let parsed_abi: ProgramABI = serde_json::from_str(s)?;
let all_types = parsed_abi
.types
.into_iter()
.map(|t| (t.type_id, t))
.collect::<HashMap<usize, TypeDeclaration>>();
let result = expand_function(&parsed_abi.functions[0], &all_types);
let expected = TokenStream::from_str(
r#"
#[doc = "Calls the contract's `some_abi_funct` (0x00000000652399f3) function"]
pub fn some_abi_funct(&self, s_1: MyStruct1, s_2: MyStruct2) -> ContractCallHandler<MyStruct1> {
Contract::method_hash(
&self.wallet.get_provider().expect("Provider not set up"),
self.contract_id.clone(),
&self.wallet,
[0, 0, 0, 0, 101 , 35 , 153 , 243],
Some(ParamType::Struct(vec![ParamType::U64, ParamType::B256])),
&[s_1.into_token(), s_2.into_token(),]
)
.expect("method not found (this should never happen)")
}
"#,
);
let expected = expected?.to_string();
assert_eq!(result?.to_string(), expected);
Ok(())
}
#[test]
fn test_expand_selector() {
let result = expand_selector(Selector::default());
assert_eq!(result.to_string(), "[0 , 0 , 0 , 0 , 0 , 0 , 0 , 0]");
let result = expand_selector([1, 2, 3, 4, 5, 6, 7, 8]);
assert_eq!(result.to_string(), "[1 , 2 , 3 , 4 , 5 , 6 , 7 , 8]");
}
#[test]
fn transform_name_to_snake_case() -> Result<(), Error> {
let result = expand_input_name("CamelCaseHello");
assert_eq!(result?.to_string(), "camel_case_hello");
Ok(())
}
#[test]
fn avoids_collisions_with_keywords() -> Result<(), Error> {
let result = expand_input_name("if");
assert_eq!(result?.to_string(), "if_");
let result = expand_input_name("let");
assert_eq!(result?.to_string(), "let_");
Ok(())
}
#[test]
fn will_not_replace_b256_in_middle_of_word() {
let result = expand_b256_into_array_form("(b256, Someb256WeirdStructName, b256, b256)");
assert_eq!(
result,
"([u8; 32], Someb256WeirdStructName, [u8; 32], [u8; 32])"
);
}
}