fuels_rs/code_gen/functions_gen.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225
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;
/// Functions used by the Abigen to expand functions defined in an ABI spec.
// TODO: Right now we have an "end-to-end" test suite for the Abigen!
// under `fuels-abigen/tests/harness.rs`. But it would be nice to have
// tests at the level of this component.
/// Transforms a function defined in [`Function`] into a [`TokenStream`]
/// that represents that same function signature as a Rust-native function
/// declaration.
/// The actual logic inside the function is the function `method_hash` under
/// [`Contract`], which is responsible for encoding the function selector
/// and the function parameters that will be used in the actual contract call.
///
/// [`Contract`]: crate::contract::Contract
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)
));
// Here we turn `ParamType`s into a custom stringified version that's identical
// to how we would declare a `ParamType` in Rust code. Which will then
// be used to be tokenized and passed onto `method_hash()`.
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 ),*] }
}
/// Expands the output of a function, i.e. what comes after `->` in a function
/// signature.
fn expand_fn_outputs(outputs: &[Property]) -> Result<TokenStream, Error> {
match outputs.len() {
0 => Ok(quote! { () }),
1 => {
// If it's a struct as the type of a function's output, use its
// tokenized name only. Otherwise, parse and expand.
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 ),*) })
}
}
}
/// Expands the arguments in a function declaration and the same arguments as input
/// to a function call. For instance:
/// 1. The `my_arg: u32` in `pub fn my_func(my_arg: u32) -> ()`
/// 2. The `my_arg.into_token()` in `another_fn_call(my_arg.into_token())`
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 each [`Property`] in a function input we expand:
// 1. The name of the argument;
// 2. The type of the argument;
for (i, param) in fun.inputs.iter().enumerate() {
// This is a (likely) temporary workaround the fact that
// Sway ABI functions require gas, coin amount, and color arguments
// pre-pending the user-defined function arguments.
// Since these values (gas, coin, color) are configured elsewhere when
// creating a contract instance in the SDK, it would be noisy to keep them
// in the signature of the function that we're expanding here.
// It's the difference between being forced to write:
// contract_instance.increment_counter($gas, $coin, $color, 42)
// versus simply writing:
// contract_instance.increment_counter(42)
// Note that _any_ significant change in the way the JSON ABI is generated
// could affect this function expansion.
if param.name == "gas_" || param.name == "amount_" || param.name == "color_" {
continue;
}
// TokenStream representing the name of the argument
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);
// TokenStream representing the type of the argument
let ty = expand_input_param(
fun,
¶m.name,
&parse_param(param)?,
&rust_enum_name,
&rust_struct_name,
)?;
// Add the TokenStream to argument declarations
args.push(quote! { #name: #ty });
// This `name` TokenStream is also added to the call arguments
call_args.push(name);
}
// The final TokenStream of the argument declaration in a function declaration
let args = quote! { #( , #args )* };
// The final TokenStream of the arguments being passed in a function call
// It'll look like `&[my_arg.into_token(), another_arg.into_token()]`
// as the [`Contract`] `method_hash` function expects a slice of Tokens
// in order to encode the call.
let call_args = match call_args.len() {
0 => quote! { () },
_ => quote! { &[ #(#call_args.into_token(), )* ] },
};
Ok((args, call_args))
}
/// Expands a positional identifier string that may be empty.
///
/// Note that this expands the parameter name with `safe_ident`, meaning that
/// identifiers that are reserved keywords get `_` appended to them.
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 }
}
// Expands the type of an argument being passed in a function declaration.
// I.e.: `pub fn my_func(my_arg: u32) -> ()`, in this case, `u32` is the
// type, coming in as a `ParamType::U32`.
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 })
}
// Primitive type
_ => expand_type(kind),
}
}