Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: improve error on malformed abi #1347

Merged
merged 14 commits into from
May 4, 2024
2 changes: 1 addition & 1 deletion packages/fuels-code-gen/src/program_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ mod generated_code;
mod resolved_type;
mod utils;

pub use abigen::{Abigen, AbigenTarget, ProgramType};
pub use abigen::{Abi, Abigen, AbigenTarget, ProgramType};
27 changes: 7 additions & 20 deletions packages/fuels-code-gen/src/program_bindings/abigen.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::{collections::HashSet, path::PathBuf};

pub use abigen_target::{AbigenTarget, ProgramType};
pub use abigen_target::{Abi, AbigenTarget, ProgramType};
use fuel_abi_types::abi::full_program::FullTypeDeclaration;
use inflector::Inflector;
use itertools::Itertools;
Expand All @@ -11,8 +11,7 @@ use regex::Regex;
use crate::{
error::Result,
program_bindings::{
abigen::{abigen_target::ParsedAbigenTarget, bindings::generate_bindings},
custom_types::generate_types,
abigen::bindings::generate_bindings, custom_types::generate_types,
generated_code::GeneratedCode,
},
utils::ident,
Expand All @@ -35,9 +34,7 @@ impl Abigen {
/// for, and of what nature (Contract, Script or Predicate).
/// * `no_std`: don't use the Rust std library.
pub fn generate(targets: Vec<AbigenTarget>, no_std: bool) -> Result<TokenStream> {
let parsed_targets = Self::parse_targets(targets)?;

let generated_code = Self::generate_code(no_std, parsed_targets)?;
let generated_code = Self::generate_code(no_std, targets)?;

let use_statements = generated_code.use_statements_for_uniquely_named_types();

Expand Down Expand Up @@ -68,10 +65,7 @@ impl Abigen {
.expect("Wasm hotfix failed!")
}

fn generate_code(
no_std: bool,
parsed_targets: Vec<ParsedAbigenTarget>,
) -> Result<GeneratedCode> {
fn generate_code(no_std: bool, parsed_targets: Vec<AbigenTarget>) -> Result<GeneratedCode> {
let custom_types = Self::filter_custom_types(&parsed_targets);
let shared_types = Self::filter_shared_types(custom_types);

Expand All @@ -83,7 +77,7 @@ impl Abigen {
}

fn generate_all_bindings(
parsed_targets: Vec<ParsedAbigenTarget>,
parsed_targets: Vec<AbigenTarget>,
hal3e marked this conversation as resolved.
Show resolved Hide resolved
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
Expand All @@ -96,7 +90,7 @@ impl Abigen {
}

fn generate_binding(
target: ParsedAbigenTarget,
target: AbigenTarget,
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
Expand Down Expand Up @@ -128,13 +122,6 @@ impl Abigen {
GeneratedCode::new(code, Default::default(), no_std)
}

fn parse_targets(targets: Vec<AbigenTarget>) -> Result<Vec<ParsedAbigenTarget>> {
targets
.into_iter()
.map(|target| target.try_into())
.collect()
}

fn generate_shared_types(
shared_types: HashSet<FullTypeDeclaration>,
no_std: bool,
Expand All @@ -150,7 +137,7 @@ impl Abigen {
}

fn filter_custom_types(
all_types: &[ParsedAbigenTarget],
all_types: &[AbigenTarget],
) -> impl Iterator<Item = &FullTypeDeclaration> {
all_types
.iter()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,49 +3,19 @@ use std::{convert::TryFrom, path::PathBuf, str::FromStr};
use fuel_abi_types::abi::full_program::FullProgramABI;
use proc_macro2::Ident;

use crate::{
error,
error::{Error, Result},
utils::Source,
};
use crate::{error, error::Error};

#[derive(Debug, Clone)]
pub struct AbigenTarget {
pub name: String,
pub abi: String,
pub program_type: ProgramType,
}

pub(crate) struct Abi {
pub(crate) path: Option<PathBuf>,
pub(crate) abi: FullProgramABI,
}

pub(crate) struct ParsedAbigenTarget {
pub name: String,
pub source: Abi,
pub program_type: ProgramType,
}

impl TryFrom<AbigenTarget> for ParsedAbigenTarget {
type Error = Error;

fn try_from(value: AbigenTarget) -> Result<Self> {
Ok(Self {
name: value.name,
source: parse_program_abi(&value.abi)?,
program_type: value.program_type,
})
}
}

fn parse_program_abi(abi_source: &str) -> Result<Abi> {
let source = Source::parse(abi_source).expect("failed to parse JSON ABI");

let json_abi_str = source.get().expect("failed to parse JSON ABI from string");
let abi = FullProgramABI::from_json_abi(&json_abi_str)?;
let path = source.path();
Ok(Abi { path, abi })
#[derive(Debug, Clone)]
pub struct Abi {
pub path: Option<PathBuf>,
pub abi: FullProgramABI,
hal3e marked this conversation as resolved.
Show resolved Hide resolved
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
error::Result,
program_bindings::{
abigen::{
abigen_target::ParsedAbigenTarget,
abigen_target::AbigenTarget,
bindings::{
contract::contract_bindings, predicate::predicate_bindings, script::script_bindings,
},
Expand All @@ -19,7 +19,7 @@ mod predicate;
mod script;
mod utils;

pub(crate) fn generate_bindings(target: ParsedAbigenTarget, no_std: bool) -> Result<GeneratedCode> {
pub(crate) fn generate_bindings(target: AbigenTarget, no_std: bool) -> Result<GeneratedCode> {
let bindings_generator = match target.program_type {
ProgramType::Script => script_bindings,
ProgramType::Contract => contract_bindings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub(crate) fn extract_main_fn(abi: &[FullABIFunction]) -> Result<&FullABIFunctio
.map(|candidate| candidate.name())
.collect::<Vec<_>>();
Err(error!(
"ABI must have one and only one function with the name 'main'. Got: {fn_names:?}"
"`abi` must have only one function with the name 'main'. Got: {fn_names:?}"
))
}
}
Expand Down Expand Up @@ -45,7 +45,7 @@ mod tests {

assert_eq!(
err.to_string(),
r#"ABI must have one and only one function with the name 'main'. Got: ["main", "another", "main"]"#
r#"`abi` must have only one function with the name 'main'. Got: ["main", "another", "main"]"#
);
}

Expand Down
15 changes: 15 additions & 0 deletions packages/fuels-code-gen/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,18 @@ pub use fuel_abi_types::utils::{ident, safe_ident, TypePath};
pub use source::Source;

mod source;

use fuel_abi_types::abi::full_program::FullProgramABI;

use crate::{error, error::Result, Abi};

pub fn parse_program_abi(abi: &str) -> Result<Abi> {
let source = Source::parse(abi)?;

let json_abi_str = source.get()?;
let abi = FullProgramABI::from_json_abi(&json_abi_str)
.map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e))?;
let path = source.path();

Ok(Abi { path, abi })
}
35 changes: 23 additions & 12 deletions packages/fuels-code-gen/src/utils/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,18 @@ impl Source {
if source.starts_with('{') || source.starts_with('[') || source.starts_with('\n') {
return Ok(Source::String(source.to_owned()));
}
let root = env::current_dir()?.canonicalize()?;

let current_dir = env::current_dir()
.map_err(|e| error!("unable to get current directory: ").combine(e))?;

let root = current_dir.canonicalize().map_err(|e| {
error!(
"unable to canonicalize current directory {}: ",
current_dir.display()
)
.combine(e)
})?;

Ok(Source::with_root(root, source))
}

Expand All @@ -62,12 +73,11 @@ impl Source {
Source::Local(path.as_ref().into())
}

/// Retrieves the source JSON of the artifact this will either read the JSON
/// from the file system or retrieve a contract ABI from the network
/// depending on the source type.
/// Retrieves the source JSON of the artifact. Either read the JSON
/// from the file system or the provided `String`.
pub fn get(&self) -> Result<String> {
match self {
Source::Local(path) => get_local_contract(path),
Source::Local(path) => read_abi(path),
Source::String(abi) => Ok(abi.clone()),
}
}
Expand All @@ -80,17 +90,17 @@ impl Source {
}
}

fn get_local_contract(path: &Path) -> Result<String> {
fn read_abi(path: &Path) -> Result<String> {
let path = if path.is_relative() {
let absolute_path = path.canonicalize().map_err(|e| {
error!(
"unable to canonicalize file from working dir {} with path {}",
"unable to canonicalize file from working dir {} with path {}: {}",
env::current_dir()
.map(|cwd| cwd.display().to_string())
.unwrap_or_else(|err| format!("??? ({err})")),
path.display()
path.display(),
e
)
.combine(e)
})?;
Cow::Owned(absolute_path)
} else {
Expand All @@ -99,11 +109,12 @@ fn get_local_contract(path: &Path) -> Result<String> {

let json = fs::read_to_string(&path).map_err(|e| {
error!(
"failed to read artifact JSON file with path {}",
path.display()
"failed to read `abi` file with path {}: {}",
path.display(),
e
)
.combine(e)
})?;

Ok(json)
}

Expand Down
15 changes: 9 additions & 6 deletions packages/fuels-macros/src/abigen/parsing.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use fuels_code_gen::{AbigenTarget, ProgramType};
use fuels_code_gen::{utils::parse_program_abi, Abi, AbigenTarget, ProgramType};
use syn::{
parse::{Parse, ParseStream},
Result,
Expand All @@ -16,7 +16,7 @@ impl From<MacroAbigenTarget> for AbigenTarget {
fn from(macro_target: MacroAbigenTarget) -> Self {
AbigenTarget {
name: macro_target.name,
abi: macro_target.abi,
source: macro_target.source,
program_type: macro_target.program_type,
}
}
Expand All @@ -27,8 +27,8 @@ impl From<MacroAbigenTarget> for AbigenTarget {
#[derive(Debug)]
pub(crate) struct MacroAbigenTarget {
pub(crate) name: String,
pub(crate) abi: String,
pub(crate) program_type: ProgramType,
pub(crate) source: Abi,
pub program_type: ProgramType,
}

pub(crate) struct MacroAbigenTargets {
Expand All @@ -54,11 +54,14 @@ impl MacroAbigenTarget {
name_values.validate_has_no_other_names(&["name", "abi"])?;

let name = name_values.get_as_lit_str("name")?.value();
let abi = name_values.get_as_lit_str("abi")?.value();
let abi_lit_str = name_values.get_as_lit_str("abi")?;

let source = parse_program_abi(&abi_lit_str.value())
.map_err(|e| syn::Error::new(abi_lit_str.span(), e.to_string()))?;

Ok(Self {
name,
abi,
source,
program_type,
})
}
Expand Down
8 changes: 6 additions & 2 deletions packages/fuels-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,18 @@ mod setup_program_test;
pub fn abigen(input: TokenStream) -> TokenStream {
let targets = parse_macro_input!(input as MacroAbigenTargets);

Abigen::generate(targets.into(), false).unwrap().into()
Abigen::generate(targets.into(), false)
.expect("abigen generation failed")
.into()
}

#[proc_macro]
pub fn wasm_abigen(input: TokenStream) -> TokenStream {
let targets = parse_macro_input!(input as MacroAbigenTargets);

Abigen::generate(targets.into(), true).unwrap().into()
Abigen::generate(targets.into(), true)
.expect("abigen generation failed")
.into()
}

/// Used to reduce boilerplate in integration tests.
Expand Down