Skip to content

Commit

Permalink
feat(abi): throw errors rather than returning string from `noirc_abi_…
Browse files Browse the repository at this point in the history
…wasm`
  • Loading branch information
TomAFrench committed Sep 25, 2023
1 parent 0490549 commit aa370e8
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 12 deletions.
47 changes: 47 additions & 0 deletions tooling/noirc_abi_wasm/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use js_sys::{Error, JsString};
use noirc_abi::errors::{AbiError, InputParserError};
use wasm_bindgen::prelude::wasm_bindgen;

#[wasm_bindgen(typescript_custom_section)]
const ABI_ERROR: &'static str = r#"
export type ABIError = Error;
"#;

/// JsAbiError is a raw js error.
/// It'd be ideal that ABI error was a subclass of Error, but for that we'd need to use JS snippets or a js module.
/// Currently JS snippets don't work with a nodejs target. And a module would be too much for just a custom error type.
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(extends = Error, js_name = "AbiError", typescript_type = "AbiError")]
#[derive(Clone, Debug, PartialEq, Eq)]
pub type JsAbiError;

#[wasm_bindgen(constructor, js_class = "Error")]
fn constructor(message: JsString) -> JsAbiError;
}

impl JsAbiError {
/// Creates a new execution error with the given call stack.
/// Call stacks won't be optional in the future, after removing ErrorLocation in ACVM.
pub fn new(message: String) -> Self {
JsAbiError::constructor(JsString::from(message))
}
}

impl From<String> for JsAbiError {
fn from(value: String) -> Self {
JsAbiError::new(value)
}
}

impl From<AbiError> for JsAbiError {
fn from(value: AbiError) -> Self {
JsAbiError::new(value.to_string())
}
}

impl From<InputParserError> for JsAbiError {
fn from(value: InputParserError) -> Self {
JsAbiError::new(value.to_string())
}
}
22 changes: 10 additions & 12 deletions tooling/noirc_abi_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#![warn(unreachable_pub)]
#![warn(clippy::semicolon_if_nothing_returned)]

use errors::JsAbiError;
// See Cargo.toml for explanation.
use getrandom as _;

Expand All @@ -14,6 +15,7 @@ use std::collections::BTreeMap;
use gloo_utils::format::JsValueSerdeExt;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};

mod errors;
mod js_witness_map;
mod temp;

Expand All @@ -25,7 +27,7 @@ pub fn abi_encode(
abi: JsValue,
inputs: JsValue,
return_value: JsValue,
) -> Result<JsWitnessMap, JsValue> {
) -> Result<JsWitnessMap, JsAbiError> {
console_error_panic_hook::set_once();
let abi: Abi = JsValueSerdeExt::into_serde(&abi).map_err(|err| err.to_string())?;
let inputs: BTreeMap<String, JsonTypes> =
Expand All @@ -36,14 +38,11 @@ pub fn abi_encode(
} else {
let toml_return_value =
JsValueSerdeExt::into_serde(&return_value).expect("could not decode return value");
Some(
input_value_from_json_type(
toml_return_value,
abi.return_type.as_ref().unwrap(),
MAIN_RETURN_NAME,
)
.map_err(|err| err.to_string())?,
)
Some(input_value_from_json_type(
toml_return_value,
abi.return_type.as_ref().unwrap(),
MAIN_RETURN_NAME,
)?)
};

let abi_map = abi.to_btree_map();
Expand All @@ -55,10 +54,9 @@ pub fn abi_encode(
.ok_or_else(|| InputParserError::MissingArgument(arg_name.clone()))?;
input_value_from_json_type(value.clone(), &abi_type, &arg_name)
.map(|input_value| (arg_name, input_value))
})
.map_err(|err| err.to_string())?;
})?;

let witness_map = abi.encode(&parsed_inputs, return_value).map_err(|err| err.to_string())?;
let witness_map = abi.encode(&parsed_inputs, return_value)?;

Ok(witness_map.into())
}
Expand Down
14 changes: 14 additions & 0 deletions tooling/noirc_abi_wasm/test/browser/uint_overflow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { expect } from "@esm-bundle/chai";
import initNoirAbi, { abiEncode } from "@noir-lang/noirc_abi";

beforeEach(async () => {
await initNoirAbi();
});

it("errors when an integer input overflows", async () => {
const { abi, inputs } = await import("../shared/uint_overflow");

expect(() => abiEncode(abi, inputs, null)).to.throw(
"The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)",
);
});
10 changes: 10 additions & 0 deletions tooling/noirc_abi_wasm/test/node/uint_overflow.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { expect } from "chai";
import { abiEncode } from "@noir-lang/noirc_abi";

it("errors when an integer input overflows", async () => {
const { abi, inputs } = await import("../shared/uint_overflow");

expect(() => abiEncode(abi, inputs, null)).to.throw(
"The parameter foo is expected to be a Integer { sign: Unsigned, width: 32 } but found incompatible value Field(2³⁸)",
);
});
16 changes: 16 additions & 0 deletions tooling/noirc_abi_wasm/test/shared/uint_overflow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const abi = {
parameters: [
{
name: "foo",
type: { kind: "integer", sign: "unsigned", width: 32 },
visibility: "private",
},
],
param_witnesses: { foo: [1] },
return_type: null,
return_witnesses: [],
};

export const inputs = {
foo: `0x${(1n << 38n).toString(16)}`,
};

0 comments on commit aa370e8

Please sign in to comment.