diff --git a/yarn-project/bb-prover/src/avm_proving.test.ts b/yarn-project/bb-prover/src/avm_proving.test.ts index 6923c7057626..5394306ad7b7 100644 --- a/yarn-project/bb-prover/src/avm_proving.test.ts +++ b/yarn-project/bb-prover/src/avm_proving.test.ts @@ -1,41 +1,18 @@ import { - AvmCircuitInputs, - AvmCircuitPublicInputs, - Gas, - GlobalVariables, - type PublicFunction, - PublicKeys, - SerializableContractInstance, VerificationKeyData, } from '@aztec/circuits.js'; -import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; -import { AztecAddress } from '@aztec/foundation/aztec-address'; -import { Fr, Point } from '@aztec/foundation/fields'; +import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; -import { openTmpStore } from '@aztec/kv-store/utils'; -import { AvmSimulator, PublicSideEffectTrace, type WorldStateDB } from '@aztec/simulator'; -import { - getAvmTestContractBytecode, - getAvmTestContractFunctionSelector, - initContext, - initExecutionEnvironment, - initPersistableStateManager, - resolveAvmTestContractAssertionMessage, -} from '@aztec/simulator/avm/fixtures'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; -import { MerkleTrees } from '@aztec/world-state'; +import { simulateAvmTestContractGenerateCircuitInputs } from '@aztec/simulator'; -import { mock } from 'jest-mock-extended'; import fs from 'node:fs/promises'; import { tmpdir } from 'node:os'; import path from 'path'; import { type BBSuccess, BB_RESULT, generateAvmProof, verifyAvmProof } from './bb/execute.js'; -import { getPublicInputs } from './test/test_avm.js'; import { extractAvmVkData } from './verification_key/verification_key_data.js'; const TIMEOUT = 180_000; -const TIMESTAMP = new Fr(99833); describe('AVM WitGen, proof generation and verification', () => { it( @@ -50,77 +27,11 @@ describe('AVM WitGen, proof generation and verification', () => { ); }); -/************************************************************************ - * Helpers - ************************************************************************/ - -/** - * If assertionErrString is set, we expect a (non exceptional halting) revert due to a failing assertion and - * we check that the revert reason error contains this string. However, the circuit must correctly prove the - * execution. - */ -const proveAndVerifyAvmTestContract = async ( +async function proveAndVerifyAvmTestContract ( functionName: string, calldata: Fr[] = [], - assertionErrString?: string, -) => { - const startSideEffectCounter = 0; - const functionSelector = getAvmTestContractFunctionSelector(functionName); - calldata = [functionSelector.toField(), ...calldata]; - const globals = GlobalVariables.empty(); - globals.timestamp = TIMESTAMP; - - const worldStateDB = mock(); - // - // Top level contract call - const bytecode = getAvmTestContractBytecode('public_dispatch'); - const fnSelector = getAvmTestContractFunctionSelector('public_dispatch'); - const publicFn: PublicFunction = { bytecode, selector: fnSelector }; - const contractClass = makeContractClassPublic(0, publicFn); - const contractInstance = makeContractInstanceFromClassId(contractClass.id); - - // The values here should match those in `avm_simulator.test.ts` - const instanceGet = new SerializableContractInstance({ - version: 1, - salt: new Fr(0x123), - deployer: new AztecAddress(new Fr(0x456)), - contractClassId: new Fr(0x789), - initializationHash: new Fr(0x101112), - publicKeys: new PublicKeys( - new Point(new Fr(0x131415), new Fr(0x161718), false), - new Point(new Fr(0x192021), new Fr(0x222324), false), - new Point(new Fr(0x252627), new Fr(0x282930), false), - new Point(new Fr(0x313233), new Fr(0x343536), false), - ), - }).withAddress(contractInstance.address); - - worldStateDB.getContractInstance - .mockResolvedValueOnce(contractInstance) - .mockResolvedValueOnce(instanceGet) // test gets deployer - .mockResolvedValueOnce(instanceGet) // test gets class id - .mockResolvedValueOnce(instanceGet) // test gets init hash - .mockResolvedValue(contractInstance); - worldStateDB.getContractClass.mockResolvedValue(contractClass); - - const storageValue = new Fr(5); - worldStateDB.storageRead.mockResolvedValue(Promise.resolve(storageValue)); - - const trace = new PublicSideEffectTrace(startSideEffectCounter); - const telemetry = new NoopTelemetryClient(); - const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); - worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); - const persistableState = initPersistableStateManager({ worldStateDB, trace, merkleTrees, doMerkleOperations: true }); - const environment = initExecutionEnvironment({ - functionSelector, - calldata, - globals, - address: contractInstance.address, - }); - const context = initContext({ env: environment, persistableState }); - - worldStateDB.getBytecode.mockResolvedValue(bytecode); - - const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas); +) { + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata); const internalLogger = createDebugLogger('aztec:avm-proving-test'); const logger = (msg: string, _data?: any) => internalLogger.verbose(msg); @@ -129,39 +40,11 @@ const proveAndVerifyAvmTestContract = async ( const bbPath = path.resolve('../../barretenberg/cpp/build/bin/bb'); const bbWorkingDirectory = await fs.mkdtemp(path.join(tmpdir(), 'bb-')); - // First we simulate (though it's not needed in this simple case). - const simulator = new AvmSimulator(context); - const avmResult = await simulator.execute(); - - if (assertionErrString == undefined) { - expect(avmResult.reverted).toBe(false); - } else { - // Explicit revert when an assertion failed. - expect(avmResult.reverted).toBe(true); - expect(avmResult.revertReason).toBeDefined(); - expect(resolveAvmTestContractAssertionMessage(functionName, avmResult.revertReason!, avmResult.output)).toContain( - assertionErrString, - ); - } - - const pxResult = trace.toPublicFunctionCallResult( - environment, - startGas, - /*bytecode=*/ simulator.getBytecode()!, - avmResult.finalize(), - functionName, - ); - - const avmCircuitInputs = new AvmCircuitInputs( - functionName, - /*calldata=*/ context.environment.calldata, - /*publicInputs=*/ getPublicInputs(pxResult), - /*avmHints=*/ pxResult.avmCircuitHints, - /*output*/ AvmCircuitPublicInputs.empty(), - ); - // Then we prove. const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger); + if (proofRes.status === BB_RESULT.FAILURE) { + internalLogger.error(`Proof generation failed: ${proofRes.reason}`); + } expect(proofRes.status).toEqual(BB_RESULT.SUCCESS); // Then we test VK extraction and serialization. diff --git a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts index bb411de60c88..3e8baf1aa1f3 100644 --- a/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts +++ b/yarn-project/circuits.js/src/structs/public_circuit_public_inputs.ts @@ -39,6 +39,7 @@ import { PublicInnerCallRequest } from './public_inner_call_request.js'; import { ReadRequest } from './read_request.js'; import { RevertCode } from './revert_code.js'; import { TreeLeafReadRequest } from './tree_leaf_read_request.js'; +import { inspect } from 'util'; // TO BE REMOVED // This is currently the output of the AVM. It should be replaced by AvmCircuitPublicInputs eventually. @@ -324,4 +325,66 @@ export class PublicCircuitPublicInputs { reader.readField(), ); } + + [inspect.custom]() { + return `PublicCircuitPublicInputs { + callContext: ${inspect(this.callContext)}, + argsHash: ${inspect(this.argsHash)}, + returnsHash: ${inspect(this.returnsHash)}, + noteHashReadRequests: [${this.noteHashReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifierReadRequests: [${this.nullifierReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifierNonExistentReadRequests: [${this.nullifierNonExistentReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + l1ToL2MsgReadRequests: [${this.l1ToL2MsgReadRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + contractStorageUpdateRequests: [${this.contractStorageUpdateRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + contractStorageReads: [${this.contractStorageReads + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + publicCallRequests: [${this.publicCallRequests + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + noteHashes: [${this.noteHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + nullifiers: [${this.nullifiers + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + l2ToL1Msgs: [${this.l2ToL1Msgs + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + startSideEffectCounter: ${inspect(this.startSideEffectCounter)}, + endSideEffectCounter: ${inspect(this.endSideEffectCounter)}, + startSideEffectCounter: ${inspect(this.startSideEffectCounter)}, + unencryptedLogsHashes: [${this.unencryptedLogsHashes + .filter(x => !x.isEmpty()) + .map(h => inspect(h)) + .join(', ')}]}, + historicalHeader: ${inspect(this.historicalHeader)}, + globalVariables: ${inspect(this.globalVariables)}, + proverAddress: ${inspect(this.proverAddress)}, + revertCode: ${inspect(this.revertCode)}, + startGasLeft: ${inspect(this.startGasLeft)}, + endGasLeft: ${inspect(this.endGasLeft)}, + transactionFee: ${inspect(this.transactionFee)}, + }`; + } } diff --git a/yarn-project/ivc-integration/src/avm_integration.test.ts b/yarn-project/ivc-integration/src/avm_integration.test.ts index 4bbb4e196335..91ae5a25d0e8 100644 --- a/yarn-project/ivc-integration/src/avm_integration.test.ts +++ b/yarn-project/ivc-integration/src/avm_integration.test.ts @@ -3,19 +3,8 @@ import { BB_RESULT, generateAvmProof, generateProof, - getPublicInputs, verifyProof, } from '@aztec/bb-prover'; -import { - AvmCircuitInputs, - AvmCircuitPublicInputs, - AztecAddress, - Gas, - GlobalVariables, - type PublicFunction, - PublicKeys, - SerializableContractInstance, -} from '@aztec/circuits.js'; import { AVM_PROOF_LENGTH_IN_FIELDS, AVM_PUBLIC_COLUMN_MAX_SIZE, @@ -23,27 +12,14 @@ import { AVM_VERIFICATION_KEY_LENGTH_IN_FIELDS, PUBLIC_CIRCUIT_PUBLIC_INPUTS_LENGTH, } from '@aztec/circuits.js/constants'; -import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; -import { Fr, Point } from '@aztec/foundation/fields'; +import { Fr } from '@aztec/foundation/fields'; import { createDebugLogger } from '@aztec/foundation/log'; import { BufferReader } from '@aztec/foundation/serialize'; -import { openTmpStore } from '@aztec/kv-store/utils'; import { type FixedLengthArray } from '@aztec/noir-protocol-circuits-types/types'; -import { AvmSimulator, PublicSideEffectTrace, type WorldStateDB } from '@aztec/simulator'; -import { - getAvmTestContractBytecode, - getAvmTestContractFunctionSelector, - initContext, - initExecutionEnvironment, - initPersistableStateManager, - resolveAvmTestContractAssertionMessage, -} from '@aztec/simulator/avm/fixtures'; -import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; -import { MerkleTrees } from '@aztec/world-state'; +import { simulateAvmTestContractGenerateCircuitInputs } from '@aztec/simulator'; import { jest } from '@jest/globals'; import fs from 'fs/promises'; -import { mock } from 'jest-mock-extended'; import { tmpdir } from 'node:os'; import os from 'os'; import path from 'path'; @@ -153,106 +129,24 @@ describe('AVM Integration', () => { }); }); -// Helper - -const proveAvmTestContract = async ( +async function proveAvmTestContract ( functionName: string, calldata: Fr[] = [], - assertionErrString?: string, -): Promise => { - const worldStateDB = mock(); - const startSideEffectCounter = 0; - const functionSelector = getAvmTestContractFunctionSelector(functionName); - calldata = [functionSelector.toField(), ...calldata]; - const globals = GlobalVariables.empty(); - - // Top level contract call - const bytecode = getAvmTestContractBytecode('public_dispatch'); - const fnSelector = getAvmTestContractFunctionSelector('public_dispatch'); - const publicFn: PublicFunction = { bytecode, selector: fnSelector }; - const contractClass = makeContractClassPublic(0, publicFn); - const contractInstance = makeContractInstanceFromClassId(contractClass.id); - - const instanceGet = new SerializableContractInstance({ - version: 1, - salt: new Fr(0x123), - deployer: AztecAddress.fromNumber(0x456), - contractClassId: new Fr(0x789), - initializationHash: new Fr(0x101112), - publicKeys: new PublicKeys( - new Point(new Fr(0x131415), new Fr(0x161718), false), - new Point(new Fr(0x192021), new Fr(0x222324), false), - new Point(new Fr(0x252627), new Fr(0x282930), false), - new Point(new Fr(0x313233), new Fr(0x343536), false), - ), - }).withAddress(contractInstance.address); +): Promise { + const avmCircuitInputs = await simulateAvmTestContractGenerateCircuitInputs(functionName, calldata); - worldStateDB.getContractInstance - .mockResolvedValueOnce(contractInstance) - .mockResolvedValueOnce(instanceGet) // test gets deployer - .mockResolvedValueOnce(instanceGet) // test gets class id - .mockResolvedValueOnce(instanceGet) // test gets init hash - .mockResolvedValue(contractInstance); - - worldStateDB.getContractClass.mockResolvedValue(contractClass); - - const storageValue = new Fr(5); - worldStateDB.storageRead.mockResolvedValue(storageValue); - - const trace = new PublicSideEffectTrace(startSideEffectCounter); - const telemetry = new NoopTelemetryClient(); - const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); - worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); - const persistableState = initPersistableStateManager({ worldStateDB, trace, merkleTrees, doMerkleOperations: true }); - const environment = initExecutionEnvironment({ - functionSelector, - calldata, - globals, - address: contractInstance.address, - }); - const context = initContext({ env: environment, persistableState }); + const internalLogger = createDebugLogger('aztec:avm-proving-test'); + const logger = (msg: string, _data?: any) => internalLogger.verbose(msg); - worldStateDB.getBytecode.mockResolvedValue(bytecode); - - const startGas = new Gas(context.machineState.gasLeft.daGas, context.machineState.gasLeft.l2Gas); - - // Use a simple contract that emits a side effect // The paths for the barretenberg binary and the write path are hardcoded for now. const bbPath = path.resolve('../../barretenberg/cpp/build/bin/bb'); const bbWorkingDirectory = await fs.mkdtemp(path.join(tmpdir(), 'bb-')); - // First we simulate (though it's not needed in this simple case). - const simulator = new AvmSimulator(context); - const avmResult = await simulator.execute(); - - if (assertionErrString == undefined) { - expect(avmResult.reverted).toBe(false); - } else { - // Explicit revert when an assertion failed. - expect(avmResult.reverted).toBe(true); - expect(avmResult.revertReason).toBeDefined(); - expect(resolveAvmTestContractAssertionMessage(functionName, avmResult.revertReason!, avmResult.output)).toContain( - assertionErrString, - ); - } - - const pxResult = trace.toPublicFunctionCallResult( - environment, - startGas, - /*bytecode=*/ simulator.getBytecode()!, - avmResult.finalize(), - functionName, - ); - - const avmCircuitInputs = new AvmCircuitInputs( - functionName, - /*calldata=*/ context.environment.calldata, - /*publicInputs=*/ getPublicInputs(pxResult), - /*avmHints=*/ pxResult.avmCircuitHints, - AvmCircuitPublicInputs.empty(), - ); // Then we prove. - const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger.info); + const proofRes = await generateAvmProof(bbPath, bbWorkingDirectory, avmCircuitInputs, logger); + if (proofRes.status === BB_RESULT.FAILURE) { + internalLogger.error(`Proof generation failed: ${proofRes.reason}`); + } expect(proofRes.status).toEqual(BB_RESULT.SUCCESS); return proofRes as BBSuccess; diff --git a/yarn-project/simulator/src/avm/journal/journal.ts b/yarn-project/simulator/src/avm/journal/journal.ts index 637ec02d7fe7..dd62d155e73e 100644 --- a/yarn-project/simulator/src/avm/journal/journal.ts +++ b/yarn-project/simulator/src/avm/journal/journal.ts @@ -69,6 +69,8 @@ export class AvmPersistableStateManager { worldStateDB: WorldStateDB, trace: PublicSideEffectTraceInterface, pendingSiloedNullifiers: Fr[], + doMerkleOperations: boolean = false, + merkleTrees?: MerkleTreeWriteOperations, ) { const parentNullifiers = NullifierManager.newWithPendingSiloedNullifiers(worldStateDB, pendingSiloedNullifiers); return new AvmPersistableStateManager( @@ -76,6 +78,8 @@ export class AvmPersistableStateManager { trace, /*publicStorage=*/ new PublicStorage(worldStateDB), /*nullifiers=*/ parentNullifiers.fork(), + doMerkleOperations, + merkleTrees, ); } diff --git a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts index 6cc11d08fd60..768243bf4732 100644 --- a/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts +++ b/yarn-project/simulator/src/public/enqueued_call_side_effect_trace.ts @@ -320,7 +320,7 @@ export class PublicEnqueuedCallSideEffectTrace implements PublicSideEffectTraceI public traceNewNoteHash( contractAddress: AztecAddress, noteHash: Fr, - leafIndex: Fr, + leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath(), ) { if (this.noteHashes.length + this.previousSideEffectArrayLengths.noteHashes >= MAX_NOTE_HASHES_PER_TX) { diff --git a/yarn-project/simulator/src/public/index.ts b/yarn-project/simulator/src/public/index.ts index b7a3a5929f52..c4a4783f2425 100644 --- a/yarn-project/simulator/src/public/index.ts +++ b/yarn-project/simulator/src/public/index.ts @@ -7,3 +7,4 @@ export { PublicProcessor, PublicProcessorFactory } from './public_processor.js'; export { PublicSideEffectTrace } from './side_effect_trace.js'; export { PublicEnqueuedCallSideEffectTrace } from './enqueued_call_side_effect_trace.js'; export { getExecutionRequestsByPhase } from './utils.js'; +export { createTxForPublicCall, simulateAvmTestContractGenerateCircuitInputs } from './test_helper.js'; diff --git a/yarn-project/simulator/src/public/public_tx_context.ts b/yarn-project/simulator/src/public/public_tx_context.ts index 4cb1390e2dce..65c287fc769d 100644 --- a/yarn-project/simulator/src/public/public_tx_context.ts +++ b/yarn-project/simulator/src/public/public_tx_context.ts @@ -70,6 +70,7 @@ export class PublicTxContext { private readonly nonRevertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, private readonly revertibleAccumulatedDataFromPrivate: PrivateToPublicAccumulatedData, public trace: PublicEnqueuedCallSideEffectTrace, // FIXME(dbanks12): should be private + private doMerkleOperations: boolean, ) { this.log = createDebugLogger(`aztec:public_tx_context`); this.gasUsed = startGasUsed; @@ -80,6 +81,7 @@ export class PublicTxContext { worldStateDB: WorldStateDB, tx: Tx, globalVariables: GlobalVariables, + doMerkleOperations: boolean, ) { const nonRevertibleAccumulatedDataFromPrivate = tx.data.forPublic!.nonRevertibleAccumulatedData; const revertibleAccumulatedDataFromPrivate = tx.data.forPublic!.revertibleAccumulatedData; @@ -113,6 +115,7 @@ export class PublicTxContext { worldStateDB, trace, nonRevertibleNullifiersFromPrivate, + doMerkleOperations, ); return new PublicTxContext( @@ -131,6 +134,7 @@ export class PublicTxContext { tx.data.forPublic!.nonRevertibleAccumulatedData, tx.data.forPublic!.revertibleAccumulatedData, enqueuedCallTrace, + doMerkleOperations, ); } diff --git a/yarn-project/simulator/src/public/public_tx_simulator.ts b/yarn-project/simulator/src/public/public_tx_simulator.ts index a4d312965005..0759261a0e2f 100644 --- a/yarn-project/simulator/src/public/public_tx_simulator.ts +++ b/yarn-project/simulator/src/public/public_tx_simulator.ts @@ -58,6 +58,7 @@ export class PublicTxSimulator { client: TelemetryClient, private globalVariables: GlobalVariables, private realAvmProvingRequests: boolean = true, + private doMerkleOperations: boolean = false, ) { this.log = createDebugLogger(`aztec:public_tx_simulator`); this.metrics = new ExecutorMetrics(client, 'PublicTxSimulator'); @@ -71,7 +72,7 @@ export class PublicTxSimulator { async simulate(tx: Tx): Promise { this.log.verbose(`Processing tx ${tx.getTxHash()}`); - const context = await PublicTxContext.create(this.db, this.worldStateDB, tx, this.globalVariables); + const context = await PublicTxContext.create(this.db, this.worldStateDB, tx, this.globalVariables, this.doMerkleOperations); // add new contracts to the contracts db so that their functions may be found and called // TODO(#4073): This is catching only private deployments, when we add public ones, we'll diff --git a/yarn-project/simulator/src/public/side_effect_trace.ts b/yarn-project/simulator/src/public/side_effect_trace.ts index 2ec074470e74..ac1f4a98f167 100644 --- a/yarn-project/simulator/src/public/side_effect_trace.ts +++ b/yarn-project/simulator/src/public/side_effect_trace.ts @@ -199,7 +199,7 @@ export class PublicSideEffectTrace implements PublicSideEffectTraceInterface { public traceNewNoteHash( _contractAddress: AztecAddress, noteHash: Fr, - leafIndex: Fr, + leafIndex: Fr = Fr.zero(), path: Fr[] = emptyNoteHashPath(), ) { if (this.noteHashes.length >= MAX_NOTE_HASHES_PER_TX) { diff --git a/yarn-project/simulator/src/public/test_helper.ts b/yarn-project/simulator/src/public/test_helper.ts new file mode 100644 index 000000000000..5c7176e38750 --- /dev/null +++ b/yarn-project/simulator/src/public/test_helper.ts @@ -0,0 +1,166 @@ +import { + Gas, + GlobalVariables, + type PublicFunction, + PublicKeys, + SerializableContractInstance, + GasFees, + CallContext, + MAX_L2_GAS_PER_ENQUEUED_CALL, + DEFAULT_GAS_LIMIT, + PartialPrivateTailPublicInputsForPublic, + GasSettings, + TxContext, + TxConstantData, + Header, + PrivateKernelTailCircuitPublicInputs, + RollupValidationRequests, + AvmCircuitInputs, +} from '@aztec/circuits.js'; +import { makeContractClassPublic, makeContractInstanceFromClassId } from '@aztec/circuits.js/testing'; +import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { Fr, Point } from '@aztec/foundation/fields'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { PublicTxSimulator, type WorldStateDB } from '@aztec/simulator'; +import { + getAvmTestContractBytecode, + getAvmTestContractFunctionSelector, +} from '../avm/fixtures/index.js'; +import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; +import { MerkleTrees } from '@aztec/world-state'; + +import { mock } from 'jest-mock-extended'; + +import { PublicExecutionRequest, Tx } from '@aztec/circuit-types'; + +const TIMESTAMP = new Fr(99833); + +/************************************************************************ + * Helpers + ************************************************************************/ + +/** + * If assertionErrString is set, we expect a (non exceptional halting) revert due to a failing assertion and + * we check that the revert reason error contains this string. However, the circuit must correctly prove the + * execution. + */ +export async function simulateAvmTestContractGenerateCircuitInputs ( + functionName: string, + calldata: Fr[] = [], + assertionErrString?: string, +): Promise { + const sender = AztecAddress.random(); + const functionSelector = getAvmTestContractFunctionSelector(functionName); + calldata = [functionSelector.toField(), ...calldata]; + + const globalVariables = GlobalVariables.empty(); + globalVariables.gasFees = GasFees.default(); + globalVariables.timestamp = TIMESTAMP; + + const worldStateDB = mock(); + const telemetry = new NoopTelemetryClient(); + const merkleTrees = await (await MerkleTrees.new(openTmpStore(), telemetry)).fork(); + worldStateDB.getMerkleInterface.mockReturnValue(merkleTrees); + + // Top level contract call + const bytecode = getAvmTestContractBytecode('public_dispatch'); + const dispatchSelector = getAvmTestContractFunctionSelector('public_dispatch'); + const publicFn: PublicFunction = { bytecode, selector: dispatchSelector }; + const contractClass = makeContractClassPublic(0, publicFn); + const contractInstance = makeContractInstanceFromClassId(contractClass.id); + + // The values here should match those in `avm_simulator.test.ts` + const instanceGet = new SerializableContractInstance({ + version: 1, + salt: new Fr(0x123), + deployer: new AztecAddress(new Fr(0x456)), + contractClassId: new Fr(0x789), + initializationHash: new Fr(0x101112), + publicKeys: new PublicKeys( + new Point(new Fr(0x131415), new Fr(0x161718), false), + new Point(new Fr(0x192021), new Fr(0x222324), false), + new Point(new Fr(0x252627), new Fr(0x282930), false), + new Point(new Fr(0x313233), new Fr(0x343536), false), + ), + }).withAddress(contractInstance.address); + worldStateDB.getContractInstance + .mockResolvedValueOnce(contractInstance) + .mockResolvedValueOnce(instanceGet) // test gets deployer + .mockResolvedValueOnce(instanceGet) // test gets class id + .mockResolvedValueOnce(instanceGet) // test gets init hash + .mockResolvedValue(contractInstance); + worldStateDB.getContractClass.mockResolvedValue(contractClass); + worldStateDB.getBytecode.mockResolvedValue(bytecode); + + const storageValue = new Fr(5); + worldStateDB.storageRead.mockResolvedValue(Promise.resolve(storageValue)); + + const simulator = new PublicTxSimulator( + merkleTrees, + worldStateDB, + new NoopTelemetryClient(), + globalVariables, + /*doMerkleOperations=*/ true, + ); + + const callContext = new CallContext( + sender, + contractInstance.address, + dispatchSelector, + /*isStaticCall=*/ false, + ); + const executionRequest = new PublicExecutionRequest(callContext, calldata); + + const tx: Tx = createTxForPublicCall(executionRequest); + + const avmResult = await simulator.simulate(tx); + + if (assertionErrString == undefined) { + expect(avmResult.revertCode.isOK()).toBe(true); + } else { + // Explicit revert when an assertion failed. + expect(avmResult.revertCode.isOK()).toBe(false); + expect(avmResult.revertReason).toBeDefined(); + expect(avmResult.revertReason?.getMessage()).toContain(assertionErrString); + } + + const avmCircuitInputs: AvmCircuitInputs = avmResult.avmProvingRequest.inputs; + return avmCircuitInputs; +}; + +/** + * Craft a carrier transaction for a public call for simulation by PublicTxSimulator. + */ +export function createTxForPublicCall(executionRequest: PublicExecutionRequest, gasUsedByPrivate: Gas = Gas.empty(), isTeardown: boolean = false): Tx { + const callRequest = executionRequest.toCallRequest(); + // use max limits + const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_ENQUEUED_CALL); + + const forPublic = PartialPrivateTailPublicInputsForPublic.empty(); + // TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash. + forPublic.nonRevertibleAccumulatedData.nullifiers[0] = Fr.random(); // fake tx nullifier + if (isTeardown) { + forPublic.publicTeardownCallRequest = callRequest; + } else { + forPublic.revertibleAccumulatedData.publicCallRequests[0] = callRequest; + } + + const teardownGasLimits = isTeardown ? gasLimits : Gas.empty(); + const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty()); + const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); + const constantData = new TxConstantData(Header.empty(), txContext, Fr.zero(), Fr.zero()); + + const txData = new PrivateKernelTailCircuitPublicInputs( + constantData, + RollupValidationRequests.empty(), + /*gasUsed=*/ gasUsedByPrivate, + AztecAddress.zero(), + forPublic, + ); + const tx = isTeardown ? Tx.newWithTxData(txData, executionRequest) : Tx.newWithTxData(txData); + if (!isTeardown) { + tx.enqueuedPublicFunctionCalls[0] = executionRequest; + } + + return tx; +} diff --git a/yarn-project/txe/src/oracle/txe_oracle.ts b/yarn-project/txe/src/oracle/txe_oracle.ts index 65811c2d6db7..988aecdb38af 100644 --- a/yarn-project/txe/src/oracle/txe_oracle.ts +++ b/yarn-project/txe/src/oracle/txe_oracle.ts @@ -8,7 +8,6 @@ import { PublicDataWitness, PublicExecutionRequest, SimulationError, - Tx, type UnencryptedL2Log, } from '@aztec/circuit-types'; import { type CircuitWitnessGenerationStats } from '@aztec/circuit-types/stats'; @@ -16,35 +15,27 @@ import { CallContext, type ContractInstance, type ContractInstanceWithAddress, - DEFAULT_GAS_LIMIT, - Gas, GasFees, - GasSettings, GlobalVariables, Header, IndexedTaggingSecret, type KeyValidationRequest, type L1_TO_L2_MSG_TREE_HEIGHT, - MAX_L2_GAS_PER_ENQUEUED_CALL, NULLIFIER_SUBTREE_HEIGHT, type NULLIFIER_TREE_HEIGHT, type NullifierLeafPreimage, PRIVATE_CONTEXT_INPUTS_LENGTH, type PUBLIC_DATA_TREE_HEIGHT, PUBLIC_DISPATCH_SELECTOR, - PartialPrivateTailPublicInputsForPublic, PrivateContextInputs, - PrivateKernelTailCircuitPublicInputs, PublicDataTreeLeaf, type PublicDataTreeLeafPreimage, type PublicDataWrite, - RollupValidationRequests, - TxConstantData, - TxContext, computeContractClassId, computeTaggingSecret, deriveKeys, getContractClassFromArtifact, + Gas, } from '@aztec/circuits.js'; import { Schnorr } from '@aztec/circuits.js/barretenberg'; import { computePublicDataTreeLeafSlot, siloNoteHash, siloNullifier } from '@aztec/circuits.js/hash'; @@ -80,6 +71,7 @@ import { resolveAssertionMessageFromError, toACVMWitness, witnessMapToFields, + createTxForPublicCall, } from '@aztec/simulator'; import { NoopTelemetryClient } from '@aztec/telemetry-client/noop'; import { MerkleTreeSnapshotOperationsFacade, type MerkleTrees } from '@aztec/world-state'; @@ -663,7 +655,6 @@ export class TXE implements TypedOracle { const globalVariables = GlobalVariables.empty(); globalVariables.chainId = this.chainId; - globalVariables.chainId = this.chainId; globalVariables.version = this.version; globalVariables.blockNumber = new Fr(this.blockNumber); globalVariables.gasFees = GasFees.default(); @@ -676,7 +667,10 @@ export class TXE implements TypedOracle { /*realAvmProvingRequests=*/ false, ); - const tx = this.createTxForPublicCall(executionRequest, isTeardown); + // When setting up a teardown call, we tell it that + // private execution used Gas(1, 1) so it can compute a tx fee. + const gasUsedByPrivate = isTeardown ? new Gas(1, 1) : Gas.empty(); + const tx = createTxForPublicCall(executionRequest, gasUsedByPrivate, isTeardown); const result = await simulator.simulate(tx); return Promise.resolve(result); @@ -886,44 +880,4 @@ export class TXE implements TypedOracle { return preimage.value; } - - /** - * Craft a carrier transaction for a public call. - */ - private createTxForPublicCall(executionRequest: PublicExecutionRequest, teardown: boolean): Tx { - const callRequest = executionRequest.toCallRequest(); - // use max limits - const gasLimits = new Gas(DEFAULT_GAS_LIMIT, MAX_L2_GAS_PER_ENQUEUED_CALL); - - const forPublic = PartialPrivateTailPublicInputsForPublic.empty(); - // TODO(#9269): Remove this fake nullifier method as we move away from 1st nullifier as hash. - forPublic.nonRevertibleAccumulatedData.nullifiers[0] = Fr.random(); // fake tx nullifier - if (teardown) { - forPublic.publicTeardownCallRequest = callRequest; - } else { - forPublic.revertibleAccumulatedData.publicCallRequests[0] = callRequest; - } - - // When setting up a teardown call, we tell it that - // private execution "used" Gas(1, 1) so it can compute a tx fee. - const gasUsedByPrivate = teardown ? new Gas(1, 1) : Gas.empty(); - const teardownGasLimits = teardown ? gasLimits : Gas.empty(); - const gasSettings = new GasSettings(gasLimits, teardownGasLimits, GasFees.empty()); - const txContext = new TxContext(Fr.zero(), Fr.zero(), gasSettings); - const constantData = new TxConstantData(Header.empty(), txContext, Fr.zero(), Fr.zero()); - - const txData = new PrivateKernelTailCircuitPublicInputs( - constantData, - RollupValidationRequests.empty(), - /*gasUsed=*/ gasUsedByPrivate, - AztecAddress.zero(), - forPublic, - ); - const tx = teardown ? Tx.newWithTxData(txData, executionRequest) : Tx.newWithTxData(txData); - if (!teardown) { - tx.enqueuedPublicFunctionCalls[0] = executionRequest; - } - - return tx; - } }