diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index ae0d5e3d40a..6284c1af9a5 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -115,10 +115,13 @@ contract DocsExample { // and returns the response. // Used to test that we can retrieve values through calls and // correctly return them in the simulation - context.call_private_function_no_args( + let mut leader: Leader = context.call_private_function_no_args( context.this_address(), FunctionSelector::from_signature("get_shared_immutable_constrained_private()") - ).unpack_into() + ).unpack_into(); + + leader.points += 1; + leader } #[aztec(public)] @@ -127,10 +130,13 @@ contract DocsExample { // and returns the response. // Used to test that we can retrieve values through calls and // correctly return them in the simulation - context.call_public_function_no_args( + let mut leader: Leader = context.call_public_function_no_args( context.this_address(), FunctionSelector::from_signature("get_shared_immutable_constrained_public()") - ).deserialize_into() + ).deserialize_into(); + + leader.points += 1; + leader } #[aztec(public)] @@ -138,6 +144,12 @@ contract DocsExample { storage.shared_immutable.read_public() } + #[aztec(public)] + fn get_shared_immutable_constrained_public_multiple() -> pub [Leader; 5] { + let a = storage.shared_immutable.read_public(); + [a, a, a, a, a] + } + #[aztec(private)] fn get_shared_immutable_constrained_private() -> pub Leader { storage.shared_immutable.read_private() diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 7658dd95029..d0bfaa8ff32 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -671,7 +671,7 @@ export class AztecNodeService implements AztecNode { throw reverted[0].revertReason; } this.log.info(`Simulated tx ${tx.getTxHash()} succeeds`); - return returns; + return returns[0]; } public setConfig(config: Partial): Promise { diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index 5f8033124db..f0c2a9e3578 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -1,6 +1,6 @@ import { type FunctionCall, PackedValues, TxExecutionRequest } from '@aztec/circuit-types'; import { type AztecAddress, FunctionData, GasSettings, TxContext } from '@aztec/circuits.js'; -import { type FunctionAbi, FunctionType, encodeArguments } from '@aztec/foundation/abi'; +import { type FunctionAbi, FunctionType, decodeReturnValues, encodeArguments } from '@aztec/foundation/abi'; import { type Wallet } from '../account/wallet.js'; import { BaseContractInteraction, type SendMethodOptions } from './base_contract_interaction.js'; @@ -73,7 +73,6 @@ export class ContractFunctionInteraction extends BaseContractInteraction { * 2. It supports `unconstrained`, `private` and `public` functions * 3. For `private` execution it: * 3.a SKIPS the entrypoint and starts directly at the function - * 3.b SKIPS public execution entirely * 4. For `public` execution it: * 4.a Removes the `txRequest` value after ended simulation * 4.b Ignores the `from` in the options @@ -86,10 +85,6 @@ export class ContractFunctionInteraction extends BaseContractInteraction { return this.wallet.viewTx(this.functionDao.name, this.args, this.contractAddress, options.from); } - // TODO: If not unconstrained, we return a size 4 array of fields. - // TODO: It should instead return the correctly decoded value - // TODO: The return type here needs to be fixed! @LHerskind - if (this.functionDao.functionType == FunctionType.SECRET) { const nodeInfo = await this.wallet.getNodeInfo(); const packedArgs = PackedValues.fromValues(encodeArguments(this.functionDao, this.args)); @@ -103,13 +98,15 @@ export class ContractFunctionInteraction extends BaseContractInteraction { authWitnesses: [], gasSettings: options.gasSettings ?? GasSettings.simulation(), }); - const simulatedTx = await this.pxe.simulateTx(txRequest, false, options.from ?? this.wallet.getAddress()); - return simulatedTx.privateReturnValues?.[0]; + const simulatedTx = await this.pxe.simulateTx(txRequest, true, options.from ?? this.wallet.getAddress()); + const flattened = simulatedTx.privateReturnValues; + return flattened ? decodeReturnValues(this.functionDao, flattened) : []; } else { const txRequest = await this.create(); const simulatedTx = await this.pxe.simulateTx(txRequest, true); this.txRequest = undefined; - return simulatedTx.publicReturnValues?.[0]; + const flattened = simulatedTx.publicReturnValues; + return flattened ? decodeReturnValues(this.functionDao, flattened) : []; } } } diff --git a/yarn-project/circuit-types/src/interfaces/aztec-node.ts b/yarn-project/circuit-types/src/interfaces/aztec-node.ts index 918630b042e..03e9ddbff8a 100644 --- a/yarn-project/circuit-types/src/interfaces/aztec-node.ts +++ b/yarn-project/circuit-types/src/interfaces/aztec-node.ts @@ -7,7 +7,6 @@ import { type PUBLIC_DATA_TREE_HEIGHT, } from '@aztec/circuits.js'; import { type L1ContractAddresses } from '@aztec/ethereum'; -import { type ProcessReturnValues } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { type Fr } from '@aztec/foundation/fields'; import { type ContractClassPublic, type ContractInstanceWithAddress } from '@aztec/types/contracts'; @@ -22,7 +21,7 @@ import { } from '../logs/index.js'; import { type MerkleTreeId } from '../merkle_tree_id.js'; import { type SiblingPath } from '../sibling_path/index.js'; -import { type Tx, type TxHash, type TxReceipt } from '../tx/index.js'; +import { type ProcessReturnValues, type Tx, type TxHash, type TxReceipt } from '../tx/index.js'; import { type TxEffect } from '../tx_effect.js'; import { type SequencerConfig } from './configs.js'; import { type L2BlockNumber } from './l2_block_number.js'; @@ -283,7 +282,7 @@ export interface AztecNode { * This currently just checks that the transaction execution succeeds. * @param tx - The transaction to simulate. **/ - simulatePublicCalls(tx: Tx): Promise; + simulatePublicCalls(tx: Tx): Promise; /** * Updates the configuration of this node. diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index f0b3c805ad9..73d0ccf6317 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -11,7 +11,7 @@ import { getContractClassFromArtifact, } from '@aztec/circuits.js'; import { makePublicCallRequest } from '@aztec/circuits.js/testing'; -import { type ContractArtifact, type DecodedReturn } from '@aztec/foundation/abi'; +import { type ContractArtifact } from '@aztec/foundation/abi'; import { makeTuple } from '@aztec/foundation/array'; import { times } from '@aztec/foundation/collection'; import { randomBytes } from '@aztec/foundation/crypto'; @@ -21,7 +21,7 @@ import { type ContractInstanceWithAddress, SerializableContractInstance } from ' import { EncryptedL2Log } from './logs/encrypted_l2_log.js'; import { EncryptedFunctionL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js'; import { ExtendedNote } from './notes/index.js'; -import { SimulatedTx, Tx, TxHash } from './tx/index.js'; +import { type ProcessReturnValues, SimulatedTx, Tx, TxHash } from './tx/index.js'; /** * Testing utility to create empty logs composed from a single empty log. @@ -94,7 +94,7 @@ export const mockTxForRollup = (seed = 1, { hasLogs = false }: { hasLogs?: boole export const mockSimulatedTx = (seed = 1, hasLogs = true) => { const tx = mockTx(seed, { hasLogs }); - const dec: DecodedReturn = [1n, 2n, 3n, 4n]; + const dec: ProcessReturnValues = [new Fr(1n), new Fr(2n), new Fr(3n), new Fr(4n)]; return new SimulatedTx(tx, dec, dec); }; diff --git a/yarn-project/circuit-types/src/tx/simulated_tx.test.ts b/yarn-project/circuit-types/src/tx/simulated_tx.test.ts index 29cc0019477..167c91259fa 100644 --- a/yarn-project/circuit-types/src/tx/simulated_tx.test.ts +++ b/yarn-project/circuit-types/src/tx/simulated_tx.test.ts @@ -6,4 +6,11 @@ describe('simulated_tx', () => { const simulatedTx = mockSimulatedTx(); expect(SimulatedTx.fromJSON(simulatedTx.toJSON())).toEqual(simulatedTx); }); + + it('convert undefined effects to and from json', () => { + const simulatedTx = mockSimulatedTx(); + simulatedTx.privateReturnValues = undefined; + simulatedTx.publicReturnValues = undefined; + expect(SimulatedTx.fromJSON(simulatedTx.toJSON())).toEqual(simulatedTx); + }); }); diff --git a/yarn-project/circuit-types/src/tx/simulated_tx.ts b/yarn-project/circuit-types/src/tx/simulated_tx.ts index febf33cdfb4..baef7a2d4ea 100644 --- a/yarn-project/circuit-types/src/tx/simulated_tx.ts +++ b/yarn-project/circuit-types/src/tx/simulated_tx.ts @@ -1,8 +1,9 @@ -import { AztecAddress } from '@aztec/circuits.js'; -import { type ProcessReturnValues } from '@aztec/foundation/abi'; +import { Fr } from '@aztec/circuits.js'; import { Tx } from './tx.js'; +export type ProcessReturnValues = Fr[] | undefined; + export class SimulatedTx { constructor( public tx: Tx, @@ -15,17 +16,11 @@ export class SimulatedTx { * @returns A plain object with SimulatedTx properties. */ public toJSON() { - const returnToJson = (data: ProcessReturnValues): string => { - const replacer = (key: string, value: any): any => { - if (typeof value === 'bigint') { - return value.toString() + 'n'; // Indicate bigint with "n" - } else if (value instanceof AztecAddress) { - return value.toString(); - } else { - return value; - } - }; - return JSON.stringify(data, replacer); + const returnToJson = (data: ProcessReturnValues | undefined): string => { + if (data === undefined) { + return JSON.stringify(data); + } + return JSON.stringify(data.map(fr => fr.toString())); }; return { @@ -41,22 +36,11 @@ export class SimulatedTx { * @returns A Tx class object. */ public static fromJSON(obj: any) { - const returnFromJson = (json: string): ProcessReturnValues => { - if (json == undefined) { + const returnFromJson = (json: string): ProcessReturnValues | undefined => { + if (json === undefined) { return json; } - const reviver = (key: string, value: any): any => { - if (typeof value === 'string') { - if (value.match(/\d+n$/)) { - // Detect bigint serialization - return BigInt(value.slice(0, -1)); - } else if (value.match(/^0x[a-fA-F0-9]{64}$/)) { - return AztecAddress.fromString(value); - } - } - return value; - }; - return JSON.parse(json, reviver); + return JSON.parse(json).map(Fr.fromString); }; const tx = Tx.fromJSON(obj.tx); diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 3de9f8a1b0b..1a941a6f685 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -80,15 +80,15 @@ describe('e2e_avm_simulator', () => { }, 50_000); it('Can execute ACVM function among AVM functions', async () => { - expect(await avmContract.methods.constant_field_acvm().simulate()).toEqual([123456n]); + expect(await avmContract.methods.constant_field_acvm().simulate()).toEqual(123456n); }); it('Can call AVM function from ACVM', async () => { - expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual([123456n]); + expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual(123456n); }); it('Can call ACVM function from AVM', async () => { - expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual([123456n]); + expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual(123456n); }); it('AVM sees settled nullifiers by ACVM', async () => { @@ -146,7 +146,7 @@ describe('e2e_avm_simulator', () => { describe('Storage', () => { it('Read immutable (initialized) storage (Field)', async () => { - expect(await avmContract.methods.read_storage_immutable().simulate()).toEqual([42n]); + expect(await avmContract.methods.read_storage_immutable().simulate()).toEqual(42n); }); }); }); diff --git a/yarn-project/end-to-end/src/e2e_state_vars.test.ts b/yarn-project/end-to-end/src/e2e_state_vars.test.ts index 56783ceee2e..f13ee0bd420 100644 --- a/yarn-project/end-to-end/src/e2e_state_vars.test.ts +++ b/yarn-project/end-to-end/src/e2e_state_vars.test.ts @@ -38,6 +38,7 @@ describe('e2e_state_vars', () => { // checking the return values with: // 1. A constrained private function that reads it directly // 2. A constrained private function that calls another private function that reads. + // The indirect, adds 1 to the point to ensure that we are returning the correct value. await contract.methods.initialize_shared_immutable(1).send().wait(); @@ -45,12 +46,8 @@ describe('e2e_state_vars', () => { const b = await contract.methods.get_shared_immutable_constrained_private_indirect().simulate(); const c = await contract.methods.get_shared_immutable().simulate(); - expect((a as any)[0]).toEqual((c as any)['account'].toBigInt()); - expect((a as any)[1]).toEqual((c as any)['points']); - expect((b as any)[0]).toEqual((c as any)['account'].toBigInt()); - expect((b as any)[1]).toEqual((c as any)['points']); - - expect(a).toEqual(b); + expect(a).toEqual(c); + expect(b).toEqual({ account: c.account, points: c.points + 1n }); await contract.methods.match_shared_immutable(c.account, c.points).send().wait(); }); @@ -58,20 +55,28 @@ describe('e2e_state_vars', () => { // Reads the value using an unconstrained function checking the return values with: // 1. A constrained public function that reads it directly // 2. A constrained public function that calls another public function that reads. + // The indirect, adds 1 to the point to ensure that we are returning the correct value. const a = await contract.methods.get_shared_immutable_constrained_public().simulate(); const b = await contract.methods.get_shared_immutable_constrained_public_indirect().simulate(); const c = await contract.methods.get_shared_immutable().simulate(); - expect((a as any)[0]).toEqual((c as any)['account'].toBigInt()); - expect((a as any)[1]).toEqual((c as any)['points']); - expect((b as any)[0]).toEqual((c as any)['account'].toBigInt()); - expect((b as any)[1]).toEqual((c as any)['points']); + expect(a).toEqual(c); + expect(b).toEqual({ account: c.account, points: c.points + 1n }); - expect(a).toEqual(b); await contract.methods.match_shared_immutable(c.account, c.points).send().wait(); }); + it('public multiread of SharedImmutable', async () => { + // Reads the value using an unconstrained function checking the return values with: + // 1. A constrained public function that reads 5 times directly (going beyond the previous 4 Field return value) + + const a = await contract.methods.get_shared_immutable_constrained_public_multiple().simulate(); + const c = await contract.methods.get_shared_immutable().simulate(); + + expect(a).toEqual([c, c, c, c, c]); + }); + it('initializing SharedImmutable the second time should fail', async () => { // Jest executes the tests sequentially and the first call to initialize_shared_immutable was executed // in the previous test, so the call bellow should fail. diff --git a/yarn-project/foundation/src/abi/decoder.ts b/yarn-project/foundation/src/abi/decoder.ts index deafe602ae1..c98bac63823 100644 --- a/yarn-project/foundation/src/abi/decoder.ts +++ b/yarn-project/foundation/src/abi/decoder.ts @@ -1,20 +1,19 @@ import { AztecAddress } from '../aztec-address/index.js'; import { type Fr } from '../fields/index.js'; -import { type ABIParameter, type ABIVariable, type AbiType, type FunctionArtifact } from './abi.js'; +import { type ABIParameter, type ABIVariable, type AbiType, type FunctionAbi } from './abi.js'; import { isAztecAddressStruct } from './utils.js'; /** * The type of our decoded ABI. */ export type DecodedReturn = bigint | boolean | AztecAddress | DecodedReturn[] | { [key: string]: DecodedReturn }; -export type ProcessReturnValues = (DecodedReturn | undefined)[] | undefined; /** * Decodes return values from a function call. * Missing support for integer and string. */ class ReturnValuesDecoder { - constructor(private artifact: FunctionArtifact, private flattened: Fr[]) {} + constructor(private artifact: FunctionAbi, private flattened: Fr[]) {} /** * Decodes a single return value from field to the given type. @@ -97,7 +96,7 @@ class ReturnValuesDecoder { * @param returnValues - The decoded return values. * @returns */ -export function decodeReturnValues(abi: FunctionArtifact, returnValues: Fr[]) { +export function decodeReturnValues(abi: FunctionAbi, returnValues: Fr[]) { return new ReturnValuesDecoder(abi, returnValues.slice()).decode(); } diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 45f0a9eb16e..7aa8f8fb00c 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -417,8 +417,7 @@ export class PXEService implements PXE { } if (simulatePublic) { - // Only one transaction, so we can take index 0. - simulatedTx.publicReturnValues = (await this.#simulatePublicCalls(simulatedTx.tx))[0]; + simulatedTx.publicReturnValues = await this.#simulatePublicCalls(simulatedTx.tx); } if (!msgSender) { @@ -646,7 +645,7 @@ export class PXEService implements PXE { await this.patchPublicCallStackOrdering(publicInputs, enqueuedPublicFunctions); const tx = new Tx(publicInputs, proof, encryptedLogs, unencryptedLogs, enqueuedPublicFunctions); - return new SimulatedTx(tx, [executionResult.returnValues]); + return new SimulatedTx(tx, executionResult.returnValues); } /** diff --git a/yarn-project/simulator/src/client/execution_result.ts b/yarn-project/simulator/src/client/execution_result.ts index 5118b28d607..9dfe877ba92 100644 --- a/yarn-project/simulator/src/client/execution_result.ts +++ b/yarn-project/simulator/src/client/execution_result.ts @@ -4,7 +4,6 @@ import { type PrivateCallStackItem, type PublicCallRequest, } from '@aztec/circuits.js'; -import { type DecodedReturn } from '@aztec/foundation/abi'; import { type Fr } from '@aztec/foundation/fields'; import { type ACVMField } from '../acvm/index.js'; @@ -40,8 +39,8 @@ export interface ExecutionResult { // Needed when we enable chained txs. The new notes can be cached and used in a later transaction. /** The notes created in the executed function. */ newNotes: NoteAndSlot[]; - /** The decoded return values of the executed function. */ - returnValues: DecodedReturn; + /** The raw return values of the executed function. */ + returnValues: Fr[]; /** The nested executions. */ nestedExecutions: this[]; /** Enqueued public function execution requests to be picked up by the sequencer. */ diff --git a/yarn-project/simulator/src/client/private_execution.test.ts b/yarn-project/simulator/src/client/private_execution.test.ts index b3bdc01968b..716232104a6 100644 --- a/yarn-project/simulator/src/client/private_execution.test.ts +++ b/yarn-project/simulator/src/client/private_execution.test.ts @@ -433,7 +433,7 @@ describe('Private Execution test suite', () => { const artifact = getFunctionArtifact(ChildContractArtifact, 'value'); const result = await runSimulator({ args: [initialValue], artifact }); - expect(result.returnValues).toEqual([initialValue + privateIncrement]); + expect(result.returnValues).toEqual([new Fr(initialValue + privateIncrement)]); }); it('parent should call child', async () => { @@ -452,12 +452,12 @@ describe('Private Execution test suite', () => { const args = [childAddress, childSelector]; const result = await runSimulator({ args, artifact: parentArtifact }); - expect(result.returnValues).toEqual([privateIncrement]); + expect(result.returnValues).toEqual([new Fr(privateIncrement)]); expect(oracle.getFunctionArtifact.mock.calls[0]).toEqual([childAddress, childSelector]); expect(oracle.getPortalContractAddress.mock.calls[0]).toEqual([childAddress]); expect(result.nestedExecutions).toHaveLength(1); - expect(result.nestedExecutions[0].returnValues).toEqual([privateIncrement]); + expect(result.nestedExecutions[0].returnValues).toEqual([new Fr(privateIncrement)]); // check that Aztec.nr calculated the call stack item hash like cpp does const expectedCallStackItemHash = result.nestedExecutions[0].callStackItem.hash(); @@ -486,7 +486,7 @@ describe('Private Execution test suite', () => { logger.info(`Calling testCodeGen function`); const result = await runSimulator({ args, artifact: testCodeGenArtifact }); - expect(result.returnValues).toEqual([argsHash.toBigInt()]); + expect(result.returnValues).toEqual([argsHash]); }); it('test function should be callable through autogenerated interface', async () => { @@ -504,11 +504,11 @@ describe('Private Execution test suite', () => { const args = [testAddress]; const result = await runSimulator({ args, artifact: parentArtifact }); - expect(result.returnValues).toEqual([argsHash.toBigInt()]); + expect(result.returnValues).toEqual([argsHash]); expect(oracle.getFunctionArtifact.mock.calls[0]).toEqual([testAddress, testCodeGenSelector]); expect(oracle.getPortalContractAddress.mock.calls[0]).toEqual([testAddress]); expect(result.nestedExecutions).toHaveLength(1); - expect(result.nestedExecutions[0].returnValues).toEqual([argsHash.toBigInt()]); + expect(result.nestedExecutions[0].returnValues).toEqual([argsHash]); }); }); @@ -899,7 +899,7 @@ describe('Private Execution test suite', () => { const readRequest = sideEffectArrayToValueArray(result.callStackItem.publicInputs.noteHashReadRequests)[0]; expect(readRequest).toEqual(innerNoteHash); - expect(result.returnValues).toEqual([amountToTransfer]); + expect(result.returnValues).toEqual([new Fr(amountToTransfer)]); const nullifier = result.callStackItem.publicInputs.newNullifiers[0]; const siloedNullifierSecretKey = computeSiloedNullifierSecretKey( @@ -974,7 +974,7 @@ describe('Private Execution test suite', () => { const readRequest = execGetThenNullify.callStackItem.publicInputs.noteHashReadRequests[0]; expect(readRequest.value).toEqual(innerNoteHash); - expect(execGetThenNullify.returnValues).toEqual([amountToTransfer]); + expect(execGetThenNullify.returnValues).toEqual([new Fr(amountToTransfer)]); const nullifier = execGetThenNullify.callStackItem.publicInputs.newNullifiers[0]; const siloedNullifierSecretKey = computeSiloedNullifierSecretKey( @@ -1013,7 +1013,6 @@ describe('Private Execution test suite', () => { it('gets the public key for an address', async () => { // Tweak the contract artifact so we can extract return values const artifact = getFunctionArtifact(TestContractArtifact, 'get_public_key'); - artifact.returnTypes = [{ kind: 'array', length: 2, type: { kind: 'field' } }]; // Generate a partial address, pubkey, and resulting address const completeAddress = CompleteAddress.random(); @@ -1022,7 +1021,7 @@ describe('Private Execution test suite', () => { oracle.getCompleteAddress.mockResolvedValue(completeAddress); const result = await runSimulator({ artifact, args }); - expect(result.returnValues).toEqual([pubKey.x.toBigInt(), pubKey.y.toBigInt()]); + expect(result.returnValues).toEqual([pubKey.x, pubKey.y]); }); }); @@ -1046,14 +1045,13 @@ describe('Private Execution test suite', () => { // Tweak the contract artifact so we can extract return values const artifact = getFunctionArtifact(TestContractArtifact, 'get_portal_contract_address'); - artifact.returnTypes = [{ kind: 'field' }]; const args = [aztecAddressToQuery.toField()]; // Overwrite the oracle return value oracle.getPortalContractAddress.mockResolvedValue(portalContractAddress); const result = await runSimulator({ artifact, args }); - expect(result.returnValues).toEqual([portalContractAddress.toField().value]); + expect(result.returnValues).toEqual([portalContractAddress.toField()]); }); it('this_address should return the current context address', async () => { @@ -1061,11 +1059,10 @@ describe('Private Execution test suite', () => { // Tweak the contract artifact so we can extract return values const artifact = getFunctionArtifact(TestContractArtifact, 'get_this_address'); - artifact.returnTypes = [{ kind: 'field' }]; // Overwrite the oracle return value const result = await runSimulator({ artifact, args: [], contractAddress }); - expect(result.returnValues).toEqual([contractAddress.toField().toBigInt()]); + expect(result.returnValues).toEqual([contractAddress.toField()]); }); it("this_portal_address should return the current context's portal address", async () => { @@ -1073,11 +1070,10 @@ describe('Private Execution test suite', () => { // Tweak the contract artifact so we can extract return values const artifact = getFunctionArtifact(TestContractArtifact, 'get_this_portal_address'); - artifact.returnTypes = [{ kind: 'field' }]; // Overwrite the oracle return value const result = await runSimulator({ artifact, args: [], portalContractAddress }); - expect(result.returnValues).toEqual([portalContractAddress.toField().toBigInt()]); + expect(result.returnValues).toEqual([portalContractAddress.toField()]); }); }); diff --git a/yarn-project/simulator/src/client/private_execution.ts b/yarn-project/simulator/src/client/private_execution.ts index 7a7b0d13288..d058985d750 100644 --- a/yarn-project/simulator/src/client/private_execution.ts +++ b/yarn-project/simulator/src/client/private_execution.ts @@ -1,5 +1,5 @@ import { type FunctionData, PrivateCallStackItem, PrivateCircuitPublicInputs } from '@aztec/circuits.js'; -import { type AbiType, type FunctionArtifactWithDebugMetadata, decodeReturnValues } from '@aztec/foundation/abi'; +import { type FunctionArtifactWithDebugMetadata } from '@aztec/foundation/abi'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -53,12 +53,7 @@ export async function executePrivateFunction( const callStackItem = new PrivateCallStackItem(contractAddress, functionData, publicInputs); - // Mocking the return type to be an array of 4 fields - // TODO: @LHerskind must be updated as we are progressing with the macros to get the information const rawReturnValues = await context.unpackReturns(publicInputs.returnsHash); - const returnTypes: AbiType[] = [{ kind: 'array', length: rawReturnValues.length, type: { kind: 'field' } }]; - const mockArtifact = { ...artifact, returnTypes }; - const returnValues = decodeReturnValues(mockArtifact, rawReturnValues); const noteHashReadRequestPartialWitnesses = context.getNoteHashReadRequestPartialWitnesses( publicInputs.noteHashReadRequests, @@ -73,7 +68,7 @@ export async function executePrivateFunction( acir, partialWitness, callStackItem, - returnValues, + returnValues: rawReturnValues, noteHashReadRequestPartialWitnesses, newNotes, vk: Buffer.from(artifact.verificationKey!, 'hex'), diff --git a/yarn-project/simulator/src/public/abstract_phase_manager.ts b/yarn-project/simulator/src/public/abstract_phase_manager.ts index cb56906ea2a..c39d240ded7 100644 --- a/yarn-project/simulator/src/public/abstract_phase_manager.ts +++ b/yarn-project/simulator/src/public/abstract_phase_manager.ts @@ -1,5 +1,6 @@ import { MerkleTreeId, + type ProcessReturnValues, type PublicKernelRequest, type SimulationError, type Tx, @@ -47,13 +48,6 @@ import { makeEmptyProof, } from '@aztec/circuits.js'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; -import { - type AbiType, - type DecodedReturn, - type FunctionArtifact, - type ProcessReturnValues, - decodeReturnValues, -} from '@aztec/foundation/abi'; import { arrayNonEmptyLength, padArrayEnd } from '@aztec/foundation/collection'; import { type DebugLogger, createDebugLogger } from '@aztec/foundation/log'; import { type Tuple } from '@aztec/foundation/serialize'; @@ -236,13 +230,11 @@ export abstract class AbstractPhaseManager { // separate public callstacks to be proven by separate public kernel sequences // and submitted separately to the base rollup? - const returns = []; + let returns: ProcessReturnValues = undefined; for (const enqueuedCall of enqueuedCalls) { const executionStack: (PublicExecution | PublicExecutionResult)[] = [enqueuedCall]; - let currentReturn: DecodedReturn | undefined = undefined; - // Keep track of which result is for the top/enqueued call let enqueuedExecutionResult: PublicExecutionResult | undefined; @@ -301,21 +293,12 @@ export abstract class AbstractPhaseManager { if (!enqueuedExecutionResult) { enqueuedExecutionResult = result; - - // TODO(#5450) Need to use the proper return values here - const returnTypes: AbiType[] = [ - { kind: 'array', length: result.returnValues.length, type: { kind: 'field' } }, - ]; - const mockArtifact = { returnTypes } as any as FunctionArtifact; - - currentReturn = decodeReturnValues(mockArtifact, result.returnValues); + returns = result.returnValues; } } // HACK(#1622): Manually patches the ordering of public state actions // TODO(#757): Enforce proper ordering of public state actions patchPublicStorageActionOrdering(kernelOutput, enqueuedExecutionResult!, this.phase); - - returns.push(currentReturn); } // TODO(#3675): This should be done in a public kernel circuit diff --git a/yarn-project/simulator/src/public/avm_executor.test.ts b/yarn-project/simulator/src/public/avm_executor.test.ts index d0c0fc234dc..43c1dc7663c 100644 --- a/yarn-project/simulator/src/public/avm_executor.test.ts +++ b/yarn-project/simulator/src/public/avm_executor.test.ts @@ -72,6 +72,6 @@ describe('AVM WitGen and Proof Generation', () => { const valid = await executor.verifyAvmProof(vk, proof); expect(valid).toBe(true); }, - 60 * 1000, - ); // 60 seconds should be enough to generate the proof with 16-bit range checks + 120 * 1000, + ); // 120 seconds should be enough to generate the proof with 16-bit range checks }); diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 9354bd6d6e9..b749c2276fa 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -1,6 +1,7 @@ import { type BlockProver, type FailedTx, + type ProcessReturnValues, type ProcessedTx, type PublicKernelRequest, type SimulationError, @@ -13,7 +14,6 @@ import { } from '@aztec/circuit-types'; import { type TxSequencerProcessingStats } from '@aztec/circuit-types/stats'; import { type GlobalVariables, type Header, type KernelCircuitPublicInputs } from '@aztec/circuits.js'; -import { type ProcessReturnValues } from '@aztec/foundation/abi'; import { createDebugLogger } from '@aztec/foundation/log'; import { Timer } from '@aztec/foundation/timer'; import { PublicExecutor, type PublicStateDB, type SimulationProvider } from '@aztec/simulator'; diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index b1b5ca3d8ac..f2c605a0886 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -117,9 +117,10 @@ type NoirCompiledContractFunction = NoirCompiledContract['functions'][number]; /** * Generates a function build artifact. Replaces verification key with a mock value. * @param fn - Noir function entry. + * @param contract - Parent contract. * @returns Function artifact. */ -function generateFunctionArtifact(fn: NoirCompiledContractFunction): FunctionArtifact { +function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: NoirCompiledContract): FunctionArtifact { if (fn.custom_attributes === undefined) { throw new Error( `No custom attributes found for contract function ${fn.name}. Try rebuilding the contract with the latest nargo version.`, @@ -134,10 +135,25 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction): FunctionArt parameters = parameters.slice(1); } - // If the function is secret, the return is the public inputs, which should be omitted let returnTypes: AbiType[] = []; - if (functionType !== 'secret' && fn.abi.return_type) { + if (functionType === FunctionType.UNCONSTRAINED && fn.abi.return_type) { returnTypes = [fn.abi.return_type.abi_type]; + } else { + const pathToFind = `${contract.name}::${fn.name}_abi`; + const abiStructs: AbiType[] = contract.outputs.structs['functions']; + + const returnStruct = abiStructs.find(a => a.kind === 'struct' && a.path === pathToFind); + + if (returnStruct) { + if (returnStruct.kind !== 'struct') { + throw new Error('Could not generate contract function artifact'); + } + + const returnTypeField = returnStruct.fields.find(field => field.name === 'return_type'); + if (returnTypeField) { + returnTypes = [returnTypeField.type]; + } + } } return { @@ -190,7 +206,7 @@ function hasKernelFunctionInputs(params: ABIParameter[]): boolean { function generateContractArtifact(contract: NoirCompiledContract, aztecNrVersion?: string): ContractArtifact { return { name: contract.name, - functions: contract.functions.map(generateFunctionArtifact), + functions: contract.functions.map(f => generateFunctionArtifact(f, contract)), outputs: contract.outputs, fileMap: contract.file_map, aztecNrVersion,