From 4e441fb94cc5a37c27a26973f115d0445ce24326 Mon Sep 17 00:00:00 2001 From: iAmMichaelConnor Date: Mon, 7 Aug 2023 14:50:29 +0000 Subject: [PATCH 01/15] feat: contract interface generated for Field param types only --- yarn-project/foundation/src/abi/abi_coder.ts | 2 +- yarn-project/noir-compiler/src/cli.ts | 4 +- .../src/contract-interface-gen/noir.ts | 415 ++++++++++++++++++ .../typescript.ts} | 57 +-- yarn-project/noir-compiler/src/index.ts | 3 +- yarn-project/noir-contracts/scripts/types.sh | 5 +- .../contracts/child_contract/src/storage.nr | 4 +- .../contracts/import_test_contract/Nargo.toml | 7 + .../import_test_contract/src/main.nr | 29 ++ .../src/zk_token_contract_interface.nr | 261 +++++++++++ .../src/zk_token_contract_interface_MASTER.nr | 125 ++++++ .../contracts/lending_contract/src/storage.nr | 6 +- .../non_native_token_contract/src/storage.nr | 16 +- .../public_token_contract/src/storage.nr | 9 +- .../noir-contracts/src/scripts/copy_output.ts | 101 ++--- .../noir-libs/noir-aztec/src/state_vars.nr | 1 - .../noir-aztec/src/state_vars/public_state.nr | 2 +- .../noir-libs/noir-aztec/src/types.nr | 3 +- .../noir-libs/noir-aztec/src/types/point.nr | 22 +- .../type_serialisation.nr | 1 + .../type_serialisation/bool_serialisation.nr | 16 + .../type_serialisation/field_serialisation.nr | 2 +- .../type_serialisation/u32_serialisation.nr | 2 +- 23 files changed, 989 insertions(+), 104 deletions(-) create mode 100644 yarn-project/noir-compiler/src/contract-interface-gen/noir.ts rename yarn-project/noir-compiler/src/{typegen/index.ts => contract-interface-gen/typescript.ts} (97%) create mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml create mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr create mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr create mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr rename yarn-project/noir-libs/noir-aztec/src/{state_vars => types}/type_serialisation.nr (92%) create mode 100644 yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/bool_serialisation.nr rename yarn-project/noir-libs/noir-aztec/src/{state_vars => types}/type_serialisation/field_serialisation.nr (82%) rename yarn-project/noir-libs/noir-aztec/src/{state_vars => types}/type_serialisation/u32_serialisation.nr (82%) diff --git a/yarn-project/foundation/src/abi/abi_coder.ts b/yarn-project/foundation/src/abi/abi_coder.ts index c33e3db4cd9..5fd4ccd48fa 100644 --- a/yarn-project/foundation/src/abi/abi_coder.ts +++ b/yarn-project/foundation/src/abi/abi_coder.ts @@ -34,7 +34,7 @@ export function computeFunctionSelector(signature: string, size: number) { */ export function generateFunctionSelector(name: string, parameters: ABIParameter[]) { const signature = computeFunctionSignature(name, parameters); - return keccak(Buffer.from(signature)).slice(0, 4); + return computeFunctionSelector(signature, 4); } /** diff --git a/yarn-project/noir-compiler/src/cli.ts b/yarn-project/noir-compiler/src/cli.ts index d35fff1cc0c..f335b8bdebb 100644 --- a/yarn-project/noir-compiler/src/cli.ts +++ b/yarn-project/noir-compiler/src/cli.ts @@ -7,7 +7,7 @@ import fs from 'fs/promises'; import nodePath from 'path'; import { ContractCompiler } from './compile.js'; -import { generateType } from './index.js'; +import { generateTSContractInterface } from './index.js'; const program = new Command(); const log = createConsoleLogger('noir-compiler-cli'); @@ -39,7 +39,7 @@ const main = async () => { .argument('[targetPath]', 'Path to the output file') .action(async (buildPath: string, targetPath: string) => { const artifact = readJSONSync(buildPath); - const output = generateType(artifact); + const output = generateTSContractInterface(artifact); await fs.writeFile(targetPath, output); log(`Written type for ${artifact.name} to ${targetPath}`); }); diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts new file mode 100644 index 00000000000..0acf9896d7c --- /dev/null +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -0,0 +1,415 @@ +import { ABIParameter, ContractAbi, FunctionAbi, generateFunctionSelector } from '@aztec/foundation/abi'; + +import { log } from 'console'; +import upperFirst from 'lodash.upperfirst'; + +/** + * Structured data describing a Noir Contract function parameter. + * We sometimes make use of this (instead of the Contract's JSON ABI) when generating Noir code, because the `type` field is a Noir type (as opposed to a typescript type or an abi type, which seem to be 'lossy', in terms of expressing the type). + */ +type StructuredParam = { + /** + * parameter name + */ + name: string; + /** + * parameter type (this is a Noir type: not a typescript type or a type as formatted in the json abi, but a type that you write when writing actual Noir code). + */ + type: string; +}; + +/** + * Structured data describing a Noir Contract function. + * We sometimes make use of this (instead of the Contract's JSON ABI) when generating Noir code, because the structured param field is easier to generate Noir code from, and it includes a selector. + * TODO: returnTypes isn't used yet... + */ +type FuncData = { + name: string; + parameters: string[]; + structuredParameters: StructuredParam[]; + returnTypes: string[]; + selector: string | null; +}; + +/** + * Remove comments from a Noir contract file. + * @param sourceCode - the original Noir Contract file. + * @returns - a version of the Noir Contract file, but with comments removed (for easier later parsing). + */ +function removeComments(sourceCode: string): string { + // Hybrid comments pattern `//******/` + const hybridCommentPattern = /\/\/\*+\/\n/g; + sourceCode = sourceCode.replace(hybridCommentPattern, ''); + + // Remove block comments (/* ... */) + const blockCommentPattern = /\/\*[\s\S]*?\*\//g; + sourceCode = sourceCode.replace(blockCommentPattern, ''); + + // Remove line comments (// ...) + const lineCommentPattern = /\/\/[^\n]*(?:\n|$)/g; + sourceCode = sourceCode.replace(lineCommentPattern, ''); + + return sourceCode; +} + +/** + * Convert strings of the form `param_name: NoirTypeName` to a StructuredParam. + * @param paramStrings - `param_name: NoirTypeName` + * @returns the same data, but in a StructuredParam format. + */ +function getStructuredParameters(paramStrings: string[]): StructuredParam[] { + const structuredParams = paramStrings + .map(item => item.split(':').map(part => part.trim())) + .map(item => { + return { name: item[0], type: item[1] }; + }); + return structuredParams; +} + +/** + * Parses the Noir Contact source code. + * Removes comments. + * Then searches for all PRIVATE functions, matching: + * 1: private function name + * 2: parameters. + * Parameters are read directly as `param_name: NoirTypeName`, and then are converted into a StructuredParam. + * NOTE: return types are not processed, because a PRIVATE Noir Contract function does not return its return_values. The thing that's returned is always a `PrivateCircuitPublicInputs` struct, which won't be transpiled over into our contract interface. + * @param sourceCode - the original Noir Contract file. + * @returns - FuncData for every PRIVATE function in the Noir contract. + */ +function extractPrivateFunctionData(sourceCode: string): FuncData[] { + const functionPattern = /fn\s+(\w+)\s*\(\s*([^)]*)\s*\)\s*->\s*distinct\s+pub\s+abi::PrivateCircuitPublicInputs/g; + const functionData: FuncData[] = []; + let match: RegExpExecArray | null; + + while ((match = functionPattern.exec(sourceCode)) !== null) { + const functionName = match[1]; + const paramsWithoutComments = removeComments(match[2]); + const parameters = paramsWithoutComments + .split(',') + .map(param => param.trim()) + .filter(param => param !== '') + .filter(param => !param.includes('PrivateContextInputs')); // Remove this type of param, as it isn't needed in the contract interface. + functionData.push({ + name: functionName, + parameters, + structuredParameters: getStructuredParameters(parameters), + returnTypes: [], // TODO + selector: null, + }); + } + + return functionData; +} + +/** + * Parse and extract information about each function in the source code. We do this (rather than relying on the json artifact/abi, because the abi is lossy, in that it doesn't preserve the names of the original Noir types, which are useful for generating more Noir code with those same types). + * @param sourceCode - the original Noir Contract file. + * @returns - FuncData for every function in the Noir contract. + * TODO: It doesn't process public (`open`) functions yet! + */ +function extractFunctionData(sourceCode: string): FuncData[] { + return extractPrivateFunctionData(sourceCode); +} + +/** + * Populate the `selector` field of FuncData, for every function in our contract. + * @param functionData - An array of data describing the name, selector, params, and return types of every function. The + * @param abi - The ContractAbi of the Noir Contract, as generated in the `copy_output.ts` file. Rather than use our custom FuncData (which is very useful for generating Noir code, because it preserves the original Noir type names), we use the typescript ContractAbi struct to generate the selector, because the pre-existing `generateFunctionSelector` function expects the `AbiParameter` type. + * @returns functionData, but mutated to add a `selector` for every function. + */ +function addFunctionSelectors(functionData: FuncData[], abi: ContractAbi) { + return functionData.map(f => { + const fAbi = abi.functions.find(fAbi => fAbi.name === f.name); + if (!fAbi) { + log( + `Couldn't find fn '${f.name}' (which was found by parsing contract '${abi.name}' in the json abi's functons.`, + ); + return f; + } + // throw new Error( + // `Couldn't find fn '${f.name}' (which was found by parsing contract '${abi.name}' in the json abi's functons.`, + // ); + if (f.selector) { + throw new Error( + `Didn't expect function ${f.name} to already have a selector to have been calculated and assigned!`, + ); + } + return Object.assign(f, { + selector: `0x${generateFunctionSelector(fAbi.name, fAbi.parameters).toString('hex')}`, + }); + }); +} + +/** + * Parse and extract information about each function in the source code. We do this (rather than relying on the json artifact/abi, because the abi is lossy, in that it doesn't preserve the names of the original Noir types, which are useful for generating more Noir code with those same types). + * @param sourceCode - the original Noir Contract file. + * @param abi - The ContractAbi of the Noir Contract, as generated in the `copy_output.ts` file. + * @returns - FuncData for every function in the Noir contract. + * TODO: It doesn't process public (`open`) functions yet! + */ +function generateFunctionData(sourceCode: string, abi: ContractAbi) { + let functionData = extractFunctionData(sourceCode); + // Populate the function selector fields of the function data. + functionData = addFunctionSelectors(functionData, abi); + log('functionData', functionData); + return functionData; +} + +/** + * Code generation. + * Declare the `serialised_args` array. + * @param structuredParams - the parameters for a particular Noir Contract function. We'll make use of the param types to generate `_SERIALISED_LEN` global fields. This code generator relies on such globals existing in the `noir-libs/.../types/` directory (which is temporary, until we have traits which can enforce the existence of serialisation methods). + * @returns a code string. + */ +function declareSerialisedArgs(structuredParams: StructuredParam[]) { + const serialisedLengthNames = structuredParams.map(p => `${p.type.toUpperCase()}_SERIALISED_LEN`); + return ` + let mut serialised_args = [0; ${serialisedLengthNames.join(' + ')}]; +`; +} + +/** + * Code generation. + * Populate the `serialised_args` array with serialised versions of the contract's args. For each param of this function, this function will be called, generating a chunk of code which will populate some more of the `serialised_args` array. + * @param structuredParam - a parameter of a particular Noir Contract function. We'll make use of the param's name and type to generate the name of (and call) the serialisation method. This code generator relies on such methods existing in the `noir-libs/.../types/` directory (which is temporary, until we have traits which can enforce the existence of serialisation methods). + * @returns a code string. + */ +function generateSerialisationChunk(structuredParam: StructuredParam) { + const { name, type } = structuredParam; + return ` + let serialise${type} = ${type}SerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialise${type}(${name}), + spread_index + ); + spread_index += ${type.toUpperCase()}_SERIALISED_LEN; + `; +} + +/** + * Code generation. + * Call a function of the contract being described by this contract interface. + * @param selector - the function selector of a function + * @returns a code string. + */ +function generateCallStatement(selector: string) { + return ` + context.call_private_function(self.address, ${selector}, serialised_args).public_inputs.return_values + `; +} + +/** + * Code generation. + * Generate a function interface for a particular function of the Noir Contract being processed. This function will be a method of the ContractInterface struct being created here. + * @param functionData - data relating to the function, which can be used to generate a callable Noir Function. + * @returns a code string. + */ +function generateFunctionInterface(functionData: FuncData) { + const { name, parameters, structuredParameters, selector } = functionData; + + const serialisedArgsDeclaration = declareSerialisedArgs(structuredParameters!); + const serialisationChunks = structuredParameters!.map(generateSerialisationChunk); + const callStatement = generateCallStatement(selector!); + + return ` + fn ${name}( + self, + context: &mut Context, + ${parameters.join(',\n\t\t')} + ) -> [Field; RETURN_VALUES_LENGTH] { + ${serialisedArgsDeclaration} + + let mut spread_index: Field = 0; + + ${serialisationChunks.join('\n')} + + ${callStatement} + } + `; +} + +/** + * Find all `use` statements in a noir file, and copy each statement (`use ... ;`) into an `imports` array of statement strings. + * @param sourceCode - The original Noir Contract code. + * @returns An array of strings, with each string of the form: `use ... ;` + */ +function parseImports(sourceCode: string): string[] { + const importPattern = /use\s+([^;]+)/g; + const imports: string[] = []; + let match: RegExpExecArray | null; + while ((match = importPattern.exec(sourceCode)) !== null) { + const importStatement = match[1].trim(); + imports.push(importStatement); + } + return imports; +} + +/** + * Extracts all imported names, and returns those names as an array. + * This will be useful for itentifying any custom struct parameters which are used in functions of this contract. The struct names can then be matches with the corresponding import statement, so that the import statement can be copied across to the contract interface file. + * @param importStatement - a string of the form `use ... ;`. This could be a one-line import, or it could be an import statement with lots of nested braces, which imports many names into the file's scope. + * @returns - an array of imported names. + */ +function extractImportedNames(importStatement: string): string[] { + // Remove alias names delineated by `name as alias` + importStatement = importStatement.replace(/\s+as\s+\w+\s*,/g, ','); + + // rm whitespace + importStatement = importStatement.replace(/\s/g, ''); + + const names: string[] = []; + if (!importStatement.includes(`::`)) { + throw new Error(`Expected import statement to contain '::`); + } + + if (importStatement.includes(`}`)) { + // It's a braced import statement. + // Extract words ending in `,` or `}` + const importPattern = /(?:^|\W)(\w+)(?=,|\}|$)/g; + let match: RegExpExecArray | null; + while ((match = importPattern.exec(importStatement)) !== null) { + // log('match', match); + const name = match[1].trim(); + names.push(name); + } + } else { + // It's a simple 1-line import without braces, so grab the last word: + names.push(importStatement.split('::').at(-1)!); + } + + return names; +} + +/** + * Code generation. + * @returns - a string of code which will be needed in _every_ contract interface, regardless of the contract. (Hence the name 'static'). + */ +function generateStaticImports() { + return ` +use dep::std; +use dep::aztec::context::Context; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + +// --- + +`; +} + +/** + * Allows serialisation methods to be imported from other files. This expects any type which gets used as a function parameter to have a `serialise` function, a `deserialise` function and a `_SERIALISED_LEN` global. See `noir-libs/.../types/` for more details. This is a temporary solution, until Noir supports traits. + * @param type - The name of a Noir type / struct. + * @returns - A portion of an import path and names. + */ +function generateTypeSerialisationImport(type: string) { + return ` + ${type.toLowerCase()}_serialisation::{ + ${upperFirst(type)}SerialisationMethods, + ${type.toUpperCase()}_SERIALISED_LEN, + }, +`; +} + +// TODO: Identify custom types, and then generate imports for custom types' serialisation methods. +/** + * Code generation. + * Generates import statements for serialisation methods and globals. Only for Native Types so far. See the TODO for supporting the importing of custom structs. + * @param functionData - Data relating to every function in the Noir Contract. + * @returns - Code. An import statement for every native type. + */ +function generateNativeTypeSerialisationImports(functionData: FuncData[]) { + const types = functionData.flatMap(fnData => fnData.structuredParameters.map(p => p.type)); + const removeDuplicates = (arr: string[]) => [...new Set(arr)]; // we only want the import statement for a particular type to appear once, or compilation will fail! + const importStatements = removeDuplicates(types).map(generateTypeSerialisationImport); + return ` +use dep::aztec::types::type_serialisation::{ + ${importStatements.join('\n')} +}; +`; +} + +// TODO: this should probably go in noir-libs, instead. +/** + * Generate a `spread` function, which is needed for populating `serialised_args` from arrays of fields. + * @returns Code. + */ +function generateSpreadHelperFunction() { + return ` +// Spread an array into an array: +fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { + let mut j = at_index; + for i in 0..SRC_LEN { + target_arr[j] = src_arr[i]; + } + target_arr +} +`; +} + +/** + * Generate the main focus of this code generator: the contract interface struct. + * @param contractName - the name of the contract, as matches the original source file. + * @returns Code. + */ +function generateContractInterfaceStruct(contractName: string) { + return ` +struct ${contractName}_ContractInterface { + address: Field, +} +`; +} + +/** + * + * @param contractName - the name of the contract, as matches the original source file. + * @param functions - An array of strings, where each string is valid Noir code describing the function interface of one of the contract's functions (as generated via `generateFunctionInterface` above). + * @returns + */ +function generateContractInterfaceImpl(contractName: string, functions: string[]) { + return ` +impl ${contractName}_ContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + ${functions.join('\n')} +} +`; +} + +// YOU CAN QUICKLY TEST WITH: +// +// `NODE_OPTIONS=--no-warnings yarn ts-node --esm src/scripts/copy_output.ts zk_token` +// +// from the noir-contract/ dir of the monorepo. +// DANGER: this will write files, so make sure you run it from the correct place! + +/** + * Generates the typescript code to represent a contract. + * @param input - The compiled Noir artifact. + * @param abiImportPath - Optional path to import the ABI (if not set, will be required in the constructor). + * @returns The corresponding ts code. + */ +export function generateNoirContractInterface(sourceCode: string, abi: ContractAbi) { + const functionData: FuncData[] = generateFunctionData(sourceCode, abi); + + // Note: we'll use import statements for importing serialisation methods for custom parameter types. + const importStatements: string[] = parseImports(sourceCode); + const importedNames: string[] = importStatements.flatMap(extractImportedNames); + + const contractInterfaceStruct: string = generateContractInterfaceStruct(abi.name); + const contractInterfaceFunctions: string[] = functionData.map(f => generateFunctionInterface(f)); + const contractInterfaceImpl: string = generateContractInterfaceImpl(abi.name, contractInterfaceFunctions); + + return ` +/* Autogenerated file, do not edit! */ + +${generateStaticImports()} +${generateNativeTypeSerialisationImports(functionData)} +${generateSpreadHelperFunction()} +${contractInterfaceStruct} +${contractInterfaceImpl} +`; +} diff --git a/yarn-project/noir-compiler/src/typegen/index.ts b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts similarity index 97% rename from yarn-project/noir-compiler/src/typegen/index.ts rename to yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts index f2b1093ab73..a5edcf3bf19 100644 --- a/yarn-project/noir-compiler/src/typegen/index.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/typescript.ts @@ -47,6 +47,33 @@ function generateMethod(entry: FunctionAbi) { ${entry.name}: ((${args}) => ContractFunctionInteraction) & Pick;`; } +/** + * Generates a deploy method for this contract. + * @param input - ABI of the contract. + * @returns A type-safe deploy method in ts. + */ +function generateDeploy(input: ContractAbi) { + const ctor = input.functions.find(f => f.name === 'constructor'); + const args = (ctor?.parameters ?? []).map(generateParameter).join(', '); + const abiName = `${input.name}ContractAbi`; + + return ` + /** + * Creates a tx to deploy a new instance of this contract. + */ + public static deploy(rpc: AztecRPC, ${args}) { + return new DeployMethod<${input.name}Contract>(Point.ZERO, rpc, ${abiName}, Array.from(arguments).slice(1)); + } + + /** + * Creates a tx to deploy a new instance of this contract using the specified public key to derive the address. + */ + public static deployWithPublicKey(rpc: AztecRPC, publicKey: PublicKey, ${args}) { + return new DeployMethod<${input.name}Contract>(publicKey, rpc, ${abiName}, Array.from(arguments).slice(2)); + } + `; +} + /** * Generates the constructor by supplying the ABI to the parent class so the user doesn't have to. * @param name - Name of the contract to derive the ABI name from. @@ -93,33 +120,6 @@ function generateCreate(name: string) { }`; } -/** - * Generates a deploy method for this contract. - * @param input - ABI of the contract. - * @returns A type-safe deploy method in ts. - */ -function generateDeploy(input: ContractAbi) { - const ctor = input.functions.find(f => f.name === 'constructor'); - const args = (ctor?.parameters ?? []).map(generateParameter).join(', '); - const abiName = `${input.name}ContractAbi`; - - return ` - /** - * Creates a tx to deploy a new instance of this contract. - */ - public static deploy(rpc: AztecRPC, ${args}) { - return new DeployMethod<${input.name}Contract>(Point.ZERO, rpc, ${abiName}, Array.from(arguments).slice(1)); - } - - /** - * Creates a tx to deploy a new instance of this contract using the specified public key to derive the address. - */ - public static deployWithPublicKey(rpc: AztecRPC, publicKey: PublicKey, ${args}) { - return new DeployMethod<${input.name}Contract>(publicKey, rpc, ${abiName}, Array.from(arguments).slice(2)); - } - `; -} - /** * Generates a static getter for the contract's ABI. * @param name - Name of the contract used to derive name of the ABI import. @@ -142,7 +142,8 @@ function generateAbiGetter(name: string) { * @param abiImportPath - Optional path to import the ABI (if not set, will be required in the constructor). * @returns The corresponding ts code. */ -export function generateType(input: ContractAbi, abiImportPath?: string) { +export function generateTSContractInterface(input: ContractAbi, abiImportPath?: string) { + // `compact` removes all falsey values from an array const methods = compact(input.functions.filter(f => f.name !== 'constructor').map(generateMethod)); const deploy = abiImportPath && generateDeploy(input); const ctor = abiImportPath && generateConstructor(input.name); diff --git a/yarn-project/noir-compiler/src/index.ts b/yarn-project/noir-compiler/src/index.ts index e5d5e24730a..f3dcb1fccab 100644 --- a/yarn-project/noir-compiler/src/index.ts +++ b/yarn-project/noir-compiler/src/index.ts @@ -1,2 +1,3 @@ export * from './compile.js'; -export * from './typegen/index.js'; +export * from './contract-interface-gen/typescript.js'; +export * from './contract-interface-gen/noir.js'; diff --git a/yarn-project/noir-contracts/scripts/types.sh b/yarn-project/noir-contracts/scripts/types.sh index 7f0b6fa1dd5..c48b0e2d7ab 100755 --- a/yarn-project/noir-contracts/scripts/types.sh +++ b/yarn-project/noir-contracts/scripts/types.sh @@ -22,7 +22,8 @@ ROOT=$(pwd) write_import() { CONTRACT_NAME=$1 - + + # Convert to PascalCase if [ "$(uname)" = "Darwin" ]; then # sed \U doesn't work on mac NAME=$(echo $CONTRACT_NAME | perl -pe 's/(^|_)(\w)/\U$2/g') @@ -35,6 +36,8 @@ write_import() { write_export() { CONTRACT_NAME=$1 + + # Convert to PascalCase if [ "$(uname)" = "Darwin" ]; then # sed \U doesn't work on mac NAME=$(echo $CONTRACT_NAME | perl -pe 's/(^|_)(\w)/\U$2/g') diff --git a/yarn-project/noir-contracts/src/contracts/child_contract/src/storage.nr b/yarn-project/noir-contracts/src/contracts/child_contract/src/storage.nr index 634674859e4..5618cf75889 100644 --- a/yarn-project/noir-contracts/src/contracts/child_contract/src/storage.nr +++ b/yarn-project/noir-contracts/src/contracts/child_contract/src/storage.nr @@ -1,6 +1,6 @@ use dep::aztec::state_vars::public_state::PublicState; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FieldSerialisationMethods; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FIELD_SERIALISED_LEN; +use dep::aztec::types::type_serialisation::field_serialisation::FieldSerialisationMethods; +use dep::aztec::types::type_serialisation::field_serialisation::FIELD_SERIALISED_LEN; struct Storage { current_value: PublicState, diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml new file mode 100644 index 00000000000..806aa852d68 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml @@ -0,0 +1,7 @@ +[package] +name = "import_test_contract" +authors = [""] +compiler_version = "0.1" + +[dependencies] +aztec = { path = "../../../../noir-libs/noir-aztec" } \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr new file mode 100644 index 00000000000..cb737c74741 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -0,0 +1,29 @@ +mod zk_token_contract_interface; + +contract ImportTest { + use dep::aztec::abi; + use dep::aztec::abi::PrivateContextInputs; + use dep::aztec::context::Context; + use crate::zk_token_contract_interface::ZkToken_ContractInterface; + + // fn constructor( + // inputs: PrivateContextInputs, + // ) -> distinct pub abi::PrivateCircuitPublicInputs { + // } + + fn main( + inputs: PrivateContextInputs, + amount: Field, + sender: Field, + recipient: Field, + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let mut context = Context::new(inputs, abi::hash_args([amount, sender, recipient])); + + let zk_token_contract_address = 1234; + let zk_token_contract_instance = ZkToken_ContractInterface::at(zk_token_contract_address); + let _return_values = zk_token_contract_instance.transfer(&mut context, amount, sender, recipient); + + context.finish() + } +} + diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr new file mode 100644 index 00000000000..6bc0df24da1 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr @@ -0,0 +1,261 @@ + +/* Autogenerated file, do not edit! */ + + +use dep::std; +use dep::aztec::context::Context; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + +// --- + + + +use dep::aztec::types::type_serialisation::{ + + field_serialisation::{ + FieldSerialisationMethods, + FIELD_SERIALISED_LEN, + }, + + +}; + + +// Spread an array into an array: +fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { + let mut j = at_index; + for i in 0..SRC_LEN { + target_arr[j] = src_arr[i]; + } + target_arr +} + + +struct ZkToken_ContractInterface { + address: Field, +} + + +impl ZkToken_ContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn constructor( + self, + context: &mut Context, + initial_supply: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + + let mut spread_index: Field = 0; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(initial_supply), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(owner), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + + context.call_private_function(self.address, 0x968ffe4f, serialised_args).public_inputs.return_values + + } + + + fn mint( + self, + context: &mut Context, + amount: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + + let mut spread_index: Field = 0; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(amount), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(owner), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + + context.call_private_function(self.address, 0x1dc9c3c0, serialised_args).public_inputs.return_values + + } + + + fn transfer( + self, + context: &mut Context, + amount: Field, + sender: Field, + recipient: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + + let mut spread_index: Field = 0; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(amount), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(sender), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(recipient), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + + context.call_private_function(self.address, 0xdcd4c318, serialised_args).public_inputs.return_values + + } + + + // fn createClaims( + // self, + // context: &mut Context, + // amounts: [Field; 2], + // secrets: [Field; 2], + // sender: Field + // ) -> [Field; RETURN_VALUES_LENGTH] { + + // let mut serialised_args = [0; [FIELD; 2]_SERIALISED_LEN + [FIELD; 2]_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + + // let mut spread_index: Field = 0; + + + // let serialise[Field; 2] = [Field; 2]SerialisationMethods.serialise; + // serialised_args = spread( + // serialised_args, + // serialise[Field; 2](amounts), + // spread_index + // ); + // spread_index += [FIELD; 2]_SERIALISED_LEN; + + + // let serialise[Field; 2] = [Field; 2]SerialisationMethods.serialise; + // serialised_args = spread( + // serialised_args, + // serialise[Field; 2](secrets), + // spread_index + // ); + // spread_index += [FIELD; 2]_SERIALISED_LEN; + + + // let serialiseField = FieldSerialisationMethods.serialise; + // serialised_args = spread( + // serialised_args, + // serialiseField(sender), + // spread_index + // ); + // spread_index += FIELD_SERIALISED_LEN; + + + + // context.call_private_function(self.address, 0xd3ebc0af, serialised_args).public_inputs.return_values + + // } + + + fn claim( + self, + context: &mut Context, + amount: Field, + secret: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + + let mut spread_index: Field = 0; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(amount), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(secret), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(owner), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + + + context.call_private_function(self.address, 0x9f7bacc8, serialised_args).public_inputs.return_values + + } + +} + diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr new file mode 100644 index 00000000000..574cb54dee8 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr @@ -0,0 +1,125 @@ +use dep::std; +use dep::aztec::context::Context; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + +//------- + +// use dep::types::type_serialisation::TypeSerialisationInterface; + +use dep::aztec::types::type_serialisation::{ + field_serialisation::{ + FieldSerialisationMethods, + FIELD_SERIALISED_LEN, + }, +}; + +//------- + +// Spread an array into an array: +fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { + let mut j = at_index; + for i in 0..SRC_LEN { + target_arr[j] = src_arr[i]; + } + target_arr +} + +struct ZkToken_ContractInterface { + address: Field, +} + +impl ZkToken_ContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn mint( + self, + context: &mut Context, + amount: Field, + owner: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + let mut spread_index: Field = 0; + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(amount), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + // let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(owner), + spread_index + ); + // spread_index += FIELD_SERIALISED_LEN; + + context.call_private_function(self.address, 12345, serialised_args).public_inputs.return_values + } + + fn transfer( + self, + context: &mut Context, + amount: Field, + sender: Field, + recipient: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; + + let mut spread_index: Field = 0; + + let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(amount), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + // let serialiseField = FieldSerialisationMethods.serialise; + serialised_args = spread( + serialised_args, + serialiseField(sender), + spread_index + ); + spread_index += FIELD_SERIALISED_LEN; + + serialised_args = spread( + serialised_args, + serialiseField(recipient), + spread_index + ); + // spread_index += FIELD_SERIALISED_LEN; + + context.call_private_function(self.address, 12345, serialised_args).public_inputs.return_values + } +} + + +#[test] +// We simply test whether this compiles. We can't actually test it, because oracle calls are not supported by Noir tests yet. +fn test_transfer() { + let mut context: Context = std::unsafe::zeroed(); + + let zk_token_contract_instance = ZkToken_ContractInterface::at(1234); + + let deserialiseField = FieldSerialisationMethods.deserialise; + + let amount = deserialiseField([0; FIELD_SERIALISED_LEN]); + let sender = deserialiseField([0; FIELD_SERIALISED_LEN]); + let recipient = deserialiseField([0; FIELD_SERIALISED_LEN]); + + let return_values = zk_token_contract_instance.transfer( + &mut context, + amount, + sender, + recipient + ); +} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/lending_contract/src/storage.nr b/yarn-project/noir-contracts/src/contracts/lending_contract/src/storage.nr index 4f247028da6..61f021e06a7 100644 --- a/yarn-project/noir-contracts/src/contracts/lending_contract/src/storage.nr +++ b/yarn-project/noir-contracts/src/contracts/lending_contract/src/storage.nr @@ -1,8 +1,8 @@ use dep::aztec::state_vars::map::Map; use dep::aztec::state_vars::public_state::PublicState; -use dep::aztec::state_vars::type_serialisation::TypeSerialisationInterface; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FieldSerialisationMethods; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FIELD_SERIALISED_LEN; +use dep::aztec::types::type_serialisation::TypeSerialisationInterface; +use dep::aztec::types::type_serialisation::field_serialisation::FieldSerialisationMethods; +use dep::aztec::types::type_serialisation::field_serialisation::FIELD_SERIALISED_LEN; use dep::std::hash::pedersen; // Utility struct used to easily get a "id" for a private user that sits in the same diff --git a/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/storage.nr b/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/storage.nr index 5b5e0329dd6..b1cc0d49517 100644 --- a/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/storage.nr +++ b/yarn-project/noir-contracts/src/contracts/non_native_token_contract/src/storage.nr @@ -12,13 +12,17 @@ use dep::value_note::value_note::{ VALUE_NOTE_LEN, }; -use dep::aztec::state_vars::{ - map::Map, - set::Set, - public_state::PublicState, +use dep::aztec::{ + state_vars::{ + map::Map, + set::Set, + public_state::PublicState, + }, + types::type_serialisation::field_serialisation::{ + FIELD_SERIALISED_LEN, + FieldSerialisationMethods, + }, }; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FieldSerialisationMethods; -use dep::aztec::state_vars::type_serialisation::field_serialisation::FIELD_SERIALISED_LEN; struct Storage { balances: Map>, diff --git a/yarn-project/noir-contracts/src/contracts/public_token_contract/src/storage.nr b/yarn-project/noir-contracts/src/contracts/public_token_contract/src/storage.nr index f5c2a4f61ce..9513325c9fa 100644 --- a/yarn-project/noir-contracts/src/contracts/public_token_contract/src/storage.nr +++ b/yarn-project/noir-contracts/src/contracts/public_token_contract/src/storage.nr @@ -3,13 +3,14 @@ use dep::aztec::state_vars::{ map::Map, // highlight-start:PublicState public_state::PublicState, - type_serialisation::field_serialisation::{ - FieldSerialisationMethods, - FIELD_SERIALISED_LEN, - }, // highlight-end:PublicState }; +use dep::aztec::types::type_serialisation::field_serialisation::{ + FieldSerialisationMethods, + FIELD_SERIALISED_LEN, +}; + struct Storage { // highlight-next-line:PublicState balances: Map>, diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index 7e951210ee4..ee31453805f 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -1,6 +1,6 @@ -import { ABIParameter, ABIType, FunctionType } from '@aztec/foundation/abi'; +import { ABIParameter, ABIType, ContractAbi, FunctionAbi, FunctionType } from '@aztec/foundation/abi'; import { createConsoleLogger } from '@aztec/foundation/log'; -import { generateType } from '@aztec/noir-compiler'; +import { generateNoirContractInterface, generateTSContractInterface } from '@aztec/noir-compiler'; import { readFileSync, writeFileSync } from 'fs'; import camelCase from 'lodash.camelcase'; @@ -11,7 +11,7 @@ import { join as pathJoin } from 'path'; import mockedKeys from './mockedKeys.json' assert { type: 'json' }; -const STATEMENT_TYPES = ['type', 'params', 'return'] as const; +// const STATEMENT_TYPES = ['type', 'params', 'return'] as const; const log = createConsoleLogger('aztec:noir-contracts'); const PROJECT_CONTRACTS = [ @@ -40,19 +40,23 @@ function writeToProject(abi: any) { /** * Creates an Aztec function entry. - * @param type - The type of the function. - * @param params - The parameters of the function. + * @param type - The type of the function (secret | open | unconstrained). + * @param params - The parameters of the function ( name, type, visibility ). * @param returns - The return types of the function. * @param fn - The nargo function entry. * @returns The Aztec function entry. */ -function getFunction(type: FunctionType, params: ABIParameter[], returns: ABIType[], fn: any) { - if (!params) throw new Error(`ABI comment not found for function ${fn.name}`); +function getFunction(type: FunctionType, params: ABIParameter[], returns: ABIType, fn: any): FunctionAbi { + // if (!params) throw new Error(`ABI comment not found for function ${fn.name}`); + // If the function is not unconstrained, the first item is inputs or CallContext which we should omit if (type !== FunctionType.UNCONSTRAINED) params = params.slice(1); + // If the function is not secret, drop any padding from the end - if (type !== FunctionType.SECRET && params.length > 0 && params[params.length - 1].name.endsWith('padding')) + // TODO: I can't find a reference to 'padding' anywhere? + if (type !== FunctionType.SECRET && params.length > 0 && params[params.length - 1].name.endsWith('padding')) { params = params.slice(0, params.length - 1); + } return { name: fn.name, @@ -60,7 +64,7 @@ function getFunction(type: FunctionType, params: ABIParameter[], returns: ABITyp isInternal: fn.is_internal, parameters: params, // If the function is secret, the return is the public inputs, which should be omitted - returnTypes: type === FunctionType.SECRET ? [] : returns, + returnTypes: type === FunctionType.SECRET ? [] : [returns], bytecode: fn.bytecode, // verificationKey: Buffer.from(fn.verification_key).toString('hex'), verificationKey: mockedKeys.verificationKey, @@ -69,36 +73,21 @@ function getFunction(type: FunctionType, params: ABIParameter[], returns: ABITyp /** * Creates the Aztec function entries from the source code and the nargo output. - * @param source - The source code of the contract. - * @param output - The nargo output. + * @param sourceCode - The source code of the contract. + * @param buildJson - The nargo output. * @returns The Aztec function entries. */ -function getFunctions(source: string, output: any) { - const abiComments = Array.from(source.matchAll(/\/\/\/ ABI (\w+) (params|return|type) (.+)/g)).map(match => ({ - functionName: match[1], - statementType: match[2], - value: JSON.parse(match[3]), - })); - - return output.functions +function getFunctions(sourceCode: string, buildJson: any): FunctionAbi[] { + /** + * Sort functions alphabetically, by name. + * Remove the proving key field of the function. + * + */ + return buildJson.functions .sort((fnA: any, fnB: any) => fnA.name.localeCompare(fnB.name)) .map((fn: any) => { delete fn.proving_key; - const thisFunctionAbisComments = abiComments - .filter(abi => abi.functionName === fn.name) - .reduce( - (acc, comment) => ({ - ...acc, - [comment.statementType]: comment.value, - }), - {} as Record<(typeof STATEMENT_TYPES)[number], any>, - ); - return getFunction( - thisFunctionAbisComments.type || (fn.function_type.toLowerCase() as FunctionType), - thisFunctionAbisComments.params || fn.abi.parameters, - thisFunctionAbisComments.return || [fn.abi.return_type], - fn, - ); + return getFunction(fn.function_type.toLowerCase() as FunctionType, fn.abi.parameters, fn.abi.return_type, fn); }); } @@ -106,27 +95,39 @@ const main = () => { const name = process.argv[2]; if (!name) throw new Error(`Missing argument contract name`); - const folderName = `${snakeCase(name)}_contract`; - const folderPath = `src/contracts/${folderName}`; - const source = readFileSync(`${folderPath}/src/main.nr`).toString(); - const contractName = process.argv[3] ?? upperFirst(camelCase(name)); - const build = JSON.parse(readFileSync(`${folderPath}/target/${folderName}-${contractName}.json`).toString()); - const artifacts = 'src/artifacts'; + const projectName = `${snakeCase(name)}_contract`; + const projectDirPath = `src/contracts/${projectName}`; + const sourceCodeFilePath = `${projectDirPath}/src/main.nr`; + const sourceCode = readFileSync(sourceCodeFilePath).toString(); - const abi = { - name: build.name, - functions: getFunctions(source, build), + const contractName = upperFirst(camelCase(name)); + const buildJsonFilePath = `${projectDirPath}/target/${projectName}-${contractName}.json`; + const buildJson = JSON.parse(readFileSync(buildJsonFilePath).toString()); + + // Remove extraneous information from the buildJson (which was output by Nargo) to hone in on the function data we actually care about: + const artifactJson: ContractAbi = { + name: buildJson.name, + functions: getFunctions(sourceCode, buildJson), }; - const exampleFile = `${artifacts}/${snakeCase(name)}_contract.json`; - writeFileSync(exampleFile, JSON.stringify(abi, null, 2) + '\n'); - log(`Written ${exampleFile}`); + // Write the artifact: + const artifactsDir = 'src/artifacts'; + const artifactDestFilePath = `${artifactsDir}/${snakeCase(name)}_contract.json`; + writeFileSync(artifactDestFilePath, JSON.stringify(artifactJson, null, 2) + '\n'); + log(`Written ${artifactDestFilePath}`); + + // Write some artifacts to other packages in the monorepo: + writeToProject(artifactJson); - writeToProject(abi); + // Write a .ts contract interface, for consumption by the typescript code + const tsInterfaceDestFilePath = `src/types/${name}.ts`; + writeFileSync(tsInterfaceDestFilePath, generateTSContractInterface(artifactJson, '../artifacts/index.js')); + log(`Written ${tsInterfaceDestFilePath}`); - const typeFile = `src/types/${name}.ts`; - writeFileSync(typeFile, generateType(abi, '../artifacts/index.js')); - log(`Written ${typeFile}`); + // Write a .nr contract interface, for consumption by other Noir Contracts + const noirInterfaceDestFilePath = `${projectDirPath}/src/${projectName}_interface.nr`; + writeFileSync(noirInterfaceDestFilePath, generateNoirContractInterface(sourceCode, artifactJson)); + log(`Written ${noirInterfaceDestFilePath}`); }; try { diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars.nr b/yarn-project/noir-libs/noir-aztec/src/state_vars.nr index 0148c8a5459..b35df0399d1 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars.nr +++ b/yarn-project/noir-libs/noir-aztec/src/state_vars.nr @@ -1,6 +1,5 @@ mod immutable_singleton; mod map; mod public_state; -mod type_serialisation; mod set; mod singleton; \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars/public_state.nr b/yarn-project/noir-libs/noir-aztec/src/state_vars/public_state.nr index b4cae24ecfa..900255b5625 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars/public_state.nr +++ b/yarn-project/noir-libs/noir-aztec/src/state_vars/public_state.nr @@ -1,6 +1,6 @@ use crate::oracle::storage::storage_read; use crate::oracle::storage::storage_write; -use crate::state_vars::type_serialisation::TypeSerialisationInterface; +use crate::types::type_serialisation::TypeSerialisationInterface; struct PublicState { storage_slot: Field, diff --git a/yarn-project/noir-libs/noir-aztec/src/types.nr b/yarn-project/noir-libs/noir-aztec/src/types.nr index 4614a0c05bd..babc0a6298c 100644 --- a/yarn-project/noir-libs/noir-aztec/src/types.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types.nr @@ -1,3 +1,4 @@ +mod option; // This can/should be moved out into an official noir library mod point; mod vec; // This can/should be moved out into an official noir library -mod option; // This can/should be moved out into an official noir library \ No newline at end of file +mod type_serialisation; \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/types/point.nr b/yarn-project/noir-libs/noir-aztec/src/types/point.nr index 81e48422671..47ea73ffcde 100644 --- a/yarn-project/noir-libs/noir-aztec/src/types/point.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types/point.nr @@ -1,3 +1,5 @@ +use crate::types::type_serialisation::TypeSerialisationInterface; + struct Point { x: Field, y: Field, @@ -7,4 +9,22 @@ impl Point { fn new(x: Field, y: Field) -> Self { Point { x, y } } -} \ No newline at end of file +} + +global POINT_SERIALISED_LEN: Field = 2; + +fn deserialisePoint(fields: [Field; POINT_SERIALISED_LEN]) -> Point { + Point { + x: fields[0], + y: fields[1], + } +} + +fn serialisePoint(point: Point) -> [Field; POINT_SERIALISED_LEN] { + [point.x, point.y] +} + +global PointSerialisationMethods = TypeSerialisationInterface { + deserialise: deserialisePoint, + serialise: serialisePoint, +}; \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation.nr b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation.nr similarity index 92% rename from yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation.nr rename to yarn-project/noir-libs/noir-aztec/src/types/type_serialisation.nr index d76e52102a5..dfe738d2c38 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation.nr @@ -1,3 +1,4 @@ +mod bool_serialisation; mod field_serialisation; mod u32_serialisation; diff --git a/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/bool_serialisation.nr b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/bool_serialisation.nr new file mode 100644 index 00000000000..734f725f35b --- /dev/null +++ b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/bool_serialisation.nr @@ -0,0 +1,16 @@ +use crate::types::type_serialisation::TypeSerialisationInterface; + +global BOOL_SERIALISED_LEN: Field = 1; + +fn deserialiseBool(fields: [Field; BOOL_SERIALISED_LEN]) -> bool { + fields[0] as bool +} + +fn serialiseBool(value: bool) -> [Field; BOOL_SERIALISED_LEN] { + [value as Field] +} + +global BoolSerialisationMethods = TypeSerialisationInterface { + deserialise: deserialiseBool, + serialise: serialiseBool, +}; \ No newline at end of file diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/field_serialisation.nr b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/field_serialisation.nr similarity index 82% rename from yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/field_serialisation.nr rename to yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/field_serialisation.nr index 5352d0e3e5f..5fcaf370523 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/field_serialisation.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/field_serialisation.nr @@ -1,4 +1,4 @@ -use crate::state_vars::type_serialisation::TypeSerialisationInterface; +use crate::types::type_serialisation::TypeSerialisationInterface; global FIELD_SERIALISED_LEN: Field = 1; diff --git a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/u32_serialisation.nr b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/u32_serialisation.nr similarity index 82% rename from yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/u32_serialisation.nr rename to yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/u32_serialisation.nr index 21178a167f2..dd00ebfedfd 100644 --- a/yarn-project/noir-libs/noir-aztec/src/state_vars/type_serialisation/u32_serialisation.nr +++ b/yarn-project/noir-libs/noir-aztec/src/types/type_serialisation/u32_serialisation.nr @@ -1,4 +1,4 @@ -use crate::state_vars::type_serialisation::TypeSerialisationInterface; +use crate::types::type_serialisation::TypeSerialisationInterface; global U32_SERIALISED_LEN: Field = 1; From 49d272159f1b27521ad34081c7f1622ccac19dff Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 9 Aug 2023 12:47:52 -0300 Subject: [PATCH 02/15] Generate noir interface from ABI code only --- .../src/client/private_execution.test.ts | 47 +- yarn-project/noir-compiler/package.json | 8 + .../src/contract-interface-gen/noir.ts | 484 ++++++------------ .../contracts/import_test_contract/Nargo.toml | 1 + .../import_test_contract/src/main.nr | 52 +- .../src/test_contract_interface.nr | 1 + .../src/zk_token_contract_interface.nr | 261 ---------- .../src/zk_token_contract_interface_MASTER.nr | 125 ----- .../src/contracts/test_contract/src/main.nr | 39 ++ .../src/test_contract_interface.nr | 145 ++++++ .../noir-contracts/src/scripts/copy_output.ts | 14 +- yarn-project/yarn.lock | 17 + 12 files changed, 461 insertions(+), 733 deletions(-) create mode 120000 yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr delete mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr delete mode 100644 yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr create mode 100644 yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr diff --git a/yarn-project/acir-simulator/src/client/private_execution.test.ts b/yarn-project/acir-simulator/src/client/private_execution.test.ts index 43a69221d78..ffe6c12abc7 100644 --- a/yarn-project/acir-simulator/src/client/private_execution.test.ts +++ b/yarn-project/acir-simulator/src/client/private_execution.test.ts @@ -18,6 +18,7 @@ import { computeContractAddressFromPartial, computeSecretMessageHash, computeUniqueCommitment, + computeVarArgsHash, siloCommitment, } from '@aztec/circuits.js/abis'; import { pedersenPlookupCommitInputs } from '@aztec/circuits.js/barretenberg'; @@ -32,6 +33,7 @@ import { DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { AppendOnlyTree, Pedersen, StandardTree, newTree } from '@aztec/merkle-tree'; import { ChildContractAbi, + ImportTestContractAbi, NonNativeTokenContractAbi, ParentContractAbi, PendingCommitmentsContractAbi, @@ -641,7 +643,50 @@ describe('Private Execution test suite', () => { }); }); - describe('consuming Messages', () => { + describe('nested calls through autogenerated interface', () => { + let args: any[]; + let argsHash: Fr; + let testCodeGenAbi: FunctionAbi; + + beforeAll(async () => { + // These args should match the ones hardcoded in importer contract + const dummyNote = { amount: 1, secretHash: 2 }; + const deepStruct = { aField: 1, aBool: true, aNote: dummyNote, manyNotes: [dummyNote, dummyNote, dummyNote] }; + args = [1, true, 1, [1, 2], dummyNote, deepStruct]; + testCodeGenAbi = TestContractAbi.functions.find(f => f.name === 'testCodeGen')!; + const serialisedArgs = encodeArguments(testCodeGenAbi, args); + argsHash = await computeVarArgsHash(await CircuitsWasm.get(), serialisedArgs); + }); + + it('test function should be directly callable', async () => { + logger(`Calling testCodeGen function`); + const result = await runSimulator({ args, abi: testCodeGenAbi }); + + expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(argsHash); + }); + + it('test function should be callable through autogenerated interface', async () => { + const importerAddress = AztecAddress.random(); + const testAddress = AztecAddress.random(); + const parentAbi = ImportTestContractAbi.functions.find(f => f.name === 'main')!; + const testCodeGenSelector = generateFunctionSelector(testCodeGenAbi.name, testCodeGenAbi.parameters); + + oracle.getFunctionABI.mockResolvedValue(testCodeGenAbi); + oracle.getPortalContractAddress.mockResolvedValue(EthAddress.ZERO); + + logger(`Calling importer main function`); + const args = [testAddress]; + const result = await runSimulator({ args, abi: parentAbi, origin: importerAddress }); + + expect(result.callStackItem.publicInputs.returnValues[0]).toEqual(argsHash); + expect(oracle.getFunctionABI.mock.calls[0]).toEqual([testAddress, testCodeGenSelector]); + expect(oracle.getPortalContractAddress.mock.calls[0]).toEqual([testAddress]); + expect(result.nestedExecutions).toHaveLength(1); + expect(result.nestedExecutions[0].callStackItem.publicInputs.returnValues[0]).toEqual(argsHash); + }); + }); + + describe('consuming messages', () => { const contractAddress = defaultContractAddress; const recipientPk = PrivateKey.fromString('0c9ed344548e8f9ba8aa3c9f8651eaa2853130f6c1e9c050ccf198f7ea18a7ec'); diff --git a/yarn-project/noir-compiler/package.json b/yarn-project/noir-compiler/package.json index 93c3b9a6e86..24c5ace3f5f 100644 --- a/yarn-project/noir-compiler/package.json +++ b/yarn-project/noir-compiler/package.json @@ -40,7 +40,11 @@ "@noir-lang/noir_wasm": "0.5.1-9740f54", "commander": "^9.0.0", "fs-extra": "^11.1.1", + "lodash.camelcase": "^4.3.0", + "lodash.capitalize": "^4.2.1", "lodash.compact": "^3.0.1", + "lodash.times": "^4.3.2", + "lodash.upperfirst": "^4.3.1", "toml": "^3.0.0", "tslib": "^2.4.0" }, @@ -49,7 +53,11 @@ "@rushstack/eslint-patch": "^1.1.4", "@types/fs-extra": "^11.0.1", "@types/jest": "^29.5.0", + "@types/lodash.camelcase": "^4.3.7", + "@types/lodash.capitalize": "^4.2.7", "@types/lodash.compact": "^3.0.7", + "@types/lodash.times": "^4.3.7", + "@types/lodash.upperfirst": "^4.3.7", "@types/node": "^18.7.23", "jest": "^29.5.0", "ts-jest": "^29.1.0", diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index 0acf9896d7c..a15d17d5256 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -1,350 +1,155 @@ -import { ABIParameter, ContractAbi, FunctionAbi, generateFunctionSelector } from '@aztec/foundation/abi'; - -import { log } from 'console'; +import { + ABIParameter, + ABIVariable, + ContractAbi, + FunctionAbi, + StructType, + generateFunctionSelector, +} from '@aztec/foundation/abi'; + +import camelCase from 'lodash.camelcase'; +import compact from 'lodash.compact'; +import times from 'lodash.times'; import upperFirst from 'lodash.upperfirst'; /** - * Structured data describing a Noir Contract function parameter. - * We sometimes make use of this (instead of the Contract's JSON ABI) when generating Noir code, because the `type` field is a Noir type (as opposed to a typescript type or an abi type, which seem to be 'lossy', in terms of expressing the type). - */ -type StructuredParam = { - /** - * parameter name - */ - name: string; - /** - * parameter type (this is a Noir type: not a typescript type or a type as formatted in the json abi, but a type that you write when writing actual Noir code). - */ - type: string; -}; - -/** - * Structured data describing a Noir Contract function. - * We sometimes make use of this (instead of the Contract's JSON ABI) when generating Noir code, because the structured param field is easier to generate Noir code from, and it includes a selector. - * TODO: returnTypes isn't used yet... + * Generates a call to a private function using the context. + * @param selector - The function selector of a function + * @returns A code string. */ -type FuncData = { - name: string; - parameters: string[]; - structuredParameters: StructuredParam[]; - returnTypes: string[]; - selector: string | null; -}; +function generateCallStatement(selector: string) { + return ` + context.call_private_function(self.address, ${selector}, serialised_args)`; +} /** - * Remove comments from a Noir contract file. - * @param sourceCode - the original Noir Contract file. - * @returns - a version of the Noir Contract file, but with comments removed (for easier later parsing). + * Formats a fragment of a struct name. + * @param str - A fragment. + * @returns A capitalised camelcase string. */ -function removeComments(sourceCode: string): string { - // Hybrid comments pattern `//******/` - const hybridCommentPattern = /\/\/\*+\/\n/g; - sourceCode = sourceCode.replace(hybridCommentPattern, ''); - - // Remove block comments (/* ... */) - const blockCommentPattern = /\/\*[\s\S]*?\*\//g; - sourceCode = sourceCode.replace(blockCommentPattern, ''); - - // Remove line comments (// ...) - const lineCommentPattern = /\/\/[^\n]*(?:\n|$)/g; - sourceCode = sourceCode.replace(lineCommentPattern, ''); - - return sourceCode; +function formatStructNameFragment(str: string) { + return upperFirst(camelCase(str)); } /** - * Convert strings of the form `param_name: NoirTypeName` to a StructuredParam. - * @param paramStrings - `param_name: NoirTypeName` - * @returns the same data, but in a StructuredParam format. + * Returns a struct name given a list of fragments. + * @param fragments - Fragments. + * @returns The concatenation of the capitalised fragments. */ -function getStructuredParameters(paramStrings: string[]): StructuredParam[] { - const structuredParams = paramStrings - .map(item => item.split(':').map(part => part.trim())) - .map(item => { - return { name: item[0], type: item[1] }; - }); - return structuredParams; +function getStructName(...fragments: string[]) { + return fragments.map(formatStructNameFragment).join('') + 'Struct'; } /** - * Parses the Noir Contact source code. - * Removes comments. - * Then searches for all PRIVATE functions, matching: - * 1: private function name - * 2: parameters. - * Parameters are read directly as `param_name: NoirTypeName`, and then are converted into a StructuredParam. - * NOTE: return types are not processed, because a PRIVATE Noir Contract function does not return its return_values. The thing that's returned is always a `PrivateCircuitPublicInputs` struct, which won't be transpiled over into our contract interface. - * @param sourceCode - the original Noir Contract file. - * @returns - FuncData for every PRIVATE function in the Noir contract. + * Returns a Noir type name for the given ABI variable. + * @param param - ABI variable to translate to a Noir type name. + * @param parentNames - Function name or parent structs or arrays to use for struct qualified names. + * @returns A valid Noir basic type name or a name for a struct. */ -function extractPrivateFunctionData(sourceCode: string): FuncData[] { - const functionPattern = /fn\s+(\w+)\s*\(\s*([^)]*)\s*\)\s*->\s*distinct\s+pub\s+abi::PrivateCircuitPublicInputs/g; - const functionData: FuncData[] = []; - let match: RegExpExecArray | null; - - while ((match = functionPattern.exec(sourceCode)) !== null) { - const functionName = match[1]; - const paramsWithoutComments = removeComments(match[2]); - const parameters = paramsWithoutComments - .split(',') - .map(param => param.trim()) - .filter(param => param !== '') - .filter(param => !param.includes('PrivateContextInputs')); // Remove this type of param, as it isn't needed in the contract interface. - functionData.push({ - name: functionName, - parameters, - structuredParameters: getStructuredParameters(parameters), - returnTypes: [], // TODO - selector: null, - }); +function getTypeName(param: ABIVariable, ...parentNames: string[]): string { + const type = param.type; + switch (type.kind) { + case 'field': + return 'Field'; + case 'boolean': + return 'bool'; + case 'integer': + return `${type.sign === 'signed' ? 'i' : 'u'}${type.width}`; + case 'string': + throw new Error(`Strings not supported yet`); + case 'array': + return `[${getTypeName({ name: param.name, type: type.type }, ...parentNames)};${type.length}]`; + case 'struct': + return getStructName(param.name, ...parentNames); + default: + throw new Error(`Unknown type ${type}`); } - - return functionData; } /** - * Parse and extract information about each function in the source code. We do this (rather than relying on the json artifact/abi, because the abi is lossy, in that it doesn't preserve the names of the original Noir types, which are useful for generating more Noir code with those same types). - * @param sourceCode - the original Noir Contract file. - * @returns - FuncData for every function in the Noir contract. - * TODO: It doesn't process public (`open`) functions yet! + * Generates a parameter string. + * @param param - ABI parameter. + * @param functionData - Parent function. + * @returns A Noir string with the param name and type to be used in a function call. */ -function extractFunctionData(sourceCode: string): FuncData[] { - return extractPrivateFunctionData(sourceCode); +function generateParameter(param: ABIParameter, functionData: FunctionAbi) { + const typename = getTypeName(param, functionData.name); + return `${param.name}: ${typename}`; } /** - * Populate the `selector` field of FuncData, for every function in our contract. - * @param functionData - An array of data describing the name, selector, params, and return types of every function. The - * @param abi - The ContractAbi of the Noir Contract, as generated in the `copy_output.ts` file. Rather than use our custom FuncData (which is very useful for generating Noir code, because it preserves the original Noir type names), we use the typescript ContractAbi struct to generate the selector, because the pre-existing `generateFunctionSelector` function expects the `AbiParameter` type. - * @returns functionData, but mutated to add a `selector` for every function. + * Collects all parameters for a given function and flattens them according to how they should be serialised. + * @param parameters - Paramters for a function. + * @returns List of parameters flattened to basic data types. */ -function addFunctionSelectors(functionData: FuncData[], abi: ContractAbi) { - return functionData.map(f => { - const fAbi = abi.functions.find(fAbi => fAbi.name === f.name); - if (!fAbi) { - log( - `Couldn't find fn '${f.name}' (which was found by parsing contract '${abi.name}' in the json abi's functons.`, +function collectParametersForSerialisation(parameters: ABIVariable[]) { + const flattened: string[] = []; + for (const parameter of parameters) { + const { name } = parameter; + if (parameter.type.kind === 'array') { + const nestedType = parameter.type.type; + const nested = times(parameter.type.length, i => + collectParametersForSerialisation([{ name: `${name}[${i}]`, type: nestedType }]), ); - return f; - } - // throw new Error( - // `Couldn't find fn '${f.name}' (which was found by parsing contract '${abi.name}' in the json abi's functons.`, - // ); - if (f.selector) { - throw new Error( - `Didn't expect function ${f.name} to already have a selector to have been calculated and assigned!`, + flattened.push(...nested.flat()); + } else if (parameter.type.kind === 'struct') { + const nested = parameter.type.fields.map(field => + collectParametersForSerialisation([{ name: `${name}.${field.name}`, type: field.type }]), ); + flattened.push(...nested.flat()); + } else if (parameter.type.kind === 'string') { + throw new Error(`String not yet supported`); + } else if (parameter.type.kind === 'field') { + flattened.push(name); + } else { + flattened.push(`${name} as Field`); } - return Object.assign(f, { - selector: `0x${generateFunctionSelector(fAbi.name, fAbi.parameters).toString('hex')}`, - }); - }); -} - -/** - * Parse and extract information about each function in the source code. We do this (rather than relying on the json artifact/abi, because the abi is lossy, in that it doesn't preserve the names of the original Noir types, which are useful for generating more Noir code with those same types). - * @param sourceCode - the original Noir Contract file. - * @param abi - The ContractAbi of the Noir Contract, as generated in the `copy_output.ts` file. - * @returns - FuncData for every function in the Noir contract. - * TODO: It doesn't process public (`open`) functions yet! - */ -function generateFunctionData(sourceCode: string, abi: ContractAbi) { - let functionData = extractFunctionData(sourceCode); - // Populate the function selector fields of the function data. - functionData = addFunctionSelectors(functionData, abi); - log('functionData', functionData); - return functionData; -} - -/** - * Code generation. - * Declare the `serialised_args` array. - * @param structuredParams - the parameters for a particular Noir Contract function. We'll make use of the param types to generate `_SERIALISED_LEN` global fields. This code generator relies on such globals existing in the `noir-libs/.../types/` directory (which is temporary, until we have traits which can enforce the existence of serialisation methods). - * @returns a code string. - */ -function declareSerialisedArgs(structuredParams: StructuredParam[]) { - const serialisedLengthNames = structuredParams.map(p => `${p.type.toUpperCase()}_SERIALISED_LEN`); - return ` - let mut serialised_args = [0; ${serialisedLengthNames.join(' + ')}]; -`; -} - -/** - * Code generation. - * Populate the `serialised_args` array with serialised versions of the contract's args. For each param of this function, this function will be called, generating a chunk of code which will populate some more of the `serialised_args` array. - * @param structuredParam - a parameter of a particular Noir Contract function. We'll make use of the param's name and type to generate the name of (and call) the serialisation method. This code generator relies on such methods existing in the `noir-libs/.../types/` directory (which is temporary, until we have traits which can enforce the existence of serialisation methods). - * @returns a code string. - */ -function generateSerialisationChunk(structuredParam: StructuredParam) { - const { name, type } = structuredParam; - return ` - let serialise${type} = ${type}SerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialise${type}(${name}), - spread_index - ); - spread_index += ${type.toUpperCase()}_SERIALISED_LEN; - `; + } + return flattened; } /** - * Code generation. - * Call a function of the contract being described by this contract interface. - * @param selector - the function selector of a function - * @returns a code string. + * Generates Noir code for serialising the parameters into an array of fields. + * @param parameters - Parameters to serialise. + * @returns The serialisation code. */ -function generateCallStatement(selector: string) { - return ` - context.call_private_function(self.address, ${selector}, serialised_args).public_inputs.return_values - `; +function generateSerialisation(parameters: ABIParameter[]) { + const flattened = collectParametersForSerialisation(parameters); + const declaration = ` let mut serialised_args = [0; ${flattened.length}];`; + const lines = flattened.map((param, i) => ` serialised_args[${i}] = ${param};`); + return [declaration, ...lines].join('\n'); } /** - * Code generation. * Generate a function interface for a particular function of the Noir Contract being processed. This function will be a method of the ContractInterface struct being created here. * @param functionData - data relating to the function, which can be used to generate a callable Noir Function. * @returns a code string. */ -function generateFunctionInterface(functionData: FuncData) { - const { name, parameters, structuredParameters, selector } = functionData; - - const serialisedArgsDeclaration = declareSerialisedArgs(structuredParameters!); - const serialisationChunks = structuredParameters!.map(generateSerialisationChunk); - const callStatement = generateCallStatement(selector!); +function generateFunctionInterface(functionData: FunctionAbi) { + const { name, parameters } = functionData; + const selector = '0x' + generateFunctionSelector(name, parameters).toString('hex'); + const serialisation = generateSerialisation(parameters); + const callStatement = generateCallStatement(selector); + const allParams = ['self', 'context: &mut Context', ...parameters.map(p => generateParameter(p, functionData))]; return ` fn ${name}( - self, - context: &mut Context, - ${parameters.join(',\n\t\t')} + ${allParams.join(',\n ')} ) -> [Field; RETURN_VALUES_LENGTH] { - ${serialisedArgsDeclaration} - - let mut spread_index: Field = 0; - - ${serialisationChunks.join('\n')} - - ${callStatement} +${serialisation} +${callStatement} } `; } /** - * Find all `use` statements in a noir file, and copy each statement (`use ... ;`) into an `imports` array of statement strings. - * @param sourceCode - The original Noir Contract code. - * @returns An array of strings, with each string of the form: `use ... ;` - */ -function parseImports(sourceCode: string): string[] { - const importPattern = /use\s+([^;]+)/g; - const imports: string[] = []; - let match: RegExpExecArray | null; - while ((match = importPattern.exec(sourceCode)) !== null) { - const importStatement = match[1].trim(); - imports.push(importStatement); - } - return imports; -} - -/** - * Extracts all imported names, and returns those names as an array. - * This will be useful for itentifying any custom struct parameters which are used in functions of this contract. The struct names can then be matches with the corresponding import statement, so that the import statement can be copied across to the contract interface file. - * @param importStatement - a string of the form `use ... ;`. This could be a one-line import, or it could be an import statement with lots of nested braces, which imports many names into the file's scope. - * @returns - an array of imported names. - */ -function extractImportedNames(importStatement: string): string[] { - // Remove alias names delineated by `name as alias` - importStatement = importStatement.replace(/\s+as\s+\w+\s*,/g, ','); - - // rm whitespace - importStatement = importStatement.replace(/\s/g, ''); - - const names: string[] = []; - if (!importStatement.includes(`::`)) { - throw new Error(`Expected import statement to contain '::`); - } - - if (importStatement.includes(`}`)) { - // It's a braced import statement. - // Extract words ending in `,` or `}` - const importPattern = /(?:^|\W)(\w+)(?=,|\}|$)/g; - let match: RegExpExecArray | null; - while ((match = importPattern.exec(importStatement)) !== null) { - // log('match', match); - const name = match[1].trim(); - names.push(name); - } - } else { - // It's a simple 1-line import without braces, so grab the last word: - names.push(importStatement.split('::').at(-1)!); - } - - return names; -} - -/** - * Code generation. - * @returns - a string of code which will be needed in _every_ contract interface, regardless of the contract. (Hence the name 'static'). + * Generates static impots. + * @returns A string of code which will be needed in every contract interface, regardless of the contract. */ function generateStaticImports() { - return ` -use dep::std; + return `use dep::std; use dep::aztec::context::Context; -use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; - -// --- - -`; -} - -/** - * Allows serialisation methods to be imported from other files. This expects any type which gets used as a function parameter to have a `serialise` function, a `deserialise` function and a `_SERIALISED_LEN` global. See `noir-libs/.../types/` for more details. This is a temporary solution, until Noir supports traits. - * @param type - The name of a Noir type / struct. - * @returns - A portion of an import path and names. - */ -function generateTypeSerialisationImport(type: string) { - return ` - ${type.toLowerCase()}_serialisation::{ - ${upperFirst(type)}SerialisationMethods, - ${type.toUpperCase()}_SERIALISED_LEN, - }, -`; -} - -// TODO: Identify custom types, and then generate imports for custom types' serialisation methods. -/** - * Code generation. - * Generates import statements for serialisation methods and globals. Only for Native Types so far. See the TODO for supporting the importing of custom structs. - * @param functionData - Data relating to every function in the Noir Contract. - * @returns - Code. An import statement for every native type. - */ -function generateNativeTypeSerialisationImports(functionData: FuncData[]) { - const types = functionData.flatMap(fnData => fnData.structuredParameters.map(p => p.type)); - const removeDuplicates = (arr: string[]) => [...new Set(arr)]; // we only want the import statement for a particular type to appear once, or compilation will fail! - const importStatements = removeDuplicates(types).map(generateTypeSerialisationImport); - return ` -use dep::aztec::types::type_serialisation::{ - ${importStatements.join('\n')} -}; -`; -} - -// TODO: this should probably go in noir-libs, instead. -/** - * Generate a `spread` function, which is needed for populating `serialised_args` from arrays of fields. - * @returns Code. - */ -function generateSpreadHelperFunction() { - return ` -// Spread an array into an array: -fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { - let mut j = at_index; - for i in 0..SRC_LEN { - target_arr[j] = src_arr[i]; - } - target_arr -} -`; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH;`; } /** @@ -353,22 +158,20 @@ fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Fi * @returns Code. */ function generateContractInterfaceStruct(contractName: string) { - return ` -struct ${contractName}_ContractInterface { + return `struct ${contractName}ContractInterface { address: Field, } `; } /** - * - * @param contractName - the name of the contract, as matches the original source file. + * Generates the implementation of the contract interface struct. + * @param contractName - The name of the contract, as matches the original source file. * @param functions - An array of strings, where each string is valid Noir code describing the function interface of one of the contract's functions (as generated via `generateFunctionInterface` above). - * @returns + * @returns Code. */ function generateContractInterfaceImpl(contractName: string, functions: string[]) { - return ` -impl ${contractName}_ContractInterface { + return `impl ${contractName}ContractInterface { fn at(address: Field) -> Self { Self { address, @@ -379,37 +182,62 @@ impl ${contractName}_ContractInterface { `; } -// YOU CAN QUICKLY TEST WITH: -// -// `NODE_OPTIONS=--no-warnings yarn ts-node --esm src/scripts/copy_output.ts zk_token` -// -// from the noir-contract/ dir of the monorepo. -// DANGER: this will write files, so make sure you run it from the correct place! +/** Represents a struct along its parent names to derive a fully qualified name. */ +type StructInfo = ABIVariable & { /** Parent name */ parentNames: string[] }; /** - * Generates the typescript code to represent a contract. - * @param input - The compiled Noir artifact. - * @param abiImportPath - Optional path to import the ABI (if not set, will be required in the constructor). - * @returns The corresponding ts code. + * Generates a Noir struct. + * @param struct - Struct info. + * @returns Code representing the struct. */ -export function generateNoirContractInterface(sourceCode: string, abi: ContractAbi) { - const functionData: FuncData[] = generateFunctionData(sourceCode, abi); +function generateStruct(struct: StructInfo) { + const fields = (struct.type as StructType).fields.map( + field => ` ${field.name}: ${getTypeName(field, struct.name, ...struct.parentNames)},`, + ); - // Note: we'll use import statements for importing serialisation methods for custom parameter types. - const importStatements: string[] = parseImports(sourceCode); - const importedNames: string[] = importStatements.flatMap(extractImportedNames); + return ` +struct ${getStructName(struct.name, ...struct.parentNames)} { +${fields.join('\n')} +}`; +} - const contractInterfaceStruct: string = generateContractInterfaceStruct(abi.name); - const contractInterfaceFunctions: string[] = functionData.map(f => generateFunctionInterface(f)); - const contractInterfaceImpl: string = generateContractInterfaceImpl(abi.name, contractInterfaceFunctions); +/** + * Collects all structs across all parameters. + * @param params - Parameters to look for structs, either structs themselves or nested. + * @param parentNames - Parent names to derive fully qualified names when needed. + * @returns A list of struct infos. + */ +function collectStructs(params: ABIVariable[], parentNames: string[]): StructInfo[] { + const structs: StructInfo[] = []; + for (const param of params) { + if (param.type.kind === 'struct') { + const struct = { ...param, parentNames }; + structs.push(struct, ...collectStructs(param.type.fields, [param.name, ...parentNames])); + } else if (param.type.kind === 'array') { + structs.push(...collectStructs([{ name: param.name, type: param.type.type }], [...parentNames])); + } + } + return structs; +} - return ` -/* Autogenerated file, do not edit! */ +/** + * Generates the Noir code to represent an interface for calling a contract. + * @param abi - The compiled Noir artifact. + * @returns The corresponding ts code. + */ +export function generateNoirContractInterface(abi: ContractAbi) { + const contractStruct: string = generateContractInterfaceStruct(abi.name); + const methods = compact(abi.functions.filter(f => f.name !== 'constructor')); + const paramStructs = methods.flatMap(m => collectStructs(m.parameters, [m.name])).map(generateStruct); + const functionInterfaces = methods.map(generateFunctionInterface); + const contractImpl: string = generateContractInterfaceImpl(abi.name, functionInterfaces); + return `/* Autogenerated file, do not edit! */ + ${generateStaticImports()} -${generateNativeTypeSerialisationImports(functionData)} -${generateSpreadHelperFunction()} -${contractInterfaceStruct} -${contractInterfaceImpl} +${paramStructs.join('\n')} + +${contractStruct} +${contractImpl} `; } diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml b/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml index 806aa852d68..41b131fcc79 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/Nargo.toml @@ -2,6 +2,7 @@ name = "import_test_contract" authors = [""] compiler_version = "0.1" +type = "bin" [dependencies] aztec = { path = "../../../../noir-libs/noir-aztec" } \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr index cb737c74741..b157248e15f 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -1,28 +1,50 @@ -mod zk_token_contract_interface; +mod test_contract_interface; contract ImportTest { use dep::aztec::abi; use dep::aztec::abi::PrivateContextInputs; use dep::aztec::context::Context; - use crate::zk_token_contract_interface::ZkToken_ContractInterface; - - // fn constructor( - // inputs: PrivateContextInputs, - // ) -> distinct pub abi::PrivateCircuitPublicInputs { - // } + + use crate::test_contract_interface::{ + TestContractInterface, + AStructTestCodeGenStruct, + ADeepStructTestCodeGenStruct, + ANoteADeepStructTestCodeGenStruct, + ManyNotesADeepStructTestCodeGenStruct, + }; + fn constructor( + inputs: PrivateContextInputs, + ) -> distinct pub abi::PrivateCircuitPublicInputs { + Context::new(inputs, 0).finish() + } + fn main( inputs: PrivateContextInputs, - amount: Field, - sender: Field, - recipient: Field, + target: Field ) -> distinct pub abi::PrivateCircuitPublicInputs { - let mut context = Context::new(inputs, abi::hash_args([amount, sender, recipient])); - - let zk_token_contract_address = 1234; - let zk_token_contract_instance = ZkToken_ContractInterface::at(zk_token_contract_address); - let _return_values = zk_token_contract_instance.transfer(&mut context, amount, sender, recipient); + let mut context = Context::new(inputs, abi::hash_args([target])); + let test_contract_instance = TestContractInterface::at(target); + let return_values = test_contract_instance.testCodeGen( + &mut context, + 1, + true, + 1 as u32, + [1, 2], + AStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + ADeepStructTestCodeGenStruct { + aField: 1, + aBool: true, + aNote: ANoteADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + manyNotes: [ + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + ManyNotesADeepStructTestCodeGenStruct { amount: 1, secretHash: 2 }, + ] + } + ); + context.return_values.push(return_values[0]); context.finish() } } diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr new file mode 120000 index 00000000000..412fbaacd2d --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/test_contract_interface.nr @@ -0,0 +1 @@ +../../test_contract/src/test_contract_interface.nr \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr deleted file mode 100644 index 6bc0df24da1..00000000000 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface.nr +++ /dev/null @@ -1,261 +0,0 @@ - -/* Autogenerated file, do not edit! */ - - -use dep::std; -use dep::aztec::context::Context; -use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; - -// --- - - - -use dep::aztec::types::type_serialisation::{ - - field_serialisation::{ - FieldSerialisationMethods, - FIELD_SERIALISED_LEN, - }, - - -}; - - -// Spread an array into an array: -fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { - let mut j = at_index; - for i in 0..SRC_LEN { - target_arr[j] = src_arr[i]; - } - target_arr -} - - -struct ZkToken_ContractInterface { - address: Field, -} - - -impl ZkToken_ContractInterface { - fn at(address: Field) -> Self { - Self { - address, - } - } - - fn constructor( - self, - context: &mut Context, - initial_supply: Field, - owner: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - - let mut spread_index: Field = 0; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(initial_supply), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(owner), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - - context.call_private_function(self.address, 0x968ffe4f, serialised_args).public_inputs.return_values - - } - - - fn mint( - self, - context: &mut Context, - amount: Field, - owner: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - - let mut spread_index: Field = 0; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(amount), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(owner), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - - context.call_private_function(self.address, 0x1dc9c3c0, serialised_args).public_inputs.return_values - - } - - - fn transfer( - self, - context: &mut Context, - amount: Field, - sender: Field, - recipient: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - - let mut spread_index: Field = 0; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(amount), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(sender), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(recipient), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - - context.call_private_function(self.address, 0xdcd4c318, serialised_args).public_inputs.return_values - - } - - - // fn createClaims( - // self, - // context: &mut Context, - // amounts: [Field; 2], - // secrets: [Field; 2], - // sender: Field - // ) -> [Field; RETURN_VALUES_LENGTH] { - - // let mut serialised_args = [0; [FIELD; 2]_SERIALISED_LEN + [FIELD; 2]_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - - // let mut spread_index: Field = 0; - - - // let serialise[Field; 2] = [Field; 2]SerialisationMethods.serialise; - // serialised_args = spread( - // serialised_args, - // serialise[Field; 2](amounts), - // spread_index - // ); - // spread_index += [FIELD; 2]_SERIALISED_LEN; - - - // let serialise[Field; 2] = [Field; 2]SerialisationMethods.serialise; - // serialised_args = spread( - // serialised_args, - // serialise[Field; 2](secrets), - // spread_index - // ); - // spread_index += [FIELD; 2]_SERIALISED_LEN; - - - // let serialiseField = FieldSerialisationMethods.serialise; - // serialised_args = spread( - // serialised_args, - // serialiseField(sender), - // spread_index - // ); - // spread_index += FIELD_SERIALISED_LEN; - - - - // context.call_private_function(self.address, 0xd3ebc0af, serialised_args).public_inputs.return_values - - // } - - - fn claim( - self, - context: &mut Context, - amount: Field, - secret: Field, - owner: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - - let mut spread_index: Field = 0; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(amount), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(secret), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(owner), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - - - context.call_private_function(self.address, 0x9f7bacc8, serialised_args).public_inputs.return_values - - } - -} - diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr deleted file mode 100644 index 574cb54dee8..00000000000 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/zk_token_contract_interface_MASTER.nr +++ /dev/null @@ -1,125 +0,0 @@ -use dep::std; -use dep::aztec::context::Context; -use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; - -//------- - -// use dep::types::type_serialisation::TypeSerialisationInterface; - -use dep::aztec::types::type_serialisation::{ - field_serialisation::{ - FieldSerialisationMethods, - FIELD_SERIALISED_LEN, - }, -}; - -//------- - -// Spread an array into an array: -fn spread(mut target_arr: [Field; TARGET_LEN], src_arr: [Field; SRC_LEN], at_index: Field) -> [Field; TARGET_LEN] { - let mut j = at_index; - for i in 0..SRC_LEN { - target_arr[j] = src_arr[i]; - } - target_arr -} - -struct ZkToken_ContractInterface { - address: Field, -} - -impl ZkToken_ContractInterface { - fn at(address: Field) -> Self { - Self { - address, - } - } - - fn mint( - self, - context: &mut Context, - amount: Field, - owner: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - let mut spread_index: Field = 0; - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(amount), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - // let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(owner), - spread_index - ); - // spread_index += FIELD_SERIALISED_LEN; - - context.call_private_function(self.address, 12345, serialised_args).public_inputs.return_values - } - - fn transfer( - self, - context: &mut Context, - amount: Field, - sender: Field, - recipient: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - let mut serialised_args = [0; FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN + FIELD_SERIALISED_LEN]; - - let mut spread_index: Field = 0; - - let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(amount), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - // let serialiseField = FieldSerialisationMethods.serialise; - serialised_args = spread( - serialised_args, - serialiseField(sender), - spread_index - ); - spread_index += FIELD_SERIALISED_LEN; - - serialised_args = spread( - serialised_args, - serialiseField(recipient), - spread_index - ); - // spread_index += FIELD_SERIALISED_LEN; - - context.call_private_function(self.address, 12345, serialised_args).public_inputs.return_values - } -} - - -#[test] -// We simply test whether this compiles. We can't actually test it, because oracle calls are not supported by Noir tests yet. -fn test_transfer() { - let mut context: Context = std::unsafe::zeroed(); - - let zk_token_contract_instance = ZkToken_ContractInterface::at(1234); - - let deserialiseField = FieldSerialisationMethods.deserialise; - - let amount = deserialiseField([0; FIELD_SERIALISED_LEN]); - let sender = deserialiseField([0; FIELD_SERIALISED_LEN]); - let recipient = deserialiseField([0; FIELD_SERIALISED_LEN]); - - let return_values = zk_token_contract_instance.transfer( - &mut context, - amount, - sender, - recipient - ); -} \ No newline at end of file diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index db44cc275b5..8bf766112b4 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -2,6 +2,7 @@ contract Test { use dep::aztec::{ abi, + types::vec::BoundedVec, abi::{ PublicContextInputs, PrivateContextInputs @@ -64,6 +65,37 @@ contract Test { context.finish() } + // Test codegen for noir interfaces + fn testCodeGen( + inputs: PrivateContextInputs, + aField: Field, + aBool: bool, + aNumber: u32, + anArray: [Field; 2], + aStruct: DummyNote, + aDeepStruct: DeepStruct, + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let mut args: BoundedVec = BoundedVec::new(0); + args.push(aField); + args.push(aBool as Field); + args.push(aNumber as Field); + args.push_array(anArray); + args.push(aStruct.amount); + args.push(aStruct.secretHash); + args.push(aDeepStruct.aField); + args.push(aDeepStruct.aBool as Field); + args.push(aDeepStruct.aNote.amount); + args.push(aDeepStruct.aNote.secretHash); + for note in aDeepStruct.manyNotes { + args.push(note.amount); + args.push(note.secretHash); + } + let args_hash = abi::hash_args(args.storage); + let mut context = Context::new(inputs, args_hash); + context.return_values.push(args_hash); + context.finish() + } + // Purely exists for testing open fn createL2ToL1MessagePublic( _inputs: PublicContextInputs, @@ -109,4 +141,11 @@ contract Test { dep::std::hash::pedersen([self.amount, self.secretHash])[0] } } + + struct DeepStruct { + aField: Field, + aBool: bool, + aNote: DummyNote, + manyNotes: [DummyNote; 3], + } } diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr new file mode 100644 index 00000000000..88c46dd1a67 --- /dev/null +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr @@ -0,0 +1,145 @@ +/* Autogenerated file, do not edit! */ + +use dep::std; +use dep::aztec::context::Context; +use dep::aztec::constants_gen::RETURN_VALUES_LENGTH; + +struct AStructTestCodeGenStruct { + amount: Field, + secretHash: Field, +} + +struct ADeepStructTestCodeGenStruct { + aField: Field, + aBool: bool, + aNote: ANoteADeepStructTestCodeGenStruct, + manyNotes: [ManyNotesADeepStructTestCodeGenStruct;3], +} + +struct ANoteADeepStructTestCodeGenStruct { + amount: Field, + secretHash: Field, +} + +struct ManyNotesADeepStructTestCodeGenStruct { + amount: Field, + secretHash: Field, +} + +struct TestContractInterface { + address: Field, +} + +impl TestContractInterface { + fn at(address: Field) -> Self { + Self { + address, + } + } + + fn createL2ToL1MessagePublic( + self, + context: &mut Context, + amount: Field, + secretHash: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = secretHash; + + context.call_private_function(self.address, 0x1c031d17, serialised_args) + } + + + fn createNullifierPublic( + self, + context: &mut Context, + amount: Field, + secretHash: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = secretHash; + + context.call_private_function(self.address, 0x0217ef40, serialised_args) + } + + + fn getPortalContractAddress( + self, + context: &mut Context, + aztec_address: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 1]; + serialised_args[0] = aztec_address; + + context.call_private_function(self.address, 0xe5df1726, serialised_args) + } + + + fn getPublicKey( + self, + context: &mut Context, + address: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 1]; + serialised_args[0] = address; + + context.call_private_function(self.address, 0x553aaad4, serialised_args) + } + + + fn getThisAddress( + self, + context: &mut Context + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 0]; + + context.call_private_function(self.address, 0xd3953822, serialised_args) + } + + + fn getThisPortalAddress( + self, + context: &mut Context + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 0]; + + context.call_private_function(self.address, 0x82cc9431, serialised_args) + } + + + fn testCodeGen( + self, + context: &mut Context, + aField: Field, + aBool: bool, + aNumber: u32, + anArray: [Field;2], + aStruct: AStructTestCodeGenStruct, + aDeepStruct: ADeepStructTestCodeGenStruct + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 17]; + serialised_args[0] = aField; + serialised_args[1] = aBool as Field; + serialised_args[2] = aNumber as Field; + serialised_args[3] = anArray[0]; + serialised_args[4] = anArray[1]; + serialised_args[5] = aStruct.amount; + serialised_args[6] = aStruct.secretHash; + serialised_args[7] = aDeepStruct.aField; + serialised_args[8] = aDeepStruct.aBool as Field; + serialised_args[9] = aDeepStruct.aNote.amount; + serialised_args[10] = aDeepStruct.aNote.secretHash; + serialised_args[11] = aDeepStruct.manyNotes[0].amount; + serialised_args[12] = aDeepStruct.manyNotes[0].secretHash; + serialised_args[13] = aDeepStruct.manyNotes[1].amount; + serialised_args[14] = aDeepStruct.manyNotes[1].secretHash; + serialised_args[15] = aDeepStruct.manyNotes[2].amount; + serialised_args[16] = aDeepStruct.manyNotes[2].secretHash; + + context.call_private_function(self.address, 0x7c97ca29, serialised_args) + } + +} + diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index ee31453805f..a277d45003b 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -20,6 +20,8 @@ const PROJECT_CONTRACTS = [ { name: 'EcdsaAccount', target: '../aztec.js/src/abis/', exclude: [] }, ]; +const INTERFACE_CONTRACTS = ['test']; + /** * Writes the contract to a specific project folder, if needed. * @param abi - The Abi to write. @@ -125,9 +127,15 @@ const main = () => { log(`Written ${tsInterfaceDestFilePath}`); // Write a .nr contract interface, for consumption by other Noir Contracts - const noirInterfaceDestFilePath = `${projectDirPath}/src/${projectName}_interface.nr`; - writeFileSync(noirInterfaceDestFilePath, generateNoirContractInterface(sourceCode, artifactJson)); - log(`Written ${noirInterfaceDestFilePath}`); + if (INTERFACE_CONTRACTS.includes(name)) { + const noirInterfaceDestFilePath = `${projectDirPath}/src/${projectName}_interface.nr`; + try { + writeFileSync(noirInterfaceDestFilePath, generateNoirContractInterface(artifactJson)); + log(`Written ${noirInterfaceDestFilePath}`); + } catch (err) { + log(`Error generating noir interface for ${name}: ${err}`); + } + } }; try { diff --git a/yarn-project/yarn.lock b/yarn-project/yarn.lock index 19eced38760..42697ded126 100644 --- a/yarn-project/yarn.lock +++ b/yarn-project/yarn.lock @@ -503,12 +503,20 @@ __metadata: "@rushstack/eslint-patch": ^1.1.4 "@types/fs-extra": ^11.0.1 "@types/jest": ^29.5.0 + "@types/lodash.camelcase": ^4.3.7 + "@types/lodash.capitalize": ^4.2.7 "@types/lodash.compact": ^3.0.7 + "@types/lodash.times": ^4.3.7 + "@types/lodash.upperfirst": ^4.3.7 "@types/node": ^18.7.23 commander: ^9.0.0 fs-extra: ^11.1.1 jest: ^29.5.0 + lodash.camelcase: ^4.3.0 + lodash.capitalize: ^4.2.1 lodash.compact: ^3.0.1 + lodash.times: ^4.3.2 + lodash.upperfirst: ^4.3.1 toml: ^3.0.0 ts-jest: ^29.1.0 ts-node: ^10.9.1 @@ -3211,6 +3219,15 @@ __metadata: languageName: node linkType: hard +"@types/lodash.capitalize@npm:^4.2.7": + version: 4.2.7 + resolution: "@types/lodash.capitalize@npm:4.2.7" + dependencies: + "@types/lodash": "*" + checksum: dab8b781d7dcc56c18ba0c8286a6ccb61cc598d936a449265453a473e62b2b6d7c109c4447dfeb8ccacc4088769bc3bfd0d39bc8797f03e4e685d4f4b1bc7c01 + languageName: node + linkType: hard + "@types/lodash.chunk@npm:^4.2.7": version: 4.2.7 resolution: "@types/lodash.chunk@npm:4.2.7" From 23ae93b2e711b2e2e80cecd25ef06407f3d7f9ec Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 9 Aug 2023 16:38:46 -0300 Subject: [PATCH 03/15] Restrict to private fns --- .../src/contract-interface-gen/noir.ts | 5 +++- .../src/test_contract_interface.nr | 28 ------------------- 2 files changed, 4 insertions(+), 29 deletions(-) diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index a15d17d5256..5ef61babdd7 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -3,6 +3,7 @@ import { ABIVariable, ContractAbi, FunctionAbi, + FunctionType, StructType, generateFunctionSelector, } from '@aztec/foundation/abi'; @@ -227,7 +228,9 @@ function collectStructs(params: ABIVariable[], parentNames: string[]): StructInf */ export function generateNoirContractInterface(abi: ContractAbi) { const contractStruct: string = generateContractInterfaceStruct(abi.name); - const methods = compact(abi.functions.filter(f => f.name !== 'constructor')); + const methods = compact( + abi.functions.filter(f => f.name !== 'constructor' && !f.isInternal && f.functionType === FunctionType.SECRET), + ); const paramStructs = methods.flatMap(m => collectStructs(m.parameters, [m.name])).map(generateStruct); const functionInterfaces = methods.map(generateFunctionInterface); const contractImpl: string = generateContractInterfaceImpl(abi.name, functionInterfaces); diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr index 88c46dd1a67..634504d4ded 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr @@ -37,34 +37,6 @@ impl TestContractInterface { } } - fn createL2ToL1MessagePublic( - self, - context: &mut Context, - amount: Field, - secretHash: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - let mut serialised_args = [0; 2]; - serialised_args[0] = amount; - serialised_args[1] = secretHash; - - context.call_private_function(self.address, 0x1c031d17, serialised_args) - } - - - fn createNullifierPublic( - self, - context: &mut Context, - amount: Field, - secretHash: Field - ) -> [Field; RETURN_VALUES_LENGTH] { - let mut serialised_args = [0; 2]; - serialised_args[0] = amount; - serialised_args[1] = secretHash; - - context.call_private_function(self.address, 0x0217ef40, serialised_args) - } - - fn getPortalContractAddress( self, context: &mut Context, From 2a6b510cbc24d7abd034b7fcff90cf5a04d6f51b Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 9 Aug 2023 16:49:30 -0300 Subject: [PATCH 04/15] Generate calls to public fns as well --- .../src/contract-interface-gen/noir.ts | 14 +++++----- .../src/test_contract_interface.nr | 28 +++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index 5ef61babdd7..96e1df42b0f 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -15,12 +15,14 @@ import upperFirst from 'lodash.upperfirst'; /** * Generates a call to a private function using the context. - * @param selector - The function selector of a function + * @param selector - The selector of a function. + * @param functionType - Type of the function. * @returns A code string. */ -function generateCallStatement(selector: string) { +function generateCallStatement(selector: string, functionType: FunctionType) { + const callMethod = functionType === FunctionType.SECRET ? 'call_private_function' : 'call_public_function'; return ` - context.call_private_function(self.address, ${selector}, serialised_args)`; + context.${callMethod}(self.address, ${selector}, serialised_args)`; } /** @@ -130,7 +132,7 @@ function generateFunctionInterface(functionData: FunctionAbi) { const { name, parameters } = functionData; const selector = '0x' + generateFunctionSelector(name, parameters).toString('hex'); const serialisation = generateSerialisation(parameters); - const callStatement = generateCallStatement(selector); + const callStatement = generateCallStatement(selector, functionData.functionType); const allParams = ['self', 'context: &mut Context', ...parameters.map(p => generateParameter(p, functionData))]; return ` @@ -228,9 +230,7 @@ function collectStructs(params: ABIVariable[], parentNames: string[]): StructInf */ export function generateNoirContractInterface(abi: ContractAbi) { const contractStruct: string = generateContractInterfaceStruct(abi.name); - const methods = compact( - abi.functions.filter(f => f.name !== 'constructor' && !f.isInternal && f.functionType === FunctionType.SECRET), - ); + const methods = compact(abi.functions.filter(f => f.name !== 'constructor' && !f.isInternal)); const paramStructs = methods.flatMap(m => collectStructs(m.parameters, [m.name])).map(generateStruct); const functionInterfaces = methods.map(generateFunctionInterface); const contractImpl: string = generateContractInterfaceImpl(abi.name, functionInterfaces); diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr index 634504d4ded..121b92cf375 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr @@ -37,6 +37,34 @@ impl TestContractInterface { } } + fn createL2ToL1MessagePublic( + self, + context: &mut Context, + amount: Field, + secretHash: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = secretHash; + + context.call_public_function(self.address, 0x1c031d17, serialised_args) + } + + + fn createNullifierPublic( + self, + context: &mut Context, + amount: Field, + secretHash: Field + ) -> [Field; RETURN_VALUES_LENGTH] { + let mut serialised_args = [0; 2]; + serialised_args[0] = amount; + serialised_args[1] = secretHash; + + context.call_public_function(self.address, 0x0217ef40, serialised_args) + } + + fn getPortalContractAddress( self, context: &mut Context, From fc80ef38981b6ecf4c954726e93f03c2b3a1bfc2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 10 Aug 2023 10:19:56 -0300 Subject: [PATCH 05/15] Do not return values when enqueuing a public fn --- .../noir-compiler/src/contract-interface-gen/noir.ts | 3 ++- .../src/contracts/parent_contract/src/main.nr | 12 ++++++------ .../test_contract/src/test_contract_interface.nr | 4 ++-- yarn-project/noir-libs/noir-aztec/src/context.nr | 8 +++----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index 96e1df42b0f..745bc7e9002 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -134,11 +134,12 @@ function generateFunctionInterface(functionData: FunctionAbi) { const serialisation = generateSerialisation(parameters); const callStatement = generateCallStatement(selector, functionData.functionType); const allParams = ['self', 'context: &mut Context', ...parameters.map(p => generateParameter(p, functionData))]; + const retType = functionData.functionType === FunctionType.SECRET ? `-> [Field; RETURN_VALUES_LENGTH] ` : ``; return ` fn ${name}( ${allParams.join(',\n ')} - ) -> [Field; RETURN_VALUES_LENGTH] { + ) ${retType}{ ${serialisation} ${callStatement} } diff --git a/yarn-project/noir-contracts/src/contracts/parent_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/parent_contract/src/main.nr index b78fd50d568..9c3dca33ed7 100644 --- a/yarn-project/noir-contracts/src/contracts/parent_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/parent_contract/src/main.nr @@ -58,7 +58,7 @@ contract Parent { targetValue, ])); - let _callStackItem = context.call_public_function(targetContract, targetSelector, [targetValue]); + context.call_public_function(targetContract, targetSelector, [targetValue]); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -78,9 +78,9 @@ contract Parent { ])); // Enqueue the first public call - let return_values1 = context.call_public_function(targetContract, targetSelector, [targetValue]); + context.call_public_function(targetContract, targetSelector, [targetValue]); // Enqueue the second public call - let _return_values2 = context.call_public_function(targetContract, targetSelector, [return_values1[0]]); + context.call_public_function(targetContract, targetSelector, [targetValue]); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -101,7 +101,7 @@ contract Parent { let pubEntryPointSelector = 3221316504; let thisAddress = inputs.call_context.storage_contract_address; - let _return_values = context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue]); + context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue]); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() @@ -123,9 +123,9 @@ contract Parent { let pubEntryPointSelector = 3221316504; let thisAddress = inputs.call_context.storage_contract_address; - let _return_values1 = context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue]); + context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue]); - let _return_values2 = context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue + 1]); + context.call_public_function(thisAddress, pubEntryPointSelector, [targetContract, targetSelector, targetValue + 1]); // Return private circuit public inputs. All private functions need to return this as it is part of the input of the private kernel. context.finish() diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr index 121b92cf375..32603d90544 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/test_contract_interface.nr @@ -42,7 +42,7 @@ impl TestContractInterface { context: &mut Context, amount: Field, secretHash: Field - ) -> [Field; RETURN_VALUES_LENGTH] { + ) { let mut serialised_args = [0; 2]; serialised_args[0] = amount; serialised_args[1] = secretHash; @@ -56,7 +56,7 @@ impl TestContractInterface { context: &mut Context, amount: Field, secretHash: Field - ) -> [Field; RETURN_VALUES_LENGTH] { + ) { let mut serialised_args = [0; 2]; serialised_args[0] = amount; serialised_args[1] = secretHash; diff --git a/yarn-project/noir-libs/noir-aztec/src/context.nr b/yarn-project/noir-libs/noir-aztec/src/context.nr index 36811cd88f0..8c271724b53 100644 --- a/yarn-project/noir-libs/noir-aztec/src/context.nr +++ b/yarn-project/noir-libs/noir-aztec/src/context.nr @@ -292,7 +292,7 @@ impl Context { contract_address: Field, function_selector: Field, args: [Field; ARGS_COUNT] - ) -> [Field; RETURN_VALUES_LENGTH] { + ) { let args_hash = hash_args(args); assert(args_hash == arguments::pack_arguments(args)); self.call_public_function_with_packed_args(contract_address, function_selector, args_hash) @@ -302,7 +302,7 @@ impl Context { &mut self, contract_address: Field, function_selector: Field, - ) -> [Field; RETURN_VALUES_LENGTH] { + ) { self.call_public_function_with_packed_args(contract_address, function_selector, 0) } @@ -311,7 +311,7 @@ impl Context { contract_address: Field, function_selector: Field, args_hash: Field - ) -> [Field; RETURN_VALUES_LENGTH] { + ) { let fields = enqueue_public_function_call_internal( contract_address, function_selector, @@ -368,7 +368,5 @@ impl Context { assert(item.public_inputs.call_context.storage_contract_address == contract_address); self.public_call_stack.push(item.hash()); - - item.public_inputs.return_values } } From 71556c68bdd434a1022d725bd82863bd1008bf99 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 11:38:56 -0300 Subject: [PATCH 06/15] Fix hashing of no arguments in noir --- yarn-project/noir-libs/noir-aztec/src/abi.nr | 30 +++++++++++--------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/yarn-project/noir-libs/noir-aztec/src/abi.nr b/yarn-project/noir-libs/noir-aztec/src/abi.nr index b2a4240db29..be4fbfc8da4 100644 --- a/yarn-project/noir-libs/noir-aztec/src/abi.nr +++ b/yarn-project/noir-libs/noir-aztec/src/abi.nr @@ -358,21 +358,25 @@ global ARGS_HASH_CHUNK_LENGTH: u32 = 32; global ARGS_HASH_CHUNK_COUNT: u32 = 16; fn hash_args(args: [Field; N]) -> Field { - let mut chunks_hashes = [0; ARGS_HASH_CHUNK_COUNT]; - for i in 0..ARGS_HASH_CHUNK_COUNT { - let mut chunk_hash = 0; - let start_chunk_index = i * ARGS_HASH_CHUNK_LENGTH; - if start_chunk_index < (args.len() as u32) { - let mut chunk_args = [0; ARGS_HASH_CHUNK_LENGTH]; - for j in 0..ARGS_HASH_CHUNK_LENGTH { - let item_index = i * ARGS_HASH_CHUNK_LENGTH + j; - if item_index < (args.len() as u32) { - chunk_args[j] = args[item_index]; + if args.len() == 0 { + 0 + } else { + let mut chunks_hashes = [0; ARGS_HASH_CHUNK_COUNT]; + for i in 0..ARGS_HASH_CHUNK_COUNT { + let mut chunk_hash = 0; + let start_chunk_index = i * ARGS_HASH_CHUNK_LENGTH; + if start_chunk_index < (args.len() as u32) { + let mut chunk_args = [0; ARGS_HASH_CHUNK_LENGTH]; + for j in 0..ARGS_HASH_CHUNK_LENGTH { + let item_index = i * ARGS_HASH_CHUNK_LENGTH + j; + if item_index < (args.len() as u32) { + chunk_args[j] = args[item_index]; + } } + chunk_hash = dep::std::hash::pedersen_with_separator(chunk_args, GENERATOR_INDEX__FUNCTION_ARGS)[0]; } - chunk_hash = dep::std::hash::pedersen_with_separator(chunk_args, GENERATOR_INDEX__FUNCTION_ARGS)[0]; + chunks_hashes[i] = chunk_hash; } - chunks_hashes[i] = chunk_hash; + dep::std::hash::pedersen_with_separator(chunks_hashes, GENERATOR_INDEX__FUNCTION_ARGS)[0] } - dep::std::hash::pedersen_with_separator(chunks_hashes, GENERATOR_INDEX__FUNCTION_ARGS)[0] } From 065a976aa2d91490512cb691f374f280ea1b4ea1 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 11:40:47 -0300 Subject: [PATCH 07/15] Add e2e test for autogenerated interface --- .../src/e2e_nested_contract.test.ts | 288 ++++++++++-------- .../import_test_contract/src/main.nr | 20 ++ .../src/contracts/test_contract/src/main.nr | 3 +- 3 files changed, 179 insertions(+), 132 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts index fba6e540515..e403f80e550 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts @@ -5,7 +5,7 @@ import { ContractAbi } from '@aztec/foundation/abi'; import { DebugLogger } from '@aztec/foundation/log'; import { toBigInt } from '@aztec/foundation/serialize'; import { ChildContractAbi, ParentContractAbi } from '@aztec/noir-contracts/artifacts'; -import { ChildContract, ParentContract } from '@aztec/noir-contracts/types'; +import { ChildContract, ImportTestContract, ParentContract, TestContract } from '@aztec/noir-contracts/types'; import { AztecRPC, TxStatus } from '@aztec/types'; import { setup } from './fixtures/utils.js'; @@ -17,14 +17,8 @@ describe('e2e_nested_contract', () => { let accounts: AztecAddress[]; let logger: DebugLogger; - let parentContract: ParentContract; - let childContract: ChildContract; - beforeEach(async () => { ({ aztecNode, aztecRpcServer, accounts, wallet, logger } = await setup()); - - parentContract = (await deployContract(ParentContractAbi)) as ParentContract; - childContract = (await deployContract(ChildContractAbi)) as ChildContract; }, 100_000); afterEach(async () => { @@ -34,129 +28,161 @@ describe('e2e_nested_contract', () => { } }); - const deployContract = async (abi: ContractAbi) => { - logger(`Deploying L2 contract ${abi.name}...`); - const deployer = new ContractDeployer(abi, aztecRpcServer); - const tx = deployer.deploy().send(); - - await tx.isMined({ interval: 0.1 }); - - const receipt = await tx.getReceipt(); - const contract = await Contract.create(receipt.contractAddress!, abi, wallet); - logger(`L2 contract ${abi.name} deployed at ${contract.address}`); - return contract; - }; - - const addressToField = (address: AztecAddress): bigint => Fr.fromBuffer(address.toBuffer()).value; - - const getChildStoredValue = (child: { address: AztecAddress }) => - aztecRpcServer.getPublicStorageAt(child.address, new Fr(1)).then(x => toBigInt(x!)); - - /** - * Milestone 3. - */ - it('performs nested calls', async () => { - const tx = parentContract.methods - .entryPoint(childContract.address, Fr.fromBuffer(childContract.methods.value.selector)) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - - expect(receipt.status).toBe(TxStatus.MINED); - }, 100_000); - - it('performs public nested calls', async () => { - const tx = parentContract.methods - .pubEntryPoint(childContract.address, Fr.fromBuffer(childContract.methods.pubValue.selector), 42n) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - - expect(receipt.status).toBe(TxStatus.MINED); - }, 100_000); - - it('enqueues a single public call', async () => { - const tx = parentContract.methods - .enqueueCallToChild(childContract.address, Fr.fromBuffer(childContract.methods.pubStoreValue.selector), 42n) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - expect(receipt.status).toBe(TxStatus.MINED); - - expect(await getChildStoredValue(childContract)).toEqual(42n); - }, 100_000); - - // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" - // See https://github.com/noir-lang/noir/issues/1347 - it.skip('enqueues multiple public calls', async () => { - const tx = parentContract.methods - .enqueueCallToChildTwice( - addressToField(childContract.address), - Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value, - 42n, - ) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - expect(receipt.status).toBe(TxStatus.MINED); - - expect(await getChildStoredValue(childContract)).toEqual(85n); - }, 100_000); - - it('enqueues a public call with nested public calls', async () => { - const tx = parentContract.methods - .enqueueCallToPubEntryPoint( - childContract.address, - Fr.fromBuffer(childContract.methods.pubStoreValue.selector), - 42n, - ) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - expect(receipt.status).toBe(TxStatus.MINED); - - expect(await getChildStoredValue(childContract)).toEqual(42n); - }, 100_000); - - // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" - // See https://github.com/noir-lang/noir/issues/1347 - it.skip('enqueues multiple public calls with nested public calls', async () => { - const tx = parentContract.methods - .enqueueCallsToPubEntryPoint( - childContract.address, - Fr.fromBuffer(childContract.methods.pubStoreValue.selector), - 42n, - ) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - expect(receipt.status).toBe(TxStatus.MINED); - - expect(await getChildStoredValue(childContract)).toEqual(84n); - }, 100_000); + describe('parent manually calls child', () => { + let parentContract: ParentContract; + let childContract: ChildContract; + + beforeEach(async () => { + parentContract = (await deployContract(ParentContractAbi)) as ParentContract; + childContract = (await deployContract(ChildContractAbi)) as ChildContract; + }, 100_000); + + const deployContract = async (abi: ContractAbi) => { + logger(`Deploying L2 contract ${abi.name}...`); + const deployer = new ContractDeployer(abi, aztecRpcServer); + const tx = deployer.deploy().send(); + + await tx.isMined({ interval: 0.1 }); + + const receipt = await tx.getReceipt(); + const contract = await Contract.create(receipt.contractAddress!, abi, wallet); + logger(`L2 contract ${abi.name} deployed at ${contract.address}`); + return contract; + }; + + const addressToField = (address: AztecAddress): bigint => Fr.fromBuffer(address.toBuffer()).value; + + const getChildStoredValue = (child: { address: AztecAddress }) => + aztecRpcServer.getPublicStorageAt(child.address, new Fr(1)).then(x => toBigInt(x!)); + + /** + * Milestone 3. + */ + it('performs nested calls', async () => { + const tx = parentContract.methods + .entryPoint(childContract.address, Fr.fromBuffer(childContract.methods.value.selector)) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + + expect(receipt.status).toBe(TxStatus.MINED); + }, 100_000); + + it('performs public nested calls', async () => { + const tx = parentContract.methods + .pubEntryPoint(childContract.address, Fr.fromBuffer(childContract.methods.pubValue.selector), 42n) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + + expect(receipt.status).toBe(TxStatus.MINED); + }, 100_000); + + it('enqueues a single public call', async () => { + const tx = parentContract.methods + .enqueueCallToChild(childContract.address, Fr.fromBuffer(childContract.methods.pubStoreValue.selector), 42n) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + expect(receipt.status).toBe(TxStatus.MINED); + + expect(await getChildStoredValue(childContract)).toEqual(42n); + }, 100_000); + + // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" + // See https://github.com/noir-lang/noir/issues/1347 + it.skip('enqueues multiple public calls', async () => { + const tx = parentContract.methods + .enqueueCallToChildTwice( + addressToField(childContract.address), + Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value, + 42n, + ) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + expect(receipt.status).toBe(TxStatus.MINED); + + expect(await getChildStoredValue(childContract)).toEqual(85n); + }, 100_000); + + it('enqueues a public call with nested public calls', async () => { + const tx = parentContract.methods + .enqueueCallToPubEntryPoint( + childContract.address, + Fr.fromBuffer(childContract.methods.pubStoreValue.selector), + 42n, + ) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + expect(receipt.status).toBe(TxStatus.MINED); + + expect(await getChildStoredValue(childContract)).toEqual(42n); + }, 100_000); + + // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" + // See https://github.com/noir-lang/noir/issues/1347 + it.skip('enqueues multiple public calls with nested public calls', async () => { + const tx = parentContract.methods + .enqueueCallsToPubEntryPoint( + childContract.address, + Fr.fromBuffer(childContract.methods.pubStoreValue.selector), + 42n, + ) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + expect(receipt.status).toBe(TxStatus.MINED); + + expect(await getChildStoredValue(childContract)).toEqual(84n); + }, 100_000); + + // Regression for https://github.com/AztecProtocol/aztec-packages/issues/640 + // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" + // See https://github.com/noir-lang/noir/issues/1347 + it.skip('reads fresh value after write within the same tx', async () => { + const tx = parentContract.methods + .pubEntryPointTwice( + addressToField(childContract.address), + Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value, + 42n, + ) + .send({ origin: accounts[0] }); + + await tx.isMined({ interval: 0.1 }); + const receipt = await tx.getReceipt(); + + expect(receipt.status).toBe(TxStatus.MINED); + expect(await getChildStoredValue(childContract)).toEqual(85n); + }, 100_000); + }); - // Regression for https://github.com/AztecProtocol/aztec-packages/issues/640 - // Fails with "solver opcode resolution error: cannot solve opcode: expression has too many unknowns %EXPR [ 0 ]%" - // See https://github.com/noir-lang/noir/issues/1347 - it.skip('reads fresh value after write within the same tx', async () => { - const tx = parentContract.methods - .pubEntryPointTwice( - addressToField(childContract.address), - Fr.fromBuffer(childContract.methods.pubStoreValue.selector).value, - 42n, - ) - .send({ origin: accounts[0] }); - - await tx.isMined({ interval: 0.1 }); - const receipt = await tx.getReceipt(); - - expect(receipt.status).toBe(TxStatus.MINED); - expect(await getChildStoredValue(childContract)).toEqual(85n); - }, 100_000); + describe('importer uses autogenerated test contract interface', () => { + let importerContract: ImportTestContract; + let testContract: TestContract; + + beforeEach(async () => { + logger(`Deploying importer test contract`); + importerContract = await ImportTestContract.deploy(wallet).send().deployed(); + logger(`Deploying test contract`); + testContract = await TestContract.deploy(wallet).send().deployed(); + }, 30_000); + + it('calls a method with multiple arguments', async () => { + logger(`Calling main on importer contract`); + await importerContract.methods.main(testContract.address).send().wait(); + }); + + it('calls a method no arguments', async () => { + logger(`Calling noargs on importer contract`); + await importerContract.methods.noargs(testContract.address).send().wait(); + }); + }); }); diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr index b157248e15f..558b9e4d9a9 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -1,5 +1,7 @@ mod test_contract_interface; +// Contract that uses the autogenerated interface of the Test contract for calling its functions. +// Used for testing calling into other contracts via autogenerated interfaces. contract ImportTest { use dep::aztec::abi; use dep::aztec::abi::PrivateContextInputs; @@ -19,6 +21,10 @@ contract ImportTest { Context::new(inputs, 0).finish() } + // Calls the testCodeGen on the Test contract at the target address + // Used for testing calling a function with arguments of multiple types + // See yarn-project/acir-simulator/src/client/private_execution.ts + // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts fn main( inputs: PrivateContextInputs, target: Field @@ -47,5 +53,19 @@ contract ImportTest { context.return_values.push(return_values[0]); context.finish() } + + // Calls the getThisAddress on the Test contract at the target address + // Used for testing calling a function with no arguments + // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts 'calls a method no arguments' + fn noargs( + inputs: PrivateContextInputs, + target: Field + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let mut context = Context::new(inputs, abi::hash_args([target])); + let test_contract_instance = TestContractInterface::at(target); + let return_values = test_contract_instance.getThisAddress(&mut context); + context.return_values.push(return_values[0]); + context.finish() + } } diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index 8bf766112b4..22d6b5289b7 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -1,4 +1,4 @@ -// A contract used to test whether constructing a contract works. +// A contract used for testing a random hodgepodge of small features from simulator and end-to-end tests. contract Test { use dep::aztec::{ abi, @@ -66,6 +66,7 @@ contract Test { } // Test codegen for noir interfaces + // See yarn-project/acir-simulator/src/client/private_execution.test.ts 'nested calls through autogenerated interface' fn testCodeGen( inputs: PrivateContextInputs, aField: Field, From a9df3543a9472fce36df7974d17efc5ec9d921d7 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 11:40:54 -0300 Subject: [PATCH 08/15] Remove commented out code --- yarn-project/noir-contracts/src/scripts/copy_output.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index a277d45003b..31098eff0de 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -49,8 +49,6 @@ function writeToProject(abi: any) { * @returns The Aztec function entry. */ function getFunction(type: FunctionType, params: ABIParameter[], returns: ABIType, fn: any): FunctionAbi { - // if (!params) throw new Error(`ABI comment not found for function ${fn.name}`); - // If the function is not unconstrained, the first item is inputs or CallContext which we should omit if (type !== FunctionType.UNCONSTRAINED) params = params.slice(1); From 369b6c7235876e57491328af92fd876fb82f6f7c Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 11:52:55 -0300 Subject: [PATCH 09/15] Add test for calling an open fn --- .../end-to-end/src/e2e_nested_contract.test.ts | 5 +++++ .../src/contracts/import_test_contract/src/main.nr | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts index e403f80e550..4141267d917 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts @@ -184,5 +184,10 @@ describe('e2e_nested_contract', () => { logger(`Calling noargs on importer contract`); await importerContract.methods.noargs(testContract.address).send().wait(); }); + + it('calls an open function', async () => { + logger(`Calling openfn on importer contract`); + await importerContract.methods.openfn(testContract.address).send().wait(); + }); }); }); diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr index 558b9e4d9a9..ea692c743e3 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -67,5 +67,18 @@ contract ImportTest { context.return_values.push(return_values[0]); context.finish() } + + // Calls the createNullifierPublic on the Test contract at the target address + // Used for testing calling an open function + // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts 'calls a method no arguments' + fn openfn( + inputs: PrivateContextInputs, + target: Field, + ) -> distinct pub abi::PrivateCircuitPublicInputs { + let mut context = Context::new(inputs, abi::hash_args([target])); + let test_contract_instance = TestContractInterface::at(target); + test_contract_instance.createNullifierPublic(&mut context, 1, 2); + context.finish() + } } From 958b8c33523d0d19f999e52530e1678e603001e2 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 13:05:57 -0300 Subject: [PATCH 10/15] Rename fns in importer contract --- .../end-to-end/src/e2e_nested_contract.test.ts | 4 ++-- .../src/contracts/import_test_contract/src/main.nr | 8 ++++---- .../src/contracts/test_contract/src/main.nr | 10 +++++++++- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts index 4141267d917..60fb6159c60 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts @@ -182,12 +182,12 @@ describe('e2e_nested_contract', () => { it('calls a method no arguments', async () => { logger(`Calling noargs on importer contract`); - await importerContract.methods.noargs(testContract.address).send().wait(); + await importerContract.methods.callNoArgs(testContract.address).send().wait(); }); it('calls an open function', async () => { logger(`Calling openfn on importer contract`); - await importerContract.methods.openfn(testContract.address).send().wait(); + await importerContract.methods.callOpenFn(testContract.address).send().wait(); }); }); }); diff --git a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr index ea692c743e3..2eb0637b369 100644 --- a/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/import_test_contract/src/main.nr @@ -56,8 +56,8 @@ contract ImportTest { // Calls the getThisAddress on the Test contract at the target address // Used for testing calling a function with no arguments - // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts 'calls a method no arguments' - fn noargs( + // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts + fn callNoArgs( inputs: PrivateContextInputs, target: Field ) -> distinct pub abi::PrivateCircuitPublicInputs { @@ -70,8 +70,8 @@ contract ImportTest { // Calls the createNullifierPublic on the Test contract at the target address // Used for testing calling an open function - // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts 'calls a method no arguments' - fn openfn( + // See yarn-project/end-to-end/src/e2e_nested_contract.test.ts + fn callOpenFn( inputs: PrivateContextInputs, target: Field, ) -> distinct pub abi::PrivateCircuitPublicInputs { diff --git a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr index 22d6b5289b7..33c03660f08 100644 --- a/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/test_contract/src/main.nr @@ -14,7 +14,8 @@ contract Test { create_l2_to_l1_message::create_l2_to_l1_message, create_nullifier::create_nullifier, get_public_key::get_public_key, - context::get_portal_address + context::get_portal_address, + rand::rand, }; fn constructor( @@ -125,6 +126,13 @@ contract Test { 0 } + // Purely exists for testing + unconstrained fn getRandom( + kindaSeed: Field + ) -> Field { + kindaSeed * rand() + } + struct DummyNote { amount: Field, secretHash: Field From e6ddf333f5a9c68f123ff845bd69c5e205eb25cd Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 13:06:06 -0300 Subject: [PATCH 11/15] Do not include unconstrained fns in interface --- .../src/contract-interface-gen/noir.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index 745bc7e9002..b4365af5600 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -13,6 +13,15 @@ import compact from 'lodash.compact'; import times from 'lodash.times'; import upperFirst from 'lodash.upperfirst'; +/** + * Returns whether this function type corresponds to a private call. + * @param functionType - The function type. + * @returns Whether this function type corresponds to a private call. + */ +function isPrivateCall(functionType: FunctionType) { + return functionType === FunctionType.SECRET; +} + /** * Generates a call to a private function using the context. * @param selector - The selector of a function. @@ -20,7 +29,7 @@ import upperFirst from 'lodash.upperfirst'; * @returns A code string. */ function generateCallStatement(selector: string, functionType: FunctionType) { - const callMethod = functionType === FunctionType.SECRET ? 'call_private_function' : 'call_public_function'; + const callMethod = isPrivateCall(functionType) ? 'call_private_function' : 'call_public_function'; return ` context.${callMethod}(self.address, ${selector}, serialised_args)`; } @@ -134,7 +143,7 @@ function generateFunctionInterface(functionData: FunctionAbi) { const serialisation = generateSerialisation(parameters); const callStatement = generateCallStatement(selector, functionData.functionType); const allParams = ['self', 'context: &mut Context', ...parameters.map(p => generateParameter(p, functionData))]; - const retType = functionData.functionType === FunctionType.SECRET ? `-> [Field; RETURN_VALUES_LENGTH] ` : ``; + const retType = isPrivateCall(functionData.functionType) ? `-> [Field; RETURN_VALUES_LENGTH] ` : ``; return ` fn ${name}( @@ -230,8 +239,13 @@ function collectStructs(params: ABIVariable[], parentNames: string[]): StructInf * @returns The corresponding ts code. */ export function generateNoirContractInterface(abi: ContractAbi) { + // We don't allow calling a constructor, internal fns, or unconstrained fns from other contracts + const methods = compact( + abi.functions.filter( + f => f.name !== 'constructor' && !f.isInternal && f.functionType !== FunctionType.UNCONSTRAINED, + ), + ); const contractStruct: string = generateContractInterfaceStruct(abi.name); - const methods = compact(abi.functions.filter(f => f.name !== 'constructor' && !f.isInternal)); const paramStructs = methods.flatMap(m => collectStructs(m.parameters, [m.name])).map(generateStruct); const functionInterfaces = methods.map(generateFunctionInterface); const contractImpl: string = generateContractInterfaceImpl(abi.name, functionInterfaces); From 8b1c029a6c459e98031da97dc1525f5f6ceb9a9a Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 15:20:17 -0300 Subject: [PATCH 12/15] Minor changes --- .../src/contract-interface-gen/noir.ts | 8 ++++---- .../noir-contracts/src/scripts/copy_output.ts | 19 +++++++++---------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts index b4365af5600..2e38b9fea2c 100644 --- a/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts +++ b/yarn-project/noir-compiler/src/contract-interface-gen/noir.ts @@ -35,11 +35,11 @@ function generateCallStatement(selector: string, functionType: FunctionType) { } /** - * Formats a fragment of a struct name. - * @param str - A fragment. + * Formats a string as pascal case. + * @param str - A string. * @returns A capitalised camelcase string. */ -function formatStructNameFragment(str: string) { +function toPascalCase(str: string) { return upperFirst(camelCase(str)); } @@ -49,7 +49,7 @@ function formatStructNameFragment(str: string) { * @returns The concatenation of the capitalised fragments. */ function getStructName(...fragments: string[]) { - return fragments.map(formatStructNameFragment).join('') + 'Struct'; + return fragments.map(toPascalCase).join('') + 'Struct'; } /** diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index 31098eff0de..b1883dbe11c 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -48,20 +48,19 @@ function writeToProject(abi: any) { * @param fn - The nargo function entry. * @returns The Aztec function entry. */ -function getFunction(type: FunctionType, params: ABIParameter[], returns: ABIType, fn: any): FunctionAbi { +function getFunction(fn: any): FunctionAbi { + const type = fn.function_type.toLowerCase(); + const returns = fn.abi.return_type; + const isInternal = fn.is_internal; + let params = fn.abi.parameters; + // If the function is not unconstrained, the first item is inputs or CallContext which we should omit if (type !== FunctionType.UNCONSTRAINED) params = params.slice(1); - // If the function is not secret, drop any padding from the end - // TODO: I can't find a reference to 'padding' anywhere? - if (type !== FunctionType.SECRET && params.length > 0 && params[params.length - 1].name.endsWith('padding')) { - params = params.slice(0, params.length - 1); - } - return { name: fn.name, functionType: type, - isInternal: fn.is_internal, + isInternal, parameters: params, // If the function is secret, the return is the public inputs, which should be omitted returnTypes: type === FunctionType.SECRET ? [] : [returns], @@ -77,7 +76,7 @@ function getFunction(type: FunctionType, params: ABIParameter[], returns: ABITyp * @param buildJson - The nargo output. * @returns The Aztec function entries. */ -function getFunctions(sourceCode: string, buildJson: any): FunctionAbi[] { +function getFunctions(_sourceCode: string, buildJson: any): FunctionAbi[] { /** * Sort functions alphabetically, by name. * Remove the proving key field of the function. @@ -87,7 +86,7 @@ function getFunctions(sourceCode: string, buildJson: any): FunctionAbi[] { .sort((fnA: any, fnB: any) => fnA.name.localeCompare(fnB.name)) .map((fn: any) => { delete fn.proving_key; - return getFunction(fn.function_type.toLowerCase() as FunctionType, fn.abi.parameters, fn.abi.return_type, fn); + return getFunction(fn); }); } From 52436f8c9bc2659d4ca3bd61215f8769e8561396 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 15:20:35 -0300 Subject: [PATCH 13/15] Increase e2e test timeout --- yarn-project/end-to-end/src/e2e_nested_contract.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts index 60fb6159c60..fb8cdedd015 100644 --- a/yarn-project/end-to-end/src/e2e_nested_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_nested_contract.test.ts @@ -178,16 +178,16 @@ describe('e2e_nested_contract', () => { it('calls a method with multiple arguments', async () => { logger(`Calling main on importer contract`); await importerContract.methods.main(testContract.address).send().wait(); - }); + }, 30_000); it('calls a method no arguments', async () => { logger(`Calling noargs on importer contract`); await importerContract.methods.callNoArgs(testContract.address).send().wait(); - }); + }, 30_000); it('calls an open function', async () => { logger(`Calling openfn on importer contract`); await importerContract.methods.callOpenFn(testContract.address).send().wait(); - }); + }, 30_000); }); }); From 9f27accfe970717a5d9c018231ff502faf2e2f60 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 15:20:55 -0300 Subject: [PATCH 14/15] Format --- yarn-project/noir-contracts/src/scripts/copy_output.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/noir-contracts/src/scripts/copy_output.ts b/yarn-project/noir-contracts/src/scripts/copy_output.ts index b1883dbe11c..3531b916e41 100644 --- a/yarn-project/noir-contracts/src/scripts/copy_output.ts +++ b/yarn-project/noir-contracts/src/scripts/copy_output.ts @@ -1,4 +1,4 @@ -import { ABIParameter, ABIType, ContractAbi, FunctionAbi, FunctionType } from '@aztec/foundation/abi'; +import { ContractAbi, FunctionAbi, FunctionType } from '@aztec/foundation/abi'; import { createConsoleLogger } from '@aztec/foundation/log'; import { generateNoirContractInterface, generateTSContractInterface } from '@aztec/noir-compiler'; From 82d5bd0b34bc789c779a916a2e472c6b74424d76 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 11 Aug 2023 15:26:03 -0300 Subject: [PATCH 15/15] Update contract artifacts after merge --- yarn-project/aztec.js/src/abis/ecdsa_account_contract.json | 2 +- yarn-project/aztec.js/src/abis/schnorr_account_contract.json | 2 +- .../aztec.js/src/abis/schnorr_single_key_account_contract.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn-project/aztec.js/src/abis/ecdsa_account_contract.json b/yarn-project/aztec.js/src/abis/ecdsa_account_contract.json index 40ba33f0262..6b9d14f4b01 100644 --- a/yarn-project/aztec.js/src/abis/ecdsa_account_contract.json +++ b/yarn-project/aztec.js/src/abis/ecdsa_account_contract.json @@ -152,7 +152,7 @@ } ], "returnTypes": [], - "bytecode": "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", + "bytecode": "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", "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" } ] diff --git a/yarn-project/aztec.js/src/abis/schnorr_account_contract.json b/yarn-project/aztec.js/src/abis/schnorr_account_contract.json index 3c4ff949cd6..d90bd6cfc02 100644 --- a/yarn-project/aztec.js/src/abis/schnorr_account_contract.json +++ b/yarn-project/aztec.js/src/abis/schnorr_account_contract.json @@ -140,7 +140,7 @@ } ], "returnTypes": [], - "bytecode": "H4sIAAAAAAAA/+2dB5hbxfHAn6TruuLrdy7nc++2dMV35yrb2MaAccMYYxs3zmAwNsWmE3oLCT0EQgsQeu+9Qwgh9JLQW3pCOgmEAP/d8ww32ns+MJqVZ/967/vmm30rafc3s7uz+6R9T5dkeV65En2ElISVZEEaz7ON8xxIZ2/+mAcf96qUVCupUVJLPoev91TSS0lvJX3g9TB5vU5JXyX1SvqR+gYoySPnA43zQcb5YON8iHE+1DgfZpwPN85HGOcjjfNRxvlo43yMcR4zzuPGeYNx3micNxnnzcb5WOO8xThvNc7bjPNxxvl443yCcT7ROJ9knE82zhPG+RTjfKpxPs043844n26czzDOZxrn2xvns4zzHYzzHY3znYzz2cb5zsb5HON8rnE+zzifb5wvMM53Mc4XGue7GueLjPPdjPPFxvnuxvkS43ypcb7MON/DOF9unK8wzlca56vgXMeHiLe5v+hDxwE99vV412Ncj+uh3ubxq8esHqd6bOrxqMegHnd6rOnxpceUHkd67OjxoseIHhd6LOj+r/u87ue6b+v+rPvwZKhb90/dJ3U/1H1P9zfdx3S/0n1J9x/dZ3Q/0X1D9wfdB+ZAW8+DNl0AbbcQ2mgRtMVi8PkS8O0y8OFy8NVK8In2j4699eAPHW+/8DbHXK1rQNeC7gm6F+jeoPuArgPdF3Q96H6g+4MeAHog6EGgB4MeAnoo6GGgh4MeAXok6FGgR4MeAzoGOg66AXQj6CbQzaS81Ur29PHNWHhPC+hW0G2gx4EeD3oC6ImgJ4GeDDoBegroqaCngd4O9HTQM0DPBL096FmgdwC9I+idQM8GvTPoOaDngp4Hej7oBaB3Ab2Q+KZdyRov+QiBToBujI1tampvaWiPN8ZXxhraVrU2x5qaV41tjbfGm1ub92xobWxsb21qbWlb1dYSa4s3NbbH1zS3Na6JbT72ImXFUjxscu7tCOdaRzj3cYRzX0c41znCuZ8jnOsd4dzgCOf+jnAe4AjngY5wHuQI50ZHODc5wnmwI5yHOMJ5qCOchzFymtdk+ppXX5ssAr0b6MWgdwe9BPRS0MtA7wF6OegVoFeCXgV6L9B7g14Leh/Q+4JeB3o/0OtBbwC9P+gDQB8I+iDQG0FvAn0w6ENAHwr6MK/zmuxwJUd4yQd3Gx7pudHXjnKE8zuOcB7tCOcxjnAe6wjncY5wHu8I5wmOcJ7oCOdJjnCe7AjnKR7/Gq0HlKe/T9drlXbQh4M+EvRRoL8D+mjQx4A+FvRxoI8HfQLoE0GfBPpk0Kd4nWukU5V819v820+et+UjweODuL2ym2yW3WCx7EaLZTdZLLvZYtljLZbdkkPKPA3090B/H/TpoM8AfSb5zAmFm3U+iD5yvc48HEc5JA9fzyZ5+HoWycPXIyQPXw+TPHw9RPLwdc+oXx8J0LEUjxyva4yNpXhom8uIHZ6PvSEfv4R9/IevZ/v4j7YHvo7tUqwk6lO3/kwBr73xkJd8JEga66IsEUEsWYJYsgWx5AhiyRXEkieIJV8QS2gbs9CYioceTy9HOl/HdSqNw6WQpnEYYzeNw+WkTMyrIDZjXiWk6fyJjD1IHvqulOTBNJw0dxRBupzkFUO6guSVQLrSh4W2DX4mATqW2tHRNrSeBDnHugoIQ6UAlnxBLHmCWHIFseQIYskWxJIliCUiiCVssGxp7WuDjx4Jkq7wYYkIYskSxJItiCVHEEuuIJY8QSz5glgKBLFEBbEUCmIpEsRSLIilRBCL7XXE1rDYvmb6Oha/61l6zUmve8sNfnoNW0jy8FqziOThNWkxyauCdAnJC/vw4VqGXpvimoJew+LcTq91cY6l18Q412H9+nMfk+v3Gsin1++1kKbX7z0hTa/fe5EyMa83pOn1ex9I0+v3OkjnkTxkrCF5aEstyUObe5I89E0vkoc+7E3y0Nd9SF41pOt8+Gifxc8kQMdSOzr6LK0nQc6xLnqdXyeApVIQS4kglmJBLEWCWAoFsUQFsRQIYskXxJIniCVXEEuOIJZsQSxZglgigljCPiy9eVlidG3nESZ6JEiarg17MbPoMntasK/XVtjXk9hXa8E+5jLjuswaC5z1vGW26Hbo633zdqgn7dCP2T5dRn9SF3JhPVHyehXh6M/cdiFSJ5aL55Tvm7L2cIi11CHWModYyx1irXCItXobs/LXG++IybRefXQXkynLAFaWzXPOQOYydRmDCD/aiuxR8vpAYtsgXo6O9h3gJfsUzweRegP7WesN7PcC+wP7A/sD+wP7A/sD+wP7A/sD+wP7A/sD+wP7A/sD+wP7A/sD+yXYv6X7cpi/Z+92j+0AH5aIIJYsQSzZglhyBLHkCmLJE8SSL4ilQBBLVBBLoSCWIkEsxYJYSgSx9BDEUiqIpUwQS7kglgpBLJWCWKoEsVQLYqkRxFIriKWnIJZeglh6C2LpI4ilThBLX0Es9YJY+gli6S+IZaAgltA2ZtnSvd/4epjk4fdqEZI3GNL0/uchkKb3Pw8ldmLeMEjT+5+HQ5re/zyCpFGPhDS9X3kUpOm9zqMhTe+THgNpej81Pgi5L8nDh+L2I3noD+o/9Mcgkof+GEzy0B9DSB76YyjJQ38MI3noj+EkD/1B/YPfQ4wkedjfRpE8vC4fTfLw+ngMycPr1BjJw+tF9I+2Kzur83V8L+07cZ9yME3HANadwPczjAFaT4KcY130XvKYAJaBglj6C2LpJ4ilXhBLX0EsdYJY+ghi6S2IpZcglp6CWGoFsdQIYqkWxFIliKVSEEuFIJZyQSxlglhKBbH0EMRSIoilWBBLkSCWQkEsUUEsBYJY8gWx5AliyRXEkiOIJVsQS5YglogglrAPi409nfg9oD7wu7qBhAOZRhOOUcw+0WWM9OEYRTiw/pGEYwQvR8f/lQ334RhBOLD+4YRjGC9Hx3+bDfXhGEY4sH76/foQXo4mXcZgH44hhAPrH0w4mPf8dvxn2gAfjkGEA+sfQDgaeDk6/l+t0YejgXBg/fi+Le1FbuRl6/Y3Hz+WiCCWLEEs2YJYcgSx5ApiyRPEki+IpUAQS1QQS6EgliJBLMWCWEoEsfQQxFIqiKVMEEu5IJYKQSyVgliqBLFUC2KpEcRSK4ilpyCWXoJYegti6SOIpU4QS19BLPWCWPoJYukviGWAIJaBglgGCWIZLIhliCCWoYJYhgliGS6IZYQglpGCWEYJYhktiGWMIJaYIJa4IJYGQSyhbcyypfuX8HV6L0sTpOk9L82QpvfLjIU0vdemBdL0Pp1WSA8meW2QpvcHhX2Y8Xe3JpKHv381kzz8HWosycPfg1pIHv4u00ry8PcRZNJlDYh2vo48YfKZcZCm93iNhzS9x2sCKRPzJkKa3uM1CdL0Hi/kof5A7nEkD+0bT/LQDxNIHvprIslDv07yYaF9Fj+TAB1L7ejos7SeBDnHuuj9RpMEsDQIYokLYokJYhkjiGW0IJZRglhGCmIZIYhluCCWYYJYhgpiGSKIZbAglkGCWAYKYhkgiKW/IJZ+gljqBbH0FcRSJ4iljyCW3oJYegli6SmIpVYQS40glmpBLFWCWCoFsVQIYikXxFImiKVUEEsPQSwlgliKBbEUCWIpFMQSFcRSIIglXxBLniCWXEEsOYJYsgWxZAliiQhiCRss9LfANpKHv9nR3yjxtz36Wyb+Bkh/88TfCulvo5MhTX9DDRt89LdW+pshtiX9bRH7Gv0NEscC/a0SxyrWr8+39Js48iRAx1I7uv1NnP6Oa75P29ZAfvvN8/kMxmb62y/OHfS33ygpE/PovVWYh2sD+tsv1kef70nrQ431FZA8rC9K8rC+QpKH9RX5sNC2wc8kQMdSOzrahtaTIOeFxJ6QDx++TtsD7fy69kC/0fag90ZiHq4b/dqD+g/ro37urj1ou2F9tH2xPlp/DnlPAnQsxYP6gtaPzF/nW/QB9S22EbWV3i+HeSXENsyj9aHG+qgfsT7qb6yPtgvWR/uN6Vva9pRJfxav7RKgY6kdDbouvEbDo7v4VEoY8ZqX3mNXzsvXMR7LDBY8x7qihKHYHktLdAt14xEmdZdZ8INn+AGPMh+WiCCWLEEs2YJYcgSx5ApiyRPEki+IpUAQS1QQS6EgliJBLMWCWEoEsfQQxFIqiCW0jVn8rnnpOpOuxXH9RdfgFYZNOg9/O6NrcPxtj67B8bfHEpIX9uHDdVU5ycP1TQXJw3VGJcnD+b6K5OG8i/Xrz82LdmUN+7BW+dhE2xDrToCOpXZ0tCGtJ0HOsS56bVwlgKVUEEsPQSwlgliKBbEUCWIpFMQSFcRSIIglXxBLniCWXEEsOYJYsgWxZAliiQhiCfuwVPCydNyWhGtIfeCaroJwIBN9PhbzujwWMjjqSb30GWE1zG2hy6j1sb+G2I/115I8TNNrOO620TG9p9EeeqyclWXPH7rMOmY7dNvifmB9HE3sqiP+s1FvH6PeGqNe/R76XKajCSt+NkLec3FWZztcAGm6Dxz7g267vkZd9FoOX8PfU+ot2I51eFB+T5JG2+uJ7fXkM+XEdnzP5cT2vtHOz/XnZe/4Wb4flBUm3P0JK/Nzyxt0GfR50Fh+P5I3mKQxTuBn6P09gwmnjXhFObD+GpI31IdzMOEcYrxPcw7j5ezof5QjROrFuiLkPbeQvlVH+paNdh7mdfUffV7RCN46G/W4H+4lH919D0WfszKSlyVmaw0xivCjrcgeJa/T51pyP/M/5CU/8z9BzukzWgL7Wet1yv4t/U7OPM66/Z55pA9LRBBLliCWbEEsOYJYcgWx5AliyRfEUiCIJSqIpVAQS5EglmJBLCWCWHoIYikVxFImiKVcEEuFIJZKQSxVgliqBbHUCGKpFcTSUxBLL0EsvQWx9BHEUieIpa8glnpBLP0EsfQXxDJAEMtAQSyDBLEMFsQyRBDLUEEswwSxDBfEMkIQS2gbs2xpfzW+Xk3y8Ht7+vxsfGbsMJIX9qkDv1MfRfLwu20sQ3+/vGe0a31hn/pG+XDZ9iWtJ0HOsS66z3mUAJYRgliGC2IZJohlqCCWIYJYBgtiGSSIZaAglgGCWPoLYukniKVeEEtfQSx1glj6CGLpLYillyCWnoJYagWx1AhiqRbEUiWIpVIQS4UglnJBLGWCWEoFsfQQxFIiiKVYEEuRIJZCQSxRQSwFgljyBbHkCWLJFcSSI4glWxBLliCWiCCWsMES7O3/epZgb78/S7C3358l2NvvzxLs7fdnKRTEUiSIJdjb788S7O33Zwn29vuzBHv7/VmCvf3+LMHefn+WYG+/P0uwt9+fJdjb789SL4ilnyCW/oJYgr39/izB3n5/lmBvvz9LsLffn2WEIBbb38tvDctoQSyhbczydfc8jCZ5YeOz+nvyS8g9CvgfdWHyGfwvO/ofVGMhTf+DqoWUiXn4H3o5JA//ay/Xh5X+R94YSNP/0otBmv7nXhzS9L/5GiBN/8MP/xuvzYeFtiF+JgE6ltrR0Ya0ngQ5x7rovRZtAlhGC2IZJYhlhCCW4YJYhgliGSqIZYgglsGCWAYJYhkoiGWAIJb+glj6CWKpF8TSVxBLnSCWPoJYegti6SWIpacgllpBLDWCWKoFsVQJYqkUxFIhiKVcEEuZIJZSQSw9BLGUCGIpFsRSJIilUBBLVBBLgSCWfEEseYJYcgWx5AhiyRbEkiWIJSKIJezD0sLL0kB/o/EIEz0SJE1/YxlrMGu+Zgu+Gmuw4DnWFSUMIy2yRH3qtlBPQ75hsz66axP6+xj+fjaW8I1j9kOI1IPl4jnWRX01xiJL1KduC/U05Bs266O7NsH69efGQ7qJ8E1g9kOI1IPl4jnWRX0Vs8gS9anbQj0N+YbN+uiuTbB+/bmJkB5P+CYx+yFE6sFy8Rzror6KW2SJ+tRtoZ6GfMNmfXTXJli//txkSE8kfAlmP4RIPVjuZKMO6qsGiyxRn7ot1NNAfYtHd22Caf25KZCeTPimMvshROrBcvEc66K+arTIEt1C3XiESd1TLPjBM/yAxxQflogglixBLNmCWHIEseQKYskTxJIviKVAEEtUEEuhIJYiQSzFglhKBLH0EMRSKoilTBBLuSCWCkEslYJYqgSxVAtiqRHEUiuIpacgll6CWHoLYukjiKVOEEtfQSz1glj6CWLpL4hlgCCWgYJYBgliGSyIZYgglqGCWIYJYhkuiGWEIJaRglhGCWIZLYhljCCWmCCWuCCWBkEsjYJYmgSxNAtiGSuIpUUQS6sgljZBLOMEsYwXxDJBEMtEQSyTBLFMFsSSEMQS2sYsW3q+DL5On7EyFdL0+SzTIE2f7bIdpMeTvOmQnkjyZkB6MsmbCekykrc9pAeRvFmQDpO8sI9tuI9mKsnD/SzTSB7uK9mO5OH+jukkD/dZzCB5uN9hJsnDfQfbkzz8/R/ZdZ0fRbvaRPsEfj4BOpba0dEnaD0Jco510efVzBLAkhDEMlkQyyRBLBMFsUwQxDJeEMs4QSxtglhaBbG0CGIZK4ilWRBLkyCWRkEsDYJY4oJYYoJYxghiGS2IZZQglpGCWEYIYhkuiGWYIJahgliGCGIZLIhlkCCWgYJYBghi6S+IpZ8glnpBLH0FsdQJYukjiKW3IJZeglh6CmKpFcRSI4ilWhBLlSCWSkEsFYJYygWxlAliKRXE0kMQS4kglmJBLEWCWAoFsUQFsRQIYskXxJIniCVXEEuOIJZsQSxZglgigljCBksBeb0HycN9NvR5ijMgPZbk4b6dJpJn7k3SebgPaCLJmwpp3O8RPCfo61mC5wT5s+QIYgmeE+TPki+IJXhOkD9L8Jwgf5bgOUH+LMFzgvxZgucE+bMEzwnyZwmeE+TPEjwnyJ8leE6QP0vwnCB/luA5Qf4s9YJY+gli6S+IJXhOkD9L8Jwgf5bgOUH+LMMEsQwXxDJCEEvwnCB/luA5Qf4swXOC/FmC5wT5swTPCfJnCZ4T5M8SPCfInyV4TpA/S/CcIH+W4DlB/iwJQSxTBbFME8SynSCW6YJYZghimSmIZXtBLLMEsYS2MUu+1/1zyOiztXaA9AyStyOk6bO6doI0fabXbEjTZ3/tDOmpJC/sw4d77XYgebjnbUeSh3vPdiJ5uAdsNsnDvVhYv/7c5MLO1+dCfph8Zh6kIyRvPqSzSN4CUibm7QLpHJK3ENK5JG9XSOeRPGScS/LQlnkkD22eT/LQNwtIHvpwF5KHvl5I8uZAelcfPtpn8TMJ0LHUjo4+S+tJkHOsiz4nbVcBLLMEsWwviGWmIJYZglimC2LZThDLNEEsUwWxJASxTBbEMkkQy0RBLBMEsYwXxDJOEEubIJZWQSwtgljGCmJpFsTSJIilURBLgyCWuCCWmCCWMYJYRgtiGSWIZaQglhGCWIYLYhkmiGWoIJYhglgGC2IZJIhloCCWAYJY+gti6SeIpV4QS19BLHWCWPoIYuktiKWXIJaeglhqBbHUCGKpFsRSJYilUhBLhSCWckEsZYJYSgWx9BDEUiKIpVgQS5EglkJBLFFBLAWCWPIFseQJYskVxJIjiCVbEEuWIJaIIJawD8suvCwttE5dH16T0T2K85nrpPsiPeIHeiRIej5hmcvLEtP1LiLlJ0gdtN7deOuN03pDIFgH5kdI+jKcbMj79IH7+5BZv22ez/toeoHxmSh5fZ5lm+cSjgQ5x7p0LDiP2DrPh3sh4cbXdybcNczcuoz5hAPrp88dYu6XLXSPMR7djZG5hIW53TrGyGJSfoLUQevdndnvtF4cI1gH5kdI+g7Sb3bvTH7Vb5BZv22Rz/to2hxDUfL6Iss207GaIOdYlx4j1xJbF/lwzyfc+Ppswm1jjNCxjfXTMcLcLzvGCLVdH92Nkd0IC3O7dYyRJaT8BKmD1ruU2e+0XhwjWAfmR0j6KdJvlnYmv+o3yKzfttjnfTRtjqEoeX2xZZvpWE2Qc6xLj5EHiK2Lfbjp/Iev70S4bYwROraxfjpGmPtlxxihtuujuzGyO2FhbreOMbKMlJ8gddB692D2O60XxwjWgfkRkn6D9Js9OpNf9Rtk1m9b4vM+mjbHUJS8vsSyzXSsJsg51qXHyPPE1iU+3HT+w9d3JNw2xggd21g/HSPM/bJjjFDb9dHdGFlKWJjbrWOMLCflJ0gdtN4VvPXGab04RrAOzI+Q9Eek36zoTH7Vb5BZv22Zz/to2hxDUfL6Mss207GaIOdYlx4jHxBbl/lw0/kPX9+BcNsYI0sJB9ZPxwhzv+wYI9R2fXQ3RvYgLCt4WTrGyEpSfoLUQetdxVtvnNaLYwTrwPwISdMbe1d1Jr/qNytA6+613Od9NG2OoSh5fbllm1cQjgQ5x7r0GPmYjJHlPtxLCTe+PoVw2xgjdGxj/XSMrOCts2OMUNv10d0YWUFYmNutY4ysJuUnSB203j15643TenGMYB2YHyHpcjJG9uxMftVvkFl3r5U+76NpcwxFyesrLdtMx2qCnGNdeozkEVtX+nDT+Q9fn0O4bYyRFYQD68d68gkHfYa/zbiK5eI5bcsehr8ssLREferWbVcX7Uz3jdptE+qLnj5tgnkrCd9Z8COKHlv4u8Eg4CyCz+FvhPSZBQWkDMzDbkqfWUD/8wPz8Ddq+swC/A2dPrMgTNKokaGA5CFDlOQhQyHJQ4YikocMxYRpS8/VQJ4E6FhqR7fP1aC2m+/Tts0p7Gpr2MfWiI+ttM3CpEzMo/9JhXn4mVyf8qiPcgxbYqkdHT6i9SS85P9A0gd9jkOuPZaYK2XqtsnzurZhvk9egU8e9n/an3Ac0f6E48ivP9FxTD+DGj9DxzH2QTqOkYuOY2xjGkdySPkJ0LHUjgbqRzy6G7PUvgLDDznELia+jrFRYLAUGD6MEoZcayzxjvnPrLvAxw90Lw71TSEzjy6zmLlM2ufx6K4/0DkNbd2rfePOGza2HxQin8cycc9sLikjTN4X8brWneV1PbJJOoekC8nnokad2jbcU0fnR6y/hLBZiFkNdPxT++iR8LGLxgP6n4F5vHxJ8xCWi+dYV5QwROyxNEZ96i7Ygh+YY2LS3IJl6z54AFnbMse5Rv6xvPk6F8eDPo4mdtH/Y7NRb5FRb9Sol8b1HHgPsuJnI+Q9R5FrjMMgTfel0rVdiVHXlsY43WNLx2UPkkZ/0RhURtJh4zN0PzP9DzPc+50AHUvtaMw3OPTRXSyh/2FWycvS0d5VpPwEqYPWW81bb5zWi9+nYB2YHyHpM8lFT3Vn8qs+gMz0vgH6PpouMz4TJa9XWLa5knAkyDnWpfvqicTWCh9uGs/xdfr/fxXM3LqMcsKRZ7DlEztobK9Ko/+qiE+yDX9ZYOmY48y6bfm+8mt8j3n4PnO+y+Jl6njMBY25YVKvPuh+U9ofuK+9dRl+a1F63Y3102sL5vVOA7aDyeG39sH3RbfAzbwein+T60HKEhHEkiWIxeKadatZcgSx5ApiyRPEEtrGLN/ke1lcs9Pv0ej6HfNwLU6/R8N66PdeuN6n1+fmtQMtj/qoyLAlltrR4SNaT4KcY130e9liASx5glhyBbHkCGLJFsSSJYglIoglbLBsKa5h7PL7/oGu13qQNOpSUh/m4fUG/X0gbPDR3xto7MS2LCF5yEXrx7FQSvKQtYywb2l9aeM7fnokSDpqaM9LXl9ua5YsQSzZglhyBLHY+01o61ls/1a2NSz5glgKBLGEtjGL37o71d+su/uenP7OhPMEnbtwnqC/iZSRNGr8fo/OZ2Ef27qbu+gcZ66T6NxF5zhkpXMcstI5DlkpO7Iiu65zcWFXm2ifwM8nQMdSOzr6BK0nQc6xLnqdUS6ApUAQS74gljxBLLmCWHIEsWQLYskSxBIRxBI2WHBPBveeBzov0PkN4xuda3EO87v+o3MtzmF0rqXXtJjnd51I60Ntez8onS/NPTDIkwAdS/Fwpcyv23Ma9vEnzcP01u45xX0J5lqPPmuK9gdzree3J4Luc6C/a3W31qPX+zZiANaD5Zp7JqJe17WjDZaoT93UDxEBfjDXy9vCD1kC/GBeI2wLP2QL8AMy5G1DP+QI8AONo9vKD7kC/IAMBWn2g67X/J6CdWMGHhGj7MbY2Kam9paG9nhjfGWsoW1Va3OsqXnV2NZ4a7y5tXnPhtbGxvbWptaWtlVtLbG2eFNje3xNc1vjGig8zMh5OiPXWXxcsYhf45A8Lvs5mSnv2SSNgT/s0ydyLNjkGfWYfiz2LHd8G410toVyz/H4Or8tu8/hb6MYbXfpPsWD+QopfiYj57mMZaUr8J3r2Ql8PyDpIPClWOa54FDucs/zZAc+bfd5/G1kNfBx+jRdQeAMz04Q+CFJB0EgxTLPAIdyl3u+JzsIaLvP52+jjo6a43V2wtOUfKHke6C/D1ofFyj5EdhGv6c9E95zAXnvhUouIu/9JuVfrOSSbsq/mLz3UiU/9nnv6fCeS0HrAXeZkst93nsGvOcy0JrxCiU/8ZIPc7WVav/g7GtXerzfUdB9elXgl2rQNaBrQfcE3Qt0b9B9QNeB7gu6HnQ/0P2JvkrJ1eBrGhy5x9FVfGXF6qGca5Rcq+Q6JdcruUHJjUpuUnKzkluU3KrkNiW3K7lDyZ1K7lJyt5J7lNyr5D4l9yt5QMmDSh5S8rCSR5Q8quQxJY8reULJk0p+quQpcFII/KZZ8rzO82uN8+uM8+uN8xuM8xuN85uM85uN81uM81uN89uM89uN8zuM8zuN87uM87uN83uM83uN8/uM8/uN8weM8weN84eM84eN80eM80eN88eM88eN8yeM8yeN858a5095XZ/5YH7FFkvtSBozqcaraxjL+ixiZzHM9RVl+xp9xOLXMpWl2+I6Rv/9T7z/OoqOX596WQ1gc/wGRv99Ltl/TV9xxm9MrawYsTl+E6P/vpDqv4YkzvjN376smGFz/BZG/30p0H9j13ThjN/67cpq9bE5fhuj//TFgSj/tfpyxm/f+rJatmBz/A5G/4Uk+a9li5zxO7eurIZubI7fxei/sBT/tXTLGb/7m5e1+mtsjt/D6L+IBP+1fC1n/N5vVlbsG9gcv4/Rf1nb2n+xb8QZv//ry2r+hjbHH2D0X/a29F/TN+aMP9htWU1rtsLm+EOM/svZVv5r2SrO+MNbLqt1K22OP8Lov9xt4L+2NVvNGX/Uv6zYt7A5/hij//LS7b/Yt+KMP961rPi3tDn+BKP/8tPpvz2/NWf8yeSyGlOwOf5TRv8VpMl/DWtS4ow/5fF9l0i/s0vVf9E0+S+W2hFn/J4t/jmj/wod8R/j90TxLxn9V+SI/xi/54jTa/5U/VfsiP8Yr9PjEUb/lTjiP8brzHg2o/96OOI/xuukeC6j/0od8R/jOj+ez+i/Mkf8x7hOjUcZ/VfuiP8Y11nxIkb/VTjiP8Z1QryE0X+VjviPcZ6LlzL6r8oR/zHG6Xg5o/+qHfEfY5yJVzL6r8YR/zGOkzhjn4lz+k/vZ9P3Qpv7g7H8Ad7mfW4DQQ8CPRj0ENBDQQ8DPRz0CNAjQY8CPRr0GNAx0HHQDaAbQTeBbgY9FnQL6FbQbaDHgR4PegLoiaAngZ4MOgF6CuipoKeB3g70dNAzQM8EvT3oWaB3AL0j6J1Azwa9M+g5oOeCngd6PugFoHcBvRD0rqAXgd4N9GLQu4NeAnop6GWg9wC9HPQK0CtBrwJd720+cL8j7oPE/ZFPgMb9lI+Bxv2Xj4DG/Zq4jxP3d+K+T9wPivtEcf8o7ivF/aa4DxX3p+K+VdzPivtccf8r7ovF/bK4jxb31+K+W9yPi/t0cf/uNaB/5iUfYdAJ0LHUjvjPPL74+jRjWem6Cehpjzem4fFzkg5uAkqxzKfBodzlPuPxdVhbdj/D30ZW7wTk9Gm6gkCdZycI/IKkgyCQYpl14FDucp/1ZAcBbfez/G3UMbjMf2y0xc/FbJOzpyOcVR5/sAqRMp9T8rySF5S8qOQlJS8reUXJq0peU/JLJb9S8rqSN5S8qeQtJW8reUfJu0reU/K+kg+UfKjk10p+o+S3Sn6n5PdK/qDkj0r+pOTPSj5S8hclf1XyNyV/V/IPJf9U8i8lHyv5t5L/KPlEyadK/qvkMyX/U/K5t/kq8kswKqQkrCSiJEtJtpIcJblK8pTkKylQElVSqKSIRET6lGIzeNMnAoZIHg3u+sgh6QToWIqHhckipq/E84gdnmFvsWfjH7yakp7U6Bn+NP1G/alZ8emiq1euWzf3wLUHr9zYPmPT+tUb125YT7t1tlFMxMc8M58+ENT801LazPTBjyGTPwE61TmFzk+x1I54umL+C56dWOrxcjZYLDtOO1cxOLiEdG4cZ2Gv6z/XhUg76c74pde1rUIkHYb3RLp5T2gL5dDxTv/FxZXYZXUhi7fd6gb83Ou8Dbck1LVS7q+B6SBK7ZbUNWs0f6plfbUNJeTeIvRFLwhINCD1AAeXBgHJzYDUwwhIpWkISHQQpRqQejAGpFIHA9JLXhCQaEAqAweXBwHJzYBUZgSk8jQEJDqIUg1IZYwBqdzBgPSaFwQkGpAqwMGVQUByMyBVGAGpMg0BiQ6iVANSBWNAqnQwIP3SCwISDUhV4ODqICC5GZCqjIBUnYaARAdRqgGpijEgVTsYkH7lBQGJBqQacHBtEJDcDEg1RkCqTUNAooMo1YBUwxiQah0MSEWhICDRgNQTHNwrCEhuBqSeRkDqlYaAVBTiC0g9GQNSL0uDm9t/dHtXqjY/x1hWb+aA3qXze/wBnZOZ8vYhJ8E+1BTL1I3UJ2RhnyRj8LBld12IvY26/Z8rzr1TqZbVNyS7X+q26Rvi33/W4shdk5xtXc/Y1i2Md7CmayKqtzQR9QsmIt5G6mdhIuovfCLSdve3PBFJ96lHOjInJ70JIlXO5xnLGuDgan6ApSA6MAiivI000EIQHSQ8iGq7B2Xwan6w8NW8bpvBFlbzbRm4mh/C2NZtDq7mh1iaiIYGExFvIw21MBENEz4RabuHObaa5/apRzoyJye9VThVztcZyxru4Gp+uKUgOiIIoryNNMJCEB0pPIhqu0dm8Gp+lPDVvG6bURZW8+MzcDU/mrGtxzu4mh9taSIaE0xEvI00xsJEFBM+EWm7Y46t5jl9mq4g0MtSEIgHQYC3keIWgkCD8CCg7W7I4NVoo/DVqG6bRgur0YkZuBptYmzriQ6uRpssTUTNwUTE20jNFiaiscInIm33WMdWo5w+TVcQqLQUBFqCIMDbSC0WgkCr8CCg7W7N4NVom/DVqG6bNgur0ckZuBodx9jWkx1cjY6zNBGNDyYi3kYab2EimiB8ItJ2T3BsNcrp03QFgWpLQWBiEAR4G2mihSAwSXgQ0HZPyuDV6GThq1HdNpMtrEanZOBqNMHY1lMcXI0mLE1EU4KJiLeRpliYiKYKn4i03VMdW41y+jRdQaDWUhCYFgQB3kaaZiEIbCc8CGi7t8vg1eh04atR3TbTLaxGp2XganQGY1tPc3A1OsPSRDQzmIh4G2mmhYloe+ETkbZ7e8dWo9w+9UhHppyplh1mtPllRq5ZjAEpXUF0lqUgukMQRHkbaQcLQXRH4UFU271jBq/mdxK+mtdts5OF1fz0DFzNz2Zs6+kOruZnW5qIdg4mIt5G2tnCRDRH+ESk7Z7j2Gqe26ce6ciUM9Wy6xhtfoWRa66Dq/m5loLovCCI8jbSPAtBdL7wIKrtnp/Bq/kFwlfzum0WWFjNz8zA1fwujG0908HV/C6WJqKFwUTE20gLLUxEuwqfiLTduzq2muf0qWbTAwQHkH6u8RdKSkKbdSnoctAFShap9G7QV3K8zr/5eRk++wroV0FXwmerQddi2UoWq/TuPmVlw3tyQOeCzgOdjzygC5UsUemlpCxshMXwnteB5w3Qb4J+C/TboN8B/S7o90C/D/oD0B+C/jXo34D+Lejfgf496D+A/iPoP4H+M+iPQP8F9F9B/w3030H/A/Q/Qf8L9Meg/w36P6A/Af0p6P+C/gz0/0B/DhrlS9Ae+C8EOgw6AjoL9BLQUWwT0MOVLFPpPUjbYHB+DupYBO9dBrpMyXKVXmFEUcmLupWME326Jue+np3JeVUwOfM20ioLk/Nq4ZOztnu1hck5Xf9hyDm4bHL2coSz2uMPViFS5p7qpF3JGiV7KdlbyVol+yjZV8k6JfspWa9kg5L9lRyg5EAlBynZqGSTkoOVHKLkUCWHKTlcyRFKjlRylJLvKDlayTFKjlVynJLjlZyg5EQlJyk5WckpSk5V8l0lpyn5npLvKzldyRlKzlRylpKzlZyj5FwlP1BynpIfKjlfyQVKfqTkQiUXKblYySVKLlXyYyWXKblcyRVknJWA1v/raAbvfK/rf0Tme8nBXR+u/Pdjtiojj9jhGfbi/1jmsNbbFNN1ZXvJhzkpJXz8qVnLIb165bp1cw9ce/DKje0zNq1fvXHthvW0W2cbxUR8zDPzs4grciGdTfLwc7lEh0z+BOhU55TVzAuqdMT8NSE7sdTj5Uzb/9b+BBx8Jencwf/W8pSZlv+t1Q1I/7f2ylDXSrm/fl3DsDDF/639CeMi90rGwZ2ugLRXEJCSAtJV4OCrg4DkZkC6yghIV6chIO3FGJCuYgxIVzsYkPYOAlJSQLoGHHxtEJDcDEjXGAHp2jQEpL0ZA9I1jAHpWgcD0rogICUFpOvAwdcHAcnNgHSdEZCuT0NAWscYkK5jDEjXOxiQ9gsCUlJAugEcfGMQkNwMSDcYAenGNASk/RgD0g2MAelGBwPS+iAgJQWkm8DBNwcByc2AdJMRkG5OQ0BazxiQbmIMSDc7GJCuCAJSUkC6BRx8axCQ3AxItxgB6dY0BKQrGAPSLYwB6VZLg5vbf3R7V6o278nov9uYA3qXzu/xB3ROZsp7OzkJ9qGmWKZupNtD/OXewdj5bdl9R4i9jazerci5t/fOkOx+qdvmzhD//rNZjtytyNnWdzG29SwH71a8y9JEdHcwEfE20t0WJqJ7hE9E2u57LE9E0n3qkY7MyUlvgkiVs53R5nsdXM3faymI3hcEUd5Gus9CEL1feBDVdt+fwav5B4Sv5nXbPGBhNb9jBq7mH2Rs6x0dXM0/aGkieiiYiHgb6SELE9HDwicibffDjq3muX3qkY7MyUlvFU6VcwOjzY84uJp/xFIQfTQIoryN9KiFIPqY8CCq7X4sg1fzjwtfzeu2edzCan52Bq7mn2Bs69kOruafsDQRPRlMRLyN9KSFieinwicibfdPHVvNc/o0XUHgVktB4KkgCPA20lMWgsDPhAcBbffPMng1+rTw1ahum6ctrEbnZOBq9OeMbT3HwdXozy1NRM8EExFvIz1jYSL6hfCJSNv9C8dWo5w+TVcQuN5SEHg2CAK8jfSshSDwnPAgoO1+LoNXo88LX43qtnnewmp0XgauRl9gbOt5Dq5GX7A0Eb0YTES8jfSihYnoJeETkbb7JcdWo5w+TVcQuNFSEHg5CAK8jfSyhSDwivAgoO1+JYNXo68KX43qtnnVwmp0QQauRl9jbOsFDq5GX7M0Ef0ymIh4G+mXFiaiXwmfiLTdv3JsNcrp03QFgZstBYHXgyDA20ivWwgCbwgPAtruNzJ4Nfqm8NWobps3LaxGF2bgavQtxrZe6OBq9C1LE9HbwUTE20hvW5iI3hE+EWm733FsNcrtU490ZMqZatlhRpvXMtr8LmNASlcQfddSEH0vCKK8jfSehSD6vvAgqu1+P4NX8x8IX83rtvnAwmp+UQau5j9kbOtFDq7mP7Q0Ef06mIh4G+nXFiai3wifiLTdv3FsNc/tU490ZMqZatl9GW3eh9Hm3zq4mv+tpSD6uyCI8jbS7ywE0d8LD6La7t9n8Gr+D8JX87pt/mBhNb84A1fzf2Rs68UOrub/aGki+lMwEfE20p8sTER/Fj4Rabv/7NhqntOnmk0PEBxA+rnGXyh9JeirQV8LukDJRyr9F+grOV7n3/yshffsA3pf0NeDvhH0zaBLlfxVpf/mU9aF8J6LQF8M+hLQl4L+MehCJX9X6X+QsrAR/grv2QB6f9AHgD4Q9EGgN4LeBPpg0IeAPhT0YaAPB30E6CNBHwX6O6CPBn0M6GNBHwf6eNAngD4R9EmgTwZ9CuhTQX8X9Gmgvwf6+6BPB30G6DNBnwX6bNDngD4X9A9Anwf6h6DPB30B6B+B/jvoy0BfDnq4kn+q9L9I22Bw3hPe8xHof4IuU/KxSv87tPm93+Qvx1IdC896diYIz+CMbd0RNzMYy+5SF51A/wMO/oTEx+Avx3jKTMtfjukGfAgq0uefkInDdB7mcw6irSwrZpQV/w/jRPcJ4yo8Xf+BmArzmuRjtQ+ulYDEHZRXh+wEt08B9L/fMrhN9bHZDG5Tva8Pbn7l/L8KbpI7BAbGT0OdDaPPdaeY4iUf3IGS047/MgbKz0J8gQH9+Rnxp43+8Eko5fYxJ59mzvb5hLF9ljB/hZbi4O/S5tpvOJ4423mpLLvNo+Mrw/9asHtZmr4yTXWx9iljH+eMZ3s48pUz47iOL2X8mni5I/5jHCdxxj4TT8V/3S3iuX/e4hy//2OcO23azPkzz+fMNnPPT7pNPrcwP7Vn4E96XzC2dbuDP+kx2p/0k96X5CT4SS/FMnUjfRmy4Kiw7J/0vsCrTt5yrf6kJ92ny1WBKy1MHqFweton5duMHOGMOMKZxciZ7W2eLHDC0H1Kt5f2RRadPTz+BeSzjGVl8/kkTv1BD67yt9QvYqkd8WwL/Zeb8eOQG2Msh9GXlvuTtbbKcaA/5VqK2ZIvlvPCvLHIlfVOviPzc4E7c5G1cVngQOyIZmDsKGSOHVtqm1Q5i/g4G1wdQ0UOjKHiDBxDJY6MoR58nI2ujqEeDoyh0gwcQ2WMYyhdX9zX85WV9MV9ebgzHXxxn2KZ9eBQ7nIrhH/JrO2usPDFfbq269Z7doIgN2dvRzhrPP5gpXUhpCtVX6tSUq2kRkmtkp5KeinpraSPkjolfZXUk35ZAlpv0zWDXb7XdctvvpccDPXhylZe/eV6HrHDM+zFbck5vPWu1nVle8mHGcQTPv7UrLWQbl9/wKb2Te1zN61at3b1jE3rV29cu2H9tJXr1tHOgJVgp4j4GGnmZxGH5EI6m+Th53KJtrYfuoJ5GZKOSFltabno8XI2WCw76WaEfnDSn2QGd1rxlJmWO610A37udd5A0D/ctVLuDU3VDMu5dthZ2I9xadifcXCnKyDVBAEpKSANgJOBQUByMyANMALSwDQEpBrGgDSAMSANdDAg1QYBKSkgDYKTwUFAcjMgDTIC0uA0BKRaxoA0iDEgDXYwIPUJAlJSQBoCJ0ODgORmQBpiBKShaQhIfRgD0hDGgDTUwYBUFwSkpIA0DE6GBwHJzYA0zAhIw9MQkOoYA9IwxoA03MGA1DcISEkBaQScjAwCkpsBaYQRkEamISD1ZQxIIxgD0khLg5vbf/Uen82VjP4bxRzQu3R+jz+gczJT3tEkIAabpVIsUzfS6DB/uWMYO78tu8eE2dvI6u5Lzg1osbDsfqnbJhbm3+6xlyOP2+Bs6zhjW+/l4OM2GO1PmogagomIt5EaLExEjcInIm13o+WJSLpP8eAOonSnbqqcVYw2Nzm4mm+yFESbgyDK20jNFoLoWOFBVNs9NoNX8y3CV/O6bVosrObXZuBqvpWxrdc6uJpntD9pImoLJiLeRmqzMBGNEz4RabvHObaa5/apRzoyJye9ny1VznpGm8c7uJofbymITgiCKG8jTbAQRCcKD6La7okZvJqfJHw1r9tmkoXV/L4ZuJqfzNjW+zq4mme0P2kiSgQTEXMjWZiIpgifiLTdUxxbzXP6NF1BYKilIDA1CAK8jTTVQhCYJjwIaLunZfBqdDvhq1HdNttZWI3ul4Gr0emMbb2fg6tRRvuTJqIZwUTE20gzLExEM4VPRNrumY6tRjl9mq4gMNxSENg+CAK8jbS9hSAwS3gQ0HbPyuDV6A7CV6O6bXawsBrdkIGr0R0Z23qDg6tRRvuTJqKdgomIt5F2sjARzRY+EWm7Zzu2GuX0abqCwEhLQWDnIAjwNtLOFoLAHOFBQNs9J4NXo3OFr0Z128y1sBo9IANXo/MY2/oAB1ejjPYnTUTzg4mIt5HmW5iIFgifiLTdCxxbjXL71CMdmXKm/OfNjDb3ZLR5F8aAlK4guoulILowCKK8jbTQQhDdVXgQ1XbvmsGr+UXCV/O6bRZZWM0flIGr+d0Y2/ogB1fzjPYnTUSLg4mIt5EWW5iIdhc+EWm7d3dsNc/tU490ZMqZ8p1vjDb3YrR5iYOr+SWWgujSIIjyNtJSC0F0mfAgqu1elsGr+T2Er+Z12+xhYTW/KQNX88sZ23qTg6t5RvuTJqIVwUTE20grLExEK4VPRNrulY6t5jl9qtn0AMEBpJ8k94W3+U8utR4IejDoAiWrVHo19BX6/8A94T29QPcGPRT0cNAjQZcq2VOl2+mo9fgnnTXh9LRryo9/dYRzb+aArvsPBus10Df2Ar03aP1Q/7UqvY/lvrKvI22wzhHO/Sz2lX2hb6wDvR/pK+tVeoPlvrK/I21wgCOcB1rsK/tD3zgA9IGkrxyk0hst95VNjrTBwY5wHmKxr2yCvnEw6ENIXzlUpQ+z3FcOd6QNjnCE80iLfeVw6BtHgD6S9JWjVPo7lvvK0Y60wTGOcB5rsa8cDX3jGNDHkr5ynEofb7mvnOBIG5zoCOdJFvvKCdA3TgR9EukrJ6v0KZb7yqmOtMF3HeE8zWJfORX6xndBn0b6yvdU+vuW+8rpjrTBGRbaAF17Ovj8DNB5Ss5U6bMs+/5sR3x/jkXfnw0+P4f4/lyV/oFl35/niO9/aNH354HPf0h8f75KX2DZ9z9yxPcXWvT9j8DnFxLfX6TSF1v2/SWO+P5Si76/BHx+KfH9j1X6Msu+v9wR319h0feXg8+vIL7/iUpfadn3Vzni+6st+v4q8PnVxPfXqPS1ln1/nSO+v96i768Dn19PfH+DSt9o2fc3OeL7mx3hvMURzlsd4bzNEc7bHeG8wxHOOx3hvMsRzrsd4bzHEc57HeG8zxHO+x3hfMARzgcd4XzIEc6HHeF8xBHORx3hfMwRzscd4XzCwjX0MChvT7h2rgd9E+ibQd8C+lbQa0GvB30Q6ENBHwX6ONAng/4e6DNBnwv6fNAXgf4x6J+Avgb0DaBvA3076DtA3wn6LtB3g74H9L2g7wN9P+gHQD8I+iHQD4N+BPSjoB8D/TjoJ0CPUPKkSv803LkPHH+PrIT3rAL9JOgyJU+p9M/CXtIRZu4/nDfvPM3XF+PpuuGmn8c7fvD4OWm34IabFMvsBw7lLvcZxs5vy+5nwuxt1HE3W8TrekgeXDY5+zjCWevxByutCyH9C9XXnlXynJLnlbyg5EUlLyl5WckrSl5V8pqSX5J+WQJab6Ixg10+6WshkmfMa1/dGMXor5iF4BrL9jb/mIB2eIa9xV7yjV5M9a7WdWV7yYcZxBM+/tSstZBuX3/ApvZN7XM3rVq3dvWMTetXb1y7Yf20levW0c6AlWCniPgYaeZnEYfkQjqb5OHncokOmVYkQKcaiZ9hXoakI1I+Z+lyw+PlbLBYdpx2rl/ByeskE0db2OvsUDmkPbCddGf80uvaViGSDsN7It28J7SFcuiox8/jqGf2iZUIZnX5FwLn6gb8HCrS56+Hu1bK/SCC5xiWc+1rNh+/Ylwavs44uNMVkJ4PAlJSQHoDTt4MApKbAekNIyC9mYaA9DxjQHqDMSC96WBAeiEISEkB6S04eTsISG4GpLeMgPR2GgLSC4wB6S3GgPS2gwHplSAgJQWkd+Dk3SAguRmQ3jEC0rtpCEivMAakdxgD0rsOBqRXg4CUFJDeg5P3g4DkZkB6zwhI76chIL3KGJDeYwxI7zsYkF4LAlJSQPoATj4MApKbAekDIyB9mIaA9BpjQPqAMSB9aGlwc/uvn8dn8y8Y/fdr5oDepfN7/AGdk5ny/oYExGCzVIpl6kb6TZi/3N8ydn5bdv82zN5GVh+Tz7kB7Xdh2f1St83vwvzbPQ5x5DH5nG39e8a2PsTBx+Qz2p80Ef0hmIh4G+kPFiaiPwqfiLTdf7Q8EUn3qUc6Micn3ambKuezjDb/ycHV/J8sBdE/B0GUt5H+bCGIfiQ8iGq7P8rg1fxfhK/mddv8xcJq/rAMXM3/lbGtD3NwNc9of9JE9LdgIuJtpL9ZmIj+Lnwi0nb/3bHVPLdPPdKROTnp/Wypcv6S0eZ/OLia/4elIPrPIIjyNtI/LQTRfwkPotruf2Xwav5j4at53TYfW1jNH5GBq/l/M7b1EQ6u5hntT5qI/hNMRLyN9B8LE9Enwicibfcnjq3mOX2ariDwrqUg8GkQBHgb6VMLQeC/woOAtvu/Gbwa/Uz4alS3zWcWVqNHZeBq9H+MbX2Ug6tRRvuTJqLPg4mIt5E+tzARfSF8ItJ2f+HYapTTp+kKAu9bCgJfBkGAt5G+tBAEtHewLIlBoOP2xAh7GzmzGg1FZPdL3TaakXs1enQGrkbDjG19tIOrUUb7kyaiCIkfwUSUYpm6kSIR/nKzhE9E2u4syxORZJ+mKwhw3jlJebODIMDbSNkWgkCO8CCg7c7J4NVorvDVqG6bXAur0WMzcDWax9jWxzq4Gs2ztBrNDyYi3kbKtzARFQifiLTdBY6tRrl96pGOTDlTvgxntPlFxoAcZQxI6QqiUUtBtDAIoryNVGghiBYJD6La7qIMXs0XC1/N67YptrCaPz4DV/MljG19vIOr+RJLE1GPYCLibaQeFiaiUuETkba71LHVfKkjq/l+jDa/xBiQyxxczZdZCqLlQRDlbaRyC0G0QngQ1XZXZPBqvlL4al63TaWF1fyJGbiar2Js6xMdXM1XWZqIqoOJiLeRqi1MRDXCJyJtd41jq3lOn2o2PUBwAOknyX3hbf6TS63fBP026AIltar+ntBX6P8DvwjveQn0y6DfBf0+6A9BlyrppcrpHfG87vyVqo19Iulp11Q56xzh7Msc0HX/wS6g20r3jTrQfUHrh/rXq3Q/y32lvyNtMMARzoEW+0p/6BsDQA8kfWWQSg+23FeGONIGQx3hHGaxrwyBvjEU9DDSV4ar9AjLfWWkI20wyhHO0Rb7ykjoG6NAjyZ9ZYxKxyz3lbgjbdDgCGejxb4Sh77RALqR9JUmlW623FfGOtIGLY5wtlrsK2Ohb7SAbiV9pU2lx1nuK+MdaYMJjnBOtNhXxkPfmAB6Iukrk1R6suW+knCkDaY4wjnVYl9JQN+YAnoq6SvTVHo7y31luiNtMMNCG+AXztPB5zNA5ymZqdLbW/b9LEd8v4NF388Cn+9AfL+jSu9k2fezHfH9zhZ9Pxt8vjPx/RyVnmvZ9/Mc8f18i76fBz6fT3y/QKV3sez7hY74fleLvl8IPt+V+H6RSu9m2feLHfH97hZ9vxh8vjvx/RKVXmrZ98sc8f0eFn2/DHy+B/H9cpVeYdn3Kx3x/SqLvl8JPl9FfL9apfe07Pt2R3y/xhHOvRzh3NsRzrWOcO7jCOe+jnCuc4RzP0c41zvCucERzv0d4TzAEc4DHeE8yBHOjY5wbnKE82BHOA9xhPNQRzgPc4TzcEc4j7BwDT0MyusF1876P/a0bofzNaD3Ar036HrQg0APBz0GdBPoNtCTQE8DPRP0jqDngF4AehHoJaCXg14Nei3ofUDvC3od6P1Arwe9AfT+oA8AfSDog0BvBL0J9MGgDwF9KOjDQB8O+gjQI5QcqdJHRTr3gePXEL8A39bCe48EXabkOyp9dGTze42vLaz0pYowW1+K++B+27LjZgZj2V3qCpMyjwGnH0ucnw867HV+15RD2gPfqu8R+NLr2lYhkg7DeyLdvCe0hXLySR5+vpiwMPokZuGmoJjVm35C4FzdgA9BRfr8WBIsTedhPucg2sqyYkZZ8WMifFzH8k0U8W8akGKpHfFUmNckH6t9cK0EJO6g/EzYTnA7Dhrw+G8Z3Kb62GwGt6ne1wc3v3L+XwU3yR0CA+Nxkc6G0ee6U0zxkg/uQMlpx/GMgfKECF9gQH+eQPxpoz8cG0m5fczJp5mzfY5lbJ+TmW+LT3Hwd2lz7TccT5ztfIosu82j4zEAx1uw+9Q0PQYh1cXacYx9nDOefdeRx0gwjuv4KYyPfjjNEf8xjpM4Y5+Jp+K/7hbx4dTGb5d25hy/JzJebNm0mfPRLScx28w9P+k2OcnC/HRWBj6m52TGtj7Lwcf0MNqf9JieU8jFePCYnhTL1I10SoS/3FMZJwpbdp8aYW8jq4/pke7Tp9QofDrMP3l815GfGU9zhPN7jnB+n5EzW5WhBScM3ad0e2lffN/4mp57AVnBuKg4nXFRkQU+MQ+u8rfUL2KpHfHTLfRfbsbvODLGzmDktNyfrLXVGQ70pzMt9SfJF8tnCb9YtrXeOduR2HGOO3ORtXF5jgOx49wMjB0/sPTlIvcYOo+Ps8HVMXSeA2Pohxk4hs53ZAxdwMfZ6OoYusCBMfSjDBxDFzoyhi5yZM15sSOclzjCeSkzJ3fMOF+VcaEFu88RvlHoClXGlRbsPlfmRqEunD9mjJuMbR235T/udr7MkfhzuSOcVzjC+RNHOK90hPMqRzivdoTzGkc4r3WE8zpHOK93hPMGRzhvdITzJkc4bxZ+HbRaFZhv4bemHwq/DspVNudZsPt8R66DbmG8DmJs6/j5wvtNVPWZQgv95lbhcaJY2Vxiwe7bhNtdqmwus2D37cLt1t9Vn21hw/6Fwse33g9zlgW7L3JkXriDcV5gbOv4RcL7jd4L8QML/eZO4XFC/359vgW77xJut/7N8UILdt/tyHXNPY5w3usI532OcN7vCOcDjnA+6AjnQ45wPpymvSCx1I6Oh79w2fyIIzaHGW1+1BGbI4w2P+aIzVmMNj/uiM3ZjDY/4YjNOYw2P+mIzacy2vxTR2y+iPH+4KccsfliRpt/5ojNlzDa/LQjNl/KaPPPHbH5x4w2P+OIzZcx2vwLR2y+nNHmZx2x+QpGm59zxOafMNr8vCM2X8lo8wuO2HwVo80vOmLz1Yw2v+SIzdcw2vyyIzZfy2jzK47YfB2jza86YvP1jDa/5ojNNzDa/EtHbL6R0eZfOWLzTYw2v+6IzTcz2vyGIzbfwmjzm47YfCujzW85YvNtjDa/7YjNtzPa/I4jNt/BaPO7jth8J6PN7zli812MNr/viM13M9r8gSM238No84eO2Hwvo82/dsTm+xht/o0jNt/PaPNvHbH5AUabf+eIzQ8y2vx7R2x+iNHmPzhi88OMNv/REZtzPT6b/+SIzXmMNv/ZEZvzGW3+yBGbCxht/osjNkcZbf6rIzYXMtr8N0dsLmK0+e+O2FzMaPM/HLG5hNHmfzpicw9Gm//liM2ljDZ/7IjNZYw2/9sRm8sZbf6PIzZXMNr8iSM2VzLa/KkFm1eBxj/m1vdG4XOx9b0k+rpQXyfp6wa9jtbrSr3O0usOPQ/reUnHaR239DjW/Vq3s7a7Skm1kholtUp6KumlpLeSPkrqlPRVUq+kn5L+SgYoGahkkJLBSoYoGapkmJLhSkYoGalklJLRSsZoXyjRD0xu0D5W0qSkWclYJS1KWpW0KRmnZLySCUomKpmkZDK0zxQlU5VMU7KdkulKZiiZqWR7JbOU7KBkRyU7KZmtZGclc5TMVTJPyXwlC5TsomShkl2VLFKym5LFSnZXskTJUiXLlOyhZLmSFUpWQluMg/bQ9w/q++n0/WX6fit9/5G+H0ffn6Lv19D3L+j9/Hp/u97vrfc/6/3Aen+s3i+q90/q/YR6f53eb6b3X+n9SHp/jt6vovdv6P0M+vd9/Xu3/v1X/x6qfx/Uv5fp34/07yn69wX9fbv+/ll/H6u/n9Tf1+nvr/T3Ofr7DX29r69/9fWgvj7S1wt6/azXk3p9pdcbev7V85GOzzpe6fGr+/P/Aad12+oltAUA", + "bytecode": "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", "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" } ] diff --git a/yarn-project/aztec.js/src/abis/schnorr_single_key_account_contract.json b/yarn-project/aztec.js/src/abis/schnorr_single_key_account_contract.json index 23c2a5e18d0..7c6243b45be 100644 --- a/yarn-project/aztec.js/src/abis/schnorr_single_key_account_contract.json +++ b/yarn-project/aztec.js/src/abis/schnorr_single_key_account_contract.json @@ -95,7 +95,7 @@ } ], "returnTypes": [], - "bytecode": "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", + "bytecode": "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", "verificationKey": "0000000200000800000000740000000f00000003515f3109623eb3c25aa5b16a1a79fd558bac7a7ce62c4560a8c537c77ce80dd339128d1d37b6582ee9e6df9567efb64313471dfa18f520f9ce53161b50dbf7731bc5f900000003515f322bc4cce83a486a92c92fd59bd84e0f92595baa639fc2ed86b00ffa0dfded2a092a669a3bdb7a273a015eda494457cc7ed5236f26cee330c290d45a33b9daa94800000003515f332729426c008c085a81bd34d8ef12dd31e80130339ef99d50013a89e4558eee6d0fa4ffe2ee7b7b62eb92608b2251ac31396a718f9b34978888789042b790a30100000003515f342be6b6824a913eb7a57b03cb1ee7bfb4de02f2f65fe8a4e97baa7766ddb353a82a8a25c49dc63778cd9fe96173f12a2bc77f3682f4c4448f98f1df82c75234a100000003515f351f85760d6ab567465aadc2f180af9eae3800e6958fec96aef53fd8a7b195d7c000c6267a0dd5cfc22b3fe804f53e266069c0e36f51885baec1e7e67650c62e170000000c515f41524954484d455449430d9d0f8ece2aa12012fa21e6e5c859e97bd5704e5c122064a66051294bc5e04213f61f54a0ebdf6fee4d4a6ecf693478191de0c2899bcd8e86a636c8d3eff43400000003515f43224a99d02c86336737c8dd5b746c40d2be6aead8393889a76a18d664029096e90f7fe81adcc92a74350eada9622ac453f49ebac24a066a1f83b394df54dfa0130000000c515f46495845445f42415345060e8a013ed289c2f9fd7473b04f6594b138ddb4b4cf6b901622a14088f04b8d2c83ff74fce56e3d5573b99c7b26d85d5046ce0c6559506acb7a675e7713eb3a00000007515f4c4f4749430721a91cb8da4b917e054f72147e1760cfe0ef3d45090ac0f4961d84ec1996961a25e787b26bd8b50b1a99450f77a424a83513c2b33af268cd253b0587ff50c700000003515f4d05dbd8623b8652511e1eb38d38887a69eceb082f807514f09e127237c5213b401b9325b48c6c225968002318095f89d0ef9cf629b2b7f0172e03bc39aacf6ed800000007515f52414e474504b57a3805e41df328f5ca9aefa40fad5917391543b7b65c6476e60b8f72e9ad07c92f3b3e11c8feae96dedc4b14a6226ef3201244f37cfc1ee5b96781f48d2b000000075349474d415f3125001d1954a18571eaa007144c5a567bb0d2be4def08a8be918b8c05e3b27d312c59ed41e09e144eab5de77ca89a2fd783be702a47c951d3112e3de02ce6e47c000000075349474d415f3223994e6a23618e60fa01c449a7ab88378709197e186d48d604bfb6931ffb15ad11c5ec7a0700570f80088fd5198ab5d5c227f2ad2a455a6edeec024156bb7beb000000075349474d415f3300cda5845f23468a13275d18bddae27c6bb189cf9aa95b6a03a0cb6688c7e8d829639b45cf8607c525cc400b55ebf90205f2f378626dc3406cc59b2d1b474fba000000075349474d415f342d299e7928496ea2d37f10b43afd6a80c90a33b483090d18069ffa275eedb2fc2f82121e8de43dc036d99b478b6227ceef34248939987a19011f065d8b5cef5c0000000010000000000000000100000002000000030000000400000005000000060000000700000008000000090000000a0000000b0000000c0000000d0000000e0000000f" } ]