Skip to content

Commit

Permalink
fix: correct signed integer handling in noirc_abi (#6638)
Browse files Browse the repository at this point in the history
Co-authored-by: Ary Borenszweig <asterite@gmail.com>
Co-authored-by: guipublic <guipublic@gmail.com>
Co-authored-by: guipublic <47281315+guipublic@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 2, 2024
1 parent d80a9d7 commit ecaf63d
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 13 deletions.
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'],
};

0 comments on commit ecaf63d

Please sign in to comment.