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};
29 changes: 8 additions & 21 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,11 +77,11 @@ impl Abigen {
}

fn generate_all_bindings(
parsed_targets: Vec<ParsedAbigenTarget>,
targets: Vec<AbigenTarget>,
no_std: bool,
shared_types: &HashSet<FullTypeDeclaration>,
) -> Result<GeneratedCode> {
parsed_targets
targets
.into_iter()
.map(|target| Self::generate_binding(target, no_std, shared_types))
.fold_ok(GeneratedCode::default(), |acc, generated_code| {
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
126 changes: 98 additions & 28 deletions packages/fuels-code-gen/src/program_bindings/abigen/abigen_target.rs
Original file line number Diff line number Diff line change
@@ -1,51 +1,121 @@
use std::{convert::TryFrom, path::PathBuf, str::FromStr};
use std::{
convert::TryFrom,
env, fs,
path::{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, Result};

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

impl AbigenTarget {
pub fn new(name: String, source: Abi, program_type: ProgramType) -> Self {
Self {
name,
source,
program_type,
}
}

pub fn name(&self) -> &str {
&self.name
}

pub fn source(&self) -> &Abi {
&self.source
}

pub fn program_type(&self) -> ProgramType {
self.program_type
}
}

pub(crate) struct Abi {
#[derive(Debug, Clone)]
pub 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 Abi {
pub fn load_from(path: impl AsRef<Path>) -> Result<Abi> {
let path = Self::canonicalize_path(path.as_ref())?;

impl TryFrom<AbigenTarget> for ParsedAbigenTarget {
type Error = Error;
let json_abi = fs::read_to_string(&path).map_err(|e| {
error!(
"failed to read `abi` file with path {}: {}",
path.display(),
e
)
})?;
let abi = Self::parse_from_json(&json_abi)?;

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

fn canonicalize_path(path: &Path) -> Result<PathBuf> {
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)
})?;

let path = root.join(path);

if path.is_relative() {
path.canonicalize().map_err(|e| {
error!(
"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(),
e
)
})
} else {
Ok(path)
}
}

fn parse_from_json(json_abi: &str) -> Result<FullProgramABI> {
FullProgramABI::from_json_abi(json_abi)
.map_err(|e| error!("malformed `abi`. Did you use `forc` to create it?: ").combine(e))
}

pub fn path(&self) -> Option<&PathBuf> {
self.path.as_ref()
}

pub fn abi(&self) -> &FullProgramABI {
&self.abi
}
}

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

fn from_str(json_abi: &str) -> Result<Self> {
let abi = Abi::parse_from_json(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 })
Ok(Abi { path: None, abi })
}
}

#[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
3 changes: 0 additions & 3 deletions packages/fuels-code-gen/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
pub use fuel_abi_types::utils::{ident, safe_ident, TypePath};
pub use source::Source;

mod source;
Loading
Loading