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

fix: correct signed integer handling in noirc_abi #6638

Merged
merged 11 commits into from
Dec 2, 2024
7 changes: 7 additions & 0 deletions tooling/noirc_abi/proptest-regressions/input_parser/json.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc b3f9ae88d54944ca274764f4d99a2023d4b0ac09beb89bc599cbba1e45dd3620 # shrinks to (typ, value) = (Integer { sign: Signed, width: 1 }, -1)
9 changes: 9 additions & 0 deletions tooling/noirc_abi/proptest-regressions/input_parser/toml.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 9d200afb8f5c01e3414d24eebe1436a7eef5377a46a9a9235aaa7f81e0b33656 # shrinks to (typ, value) = (Integer { sign: Signed, width: 8 }, -1)
cc 7fd29637e5566d819992185c1a95438e9949a555928a911b3918eed2e3f7a1fd # shrinks to (typ, value) = (Integer { sign: Signed, width: 64 }, -1)
cc 8ecbda39d887674b53ca23a861ac30fbb10c123bb70c57e69b336c86a3d9dea8 # shrinks to (abi, input_map) = (Abi { parameters: [AbiParameter { name: "¡", typ: Struct { path: "�)\u{1b}=�?Ⱥ\u{59424}?{\u{e4d5e}%Ѩ/Q\u{36a17}/*\";\u{b}&iC_\u{d313f}S\u{1b}\u{9dfec}\r/\u{10530d}", fields: [("?p*\"/\u{202e}\u{6f038}\u{537ca}.y@~𘛶?4\u{1b}*", Field), (".Ⱥ/$\u{7f}\u{103c06}%\\\u{202e}][0\u{88479}]\"*~\u{36fd5}\u{5}\u{feff}]{/", Tuple { fields: [String { length: 937 }] }), ("r\u{ac3a5}&:", Boolean), ("$d6🕴/:|�\u{37f8b}\r\u{a13b7}C$𲁹\\&\u{f8712}?\u{db61c}t%\u{57be1}\0", Field), ("/\u{6378b}\u{a426c}¥\u{7}/\u{fcb29}$\u{53c6b}\u{12d6f}\u{12bd3}.\u{f2f82}\u{8613e}*$\u{fd32f}\u{e29f7}\0𨺉'¬\"1", Struct { path: "\\\u{4a5ac}<\u{9e505}\u{4f3af}🕴&?<:^\u{7}\u{88}\u{3e1ff}(¥\u{531f3}K{:¥𦺀", fields: [("n\0Ѩ/\u{1b}𥐰\u{a4906}�¥`{\u{389d4}`1\u{7708a})\u{3dac4}8\u{93e5f}㒭\\\"\u{e6824}\u{b}Ѩ\u{88946}Ⱥ{", Integer { sign: Signed, width: 127 })] }), ("¥🕴\u{1b}¥🕴=sR\0\u{35f36}\u{867dc}>ä\u{202e}f:BȺ?:``*·¥\u{74ca5}\"", Tuple { fields: [Boolean, Field, String { length: 205 }, String { length: 575 }, Integer { sign: Signed, width: 124 }, String { length: 923 }, String { length: 294 }] })] }, visibility: Public }], return_type: None, error_types: {} }, {"¡": Struct({"$d6🕴/:|�\u{37f8b}\r\u{a13b7}C$𲁹\\&\u{f8712}?\u{db61c}t%\u{57be1}\0": Field(-8275115097504119425402713293372777967031130481426075481525511323101167533940), ".Ⱥ/$\u{7f}\u{103c06}%\\\u{202e}][0\u{88479}]\"*~\u{36fd5}\u{5}\u{feff}]{/": Vec([String("A \0A 0 aA0 a0aa00 A\000 0 \0\0aA\0\0a \0 \0a 0A\0A\0 Aa0aAA0A\0aa\00 0\0\0\0\0\00a Aa0 \0 a A0 \0AA0A Aa Aa\00aAaAaaA0A0 aA0 \0 Aa\00 \0000AAA a \0AAaaA\0\0a A0a0AA\0aA00 aA a0A\0AAa0a\0A0a\0\0A0A \00Aaaaa a A AO.*D\r.`bD4a\n*\u{15}\\B\"ace.8&A\t[AV8w<\u{18}\"\u{f}4`^Q\u{1b}U*$Z/\0\u{b}]qw${`\"=X&A\\\u{e}%`\\:\"$\u{1}.(6_C:\u{7}a`V=N**\u{1b})#Y\u{7f}#\u{b}$l\t}.Mns5!\t*$g\u{18}\rC\u{11}\"$=\u{7}.?&\u{1}yW\t.Y|<6\u{12}\u{e}/4J<X\n$.8Pwi;4.H*}0Q WU`}%CH6L{-K{'Ph:H\u{7f}Z\u{7f}wD;/\u{16}\u{5}|{t/.=z\u{1}\"\u{1e}\u{1b}<*\\\\\"?xX?j$>J*&/V$`\"&`x#R\np\\%'*\n:P\0K\u{b}*`\r7Ym\t_\u{b}=$\u{16}`0v\u{7f}'NV^N4J<9=G*A:!b\u{1c}:'c{ST&z![\u{7f}/.={E*pmaWC\u{7f}7p{<\"']\u{8}?`\u{1b}\"\\\u{1}$\u{18}/!\u{16}-\t:E7CUs%_qw*xf.S\t\u{4}'=\"&%t'\u{1f}\u{7f}\u{b}$<zt;?.ma&8\u{1b}$*//\0\u{1b}O.\u{7f}`\u{b}l&@r\n\u{7f}\u{16}krd?/_E7U</\u{1b}\\\u{b}{`:&.q*|$\u{14}F\\Y`88\u{1c}O|{L\u{15}e1;`slh\u{b}:=QM<*e\"?\u{17}\u{b}\t*\r<:*\u{b}\u{7f}'\u{18}\u{c}\u{17}&%\"\u{7f}$x'\u{18}$<A:t\u{19}L{}K\nlOwUP\u{7f},`%?{{:\u{8}?GU\u{10}F:\\*3}\u{b}Ka.\u{7f}<\ra\u{10}`#rF\nZ\rc*%\u{b}$/\u{b}%aEV<41]^\0=B|y3Q,\u{b}`p\u{b}<*Z4:XMWP\n\0\u{1a}T>.=f\u{6}\"$A}xV_$\u{1a}nH\n\u{1b}?<&\n\u{15}U\\-b\u{1d}|\u{b}\u{2}t \rwA{L\u{11}\u{6}\u{10}\0\u{1b}G[x?&Yi?&7\u{b}?\r\u{1f}b\\$=\u{b}x& Q/\t\u{4}|X\"7\"{\0\0j'.\0\\e1zR.\u{c}\n<\u{b}Q*R+y8\u{19}(o\u{1f}@m\nt+<S\nN\u{1e}j\u{1b}\"\r$\"&`\u{14}-<NE*nxC\r\u{14}'8\t.IN1V?$HC$\u{1b}I\"@\u{7f}\"s?\nVcjr\u{1b}\08/\"S=>\u{7f}Q\\+.Rn?\u{17}UZ\"$\u{b}/\0B=9=\t{\u{8}qZ&`!:D{\u{6}IO.H\u{7f}:?/3@\r\u{1b}o<OS\u{7f}a\0&lOy\n{w(**0c")]), "/\u{6378b}\u{a426c}¥\u{7}/\u{fcb29}$\u{53c6b}\u{12d6f}\u{12bd3}.\u{f2f82}\u{8613e}*$\u{fd32f}\u{e29f7}\0𨺉'¬\"1": Struct({"n\0Ѩ/\u{1b}𥐰\u{a4906}�¥`{\u{389d4}`1\u{7708a})\u{3dac4}8\u{93e5f}㒭\\\"\u{e6824}\u{b}Ѩ\u{88946}Ⱥ{": Field(96037160722359396044901023537963606465)}), "?p*\"/\u{202e}\u{6f038}\u{537ca}.y@~𘛶?4\u{1b}*": Field(0), "r\u{ac3a5}&:": Field(1), "¥🕴\u{1b}¥🕴=sR\0\u{35f36}\u{867dc}>ä\u{202e}f:BȺ?:``*·¥\u{74ca5}\"": Vec([Field(1), Field(8822392870083219098626030699076694602179106416928939583840848325203494062169), String("*TXn;{}\"_)_9\nk\\#ts\u{10}%\\c\n/2._::Oj*\u{7f}\0\r&PUMl\u{10}$/u?L}\u{7f}*P&<%=\u{7}S#%A\n \u{e}\\#v!\"\nepRp.{vH{&@\t\u{1f}\u{b}?=T\u{f}\"B\u{11}\n/{HY.\u{16}\n\nj<&\u{3}{f\n/9J*&x.$/,\r\0\u{1c}'\u{5}\u{13}\u{1b}`T\0`\n&/&\u{15}\u{b}w:{SK\u{7f}\\apR%/'0`0\n'd$$\u{7f}Vs\t<{\nDTT\\F\n\u{15}y.\\\t*-)&D$*u\u{b}\u{1b}?{\u{b}/\n\u{7f}0*.7\0\n:\u{b}.rSk<6~>{#"), String(".\"JA%q6i\ra/:F\u{16}?q<\t\rN\\13?H<;?{`\u{1d}p{.\"5?*@'N\"\u{1a}P,\u{1b}\u{7f}c+dt5':Y\u{1b}k/G>k/eM$XIX')\u{1b}'&\u{7f}\\\r\u{1b}`'P_.\n.?\0p`Y\u{c}`._\u{b}B\0\ng/*v$jfJ:\u{c}<b\r~o\u{7f}\u{b}\\lyE*'\u{7f}~j:.N0U <\0?*D\0%P\u{7f}\u{15}4j$\u{5}s\u{b}\"{F/:\u{1b}\u{b}\u{1b}.f\"+\tcw\\:\u{b}<@(V.}`Ws`h%j}x&'\u{5}!4e?KT#\u{b}'Y\\6\0m\u{b}\\\u{8}Eo7.Jk&\u{b},3\u{17}Jd\0y*\tC\u{3}\\\u{f}l\".*'\"\u{1b}\u{12} \"{^*!`\n\u{1b}#Ox\u{7f}{\u{1b}&?_$\u{1b}{G`'\0bdiouv`\u{17}\u{7f}B.zq?'\0Ys\tb\n&&:?\\u'h::l%\\'`i\t'{=2?1\u{b}N\u{7f}&`<;\0\u{1e}=\u{7f}\u{16}dHdri\u{7f}'`;LB$\nJ\u{16}\n.\u{1a}*eqr\rdgU\\\u{1b}\u{19}4Ry\n5$J`&\\\u{b}:f\"/=q\0{R+U\t*&W$\u{14}\0v~'<\u{1b}<>\u{1b}Pv}xn7ph@#{_<{.JD?r%'E\n7s9n/],u![;%*\u{2}{y`MgRdok8\"%<*>*{GyFJ}?\0W%#\0\u{1b}\u{7f}\u{16}G:\t=w\u{7f}:q\u{7f}:{k?\u{b}(:ca{$*1X/cw\u{1b}Z6I\rX\0\u{1b}(.^14\r\\=s\u{1b}w\u{3}F~\n\u{1e})/$0:=[\u{1},\\\\\t<K\0nI\\P&:.{<\u{18}g<+^.uk\0\u{13}b`.;\\G\\S{!/:&\"y8%7R\u{5}\u{12}j\u{b}\\Gf&N$|F><n$J<\u{b}z''\u{b})\u{7f}lwc\t/"), Field(21267647932558653966460912964485513215), String("\0\t3\u{b}M\n?B\u{7f}:n&C\u{6}`>g\u{16}:<Z\u{13}z}w{:^#>],J`\0N\n\u{1b}\u{1b}\u{1b}{.xb\u{1a}\r'12#?e\\#/\tA\u{7f}\".\\Ke=\\?!v+P\u{17}\r\u{12}x.=A.`0<&?\niR/*WW\rnV)5<yob%\u{7f}\u{16}wS`_\u{1a}\0`>vY.~\n _h\0&5f#\r\u{2}-S%\t s..\u{7f}!X}\"=\"<?Ia&\u{e}8\t\u{e}%?u'\u{1b}b\u{1b}\r1=\u{b}\u{1b}%m\tAN'l\u{3}V\u{7}:q{\n<\u{b}Q'\u{13}\\\r%#\"\\%i%\\\r\r:5=%',U?D\u{19}oS\u{1c}\"/\043\"x5Twu&\u{5}:G*.<\u{c}\u{7f}%1^\n=\u{3}\u{b}\norXp\"vF??&\u{16}eZdM.t%t/h@F&:O\u{11}xD=\n%`\nXT|cSH:\u{b}\u{15}19T$:\r\r/%\u{b}H\tJ\nY`u\u{2},&\0/\r\u{f}5L\0rFa\0\n(*,$p\r(oc\u{7f}Ua?3/\\\u{17}$&\n\u{e}G\"\\Kv]%={3U\u{7f}:{\":@wk7?C~t;\u{1b}!\u{1a}*V+&\"k\u{14}`9\u{7f}w\04\0sO\u{2}2Q\tZ\n>?\u{5}y\u{4}`fr&R&d: 1Ht\"4`y_/S.71#{|%$%&ehy\u{16}J_\u{e}=:.%'\"N=J:\r:{&.\u{12}\u{b})&N\u{10}R_3;11\u{b}Qd<`<{?xF:~\"%<=<<\03:t??&\r;{\u{13}<z\u{7f}\\\n>?__Y\u{6})\\k,vs?\n`G(*\n!\u{1b}[@z\0$?*yKLJh_\u{13}F<uZ?q{,\u{16}\0tLp!H\ra?&b?{{jWi\0/&`?5<\0$=m\"Q\"\u{1b}{J8\u{1}.*1\" t*~{.K\u{12}.1:`'#q$((q=I*['&\u{11}:\u{6}^v\u{17}{C.R<9#\u{1b}{\0\"K&xD0\u{15}d2u\u{b} p*k7\u{1b}:\u{16}L2x\u{1d}\u{b}{\t\tt\u{1b}U'\0\t+'\0g\u{b}'\r/\nio.\u{6}=@Z7\t?){\u{7f}\"\"={eW\u{7}:w._=<5\r%\n\u{6}\nws\0e<\\6\u{1a}<i.*s'\r{c'raI:\"?\n=]n[z\u{7f}+:\u{1b}4p&@?$e {H.%?:\u{12}\u{1b}z\\\u{b}\\aI?\t\u{1b}${/\rM*\u{1b}:]*\r.c$u'PmM(lLo@PH~1P}$H?_Ia5$Rg6]-JC`<$*/\u{7f}<TK(jMY\u{19}%<?A%!4'2\u{13}b$$=/q\u{18}=&\r\u{1b}?%5jK\0%2<:\u{7f}olOMXI*\u{1e}r2ua6\".u)k:A'\u{1b}Rh8k\u{19}\u{1a}=M#u;#>kY'\\?T^\u{1f}$1n`'[\n\u{7f}\0+l\u{b}\u{1a}E\u{b}&(/\u{b}\rr\t:&\0<s8/\u{b}\thCB\n\u{6}*%cO~<V/Ql*2r\t"), String("Z%*?]R{\u{b}\0T\\\u{1b}+J?=P4?\u{b}L\u{2}/\0\u{1b}\r\0.\n$4\u{b}:?a{SF\u{b}\u{1b}=\\j\t{\"\\B\u{5}'$.}`X\u{1b}\\.\u{1c}6*\u{7f}j<&?$b `<<\0KkAQ)</\0?9\u{18}N$%\0.\tO\u{7f}\0\tUkI*7'\u{1c}t=x\r*S{\u{5}I/\t\u{7f}5\":b=j\u{16}\"\u{b}8<\u{e}\u{7f}la\u{b}?.aV}\u{6}{(={P42Qo*uX=\u{1a}M:k}l2-&qM*{{\u{6}ut%\u{1b}\0\\\r\rw/\0\u{1d}+Bc\u{7}\u{b}f\"'Z l\u{7f}:nD4\u{1f}$5ONG\r\rFd\r.c'r'Nt\rn-Z\\\tk2s*GK!7D&KX/^6%>+N'N:oC:*``IN\u{b}*.:\t$7+'*U:\t<y<6nu'9/RIP/'\u{b}\\mj\u{6}\u{7f}#\u{1c}@Rh<*\\b*f\0xa6\n")])})})
47 changes: 45 additions & 2 deletions tooling/noirc_abi/src/input_parser/json.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{parse_str_to_field, InputValue};
use super::{field_to_signed_hex, parse_str_to_field, parse_str_to_signed, InputValue};
use crate::{errors::InputParserError, Abi, AbiType, MAIN_RETURN_NAME};
use acvm::{AcirField, FieldElement};
use iter_extended::{try_btree_map, try_vecmap};
Expand Down Expand Up @@ -60,7 +60,7 @@ pub(crate) fn serialize_to_json(
Ok(json_string)
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(untagged)]
pub enum JsonTypes {
// This is most likely going to be a hex string
Expand All @@ -86,6 +86,9 @@ impl JsonTypes {
abi_type: &AbiType,
) -> Result<JsonTypes, InputParserError> {
let json_value = match (value, abi_type) {
(InputValue::Field(f), AbiType::Integer { sign: crate::Sign::Signed, width }) => {
JsonTypes::String(field_to_signed_hex(*f, *width))
}
(InputValue::Field(f), AbiType::Field | AbiType::Integer { .. }) => {
JsonTypes::String(Self::format_field_string(*f))
}
Expand Down Expand Up @@ -143,6 +146,9 @@ impl InputValue {
) -> Result<InputValue, InputParserError> {
let input_value = match (value, param_type) {
(JsonTypes::String(string), AbiType::String { .. }) => InputValue::String(string),
(JsonTypes::String(string), AbiType::Integer { sign: crate::Sign::Signed, width }) => {
InputValue::Field(parse_str_to_signed(&string, *width)?)
}
(
JsonTypes::String(string),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean,
Expand Down Expand Up @@ -192,3 +198,40 @@ impl InputValue {
Ok(input_value)
}
}

#[cfg(test)]
mod test {
use proptest::prelude::*;

use crate::{
arbitrary::arb_abi_and_input_map,
input_parser::{arbitrary::arb_signed_integer_type_and_value, json::JsonTypes, InputValue},
};

use super::{parse_json, serialize_to_json};

proptest! {
#[test]
fn serializing_and_parsing_returns_original_input((abi, input_map) in arb_abi_and_input_map()) {
let json = serialize_to_json(&input_map, &abi).expect("should be serializable");
let parsed_input_map = parse_json(&json, &abi).expect("should be parsable");

prop_assert_eq!(parsed_input_map, input_map);
}

#[test]
fn signed_integer_serialization_roundtrip((typ, value) in arb_signed_integer_type_and_value()) {
let string_input = JsonTypes::String(value.to_string());
let input_value = InputValue::try_from_json(string_input, &typ, "foo").expect("should be parsable");
let JsonTypes::String(output_string) = JsonTypes::try_from_input_value(&input_value, &typ).expect("should be serializable") else {
panic!("wrong type output");
};
let output_number = if let Some(output_string) = output_string.strip_prefix("-0x") {
-i64::from_str_radix(output_string, 16).unwrap()
} else {
i64::from_str_radix(output_string.strip_prefix("0x").unwrap(), 16).unwrap()
};
prop_assert_eq!(output_number, value);
}
}
}
57 changes: 55 additions & 2 deletions tooling/noirc_abi/src/input_parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ mod serialization_tests {
typ: AbiType::Field,
visibility: AbiVisibility::Private,
},
AbiParameter {
name: "signed_example".into(),
typ: AbiType::Integer { sign: Sign::Signed, width: 8 },
visibility: AbiVisibility::Private,
},
AbiParameter {
name: "bar".into(),
typ: AbiType::Struct {
Expand All @@ -272,6 +277,7 @@ mod serialization_tests {

let input_map: BTreeMap<String, InputValue> = BTreeMap::from([
("foo".into(), InputValue::Field(FieldElement::one())),
("signed_example".into(), InputValue::Field(FieldElement::from(240u128))),
(
"bar".into(),
InputValue::Struct(BTreeMap::from([
Expand Down Expand Up @@ -317,7 +323,9 @@ fn parse_str_to_field(value: &str) -> Result<FieldElement, InputParserError> {
}

fn parse_str_to_signed(value: &str, width: u32) -> Result<FieldElement, InputParserError> {
let big_num = if let Some(hex) = value.strip_prefix("0x") {
let big_num = if let Some(hex) = value.strip_prefix("-0x") {
BigInt::from_str_radix(hex, 16).map(|value| -value)
} else if let Some(hex) = value.strip_prefix("0x") {
BigInt::from_str_radix(hex, 16)
} else {
BigInt::from_str_radix(value, 10)
Expand Down Expand Up @@ -357,12 +365,23 @@ fn field_from_big_int(bigint: BigInt) -> FieldElement {
}
}

fn field_to_signed_hex(f: FieldElement, bit_size: u32) -> String {
let f_u128 = f.to_u128();
let max = 2_u128.pow(bit_size - 1) - 1;
if f_u128 > max {
let f = FieldElement::from(2_u128.pow(bit_size) - f_u128);
format!("-0x{}", f.to_hex())
} else {
format!("0x{}", f.to_hex())
}
}

#[cfg(test)]
mod test {
use acvm::{AcirField, FieldElement};
use num_bigint::BigUint;

use super::parse_str_to_field;
use super::{parse_str_to_field, parse_str_to_signed};

fn big_uint_from_field(field: FieldElement) -> BigUint {
BigUint::from_bytes_be(&field.to_be_bytes())
Expand Down Expand Up @@ -400,4 +419,38 @@ mod test {
let noncanonical_field = FieldElement::modulus().to_string();
assert!(parse_str_to_field(&noncanonical_field).is_err());
}

#[test]
fn test_parse_str_to_signed() {
let value = parse_str_to_signed("1", 8).unwrap();
assert_eq!(value, FieldElement::from(1_u128));

let value = parse_str_to_signed("-1", 8).unwrap();
assert_eq!(value, FieldElement::from(255_u128));

let value = parse_str_to_signed("-1", 16).unwrap();
assert_eq!(value, FieldElement::from(65535_u128));
}
}

#[cfg(test)]
mod arbitrary {
use proptest::prelude::*;

use crate::{AbiType, Sign};

pub(super) fn arb_signed_integer_type_and_value() -> BoxedStrategy<(AbiType, i64)> {
(2u32..=64)
.prop_flat_map(|width| {
let typ = Just(AbiType::Integer { width, sign: Sign::Signed });
let value = if width == 64 {
// Avoid overflow
i64::MIN..i64::MAX
} else {
-(2i64.pow(width - 1))..(2i64.pow(width - 1) - 1)
};
(typ, value)
})
.boxed()
}
}
47 changes: 44 additions & 3 deletions tooling/noirc_abi/src/input_parser/toml.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::{parse_str_to_field, parse_str_to_signed, InputValue};
use super::{field_to_signed_hex, parse_str_to_field, parse_str_to_signed, InputValue};
use crate::{errors::InputParserError, Abi, AbiType, MAIN_RETURN_NAME};
use acvm::{AcirField, FieldElement};
use iter_extended::{try_btree_map, try_vecmap};
Expand Down Expand Up @@ -60,7 +60,7 @@ pub(crate) fn serialize_to_toml(
Ok(toml_string)
}

#[derive(Debug, Deserialize, Serialize, Clone)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
#[serde(untagged)]
enum TomlTypes {
// This is most likely going to be a hex string
Expand All @@ -83,6 +83,9 @@ impl TomlTypes {
abi_type: &AbiType,
) -> Result<TomlTypes, InputParserError> {
let toml_value = match (value, abi_type) {
(InputValue::Field(f), AbiType::Integer { sign: crate::Sign::Signed, width }) => {
TomlTypes::String(field_to_signed_hex(*f, *width))
}
(InputValue::Field(f), AbiType::Field | AbiType::Integer { .. }) => {
let f_str = format!("0x{}", f.to_hex());
TomlTypes::String(f_str)
Expand Down Expand Up @@ -126,6 +129,7 @@ impl InputValue {
) -> Result<InputValue, InputParserError> {
let input_value = match (value, param_type) {
(TomlTypes::String(string), AbiType::String { .. }) => InputValue::String(string),

(
TomlTypes::String(string),
AbiType::Field
Expand All @@ -139,7 +143,7 @@ impl InputValue {
TomlTypes::Integer(integer),
AbiType::Field | AbiType::Integer { .. } | AbiType::Boolean,
) => {
let new_value = FieldElement::from(i128::from(integer));
let new_value = FieldElement::from(u128::from(integer));

InputValue::Field(new_value)
}
Expand Down Expand Up @@ -179,3 +183,40 @@ impl InputValue {
Ok(input_value)
}
}

#[cfg(test)]
mod test {
use proptest::prelude::*;

use crate::{
arbitrary::arb_abi_and_input_map,
input_parser::{arbitrary::arb_signed_integer_type_and_value, toml::TomlTypes, InputValue},
};

use super::{parse_toml, serialize_to_toml};

proptest! {
#[test]
fn serializing_and_parsing_returns_original_input((abi, input_map) in arb_abi_and_input_map()) {
let toml = serialize_to_toml(&input_map, &abi).expect("should be serializable");
let parsed_input_map = parse_toml(&toml, &abi).expect("should be parsable");

prop_assert_eq!(parsed_input_map, input_map);
}

#[test]
fn signed_integer_serialization_roundtrip((typ, value) in arb_signed_integer_type_and_value()) {
let string_input = TomlTypes::String(value.to_string());
let input_value = InputValue::try_from_toml(string_input.clone(), &typ, "foo").expect("should be parsable");
let TomlTypes::String(output_string) = TomlTypes::try_from_input_value(&input_value, &typ).expect("should be serializable") else {
panic!("wrong type output");
};
let output_number = if let Some(output_string) = output_string.strip_prefix("-0x") {
-i64::from_str_radix(output_string, 16).unwrap()
} else {
i64::from_str_radix(output_string.strip_prefix("0x").unwrap(), 16).unwrap()
};
prop_assert_eq!(output_number, value);
}
}
}
5 changes: 3 additions & 2 deletions tooling/noirc_abi_wasm/test/browser/abi_encode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ it('recovers original inputs when abi encoding and decoding', async () => {
const foo: Field = inputs.foo as Field;
const bar: Field[] = inputs.bar as Field[];
expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(bar[1]));
expect(parseInt(decoded_inputs.inputs.bar[0])).to.be.equal(parseInt(bar[0]));
expect(parseInt(decoded_inputs.inputs.bar[1])).to.be.equal(parseInt(bar[1]));
expect(parseInt(decoded_inputs.inputs.bar[2])).to.be.equal(parseInt(bar[2]));
expect(decoded_inputs.return_value).to.be.null;
});
5 changes: 3 additions & 2 deletions tooling/noirc_abi_wasm/test/node/abi_encode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ it('recovers original inputs when abi encoding and decoding', async () => {
const foo: Field = inputs.foo as Field;
const bar: Field[] = inputs.bar as Field[];
expect(BigInt(decoded_inputs.inputs.foo)).to.be.equal(BigInt(foo));
expect(BigInt(decoded_inputs.inputs.bar[0])).to.be.equal(BigInt(bar[0]));
expect(BigInt(decoded_inputs.inputs.bar[1])).to.be.equal(BigInt(bar[1]));
expect(parseInt(decoded_inputs.inputs.bar[0])).to.be.equal(parseInt(bar[0]));
expect(parseInt(decoded_inputs.inputs.bar[1])).to.be.equal(parseInt(bar[1]));
expect(parseInt(decoded_inputs.inputs.bar[2])).to.be.equal(parseInt(bar[2]));
expect(decoded_inputs.return_value).to.be.null;
});
4 changes: 2 additions & 2 deletions tooling/noirc_abi_wasm/test/shared/abi_encode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const abi: Abi = {
{ name: 'foo', type: { kind: 'field' }, visibility: 'private' },
{
name: 'bar',
type: { kind: 'array', length: 2, type: { kind: 'field' } },
type: { kind: 'array', length: 3, type: { kind: 'integer', sign: 'signed', width: 32 } },
visibility: 'private',
},
],
Expand All @@ -15,5 +15,5 @@ export const abi: Abi = {

export const inputs: InputMap = {
foo: '1',
bar: ['1', '2'],
bar: ['1', '2', '-1'],
};
Loading