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 75bb75ff294..f318253d9ce 100644 --- a/noir-projects/aztec-nr/aztec/src/context/avm_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/avm_context.nr @@ -325,4 +325,4 @@ fn call_static( // TODO(5110): consider passing in calldata directly temporary_function_selector: Field ) -> ([Field; RET_SIZE], u8) {} -// ^ return data ^ success \ No newline at end of file +// ^ return data ^ success diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 7fc9a841cf2..73e8bb9295e 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -4,6 +4,8 @@ members = [ "contracts/auth_contract", "contracts/avm_initializer_test_contract", "contracts/avm_test_contract", + "contracts/avm_nested_calls_test_contract", + "contracts/avm_acvm_interop_test_contract", "contracts/fpc_contract", "contracts/benchmarking_contract", "contracts/card_game_contract", diff --git a/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/Nargo.toml new file mode 100644 index 00000000000..87e26e0aaa2 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "avm_acvm_interop_test_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } +authwit = { path = "../../../aztec-nr/authwit" } diff --git a/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/src/main.nr new file mode 100644 index 00000000000..c1af41666e9 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_acvm_interop_test_contract/src/main.nr @@ -0,0 +1,72 @@ +use dep::aztec::protocol_types::traits::{Serialize, Deserialize}; + +contract AvmAcvmInteropTest { + // Libs + use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::context::gas::GasOpts; + use dep::aztec::context::public_context::FunctionReturns; + use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; + use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; + + // Use the standard context interface to emit a new nullifier + #[aztec(public-vm)] + fn new_nullifier(nullifier: Field) { + context.push_new_nullifier(nullifier, 0); + } + + /************************************************************************ + * ACVM interoperability + ************************************************************************/ + #[aztec(public)] + fn constant_field_acvm() -> pub Field { + 123456 + } + + #[aztec(public-vm)] + fn constant_field_avm() -> pub Field { + 123456 + } + + #[aztec(public)] + fn new_nullifier_acvm(nullifier: Field) -> pub Field { + context.push_new_nullifier(nullifier, 0); + } + + #[aztec(public)] + fn assert_unsiloed_nullifier_acvm(unsiloed_nullifier: Field) { + assert(context.nullifier_exists(unsiloed_nullifier, context.this_address())); + } + + #[aztec(public-vm)] + fn call_acvm_from_avm() -> pub Field { + context.call_public_function( + context.this_address(), + FunctionSelector::from_signature("constant_field_acvm()"), + [], + GasOpts::default() + ).deserialize_into() + } + + #[aztec(public)] + fn call_avm_from_acvm() -> pub Field { + context.call_public_function( + context.this_address(), + FunctionSelector::from_signature("constant_field_avm()"), + [], + GasOpts::default() + ).deserialize_into() + } + + #[aztec(public-vm)] + fn avm_to_acvm_call(selector: FunctionSelector, args: Field) { + context.call_public_function(context.this_address(), selector, [args], GasOpts::default()).assert_empty(); + } + + /************************************************************************ + * Authwit functions + ************************************************************************/ + #[aztec(public-vm)] + fn test_authwit_send_money(from: AztecAddress, _to: AztecAddress, _amount: Field) { + assert_current_call_valid_authwit_public(&mut context, from); + } +} diff --git a/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/Nargo.toml new file mode 100644 index 00000000000..ff0d26a2ce9 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/Nargo.toml @@ -0,0 +1,8 @@ +[package] +name = "avm_nested_calls_test_contract" +authors = [""] +compiler_version = ">=0.25.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } diff --git a/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/src/main.nr new file mode 100644 index 00000000000..4c056f838de --- /dev/null +++ b/noir-projects/noir-contracts/contracts/avm_nested_calls_test_contract/src/main.nr @@ -0,0 +1,164 @@ +use dep::aztec::protocol_types::traits::{Serialize, Deserialize}; + +contract AvmNestedCallsTest { + // Libs + use dep::aztec::state_vars::PublicMutable; + use dep::aztec::protocol_types::address::AztecAddress; + use dep::aztec::context::gas::GasOpts; + use dep::aztec::context::public_context::FunctionReturns; + use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; + + #[aztec(storage)] + struct Storage { + single: PublicMutable, + } + + /************************************************************************ + * Storage + ************************************************************************/ + #[aztec(public-vm)] + fn set_storage_single(a: Field) { + storage.single.write(a); + } + + #[aztec(public-vm)] + fn add_args_return(arg_a: Field, arg_b: Field) -> pub Field { + arg_a + arg_b + } + + // Use the standard context interface to emit a new nullifier + #[aztec(public-vm)] + fn new_nullifier(nullifier: Field) { + context.push_new_nullifier(nullifier, 0); + } + + // Directly call the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn raw_nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { + let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); + + // Nested call + let results = context.call_public_function_raw( + GasOpts::default(), + context.this_address(), + selector, + [arg_a, arg_b] + ); + let data_to_return: [Field; 1] = results.0; + // this explicit size is necessary to ensure that ret_size is compile-time + // (ensure the data_to_return is in a HeapArray not a HeapVector) + let success: u8 = results.1; + + assert(success == 1, "Call failed"); + + let add_result = data_to_return[0]; + add_result + } + + // Directly call the external call opcode to initiate a nested call to the add function with user-specified gas + #[aztec(public-vm)] + fn raw_nested_call_to_add_with_gas( + arg_a: Field, + arg_b: Field, + l1_gas: Field, + l2_gas: Field, + da_gas: Field + ) -> pub Field { + let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); + + // Nested call + let results = context.call_public_function_raw( + GasOpts::new(l1_gas, l2_gas, da_gas), + context.this_address(), + selector, + [arg_a, arg_b] + ); + let data_to_return: [Field; 1] = results.0; + // this explicit size is necessary to ensure that ret_size is compile-time + // (ensure the data_to_return is in a HeapArray not a HeapVector) + let _success: u8 = results.1; + + let add_result = data_to_return[0]; + add_result + } + + // Use the `call_public_function` wrapper to initiate a nested call to the add function + #[aztec(public-vm)] + fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { + let selector = FunctionSelector::from_signature("add_args_return(Field,Field)"); + + // Nested call using standard context interface function + context.call_public_function( + context.this_address(), + selector, + [arg_a, arg_b], + GasOpts::default() + ).deserialize_into() + } + + // Directly call_static the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn raw_nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub (Field, u8) { + let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); + + let (result_data, success): ([Field; 1], u8) = context.static_call_public_function_raw( + GasOpts::default(), + context.this_address(), + selector, + [arg_a, arg_b] + ); + + (result_data[0], success) + } + + // Directly call_static `set_storage_single`. Should fail since it's accessing storage. + #[aztec(public-vm)] + fn raw_nested_static_call_to_set_storage() -> pub u8 { + let selector = FunctionSelector::from_signature("set_storage_single(Field)").to_field(); + let calldata: [Field; 1] = [20]; + + let (_data_to_return, success): ([Field; 0], u8) = context.static_call_public_function_raw(GasOpts::default(), context.this_address(), selector, calldata); + + success + } + + // Indirectly call_static the external call opcode to initiate a nested call to the add function + #[aztec(public-vm)] + fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { + let selector = FunctionSelector::from_signature("add_args_return(Field,Field)"); + + context.static_call_public_function( + context.this_address(), + selector, + [arg_a, arg_b], + GasOpts::default() + ).deserialize_into() + } + + // Indirectly call_static `set_storage_single`. Should revert since it's accessing storage. + #[aztec(public-vm)] + fn nested_static_call_to_set_storage() { + let selector = FunctionSelector::from_signature("set_storage_single(Field)"); + let calldata: [Field; 1] = [20]; + + context.static_call_public_function(context.this_address(), selector, calldata, GasOpts::default()).assert_empty(); + } + + #[aztec(public-vm)] + fn create_same_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { + context.push_new_nullifier(nullifier, 0); + + let selector = FunctionSelector::from_signature("new_nullifier(Field)"); + let calldata: [Field; 1] = [nullifier]; + let _ : FunctionReturns<0> = context.call_public_function(nestedAddress, selector, calldata, GasOpts::default()); + } + + #[aztec(public-vm)] + fn create_different_nullifier_in_nested_call(nestedAddress: AztecAddress, nullifier: Field) { + context.push_new_nullifier(nullifier, 0); + + let selector = FunctionSelector::from_signature("new_nullifier(Field)"); + let calldata: [Field; 1] = [nullifier + 1]; + let _ : FunctionReturns<0> = context.call_public_function(nestedAddress, selector, calldata, GasOpts::default()); + } +} diff --git a/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml index dff79424f9e..c7b0599cbe7 100644 --- a/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/avm_test_contract/Nargo.toml @@ -7,4 +7,3 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } compressed_string = { path = "../../../aztec-nr/compressed-string" } -authwit = { path = "../../../aztec-nr/authwit" } 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 da6e9a1b0f1..22b28eaae18 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 @@ -26,17 +26,10 @@ contract AvmTest { // Libs use dep::aztec::prelude::{Map, Deserialize}; use dep::aztec::state_vars::PublicMutable; - use dep::aztec::history::nullifier_inclusion::prove_nullifier_inclusion; - use dep::aztec::protocol_types::{ - address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH, - contract_instance::ContractInstance, hash::silo_nullifier - }; - use dep::aztec::context::gas::GasOpts; + use dep::aztec::protocol_types::{address::{AztecAddress, EthAddress}, constants::L1_TO_L2_MESSAGE_LENGTH}; use dep::aztec::oracle::get_contract_instance::{get_contract_instance_avm, get_contract_instance_internal_avm}; use dep::aztec::protocol_types::abis::function_selector::FunctionSelector; - use dep::aztec::protocol_types::traits::ToField; use dep::compressed_string::CompressedString; - use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; // avm lib use dep::aztec::avm::hash::{keccak256, poseidon, sha256}; @@ -176,54 +169,6 @@ contract AvmTest { dep::std::hash::pedersen_hash_with_separator(data, 20) } - /************************************************************************ - * ACVM interoperability - ************************************************************************/ - #[aztec(public)] - fn constant_field_acvm() -> pub Field { - 123456 - } - - #[aztec(public-vm)] - fn constant_field_avm() -> pub Field { - 123456 - } - - #[aztec(public)] - fn new_nullifier_acvm(nullifier: Field) -> pub Field { - context.push_new_nullifier(nullifier, 0); - } - - #[aztec(public)] - fn assert_unsiloed_nullifier_acvm(unsiloed_nullifier: Field) { - assert(context.nullifier_exists(unsiloed_nullifier, context.this_address())); - } - - #[aztec(public-vm)] - fn call_acvm_from_avm() -> pub Field { - context.call_public_function( - context.this_address(), - FunctionSelector::from_signature("constant_field_acvm()"), - [], - GasOpts::default() - ).deserialize_into() - } - - #[aztec(public)] - fn call_avm_from_acvm() -> pub Field { - context.call_public_function( - context.this_address(), - FunctionSelector::from_signature("constant_field_avm()"), - [], - GasOpts::default() - ).deserialize_into() - } - - #[aztec(public-vm)] - fn avm_to_acvm_call(selector: FunctionSelector, args: Field) { - context.call_public_function(context.this_address(), selector, [args], GasOpts::default()).assert_empty(); - } - /************************************************************************ * Contract instance ************************************************************************/ @@ -246,14 +191,6 @@ contract AvmTest { assert(ci.is_some()); } - /************************************************************************ - * Authwit functions - ************************************************************************/ - #[aztec(public-vm)] - fn test_authwit_send_money(from: AztecAddress, _to: AztecAddress, _amount: Field) { - assert_current_call_valid_authwit_public(&mut context, from); - } - /************************************************************************ * AvmContext functions ************************************************************************/ @@ -393,116 +330,4 @@ contract AvmTest { fn send_l2_to_l1_msg(recipient: EthAddress, content: Field) { context.message_portal(recipient, content) } - - // Directly call the external call opcode to initiate a nested call to the add function - #[aztec(public-vm)] - fn raw_nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { - let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); - - // Nested call - let results = context.call_public_function_raw( - GasOpts::default(), - context.this_address(), - selector, - [arg_a, arg_b] - ); - let data_to_return: [Field; 1] = results.0; - // this explicit size is necessary to ensure that ret_size is compile-time - // (ensure the data_to_return is in a HeapArray not a HeapVector) - let success: u8 = results.1; - - assert(success == 1, "Call failed"); - - let add_result = data_to_return[0]; - add_result - } - - // Directly call the external call opcode to initiate a nested call to the add function with user-specified gas - #[aztec(public-vm)] - fn raw_nested_call_to_add_with_gas( - arg_a: Field, - arg_b: Field, - l1_gas: Field, - l2_gas: Field, - da_gas: Field - ) -> pub Field { - let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); - - // Nested call - let results = context.call_public_function_raw( - GasOpts::new(l1_gas, l2_gas, da_gas), - context.this_address(), - selector, - [arg_a, arg_b] - ); - let data_to_return: [Field; 1] = results.0; - // this explicit size is necessary to ensure that ret_size is compile-time - // (ensure the data_to_return is in a HeapArray not a HeapVector) - let success: u8 = results.1; - - let add_result = data_to_return[0]; - add_result - } - - // Use the `call_public_function` wrapper to initiate a nested call to the add function - #[aztec(public-vm)] - fn nested_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { - let selector = FunctionSelector::from_signature("add_args_return(Field,Field)"); - - // Nested call using standard context interface function - context.call_public_function( - context.this_address(), - selector, - [arg_a, arg_b], - GasOpts::default() - ).deserialize_into() - } - - // Directly call_static the external call opcode to initiate a nested call to the add function - #[aztec(public-vm)] - fn raw_nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub (Field, u8) { - let selector = FunctionSelector::from_signature("add_args_return(Field,Field)").to_field(); - - let (result_data, success): ([Field; 1], u8) = context.static_call_public_function_raw( - GasOpts::default(), - context.this_address(), - selector, - [arg_a, arg_b] - ); - - (result_data[0], success) - } - - // Directly call_static `set_storage_single`. Should fail since it's accessing storage. - #[aztec(public-vm)] - fn raw_nested_static_call_to_set_storage() -> pub u8 { - let selector = FunctionSelector::from_signature("set_storage_single(Field)").to_field(); - let calldata: [Field; 1] = [20]; - - let (_data_to_return, success): ([Field; 0], u8) = context.static_call_public_function_raw(GasOpts::default(), context.this_address(), selector, calldata); - - success - } - - // Indirectly call_static the external call opcode to initiate a nested call to the add function - #[aztec(public-vm)] - fn nested_static_call_to_add(arg_a: Field, arg_b: Field) -> pub Field { - let selector = FunctionSelector::from_signature("add_args_return(Field,Field)"); - - context.static_call_public_function( - context.this_address(), - selector, - [arg_a, arg_b], - GasOpts::default() - ).deserialize_into() - } - - // Indirectly call_static `set_storage_single`. Should revert since it's accessing storage. - #[aztec(public-vm)] - fn nested_static_call_to_set_storage() { - let selector = FunctionSelector::from_signature("set_storage_single(Field)"); - let calldata: [Field; 1] = [20]; - - context.static_call_public_function(context.this_address(), selector, calldata, GasOpts::default()).assert_empty(); - } } diff --git a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts index 29f22441a57..3de9f8a1b0b 100644 --- a/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts +++ b/yarn-project/end-to-end/src/e2e_avm_simulator.test.ts @@ -1,5 +1,10 @@ import { type AccountWallet, AztecAddress, Fr, FunctionSelector, TxStatus } from '@aztec/aztec.js'; -import { AvmInitializerTestContract, AvmTestContract } from '@aztec/noir-contracts.js'; +import { + AvmAcvmInteropTestContract, + AvmInitializerTestContract, + AvmNestedCallsTestContract, + AvmTestContract, +} from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; @@ -65,81 +70,127 @@ describe('e2e_avm_simulator', () => { expect(tx.status).toEqual(TxStatus.MINED); }); }); + }); - describe('ACVM interoperability', () => { - it('Can execute ACVM function among AVM functions', async () => { - expect(await avmContract.methods.constant_field_acvm().simulate()).toEqual([123456n]); - }); + describe('ACVM interoperability', () => { + let avmContract: AvmAcvmInteropTestContract; - it('Can call AVM function from ACVM', async () => { - expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual([123456n]); - }); + beforeEach(async () => { + avmContract = await AvmAcvmInteropTestContract.deploy(wallet).send().deployed(); + }, 50_000); - it('Can call ACVM function from AVM', async () => { - expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual([123456n]); - }); + it('Can execute ACVM function among AVM functions', async () => { + expect(await avmContract.methods.constant_field_acvm().simulate()).toEqual([123456n]); + }); - it('AVM sees settled nullifiers by ACVM', async () => { - const nullifier = new Fr(123456); - await avmContract.methods.new_nullifier(nullifier).send().wait(); - await avmContract.methods.assert_unsiloed_nullifier_acvm(nullifier).send().wait(); - }); + it('Can call AVM function from ACVM', async () => { + expect(await avmContract.methods.call_avm_from_acvm().simulate()).toEqual([123456n]); + }); - it('AVM nested call to ACVM sees settled nullifiers', async () => { - const nullifier = new Fr(123456); - await avmContract.methods.new_nullifier(nullifier).send().wait(); - await avmContract.methods - .avm_to_acvm_call(FunctionSelector.fromSignature('assert_unsiloed_nullifier_acvm(Field)'), nullifier) + it('Can call ACVM function from AVM', async () => { + expect(await avmContract.methods.call_acvm_from_avm().simulate()).toEqual([123456n]); + }); + + it('AVM sees settled nullifiers by ACVM', async () => { + const nullifier = new Fr(123456); + await avmContract.methods.new_nullifier(nullifier).send().wait(); + await avmContract.methods.assert_unsiloed_nullifier_acvm(nullifier).send().wait(); + }); + + it('AVM nested call to ACVM sees settled nullifiers', async () => { + const nullifier = new Fr(123456); + await avmContract.methods.new_nullifier(nullifier).send().wait(); + await avmContract.methods + .avm_to_acvm_call(FunctionSelector.fromSignature('assert_unsiloed_nullifier_acvm(Field)'), nullifier) + .send() + .wait(); + }); + describe('Authwit', () => { + it('Works if authwit provided', async () => { + const recipient = AztecAddress.random(); + const action = avmContract.methods.test_authwit_send_money( + /*from=*/ wallet.getCompleteAddress(), + recipient, + 100, + ); + let tx = await wallet + .setPublicAuthWit({ caller: wallet.getCompleteAddress().address, action }, /*authorized=*/ true) .send() .wait(); - // }); - }); + expect(tx.status).toEqual(TxStatus.MINED); - describe('Authwit', () => { - it('Works if authwit provided', async () => { - const recipient = AztecAddress.random(); - const action = avmContract.methods.test_authwit_send_money( - /*from=*/ wallet.getCompleteAddress(), - recipient, - 100, - ); - let tx = await wallet - .setPublicAuthWit({ caller: wallet.getCompleteAddress().address, action }, /*authorized=*/ true) - .send() - .wait(); - expect(tx.status).toEqual(TxStatus.MINED); - - tx = await avmContract.methods - .test_authwit_send_money(/*from=*/ wallet.getCompleteAddress(), recipient, 100) - .send() - .wait(); - expect(tx.status).toEqual(TxStatus.MINED); - }); + tx = await avmContract.methods + .test_authwit_send_money(/*from=*/ wallet.getCompleteAddress(), recipient, 100) + .send() + .wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); - it('Fails if authwit not provided', async () => { - await expect( - async () => - await avmContract.methods - .test_authwit_send_money(/*from=*/ wallet.getCompleteAddress(), /*to=*/ AztecAddress.random(), 100) - .send() - .wait(), - ).rejects.toThrow(/Message not authorized by account/); - }); + it('Fails if authwit not provided', async () => { + await expect( + async () => + await avmContract.methods + .test_authwit_send_money(/*from=*/ wallet.getCompleteAddress(), /*to=*/ AztecAddress.random(), 100) + .send() + .wait(), + ).rejects.toThrow(/Message not authorized by account/); }); + }); - describe('AvmInitializerTestContract', () => { - let avmContract: AvmInitializerTestContract; + describe('AvmInitializerTestContract', () => { + let avmContract: AvmInitializerTestContract; - beforeEach(async () => { - avmContract = await AvmInitializerTestContract.deploy(wallet).send().deployed(); - }, 50_000); + beforeEach(async () => { + avmContract = await AvmInitializerTestContract.deploy(wallet).send().deployed(); + }, 50_000); - describe('Storage', () => { - it('Read immutable (initialized) storage (Field)', async () => { - expect(await avmContract.methods.read_storage_immutable().simulate()).toEqual([42n]); - }); + describe('Storage', () => { + it('Read immutable (initialized) storage (Field)', async () => { + expect(await avmContract.methods.read_storage_immutable().simulate()).toEqual([42n]); }); }); }); }); + + describe('AvmNestedCallsTestContract', () => { + let avmContract: AvmNestedCallsTestContract; + let secondAvmContract: AvmNestedCallsTestContract; + + beforeEach(async () => { + avmContract = await AvmNestedCallsTestContract.deploy(wallet).send().deployed(); + secondAvmContract = await AvmNestedCallsTestContract.deploy(wallet).send().deployed(); + }, 50_000); + + it('Should NOT be able to emit the same unsiloed nullifier from the same contract', async () => { + const nullifier = new Fr(1); + await expect( + avmContract.methods.create_same_nullifier_in_nested_call(avmContract.address, nullifier).send().wait(), + ).rejects.toThrow(); + }); + it('Should be able to emit different unsiloed nullifiers from the same contract', async () => { + const nullifier = new Fr(1); + const tx = await avmContract.methods + .create_different_nullifier_in_nested_call(avmContract.address, nullifier) + .send() + .wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); + // TODO(4293): this should work! Fails in public kernel because both nullifiers are incorrectly being siloed by same address + it.skip('Should be able to emit the same unsiloed nullifier from two different contracts', async () => { + const nullifier = new Fr(1); + const tx = await avmContract.methods + .create_same_nullifier_in_nested_call(secondAvmContract.address, nullifier) + .send() + .wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); + it('Should be able to emit different unsiloed nullifiers from two different contracts', async () => { + const nullifier = new Fr(1); + const tx = await avmContract.methods + .create_different_nullifier_in_nested_call(secondAvmContract.address, nullifier) + .send() + .wait(); + expect(tx.status).toEqual(TxStatus.MINED); + }); + }); }); diff --git a/yarn-project/simulator/src/avm/avm_simulator.test.ts b/yarn-project/simulator/src/avm/avm_simulator.test.ts index 135eb7b75f7..78d0aa01011 100644 --- a/yarn-project/simulator/src/avm/avm_simulator.test.ts +++ b/yarn-project/simulator/src/avm/avm_simulator.test.ts @@ -5,7 +5,7 @@ import { AztecAddress } from '@aztec/foundation/aztec-address'; import { keccak, pedersenHash, poseidonHash, sha256 } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; -import { AvmTestContractArtifact } from '@aztec/noir-contracts.js'; +import { AvmNestedCallsTestContractArtifact, AvmTestContractArtifact } from '@aztec/noir-contracts.js'; import { jest } from '@jest/globals'; import { strict as assert } from 'assert'; @@ -504,117 +504,6 @@ describe('AVM simulator: transpiled Noir contracts', () => { }); }); - describe('Nested external calls', () => { - it(`Nested call succeeds`, 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') - .mockReturnValue(Promise.resolve(addBytecode)); - - const results = await new AvmSimulator(context).executeBytecode(callBytecode); - - expect(results.revertReason).toBeUndefined(); - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(3)]); - }); - - // TODO(https://github.com/AztecProtocol/aztec-packages/issues/5625): gas not plumbed through correctly in nested calls. - // it(`Nested call with not enough gas`, async () => { - // const gas = [/*l1=*/ 10000, /*l2=*/ 20, /*da=*/ 10000].map(g => new Fr(g)); - // const calldata: Fr[] = [new Fr(1), new Fr(2), ...gas]; - // const callBytecode = getAvmTestContractBytecode('raw_nested_call_to_add_with_gas'); - // const addBytecode = getAvmTestContractBytecode('add_args_return'); - // const context = initContext({ env: initExecutionEnvironment({ calldata }) }); - // jest - // .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') - // .mockReturnValue(Promise.resolve(addBytecode)); - - // const results = await new AvmSimulator(context).executeBytecode(callBytecode); - - // // Outer frame should not revert, but inner should, so the forwarded return value is 0 - // expect(results.revertReason).toBeUndefined(); - // expect(results.reverted).toBe(false); - // expect(results.output).toEqual([new Fr(0)]); - // }); - - 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') - .mockReturnValue(Promise.resolve(addBytecode)); - - const results = await new AvmSimulator(context).executeBytecode(callBytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([new Fr(3)]); - }); - - 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') - .mockReturnValue(Promise.resolve(addBytecode)); - - 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)]); - }); - - 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') - .mockReturnValue(Promise.resolve(nestedBytecode)); - - 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. - }); - - 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') - .mockReturnValue(Promise.resolve(addBytecode)); - - const results = await new AvmSimulator(context).executeBytecode(callBytecode); - - expect(results.reverted).toBe(false); - expect(results.output).toEqual([/*result=*/ new Fr(3)]); - }); - - 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') - .mockReturnValue(Promise.resolve(nestedBytecode)); - - const results = await new AvmSimulator(context).executeBytecode(callBytecode); - - expect(results.reverted).toBe(true); // The outer call should revert. - // TODO(fcarreiro): revertReason lost in translation between results. - // expect(results.revertReason).toEqual(/StaticCallStorageAlterError/); - }); - }); - describe('Storage accesses', () => { it('Should set value in storage (single)', async () => { const slot = 1n; @@ -904,6 +793,117 @@ describe('AVM simulator: transpiled Noir contracts', () => { expect(results.reverted).toBe(false); }); }); + + describe('Nested external calls', () => { + it(`Nested call succeeds`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmNestedCallsTestContractBytecode('raw_nested_call_to_add'); + const addBytecode = getAvmNestedCallsTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(addBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.revertReason).toBeUndefined(); + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/5625): gas not plumbed through correctly in nested calls. + // it(`Nested call with not enough gas`, async () => { + // const gas = [/*l1=*/ 10000, /*l2=*/ 20, /*da=*/ 10000].map(g => new Fr(g)); + // const calldata: Fr[] = [new Fr(1), new Fr(2), ...gas]; + // const callBytecode = getAvmNestedCallsTestContractBytecode('raw_nested_call_to_add_with_gas'); + // const addBytecode = getAvmNestedCallsTestContractBytecode('add_args_return'); + // const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + // jest + // .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + // .mockReturnValue(Promise.resolve(addBytecode)); + + // const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + // // Outer frame should not revert, but inner should, so the forwarded return value is 0 + // expect(results.revertReason).toBeUndefined(); + // expect(results.reverted).toBe(false); + // expect(results.output).toEqual([new Fr(0)]); + // }); + + it(`Nested call through the old interface`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmNestedCallsTestContractBytecode('nested_call_to_add'); + const addBytecode = getAvmNestedCallsTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(addBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([new Fr(3)]); + }); + + it(`Nested static call`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmNestedCallsTestContractBytecode('raw_nested_static_call_to_add'); + const addBytecode = getAvmNestedCallsTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(addBytecode)); + + 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)]); + }); + + it(`Nested static call which modifies storage`, async () => { + const callBytecode = getAvmNestedCallsTestContractBytecode('raw_nested_static_call_to_set_storage'); + const nestedBytecode = getAvmNestedCallsTestContractBytecode('set_storage_single'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(nestedBytecode)); + + 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. + }); + + it(`Nested static call (old interface)`, async () => { + const calldata: Fr[] = [new Fr(1), new Fr(2)]; + const callBytecode = getAvmNestedCallsTestContractBytecode('nested_static_call_to_add'); + const addBytecode = getAvmNestedCallsTestContractBytecode('add_args_return'); + const context = initContext({ env: initExecutionEnvironment({ calldata }) }); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(addBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(false); + expect(results.output).toEqual([/*result=*/ new Fr(3)]); + }); + + it(`Nested static call which modifies storage (old interface)`, async () => { + const callBytecode = getAvmNestedCallsTestContractBytecode('nested_static_call_to_set_storage'); + const nestedBytecode = getAvmNestedCallsTestContractBytecode('set_storage_single'); + const context = initContext(); + jest + .spyOn(context.persistableState.hostStorage.contractsDb, 'getBytecode') + .mockReturnValue(Promise.resolve(nestedBytecode)); + + const results = await new AvmSimulator(context).executeBytecode(callBytecode); + + expect(results.reverted).toBe(true); // The outer call should revert. + // TODO(fcarreiro): revertReason lost in translation between results. + // expect(results.revertReason).toEqual(/StaticCallStorageAlterError/); + }); + }); }); function getAvmTestContractBytecode(functionName: string): Buffer { @@ -914,3 +914,12 @@ function getAvmTestContractBytecode(functionName: string): Buffer { ); return artifact.bytecode; } + +function getAvmNestedCallsTestContractBytecode(functionName: string): Buffer { + const artifact = AvmNestedCallsTestContractArtifact.functions.find(f => f.name === functionName)!; + assert( + !!artifact?.bytecode, + `No bytecode found for function ${functionName}. Try re-running bootstrap.sh on the repository root.`, + ); + return artifact.bytecode; +}