From 22ad5a5728afce5dcf32c8e6d8025691081e0de1 Mon Sep 17 00:00:00 2001 From: Gregorio Juliana Date: Mon, 20 May 2024 12:29:28 +0200 Subject: [PATCH] feat: View functions with static context enforcing (#6338) Closes https://github.com/AztecProtocol/aztec-packages/issues/6078 Introduces the `#[aztec(view)]` (open to different naming, @spalladino @rahul-kothari ) modifier to functions, that forces them to be executed in an static context. It also forces generation of a "static only" `CallInterface` for them, trying to spare users from making regular calls to them. ~~Need input from the AVM team (@dbanks12 @fcarreiro) on how to implement the concept in the AVMContext.~~ In order to support direct simulated calls to view functions, the simulate method has been modified to go through the account entrypoint, which has led to implementing retrieval of return values through the whole callstack instead of just taking the latest one. Also adds the ability to navigate from contract interfaces to their implementations via LSP! --------- Co-authored-by: dbanks12 --- .../barretenberg/vm/avm_trace/avm_opcode.hpp | 2 + .../vm/avm_trace/aztec_constants.hpp | 2 +- docs/docs/migration_notes.md | 48 ++- .../img/sandbox_unconstrained_function.svg | 2 +- .../src/core/libraries/ConstantsGen.sol | 2 +- .../aztec-nr/authwit/src/entrypoint/app.nr | 8 +- .../aztec-nr/authwit/src/entrypoint/fee.nr | 12 +- .../authwit/src/entrypoint/function_call.nr | 12 +- noir-projects/aztec-nr/aztec/src/context.nr | 10 +- .../aztec/src/context/call_interfaces.nr | 407 ++++++++++++++++++ .../src/context/inputs/avm_context_inputs.nr | 2 + .../aztec-nr/aztec/src/context/interface.nr | 283 +----------- .../aztec/src/context/private_context.nr | 2 +- noir-projects/noir-contracts/Nargo.toml | 2 + .../src/dapp_payload.nr | 14 +- .../app_subscription_contract/src/main.nr | 8 +- .../src/main.nr | 4 +- .../contracts/avm_test_contract/src/main.nr | 2 +- .../crowdfunding_contract/src/main.nr | 3 +- .../docs_example_contract/src/main.nr | 6 +- .../contracts/lending_contract/src/main.nr | 4 +- .../contracts/price_feed_contract/src/main.nr | 1 + .../static_child_contract/Nargo.toml | 9 + .../static_child_contract/src/main.nr | 142 ++++++ .../static_parent_contract/Nargo.toml | 9 + .../static_parent_contract/src/main.nr | 196 +++++++++ .../contracts/uniswap_contract/src/main.nr | 15 +- .../crates/types/src/abis/function_data.nr | 10 +- .../types/src/abis/private_call_stack_item.nr | 2 +- .../types/src/abis/public_call_stack_item.nr | 8 +- .../crates/types/src/constants.nr | 2 +- .../src/tests/fixtures/contract_functions.nr | 2 + .../types/src/transaction/tx_request.nr | 4 +- noir/noir-repo/aztec_macros/src/lib.rs | 11 +- .../src/transforms/contract_interface.rs | 47 +- .../aztec_macros/src/transforms/functions.rs | 33 ++ .../aztec_macros/src/utils/ast_utils.rs | 13 +- .../aztec.js/src/contract/contract.test.ts | 13 +- .../contract/contract_function_interaction.ts | 47 +- .../default_multi_call_entrypoint.ts | 2 + .../aztec.js/src/entrypoint/payload.ts | 4 + .../src/fee/native_fee_payment_method.ts | 6 +- .../src/fee/private_fee_payment_method.ts | 9 +- .../src/fee/public_fee_payment_method.ts | 6 +- .../aztec.js/src/wallet/account_wallet.ts | 3 + .../aztec.js/src/wallet/base_wallet.ts | 9 +- .../circuit-types/src/interfaces/pxe.ts | 4 +- yarn-project/circuit-types/src/mocks.ts | 4 +- .../src/tx/public_simulation_output.ts | 31 +- .../circuit-types/src/tx/simulated_tx.ts | 12 +- yarn-project/circuits.js/src/constants.gen.ts | 2 +- .../src/contract/contract_address.test.ts | 1 + .../__snapshots__/function_data.test.ts.snap | 2 +- .../private_call_stack_item.test.ts.snap | 4 +- .../public_call_stack_item.test.ts.snap | 8 +- .../__snapshots__/tx_request.test.ts.snap | 2 +- .../src/structs/function_data.test.ts | 2 +- .../circuits.js/src/structs/function_data.ts | 20 +- .../structs/public_call_stack_item.test.ts | 4 +- .../src/structs/tx_request.test.ts | 2 +- .../circuits.js/src/tests/factories.ts | 8 +- .../end-to-end/src/e2e_fees/failures.test.ts | 15 +- .../end-to-end/src/e2e_static_calls.test.ts | 180 +++++++- .../end-to-end/src/fixtures/fixtures.ts | 3 + .../entrypoints/src/account_entrypoint.ts | 3 + .../entrypoints/src/dapp_entrypoint.ts | 2 + yarn-project/foundation/src/abi/abi.ts | 4 + .../foundation/src/abi/encoder.test.ts | 8 + .../src/type_conversion.test.ts | 2 +- .../src/type_conversion.ts | 7 +- .../pxe/src/pxe_service/pxe_service.ts | 6 +- .../src/pxe_service/test/pxe_test_suite.ts | 2 +- .../src/avm/avm_execution_environment.ts | 14 +- .../src/client/client_execution_context.ts | 4 +- .../simulator/src/client/simulator.ts | 2 +- .../client/unconstrained_execution.test.ts | 2 +- yarn-project/simulator/src/common/index.ts | 1 + .../simulator/src/common/return_values.ts | 18 + yarn-project/simulator/src/mocks/fixtures.ts | 2 +- .../src/public/abstract_phase_manager.ts | 27 +- .../simulator/src/public/index.test.ts | 6 +- .../src/public/public_execution_context.ts | 2 +- .../simulator/src/public/public_processor.ts | 14 +- .../src/public/setup_phase_manager.ts | 2 +- .../src/public/tail_phase_manager.ts | 2 +- .../src/public/teardown_phase_manager.ts | 2 +- .../src/public/transitional_adaptors.ts | 6 +- .../types/src/abi/contract_artifact.ts | 3 + yarn-project/types/src/noir/index.ts | 1 + 89 files changed, 1391 insertions(+), 493 deletions(-) create mode 100644 noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr create mode 100644 noir-projects/noir-contracts/contracts/static_child_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr create mode 100644 noir-projects/noir-contracts/contracts/static_parent_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr create mode 100644 yarn-project/simulator/src/common/return_values.ts diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp index fd655a026f3..892ec941bd3 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/avm_opcode.hpp @@ -98,6 +98,8 @@ enum class OpCode : uint8_t { // Gadgets KECCAK, POSEIDON2, + SHA256, + PEDERSEN, // Conversions TORADIXLE, diff --git a/barretenberg/cpp/src/barretenberg/vm/avm_trace/aztec_constants.hpp b/barretenberg/cpp/src/barretenberg/vm/avm_trace/aztec_constants.hpp index caa72df3834..f5e8bf5a97e 100644 --- a/barretenberg/cpp/src/barretenberg/vm/avm_trace/aztec_constants.hpp +++ b/barretenberg/cpp/src/barretenberg/vm/avm_trace/aztec_constants.hpp @@ -83,7 +83,7 @@ const size_t CONTRACT_INSTANCE_LENGTH = 5; const size_t CONTRACT_STORAGE_READ_LENGTH = 2; const size_t CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH = 2; const size_t ETH_ADDRESS_LENGTH = 1; -const size_t FUNCTION_DATA_LENGTH = 2; +const size_t FUNCTION_DATA_LENGTH = 3; const size_t FUNCTION_LEAF_PREIMAGE_LENGTH = 5; const size_t GLOBAL_VARIABLES_LENGTH = 6 + GAS_FEES_LENGTH; const size_t APPEND_ONLY_TREE_SNAPSHOT_LENGTH = 2; diff --git a/docs/docs/migration_notes.md b/docs/docs/migration_notes.md index 209e5a0e066..f607abad860 100644 --- a/docs/docs/migration_notes.md +++ b/docs/docs/migration_notes.md @@ -6,20 +6,56 @@ keywords: [sandbox, cli, aztec, notes, migration, updating, upgrading] Aztec is in full-speed development. Literally every version breaks compatibility with the previous ones. This page attempts to target errors and difficulties you might encounter when upgrading, and how to resolve them. +## 0.X.X + +### [Aztec.nr] View functions and interface navigation + +It is now possible to explicitly state a function doesn't perform any state alterations (including storage, logs, nullifiers and/or messages from L2 to L1) with the `#[aztec(view)]` attribute, similarly to solidity's `view` function modifier. + +```diff + #[aztec(public)] ++ #[aztec(view)] + fn get_price(asset_id: Field) -> Asset { + storage.assets.at(asset_id).read() + } +``` + +View functions only generate a `StaticCallInterface` that doesn't include `.call` or `.enqueue` methods. Also, the denomination `static` has been completely removed from the interfaces, in favor of the more familiar `view` + +```diff ++ let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; +- let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; +``` + +```diff +#[aztec(private)] +fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { ++ StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); +- StaticChild::at(target_contract).pub_get_value(value).static_enqueue(&mut context); +} +``` + +Additionally, the Noir LSP will now honor "go to definitions" requests for contract interfaces (Ctrl+click), taking the user to the original function implementation. + +### [Aztec.js] Simulate changes + +* `.simulate()` now tracks closer the process performed by `.send().wait()`, specifically going through the account contract entrypoint instead of directly calling the intended function. +* `wallet.viewTx(...)` has been renamed to `wallet.simulateUnconstrained(...)` to better clarify what it does. + ## 0.41.0 ### [Aztec.nr] Keys: Token note now stores an owner master nullifying public key hash instead of an owner address i.e. -struct TokenNote \{ +```diff +struct TokenNote { amount: U128, - ```diff - - owner: AztecAddress, - + npk_m_hash: Field, - ``` +- owner: AztecAddress, ++ npk_m_hash: Field, randomness: Field, -\} +} +``` Computing the nullifier similarly changes to use this master nullifying public key hash. diff --git a/docs/static/img/sandbox_unconstrained_function.svg b/docs/static/img/sandbox_unconstrained_function.svg index 8b42518887d..948a9ac042e 100644 --- a/docs/static/img/sandbox_unconstrained_function.svg +++ b/docs/static/img/sandbox_unconstrained_function.svg @@ -1307,7 +1307,7 @@ id="path53" d="M 2.8498858,-9.2967961e-4 -0.00199662,-7.4962595 H 1.2202387 l 1.5983078,4.4532922 c 0.3760724,1.0506006 0.5954479,1.7170264 0.6581267,2.0071176 h 0.062679 c 0.039174,-0.2273688 0.1958711,-0.7213079 0.4700905,-1.473977 0.2663846,-0.7683497 0.8618326,-2.430494 1.786344,-4.9864328 h 1.222235 L 4.1661393,-9.2967961e-4 Z m 6.5107536,0 H 8.2010828 V -7.4962595 H 9.3606394 Z M 8.1070647,-9.5033771 c 0,-0.2665703 0.062679,-0.4547376 0.1880362,-0.5645019 0.1410272,-0.125445 0.3133937,-0.188167 0.5014299,-0.188167 0.1645317,0 0.3133937,0.06272 0.4387512,0.188167 0.1410271,0.1097643 0.2193755,0.2979316 0.2193755,0.5645019 0,0.2508897 -0.078348,0.439057 -0.2193755,0.5645018 -0.1253575,0.1254449 -0.2742195,0.1881673 -0.4387512,0.1881673 -0.1880362,0 -0.3604027,-0.062722 -0.5014299,-0.1881673 C 8.1697435,-9.0643201 8.1070647,-9.2524874 8.1070647,-9.5033771 Z m 6.8006423,9.62789227 c -1.112547,0 -1.990049,-0.32929273 -2.632506,-1.0035588 C 11.648413,-1.5454694 11.33502,-2.4784655 11.33502,-3.6701916 c 0,-1.2074067 0.289889,-2.1717639 0.877502,-2.8852315 0.603283,-0.7056273 1.410271,-1.0662813 2.413131,-1.0662813 0.940181,0 1.676656,0.3136122 2.225095,0.9408364 0.540604,0.6115437 0.814824,1.4112546 0.814824,2.4148134 v 0.7213079 h -5.139656 c 0.01567,0.8781139 0.235045,1.55238 0.658126,2.0071176 0.415247,0.4625779 1.010695,0.68994668 1.786344,0.68994668 0.791319,0 1.582638,-0.16464638 2.381792,-0.50177938 v 1.00355878 C 16.952601,-0.17341635 16.576529,-0.0636521 16.223961,-9.2967961e-4 15.863558,0.07747335 15.424807,0.12451517 14.907707,0.12451517 Z M 14.625653,-6.680868 c -0.611117,0 -1.089043,0.2038479 -1.441611,0.5958631 -0.360402,0.3998554 -0.564108,0.9408363 -0.626787,1.630783 h 3.917421 c 0,-0.7056273 -0.172367,-1.2544485 -0.50143,-1.630783 C 15.659852,-6.4770201 15.205431,-6.680868 14.625653,-6.680868 Z M 25.743294,-9.2967961e-4 24.364362,-4.3914995 c -0.08618,-0.2665703 -0.250715,-0.8781139 -0.50143,-1.8189503 h -0.03134 c -0.188036,0.7997109 -0.360402,1.4034143 -0.501429,1.8189503 L 21.919891,-9.2967961e-4 H 20.603638 L 18.566579,-7.4962595 h 1.190896 c 0.477925,1.8816728 0.846163,3.3164482 1.096877,4.2964861 0.250715,0.9878782 0.391743,1.654304 0.438752,2.0071176 h 0.06268 c 0.03918,-0.2665703 0.109688,-0.6037033 0.219376,-1.0035588 0.125357,-0.415536 0.22721,-0.7526691 0.313394,-1.0035588 l 1.378932,-4.2964861 h 1.222235 l 1.347593,4.2964861 c 0.250715,0.799711 0.415247,1.4582964 0.50143,1.9757564 h 0.06268 c 0.01567,-0.1646463 0.07051,-0.415536 0.156696,-0.7526691 0.07835,-0.3292927 0.548439,-2.171764 1.410272,-5.5195734 h 1.190896 L 27.09089,-9.2967961e-4 Z m 8.007208,0 H 32.590946 V -8.9702365 h -3.165277 v -1.0035588 h 7.490109 v 1.0035588 H 33.750502 Z M 40.049715,-3.8269976 37.448548,-7.4962595 h 1.284914 l 1.97438,2.8852316 1.97438,-2.8852316 h 1.284914 l -2.601167,3.6692619 2.726525,3.82606792039 H 42.807579 L 40.707842,-3.0429673 38.608104,-9.2967961e-4 H 37.32319 Z m 4.896776,0 c 0,-1.2074067 0.172367,-2.3364104 0.53277,-3.387011 0.352567,-1.0427603 0.861832,-1.9600758 1.535629,-2.7597867 h 1.096877 c -0.650291,0.8781139 -1.143887,1.8424712 -1.47295,2.8852316 -0.336898,1.0506006 -0.50143,2.1247221 -0.50143,3.2302049 0,1.113323 0.164532,2.1874445 0.50143,3.23020487 0.329063,1.01923941 0.822659,1.97575643 1.47295,2.85387033 H 47.01489 C 46.341093,1.4495264 45.831828,0.54789154 45.479261,-0.47134787 45.118858,-1.5141082 44.946491,-2.6352715 44.946491,-3.8269976 Z m 4.606887,3.10476003 c 0,-0.31361213 0.06268,-0.54098093 0.188037,-0.68994673 0.141027,-0.1646463 0.352567,-0.2508897 0.626787,-0.2508897 0.266384,0 0.477925,0.086243 0.626787,0.2508897 0.141027,0.1489658 0.219376,0.3763346 0.219376,0.68994673 0,0.29793152 -0.08618,0.52530031 -0.250715,0.68994668 -0.148862,0.14112545 -0.344733,0.21952849 -0.595448,0.21952849 -0.235045,0 -0.430916,-0.0627224 -0.595448,-0.18816727961 C 49.623892,-0.14205514 49.553378,-0.38510453 49.553378,-0.72223757 Z m 3.729385,0 c 0,-0.31361213 0.06268,-0.54098093 0.188036,-0.68994673 0.141027,-0.1646463 0.352568,-0.2508897 0.626788,-0.2508897 0.266384,0 0.477925,0.086243 0.626787,0.2508897 0.141027,0.1489658 0.219376,0.3763346 0.219376,0.68994673 0,0.29793152 -0.08618,0.52530031 -0.250715,0.68994668 -0.148862,0.14112545 -0.344733,0.21952849 -0.595448,0.21952849 -0.235046,0 -0.430917,-0.0627224 -0.595448,-0.18816727961 C 53.353277,-0.14205514 53.282763,-0.38510453 53.282763,-0.72223757 Z m 3.72155,0 c 0,-0.31361213 0.06268,-0.54098093 0.188036,-0.68994673 0.141027,-0.1646463 0.352568,-0.2508897 0.626787,-0.2508897 0.266385,0 0.477926,0.086243 0.626788,0.2508897 0.141027,0.1489658 0.219375,0.3763346 0.219375,0.68994673 0,0.29793152 -0.08618,0.52530031 -0.250715,0.68994668 -0.148862,0.14112545 -0.344733,0.21952849 -0.595448,0.21952849 -0.235045,0 -0.430916,-0.0627224 -0.595447,-0.18816727961 C 57.074827,-0.14205514 57.004313,-0.38510453 57.004313,-0.72223757 Z m 6.267873,-3.10476003 c 0,1.1917261 -0.180201,2.3128894 -0.532769,3.35564973 C 62.402519,0.54789154 61.901089,1.4495264 61.235128,2.2257164 H 60.13825 c 0.626787,-0.8624333 1.104712,-1.81111001 1.441611,-2.85387033 0.329063,-1.04276037 0.501429,-2.11688187 0.501429,-3.23020487 0,-1.1054828 -0.172366,-2.1796043 -0.501429,-3.2302049 -0.313394,-1.0427604 -0.806989,-2.0071177 -1.472951,-2.8852316 h 1.128218 c 0.665961,0.7997109 1.167391,1.7248667 1.504289,2.7911479 0.352568,1.0506006 0.532769,2.1639237 0.532769,3.3556498 z m 6.557763,3.82606792039 H 68.670392 V -7.4962595 h 1.159557 z M 68.576374,-9.5033771 c 0,-0.2665703 0.06268,-0.4547376 0.188037,-0.5645019 0.141027,-0.125445 0.313393,-0.188167 0.501429,-0.188167 0.164532,0 0.313394,0.06272 0.438752,0.188167 0.141027,0.1097643 0.219375,0.2979316 0.219375,0.5645019 0,0.2508897 -0.07835,0.439057 -0.219375,0.5645018 -0.125358,0.1254449 -0.27422,0.1881673 -0.438752,0.1881673 -0.188036,0 -0.360402,-0.062722 -0.501429,-0.1881673 -0.125358,-0.1254448 -0.188037,-0.3136121 -0.188037,-0.5645018 z m 8.492969,7.4639686 c 0,0.6899467 -0.266385,1.22308729 -0.783484,1.59942184 -0.524935,0.37633456 -1.253575,0.56450183 -2.193756,0.56450183 -1.00286,0 -1.778509,-0.15680606 -2.319113,-0.47041819 V -1.380823 c 0.352568,0.1881672 0.736475,0.337133 1.159556,0.43905694 0.415247,0.10976424 0.814824,0.15680606 1.190896,0.15680606 0.579779,0 1.0342,-0.0940836 1.347593,-0.2822509 0.329064,-0.2038479 0.50143,-0.5017794 0.50143,-0.878114 0,-0.2900912 -0.125357,-0.5409809 -0.376072,-0.7526691 -0.250715,-0.2038479 -0.752145,-0.4468973 -1.50429,-0.7213079 -0.689466,-0.2665703 -1.183061,-0.5017794 -1.47295,-0.6899466 -0.297724,-0.1881673 -0.5171,-0.4076958 -0.658127,-0.6585855 -0.148862,-0.2508897 -0.219375,-0.5409809 -0.219375,-0.878114 0,-0.6037033 0.250714,-1.0819618 0.752144,-1.4426157 0.50143,-0.3528137 1.175227,-0.5331407 2.037059,-0.5331407 0.814824,0 1.606143,0.1724867 2.381792,0.5017794 l -0.407412,0.9094752 C 75.75309,-6.5240619 75.071458,-6.680868 74.468175,-6.680868 c -0.548438,0 -0.95585,0.086243 -1.222235,0.2508897 -0.274219,0.1724867 -0.407412,0.4076958 -0.407412,0.7213079 0,0.1881673 0.03918,0.360654 0.125358,0.5017794 0.101853,0.1489658 0.266384,0.2822509 0.50143,0.4076958 0.22721,0.1254448 0.665961,0.3057718 1.316253,0.5331406 0.893172,0.337133 1.50429,0.6742661 1.817683,1.0035588 0.313394,0.3136121 0.470091,0.7213079 0.470091,1.2230873 z m 8.461629,2.16392367 c -1.089043,0 -1.935206,-0.32145243 -2.538489,-0.97219759 -0.587613,-0.66642578 -0.877502,-1.61510248 -0.877502,-2.85387038 0,-1.2544485 0.297724,-2.2109655 0.908842,-2.8852315 0.603283,-0.6899467 1.45728,-1.0349201 2.569828,-1.0349201 0.376072,0 0.736475,0.047042 1.096878,0.1254449 0.352568,0.062722 0.634622,0.1489658 0.846163,0.2508897 l -0.344733,0.9721976 c -0.250715,-0.101924 -0.53277,-0.1881673 -0.846163,-0.2508897 -0.297724,-0.062722 -0.548439,-0.094084 -0.752145,-0.094084 -1.527794,0 -2.287774,0.9721976 -2.287774,2.9165928 0,0.9251558 0.172367,1.6307831 0.532769,2.1325625 0.376073,0.4860988 0.924512,0.72130788 1.660987,0.72130788 0.626787,0 1.261409,-0.13328516 1.911701,-0.40769578 v 1.00355882 c -0.50143,0.25088970039 -1.128217,0.37633455 -1.880362,0.37633485 z M 93.82807,-9.2967961e-4 93.577355,-1.0672109 h -0.03134 c -0.376072,0.46257788 -0.752144,0.77619 -1.128217,0.94083637 -0.376072,0.16464637 -0.846163,0.2508897 -1.410271,0.2508897 -0.736475,0 -1.308419,-0.18816727 -1.723666,-0.56450183 -0.423081,-0.37633455 -0.626787,-0.91731544 -0.626787,-1.63078304 0,-1.5053382 1.198731,-2.2972088 3.604027,-2.3834522 l 1.284914,-0.031361 v -0.4704182 c 0,-0.5801824 -0.125357,-1.0113991 -0.376072,-1.2858097 -0.250715,-0.2900912 -0.658127,-0.439057 -1.222235,-0.439057 -0.626788,0 -1.339758,0.2038479 -2.131077,0.5958631 l -0.344733,-0.878114 c 0.376072,-0.2038479 0.783484,-0.3606539 1.222235,-0.4704182 0.438751,-0.1254448 0.877502,-0.1881673 1.316253,-0.1881673 0.893172,0 1.551299,0.2038479 1.974381,0.5958631 0.438751,0.3998554 0.658126,1.03492 0.658126,1.913034 V -9.2967961e-4 Z M 91.258242,-0.78496 c 0.705135,0 1.261409,-0.19600758 1.660986,-0.595863 0.391742,-0.3920152 0.595448,-0.9408364 0.595448,-1.6307831 v -0.6585855 l -1.128217,0.031361 c -0.901007,0.047042 -1.559134,0.1881673 -1.97438,0.439057 -0.399577,0.2352091 -0.595448,0.6115437 -0.595448,1.1290037 0,0.4233764 0.125357,0.7448288 0.376072,0.9721976 0.250715,0.21168816 0.603283,0.3136121 1.065539,0.3136123 z m 6.878991,0.78403032039 H 96.977676 V -10.632381 h 1.159557 z m 3.541347,0 h -1.15956 V -10.632381 h 1.15956 z M 107.22565,0.12451517 c -1.11255,0 -1.99005,-0.32929273 -2.63251,-1.0035588 -0.62678,-0.66642577 -0.94018,-1.59942187 -0.94018,-2.79114797 0,-1.2074067 0.28989,-2.1717639 0.8775,-2.8852315 0.60329,-0.7056273 1.41028,-1.0662813 2.41314,-1.0662813 0.94018,0 1.67665,0.3136122 2.22509,0.9408364 0.5406,0.6115437 0.81482,1.4112546 0.81482,2.4148134 v 0.7213079 h -5.13965 c 0.0157,0.8781139 0.23504,1.55238 0.65812,2.0071176 0.41525,0.4625779 1.0107,0.68994668 1.78635,0.68994668 0.79132,0 1.58264,-0.16464638 2.38179,-0.50177938 v 1.00355878 c -0.39958,0.17248667 -0.77565,0.28225092 -1.12822,0.34497334039 C 108.1815,0.07747335 107.74275,0.12451517 107.22565,0.12451517 Z M 106.9436,-6.680868 c -0.61112,0 -1.08905,0.2038479 -1.44162,0.5958631 -0.3604,0.3998554 -0.5641,0.9408363 -0.62678,1.630783 h 3.91742 c 0,-0.7056273 -0.17237,-1.2544485 -0.50143,-1.630783 -0.3134,-0.3920152 -0.76782,-0.5958631 -1.34759,-0.5958631 z m 10.0756,5.6763795 h -0.0627 c -0.52493,0.75266912 -1.30842,1.12900367 -2.35045,1.12900367 -0.98719,0 -1.74717,-0.32929273 -2.28777,-1.0035588 -0.54844,-0.66642577 -0.81483,-1.61510247 -0.81483,-2.85387037 0,-1.2309276 0.26639,-2.1796043 0.81483,-2.8538703 0.5406,-0.6899467 1.30058,-1.0349201 2.28777,-1.0349201 1.01853,0 1.80201,0.3684943 2.35045,1.0976425 h 0.094 l -0.0627,-0.5331406 -0.0313,-0.5331406 v -3.0420379 h 1.15956 V -9.2967961e-4 h -0.94018 z m -2.25643,0.18816729 c 0.76781,0 1.32409,-0.20384789 1.66098,-0.62722429 0.35257,-0.415536 0.53277,-1.0976424 0.53277,-2.0384788 V -3.732914 c 0,-1.0427603 -0.1802,-1.7954294 -0.53277,-2.2580073 -0.3604,-0.4547376 -0.92451,-0.6899467 -1.69232,-0.6899467 -0.6738,0 -1.18306,0.2665703 -1.53563,0.7840303 -0.3369,0.5253003 -0.50143,1.2544485 -0.50143,2.1952849 0,0.9408364 0.16453,1.6621443 0.50143,2.1639237 0.35257,0.4860988 0.8775,0.72130789 1.56697,0.72130789 z m 15.84988,-2.94795399 c 0,1.2387679 -0.31339,2.1952849 -0.94018,2.88523157 -0.61112,0.67426607 -1.45728,1.0035588 -2.53849,1.0035588 -0.67379,0 -1.26924,-0.15680606 -1.78634,-0.47041819 -0.52494,-0.31361213 -0.93235,-0.76050938 -1.22224,-1.34853218 -0.27422,-0.5801824 -0.40741,-1.2701291 -0.40741,-2.06984 0,-1.2074067 0.29773,-2.1482431 0.90884,-2.8225091 0.62679,-0.6899467 1.48079,-1.0349201 2.56983,-1.0349201 1.04204,0 1.86469,0.360654 2.47581,1.0662813 0.62679,0.6899466 0.94018,1.6229427 0.94018,2.7911479 z m -5.70376,0 c 0,0.9643573 0.18803,1.6935055 0.56411,2.1952849 0.37607,0.5017794 0.94018,0.75266909 1.69232,0.75266909 0.72864,0 1.28492,-0.25088969 1.66099,-0.75266909 0.39174,-0.5017794 0.59545,-1.2309276 0.59545,-2.1952849 0,-0.9408364 -0.20371,-1.6464637 -0.59545,-2.1325625 -0.37607,-0.5017794 -0.94018,-0.7526691 -1.69233,-0.7526691 -0.73647,0 -1.28491,0.2430494 -1.66098,0.7213079 -0.37608,0.4860988 -0.56411,1.2074067 -0.56411,2.1639237 z m 12.8178,3.76334552039 V -4.8305564 c 0,-0.6272243 -0.14886,-1.0819619 -0.43875,-1.3798934 -0.27422,-0.2900912 -0.70514,-0.439057 -1.28492,-0.439057 -0.77565,0 -1.34759,0.2116882 -1.72366,0.6272243 -0.3604,0.4233764 -0.53277,1.1211633 -0.53277,2.1012012 v 3.92015162039 h -1.15956 V -7.4962595 h 0.94018 l 0.18804,1.03492 h 0.0627 c 0.22721,-0.3763345 0.54844,-0.6585855 0.97152,-0.8467527 0.41525,-0.2038479 0.8775,-0.3136122 1.37893,-0.3136122 0.91668,0 1.59831,0.2195285 2.03706,0.6585855 0.45442,0.439057 0.68947,1.1290037 0.68947,2.06984 V -9.2967961e-4 Z M 147.26169,-0.78496 c 0.18804,0 0.37607,-0.00784 0.56411,-0.0313612 0.18804,-0.0392015 0.32906,-0.078403 0.43875,-0.12544485 v 0.87811396 c -0.10969,0.0627224204 -0.28205,0.10192394 -0.53277,0.12544485 -0.23504,0.03920151 -0.45442,0.06272242 -0.65812,0.06272242 -1.44162,0 -2.16242,-0.76050941 -2.16242,-2.28936858 v -4.4532922 h -1.06554 v -0.5331406 l 1.06554,-0.4704182 0.47009,-1.5994218 h 0.65813 v 1.7248667 h 2.19375 v 0.8781139 h -2.19375 v 4.421931 c 0,0.439057 0.10185,0.7840303 0.31339,1.0349201 0.22721,0.25088966 0.53277,0.37633451 0.90884,0.3763345 z m 7.63114,0.78403032039 V -4.8305564 c 0,-0.6272243 -0.14886,-1.0819619 -0.43875,-1.3798934 -0.27422,-0.2900912 -0.70514,-0.439057 -1.28492,-0.439057 -0.79915,0 -1.3711,0.2195285 -1.72366,0.6585855 -0.36041,0.4233764 -0.53277,1.113323 -0.53277,2.06984 v 3.92015162039 h -1.15956 V -10.632381 h 1.15956 v 3.2302051 c 0,0.3763346 -0.0235,0.6899467 -0.0627,0.9408364 h 0.0627 c 0.22721,-0.3528136 0.5406,-0.6350645 0.94018,-0.8467527 0.41525,-0.2038479 0.89317,-0.3136122 1.44161,-0.3136122 0.89317,0 1.5748,0.2195285 2.03706,0.6585855 0.45442,0.439057 0.68946,1.1290037 0.68946,2.06984 V -9.2967961e-4 Z M 161.51327,0.12451517 c -1.11255,0 -1.99005,-0.32929273 -2.63251,-1.0035588 -0.62679,-0.66642577 -0.94018,-1.59942187 -0.94018,-2.79114797 0,-1.2074067 0.28989,-2.1717639 0.8775,-2.8852315 0.60329,-0.7056273 1.41027,-1.0662813 2.41313,-1.0662813 0.94019,0 1.67666,0.3136122 2.2251,0.9408364 0.5406,0.6115437 0.81482,1.4112546 0.81482,2.4148134 v 0.7213079 h -5.13965 c 0.0157,0.8781139 0.23504,1.55238 0.65812,2.0071176 0.41525,0.4625779 1.0107,0.68994668 1.78635,0.68994668 0.79132,0 1.58264,-0.16464638 2.38179,-0.50177938 v 1.00355878 c -0.39958,0.17248667 -0.77565,0.28225092 -1.12822,0.34497334039 C 162.46912,0.07747335 162.03037,0.12451517 161.51327,0.12451517 Z M 161.23121,-6.680868 c -0.61111,0 -1.08904,0.2038479 -1.44161,0.5958631 -0.3604,0.3998554 -0.5641,0.9408363 -0.62678,1.630783 h 3.91742 c 0,-0.7056273 -0.17237,-1.2544485 -0.50143,-1.630783 -0.3134,-0.3920152 -0.76782,-0.5958631 -1.3476,-0.5958631 z m 15.12908,-0.4076957 c 0,1.0270797 -0.34473,1.81111 -1.03419,2.3520909 -0.68947,0.5488212 -1.67666,0.8153915 -2.94591,0.8153915 h -1.19089 v 3.92015162039 h -1.15956 V -9.9737953 h 2.60117 c 2.48364,0 3.72938,0.9643573 3.72938,2.8852316 z m -5.17099,2.1639236 h 1.0342 c 1.04203,0 1.79418,-0.1646463 2.25643,-0.5017794 0.45442,-0.3292927 0.68947,-0.8624333 0.68947,-1.5994218 0,-0.6429049 -0.21938,-1.1290037 -0.65813,-1.4426158 -0.43875,-0.3292927 -1.12038,-0.5017794 -2.03706,-0.5017794 h -1.28491 z m 13.91468,4.92371042039 h -1.31626 L 181.09254,-4.3914995 178.36601,-9.2967961e-4 h -1.22223 L 180.46575,-5.206891 l -3.1026,-4.7669043 h 1.28492 l 2.47581,3.9515128 2.50715,-3.9515128 h 1.25357 l -3.1026,4.7041819 z m 7.01218,0 h -5.57841 V -9.9737953 h 5.57841 v 1.0035588 h -4.41885 v 3.2302049 h 4.1368 v 1.03492 h -4.1368 v 3.6692619 h 4.41885 z M 193.98085,-0.72223757 c 0,-0.31361213 0.0627,-0.54098093 0.18804,-0.68994673 0.14103,-0.1646463 0.35257,-0.2508897 0.62679,-0.2508897 0.26638,0 0.47792,0.086243 0.62678,0.2508897 0.14103,0.1489658 0.21938,0.3763346 0.21938,0.68994673 0,0.29793152 -0.0862,0.52530031 -0.25071,0.68994668 -0.14887,0.14112545 -0.34474,0.21952849 -0.59545,0.21952849 -0.23505,0 -0.43092,-0.0627224 -0.59545,-0.18816727961 C 194.05137,-0.14205514 193.98085,-0.38510453 193.98085,-0.72223757 Z m 0,0" style="fill:#f24726;fill-opacity:1;fill-rule:nonzero;stroke:none" - aria-label=" viewTx(...) is called on the PXE." + aria-label=" simulateUnconstrained(...) is called on the PXE." transform="matrix(0.4985742,0,0,0.49822691,416.05959,194.41453)" clip-path="url(#clipPath54)" /> for FunctionCall { fn serialize(self) -> [Field; FUNCTION_CALL_SIZE] { - [self.args_hash, self.function_selector.to_field(), self.target_address.to_field(), self.is_public as Field] + [self.args_hash, self.function_selector.to_field(), self.target_address.to_field(), self.is_public as Field, self.is_static as Field] } } @@ -34,6 +35,7 @@ impl FunctionCall { bytes[i + 64] = target_address_bytes[i]; } bytes[96] = self.is_public as u8; + bytes[97] = self.is_static as u8; bytes } } diff --git a/noir-projects/aztec-nr/aztec/src/context.nr b/noir-projects/aztec-nr/aztec/src/context.nr index c7d79f2e24b..70992b0cea7 100644 --- a/noir-projects/aztec-nr/aztec/src/context.nr +++ b/noir-projects/aztec-nr/aztec/src/context.nr @@ -5,11 +5,15 @@ mod private_context; mod public_context; mod avm_context; mod interface; +mod call_interfaces; mod gas; -use interface::{ - ContextInterface, PrivateCallInterface, PublicCallInterface, PrivateVoidCallInterface, - PublicVoidCallInterface, AvmCallInterface, AvmVoidCallInterface +use interface::ContextInterface; +use call_interfaces::{ + PrivateCallInterface, PrivateStaticCallInterface, PublicCallInterface, PublicStaticCallInterface, + PrivateVoidCallInterface, PrivateStaticVoidCallInterface, PublicVoidCallInterface, + PublicStaticVoidCallInterface, AvmCallInterface, AvmStaticCallInterface, AvmVoidCallInterface, + AvmStaticVoidCallInterface }; use private_context::PrivateContext; use private_context::PackedReturns; diff --git a/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr new file mode 100644 index 00000000000..a04465f60bf --- /dev/null +++ b/noir-projects/aztec-nr/aztec/src/context/call_interfaces.nr @@ -0,0 +1,407 @@ +use dep::protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress, traits::Deserialize}; + +use crate::context::private_context::PrivateContext; +use crate::context::public_context::PublicContext; +use crate::context::avm_context::AvmContext; +use crate::context::gas::GasOpts; +use crate::context::public_context::FunctionReturns; + +use crate::hash::hash_args; +use crate::oracle::arguments; + +struct PrivateCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PrivateCallInterface { + pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { + let returns = context.call_private_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ); + let unpacked: T = returns.unpack_into(); + unpacked + } + + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { + let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); + returns.unpack_into() + } + + pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { + let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); + returns.unpack_into() + } +} + +struct PrivateVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PrivateVoidCallInterface { + pub fn call(self, context: &mut PrivateContext) { + context.call_private_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ).assert_empty(); + } + + pub fn view(self, context: &mut PrivateContext) { + context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); + } + + pub fn delegate_call(self, context: &mut PrivateContext) { + context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty(); + } +} + +struct PrivateStaticCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PrivateStaticCallInterface { + pub fn view(self, context: &mut PrivateContext) -> T where T: Deserialize { + let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); + returns.unpack_into() + } +} + +struct PrivateStaticVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PrivateStaticVoidCallInterface { + pub fn view(self, context: &mut PrivateContext) { + context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); + } +} + +struct PublicCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PublicCallInterface { + pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { + let returns = context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ); + returns.deserialize_into() + } + + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); + returns.deserialize_into() + } + + pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { + let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); + returns.deserialize_into() + } + + pub fn enqueue(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ) + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) + } + + pub fn delegate_enqueue(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true) + } +} + +struct PublicVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field +} + +impl PublicVoidCallInterface { + pub fn call(self, context: &mut PublicContext) { + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ).assert_empty() + } + + pub fn view(self, context: &mut PublicContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); + } + + pub fn delegate_call(self, context: &mut PublicContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty(); + } + + pub fn enqueue(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + self.args_hash, + false, + false + ) + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) + } + + pub fn delegate_enqueue(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true) + } +} + +struct PublicStaticCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field, +} + +impl PublicStaticCallInterface { + pub fn view(self, context: &mut PublicContext) -> T where T: Deserialize { + let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); + returns.deserialize_into() + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) + } +} + +struct PublicStaticVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args_hash: Field +} + +impl PublicStaticVoidCallInterface { + pub fn view(self, context: &mut PublicContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) + } +} + +struct AvmCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args: [Field], + gas_opts: GasOpts, +} + +impl AvmCallInterface { + pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { + self.gas_opts = gas_opts; + self + } + + pub fn call(self, context: &mut AvmContext) -> T where T: Deserialize { + let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.deserialize_into() + } + + pub fn view(self, context: &mut AvmContext) -> T where T: Deserialize { + let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.deserialize_into() + } + + pub fn delegate_call(self, context: &mut AvmContext) -> T where T: Deserialize { + let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); + returns.deserialize_into() + } + + pub fn enqueue(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ false, + /*delegate=*/ false + ) + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ true, + /*delegate=*/ false + ) + } + + pub fn delegate_enqueue(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ false, + /*delegate=*/ true + ) + } +} + +struct AvmVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args: [Field], + gas_opts: GasOpts, +} + +impl AvmVoidCallInterface { + pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { + self.gas_opts = gas_opts; + self + } + + pub fn call(self, context: &mut AvmContext) { + let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.assert_empty() + } + + pub fn view(self, context: &mut AvmContext) { + let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.assert_empty() + } + + pub fn delegate_call(self, context: &mut AvmContext) { + let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); + returns.assert_empty() + } + + pub fn enqueue(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ false, + /*delegate=*/ false + ) + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ true, + /*delegate=*/ false + ) + } + + pub fn delegate_enqueue(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ false, + /*delegate=*/ true + ) + } +} + +struct AvmStaticCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args: [Field], + gas_opts: GasOpts, +} + +impl AvmStaticCallInterface { + pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { + self.gas_opts = gas_opts; + self + } + + pub fn view(self, context: &mut AvmContext) -> T where T: Deserialize { + let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.deserialize_into() + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ true, + /*delegate=*/ false + ) + } +} + +struct AvmStaticVoidCallInterface { + target_contract: AztecAddress, + selector: FunctionSelector, + args: [Field], + gas_opts: GasOpts, +} + +impl AvmStaticVoidCallInterface { + pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { + self.gas_opts = gas_opts; + self + } + + pub fn view(self, context: &mut AvmContext) { + let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); + returns.assert_empty() + } + + pub fn enqueue_view(self, context: &mut PrivateContext) { + // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. + let args_hash = arguments::pack_arguments(self.args); + context.call_public_function_with_packed_args( + self.target_contract, + self.selector, + args_hash, + /*static=*/ true, + /*delegate=*/ false + ) + } +} 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 index 0000b903f6d..7361af69643 100644 --- 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 @@ -3,6 +3,7 @@ use dep::protocol_types::traits::Empty; struct AvmContextInputs { selector: Field, args_hash: Field, + is_static_call: bool } impl Empty for AvmContextInputs { @@ -10,6 +11,7 @@ impl Empty for AvmContextInputs { AvmContextInputs { selector: 0, args_hash: 0, + is_static_call: false } } } diff --git a/noir-projects/aztec-nr/aztec/src/context/interface.nr b/noir-projects/aztec-nr/aztec/src/context/interface.nr index 0ceb66a05a8..37bf13318d0 100644 --- a/noir-projects/aztec-nr/aztec/src/context/interface.nr +++ b/noir-projects/aztec-nr/aztec/src/context/interface.nr @@ -1,9 +1,5 @@ -use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}, traits::Deserialize}; +use dep::protocol_types::{abis::function_selector::FunctionSelector, address::{AztecAddress, EthAddress}}; -use crate::oracle::arguments; -use crate::context::private_context::PrivateContext; -use crate::context::public_context::PublicContext; -use crate::context::avm_context::AvmContext; use crate::context::gas::GasOpts; use crate::context::public_context::FunctionReturns; @@ -54,280 +50,3 @@ trait PublicContextInterface { ) -> FunctionReturns; fn nullifier_exists(self, unsiloed_nullifier: Field, address: AztecAddress) -> bool; } - -struct PrivateCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args_hash: Field, -} - -impl PrivateCallInterface { - pub fn call(self, context: &mut PrivateContext) -> T where T: Deserialize { - let returns = context.call_private_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ); - let unpacked: T = returns.unpack_into(); - unpacked - } - - pub fn static_call(self, context: &mut PrivateContext) -> T where T: Deserialize { - let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); - returns.unpack_into() - } - - pub fn delegate_call(self, context: &mut PrivateContext) -> T where T: Deserialize { - let returns = context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); - returns.unpack_into() - } -} - -struct PrivateVoidCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args_hash: Field, -} - -impl PrivateVoidCallInterface { - pub fn call(self, context: &mut PrivateContext) { - context.call_private_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ).assert_empty(); - } - - pub fn static_call(self, context: &mut PrivateContext) { - context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); - } - - pub fn delegate_call(self, context: &mut PrivateContext) { - context.call_private_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty(); - } -} - -struct PublicCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args_hash: Field, -} - -impl PublicCallInterface { - pub fn call(self, context: &mut PublicContext) -> T where T: Deserialize { - let returns = context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ); - returns.deserialize_into() - } - - pub fn static_call(self, context: &mut PublicContext) -> T where T: Deserialize { - let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false); - returns.deserialize_into() - } - - pub fn delegate_call(self, context: &mut PublicContext) -> T where T: Deserialize { - let returns = context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true); - returns.deserialize_into() - } - - pub fn enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ) - } - - pub fn static_enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) - } - - pub fn delegate_enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true) - } -} - -struct PublicVoidCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args_hash: Field -} - -impl PublicVoidCallInterface { - pub fn call(self, context: &mut PublicContext) { - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ).assert_empty() - } - - pub fn static_call(self, context: &mut PublicContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false).assert_empty(); - } - - pub fn delegate_call(self, context: &mut PublicContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true).assert_empty(); - } - - pub fn enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - self.args_hash, - false, - false - ) - } - - pub fn static_enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, true, false) - } - - pub fn delegate_enqueue(self, context: &mut PrivateContext) { - context.call_public_function_with_packed_args(self.target_contract, self.selector, self.args_hash, false, true) - } -} - -struct AvmCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args: [Field], - gas_opts: GasOpts, -} - -impl AvmCallInterface { - pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { - self.gas_opts = gas_opts; - self - } - - pub fn call(self, context: &mut AvmContext) -> T where T: Deserialize { - let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.deserialize_into() - } - - pub fn static_call(self, context: &mut AvmContext) -> T where T: Deserialize { - let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.deserialize_into() - } - - pub fn delegate_call(self, context: &mut AvmContext) -> T where T: Deserialize { - let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); - returns.deserialize_into() - } - - pub fn enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ false, - /*delegate=*/ false - ) - } - - pub fn static_enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ true, - /*delegate=*/ false - ) - } - - pub fn delegate_enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ false, - /*delegate=*/ true - ) - } -} - -struct AvmVoidCallInterface { - target_contract: AztecAddress, - selector: FunctionSelector, - args: [Field], - gas_opts: GasOpts, -} - -impl AvmVoidCallInterface { - pub fn with_gas(self: &mut Self, gas_opts: GasOpts) -> &mut Self { - self.gas_opts = gas_opts; - self - } - - pub fn call(self, context: &mut AvmContext) { - let returns = context.call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.assert_empty() - } - - pub fn static_call(self, context: &mut AvmContext) { - let returns = context.static_call_public_function(self.target_contract, self.selector, self.args, self.gas_opts); - returns.assert_empty() - } - - pub fn delegate_call(self, context: &mut AvmContext) { - let returns = context.delegate_call_public_function(self.target_contract, self.selector, self.args); - returns.assert_empty() - } - - pub fn enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ false, - /*delegate=*/ false - ) - } - - pub fn static_enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ true, - /*delegate=*/ false - ) - } - - pub fn delegate_enqueue(self, context: &mut PrivateContext) { - // This packing is only here because PrivateContext's call_public* functions do not accept a slice for the args. - let args_hash = arguments::pack_arguments(self.args); - context.call_public_function_with_packed_args( - self.target_contract, - self.selector, - args_hash, - /*static=*/ false, - /*delegate=*/ true - ) - } -} diff --git a/noir-projects/aztec-nr/aztec/src/context/private_context.nr b/noir-projects/aztec-nr/aztec/src/context/private_context.nr index 9722d8aecbf..645ea474ad2 100644 --- a/noir-projects/aztec-nr/aztec/src/context/private_context.nr +++ b/noir-projects/aztec-nr/aztec/src/context/private_context.nr @@ -440,7 +440,7 @@ impl PrivateContext { assert(args_hash == item.public_inputs.args_hash); - // Assert that the call context of the enqueued call generated by the oracle matches our request. + // Assert that the call context of the call generated by the oracle matches our request. assert(item.public_inputs.call_context.is_delegate_call == is_delegate_call); assert(item.public_inputs.call_context.is_static_call == is_static_call); diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 38a37c0083b..12fbe295874 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -41,4 +41,6 @@ members = [ "contracts/uniswap_contract", "contracts/reader_contract", "contracts/multi_call_entrypoint_contract", + "contracts/static_child_contract", + "contracts/static_parent_contract" ] diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/dapp_payload.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/dapp_payload.nr index 8d6e9711a4d..4b0d7e4af7b 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/dapp_payload.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/dapp_payload.nr @@ -3,12 +3,11 @@ use dep::aztec::protocol_types::{constants::GENERATOR_INDEX__SIGNATURE_PAYLOAD, use dep::authwit::entrypoint::function_call::{FunctionCall, FUNCTION_CALL_SIZE_IN_BYTES}; +global DAPP_MAX_CALLS: u64 = 1; // FUNCTION_CALL_SIZE * DAPP_MAX_CALLS + 1 -global DAPP_PAYLOAD_SIZE: u64 = 5; +global DAPP_PAYLOAD_SIZE: u64 = 6; // FUNCTION_CALL_SIZE_IN_BYTES * DAPP_MAX_CALLS + 32 -global DAPP_PAYLOAD_SIZE_IN_BYTES: u64 = 129; - -global DAPP_MAX_CALLS: u64 = 1; +global DAPP_PAYLOAD_SIZE_IN_BYTES: u64 = 130; // Note: If you change the following struct you have to update default_entrypoint.ts // docs:start:app-payload-struct @@ -55,7 +54,8 @@ impl DAppPayload { // Executes all private and public calls // docs:start:entrypoint-execute-calls fn execute_calls(self, context: &mut PrivateContext, target_address: AztecAddress) { - for call in self.function_calls { + for i in 0..DAPP_MAX_CALLS { + let call = self.function_calls[i]; // whitelist the calls that the user can do only go to the expected Dapp contract assert(call.target_address == target_address); if call.is_public { @@ -63,7 +63,7 @@ impl DAppPayload { call.target_address, call.function_selector, call.args_hash, - false, + call.is_static, false ); } else { @@ -71,7 +71,7 @@ impl DAppPayload { call.target_address, call.function_selector, call.args_hash, - false, + call.is_static, false ); } diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index 25bcda79fc0..a232484e17d 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -56,7 +56,7 @@ contract AppSubscription { context.end_setup(); - AppSubscription::at(context.this_address()).assert_not_expired(note.expiry_block_number).static_enqueue(&mut context); + AppSubscription::at(context.this_address()).assert_not_expired(note.expiry_block_number).enqueue_view(&mut context); payload.execute_calls(&mut context, storage.target_address.read_private()); } @@ -81,12 +81,14 @@ contract AppSubscription { #[aztec(public)] #[aztec(internal)] + #[aztec(view)] fn assert_not_expired(expiry_block_number: Field) { assert((context.block_number()) as u64 < expiry_block_number as u64); } #[aztec(public)] #[aztec(internal)] + #[aztec(view)] fn assert_block_number(expiry_block_number: Field) { assert( (context.block_number() + SUBSCRIPTION_DURATION_IN_BLOCKS) as u64 @@ -111,7 +113,7 @@ contract AppSubscription { ).call(&mut context); // Assert that the given expiry_block_number < current_block_number + SUBSCRIPTION_DURATION_IN_BLOCKS. - AppSubscription::at(context.this_address()).assert_block_number(expiry_block_number).static_enqueue(&mut context); + AppSubscription::at(context.this_address()).assert_block_number(expiry_block_number).enqueue_view(&mut context); let subscriber_npk_m_hash = get_npk_m_hash(&mut context, subscriber_address); let subscriber_ivpk_m = get_ivpk_m(&mut context, subscriber_address); @@ -124,7 +126,7 @@ contract AppSubscription { } // Compiler bug workaround. You can't call an unconstrained function in another module, unless its from an - // unconstained function in your module. + // unconstrained function in your module. unconstrained fn is_initialized(subscriber_address: AztecAddress) -> pub bool { storage.subscriptions.at(subscriber_address).is_initialized() } 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 index a21791aaee4..a62fc79b5ec 100644 --- 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 @@ -58,13 +58,13 @@ contract AvmNestedCallsTest { // 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 { - AvmNestedCallsTest::at(context.this_address()).add_args_return(arg_a, arg_b).static_call(&mut context) + AvmNestedCallsTest::at(context.this_address()).add_args_return(arg_a, arg_b).view(&mut context) } // Indirectly call_static `set_storage_single`. Should revert since it's accessing storage. #[aztec(public-vm)] fn nested_static_call_to_set_storage() { - AvmNestedCallsTest::at(context.this_address()).set_storage_single(20).static_call(&mut context); + AvmNestedCallsTest::at(context.this_address()).set_storage_single(20).view(&mut context); } #[aztec(public-vm)] 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 90747ecab5c..36873f06d8b 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 @@ -377,7 +377,7 @@ contract AvmTest { */ #[aztec(private)] fn enqueue_public_from_private() { - AvmTest::at(context.this_address()).set_opcode_u8().static_enqueue(&mut context); + AvmTest::at(context.this_address()).set_opcode_u8().enqueue_view(&mut context); AvmTest::at(context.this_address()).set_read_storage_single(5).enqueue(&mut context); } } diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index 9eef23a50bd..88bc4a0c54b 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -60,6 +60,7 @@ contract Crowdfunding { // docs:start:deadline-header #[aztec(public)] #[aztec(internal)] + #[aztec(view)] fn _check_deadline() { // docs:end:deadline-header let deadline = storage.deadline.read(); @@ -72,7 +73,7 @@ contract Crowdfunding { #[aztec(private)] fn donate(amount: u64) { // 1) Check that the deadline has not passed - Crowdfunding::at(context.this_address())._check_deadline().static_enqueue(&mut context); + Crowdfunding::at(context.this_address())._check_deadline().enqueue_view(&mut context); // docs:end:call-check-deadline // docs:start:do-transfer diff --git a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr index d2ed49d84ec..7723b789a26 100644 --- a/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -117,7 +117,7 @@ contract DocsExample { // and returns the response. // Used to test that we can retrieve values through calls and // correctly return them in the simulation - let mut leader = DocsExample::at(context.this_address()).get_shared_immutable_constrained_private().static_call(&mut context); + let mut leader = DocsExample::at(context.this_address()).get_shared_immutable_constrained_private().view(&mut context); leader.points += 1; leader } @@ -128,12 +128,13 @@ contract DocsExample { // and returns the response. // Used to test that we can retrieve values through calls and // correctly return them in the simulation - let mut leader = DocsExample::at(context.this_address()).get_shared_immutable_constrained_public().static_call(&mut context); + let mut leader = DocsExample::at(context.this_address()).get_shared_immutable_constrained_public().view(&mut context); leader.points += 1; leader } #[aztec(public)] + #[aztec(view)] fn get_shared_immutable_constrained_public() -> pub Leader { storage.shared_immutable.read_public() } @@ -145,6 +146,7 @@ contract DocsExample { } #[aztec(private)] + #[aztec(view)] fn get_shared_immutable_constrained_private() -> pub Leader { storage.shared_immutable.read_private() } diff --git a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr index c640a523829..1817bc8947d 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -151,7 +151,7 @@ contract Lending { #[aztec(internal)] fn _withdraw(owner: AztecAddress, recipient: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); - let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; + let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; let coll_loc = storage.collateral.at(owner); let collateral: Field = coll_loc.read(); @@ -199,7 +199,7 @@ contract Lending { #[aztec(internal)] fn _borrow(owner: AztecAddress, to: AztecAddress, amount: Field) { let asset = Lending::at(context.this_address()).update_accumulator().call(&mut context); - let price = PriceFeed::at(asset.oracle).get_price(0).static_call(&mut context).price; + let price = PriceFeed::at(asset.oracle).get_price(0).view(&mut context).price; // Fetch collateral and static_debt, compute health of current position let collateral = U128::from_integer(storage.collateral.at(owner).read()); diff --git a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr index c4afaacf753..6aab33130b7 100644 --- a/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/price_feed_contract/src/main.nr @@ -18,6 +18,7 @@ contract PriceFeed { } #[aztec(public)] + #[aztec(view)] fn get_price(asset_id: Field) -> Asset { storage.assets.at(asset_id).read() } diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/static_child_contract/Nargo.toml new file mode 100644 index 00000000000..6c3ec004fe8 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/static_child_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "static_child_contract" +type = "contract" +authors = [""] +compiler_version = ">=0.28.0" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } +value_note = { path = "../../../aztec-nr/value-note" } diff --git a/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr new file mode 100644 index 00000000000..f6f7b7e79c7 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/static_child_contract/src/main.nr @@ -0,0 +1,142 @@ +// A contract used along with `StaticParent` contract to test static calls. +contract StaticChild { + use dep::aztec::prelude::{AztecAddress, FunctionSelector, PublicMutable, PrivateSet, PrivateContext, Deserialize}; + + use dep::aztec::{ + context::{PublicContext, Context, gas::GasOpts}, + protocol_types::{abis::{call_context::CallContext}}, + note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader}, + keys::getters::{get_npk_m_hash, get_ivpk_m} + }; + use dep::value_note::value_note::ValueNote; + + #[aztec(storage)] + struct Storage { + current_value: PublicMutable, + a_private_value: PrivateSet, + } + + // Returns base_value + chain_id + version + block_number + timestamp statically + #[aztec(public)] + #[aztec(view)] + fn pub_get_value(base_value: Field) -> Field { + let return_value = base_value + + context.chain_id() + + context.version() + + context.block_number() + + context.timestamp() as Field; + + return_value + } + + // Sets `current_value` to `new_value` + #[aztec(public)] + fn pub_set_value(new_value: Field) -> Field { + storage.current_value.write(new_value); + context.emit_unencrypted_log(new_value); + + new_value + } + + // View function that attempts to modify state. Should always fail regardless how it's called. + #[aztec(private)] + #[aztec(view)] + fn private_illegal_set_value(new_value: Field, owner: AztecAddress) -> Field { + let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); + let owner_ivpk_m = get_ivpk_m(&mut context, owner); + let mut note = ValueNote::new(new_value, owner_npk_m_hash); + storage.a_private_value.insert(&mut note, true, owner_ivpk_m); + new_value + } + + // Modify a note + #[aztec(private)] + fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { + let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); + let owner_ivpk_m = get_ivpk_m(&mut context, owner); + let mut note = ValueNote::new(new_value, owner_npk_m_hash); + storage.a_private_value.insert(&mut note, true, owner_ivpk_m); + new_value + } + + // Retrieve note value statically + #[aztec(private)] + #[aztec(view)] + fn private_get_value(amount: Field, owner: AztecAddress) -> Field { + let owner_npk_m_hash = get_npk_m_hash(&mut context, owner); + let mut options = NoteGetterOptions::new(); + options = options.select(ValueNote::properties().value, amount, Option::none()).select( + ValueNote::properties().npk_m_hash, + owner_npk_m_hash, + Option::none() + ).set_limit(1); + let notes = storage.a_private_value.get_notes(options); + notes[0].unwrap_unchecked().value + } + + // Increments `current_value` by `new_value` + #[aztec(public)] + fn pub_inc_value(new_value: Field) -> Field { + let old_value = storage.current_value.read(); + storage.current_value.write(old_value + new_value); + context.emit_unencrypted_log(new_value); + + new_value + } + + // View function that attempts to modify state. Should always fail regardless how it's called. + #[aztec(public)] + #[aztec(view)] + fn pub_illegal_inc_value(new_value: Field) -> Field { + let old_value = storage.current_value.read(); + storage.current_value.write(old_value + new_value); + context.emit_unencrypted_log(new_value); + + new_value + } + + // AVM + + // Returns base_value + chain_id + version + block_number + timestamp statically + #[aztec(public-vm)] + #[aztec(view)] + fn avm_get_value(base_value: Field) -> Field { + let return_value = base_value + + context.chain_id() + + context.version() + + context.block_number() + + context.timestamp() as Field; + + return_value + } + + // Sets `current_value` to `new_value` + #[aztec(public-vm)] + fn avm_set_value(new_value: Field) -> Field { + storage.current_value.write(new_value); + context.emit_unencrypted_log(new_value); + + new_value + } + + // Increments `current_value` by `new_value` + #[aztec(public-vm)] + fn avm_inc_value(new_value: Field) -> Field { + let old_value = storage.current_value.read(); + storage.current_value.write(old_value + new_value); + context.emit_unencrypted_log(new_value); + + new_value + } + + // View function that attempts to modify state. Should always fail regardless how it's called. + #[aztec(public-vm)] + #[aztec(view)] + fn avm_illegal_inc_value(new_value: Field) -> Field { + let old_value = storage.current_value.read(); + storage.current_value.write(old_value + new_value); + context.emit_unencrypted_log(new_value); + + new_value + } +} diff --git a/noir-projects/noir-contracts/contracts/static_parent_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/static_parent_contract/Nargo.toml new file mode 100644 index 00000000000..31ad33000b7 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/static_parent_contract/Nargo.toml @@ -0,0 +1,9 @@ +[package] +name = "static_parent_contract" +type = "contract" +authors = [""] +compiler_version = ">=0.28.0" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } +static_child_contract = { path = "../static_child_contract"} \ No newline at end of file diff --git a/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr new file mode 100644 index 00000000000..e03529b57d0 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/static_parent_contract/src/main.nr @@ -0,0 +1,196 @@ + +// A contract used along with `StaticChild` contract to test static calls. +contract StaticParent { + use dep::aztec::prelude::{AztecAddress, FunctionSelector, Deserialize}; + use dep::aztec::context::gas::GasOpts; + use dep::static_child_contract::StaticChild; + + // Public function to directly call another public function to the target_contract using the selector and value provided + #[aztec(public)] + fn public_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + arg: Field + ) -> Field { + context.call_public_function( + target_contract, + target_selector, + [arg].as_slice(), + GasOpts::default() + ).deserialize_into() + } + + // Private function to directly call another private function to the target_contract using the selector and args provided + #[aztec(private)] + fn private_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 2] + ) -> Field { + context.call_private_function(target_contract, target_selector, args).unpack_into() + } + + // Private function to enqueue a call to a public function of another contract, passing the target arguments provided + #[aztec(private)] + fn enqueue_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) { + context.call_public_function(target_contract, target_selector, args); + } + + // Private function to statically call another private function to the target_contract using the selector and values provided + #[aztec(private)] + fn private_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 2] + ) -> Field { + context.static_call_private_function(target_contract, target_selector, args).unpack_into() + } + + // Same as above but using a specific function from the interface + #[aztec(private)] + fn private_get_value_from_child( + target_contract: AztecAddress, + value: Field, + owner: AztecAddress + ) -> Field { + StaticChild::at(target_contract).private_get_value(value, owner).view(&mut context) + } + + // Private function to set a static context and verify correct propagation for nested private calls + #[aztec(private)] + fn private_nested_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 2] + ) -> Field { + StaticParent::at(context.this_address()).private_call(target_contract, target_selector, args).view(&mut context) + } + + // Public function to statically call another public function to the target_contract using the selector and value provided + #[aztec(public)] + fn public_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) -> Field { + context.static_call_public_function( + target_contract, + target_selector, + args.as_slice(), + GasOpts::default() + ).deserialize_into() + } + + // Same as above but using a specific function from the interface + #[aztec(public)] + fn public_get_value_from_child(target_contract: AztecAddress, value: Field) -> Field { + StaticChild::at(target_contract).pub_get_value(value).view(&mut context) + } + + // Public function to set a static context and verify correct propagation for nested public calls + #[aztec(public)] + fn public_nested_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) -> Field { + // Call the target public function through the pub entrypoint statically + StaticParent::at(context.this_address()).public_call(target_contract, target_selector, args[0]).view(&mut context) + } + + // Private function to enqueue a static call to a public function of another contract, passing the target arguments provided + #[aztec(private)] + fn enqueue_static_call_to_pub_function( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) { + context.static_call_public_function(target_contract, target_selector, args); + } + + // Same as above but using a specific function from the interface + #[aztec(private)] + fn enqueue_public_get_value_from_child(target_contract: AztecAddress, value: Field) { + StaticChild::at(target_contract).pub_get_value(value).enqueue_view(&mut context); + } + + // Private function to set a static context and verify correct propagation of nested enqueuing of public calls + #[aztec(private)] + fn enqueue_static_nested_call_to_pub_function( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) { + // Call the target public function through the pub entrypoint statically + StaticParent::at(context.this_address()).public_call(target_contract, target_selector, args[0]).enqueue_view(&mut context) + } + + // AVM + + #[aztec(public-vm)] + fn avm_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + arg: Field + ) -> Field { + context.call_public_function( + target_contract, + target_selector, + [arg].as_slice(), + GasOpts::default() + ).deserialize_into() + } + + // Public function to statically call another public function to the target_contract using the selector and value provided + #[aztec(public-vm)] + fn avm_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) -> Field { + context.static_call_public_function( + target_contract, + target_selector, + args.as_slice(), + GasOpts::default() + ).deserialize_into() + } + + // Same as above but using a specific function from the interface + #[aztec(public-vm)] + fn avm_get_value_from_child(target_contract: AztecAddress, value: Field) -> Field { + StaticChild::at(target_contract).avm_get_value(value).view(&mut context) + } + + // Public function to set a static context and verify correct propagation for nested public calls + #[aztec(public-vm)] + fn avm_nested_static_call( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) -> Field { + // Call the target public function through the pub entrypoint statically + StaticParent::at(context.this_address()).avm_call(target_contract, target_selector, args[0]).view(&mut context) + } + + // Same as above but using a specific function from the interface + #[aztec(private)] + fn enqueue_avm_get_value_from_child(target_contract: AztecAddress, value: Field) { + StaticChild::at(target_contract).avm_get_value(value).enqueue_view(&mut context); + } + + // Private function to set a static context and verify correct propagation of nested enqueuing of public calls + #[aztec(private)] + fn enqueue_static_nested_call_to_avm_function( + target_contract: AztecAddress, + target_selector: FunctionSelector, + args: [Field; 1] + ) { + // Call the target public function through the pub entrypoint statically + StaticParent::at(context.this_address()).avm_call(target_contract, target_selector, args[0]).enqueue_view(&mut context) + } +} diff --git a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr index afacb220568..0ccd4b5a74b 100644 --- a/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/uniswap_contract/src/main.nr @@ -57,7 +57,7 @@ contract Uniswap { assert_current_call_valid_authwit_public(&mut context, sender); } - let input_asset = TokenBridge::at(input_asset_bridge).get_token().static_call(&mut context); + let input_asset = TokenBridge::at(input_asset_bridge).get_token().view(&mut context); // Transfer funds to this contract Token::at(input_asset).transfer_public( @@ -71,8 +71,8 @@ contract Uniswap { Uniswap::at(context.this_address())._approve_bridge_and_exit_input_asset_to_L1(input_asset, input_asset_bridge, input_amount).call(&mut context); // Create swap message and send to Outbox for Uniswap Portal // this ensures the integrity of what the user originally intends to do on L1. - let input_asset_bridge_portal_address = TokenBridge::at(input_asset_bridge).get_portal_address_public().static_call(&mut context); - let output_asset_bridge_portal_address = TokenBridge::at(output_asset_bridge).get_portal_address_public().static_call(&mut context); + let input_asset_bridge_portal_address = TokenBridge::at(input_asset_bridge).get_portal_address_public().view(&mut context); + let output_asset_bridge_portal_address = TokenBridge::at(output_asset_bridge).get_portal_address_public().view(&mut context); // ensure portal exists - else funds might be lost assert( !input_asset_bridge_portal_address.is_zero(), "L1 portal address of input_asset's bridge is 0" @@ -114,7 +114,7 @@ contract Uniswap { ) { // Assert that user provided token address is same as expected by token bridge. // we can't directly use `input_asset_bridge.token` because that is a public method and public can't return data to private - Uniswap::at(context.this_address())._assert_token_is_same(input_asset, input_asset_bridge).static_enqueue(&mut context); + Uniswap::at(context.this_address())._assert_token_is_same(input_asset, input_asset_bridge).enqueue_view(&mut context); // Transfer funds to this contract Token::at(input_asset).unshield( @@ -129,8 +129,8 @@ contract Uniswap { // Create swap message and send to Outbox for Uniswap Portal // this ensures the integrity of what the user originally intends to do on L1. - let input_asset_bridge_portal_address = TokenBridge::at(input_asset_bridge).get_portal_address().static_call(&mut context); - let output_asset_bridge_portal_address = TokenBridge::at(output_asset_bridge).get_portal_address().static_call(&mut context); + let input_asset_bridge_portal_address = TokenBridge::at(input_asset_bridge).get_portal_address().view(&mut context); + let output_asset_bridge_portal_address = TokenBridge::at(output_asset_bridge).get_portal_address().view(&mut context); // ensure portal exists - else funds might be lost assert( !input_asset_bridge_portal_address.is_zero(), "L1 portal address of input_asset's bridge is 0" @@ -218,9 +218,10 @@ contract Uniswap { // docs:start:assert_token_is_same #[aztec(public)] #[aztec(internal)] + #[aztec(view)] fn _assert_token_is_same(token: AztecAddress, token_bridge: AztecAddress) { assert( - token.eq(TokenBridge::at(token_bridge).get_token().static_call(&mut context)), "input_asset address is not the same as seen in the bridge contract" + token.eq(TokenBridge::at(token_bridge).get_token().view(&mut context)), "input_asset address is not the same as seen in the bridge contract" ); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_data.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_data.nr index 2a4148182ca..f123bb616a6 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_data.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/function_data.nr @@ -7,12 +7,14 @@ use crate::{ struct FunctionData { selector : FunctionSelector, is_private : bool, + is_static : bool, } impl Eq for FunctionData { fn eq(self, other: Self) -> bool { self.selector.eq(other.selector) & - self.is_private == other.is_private + (self.is_private == other.is_private) & + (self.is_static == other.is_static) } } @@ -24,6 +26,7 @@ impl Serialize for FunctionData { [ self.selector.to_field(), self.is_private as Field, + self.is_static as Field ] } } @@ -33,6 +36,7 @@ impl Deserialize for FunctionData { Self { selector: FunctionSelector::from_field(serialized[0]), is_private: serialized[1] as bool, + is_static: serialized[2] as bool } } } @@ -48,8 +52,10 @@ impl Empty for FunctionData { FunctionData { selector: FunctionSelector::empty(), is_private: false, + is_static: false } } + } #[test] @@ -66,6 +72,6 @@ fn empty_hash() { let hash = data.hash(); // Value from function_data.test.ts "computes empty function data hash" test - let test_data_empty_hash = 0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed; + let test_data_empty_hash = 0x066e6cdc4a6ba5e4781deda650b0be6c12f975f064fc38df72c1060716759b17; assert_eq(hash, test_data_empty_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_call_stack_item.nr index 18278e5b3d2..2913e535363 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_call_stack_item.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/private_call_stack_item.nr @@ -85,6 +85,6 @@ fn empty_hash() { let hash = item.hash(); // Value from private_call_stack_item.test.ts "computes empty item hash" test - let test_data_empty_hash = 0x1eaa8a277851ba8de6f7630ec75a2324e03a00a6ee99f24dd834faa422bdee4f; + let test_data_empty_hash = 0x29d77b0175116357d39252de002a2944652f87ccb4404f9346bff6d44f020f7f; assert_eq(hash, test_data_empty_hash); } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr index 7757259ed7f..f8aefb791ed 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/abis/public_call_stack_item.nr @@ -58,7 +58,7 @@ mod tests { #[test] fn compute_call_stack_item_request_hash() { let contract_address = AztecAddress::from_field(1); - let function_data = FunctionData { selector: FunctionSelector::from_u32(2), is_private: false }; + let function_data = FunctionData { selector: FunctionSelector::from_u32(2), is_private: false, is_static: false }; let mut public_inputs = PublicCircuitPublicInputs::empty(); public_inputs.new_note_hashes[0] = NoteHash { @@ -69,14 +69,14 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: true, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item request hash" test - let test_data_call_stack_item_request_hash = 0x1fe90f27924bcd761257c1b4570f5937b6dabcb4b6047ff668a770dea8e13533; + let test_data_call_stack_item_request_hash = 0x0230a99218df238d4a28664ee700e10a4fdfe98e4a2f5c678bce8c95d5fa04b1; assert_eq(call_stack_item.hash(), test_data_call_stack_item_request_hash); } #[test] fn compute_call_stack_item_hash() { let contract_address = AztecAddress::from_field(1); - let function_data = FunctionData { selector: FunctionSelector::from_u32(2), is_private: false }; + let function_data = FunctionData { selector: FunctionSelector::from_u32(2), is_private: false, is_static: false }; let mut public_inputs = PublicCircuitPublicInputs::empty(); public_inputs.new_note_hashes[0] = NoteHash { @@ -87,7 +87,7 @@ mod tests { let call_stack_item = PublicCallStackItem { contract_address, public_inputs, is_execution_request: false, function_data }; // Value from public_call_stack_item.test.ts "Computes a callstack item hash" test - let test_data_call_stack_item_hash = 0x2bb94c518916df51853784f16991e7691eddd452831ee1197cd29cdfb492d7b8; + let test_data_call_stack_item_hash = 0x1bda75e8d4cf46fd126d14d6e28d1dc8ff3860b49ff59edd7da8bfeee909aef3; assert_eq(call_stack_item.hash(), test_data_call_stack_item_hash); } } diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr index ef19cbe4547..7173fcc77a2 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/constants.nr @@ -150,7 +150,7 @@ global CONTRACT_INSTANCE_LENGTH: u64 = 5; global CONTRACT_STORAGE_READ_LENGTH: u64 = 2; global CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH: u64 = 2; global ETH_ADDRESS_LENGTH = 1; -global FUNCTION_DATA_LENGTH: u64 = 2; +global FUNCTION_DATA_LENGTH: u64 = 3; global FUNCTION_LEAF_PREIMAGE_LENGTH: u64 = 5; global GLOBAL_VARIABLES_LENGTH: u64 = 6 + GAS_FEES_LENGTH; global APPEND_ONLY_TREE_SNAPSHOT_LENGTH = 2; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contract_functions.nr b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contract_functions.nr index 836e673b5e1..bae6a2c40b0 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contract_functions.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/tests/fixtures/contract_functions.nr @@ -14,6 +14,7 @@ global default_private_function = ContractFunction { data: FunctionData { selector: FunctionSelector { inner: 1010101 }, is_private: true, + is_static: false }, vk_hash: 0, acir_hash: 1111, @@ -33,6 +34,7 @@ global default_public_function = ContractFunction { data: FunctionData { selector: FunctionSelector { inner: 3030303 }, is_private: false, + is_static: false }, vk_hash: 0, acir_hash: 3333, diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr b/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr index 5d804fa5d0a..6e261adea38 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/transaction/tx_request.nr @@ -95,10 +95,10 @@ mod tests { origin: AztecAddress::from_field(1), args_hash: 3, tx_context: TxContext { chain_id: 0, version: 0, gas_settings }, - function_data: FunctionData { selector: FunctionSelector::from_u32(2), is_private: true } + function_data: FunctionData { selector: FunctionSelector::from_u32(2), is_private: true, is_static: false } }; // Value from tx_request.test.ts "compute hash" test - let test_data_tx_request_hash = 0x0d982a1c7a65919b2bcacbf5660f5b861132072a8190d97c82b1591da402f5ea; + let test_data_tx_request_hash = 0x249ca45ef4045007021de437e14bf865445f63af6ab5dcc8d78d2b3aa7c58c74; assert(tx_request.hash() == test_data_tx_request_hash); } } diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 5f0326bea5a..c3638b7760d 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -1,6 +1,7 @@ mod transforms; mod utils; +use noirc_errors::Location; use transforms::{ compute_note_hash_and_nullifier::inject_compute_note_hash_and_nullifier, contract_interface::{ @@ -66,6 +67,7 @@ fn transform( for submodule in ast.submodules.iter_mut().filter(|submodule| submodule.is_contract) { if transform_module( crate_id, + &file_id, context, &mut submodule.contents, submodule.name.0.contents.as_str(), @@ -86,6 +88,7 @@ fn transform( /// Returns true if an annotated node is found, false otherwise fn transform_module( crate_id: &CrateId, + file_id: &FileId, context: &HirContext, module: &mut SortedModule, module_name: &str, @@ -134,6 +137,7 @@ fn transform_module( let mut is_initializer = false; let mut is_internal = false; let mut insert_init_check = has_initializer; + let mut is_static = false; for secondary_attribute in func.def.attributes.secondary.clone() { if is_custom_attribute(&secondary_attribute, "aztec(private)") { @@ -150,6 +154,9 @@ fn transform_module( } else if is_custom_attribute(&secondary_attribute, "aztec(public-vm)") { is_public_vm = true; } + if is_custom_attribute(&secondary_attribute, "aztec(view)") { + is_static = true; + } } // Apply transformations to the function based on collected attributes @@ -161,7 +168,8 @@ fn transform_module( } else { "Public" }; - stubs.push(stub_function(fn_type, func)); + let stub_src = stub_function(fn_type, func, is_static); + stubs.push((stub_src, Location { file: *file_id, span: func.name_ident().span() })); export_fn_abi(&mut module.types, func)?; transform_function( @@ -171,6 +179,7 @@ fn transform_module( is_initializer, insert_init_check, is_internal, + is_static, )?; has_transformed_module = true; } else if storage_defined && func.def.is_unconstrained { diff --git a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs index 921bad4b612..bb63357d251 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/contract_interface.rs @@ -1,4 +1,5 @@ -use noirc_frontend::ast::{NoirFunction, UnresolvedTypeData}; +use noirc_errors::Location; +use noirc_frontend::ast::{Ident, NoirFunction, UnresolvedTypeData}; use noirc_frontend::{ graph::CrateId, macros_api::{FileId, HirContext, HirExpression, HirLiteral, HirStatement}, @@ -39,7 +40,7 @@ use crate::utils::{ // } // // The selector placeholder has to be replaced with the actual function signature after type checking in the next macro pass -pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { +pub fn stub_function(aztec_visibility: &str, func: &NoirFunction, is_static_call: bool) -> String { let fn_name = func.name().to_string(); let fn_parameters = func .parameters() @@ -59,6 +60,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { let parameters = func.parameters(); let is_void = if matches!(fn_return_type.typ, UnresolvedTypeData::Unit) { "Void" } else { "" }; + let is_static = if is_static_call { "Static" } else { "" }; let return_type_hint = if is_void == "Void" { "".to_string() } else { @@ -101,18 +103,18 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { let fn_body = format!( "{} - dep::aztec::context::{}{}CallInterface {{ + dep::aztec::context::{}{}{}CallInterface {{ target_contract: self.target_contract, selector: {}, args_hash, }}", - args_hash, aztec_visibility, is_void, fn_selector, + args_hash, aztec_visibility, is_static, is_void, fn_selector, ); format!( - "pub fn {}(self, {}) -> dep::aztec::context::{}{}CallInterface{} {{ + "pub fn {}(self, {}) -> dep::aztec::context::{}{}{}CallInterface{} {{ {} }}", - fn_name, fn_parameters, aztec_visibility, is_void, return_type_hint, fn_body + fn_name, fn_parameters, aztec_visibility, is_static, is_void, return_type_hint, fn_body ) } else { let args = format!( @@ -123,19 +125,19 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { ); let fn_body = format!( "{} - dep::aztec::context::Avm{}CallInterface {{ + dep::aztec::context::Avm{}{}CallInterface {{ target_contract: self.target_contract, selector: {}, args: args_acc, gas_opts: dep::aztec::context::gas::GasOpts::default(), }}", - args, is_void, fn_selector, + args, is_static, is_void, fn_selector, ); format!( - "pub fn {}(self, {}) -> dep::aztec::context::Avm{}CallInterface{} {{ + "pub fn {}(self, {}) -> dep::aztec::context::Avm{}{}CallInterface{} {{ {} }}", - fn_name, fn_parameters, is_void, return_type_hint, fn_body + fn_name, fn_parameters, is_static, is_void, return_type_hint, fn_body ) } } @@ -146,7 +148,7 @@ pub fn stub_function(aztec_visibility: &str, func: &NoirFunction) -> String { pub fn generate_contract_interface( module: &mut SortedModule, module_name: &str, - stubs: &[String], + stubs: &[(String, Location)], ) -> Result<(), AztecMacroError> { let contract_interface = format!( " @@ -172,7 +174,7 @@ pub fn generate_contract_interface( }} ", module_name, - stubs.join("\n"), + stubs.iter().map(|(src, _)| src.to_owned()).collect::>().join("\n"), ); let (contract_interface_ast, errors) = parse_program(&contract_interface); @@ -182,8 +184,27 @@ pub fn generate_contract_interface( } let mut contract_interface_ast = contract_interface_ast.into_sorted(); + let mut impl_with_locations = contract_interface_ast.impls.pop().unwrap(); + + impl_with_locations.methods = impl_with_locations + .methods + .iter() + .enumerate() + .map(|(i, (method, orig_span))| { + if method.name() == "at" { + (method.clone(), *orig_span) + } else { + let (_, new_location) = stubs[i]; + let mut modified_method = method.clone(); + modified_method.def.name = + Ident::new(modified_method.name().to_string(), new_location.span); + (modified_method, *orig_span) + } + }) + .collect(); + module.types.push(contract_interface_ast.types.pop().unwrap()); - module.impls.push(contract_interface_ast.impls.pop().unwrap()); + module.impls.push(impl_with_locations); module.functions.push(contract_interface_ast.functions.pop().unwrap()); Ok(()) diff --git a/noir/noir-repo/aztec_macros/src/transforms/functions.rs b/noir/noir-repo/aztec_macros/src/transforms/functions.rs index 487d15ceabe..45bce6a681a 100644 --- a/noir/noir-repo/aztec_macros/src/transforms/functions.rs +++ b/noir/noir-repo/aztec_macros/src/transforms/functions.rs @@ -10,6 +10,7 @@ use noirc_frontend::ast::{ use noirc_frontend::{macros_api::FieldElement, parse_program}; +use crate::utils::ast_utils::member_access; use crate::{ chained_dep, chained_path, utils::{ @@ -33,6 +34,7 @@ pub fn transform_function( is_initializer: bool, insert_init_check: bool, is_internal: bool, + is_static: bool, ) -> Result<(), AztecMacroError> { let context_name = format!("{}Context", ty); let inputs_name = format!("{}ContextInputs", ty); @@ -40,6 +42,12 @@ pub fn transform_function( let is_avm = ty == "Avm"; let is_private = ty == "Private"; + // Force a static context if the function is static + if is_static { + let is_static_check = create_static_check(func.name(), is_avm); + func.def.body.statements.insert(0, is_static_check); + } + // Add check that msg sender equals this address and flag function as internal if is_internal { let is_internal_check = create_internal_check(func.name()); @@ -275,6 +283,31 @@ fn create_mark_as_initialized(ty: &str) -> Statement { ))) } +/// Forces a static context for a function, ensuring that no state modifications are allowed +/// +/// ```noir +/// assert(context.inputs.call_context.is_static_call == true, "Function can only be called statically") +/// ``` +fn create_static_check(fname: &str, is_avm: bool) -> Statement { + let is_static_call_expr = if !is_avm { + ["inputs", "call_context", "is_static_call"] + .iter() + .fold(variable("context"), |acc, member| member_access(acc, member)) + } else { + ["inputs", "is_static_call"] + .iter() + .fold(variable("context"), |acc, member| member_access(acc, member)) + }; + make_statement(StatementKind::Constrain(ConstrainStatement( + make_eq(is_static_call_expr, expression(ExpressionKind::Literal(Literal::Bool(true)))), + Some(expression(ExpressionKind::Literal(Literal::Str(format!( + "Function {} can only be called statically", + fname + ))))), + ConstrainKind::Assert, + ))) +} + /// Creates a check for internal functions ensuring that the caller is self. /// /// ```noir diff --git a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs index ebb4854f86e..4e05dcaf619 100644 --- a/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs +++ b/noir/noir-repo/aztec_macros/src/utils/ast_utils.rs @@ -1,9 +1,9 @@ use noirc_errors::{Span, Spanned}; use noirc_frontend::ast::{ BinaryOpKind, CallExpression, CastExpression, Expression, ExpressionKind, FunctionReturnType, - Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MethodCallExpression, - NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind, TraitImplItem, - UnaryOp, UnresolvedType, UnresolvedTypeData, + Ident, IndexExpression, InfixExpression, Lambda, LetStatement, MemberAccessExpression, + MethodCallExpression, NoirTraitImpl, Path, Pattern, PrefixExpression, Statement, StatementKind, + TraitImplItem, UnaryOp, UnresolvedType, UnresolvedTypeData, }; use noirc_frontend::token::SecondaryAttribute; @@ -125,6 +125,13 @@ pub fn make_statement(kind: StatementKind) -> Statement { Statement { span: Span::default(), kind } } +pub fn member_access(lhs: Expression, member: &str) -> Expression { + expression(ExpressionKind::MemberAccess(Box::new(MemberAccessExpression { + lhs, + rhs: ident(member), + }))) +} + #[macro_export] macro_rules! chained_path { ( $base:expr ) => { diff --git a/yarn-project/aztec.js/src/contract/contract.test.ts b/yarn-project/aztec.js/src/contract/contract.test.ts index bcdac6b8dc9..c7e7db96a48 100644 --- a/yarn-project/aztec.js/src/contract/contract.test.ts +++ b/yarn-project/aztec.js/src/contract/contract.test.ts @@ -20,7 +20,7 @@ describe('Contract Class', () => { const mockTxRequest = { type: 'TxRequest' } as any as TxExecutionRequest; const mockTxHash = { type: 'TxHash' } as any as TxHash; const mockTxReceipt = { type: 'TxReceipt' } as any as TxReceipt; - const mockViewResultValue = 1; + const mockUnconstrainedResultValue = 1; const l1Addresses: L1ContractAddresses = { availabilityOracleAddress: EthAddress.random(), rollupAddress: EthAddress.random(), @@ -45,6 +45,7 @@ describe('Contract Class', () => { isInitializer: false, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, debugSymbols: '', parameters: [ { @@ -68,6 +69,7 @@ describe('Contract Class', () => { { name: 'baz', isInitializer: false, + isStatic: false, functionType: FunctionType.OPEN, isInternal: false, parameters: [], @@ -78,6 +80,7 @@ describe('Contract Class', () => { { name: 'qux', isInitializer: false, + isStatic: false, functionType: FunctionType.UNCONSTRAINED, isInternal: false, parameters: [ @@ -118,7 +121,7 @@ describe('Contract Class', () => { wallet.createTxExecutionRequest.mockResolvedValue(mockTxRequest); wallet.getContractInstance.mockResolvedValue(contractInstance); wallet.sendTx.mockResolvedValue(mockTxHash); - wallet.viewTx.mockResolvedValue(mockViewResultValue as any as DecodedReturn); + wallet.simulateUnconstrained.mockResolvedValue(mockUnconstrainedResultValue as any as DecodedReturn); wallet.getTxReceipt.mockResolvedValue(mockTxReceipt); wallet.getNodeInfo.mockResolvedValue(mockNodeInfo); wallet.proveTx.mockResolvedValue(mockTx); @@ -145,9 +148,9 @@ describe('Contract Class', () => { const result = await fooContract.methods.qux(123n).simulate({ from: account.address, }); - expect(wallet.viewTx).toHaveBeenCalledTimes(1); - expect(wallet.viewTx).toHaveBeenCalledWith('qux', [123n], contractAddress, account.address); - expect(result).toBe(mockViewResultValue); + expect(wallet.simulateUnconstrained).toHaveBeenCalledTimes(1); + expect(wallet.simulateUnconstrained).toHaveBeenCalledWith('qux', [123n], contractAddress, account.address); + expect(result).toBe(mockUnconstrainedResultValue); }); it('should not call create on an unconstrained function', async () => { diff --git a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts index 39344586891..6ebf682cdc1 100644 --- a/yarn-project/aztec.js/src/contract/contract_function_interaction.ts +++ b/yarn-project/aztec.js/src/contract/contract_function_interaction.ts @@ -1,5 +1,5 @@ -import { type FunctionCall, PackedValues, TxExecutionRequest } from '@aztec/circuit-types'; -import { type AztecAddress, FunctionData, GasSettings, TxContext } from '@aztec/circuits.js'; +import type { FunctionCall, TxExecutionRequest } from '@aztec/circuit-types'; +import { type AztecAddress, FunctionData, type GasSettings } from '@aztec/circuits.js'; import { type FunctionAbi, FunctionType, decodeReturnValues, encodeArguments } from '@aztec/foundation/abi'; import { type Wallet } from '../account/wallet.js'; @@ -70,42 +70,29 @@ export class ContractFunctionInteraction extends BaseContractInteraction { * Differs from prove in a few important ways: * 1. It returns the values of the function execution * 2. It supports `unconstrained`, `private` and `public` functions - * 3. For `private` execution it: - * 3.a SKIPS the entrypoint and starts directly at the function - * 4. For `public` execution it: - * 4.a Removes the `txRequest` value after ended simulation - * 4.b Ignores the `from` in the options * * @param options - An optional object containing additional configuration for the transaction. * @returns The result of the transaction as returned by the contract function. */ public async simulate(options: SimulateMethodOptions = {}): Promise { if (this.functionDao.functionType == FunctionType.UNCONSTRAINED) { - return this.wallet.viewTx(this.functionDao.name, this.args, this.contractAddress, options.from); + return this.wallet.simulateUnconstrained(this.functionDao.name, this.args, this.contractAddress, options?.from); } - if (this.functionDao.functionType == FunctionType.SECRET) { - const nodeInfo = await this.wallet.getNodeInfo(); - const packedArgs = PackedValues.fromValues(encodeArguments(this.functionDao, this.args)); - const gasSettings = options.gasSettings ?? GasSettings.simulation(); + const txRequest = await this.create(); + // const from = + // this.functionDao.functionType == FunctionType.SECRET ? options.from ?? this.wallet.getAddress() : undefined; - const txRequest = TxExecutionRequest.from({ - firstCallArgsHash: packedArgs.hash, - origin: this.contractAddress, - functionData: FunctionData.fromAbi(this.functionDao), - txContext: new TxContext(nodeInfo.chainId, nodeInfo.protocolVersion, gasSettings), - argsOfCalls: [packedArgs], - authWitnesses: [], - }); - const simulatedTx = await this.wallet.simulateTx(txRequest, true, options.from ?? this.wallet.getAddress()); - const flattened = simulatedTx.privateReturnValues; - return flattened ? decodeReturnValues(this.functionDao, flattened) : []; - } else { - const txRequest = await this.create(); - const simulatedTx = await this.wallet.simulateTx(txRequest, true); - this.txRequest = undefined; - const flattened = simulatedTx.publicOutput?.publicReturnValues; - return flattened ? decodeReturnValues(this.functionDao, flattened) : []; - } + const simulatedTx = await this.wallet.simulateTx(txRequest, true, options?.from); + + // As account entrypoints are private, for private functions we retrieve the return values from the first nested call + // since we're interested in the first set of values AFTER the account entrypoint + // For public functions we retrieve the first values directly from the public output. + const rawReturnValues = + this.functionDao.functionType == FunctionType.SECRET + ? simulatedTx.privateReturnValues?.nested?.[0].values + : simulatedTx.publicOutput?.publicReturnValues?.values; + + return rawReturnValues ? decodeReturnValues(this.functionDao, rawReturnValues) : []; } } diff --git a/yarn-project/aztec.js/src/entrypoint/default_multi_call_entrypoint.ts b/yarn-project/aztec.js/src/entrypoint/default_multi_call_entrypoint.ts index 167dfac2501..bf36456c1bb 100644 --- a/yarn-project/aztec.js/src/entrypoint/default_multi_call_entrypoint.ts +++ b/yarn-project/aztec.js/src/entrypoint/default_multi_call_entrypoint.ts @@ -39,6 +39,7 @@ export class DefaultMultiCallEntrypoint implements EntrypointInterface { isInitializer: false, functionType: 'secret', isInternal: false, + isStatic: false, parameters: [ { name: 'app_payload', @@ -73,6 +74,7 @@ export class DefaultMultiCallEntrypoint implements EntrypointInterface { }, }, { name: 'is_public', type: { kind: 'boolean' } }, + { name: 'is_static', type: { kind: 'boolean' } }, ], }, }, diff --git a/yarn-project/aztec.js/src/entrypoint/payload.ts b/yarn-project/aztec.js/src/entrypoint/payload.ts index c4bfb965aaa..7c3ec2f764d 100644 --- a/yarn-project/aztec.js/src/entrypoint/payload.ts +++ b/yarn-project/aztec.js/src/entrypoint/payload.ts @@ -33,6 +33,8 @@ type EncodedFunctionCall = { target_address: Fr; /** Whether the function is public or private */ is_public: boolean; + /** Whether the function can alter state */ + is_static: boolean; }; /* eslint-enable camelcase */ @@ -54,6 +56,7 @@ export class EntrypointPayload { function_selector: call.functionData.selector.toField(), target_address: call.to.toField(), is_public: !call.functionData.isPrivate, + is_static: call.functionData.isStatic, })); /* eslint-enable camelcase */ @@ -96,6 +99,7 @@ export class EntrypointPayload { call.function_selector, call.target_address, new Fr(call.is_public), + new Fr(call.is_static), ]), this.#nonce, ]; diff --git a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts index af5a9ac3b31..92a4779c6f0 100644 --- a/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/native_fee_payment_method.ts @@ -54,7 +54,11 @@ export class NativeFeePaymentMethod implements FeePaymentMethod { return Promise.resolve([ { to: this.#gasTokenAddress, - functionData: new FunctionData(FunctionSelector.fromSignature('pay_fee(Field)'), false), + functionData: new FunctionData( + FunctionSelector.fromSignature('pay_fee(Field)'), + /*isPrivate=*/ false, + /*isStatic=*/ false, + ), args: [gasSettings.getFeeLimit()], }, ]); diff --git a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts index f3298ed09ef..f0a96413e31 100644 --- a/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/private_fee_payment_method.ts @@ -65,7 +65,11 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod { this.wallet.getVersion(), { args: [this.wallet.getCompleteAddress().address, this.paymentContract, maxFee, nonce], - functionData: new FunctionData(FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), true), + functionData: new FunctionData( + FunctionSelector.fromSignature('unshield((Field),(Field),Field,Field)'), + /*isPrivate=*/ true, + /*isStatic=*/ false, + ), to: this.asset, }, ); @@ -78,7 +82,8 @@ export class PrivateFeePaymentMethod implements FeePaymentMethod { to: this.getPaymentContract(), functionData: new FunctionData( FunctionSelector.fromSignature('fee_entrypoint_private(Field,(Field),Field,Field)'), - true, + /*isPrivate=*/ true, + /*isStatic=*/ false, ), args: [maxFee, this.asset, secretHashForRebate, nonce], }, diff --git a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts index 33b2430ebee..5b8782107df 100644 --- a/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts +++ b/yarn-project/aztec.js/src/fee/public_fee_payment_method.ts @@ -60,7 +60,8 @@ export class PublicFeePaymentMethod implements FeePaymentMethod { args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce], functionData: new FunctionData( FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), - false, + /*isPrivate=*/ false, + /*isStatic=*/ false, ), to: this.asset, }, @@ -72,7 +73,8 @@ export class PublicFeePaymentMethod implements FeePaymentMethod { to: this.getPaymentContract(), functionData: new FunctionData( FunctionSelector.fromSignature('fee_entrypoint_public(Field,(Field),Field)'), - true, + /*isPrivate=*/ true, + /*isStatic=*/ false, ), args: [maxFee, this.asset, nonce], }, diff --git a/yarn-project/aztec.js/src/wallet/account_wallet.ts b/yarn-project/aztec.js/src/wallet/account_wallet.ts index 803d07010eb..e793504eb7d 100644 --- a/yarn-project/aztec.js/src/wallet/account_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/account_wallet.ts @@ -206,6 +206,7 @@ export class AccountWallet extends BaseWallet { isInitializer: false, functionType: FunctionType.OPEN, isInternal: true, + isStatic: false, parameters: [ { name: 'message_hash', @@ -223,6 +224,7 @@ export class AccountWallet extends BaseWallet { isInitializer: false, functionType: FunctionType.SECRET, isInternal: true, + isStatic: false, parameters: [ { name: 'message_hash', @@ -240,6 +242,7 @@ export class AccountWallet extends BaseWallet { isInitializer: false, functionType: FunctionType.UNCONSTRAINED, isInternal: false, + isStatic: false, parameters: [ { name: 'myself', diff --git a/yarn-project/aztec.js/src/wallet/base_wallet.ts b/yarn-project/aztec.js/src/wallet/base_wallet.ts index e94c9920359..d8a87d37098 100644 --- a/yarn-project/aztec.js/src/wallet/base_wallet.ts +++ b/yarn-project/aztec.js/src/wallet/base_wallet.ts @@ -130,8 +130,13 @@ export abstract class BaseWallet implements Wallet { getBlock(number: number): Promise { return this.pxe.getBlock(number); } - viewTx(functionName: string, args: any[], to: AztecAddress, from?: AztecAddress | undefined): Promise { - return this.pxe.viewTx(functionName, args, to, from); + simulateUnconstrained( + functionName: string, + args: any[], + to: AztecAddress, + from?: AztecAddress | undefined, + ): Promise { + return this.pxe.simulateUnconstrained(functionName, args, to, from); } getUnencryptedLogs(filter: LogFilter): Promise { return this.pxe.getUnencryptedLogs(filter); diff --git a/yarn-project/circuit-types/src/interfaces/pxe.ts b/yarn-project/circuit-types/src/interfaces/pxe.ts index 0ad069a0ceb..784c5d7ebbf 100644 --- a/yarn-project/circuit-types/src/interfaces/pxe.ts +++ b/yarn-project/circuit-types/src/interfaces/pxe.ts @@ -232,7 +232,7 @@ export interface PXE { getBlock(number: number): Promise; /** - * Simulate the execution of a view (read-only) function on a deployed contract without actually modifying state. + * Simulate the execution of an unconstrained function on a deployed contract without actually modifying state. * This is useful to inspect contract state, for example fetching a variable value or calling a getter function. * The function takes function name and arguments as parameters, along with the contract address * and optionally the sender's address. @@ -243,7 +243,7 @@ export interface PXE { * @param from - (Optional) The msg sender to set for the call. * @returns The result of the view function call, structured based on the function ABI. */ - viewTx(functionName: string, args: any[], to: AztecAddress, from?: AztecAddress): Promise; + simulateUnconstrained(functionName: string, args: any[], to: AztecAddress, from?: AztecAddress): Promise; /** * Gets unencrypted logs based on the provided filter. diff --git a/yarn-project/circuit-types/src/mocks.ts b/yarn-project/circuit-types/src/mocks.ts index 2e21eac53b9..0fc51c62cdb 100644 --- a/yarn-project/circuit-types/src/mocks.ts +++ b/yarn-project/circuit-types/src/mocks.ts @@ -27,7 +27,7 @@ import { type ContractInstanceWithAddress, SerializableContractInstance } from ' import { EncryptedL2Log } from './logs/encrypted_l2_log.js'; import { EncryptedFunctionL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js'; import { ExtendedNote } from './notes/index.js'; -import { type ProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js'; +import { NestedProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js'; /** * Testing utility to create empty logs composed from a single empty log. @@ -145,7 +145,7 @@ export const mockTxForRollup = (seed = 1, { hasLogs = false }: { hasLogs?: boole export const mockSimulatedTx = (seed = 1, hasLogs = true) => { const tx = mockTx(seed, { hasLogs }); - const dec: ProcessReturnValues = [new Fr(1n), new Fr(2n), new Fr(3n), new Fr(4n)]; + const dec = new NestedProcessReturnValues([new Fr(1n), new Fr(2n), new Fr(3n), new Fr(4n)]); const output = new PublicSimulationOutput( tx.encryptedLogs, tx.unencryptedLogs, diff --git a/yarn-project/circuit-types/src/tx/public_simulation_output.ts b/yarn-project/circuit-types/src/tx/public_simulation_output.ts index 24443814680..d28daa3dc4b 100644 --- a/yarn-project/circuit-types/src/tx/public_simulation_output.ts +++ b/yarn-project/circuit-types/src/tx/public_simulation_output.ts @@ -8,6 +8,31 @@ import { type PublicKernelType } from './processed_tx.js'; /** Return values of simulating a circuit. */ export type ProcessReturnValues = Fr[] | undefined; +/** Return values of simulating complete callstack. */ +export class NestedProcessReturnValues { + values: ProcessReturnValues; + nested: NestedProcessReturnValues[]; + + constructor(values: ProcessReturnValues, nested?: NestedProcessReturnValues[]) { + this.values = values; + this.nested = nested ?? []; + } + + toJSON(): any { + return { + values: this.values?.map(fr => fr.toString()), + nested: this.nested.map(n => n.toJSON()), + }; + } + + static fromJSON(json: any): NestedProcessReturnValues { + return new NestedProcessReturnValues( + json.values?.map(Fr.fromString), + json.nested?.map((n: any) => NestedProcessReturnValues.fromJSON(n)), + ); + } +} + /** * Outputs of processing the public component of a transaction. */ @@ -18,7 +43,7 @@ export class PublicSimulationOutput { public revertReason: SimulationError | undefined, public constants: CombinedConstantData, public end: CombinedAccumulatedData, - public publicReturnValues: ProcessReturnValues, + public publicReturnValues: NestedProcessReturnValues, public gasUsed: Partial>, ) {} @@ -29,7 +54,7 @@ export class PublicSimulationOutput { revertReason: this.revertReason, constants: this.constants.toBuffer().toString('hex'), end: this.end.toBuffer().toString('hex'), - publicReturnValues: this.publicReturnValues?.map(fr => fr.toString()), + publicReturnValues: this.publicReturnValues?.toJSON(), gasUsed: mapValues(this.gasUsed, gas => gas?.toJSON()), }; } @@ -41,7 +66,7 @@ export class PublicSimulationOutput { json.revertReason, CombinedConstantData.fromBuffer(Buffer.from(json.constants, 'hex')), CombinedAccumulatedData.fromBuffer(Buffer.from(json.end, 'hex')), - json.publicReturnValues?.map(Fr.fromString), + NestedProcessReturnValues.fromJSON(json.publicReturnValues), mapValues(json.gasUsed, gas => (gas ? Gas.fromJSON(gas) : undefined)), ); } diff --git a/yarn-project/circuit-types/src/tx/simulated_tx.ts b/yarn-project/circuit-types/src/tx/simulated_tx.ts index e3808a1e463..8ac2ef72a1f 100644 --- a/yarn-project/circuit-types/src/tx/simulated_tx.ts +++ b/yarn-project/circuit-types/src/tx/simulated_tx.ts @@ -1,6 +1,4 @@ -import { Fr } from '@aztec/circuits.js'; - -import { type ProcessReturnValues, PublicSimulationOutput } from './public_simulation_output.js'; +import { NestedProcessReturnValues, PublicSimulationOutput } from './public_simulation_output.js'; import { Tx } from './tx.js'; // REFACTOR: Review what we need to expose to the user when running a simulation. @@ -12,7 +10,7 @@ import { Tx } from './tx.js'; export class SimulatedTx { constructor( public tx: Tx, - public privateReturnValues?: ProcessReturnValues, + public privateReturnValues?: NestedProcessReturnValues, public publicOutput?: PublicSimulationOutput, ) {} @@ -23,7 +21,7 @@ export class SimulatedTx { public toJSON() { return { tx: this.tx.toJSON(), - privateReturnValues: this.privateReturnValues?.map(fr => fr.toString()), + privateReturnValues: this.privateReturnValues && this.privateReturnValues.toJSON(), publicOutput: this.publicOutput && this.publicOutput.toJSON(), }; } @@ -36,7 +34,9 @@ export class SimulatedTx { public static fromJSON(obj: any) { const tx = Tx.fromJSON(obj.tx); const publicOutput = obj.publicOutput ? PublicSimulationOutput.fromJSON(obj.publicOutput) : undefined; - const privateReturnValues = obj.privateReturnValues?.map(Fr.fromString); + const privateReturnValues = obj.privateReturnValues + ? NestedProcessReturnValues.fromJSON(obj.privateReturnValues) + : undefined; return new SimulatedTx(tx, privateReturnValues, publicOutput); } diff --git a/yarn-project/circuits.js/src/constants.gen.ts b/yarn-project/circuits.js/src/constants.gen.ts index 3be95e96e9a..4a35f384538 100644 --- a/yarn-project/circuits.js/src/constants.gen.ts +++ b/yarn-project/circuits.js/src/constants.gen.ts @@ -94,7 +94,7 @@ export const CONTRACT_INSTANCE_LENGTH = 5; export const CONTRACT_STORAGE_READ_LENGTH = 2; export const CONTRACT_STORAGE_UPDATE_REQUEST_LENGTH = 2; export const ETH_ADDRESS_LENGTH = 1; -export const FUNCTION_DATA_LENGTH = 2; +export const FUNCTION_DATA_LENGTH = 3; export const FUNCTION_LEAF_PREIMAGE_LENGTH = 5; export const GLOBAL_VARIABLES_LENGTH = 6 + GAS_FEES_LENGTH; export const APPEND_ONLY_TREE_SNAPSHOT_LENGTH = 2; diff --git a/yarn-project/circuits.js/src/contract/contract_address.test.ts b/yarn-project/circuits.js/src/contract/contract_address.test.ts index e81eaebfc49..4bb4b19a1fb 100644 --- a/yarn-project/circuits.js/src/contract/contract_address.test.ts +++ b/yarn-project/circuits.js/src/contract/contract_address.test.ts @@ -36,6 +36,7 @@ describe('ContractAddress', () => { functionType: FunctionType.SECRET, isInitializer: false, isInternal: false, + isStatic: false, name: 'fun', parameters: [{ name: 'param1', type: { kind: 'boolean' }, visibility: ABIParameterVisibility.SECRET }], returnTypes: [], diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/function_data.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/function_data.test.ts.snap index db45ccb85d2..8287356b029 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/function_data.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/function_data.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`FunctionData computes empty function data hash 1`] = `Fr<0x27b1d0839a5b23baf12a8d195b18ac288fcf401afb2f70b8a4b529ede5fa9fed>`; +exports[`FunctionData computes empty function data hash 1`] = `Fr<0x066e6cdc4a6ba5e4781deda650b0be6c12f975f064fc38df72c1060716759b17>`; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/private_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/private_call_stack_item.test.ts.snap index be2f85234d3..169f798b7ea 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/private_call_stack_item.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/private_call_stack_item.test.ts.snap @@ -1,5 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PrivateCallStackItem computes empty item hash 1`] = `Fr<0x1eaa8a277851ba8de6f7630ec75a2324e03a00a6ee99f24dd834faa422bdee4f>`; +exports[`PrivateCallStackItem computes empty item hash 1`] = `Fr<0x29d77b0175116357d39252de002a2944652f87ccb4404f9346bff6d44f020f7f>`; -exports[`PrivateCallStackItem computes hash 1`] = `Fr<0x0cb30e5fa0e822ff93dc0bc3752c4b277e4629dfd8c31651aca53541873a5505>`; +exports[`PrivateCallStackItem computes hash 1`] = `Fr<0x00c30fa28e1ab0d35bd0eaa591f469b7f9b92bc37aa5696eaf447b88ad331339>`; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap index 20348f214d3..5cf1ed0ff49 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/public_call_stack_item.test.ts.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x2bb94c518916df51853784f16991e7691eddd452831ee1197cd29cdfb492d7b8"`; +exports[`PublicCallStackItem Computes a callstack item hash 1`] = `"0x1bda75e8d4cf46fd126d14d6e28d1dc8ff3860b49ff59edd7da8bfeee909aef3"`; -exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x1fe90f27924bcd761257c1b4570f5937b6dabcb4b6047ff668a770dea8e13533"`; +exports[`PublicCallStackItem Computes a callstack item request hash 1`] = `"0x0230a99218df238d4a28664ee700e10a4fdfe98e4a2f5c678bce8c95d5fa04b1"`; -exports[`PublicCallStackItem computes empty item hash 1`] = `Fr<0x13e55a4c1fb75d2a348ab0abe47aba86992a0cebf5fe2b24243af0246d27d2b5>`; +exports[`PublicCallStackItem computes empty item hash 1`] = `Fr<0x2328377f64bffa61d6a773e688b4b217b1dca488b8f905d94effe3fbc968111c>`; -exports[`PublicCallStackItem computes hash 1`] = `Fr<0x0e54342ea9a248adda86b676c762fab4ed8e64f91c28da0f577162870685e5ab>`; +exports[`PublicCallStackItem computes hash 1`] = `Fr<0x20506d5ffb519b1e5763fdfe3c3f84cd6292585bacd024eccca04e31b17b4cfc>`; diff --git a/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap b/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap index fbadc0a1a84..bffda2922e4 100644 --- a/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap +++ b/yarn-project/circuits.js/src/structs/__snapshots__/tx_request.test.ts.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`TxRequest compute hash 1`] = `"0x0d982a1c7a65919b2bcacbf5660f5b861132072a8190d97c82b1591da402f5ea"`; +exports[`TxRequest compute hash 1`] = `"0x249ca45ef4045007021de437e14bf865445f63af6ab5dcc8d78d2b3aa7c58c74"`; diff --git a/yarn-project/circuits.js/src/structs/function_data.test.ts b/yarn-project/circuits.js/src/structs/function_data.test.ts index c39c0a030d3..58abf6a4bcc 100644 --- a/yarn-project/circuits.js/src/structs/function_data.test.ts +++ b/yarn-project/circuits.js/src/structs/function_data.test.ts @@ -9,7 +9,7 @@ describe('FunctionData', () => { beforeAll(() => { setupCustomSnapshotSerializers(expect); - functionData = new FunctionData(new FunctionSelector(123), true); + functionData = new FunctionData(new FunctionSelector(123), /*isPrivate=*/ true, /*isStatic=*/ false); }); it(`serializes to buffer and deserializes it back`, () => { diff --git a/yarn-project/circuits.js/src/structs/function_data.ts b/yarn-project/circuits.js/src/structs/function_data.ts index 66be3fd069b..d33a8b2ea8d 100644 --- a/yarn-project/circuits.js/src/structs/function_data.ts +++ b/yarn-project/circuits.js/src/structs/function_data.ts @@ -13,12 +13,15 @@ export class FunctionData { public selector: FunctionSelector, /** Indicates whether the function is private or public. */ public isPrivate: boolean, + /** Indicates whether the function can alter state or not. */ + public isStatic: boolean, ) {} static fromAbi(abi: FunctionAbi | ContractFunctionDao): FunctionData { return new FunctionData( FunctionSelector.fromNameAndParameters(abi.name, abi.parameters), abi.functionType === FunctionType.SECRET, + abi.isStatic, ); } @@ -27,11 +30,11 @@ export class FunctionData { * @returns The buffer. */ toBuffer(): Buffer { - return serializeToBuffer(this.selector, this.isPrivate); + return serializeToBuffer(this.selector, this.isPrivate, this.isStatic); } toFields(): Fr[] { - const fields = [this.selector.toField(), new Fr(this.isPrivate)]; + const fields = [this.selector.toField(), new Fr(this.isPrivate), new Fr(this.isStatic)]; if (fields.length !== FUNCTION_DATA_LENGTH) { throw new Error( `Invalid number of fields for FunctionData. Expected ${FUNCTION_DATA_LENGTH}, got ${fields.length}`, @@ -56,8 +59,10 @@ export class FunctionData { public static empty(args?: { /** Indicates whether the function is private or public. */ isPrivate?: boolean; + /** Indicates whether the function can alter state or not. */ + isStatic?: boolean; }): FunctionData { - return new FunctionData(FunctionSelector.empty(), args?.isPrivate ?? false); + return new FunctionData(FunctionSelector.empty(), args?.isPrivate ?? false, args?.isStatic ?? false); } /** @@ -67,7 +72,11 @@ export class FunctionData { */ static fromBuffer(buffer: Buffer | BufferReader): FunctionData { const reader = BufferReader.asReader(buffer); - return new FunctionData(reader.readObject(FunctionSelector), reader.readBoolean()); + return new FunctionData( + reader.readObject(FunctionSelector), + /*isPrivate=*/ reader.readBoolean(), + /*isStatic=*/ reader.readBoolean(), + ); } static fromFields(fields: Fr[] | FieldReader): FunctionData { @@ -75,8 +84,9 @@ export class FunctionData { const selector = FunctionSelector.fromFields(reader); const isPrivate = reader.readBoolean(); + const isStatic = reader.readBoolean(); - return new FunctionData(selector, isPrivate); + return new FunctionData(selector, isPrivate, isStatic); } hash(): Fr { diff --git a/yarn-project/circuits.js/src/structs/public_call_stack_item.test.ts b/yarn-project/circuits.js/src/structs/public_call_stack_item.test.ts index df7c6192b6b..18719981e9e 100644 --- a/yarn-project/circuits.js/src/structs/public_call_stack_item.test.ts +++ b/yarn-project/circuits.js/src/structs/public_call_stack_item.test.ts @@ -32,7 +32,7 @@ describe('PublicCallStackItem', () => { const callStack = PublicCallStackItem.empty(); callStack.contractAddress = AztecAddress.fromField(new Fr(1)); - callStack.functionData = new FunctionData(new FunctionSelector(2), false); + callStack.functionData = new FunctionData(new FunctionSelector(2), /*isPrivate=*/ false, /*isStatic=*/ false); callStack.isExecutionRequest = true; callStack.publicInputs.newNoteHashes[0] = new NoteHash(new Fr(1), 0); @@ -51,7 +51,7 @@ describe('PublicCallStackItem', () => { const callStack = PublicCallStackItem.empty(); callStack.contractAddress = AztecAddress.fromField(new Fr(1)); - callStack.functionData = new FunctionData(new FunctionSelector(2), false); + callStack.functionData = new FunctionData(new FunctionSelector(2), /*isPrivate=*/ false, /*isStatic=*/ false); callStack.publicInputs.newNoteHashes[0] = new NoteHash(new Fr(1), 0); const hash = callStack.hash(); diff --git a/yarn-project/circuits.js/src/structs/tx_request.test.ts b/yarn-project/circuits.js/src/structs/tx_request.test.ts index f4a44c77fc3..8a62b32b1b9 100644 --- a/yarn-project/circuits.js/src/structs/tx_request.test.ts +++ b/yarn-project/circuits.js/src/structs/tx_request.test.ts @@ -37,7 +37,7 @@ describe('TxRequest', () => { const gasSettings = new GasSettings(new Gas(2, 2), new Gas(1, 1), new GasFees(3, 3), new Fr(10)); const txRequest = TxRequest.from({ origin: AztecAddress.fromBigInt(1n), - functionData: new FunctionData(FunctionSelector.fromField(new Fr(2n)), true), + functionData: new FunctionData(FunctionSelector.fromField(new Fr(2n)), /*isPrivate=*/ true, /*isStatic=*/ false), argsHash: new Fr(3), txContext: new TxContext(Fr.ZERO, Fr.ZERO, gasSettings), }); diff --git a/yarn-project/circuits.js/src/tests/factories.ts b/yarn-project/circuits.js/src/tests/factories.ts index 7eb335efb0b..7037eabcb35 100644 --- a/yarn-project/circuits.js/src/tests/factories.ts +++ b/yarn-project/circuits.js/src/tests/factories.ts @@ -508,7 +508,7 @@ export function makePublicCallRequest(seed = 1): PublicCallRequest { return new PublicCallRequest( makeAztecAddress(seed), - new FunctionData(makeSelector(seed + 0x1), false), + new FunctionData(makeSelector(seed + 0x1), /*isPrivate=*/ false, /*isStatic=*/ false), childCallContext, parentCallContext, makeTuple(ARGS_LENGTH, fr, seed + 0x10), @@ -637,7 +637,7 @@ export function makePublicCallStackItem(seed = 1, full = false): PublicCallStack const callStackItem = new PublicCallStackItem( makeAztecAddress(seed), // in the public kernel, function can't be a constructor or private - new FunctionData(makeSelector(seed + 0x1), false), + new FunctionData(makeSelector(seed + 0x1), /*isPrivate=*/ false, /*isStatic=*/ false), makePublicCircuitPublicInputs(seed + 0x10, undefined, full), false, ); @@ -723,7 +723,7 @@ export function makePublicKernelInputsWithTweak( export function makeTxRequest(seed = 1): TxRequest { return TxRequest.from({ origin: makeAztecAddress(seed), - functionData: new FunctionData(makeSelector(seed + 0x100), true), + functionData: new FunctionData(makeSelector(seed + 0x100), /*isPrivate=*/ true, /*isStatic=*/ false), argsHash: fr(seed + 0x200), txContext: makeTxContext(seed + 0x400), }); @@ -759,7 +759,7 @@ export function makePrivateCallData(seed = 1): PrivateCallData { export function makePrivateCallStackItem(seed = 1): PrivateCallStackItem { return new PrivateCallStackItem( makeAztecAddress(seed), - new FunctionData(makeSelector(seed + 0x1), true), + new FunctionData(makeSelector(seed + 0x1), /*isPrivate=*/ true, /*isStatic=*/ false), makePrivateCircuitPublicInputs(seed + 0x10), ); } diff --git a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts index dfde4f8662b..35405f8b8f5 100644 --- a/yarn-project/end-to-end/src/e2e_fees/failures.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/failures.test.ts @@ -229,7 +229,8 @@ class BuggedSetupFeePaymentMethod extends PublicFeePaymentMethod { args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce], functionData: new FunctionData( FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), - false, + /*isPrivate=*/ false, + /*isStatic=*/ false, ), to: this.asset, }, @@ -243,7 +244,8 @@ class BuggedSetupFeePaymentMethod extends PublicFeePaymentMethod { to: this.getPaymentContract(), functionData: new FunctionData( FunctionSelector.fromSignature('fee_entrypoint_public(Field,(Field),Field)'), - true, + /*isPrivate=*/ true, + /*isStatic=*/ false, ), args: [tooMuchFee, this.asset, nonce], }, @@ -264,7 +266,8 @@ class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod { args: [this.wallet.getAddress(), this.paymentContract, maxFee, nonce], functionData: new FunctionData( FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), - false, + /*isPrivate=*/ false, + /*isStatic=*/ false, ), to: this.asset, }, @@ -280,7 +283,8 @@ class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod { to: this.getPaymentContract(), functionData: new FunctionData( FunctionSelector.fromSignature('fee_entrypoint_public(Field,(Field),Field)'), - true, + /*isPrivate=*/ true, + /*isStatic=*/ false, ), args: [maxFee, this.asset, nonce], }, @@ -289,7 +293,8 @@ class BuggedTeardownFeePaymentMethod extends PublicFeePaymentMethod { to: this.asset, functionData: new FunctionData( FunctionSelector.fromSignature('transfer_public((Field),(Field),Field,Field)'), - false, + /*isPrivate=*/ false, + /*isStatic=*/ false, ), args: [this.wallet.getAddress(), this.paymentContract, new Fr(1), Fr.random()], }, diff --git a/yarn-project/end-to-end/src/e2e_static_calls.test.ts b/yarn-project/end-to-end/src/e2e_static_calls.test.ts index a1c10487adf..d74e08d5a9c 100644 --- a/yarn-project/end-to-end/src/e2e_static_calls.test.ts +++ b/yarn-project/end-to-end/src/e2e_static_calls.test.ts @@ -1,18 +1,19 @@ import { type Wallet } from '@aztec/aztec.js'; -import { ChildContract, ParentContract } from '@aztec/noir-contracts.js'; +import { StaticChildContract, StaticParentContract } from '@aztec/noir-contracts.js'; +import { STATIC_CALL_STATE_MODIFICATION_ERROR, STATIC_CONTEXT_ASSERTION_ERROR } from './fixtures/fixtures.js'; import { setup } from './fixtures/utils.js'; describe('e2e_static_calls', () => { let wallet: Wallet; - let parentContract: ParentContract; - let childContract: ChildContract; + let parentContract: StaticParentContract; + let childContract: StaticChildContract; let teardown: () => Promise; beforeAll(async () => { ({ teardown, wallet } = await setup()); - parentContract = await ParentContract.deploy(wallet).send().deployed(); - childContract = await ChildContract.deploy(wallet).send().deployed(); + parentContract = await StaticParentContract.deploy(wallet).send().deployed(); + childContract = await StaticChildContract.deploy(wallet).send().deployed(); // We create a note in the set, such that later reads doesn't fail due to get_notes returning 0 notes await childContract.methods.private_set_value(42n, wallet.getCompleteAddress().address).send().wait(); @@ -20,8 +21,31 @@ describe('e2e_static_calls', () => { afterAll(() => teardown()); + describe('direct view calls to child', () => { + it('performs legal private static calls', async () => { + await childContract.methods.private_get_value(42n, wallet.getCompleteAddress().address).send().wait(); + }); + + it('fails when performing non-static calls to poorly written static private functions', async () => { + await expect( + childContract.methods.private_illegal_set_value(42n, wallet.getCompleteAddress().address).send().wait(), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + it('performs legal public static calls', async () => { + await childContract.methods.pub_get_value(42n).send().wait(); + }); + + it('fails when performing non-static calls to poorly written static public functions', async () => { + await expect(childContract.methods.pub_illegal_inc_value(42n).send().wait()).rejects.toThrow( + STATIC_CALL_STATE_MODIFICATION_ERROR, + ); + }); + }); + describe('parent calls child', () => { it('performs legal private to private static calls', async () => { + // Using low level calls await parentContract.methods .private_static_call(childContract.address, childContract.methods.private_get_value.selector, [ 42n, @@ -29,6 +53,12 @@ describe('e2e_static_calls', () => { ]) .send() .wait(); + + // Using the contract interface + await parentContract.methods + .private_get_value_from_child(childContract.address, 42n, wallet.getCompleteAddress().address) + .send() + .wait(); }); it('performs legal (nested) private to private static calls', async () => { @@ -42,10 +72,14 @@ describe('e2e_static_calls', () => { }); it('performs legal public to public static calls', async () => { + // Using low level calls await parentContract.methods .public_static_call(childContract.address, childContract.methods.pub_get_value.selector, [42n]) .send() .wait(); + + // Using contract interface + await parentContract.methods.public_get_value_from_child(childContract.address, 42n).send().wait(); }); it('performs legal (nested) public to public static calls', async () => { @@ -56,10 +90,14 @@ describe('e2e_static_calls', () => { }); it('performs legal enqueued public static calls', async () => { + // Using low level calls await parentContract.methods .enqueue_static_call_to_pub_function(childContract.address, childContract.methods.pub_get_value.selector, [42n]) .send() .wait(); + + // Using contract interface + await parentContract.methods.enqueue_public_get_value_from_child(childContract.address, 42).send().wait(); }); it('performs legal (nested) enqueued public static calls', async () => { @@ -82,7 +120,19 @@ describe('e2e_static_calls', () => { ]) .send() .wait(), - ).rejects.toThrow('Static call cannot create new notes, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + it('fails when performing non-static calls to poorly written private static functions', async () => { + await expect( + parentContract.methods + .private_call(childContract.address, childContract.methods.private_illegal_set_value.selector, [ + 42n, + wallet.getCompleteAddress().address, + ]) + .send() + .wait(), + ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); }); it('fails when performing illegal (nested) private to private static calls', async () => { @@ -94,7 +144,7 @@ describe('e2e_static_calls', () => { ]) .send() .wait(), - ).rejects.toThrow('Static call cannot create new notes, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); }); it('fails when performing illegal public to public static calls', async () => { @@ -103,7 +153,7 @@ describe('e2e_static_calls', () => { .public_static_call(childContract.address, childContract.methods.pub_set_value.selector, [42n]) .send() .wait(), - ).rejects.toThrow('Static call cannot update the state, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); }); it('fails when performing illegal (nested) public to public static calls', async () => { @@ -112,7 +162,7 @@ describe('e2e_static_calls', () => { .public_nested_static_call(childContract.address, childContract.methods.pub_set_value.selector, [42n]) .send() .wait(), - ).rejects.toThrow('Static call cannot update the state, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); }); it('fails when performing illegal enqueued public static calls', async () => { @@ -123,7 +173,7 @@ describe('e2e_static_calls', () => { ]) .send() .wait(), - ).rejects.toThrow('Static call cannot update the state, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); }); it('fails when performing illegal (nested) enqueued public static calls', async () => { @@ -136,7 +186,115 @@ describe('e2e_static_calls', () => { ) .send() .wait(), - ).rejects.toThrow('Static call cannot update the state, emit L2->L1 messages or generate logs'); + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + it('fails when performing non-static enqueue calls to poorly written public static functions', async () => { + await expect( + parentContract.methods + .enqueue_call(childContract.address, childContract.methods.pub_illegal_inc_value.selector, [ + 42n, + wallet.getCompleteAddress().address, + ]) + .send() + .wait(), + ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); + }); + }); + + // TODO(https://github.com/AztecProtocol/aztec-packages/issues/5818): clean up the following tests and either remove the "public" version above or rename these to be the new public. + // There should be 1:1 correspondence between the two sets of tests. + describe('avm', () => { + describe('direct view calls to child', () => { + it('performs legal public static calls', async () => { + await childContract.methods.avm_get_value(42n).send().wait(); + }); + + it('fails when performing non-static calls to poorly written static public functions', async () => { + await expect(childContract.methods.avm_illegal_inc_value(42n).send().wait()).rejects.toThrow( + STATIC_CALL_STATE_MODIFICATION_ERROR, + ); + }); + }); + + describe('parent calls child', () => { + it('performs legal public to public static calls', async () => { + // Using low level calls + await parentContract.methods + .avm_static_call(childContract.address, childContract.methods.avm_get_value.selector, [42n]) + .send() + .wait(); + + // Using contract interface + await parentContract.methods.public_get_value_from_child(childContract.address, 42n).send().wait(); + }); + + it('performs legal enqueued public static calls', async () => { + // Using low level calls + await parentContract.methods + .enqueue_static_call_to_pub_function(childContract.address, childContract.methods.avm_get_value.selector, [ + 42n, + ]) + .send() + .wait(); + + // Using contract interface + await parentContract.methods.enqueue_avm_get_value_from_child(childContract.address, 42).send().wait(); + }); + + it('performs legal (nested) public to public static calls', async () => { + await parentContract.methods + .avm_nested_static_call(childContract.address, childContract.methods.avm_get_value.selector, [42n]) + .send() + .wait(); + }); + + it('performs legal (nested) enqueued public static calls', async () => { + await parentContract.methods + .enqueue_static_nested_call_to_avm_function( + childContract.address, + childContract.methods.avm_get_value.selector, + [42n], + ) + .send() + .wait(); + }); + + it('fails when performing illegal enqueued public static calls', async () => { + await expect( + parentContract.methods + .enqueue_static_call_to_pub_function(childContract.address, childContract.methods.avm_set_value.selector, [ + 42n, + ]) + .send() + .wait(), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + it('fails when performing illegal (nested) enqueued public static calls', async () => { + await expect( + parentContract.methods + .enqueue_static_nested_call_to_avm_function( + childContract.address, + childContract.methods.avm_set_value.selector, + [42n], + ) + .send() + .wait(), + ).rejects.toThrow(STATIC_CALL_STATE_MODIFICATION_ERROR); + }); + + it('fails when performing non-static enqueue calls to poorly written public static functions', async () => { + await expect( + parentContract.methods + .enqueue_call(childContract.address, childContract.methods.avm_illegal_inc_value.selector, [ + 42n, + wallet.getCompleteAddress().address, + ]) + .send() + .wait(), + ).rejects.toThrow(STATIC_CONTEXT_ASSERTION_ERROR); + }); }); }); }); diff --git a/yarn-project/end-to-end/src/fixtures/fixtures.ts b/yarn-project/end-to-end/src/fixtures/fixtures.ts index ec11d1ae340..25e9922b5cb 100644 --- a/yarn-project/end-to-end/src/fixtures/fixtures.ts +++ b/yarn-project/end-to-end/src/fixtures/fixtures.ts @@ -10,3 +10,6 @@ export const BITSIZE_TOO_BIG_ERROR = "'self.__assert_max_bit_size(bit_size)'"; export const DUPLICATE_NULLIFIER_ERROR = /dropped|duplicate nullifier|reverted/; export const NO_L1_TO_L2_MSG_ERROR = /No non-nullified L1 to L2 message found for message hash|Tried to consume nonexistent L1-to-L2 message/; +export const STATIC_CALL_STATE_MODIFICATION_ERROR = + /Static call cannot update the state, emit L2->L1 messages or generate logs.*/; +export const STATIC_CONTEXT_ASSERTION_ERROR = /Assertion failed: Function .* can only be called statically.*/; diff --git a/yarn-project/entrypoints/src/account_entrypoint.ts b/yarn-project/entrypoints/src/account_entrypoint.ts index b750f3232f0..87e9a8f1023 100644 --- a/yarn-project/entrypoints/src/account_entrypoint.ts +++ b/yarn-project/entrypoints/src/account_entrypoint.ts @@ -48,6 +48,7 @@ export class DefaultAccountEntrypoint implements EntrypointInterface { isInitializer: false, functionType: 'secret', isInternal: false, + isStatic: false, parameters: [ { name: 'app_payload', @@ -82,6 +83,7 @@ export class DefaultAccountEntrypoint implements EntrypointInterface { }, }, { name: 'is_public', type: { kind: 'boolean' } }, + { name: 'is_static', type: { kind: 'boolean' } }, ], }, }, @@ -124,6 +126,7 @@ export class DefaultAccountEntrypoint implements EntrypointInterface { }, }, { name: 'is_public', type: { kind: 'boolean' } }, + { name: 'is_static', type: { kind: 'boolean' } }, ], }, }, diff --git a/yarn-project/entrypoints/src/dapp_entrypoint.ts b/yarn-project/entrypoints/src/dapp_entrypoint.ts index b80307dfc3a..2aca62739c7 100644 --- a/yarn-project/entrypoints/src/dapp_entrypoint.ts +++ b/yarn-project/entrypoints/src/dapp_entrypoint.ts @@ -61,6 +61,7 @@ export class DefaultDappEntrypoint implements EntrypointInterface { isInitializer: false, functionType: 'secret', isInternal: false, + isStatic: false, parameters: [ { name: 'payload', @@ -95,6 +96,7 @@ export class DefaultDappEntrypoint implements EntrypointInterface { }, }, { name: 'is_public', type: { kind: 'boolean' } }, + { name: 'is_static', type: { kind: 'boolean' } }, ], }, }, diff --git a/yarn-project/foundation/src/abi/abi.ts b/yarn-project/foundation/src/abi/abi.ts index ff1be9bcdd7..da33e70473b 100644 --- a/yarn-project/foundation/src/abi/abi.ts +++ b/yarn-project/foundation/src/abi/abi.ts @@ -165,6 +165,10 @@ export interface FunctionAbi { * Whether the function is internal. */ isInternal: boolean; + /** + * Whether the function can alter state or not + */ + isStatic: boolean; /** * Function parameters. */ diff --git a/yarn-project/foundation/src/abi/encoder.test.ts b/yarn-project/foundation/src/abi/encoder.test.ts index f78e1325dfd..a38b963e9d0 100644 --- a/yarn-project/foundation/src/abi/encoder.test.ts +++ b/yarn-project/foundation/src/abi/encoder.test.ts @@ -11,6 +11,7 @@ describe('abi/encoder', () => { functionType: FunctionType.SECRET, isInternal: false, isInitializer: true, + isStatic: false, parameters: [ { name: 'owner', @@ -33,6 +34,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'owner', @@ -57,6 +59,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'owner', @@ -82,6 +85,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'owner', @@ -117,6 +121,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'contract_class', @@ -148,6 +153,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'owner', @@ -170,6 +176,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'isOwner', @@ -195,6 +202,7 @@ describe('abi/encoder', () => { isInitializer: true, functionType: FunctionType.SECRET, isInternal: false, + isStatic: false, parameters: [ { name: 'owner', diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts index f03c01e1876..5ba1e2b0b2a 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.test.ts @@ -47,7 +47,7 @@ describe('Noir<>Circuits.js type conversion test suite', () => { expect(mapFunctionSelectorFromNoir(mapFunctionSelectorToNoir(functionSelector))).toEqual(functionSelector); }); - const functionData = new FunctionData(functionSelector, true); + const functionData = new FunctionData(functionSelector, /*isPrivate=*/ true, /*isStatic=*/ false); it('should map function data', () => { expect(mapFunctionDataFromNoir(mapFunctionDataToNoir(functionData))).toEqual(functionData); diff --git a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts index b2ccda0887f..360c3f19bd3 100644 --- a/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts +++ b/yarn-project/noir-protocol-circuits-types/src/type_conversion.ts @@ -406,6 +406,7 @@ export function mapFunctionDataToNoir(functionData: FunctionData): FunctionDataN return { selector: mapFunctionSelectorToNoir(functionData.selector), is_private: functionData.isPrivate, + is_static: functionData.isStatic, }; } @@ -415,7 +416,11 @@ export function mapFunctionDataToNoir(functionData: FunctionData): FunctionDataN * @returns The function data. */ export function mapFunctionDataFromNoir(functionData: FunctionDataNoir): FunctionData { - return new FunctionData(mapFunctionSelectorFromNoir(functionData.selector), functionData.is_private); + return new FunctionData( + mapFunctionSelectorFromNoir(functionData.selector), + functionData.is_private, + functionData.is_static, + ); } /** diff --git a/yarn-project/pxe/src/pxe_service/pxe_service.ts b/yarn-project/pxe/src/pxe_service/pxe_service.ts index 91c00795841..c94dd96973a 100644 --- a/yarn-project/pxe/src/pxe_service/pxe_service.ts +++ b/yarn-project/pxe/src/pxe_service/pxe_service.ts @@ -45,6 +45,7 @@ import { Timer } from '@aztec/foundation/timer'; import { type AcirSimulator, type ExecutionResult, + accumulateReturnValues, collectEnqueuedPublicFunctionCalls, collectPublicTeardownFunctionCall, collectSortedEncryptedLogs, @@ -465,7 +466,7 @@ export class PXEService implements PXE { return txHash; } - public async viewTx( + public async simulateUnconstrained( functionName: string, args: any[], to: AztecAddress, @@ -681,7 +682,8 @@ export class PXEService implements PXE { enqueuedPublicFunctions, teardownPublicFunction, ); - return new SimulatedTx(tx, executionResult.returnValues); + + return new SimulatedTx(tx, accumulateReturnValues(executionResult)); } /** diff --git a/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts b/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts index f367253bdeb..6d7422be7b4 100644 --- a/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts +++ b/yarn-project/pxe/src/pxe_service/test/pxe_test_suite.ts @@ -143,7 +143,7 @@ export const pxeTestSuite = (testName: string, pxeSetup: () => Promise) => ); }); - // Note: Not testing a successful run of `proveTx`, `sendTx`, `getTxReceipt` and `viewTx` here as it requires + // Note: Not testing a successful run of `proveTx`, `sendTx`, `getTxReceipt` and `simulateUnconstrained` here as it requires // a larger setup and it's sufficiently tested in the e2e tests. it('throws when getting public storage for non-existent contract', async () => { diff --git a/yarn-project/simulator/src/avm/avm_execution_environment.ts b/yarn-project/simulator/src/avm/avm_execution_environment.ts index e57f94eecda..411b9d60ff4 100644 --- a/yarn-project/simulator/src/avm/avm_execution_environment.ts +++ b/yarn-project/simulator/src/avm/avm_execution_environment.ts @@ -1,15 +1,15 @@ import { FunctionSelector, type GasSettings, type GlobalVariables, type Header } from '@aztec/circuits.js'; import { computeVarArgsHash } from '@aztec/circuits.js/hash'; import { type AztecAddress } from '@aztec/foundation/aztec-address'; -import { type Fr } from '@aztec/foundation/fields'; +import { Fr } from '@aztec/foundation/fields'; export class AvmContextInputs { - static readonly SIZE = 2; + static readonly SIZE = 3; - constructor(private selector: Fr, private argsHash: Fr) {} + constructor(private selector: Fr, private argsHash: Fr, private isStaticCall: boolean) {} public toFields(): Fr[] { - return [this.selector, this.argsHash]; + return [this.selector, this.argsHash, new Fr(this.isStaticCall)]; } } @@ -41,7 +41,11 @@ export class AvmExecutionEnvironment { ) { // 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(), computeVarArgsHash(calldata)); + const inputs = new AvmContextInputs( + temporaryFunctionSelector.toField(), + computeVarArgsHash(calldata), + isStaticCall, + ); this.calldata = [...inputs.toFields(), ...calldata]; } diff --git a/yarn-project/simulator/src/client/client_execution_context.ts b/yarn-project/simulator/src/client/client_execution_context.ts index 8c06e9b1b76..148cddf078e 100644 --- a/yarn-project/simulator/src/client/client_execution_context.ts +++ b/yarn-project/simulator/src/client/client_execution_context.ts @@ -439,7 +439,7 @@ export class ClientExecutionContext extends ViewDataOracle { childExecutionResult.callStackItem.publicInputs.encryptedLogsHashes.some(item => !item.isEmpty()) || childExecutionResult.callStackItem.publicInputs.unencryptedLogsHashes.some(item => !item.isEmpty()) ) { - throw new Error(`Static call cannot create new notes, emit L2->L1 messages or generate logs`); + throw new Error(`Static call cannot update the state, emit L2->L1 messages or generate logs`); } } @@ -450,7 +450,7 @@ export class ClientExecutionContext extends ViewDataOracle { * @param argsHash - The packed arguments to pass to the function. * @param sideEffectCounter - The side effect counter at the start of the call. * @param isStaticCall - Whether the call is a static call. - * @param isStaticCall - Whether the call is a delegate call. + * @param isDelegateCall - Whether the call is a delegate call. * @returns The execution result. */ override async callPrivateFunction( diff --git a/yarn-project/simulator/src/client/simulator.ts b/yarn-project/simulator/src/client/simulator.ts index 1c3c8490f84..90ea5d3deee 100644 --- a/yarn-project/simulator/src/client/simulator.ts +++ b/yarn-project/simulator/src/client/simulator.ts @@ -88,7 +88,7 @@ export class AcirSimulator { contractAddress, FunctionSelector.fromNameAndParameters(entryPointArtifact.name, entryPointArtifact.parameters), false, - false, + entryPointArtifact.isStatic, startSideEffectCounter, ); const context = new ClientExecutionContext( diff --git a/yarn-project/simulator/src/client/unconstrained_execution.test.ts b/yarn-project/simulator/src/client/unconstrained_execution.test.ts index 9077aa0489e..5a901e8aeda 100644 --- a/yarn-project/simulator/src/client/unconstrained_execution.test.ts +++ b/yarn-project/simulator/src/client/unconstrained_execution.test.ts @@ -63,7 +63,7 @@ describe('Unconstrained Execution test suite', () => { const execRequest: FunctionCall = { to: contractAddress, - functionData: new FunctionData(FunctionSelector.empty(), true), + functionData: new FunctionData(FunctionSelector.empty(), /*isPrivate=*/ true, /*isStatic=*/ false), args: encodeArguments(artifact, [owner]), }; diff --git a/yarn-project/simulator/src/common/index.ts b/yarn-project/simulator/src/common/index.ts index 9e61c2e99e5..b10327ec983 100644 --- a/yarn-project/simulator/src/common/index.ts +++ b/yarn-project/simulator/src/common/index.ts @@ -1,3 +1,4 @@ export * from './packed_values_cache.js'; export * from './errors.js'; export * from './side_effect_counter.js'; +export * from './return_values.js'; diff --git a/yarn-project/simulator/src/common/return_values.ts b/yarn-project/simulator/src/common/return_values.ts new file mode 100644 index 00000000000..1878c8c673d --- /dev/null +++ b/yarn-project/simulator/src/common/return_values.ts @@ -0,0 +1,18 @@ +import { NestedProcessReturnValues } from '@aztec/circuit-types'; + +import type { ExecutionResult } from '../client/execution_result.js'; +import type { PublicExecutionResult } from '../public/execution.js'; + +/** + * Recursively accummulate the return values of a call result and its nested executions, + * so they can be retrieved in order. + * @param executionResult + * @returns + */ +export function accumulateReturnValues( + executionResult: PublicExecutionResult | ExecutionResult, +): NestedProcessReturnValues { + const acc = new NestedProcessReturnValues(executionResult.returnValues); + acc.nested = executionResult.nestedExecutions.map(nestedExecution => accumulateReturnValues(nestedExecution)); + return acc; +} diff --git a/yarn-project/simulator/src/mocks/fixtures.ts b/yarn-project/simulator/src/mocks/fixtures.ts index da4763a3ca0..3b2ca84d697 100644 --- a/yarn-project/simulator/src/mocks/fixtures.ts +++ b/yarn-project/simulator/src/mocks/fixtures.ts @@ -133,7 +133,7 @@ export const makeFunctionCall = ( to = makeAztecAddress(30), selector = makeSelector(5), args = new Array(ARGS_LENGTH).fill(Fr.ZERO), -) => ({ to, functionData: new FunctionData(selector, false), args }); +) => ({ to, functionData: new FunctionData(selector, /*isPrivate=*/ false, /*isStatic=*/ false), args }); export function addKernelPublicCallStack( kernelOutput: PrivateKernelTailCircuitPublicInputs, diff --git a/yarn-project/simulator/src/public/abstract_phase_manager.ts b/yarn-project/simulator/src/public/abstract_phase_manager.ts index f9c0dd25aff..2d066f8345f 100644 --- a/yarn-project/simulator/src/public/abstract_phase_manager.ts +++ b/yarn-project/simulator/src/public/abstract_phase_manager.ts @@ -1,6 +1,6 @@ import { MerkleTreeId, - type ProcessReturnValues, + type NestedProcessReturnValues, type PublicKernelRequest, PublicKernelType, type SimulationError, @@ -57,6 +57,7 @@ import { type PublicExecution, type PublicExecutionResult, type PublicExecutor, + accumulateReturnValues, collectPublicDataReads, collectPublicDataUpdateRequests, isPublicExecutionResult, @@ -140,7 +141,7 @@ export abstract class AbstractPhaseManager { * revert reason, if any */ revertReason: SimulationError | undefined; - returnValues: ProcessReturnValues; + returnValues: NestedProcessReturnValues[]; /** Gas used during the execution this particular phase. */ gasUsed: Gas | undefined; }>; @@ -227,7 +228,7 @@ export abstract class AbstractPhaseManager { Proof, UnencryptedFunctionL2Logs[], SimulationError | undefined, - ProcessReturnValues, + NestedProcessReturnValues[], Gas, ] > { @@ -238,7 +239,7 @@ export abstract class AbstractPhaseManager { const enqueuedCalls = this.extractEnqueuedPublicCalls(tx); if (!enqueuedCalls || !enqueuedCalls.length) { - return [[], kernelOutput, kernelProof, [], undefined, undefined, Gas.empty()]; + return [[], kernelOutput, kernelProof, [], undefined, [], Gas.empty()]; } const newUnencryptedFunctionLogs: UnencryptedFunctionL2Logs[] = []; @@ -250,9 +251,10 @@ export abstract class AbstractPhaseManager { // separate public callstacks to be proven by separate public kernel sequences // and submitted separately to the base rollup? - let returns: ProcessReturnValues = undefined; let gasUsed = Gas.empty(); + const enqueuedCallResults = []; + for (const enqueuedCall of enqueuedCalls) { const executionStack: (PublicExecution | PublicExecutionResult)[] = [enqueuedCall]; @@ -334,13 +336,14 @@ export abstract class AbstractPhaseManager { }`, ); // TODO(@spalladino): Check gasUsed is correct. The AVM should take care of setting gasLeft to zero upon a revert. - return [[], kernelOutput, kernelProof, [], result.revertReason, undefined, gasUsed]; + return [[], kernelOutput, kernelProof, [], result.revertReason, [], gasUsed]; } if (!enqueuedExecutionResult) { enqueuedExecutionResult = result; - returns = result.returnValues; } + + enqueuedCallResults.push(accumulateReturnValues(enqueuedExecutionResult)); } // HACK(#1622): Manually patches the ordering of public state actions // TODO(#757): Enforce proper ordering of public state actions @@ -350,7 +353,15 @@ export abstract class AbstractPhaseManager { // TODO(#3675): This should be done in a public kernel circuit removeRedundantPublicDataWrites(kernelOutput, this.phase); - return [publicKernelInputs, kernelOutput, kernelProof, newUnencryptedFunctionLogs, undefined, returns, gasUsed]; + return [ + publicKernelInputs, + kernelOutput, + kernelProof, + newUnencryptedFunctionLogs, + undefined, + enqueuedCallResults, + gasUsed, + ]; } /** Returns all pending private and public nullifiers. */ diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index fb3d199d30c..2923b7dd0b5 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -184,7 +184,7 @@ describe('ACIR public execution simulator', () => { beforeEach(() => { transferArtifact = TokenContractArtifact.functions.find(f => f.name === 'transfer_public')!; - functionData = new FunctionData(FunctionSelector.empty(), false); + functionData = new FunctionData(FunctionSelector.empty(), /*isPrivate=*/ false, /*isStatic=*/ false); sender = AztecAddress.random(); args = encodeArguments(transferArtifact, [sender, recipient, 140n, 0n]); @@ -265,7 +265,7 @@ describe('ACIR public execution simulator', () => { parentEntryPointFn.name, parentEntryPointFn.parameters, ); - functionData = new FunctionData(parentEntryPointFnSelector, false); + functionData = new FunctionData(parentEntryPointFnSelector, /*isPrivate=*/ false, /*isStatic=*/ false); childContractAddress = AztecAddress.random(); callContext = makeCallContext(parentContractAddress); }, 10000); @@ -354,7 +354,7 @@ describe('ACIR public execution simulator', () => { beforeEach(async () => { contractAddress = AztecAddress.random(); await mockInitializationNullifierCallback(contractAddress); - functionData = new FunctionData(FunctionSelector.empty(), false); + functionData = new FunctionData(FunctionSelector.empty(), /*isPrivate=*/ false, /*isStatic=*/ false); amount = new Fr(1); params = [amount, new Fr(1)]; }); diff --git a/yarn-project/simulator/src/public/public_execution_context.ts b/yarn-project/simulator/src/public/public_execution_context.ts index e8c6fefcda8..144c00ee509 100644 --- a/yarn-project/simulator/src/public/public_execution_context.ts +++ b/yarn-project/simulator/src/public/public_execution_context.ts @@ -212,7 +212,7 @@ export class PublicExecutionContext extends TypedOracle { `Public function call: addr=${targetContractAddress} selector=${functionSelector} args=${args.join(',')}`, ); - const functionData = new FunctionData(functionSelector, /*isPrivate=*/ false); + const functionData = new FunctionData(functionSelector, /*isPrivate=*/ false, /*isStatic=*/ false); const callContext = CallContext.from({ msgSender: isDelegateCall ? this.execution.callContext.msgSender : this.execution.contractAddress, storageContractAddress: isDelegateCall ? this.execution.contractAddress : targetContractAddress, diff --git a/yarn-project/simulator/src/public/public_processor.ts b/yarn-project/simulator/src/public/public_processor.ts index 360ed52b889..e17b20f17ed 100644 --- a/yarn-project/simulator/src/public/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor.ts @@ -1,7 +1,7 @@ import { type BlockProver, type FailedTx, - type ProcessReturnValues, + NestedProcessReturnValues, type ProcessedTx, type PublicKernelRequest, type SimulationError, @@ -96,12 +96,12 @@ export class PublicProcessor { maxTransactions = txs.length, blockProver?: BlockProver, txValidator?: TxValidator, - ): Promise<[ProcessedTx[], FailedTx[], ProcessReturnValues[]]> { + ): Promise<[ProcessedTx[], FailedTx[], NestedProcessReturnValues[]]> { // The processor modifies the tx objects in place, so we need to clone them. txs = txs.map(tx => Tx.clone(tx)); const result: ProcessedTx[] = []; const failed: FailedTx[] = []; - const returns: ProcessReturnValues[] = []; + const returns: NestedProcessReturnValues[] = []; for (const tx of txs) { // only process up to the limit of the block @@ -129,7 +129,7 @@ export class PublicProcessor { await blockProver.addNewTx(processedTx); } result.push(processedTx); - returns.push(returnValues); + returns.push(returnValues?.[0] ?? new NestedProcessReturnValues([])); } catch (err: any) { const errorMessage = err instanceof Error ? err.message : 'Unknown error'; this.log.warn(`Failed to process tx ${tx.getTxHash()}: ${errorMessage}`); @@ -138,7 +138,7 @@ export class PublicProcessor { tx, error: err instanceof Error ? err : new Error(errorMessage), }); - returns.push([]); + returns.push(new NestedProcessReturnValues([])); } } @@ -154,8 +154,8 @@ export class PublicProcessor { return makeEmptyProcessedTx(this.historicalHeader.clone(), chainId, version); } - private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, ProcessReturnValues | undefined]> { - let returnValues: ProcessReturnValues = undefined; + private async processTxWithPublicCalls(tx: Tx): Promise<[ProcessedTx, NestedProcessReturnValues[]]> { + let returnValues: NestedProcessReturnValues[] = []; const publicRequests: PublicKernelRequest[] = []; let phase: AbstractPhaseManager | undefined = PhaseManagerFactory.phaseFromTx( tx, diff --git a/yarn-project/simulator/src/public/setup_phase_manager.ts b/yarn-project/simulator/src/public/setup_phase_manager.ts index 33581b36f5b..0573f57ca4d 100644 --- a/yarn-project/simulator/src/public/setup_phase_manager.ts +++ b/yarn-project/simulator/src/public/setup_phase_manager.ts @@ -67,7 +67,7 @@ export class SetupPhaseManager extends AbstractPhaseManager { publicKernelOutput, publicKernelProof, revertReason, - returnValues: undefined, + returnValues: [], gasUsed, }; } diff --git a/yarn-project/simulator/src/public/tail_phase_manager.ts b/yarn-project/simulator/src/public/tail_phase_manager.ts index be324885e6f..f3c40635793 100644 --- a/yarn-project/simulator/src/public/tail_phase_manager.ts +++ b/yarn-project/simulator/src/public/tail_phase_manager.ts @@ -70,7 +70,7 @@ export class TailPhaseManager extends AbstractPhaseManager { finalKernelOutput, publicKernelProof: makeEmptyProof(), revertReason: undefined, - returnValues: undefined, + returnValues: [], gasUsed: undefined, }; } diff --git a/yarn-project/simulator/src/public/teardown_phase_manager.ts b/yarn-project/simulator/src/public/teardown_phase_manager.ts index 55b1b765630..7a40812a46c 100644 --- a/yarn-project/simulator/src/public/teardown_phase_manager.ts +++ b/yarn-project/simulator/src/public/teardown_phase_manager.ts @@ -71,7 +71,7 @@ export class TeardownPhaseManager extends AbstractPhaseManager { publicKernelOutput, publicKernelProof, revertReason, - returnValues: undefined, + returnValues: [], gasUsed, }; } diff --git a/yarn-project/simulator/src/public/transitional_adaptors.ts b/yarn-project/simulator/src/public/transitional_adaptors.ts index 74e6d004788..0f2248e348c 100644 --- a/yarn-project/simulator/src/public/transitional_adaptors.ts +++ b/yarn-project/simulator/src/public/transitional_adaptors.ts @@ -65,7 +65,11 @@ export function createPublicExecution( isStaticCall: avmEnvironment.isStaticCall, sideEffectCounter: startSideEffectCounter, }); - const functionData = new FunctionData(avmEnvironment.temporaryFunctionSelector, /*isPrivate=*/ false); + const functionData = new FunctionData( + avmEnvironment.temporaryFunctionSelector, + /*isPrivate=*/ false, + /*isStatic=*/ false, + ); const execution: PublicExecution = { contractAddress: avmEnvironment.address, callContext, diff --git a/yarn-project/types/src/abi/contract_artifact.ts b/yarn-project/types/src/abi/contract_artifact.ts index 0998899167e..9758e85650d 100644 --- a/yarn-project/types/src/abi/contract_artifact.ts +++ b/yarn-project/types/src/abi/contract_artifact.ts @@ -20,6 +20,7 @@ import { AZTEC_PRIVATE_ATTRIBUTE, AZTEC_PUBLIC_ATTRIBUTE, AZTEC_PUBLIC_VM_ATTRIBUTE, + AZTEC_VIEW_ATTRIBUTE, type NoirCompiledContract, } from '../noir/index.js'; import { mockVerificationKey } from './mocked_keys.js'; @@ -138,6 +139,7 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: No } const functionType = getFunctionType(fn); const isInternal = fn.custom_attributes.includes(AZTEC_INTERNAL_ATTRIBUTE); + const isStatic = fn.custom_attributes.includes(AZTEC_VIEW_ATTRIBUTE); // If the function is not unconstrained, the first item is inputs or CallContext which we should omit let parameters = fn.abi.parameters.map(generateFunctionParameter); @@ -170,6 +172,7 @@ function generateFunctionArtifact(fn: NoirCompiledContractFunction, contract: No name: fn.name, functionType, isInternal, + isStatic, isInitializer: fn.custom_attributes.includes(AZTEC_INITIALIZER_ATTRIBUTE), parameters, returnTypes, diff --git a/yarn-project/types/src/noir/index.ts b/yarn-project/types/src/noir/index.ts index 56a449d8ef0..f268071eae0 100644 --- a/yarn-project/types/src/noir/index.ts +++ b/yarn-project/types/src/noir/index.ts @@ -12,6 +12,7 @@ export const AZTEC_PUBLIC_ATTRIBUTE = 'aztec(public)'; export const AZTEC_PUBLIC_VM_ATTRIBUTE = 'aztec(public-vm)'; export const AZTEC_INTERNAL_ATTRIBUTE = 'aztec(internal)'; export const AZTEC_INITIALIZER_ATTRIBUTE = 'aztec(initializer)'; +export const AZTEC_VIEW_ATTRIBUTE = 'aztec(view)'; /** The witness indices of the parameters. */ type ParamWitnessIndices = { /** Start */ start: number; /** End */ end: number };