diff --git a/noir-projects/aztec-nr/aztec/src/macros/utils.nr b/noir-projects/aztec-nr/aztec/src/macros/utils.nr index 9ea763d34ae6..94b287733e07 100644 --- a/noir-projects/aztec-nr/aztec/src/macros/utils.nr +++ b/noir-projects/aztec-nr/aztec/src/macros/utils.nr @@ -111,6 +111,14 @@ pub(crate) comptime fn add_to_hasher(hasher_name: Quoted, name: Quoted, typ: Typ $hasher_name.add_multiple($serialized_name[i]); } } + } else if typ.as_tuple().is_some() { + let tuple_len = typ.as_tuple().unwrap().len(); + let mut tuple_quotes: [Quoted] = []; + for i in 0..tuple_len { + let element_quote = quote { $hasher_name.add_multiple(dep::aztec::protocol_types::traits::Serialize::serialize($name.$i)); }; + tuple_quotes = tuple_quotes.push_back(element_quote); + } + tuple_quotes.join(quote {}) } else if typ.as_str().is_some() { quote { $hasher_name.add_multiple($name.as_bytes().map(| byte: u8 | byte as Field)); diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 9e6c33825e5a..5ff484e328d5 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -45,6 +45,7 @@ members = [ "contracts/test/note_getter_contract", "contracts/test/parent_contract", "contracts/test/pending_note_hashes_contract", + "contracts/test/returning_tuple_contract", "contracts/test/spam_contract", "contracts/test/state_vars_contract", "contracts/test/stateful_test_contract", diff --git a/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/Nargo.toml new file mode 100644 index 000000000000..f66addd5cec8 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "returning_tuple_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/src/main.nr new file mode 100644 index 000000000000..2612a23b0614 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test/returning_tuple_contract/src/main.nr @@ -0,0 +1,47 @@ +use aztec::macros::aztec; + +// Tests that Aztec.nr handles returning tuples correctly. +#[aztec] +pub contract ReturningTuple { + use aztec::{ + macros::functions::{private, view}, + prelude::{AztecAddress, Point}, + protocol_types::traits::{Deserialize, FromField}, + }; + + #[private] + #[view] + fn fn_that_returns_1() -> (bool,) { + (true,) + } + + #[private] + #[view] + fn fn_that_returns_2() -> (Field, u32) { + (1, 2) + } + + #[private] + #[view] + fn fn_that_returns_3() -> (Field, bool, str<4>) { + (1, true, "test") + } + + #[private] + #[view] + fn fn_that_returns_4() -> (Field, u64, bool, str<3>) { + (1, 2, false, "abc") + } + + #[private] + #[view] + fn fn_that_returns_5() -> (Field, u32, bool, str<2>, i64) { + (1, 2, true, "hi", -5) + } + + #[private] + #[view] + fn fn_that_returns_6() -> (Field, u128, bool, str<3>, AztecAddress, Point) { + (1, 2, false, "xyz", AztecAddress::from_field(1), Point::deserialize([1, 2, 3])) + } +} diff --git a/yarn-project/stdlib/src/abi/decoder.test.ts b/yarn-project/stdlib/src/abi/decoder.test.ts index ea19a4276cc1..7e048e0fb84e 100644 --- a/yarn-project/stdlib/src/abi/decoder.test.ts +++ b/yarn-project/stdlib/src/abi/decoder.test.ts @@ -1,5 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; +import { AztecAddress } from '../aztec-address/index.js'; import type { ABIParameterVisibility, FunctionArtifact } from './abi.js'; import { decodeFromAbi, decodeFunctionSignature, decodeFunctionSignatureWithParameterNames } from './decoder.js'; @@ -182,4 +183,90 @@ describe('decoder', () => { ); expect(decoded).toBe(2n ** 63n - 1n); }); + + it('decodes a tuple', () => { + // ABI copied from noir-projects/noir-contracts/target/returning_tuple_contract-ReturningTuple.json + const decoded = decodeFromAbi( + [ + { + kind: 'tuple', + fields: [ + { + kind: 'field', + }, + { + kind: 'integer', + sign: 'unsigned', + width: 128, + }, + { + kind: 'boolean', + }, + { + kind: 'string', + length: 3, + }, + { + kind: 'struct', + path: 'aztec::protocol_types::address::aztec_address::AztecAddress', + fields: [ + { + name: 'inner', + type: { + kind: 'field', + }, + }, + ], + }, + { + kind: 'struct', + path: 'std::embedded_curve_ops::EmbeddedCurvePoint', + fields: [ + { + name: 'x', + type: { + kind: 'field', + }, + }, + { + name: 'y', + type: { + kind: 'field', + }, + }, + { + name: 'is_infinite', + type: { + kind: 'boolean', + }, + }, + ], + }, + ], + }, + ], + [ + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')), // field + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex')), // u128 + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')), // bool + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000078', 'hex')), // "x" + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000079', 'hex')), // "y" + Fr.fromBuffer(Buffer.from('000000000000000000000000000000000000000000000000000000000000007a', 'hex')), // "z" + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')), // address + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000001', 'hex')), // point.x + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000002', 'hex')), // point.y + Fr.fromBuffer(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')), // point.is_infinite + ], + ); + + expect(decoded).toEqual([ + 1n, + 2n, + false, + 'xyz', + AztecAddress.fromBigInt(1n), + // eslint-disable-next-line camelcase + { x: 1n, y: 2n, is_infinite: false }, + ]); + }); }); diff --git a/yarn-project/stdlib/src/abi/decoder.ts b/yarn-project/stdlib/src/abi/decoder.ts index da5c39023a51..ff858cf287a7 100644 --- a/yarn-project/stdlib/src/abi/decoder.ts +++ b/yarn-project/stdlib/src/abi/decoder.ts @@ -7,7 +7,7 @@ import { isAztecAddressStruct, parseSignedInt } from './utils.js'; /** * The type of our decoded ABI. */ -export type AbiDecoded = bigint | boolean | AztecAddress | AbiDecoded[] | { [key: string]: AbiDecoded }; +export type AbiDecoded = bigint | boolean | string | AztecAddress | AbiDecoded[] | { [key: string]: AbiDecoded }; /** * Decodes values using a provided ABI. @@ -58,11 +58,12 @@ class AbiDecoder { return struct; } case 'string': { - const array = []; + let str = ''; for (let i = 0; i < abiType.length; i += 1) { - array.push(this.getNextField().toBigInt()); + const charCode = Number(this.getNextField().toBigInt()); + str += String.fromCharCode(charCode); } - return array; + return str; } case 'tuple': { const array = [];