diff --git a/noir-projects/aztec-nr/aztec/src/context/avm_context.nr b/noir-projects/aztec-nr/aztec/src/context/avm_context.nr index 4f72dad449b..68925164b65 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm_context.nr @@ -3,15 +3,17 @@ use dep::protocol_types::traits::Serialize; use dep::protocol_types::abis::function_selector::FunctionSelector; use dep::protocol_types::abis::public_circuit_public_inputs::PublicCircuitPublicInputs; use dep::protocol_types::constants::RETURN_VALUES_LENGTH; -use crate::context::inputs::PublicContextInputs; +use crate::context::inputs::avm_context_inputs::AvmContextInputs; use crate::context::interface::ContextInterface; use crate::context::interface::PublicContextInterface; -struct AVMContext {} +struct AVMContext { + inputs: AvmContextInputs, +} impl AVMContext { - pub fn new() -> Self { - AVMContext {} + pub fn new(inputs: AvmContextInputs) -> Self { + AVMContext { inputs } } pub fn origin(self) -> AztecAddress { @@ -190,16 +192,14 @@ impl ContextInterface for AVMContext { version() } fn selector(self) -> FunctionSelector { - assert(false, "'selector' not implemented!"); - FunctionSelector::zero() + FunctionSelector::from_field(self.inputs.selector) } fn get_header(self) -> Header { assert(false, "'get_header' not implemented!"); Header::empty() } fn get_args_hash(self) -> Field { - assert(false, "'get_args_hash' not implemented!"); - 0 + self.inputs.args_hash } } diff --git a/noir-projects/aztec-nr/aztec/src/context/inputs.nr b/noir-projects/aztec-nr/aztec/src/context/inputs.nr index 0d7c56574d5..86595969e1e 100644 --- a/noir-projects/aztec-nr/aztec/src/context/inputs.nr +++ b/noir-projects/aztec-nr/aztec/src/context/inputs.nr @@ -1,5 +1,7 @@ mod private_context_inputs; mod public_context_inputs; +mod avm_context_inputs; use crate::context::inputs::private_context_inputs::PrivateContextInputs; use crate::context::inputs::public_context_inputs::PublicContextInputs; +use crate::context::inputs::avm_context_inputs::AvmContextInputs; diff --git a/noir-projects/aztec-nr/aztec/src/context/inputs/avm_context_inputs.nr b/noir-projects/aztec-nr/aztec/src/context/inputs/avm_context_inputs.nr new file mode 100644 index 00000000000..ffd16b268ac --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/context/inputs/avm_context_inputs.nr @@ -0,0 +1,4 @@ +struct AvmContextInputs { + selector: Field, + args_hash: Field, +} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr index 43a150ae1f8..96081c70768 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/src/main.nr @@ -232,6 +232,16 @@ contract AvmTest { // context.contract_call_depth() // } + #[aztec(public-vm)] + fn check_selector() { + assert(context.selector() == FunctionSelector::from_signature("check_selector()")); + } + + #[aztec(public-vm)] + fn get_args_hash(_a: u8, _fields: [Field; 3]) -> pub Field { + context.get_args_hash() + } + #[aztec(public-vm)] fn emit_unencrypted_log() { context.accumulate_unencrypted_logs(/*event_selector=*/ 5, /*message=*/ [10, 20, 30]); diff --git a/noir/noir-repo/aztec_macros/src/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index 51a90c2ee28..5e42dccdd78 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -116,6 +116,10 @@ pub fn transform_vm_function( let create_context = create_avm_context()?; func.def.body.0.insert(0, create_context); + // Add the inputs to the params (first!) + let input = create_inputs("AvmContextInputs"); + func.def.parameters.insert(0, input); + // We want the function to be seen as a public function func.def.is_unconstrained = true; @@ -353,11 +357,14 @@ fn create_context(ty: &str, params: &[Param]) -> Result, AztecMac /// // ... /// } fn create_avm_context() -> Result { + // Create the inputs to the context + let inputs_expression = variable("inputs"); + let let_context = mutable_assignment( "context", // Assigned to call( variable_path(chained_dep!("aztec", "context", "AVMContext", "new")), // Path - vec![], // args + vec![inputs_expression], // args ), ); diff --git a/yarn-project/simulator/src/avm/avm_context.test.ts b/yarn-project/simulator/src/avm/avm_context.test.ts index e2a29c8be9b..089e1d3f4c4 100644 --- a/yarn-project/simulator/src/avm/avm_context.test.ts +++ b/yarn-project/simulator/src/avm/avm_context.test.ts @@ -1,6 +1,6 @@ import { AztecAddress, Fr } from '@aztec/circuits.js'; -import { allSameExcept, initContext } from './fixtures/index.js'; +import { allSameExcept, anyAvmContextInputs, initContext } from './fixtures/index.js'; describe('Avm Context', () => { it('New call should fork context correctly', () => { @@ -15,7 +15,8 @@ describe('Avm Context', () => { allSameExcept(context.environment, { address: newAddress, storageAddress: newAddress, - calldata: newCalldata, + // Calldata also includes AvmContextInputs + calldata: anyAvmContextInputs().concat(newCalldata), isStaticCall: false, }), ); @@ -41,7 +42,8 @@ describe('Avm Context', () => { allSameExcept(context.environment, { address: newAddress, storageAddress: newAddress, - calldata: newCalldata, + // Calldata also includes AvmContextInputs + calldata: anyAvmContextInputs().concat(newCalldata), isStaticCall: true, }), ); diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.test.ts b/yarn-project/simulator/src/avm/avm_execution_environment.test.ts index e2104cbad59..6aad1e2ea25 100644 --- a/yarn-project/simulator/src/avm/avm_execution_environment.test.ts +++ b/yarn-project/simulator/src/avm/avm_execution_environment.test.ts @@ -1,6 +1,6 @@ import { Fr } from '@aztec/foundation/fields'; -import { allSameExcept, initExecutionEnvironment } from './fixtures/index.js'; +import { allSameExcept, anyAvmContextInputs, initExecutionEnvironment } from './fixtures/index.js'; describe('Execution Environment', () => { const newAddress = new Fr(123456n); @@ -14,7 +14,8 @@ describe('Execution Environment', () => { allSameExcept(executionEnvironment, { address: newAddress, storageAddress: newAddress, - calldata, + // Calldata also includes AvmContextInputs + calldata: anyAvmContextInputs().concat(calldata), }), ); }); @@ -27,7 +28,8 @@ describe('Execution Environment', () => { allSameExcept(executionEnvironment, { address: newAddress, isDelegateCall: true, - calldata, + // Calldata also includes AvmContextInputs + calldata: anyAvmContextInputs().concat(calldata), }), ); }); @@ -41,7 +43,8 @@ describe('Execution Environment', () => { address: newAddress, storageAddress: newAddress, isStaticCall: true, - calldata, + // Calldata also includes AvmContextInputs + calldata: anyAvmContextInputs().concat(calldata), }), ); }); diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.ts b/yarn-project/simulator/src/avm/avm_execution_environment.ts index 23340dd42b8..d6ffd51d9ba 100644 --- a/yarn-project/simulator/src/avm/avm_execution_environment.ts +++ b/yarn-project/simulator/src/avm/avm_execution_environment.ts @@ -1,8 +1,19 @@ import { FunctionSelector, GlobalVariables } from '@aztec/circuits.js'; import { AztecAddress } from '@aztec/foundation/aztec-address'; +import { pedersenHash } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; +export class AvmContextInputs { + static readonly SIZE = 2; + + constructor(private selector: Fr, private argsHash: Fr) {} + + public toFields(): Fr[] { + return [this.selector, this.argsHash]; + } +} + /** * Contains variables that remain constant during AVM execution * These variables are provided by the public kernel circuit @@ -40,7 +51,15 @@ export class AvmExecutionEnvironment { // containing all functions, and function selector will become an application-level mechanism // (e.g. first few bytes of calldata + compiler-generated jump table) public readonly temporaryFunctionSelector: FunctionSelector, - ) {} + ) { + // We encode some extra inputs (AvmContextInputs) in calldata. + // This will have to go once we move away from one proof per call. + const inputs = new AvmContextInputs( + temporaryFunctionSelector.toField(), + pedersenHash(calldata.map(word => word.toBuffer())), + ); + this.calldata = [...inputs.toFields(), ...calldata]; + } public deriveEnvironmentForNestedCall( address: AztecAddress, diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index bf85390f1cb..e6d8a083fbe 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -1,5 +1,5 @@ import { UnencryptedL2Log } from '@aztec/circuit-types'; -import { EventSelector } from '@aztec/foundation/abi'; +import { EventSelector, FunctionSelector } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -12,6 +12,7 @@ import { strict as assert } from 'assert'; import { TypeTag } from './avm_memory_types.js'; import { AvmSimulator } from './avm_simulator.js'; import { + adjustCalldataIndex, initContext, initExecutionEnvironment, initGlobalVariables, @@ -20,13 +21,13 @@ import { import { Add, CalldataCopy, Return } from './opcodes/index.js'; import { encodeToBytecode } from './serialization/bytecode_serialization.js'; -describe('AVM simulator', () => { +describe('AVM simulator: injected bytecode', () => { it('Should execute bytecode that performs basic addition', async () => { const calldata: Fr[] = [new Fr(1), new Fr(2)]; // Construct bytecode const bytecode = encodeToBytecode([ - new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 2, /*dstOffset=*/ 0), + new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ adjustCalldataIndex(0), /*copySize=*/ 2, /*dstOffset=*/ 0), new Add(/*indirect=*/ 0, TypeTag.FIELD, /*aOffset=*/ 0, /*bOffset=*/ 1, /*dstOffset=*/ 2), new Return(/*indirect=*/ 0, /*returnOffset=*/ 2, /*copySize=*/ 1), ]); @@ -37,663 +38,688 @@ describe('AVM simulator', () => { expect(results.reverted).toBe(false); expect(results.output).toEqual([new Fr(3)]); }); +}); - describe('Transpiled Noir contracts', () => { - it('Should execute contract function that performs addition', async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); +describe('AVM simulator: transpiled Noir contracts', () => { + it('addition', async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + + const bytecode = getAvmTestContractBytecode('add_args_return'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + + it('U128 addition', async () => { + const calldata: Fr[] = [ + // First U128 + new Fr(1), + new Fr(2), + // Second U128 + new Fr(3), + new Fr(4), + ]; + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('add_args_return'); + const bytecode = getAvmTestContractBytecode('add_u128'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(4), new Fr(6)]); + }); + + describe.each([ + ['set_opcode_u8', 8n], + ['set_opcode_u32', 1n << 30n], + ['set_opcode_u64', 1n << 60n], + ['set_opcode_small_field', 0x001234567890abcdef1234567890abcdefn], + ['set_opcode_big_field', 0x991234567890abcdef1234567890abcdefn], + ])('SET functions', (name: string, res: bigint) => { + it(`function '${name}'`, async () => { + const context = initContext(); + const bytecode = getAvmTestContractBytecode(name); const results = await new AvmSimulator(context).executeBytecode(bytecode); expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(3)]); + expect(results.output).toEqual([new Fr(res)]); }); + }); + + describe.each([ + ['sha256_hash', sha256], + ['keccak_hash', keccak], + ])('Hashes with 2 fields returned in noir contracts', (name: string, hashFunction: (data: Buffer) => Buffer) => { + it(`Should execute contract function that performs ${name} hash`, async () => { + const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const hash = hashFunction(Buffer.concat(calldata.map(f => f.toBuffer()))); - it('Should execute contract function that performs U128 addition', async () => { - const calldata: Fr[] = [ - // First U128 - new Fr(1), - new Fr(2), - // Second U128 - new Fr(3), - new Fr(4), - ]; const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode(name); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - const bytecode = getAvmTestContractBytecode('add_u128'); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(hash.subarray(0, 16)), new Fr(hash.subarray(16, 32))]); + }); + }); + + describe.each([ + ['poseidon_hash', poseidonHash], + ['pedersen_hash', pedersenHash], + ])('Hashes with field returned in noir contracts', (name: string, hashFunction: (data: Buffer[]) => Fr) => { + it(`Should execute contract function that performs ${name} hash`, async () => { + const calldata = [new Fr(1), new Fr(2), new Fr(3)]; + const hash = hashFunction(calldata.map(f => f.toBuffer())); + + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode(name); const results = await new AvmSimulator(context).executeBytecode(bytecode); expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(4), new Fr(6)]); - }); - - describe.each([ - ['set_opcode_u8', 8n], - ['set_opcode_u32', 1n << 30n], - ['set_opcode_u64', 1n << 60n], - ['set_opcode_small_field', 0x001234567890abcdef1234567890abcdefn], - ['set_opcode_big_field', 0x991234567890abcdef1234567890abcdefn], - ])('Should execute contract SET functions', (name: string, res: bigint) => { - it(`Should execute contract function '${name}'`, async () => { - const context = initContext(); - const bytecode = getAvmTestContractBytecode(name); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(res)]); - }); + expect(results.output).toEqual([new Fr(hash)]); }); + }); + + describe('Environment getters', () => { + const testEnvGetter = async (valueName: string, value: any, functionName: string, globalVar: boolean = false) => { + // Execute + let overrides = {}; + if (globalVar === true) { + const globals = initGlobalVariables({ [valueName]: value }); + overrides = { globals }; + } else { + overrides = { [valueName]: value }; + } + const context = initContext({ env: initExecutionEnvironment(overrides) }); + const bytecode = getAvmTestContractBytecode(functionName); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - describe.each([ - ['sha256_hash', sha256], - ['keccak_hash', keccak], - ])('Hashes with 2 fields returned in noir contracts', (name: string, hashFunction: (data: Buffer) => Buffer) => { - it(`Should execute contract function that performs ${name} hash`, async () => { - const calldata = [new Fr(1), new Fr(2), new Fr(3)]; - const hash = hashFunction(Buffer.concat(calldata.map(f => f.toBuffer()))); + expect(results.reverted).toBe(false); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode(name); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + const returnData = results.output; + expect(returnData).toEqual([value.toField()]); + }; - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(hash.subarray(0, 16)), new Fr(hash.subarray(16, 32))]); - }); + it('address', async () => { + const address = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('address', address, 'get_address'); }); - describe.each([ - ['poseidon_hash', poseidonHash], - ['pedersen_hash', pedersenHash], - ])('Hashes with field returned in noir contracts', (name: string, hashFunction: (data: Buffer[]) => Fr) => { - it(`Should execute contract function that performs ${name} hash`, async () => { - const calldata = [new Fr(1), new Fr(2), new Fr(3)]; - const hash = hashFunction(calldata.map(f => f.toBuffer())); + it('storageAddress', async () => { + const storageAddress = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('storageAddress', storageAddress, 'get_storage_address'); + }); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode(name); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + it('sender', async () => { + const sender = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('sender', sender, 'get_sender'); + }); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(hash)]); - }); + it('origin', async () => { + const origin = AztecAddress.fromField(new Fr(1)); + await testEnvGetter('origin', origin, 'get_origin'); }); - describe('Test env getters from noir contract', () => { - const testEnvGetter = async (valueName: string, value: any, functionName: string, globalVar: boolean = false) => { - // Execute - let overrides = {}; - if (globalVar === true) { - const globals = initGlobalVariables({ [valueName]: value }); - overrides = { globals }; - } else { - overrides = { [valueName]: value }; - } - const context = initContext({ env: initExecutionEnvironment(overrides) }); - const bytecode = getAvmTestContractBytecode(functionName); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - - const returnData = results.output; - expect(returnData).toEqual([value.toField()]); - }; - - it('address', async () => { - const address = AztecAddress.fromField(new Fr(1)); - await testEnvGetter('address', address, 'get_address'); - }); + it('portal', async () => { + const portal = EthAddress.fromField(new Fr(1)); + await testEnvGetter('portal', portal, 'get_portal'); + }); - it('storageAddress', async () => { - const storageAddress = AztecAddress.fromField(new Fr(1)); - await testEnvGetter('storageAddress', storageAddress, 'get_storage_address'); - }); + it('getFeePerL1Gas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerL1Gas', fee, 'get_fee_per_l1_gas'); + }); - it('sender', async () => { - const sender = AztecAddress.fromField(new Fr(1)); - await testEnvGetter('sender', sender, 'get_sender'); - }); + it('getFeePerL2Gas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerL2Gas', fee, 'get_fee_per_l2_gas'); + }); - it('origin', async () => { - const origin = AztecAddress.fromField(new Fr(1)); - await testEnvGetter('origin', origin, 'get_origin'); - }); + it('getFeePerDaGas', async () => { + const fee = new Fr(1); + await testEnvGetter('feePerDaGas', fee, 'get_fee_per_da_gas'); + }); - it('portal', async () => { - const portal = EthAddress.fromField(new Fr(1)); - await testEnvGetter('portal', portal, 'get_portal'); - }); + it('chainId', async () => { + const chainId = new Fr(1); + await testEnvGetter('chainId', chainId, 'get_chain_id', /*globalVar=*/ true); + }); - it('getFeePerL1Gas', async () => { - const fee = new Fr(1); - await testEnvGetter('feePerL1Gas', fee, 'get_fee_per_l1_gas'); - }); + it('version', async () => { + const version = new Fr(1); + await testEnvGetter('version', version, 'get_version', /*globalVar=*/ true); + }); - it('getFeePerL2Gas', async () => { - const fee = new Fr(1); - await testEnvGetter('feePerL2Gas', fee, 'get_fee_per_l2_gas'); - }); + it('blockNumber', async () => { + const blockNumber = new Fr(1); + await testEnvGetter('blockNumber', blockNumber, 'get_block_number', /*globalVar=*/ true); + }); - it('getFeePerDaGas', async () => { - const fee = new Fr(1); - await testEnvGetter('feePerDaGas', fee, 'get_fee_per_da_gas'); - }); + it('timestamp', async () => { + const timestamp = new Fr(1); + await testEnvGetter('timestamp', timestamp, 'get_timestamp', /*globalVar=*/ true); + }); + }); - it('chainId', async () => { - const chainId = new Fr(1); - await testEnvGetter('chainId', chainId, 'get_chain_id', /*globalVar=*/ true); + describe('AvmContextInputs', () => { + it('selector', async () => { + const context = initContext({ + env: initExecutionEnvironment({ + temporaryFunctionSelector: FunctionSelector.fromSignature('check_selector()'), + }), }); + const bytecode = getAvmTestContractBytecode('check_selector'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - it('version', async () => { - const version = new Fr(1); - await testEnvGetter('version', version, 'get_version', /*globalVar=*/ true); - }); + expect(results.reverted).toBe(false); + }); - it('blockNumber', async () => { - const blockNumber = new Fr(1); - await testEnvGetter('blockNumber', blockNumber, 'get_block_number', /*globalVar=*/ true); - }); + it('get_args_hash', async () => { + const calldata = [new Fr(8), new Fr(1), new Fr(2), new Fr(3)]; - it('timestamp', async () => { - const timestamp = new Fr(1); - await testEnvGetter('timestamp', timestamp, 'get_timestamp', /*globalVar=*/ true); - }); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('get_args_hash'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([pedersenHash(calldata.map(f => f.toBuffer()))]); }); + }); - describe('Test tree access functions from noir contract (notes & nullifiers)', () => { - it(`Should execute contract function that checks if a note hash exists (it does not)`, async () => { - const noteHash = new Fr(42); - const leafIndex = new Fr(7); - const calldata = [noteHash, leafIndex]; + describe('Tree access (notes & nullifiers)', () => { + it(`Note hash exists (it does not)`, async () => { + const noteHash = new Fr(42); + const leafIndex = new Fr(7); + const calldata = [noteHash, leafIndex]; - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('note_hash_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('note_hash_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); - // Note hash existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.noteHashChecks).toEqual([expect.objectContaining({ noteHash, leafIndex, exists: false })]); - }); + // Note hash existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.noteHashChecks).toEqual([expect.objectContaining({ noteHash, leafIndex, exists: false })]); + }); - it(`Should execute contract function that checks if a note hash exists (it does)`, async () => { - const noteHash = new Fr(42); - const leafIndex = new Fr(7); - const calldata = [noteHash, leafIndex]; - - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - // note hash exists! - jest - .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getCommitmentIndex') - .mockReturnValue(Promise.resolve(BigInt(7))); - const bytecode = getAvmTestContractBytecode('note_hash_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=true*/ new Fr(1)]); - - // Note hash existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.noteHashChecks).toEqual([expect.objectContaining({ noteHash, leafIndex, exists: true })]); - }); + it(`Note hash exists (it does)`, async () => { + const noteHash = new Fr(42); + const leafIndex = new Fr(7); + const calldata = [noteHash, leafIndex]; - it(`Should execute contract function to emit unencrypted logs (should be traced)`, async () => { - const context = initContext(); - const bytecode = getAvmTestContractBytecode('emit_unencrypted_log'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - - const expectedFields = [new Fr(10), new Fr(20), new Fr(30)]; - const expectedString = 'Hello, world!'.split('').map(c => new Fr(c.charCodeAt(0))); - const expectedCompressedString = Buffer.from( - '\0A long time ago, in a galaxy fa' + '\0r far away...\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', - ); - expect(context.persistableState.flush().newLogs).toEqual([ - new UnencryptedL2Log( - context.environment.address, - new EventSelector(5), - Buffer.concat(expectedFields.map(f => f.toBuffer())), - ), - new UnencryptedL2Log( - context.environment.address, - new EventSelector(8), - Buffer.concat(expectedString.map(f => f.toBuffer())), - ), - new UnencryptedL2Log(context.environment.address, new EventSelector(10), expectedCompressedString), - ]); - }); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + // note hash exists! + jest + .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getCommitmentIndex') + .mockReturnValue(Promise.resolve(BigInt(7))); + const bytecode = getAvmTestContractBytecode('note_hash_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - it(`Should execute contract function to emit note hash (should be traced)`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=true*/ new Fr(1)]); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('new_note_hash'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + // Note hash existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.noteHashChecks).toEqual([expect.objectContaining({ noteHash, leafIndex, exists: true })]); + }); - expect(results.reverted).toBe(false); + it(`Emit unencrypted logs (should be traced)`, async () => { + const context = initContext(); + const bytecode = getAvmTestContractBytecode('emit_unencrypted_log'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(context.persistableState.flush().newNoteHashes).toEqual([utxo]); - }); + expect(results.reverted).toBe(false); - it(`Should execute contract function to emit nullifier (should be traced)`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; + const expectedFields = [new Fr(10), new Fr(20), new Fr(30)]; + const expectedString = 'Hello, world!'.split('').map(c => new Fr(c.charCodeAt(0))); + const expectedCompressedString = Buffer.from( + '\0A long time ago, in a galaxy fa' + '\0r far away...\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0', + ); + expect(context.persistableState.flush().newLogs).toEqual([ + new UnencryptedL2Log( + context.environment.address, + new EventSelector(5), + Buffer.concat(expectedFields.map(f => f.toBuffer())), + ), + new UnencryptedL2Log( + context.environment.address, + new EventSelector(8), + Buffer.concat(expectedString.map(f => f.toBuffer())), + ), + new UnencryptedL2Log(context.environment.address, new EventSelector(10), expectedCompressedString), + ]); + }); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('new_nullifier'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + it(`Emit note hash (should be traced)`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; - expect(results.reverted).toBe(false); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('new_note_hash'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); - }); + expect(results.reverted).toBe(false); - it(`Should execute contract function that checks if a nullifier exists (it does not)`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; + expect(context.persistableState.flush().newNoteHashes).toEqual([utxo]); + }); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('nullifier_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + it(`Emit nullifier (should be traced)`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('new_nullifier'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - // Nullifier existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(false); - }); + expect(results.reverted).toBe(false); - it(`Should execute contract function that checks if a nullifier exists (it does)`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; - - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - // nullifier exists! - jest - .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getNullifierIndex') - .mockReturnValue(Promise.resolve(BigInt(42))); - const bytecode = getAvmTestContractBytecode('nullifier_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=true*/ new Fr(1)]); - - // Nullifier existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(true); - }); + expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); + }); - it(`Should execute contract function that checks emits a nullifier and checks its existence`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; + it(`Nullifier exists (it does not)`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('emit_nullifier_and_check'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('nullifier_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(results.reverted).toBe(false); - // Nullifier existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.newNullifiers).toEqual([utxo]); - expect(trace.nullifierChecks.length).toEqual(1); - expect(trace.nullifierChecks[0].exists).toEqual(true); - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); - it(`Should execute contract function that emits same nullifier twice (should fail)`, async () => { - const utxo = new Fr(42); - const calldata = [utxo]; + // Nullifier existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.nullifierChecks.length).toEqual(1); + expect(trace.nullifierChecks[0].exists).toEqual(false); + }); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('nullifier_collision'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + it(`Nullifier exists (it does)`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; - expect(results.reverted).toBe(true); - // Only the first nullifier should be in the trace, second one failed to add - expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); - }); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + // nullifier exists! + jest + .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getNullifierIndex') + .mockReturnValue(Promise.resolve(BigInt(42))); + const bytecode = getAvmTestContractBytecode('nullifier_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=true*/ new Fr(1)]); + + // Nullifier existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.nullifierChecks.length).toEqual(1); + expect(trace.nullifierChecks[0].exists).toEqual(true); }); - describe('Test tree access functions from noir contract (l1ToL2 messages)', () => { - it(`Should execute contract function that checks if a message exists (it does not)`, async () => { - const msgHash = new Fr(42); - const leafIndex = new Fr(24); - const calldata = [msgHash, leafIndex]; + it(`Emits a nullifier and checks its existence`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - const bytecode = getAvmTestContractBytecode('l1_to_l2_msg_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('emit_nullifier_and_check'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); - // Message existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.l1ToL2MessageChecks.length).toEqual(1); - expect(trace.l1ToL2MessageChecks[0].exists).toEqual(false); - }); + expect(results.reverted).toBe(false); + // Nullifier existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.newNullifiers).toEqual([utxo]); + expect(trace.nullifierChecks.length).toEqual(1); + expect(trace.nullifierChecks[0].exists).toEqual(true); + }); - it(`Should execute contract function that checks if a message exists (it does)`, async () => { - const msgHash = new Fr(42); - const leafIndex = new Fr(24); - const calldata = [msgHash, leafIndex]; - - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getL1ToL2MembershipWitness') - .mockResolvedValue(initL1ToL2MessageOracleInput(leafIndex.toBigInt())); - const bytecode = getAvmTestContractBytecode('l1_to_l2_msg_exists'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*exists=false*/ new Fr(1)]); - // Message existence check should be in trace - const trace = context.persistableState.flush(); - expect(trace.l1ToL2MessageChecks.length).toEqual(1); - expect(trace.l1ToL2MessageChecks[0].exists).toEqual(true); - }); + it(`Emits same nullifier twice (should fail)`, async () => { + const utxo = new Fr(42); + const calldata = [utxo]; + + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('nullifier_collision'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(true); + // Only the first nullifier should be in the trace, second one failed to add + expect(context.persistableState.flush().newNullifiers).toEqual([utxo]); }); + }); - describe('Test nested external calls from noir contract', () => { - it(`Should execute contract function that makes a nested call`, async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const callBytecode = getAvmTestContractBytecode('raw_nested_call_to_add'); - const addBytecode = getAvmTestContractBytecode('add_args_return'); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + describe('Test tree access (l1ToL2 messages)', () => { + it(`Message exists (it does not)`, async () => { + const msgHash = new Fr(42); + const leafIndex = new Fr(24); + const calldata = [msgHash, leafIndex]; - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + const bytecode = getAvmTestContractBytecode('l1_to_l2_msg_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(3)]); - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=false*/ new Fr(0)]); + // Message existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.l1ToL2MessageChecks.length).toEqual(1); + expect(trace.l1ToL2MessageChecks[0].exists).toEqual(false); + }); - it(`Should execute contract function that makes a nested call through the old interface`, async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const callBytecode = getAvmTestContractBytecode('nested_call_to_add'); - const addBytecode = getAvmTestContractBytecode('add_args_return'); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + it(`Message exists (it does)`, async () => { + const msgHash = new Fr(42); + const leafIndex = new Fr(24); + const calldata = [msgHash, leafIndex]; - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.commitmentsDb, 'getL1ToL2MembershipWitness') + .mockResolvedValue(initL1ToL2MessageOracleInput(leafIndex.toBigInt())); + const bytecode = getAvmTestContractBytecode('l1_to_l2_msg_exists'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(3)]); - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*exists=false*/ new Fr(1)]); + // Message existence check should be in trace + const trace = context.persistableState.flush(); + expect(trace.l1ToL2MessageChecks.length).toEqual(1); + expect(trace.l1ToL2MessageChecks[0].exists).toEqual(true); + }); + }); - it(`Should execute contract function that makes a nested static call`, async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const callBytecode = getAvmTestContractBytecode('raw_nested_static_call_to_add'); - const addBytecode = getAvmTestContractBytecode('add_args_return'); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + describe('Nested external calls', () => { + it(`Nested call`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('raw_nested_call_to_add'); + const addBytecode = getAvmTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*result=*/ new Fr(3), /*success=*/ new Fr(1)]); - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); - it(`Should execute contract function that makes a nested static call which modifies storage`, async () => { - const callBytecode = getAvmTestContractBytecode('raw_nested_static_call_to_set_storage'); - const nestedBytecode = getAvmTestContractBytecode('set_storage_single'); - const context = initContext(); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + it(`Nested call through the old interface`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('nested_call_to_add'); + const addBytecode = getAvmTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); - expect(results.reverted).toBe(false); // The outer call should not revert. - expect(results.output).toEqual([new Fr(0)]); // The inner call should have reverted. - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); - it(`Should execute contract function that makes a nested static call (old interface)`, async () => { - const calldata: Fr[] = [new Fr(1), new Fr(2)]; - const callBytecode = getAvmTestContractBytecode('nested_static_call_to_add'); - const addBytecode = getAvmTestContractBytecode('add_args_return'); - const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(addBytecode)); + it(`Nested static call`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('raw_nested_static_call_to_add'); + const addBytecode = getAvmTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*result=*/ new Fr(3)]); - }); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*result=*/ new Fr(3), /*success=*/ new Fr(1)]); + }); - it(`Should execute contract function that makes a nested static call which modifies storage (old interface)`, async () => { - const callBytecode = getAvmTestContractBytecode('nested_static_call_to_set_storage'); - const nestedBytecode = getAvmTestContractBytecode('set_storage_single'); - const context = initContext(); - jest - .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - .mockReturnValueOnce(Promise.resolve(nestedBytecode)); + it(`Nested static call which modifies storage`, async () => { + const callBytecode = getAvmTestContractBytecode('raw_nested_static_call_to_set_storage'); + const nestedBytecode = getAvmTestContractBytecode('set_storage_single'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(nestedBytecode)); - const results = await new AvmSimulator(context).executeBytecode(callBytecode); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); - expect(results.reverted).toBe(true); // The outer call should revert. - }); + expect(results.reverted).toBe(false); // The outer call should not revert. + expect(results.output).toEqual([new Fr(0)]); // The inner call should have reverted. }); - describe('Storage accesses', () => { - it('Should set value in storage (single)', async () => { - const slot = 1n; - const address = AztecAddress.fromField(new Fr(420)); - const value = new Fr(88); - const calldata = [value]; + it(`Nested static call (old interface)`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmTestContractBytecode('nested_static_call_to_add'); + const addBytecode = getAvmTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(addBytecode)); - const context = initContext({ - env: initExecutionEnvironment({ calldata, address, storageAddress: address }), - }); - const bytecode = getAvmTestContractBytecode('set_storage_single'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); - expect(results.reverted).toBe(false); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*result=*/ new Fr(3)]); + }); - // World state - const worldState = context.persistableState.flush(); - const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; - const adminSlotValue = storageSlot.get(slot); - expect(adminSlotValue).toEqual(value); + it(`Nested static call which modifies storage (old interface)`, async () => { + const callBytecode = getAvmTestContractBytecode('nested_static_call_to_set_storage'); + const nestedBytecode = getAvmTestContractBytecode('set_storage_single'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValueOnce(Promise.resolve(nestedBytecode)); - // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - const slotTrace = storageTrace.get(slot); - expect(slotTrace).toEqual([value]); - }); + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(true); // The outer call should revert. + }); + }); + + describe('Storage accesses', () => { + it('Should set value in storage (single)', async () => { + const slot = 1n; + const address = AztecAddress.fromField(new Fr(420)); + const value = new Fr(88); + const calldata = [value]; - it('Should read value in storage (single)', async () => { - const slot = 1n; - const value = new Fr(12345); - const address = AztecAddress.fromField(new Fr(420)); - const storage = new Map([[slot, value]]); - - const context = initContext({ - env: initExecutionEnvironment({ storageAddress: address }), - }); - jest - .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') - .mockImplementation((_address, slot) => Promise.resolve(storage.get(slot.toBigInt())!)); - const bytecode = getAvmTestContractBytecode('read_storage_single'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - // Get contract function artifact - expect(results.reverted).toBe(false); - expect(results.output).toEqual([value]); - - // Tracing - const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - const slotTrace = storageTrace.get(slot); - expect(slotTrace).toEqual([value]); + const context = initContext({ + env: initExecutionEnvironment({ calldata, address, storageAddress: address }), }); + const bytecode = getAvmTestContractBytecode('set_storage_single'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + + // World state + const worldState = context.persistableState.flush(); + const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; + const adminSlotValue = storageSlot.get(slot); + expect(adminSlotValue).toEqual(value); + + // Tracing + const storageTrace = worldState.storageWrites.get(address.toBigInt())!; + const slotTrace = storageTrace.get(slot); + expect(slotTrace).toEqual([value]); + }); + + it('Should read value in storage (single)', async () => { + const slot = 1n; + const value = new Fr(12345); + const address = AztecAddress.fromField(new Fr(420)); + const storage = new Map([[slot, value]]); - it('Should set and read a value from storage (single)', async () => { - const slot = 1n; - const value = new Fr(12345); - const address = AztecAddress.fromField(new Fr(420)); - const calldata = [value]; - - const context = initContext({ - env: initExecutionEnvironment({ calldata, address, storageAddress: address }), - }); - const bytecode = getAvmTestContractBytecode('set_read_storage_single'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([value]); - - // Test read trace - const worldState = context.persistableState.flush(); - const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; - const slotReadTrace = storageReadTrace.get(slot); - expect(slotReadTrace).toEqual([value]); - - // Test write trace - const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; - const slotWriteTrace = storageWriteTrace.get(slot); - expect(slotWriteTrace).toEqual([value]); + const context = initContext({ + env: initExecutionEnvironment({ storageAddress: address }), }); + jest + .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') + .mockImplementation((_address, slot) => Promise.resolve(storage.get(slot.toBigInt())!)); + const bytecode = getAvmTestContractBytecode('read_storage_single'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + // Get contract function artifact + expect(results.reverted).toBe(false); + expect(results.output).toEqual([value]); + + // Tracing + const worldState = context.persistableState.flush(); + const storageTrace = worldState.storageReads.get(address.toBigInt())!; + const slotTrace = storageTrace.get(slot); + expect(slotTrace).toEqual([value]); + }); + + it('Should set and read a value from storage (single)', async () => { + const slot = 1n; + const value = new Fr(12345); + const address = AztecAddress.fromField(new Fr(420)); + const calldata = [value]; - it('Should set a value in storage (list)', async () => { - const slot = 2n; - const sender = AztecAddress.fromField(new Fr(1)); - const address = AztecAddress.fromField(new Fr(420)); - const calldata = [new Fr(1), new Fr(2)]; - - const context = initContext({ - env: initExecutionEnvironment({ sender, address, calldata, storageAddress: address }), - }); - const bytecode = getAvmTestContractBytecode('set_storage_list'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - - const worldState = context.persistableState.flush(); - const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; - expect(storageSlot.get(slot)).toEqual(calldata[0]); - expect(storageSlot.get(slot + 1n)).toEqual(calldata[1]); - - // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageTrace.get(slot)).toEqual([calldata[0]]); - expect(storageTrace.get(slot + 1n)).toEqual([calldata[1]]); + const context = initContext({ + env: initExecutionEnvironment({ calldata, address, storageAddress: address }), }); + const bytecode = getAvmTestContractBytecode('set_read_storage_single'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([value]); + + // Test read trace + const worldState = context.persistableState.flush(); + const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; + const slotReadTrace = storageReadTrace.get(slot); + expect(slotReadTrace).toEqual([value]); + + // Test write trace + const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; + const slotWriteTrace = storageWriteTrace.get(slot); + expect(slotWriteTrace).toEqual([value]); + }); + + it('Should set a value in storage (list)', async () => { + const slot = 2n; + const sender = AztecAddress.fromField(new Fr(1)); + const address = AztecAddress.fromField(new Fr(420)); + const calldata = [new Fr(1), new Fr(2)]; - it('Should read a value in storage (list)', async () => { - const slot = 2n; - const address = AztecAddress.fromField(new Fr(420)); - const values = [new Fr(1), new Fr(2)]; - const storage = new Map([ - [slot, values[0]], - [slot + 1n, values[1]], - ]); - - const context = initContext({ - env: initExecutionEnvironment({ address, storageAddress: address }), - }); - jest - .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') - .mockImplementation((_address, slot) => Promise.resolve(storage.get(slot.toBigInt())!)); - const bytecode = getAvmTestContractBytecode('read_storage_list'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual(values); - - // Tracing - const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - expect(storageTrace.get(slot)).toEqual([values[0]]); - expect(storageTrace.get(slot + 1n)).toEqual([values[1]]); + const context = initContext({ + env: initExecutionEnvironment({ sender, address, calldata, storageAddress: address }), }); + const bytecode = getAvmTestContractBytecode('set_storage_list'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - it('Should set a value in storage (map)', async () => { - const address = AztecAddress.fromField(new Fr(420)); - const value = new Fr(12345); - const calldata = [address.toField(), value]; + expect(results.reverted).toBe(false); + + const worldState = context.persistableState.flush(); + const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; + expect(storageSlot.get(slot)).toEqual(calldata[0]); + expect(storageSlot.get(slot + 1n)).toEqual(calldata[1]); + + // Tracing + const storageTrace = worldState.storageWrites.get(address.toBigInt())!; + expect(storageTrace.get(slot)).toEqual([calldata[0]]); + expect(storageTrace.get(slot + 1n)).toEqual([calldata[1]]); + }); + + it('Should read a value in storage (list)', async () => { + const slot = 2n; + const address = AztecAddress.fromField(new Fr(420)); + const values = [new Fr(1), new Fr(2)]; + const storage = new Map([ + [slot, values[0]], + [slot + 1n, values[1]], + ]); + + const context = initContext({ + env: initExecutionEnvironment({ address, storageAddress: address }), + }); + jest + .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') + .mockImplementation((_address, slot) => Promise.resolve(storage.get(slot.toBigInt())!)); + const bytecode = getAvmTestContractBytecode('read_storage_list'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); - const context = initContext({ - env: initExecutionEnvironment({ address, calldata, storageAddress: address }), - }); - const bytecode = getAvmTestContractBytecode('set_storage_map'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); + expect(results.reverted).toBe(false); + expect(results.output).toEqual(values); - expect(results.reverted).toBe(false); - // returns the storage slot for modified key - const slotNumber = results.output[0].toBigInt(); + // Tracing + const worldState = context.persistableState.flush(); + const storageTrace = worldState.storageReads.get(address.toBigInt())!; + expect(storageTrace.get(slot)).toEqual([values[0]]); + expect(storageTrace.get(slot + 1n)).toEqual([values[1]]); + }); - const worldState = context.persistableState.flush(); - const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; - expect(storageSlot.get(slotNumber)).toEqual(value); + it('Should set a value in storage (map)', async () => { + const address = AztecAddress.fromField(new Fr(420)); + const value = new Fr(12345); + const calldata = [address.toField(), value]; - // Tracing - const storageTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageTrace.get(slotNumber)).toEqual([value]); + const context = initContext({ + env: initExecutionEnvironment({ address, calldata, storageAddress: address }), }); + const bytecode = getAvmTestContractBytecode('set_storage_map'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + // returns the storage slot for modified key + const slotNumber = results.output[0].toBigInt(); + + const worldState = context.persistableState.flush(); + const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; + expect(storageSlot.get(slotNumber)).toEqual(value); - it('Should read-add-set a value in storage (map)', async () => { - const address = AztecAddress.fromField(new Fr(420)); - const value = new Fr(12345); - const calldata = [address.toField(), value]; - - const context = initContext({ - env: initExecutionEnvironment({ address, calldata, storageAddress: address }), - }); - const bytecode = getAvmTestContractBytecode('add_storage_map'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - expect(results.reverted).toBe(false); - // returns the storage slot for modified key - const slotNumber = results.output[0].toBigInt(); - - const worldState = context.persistableState.flush(); - const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; - expect(storageSlot.get(slotNumber)).toEqual(value); - - // Tracing - const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; - expect(storageReadTrace.get(slotNumber)).toEqual([new Fr(0)]); - const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; - expect(storageWriteTrace.get(slotNumber)).toEqual([value]); + // Tracing + const storageTrace = worldState.storageWrites.get(address.toBigInt())!; + expect(storageTrace.get(slotNumber)).toEqual([value]); + }); + + it('Should read-add-set a value in storage (map)', async () => { + const address = AztecAddress.fromField(new Fr(420)); + const value = new Fr(12345); + const calldata = [address.toField(), value]; + + const context = initContext({ + env: initExecutionEnvironment({ address, calldata, storageAddress: address }), }); + const bytecode = getAvmTestContractBytecode('add_storage_map'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + expect(results.reverted).toBe(false); + // returns the storage slot for modified key + const slotNumber = results.output[0].toBigInt(); + + const worldState = context.persistableState.flush(); + const storageSlot = worldState.currentStorageValue.get(address.toBigInt())!; + expect(storageSlot.get(slotNumber)).toEqual(value); + + // Tracing + const storageReadTrace = worldState.storageReads.get(address.toBigInt())!; + expect(storageReadTrace.get(slotNumber)).toEqual([new Fr(0)]); + const storageWriteTrace = worldState.storageWrites.get(address.toBigInt())!; + expect(storageWriteTrace.get(slotNumber)).toEqual([value]); + }); + + it('Should read value in storage (map)', async () => { + const value = new Fr(12345); + const address = AztecAddress.fromField(new Fr(420)); + const calldata = [address.toField()]; - it('Should read value in storage (map)', async () => { - const value = new Fr(12345); - const address = AztecAddress.fromField(new Fr(420)); - const calldata = [address.toField()]; - - const context = initContext({ - env: initExecutionEnvironment({ calldata, address, storageAddress: address }), - }); - jest - .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') - .mockReturnValue(Promise.resolve(value)); - const bytecode = getAvmTestContractBytecode('read_storage_map'); - const results = await new AvmSimulator(context).executeBytecode(bytecode); - - // Get contract function artifact - expect(results.reverted).toBe(false); - expect(results.output).toEqual([value]); - - // Tracing - const worldState = context.persistableState.flush(); - const storageTrace = worldState.storageReads.get(address.toBigInt())!; - expect([...storageTrace.values()]).toEqual([[value]]); + const context = initContext({ + env: initExecutionEnvironment({ calldata, address, storageAddress: address }), }); + jest + .spyOn(context.persistableState.hostStorage.publicStateDb, 'storageRead') + .mockReturnValue(Promise.resolve(value)); + const bytecode = getAvmTestContractBytecode('read_storage_map'); + const results = await new AvmSimulator(context).executeBytecode(bytecode); + + // Get contract function artifact + expect(results.reverted).toBe(false); + expect(results.output).toEqual([value]); + + // Tracing + const worldState = context.persistableState.flush(); + const storageTrace = worldState.storageReads.get(address.toBigInt())!; + expect([...storageTrace.values()]).toEqual([[value]]); }); }); }); diff --git a/yarn-project/simulator/src/avm/fixtures/index.ts b/yarn-project/simulator/src/avm/fixtures/index.ts index 14db8d632a3..2e75d31be4c 100644 --- a/yarn-project/simulator/src/avm/fixtures/index.ts +++ b/yarn-project/simulator/src/avm/fixtures/index.ts @@ -10,7 +10,7 @@ import merge from 'lodash.merge'; import { CommitmentsDB, MessageLoadOracleInputs, PublicContractsDB, PublicStateDB } from '../../index.js'; import { AvmContext } from '../avm_context.js'; -import { AvmExecutionEnvironment } from '../avm_execution_environment.js'; +import { AvmContextInputs, AvmExecutionEnvironment } from '../avm_execution_environment.js'; import { AvmMachineState } from '../avm_machine_state.js'; import { HostStorage } from '../journal/host_storage.js'; import { AvmPersistableStateManager } from '../journal/journal.js'; @@ -113,3 +113,19 @@ export function initL1ToL2MessageOracleInput( new SiblingPath(L1_TO_L2_MSG_TREE_HEIGHT, Array(L1_TO_L2_MSG_TREE_HEIGHT)), ); } + +/** + * Adjust the user index to account for the AvmContextInputs size. + * This is a hack for testing, and should go away once AvmContextInputs themselves go away. + */ +export function adjustCalldataIndex(userIndex: number): number { + return userIndex + AvmContextInputs.SIZE; +} + +export function anyAvmContextInputs() { + const tv = []; + for (let i = 0; i < AvmContextInputs.SIZE; i++) { + tv.push(expect.any(Fr)); + } + return tv; +} diff --git a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts index 1df77f9d0a6..d6112104d45 100644 --- a/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/external_calls.test.ts @@ -6,7 +6,7 @@ import { mock } from 'jest-mock-extended'; import { CommitmentsDB, PublicContractsDB, PublicStateDB } from '../../index.js'; import { AvmContext } from '../avm_context.js'; import { Field, Uint8 } from '../avm_memory_types.js'; -import { initContext } from '../fixtures/index.js'; +import { adjustCalldataIndex, initContext } from '../fixtures/index.js'; import { HostStorage } from '../journal/host_storage.js'; import { AvmPersistableStateManager } from '../journal/journal.js'; import { encodeToBytecode } from '../serialization/bytecode_serialization.js'; @@ -70,7 +70,12 @@ describe('External Calls', () => { const retSize = 2; const successOffset = 7; const otherContextInstructionsBytecode = encodeToBytecode([ - new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0), + new CalldataCopy( + /*indirect=*/ 0, + /*csOffset=*/ adjustCalldataIndex(0), + /*copySize=*/ argsSize, + /*dstOffset=*/ 0, + ), new SStore(/*indirect=*/ 0, /*srcOffset=*/ 0, /*size=*/ 1, /*slotOffset=*/ 0), new Return(/*indirect=*/ 0, /*retOffset=*/ 0, /*size=*/ 2), ]); @@ -162,7 +167,12 @@ describe('External Calls', () => { context.machineState.memory.setSlice(2, args); const otherContextInstructions: Instruction[] = [ - new CalldataCopy(/*indirect=*/ 0, /*csOffset=*/ 0, /*copySize=*/ argsSize, /*dstOffset=*/ 0), + new CalldataCopy( + /*indirect=*/ 0, + /*csOffset=*/ adjustCalldataIndex(0), + /*copySize=*/ argsSize, + /*dstOffset=*/ 0, + ), new SStore(/*indirect=*/ 0, /*srcOffset=*/ 1, /*size=*/ 1, /*slotOffset=*/ 0), ]; diff --git a/yarn-project/simulator/src/avm/opcodes/memory.test.ts b/yarn-project/simulator/src/avm/opcodes/memory.test.ts index d7b691435a1..0b8102f98c7 100644 --- a/yarn-project/simulator/src/avm/opcodes/memory.test.ts +++ b/yarn-project/simulator/src/avm/opcodes/memory.test.ts @@ -3,7 +3,7 @@ import { Fr } from '@aztec/foundation/fields'; import { AvmContext } from '../avm_context.js'; import { Field, TypeTag, Uint8, Uint16, Uint32, Uint64, Uint128 } from '../avm_memory_types.js'; import { InstructionExecutionError } from '../errors.js'; -import { initContext, initExecutionEnvironment } from '../fixtures/index.js'; +import { adjustCalldataIndex, initContext, initExecutionEnvironment } from '../fixtures/index.js'; import { Addressing, AddressingMode } from './addressing_mode.js'; import { CMov, CalldataCopy, Cast, Mov, Set } from './memory.js'; @@ -435,7 +435,12 @@ describe('Memory instructions', () => { context = initContext({ env: initExecutionEnvironment({ calldata }) }); context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 0, /*dstOffset=*/ 0).execute(context); + await new CalldataCopy( + /*indirect=*/ 0, + /*cdOffset=*/ adjustCalldataIndex(0), + /*copySize=*/ 0, + /*dstOffset=*/ 0, + ).execute(context); const actual = context.machineState.memory.get(0); expect(actual).toEqual(new Uint16(12)); @@ -446,7 +451,12 @@ describe('Memory instructions', () => { context = initContext({ env: initExecutionEnvironment({ calldata }) }); context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 0, /*copySize=*/ 3, /*dstOffset=*/ 0).execute(context); + await new CalldataCopy( + /*indirect=*/ 0, + /*cdOffset=*/ adjustCalldataIndex(0), + /*copySize=*/ 3, + /*dstOffset=*/ 0, + ).execute(context); const actual = context.machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 3); expect(actual).toEqual([new Field(1), new Field(2), new Field(3)]); @@ -457,7 +467,12 @@ describe('Memory instructions', () => { context = initContext({ env: initExecutionEnvironment({ calldata }) }); context.machineState.memory.set(0, new Uint16(12)); // Some previous data to be overwritten - await new CalldataCopy(/*indirect=*/ 0, /*cdOffset=*/ 1, /*copySize=*/ 2, /*dstOffset=*/ 0).execute(context); + await new CalldataCopy( + /*indirect=*/ 0, + /*cdOffset=*/ adjustCalldataIndex(1), + /*copySize=*/ 2, + /*dstOffset=*/ 0, + ).execute(context); const actual = context.machineState.memory.getSlice(/*offset=*/ 0, /*size=*/ 2); expect(actual).toEqual([new Field(2), new Field(3)]);