From 412b3bfed7a375bd21f468cf5c5349c87700836e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 26 Mar 2024 21:28:31 +0000 Subject: [PATCH 1/9] Add docs on call types --- docs/docs/misc/glossary/call_types.md | 95 +++++++++++++++++++ .../misc/{glossary.md => glossary/main.md} | 0 docs/sidebars.js | 10 +- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 docs/docs/misc/glossary/call_types.md rename docs/docs/misc/{glossary.md => glossary/main.md} (100%) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md new file mode 100644 index 000000000000..31361686aee5 --- /dev/null +++ b/docs/docs/misc/glossary/call_types.md @@ -0,0 +1,95 @@ +--- +## title: Call Types +--- + +# Understanding Call Types + +## What is a Call + +We say that a smart contract is called when one of its functions is invoked and its code is run. This means there'll be: + +- a caller +- arguments +- return values +- a call status (successful or failed) + +There are multiple types of calls, and some of the naming can make things **very** confusing. This page lists the different call types and execution modes, pointing out key differences between them. + +# Ethereum Call Types + +Even though we're discussing Aztec, its design is heavily influenced by Ethereum and many of the APIs and concepts are quite similar. It is therefore worthwhile to briefly review how things work there and what naming conventions are used to provide context to the Aztec-specific concepts. + +Broadly speaking, Ethereum contracts can be thought of as executing as a result of three different things: running certain EVM opcodes, running Solidity code (which compiles to EVM opcodes), or via the node JSON-RPC interface (e.g. when executing transactions). + +## EVM + +Certain opcodes allow contracts to make calls to other contracts, each with different semantics. We're particularly interested in `CALL` and `STATICCALL`, and how those relate to contract programming languages and client APIs. + +### `CALL` + +This is the most common and basic type of call. It grants execution control to the caller until it eventually returns. No special semantics are in play here. Most Ethereum transactions spend the majority of their time in `CALL` contexts. + +### `STATICCALL` + +This behaves almost exactly the same as `CALL`, with one key difference: any state-changing operations are forbidden and will immediately cause the call to fail. This includes writing to storage, emitting logs, or deploying new contracts. This call is used to query state on an external contract, e.g. to get data from a price oracle, check for access control permissions, etc. + +### Others + +The `CREATE` and `CREATE2` opcodes (for contract deployment) also result in something similar to a `CALL` context, but all that's special about them has to do with how deployments work. `DELEGATECALL` (and `CALLCODE`) are somewhat complicated to understand but don't have any Aztec equivalents, so they are not worth covering. + +## Solidity + +Solidity (and other contract programming languages such as Vyper) compile down to EVM opcodes, but it is useful to understand how they map language concepts to the different call types. + +### Mutating External Functions + +These are functions marked `payable` (which can receive ETH, which is a state change) or with no mutability declaration (sometimes called `nonpayable`). When one of these functions is called on a contract, the `CALL` opcode is emitted, meaning the callee can perform state changes, make further `CALL`s, etc. + +It is also possible to call such a function with `STATICCALL` manually (e.g. using assembly), but the execution will fail as soon as a state-changing opcode is executed. + +### `view` + +An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only _view_ the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). + +Note that it is entirely possible to use `CALL` to call a non-`view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that _untrusted or unknown_ contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): + +> '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' + +`pure` functions behave almost equivalently, but since `external pure` functions are so rare we'll skip them. + +## JSON-RPC + +From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). + +## `eth_sendTransaction` + +This method is how transactions are sent to a node to get them to be broadcast and eventually included in a block. The specified `to` address will be called in a `CALL` context, with some notable properties: + +- there are no return values, even if the contract function invoked does return some data +- there is no explicit caller: it is instead derived from a provided signature + +Some client libraries choose to automatically issue `eth_sendTransaction` when calling functions from a contract ABI that are not marked as `view` - [ethers is a good example](https://docs.ethers.org/v5/getting-started/#getting-started--writing). Notably, this means that any return value is lost and not available to the calling client - the library typically returns a transaction receipt instead. If the return value is required, then the only option is to simulate the call `eth_call`. + +Note that it is possible to call non state-changing functions (i.e. `view`) with `eth_sendTransaction` - this is always meaningless. What transactions do is change the blockchain state, so all calling such a function achieves is for the caller to lose funds by paying for gas fees. The sole purpose of a `view` function is to return data, and `eth_sendTransaction` does not make the return value available. + +## `eth_call` + +This method is the largest culprit of confusion around calls, but unfortunately requires understanding of all previous concepts in order to be explained. Its name is also quite unhelpful. + +What `eth_call` does is simulate a transaction (a call to a contract) given the current blockchain state. The behavior will be the exact same as `eth_sendTransaction`, except: + +- no actual transaction will be created +- while gas _will_ be measured, there'll be no transaction fees of any kind +- no signature is required: the `from` address is passed directly, and can be set to any value (even if the private key is unknown, or if they are contract addresses!) +- the return value of the called contract is available + +`eth_call` is typically used for one of the following: + +- query blockchain data, e.g. read token balances +- preview the state changes produced by a transaction, e.g. the transaction cost, token balance changes, etc + +Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** + +# Aztec + + diff --git a/docs/docs/misc/glossary.md b/docs/docs/misc/glossary/main.md similarity index 100% rename from docs/docs/misc/glossary.md rename to docs/docs/misc/glossary/main.md diff --git a/docs/sidebars.js b/docs/sidebars.js index f9b18d8372eb..51ed959a6aed 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -613,7 +613,15 @@ const sidebars = { defaultStyle: true, }, "misc/migration_notes", - "misc/glossary", + { + label: "Glossary", + type: "category", + link: { + type: "doc", + id: "misc/glossary/main", + }, + items: ["misc/glossary/call_types"], + }, { label: "Roadmap", type: "category", From 5fdf38d373277e9f819330da84b19f5276124b99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 10 Apr 2024 12:47:52 -0300 Subject: [PATCH 2/9] Update call_types.md --- docs/docs/misc/glossary/call_types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 31361686aee5..15d63a10b714 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -51,7 +51,7 @@ It is also possible to call such a function with `STATICCALL` manually (e.g. usi An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only _view_ the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). -Note that it is entirely possible to use `CALL` to call a non-`view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that _untrusted or unknown_ contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): +Note that it is entirely possible to use `CALL` to call a `view` function, and the result will be the exact same as if `STATICCALL` had been used. The reason why `STATICCALL` exists is so that _untrusted or unknown_ contracts can be called while still being able to reason about correctness. From the [EIP](https://eips.ethereum.org/EIPS/eip-214): > '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' From f04566eabf1a9c2842dc005a5d898d0ef0ad2009 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 10 Apr 2024 12:48:27 -0300 Subject: [PATCH 3/9] Update call_types.md --- docs/docs/misc/glossary/call_types.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 15d63a10b714..a733c513848b 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -55,8 +55,6 @@ Note that it is entirely possible to use `CALL` to call a `view` function, and t > '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' -`pure` functions behave almost equivalently, but since `external pure` functions are so rare we'll skip them. - ## JSON-RPC From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). From 14a69e619625237d755fb26517ca787b00626f03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Thu, 25 Apr 2024 18:21:21 +0000 Subject: [PATCH 4/9] Fix sidebar --- docs/docs/misc/glossary/call_types.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index a733c513848b..c6d2f0000c3b 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -15,39 +15,39 @@ We say that a smart contract is called when one of its functions is invoked and There are multiple types of calls, and some of the naming can make things **very** confusing. This page lists the different call types and execution modes, pointing out key differences between them. -# Ethereum Call Types +## Ethereum Call Types Even though we're discussing Aztec, its design is heavily influenced by Ethereum and many of the APIs and concepts are quite similar. It is therefore worthwhile to briefly review how things work there and what naming conventions are used to provide context to the Aztec-specific concepts. Broadly speaking, Ethereum contracts can be thought of as executing as a result of three different things: running certain EVM opcodes, running Solidity code (which compiles to EVM opcodes), or via the node JSON-RPC interface (e.g. when executing transactions). -## EVM +### EVM Certain opcodes allow contracts to make calls to other contracts, each with different semantics. We're particularly interested in `CALL` and `STATICCALL`, and how those relate to contract programming languages and client APIs. -### `CALL` +#### `CALL` This is the most common and basic type of call. It grants execution control to the caller until it eventually returns. No special semantics are in play here. Most Ethereum transactions spend the majority of their time in `CALL` contexts. -### `STATICCALL` +#### `STATICCALL` This behaves almost exactly the same as `CALL`, with one key difference: any state-changing operations are forbidden and will immediately cause the call to fail. This includes writing to storage, emitting logs, or deploying new contracts. This call is used to query state on an external contract, e.g. to get data from a price oracle, check for access control permissions, etc. -### Others +#### Others The `CREATE` and `CREATE2` opcodes (for contract deployment) also result in something similar to a `CALL` context, but all that's special about them has to do with how deployments work. `DELEGATECALL` (and `CALLCODE`) are somewhat complicated to understand but don't have any Aztec equivalents, so they are not worth covering. -## Solidity +### Solidity Solidity (and other contract programming languages such as Vyper) compile down to EVM opcodes, but it is useful to understand how they map language concepts to the different call types. -### Mutating External Functions +#### Mutating External Functions These are functions marked `payable` (which can receive ETH, which is a state change) or with no mutability declaration (sometimes called `nonpayable`). When one of these functions is called on a contract, the `CALL` opcode is emitted, meaning the callee can perform state changes, make further `CALL`s, etc. It is also possible to call such a function with `STATICCALL` manually (e.g. using assembly), but the execution will fail as soon as a state-changing opcode is executed. -### `view` +#### `view` An external function marked `view` will not be able to mutate state (write to storage, etc.), it can only _view_ the state. Solidity will emit the `STATICCALL` opcode when calling these functions, since its restrictions provide added safety to the caller (e.g. no risk of reentrancy). @@ -55,11 +55,11 @@ Note that it is entirely possible to use `CALL` to call a `view` function, and t > '`STATICCALL` adds a way to call other contracts and restrict what they can do in the simplest way. It can be safely assumed that the state of all accounts is the same before and after a static call.' -## JSON-RPC +### JSON-RPC From outside the EVM, calls to contracts are made via [JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc/) methods, typically from some client library that is aware of contract ABIs, such as [ethers.js](https://docs.ethers.org/v5) or [viem](https://viem.sh/). -## `eth_sendTransaction` +#### `eth_sendTransaction` This method is how transactions are sent to a node to get them to be broadcast and eventually included in a block. The specified `to` address will be called in a `CALL` context, with some notable properties: @@ -70,7 +70,7 @@ Some client libraries choose to automatically issue `eth_sendTransaction` when c Note that it is possible to call non state-changing functions (i.e. `view`) with `eth_sendTransaction` - this is always meaningless. What transactions do is change the blockchain state, so all calling such a function achieves is for the caller to lose funds by paying for gas fees. The sole purpose of a `view` function is to return data, and `eth_sendTransaction` does not make the return value available. -## `eth_call` +#### `eth_call` This method is the largest culprit of confusion around calls, but unfortunately requires understanding of all previous concepts in order to be explained. Its name is also quite unhelpful. @@ -88,6 +88,6 @@ What `eth_call` does is simulate a transaction (a call to a contract) given the Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** -# Aztec +## Aztec From b89c7953b782321ef9642216615cc9865e0a6619 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Fri, 26 Apr 2024 16:40:04 +0000 Subject: [PATCH 5/9] Add aztec call types --- docs/docs/misc/glossary/call_types.md | 88 ++++++++++++++++++- .../app_subscription_contract/src/main.nr | 2 + .../contracts/auth_contract/src/main.nr | 2 + .../crowdfunding_contract/src/main.nr | 2 + .../contracts/fpc_contract/src/main.nr | 2 + .../contracts/lending_contract/src/main.nr | 2 + .../end-to-end/src/e2e_auth_contract.test.ts | 6 ++ .../end-to-end/src/e2e_card_game.test.ts | 2 + 8 files changed, 104 insertions(+), 2 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index c6d2f0000c3b..79af91efaa02 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -88,6 +88,90 @@ What `eth_call` does is simulate a transaction (a call to a contract) given the Because some libraries ([such as ethers](https://docs.ethers.org/v5/getting-started/#getting-started--reading)) automatically use `eth_call` for `view` functions (which when called via Solidity result in the `STATICCALL` opcode), these concepts can be hard to tell apart. The following bears repeating: **an `eth_call`'s call context is the same as `eth_sendTransaction`, and it is a `CALL` context, not `STATICCALL`.** -## Aztec +## Aztec Call Types - +Large parts of the Aztec Network's design are still not finalized, and the nitty-gritty of contract calls is no exception. This section won't therefore contain a thorough review of these, but rather list some of the main ways contracts can currently be interacted with, with analogies to Ethereum call types when applicable. + +While Ethereum contracts are defined by bytecode that runs on the EVM, Aztec contracts have multiple modes of execution depending on the function that is invoked. + +### Private Execution + +Contract functions marked with `#[aztec(private)]` can only be called privately, and as such 'run' in the user's device. Since they're circuits, their 'execution' is actually the generation of a zk-SNARK proof that'll later be sent to the sequencer for verification. + +#### Private Calls + +Private functions from other contracts can be called either regularly or statically by using the `.call()` and `.static_call` functions. They will also be 'executed' (i.e. proved) in the user's device, and `static_call` will fail if any state changes are attempted (like the EVM's `STATICCALL`). + +#include_code private_call /noir-projects/noir-contracts/contracts/lending_contract/src/main.nr rust + +Unlike the EVM however, private execution doesn't revert in the traditional way: in case of error (e.g. a failed assertion, a state changing operation in a static context, etc.) the proof generation simply fails and no transaction request is generated, spending no network gas or user funds. + +#### Public Calls + +Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to _enqueue_ a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. + +Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted inncluding state changes caused by the private part, such as new commitments or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. + +#include_code enqueue /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust + +It is also possible to create public functions that can _only_ be invoked by privately enqueing a call from the same contract, which can very useful to update public state after private exection (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[aztec(internal)]`. + +:::warning +Calling public functions privately leaks some privacy! The caller of the function and all arguments will be revelead, so exercise care when mixing the private and public domains. +::: + +A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. + +#include_code enqueue_check /noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust + +To learn about alternative ways to acess public state privately, look into [Shared State](../../developers/contracts/references/storage/shared_state.md). + +### Public Execution + +Contract functions marked with `#[aztec(public)]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. + +Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is know about it is its correctness and side-effects (new commitments and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. + +Public functions in other contracts can be called both regularly and statically, just like on the EVM. + +#include_code public_call /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust + +:::note +This is the same function that was called by privately enqueuing a call to it! Public functions can be called either directly in a public context, or asynchronously by enqueuing in a private context. +::: + +### Top-level Unconstrained + +Contract functions with the `unconstrained` Noir keyword are a special type of function still under development, and their semantics will likely change in the near future. They are used to perform state queries from an off-chain client, and are never included in any transaction. No guarantees are made on the correctness of the result since they rely exclusively on unconstrained oracle calls. + +A reasonable mental model for them is that of a `view` Solidity function that is never called in any transaction, and is only ever invoked via `eth_call`. Note that in these the caller assumes that the node is acting honestly by exectuing the true contract bytecode with correct blockchain state, the same way the Aztec version assumes the oracles are returning legitimate data. + +### aztec.js + +There are three different ways to execute an Aztec contract function using the `aztec.js` library, with close similarities to their [JSON-RPC counterparts](#json-rpc). + +#### `simulate` + +This is used to get a result out of an execution, either private or public. It creates no transaction and spends no gas. The mental model is fairly close to that of [`eth_call`](#eth_call), in that it can be used to call any type of function, simulate its execution and get a result out of it. `simulate` is also the only way to run [top-level unconstrained functions](#top-level-unconstrained). + +#include_code public_getter /noir-projects/noir-contracts/contracts/auth_contract/src/main.nr rust + +#include_code simulate_public_getter yarn-project/end-to-end/src/e2e_auth_contract.test.ts typescript + +:::warning +No correctness is guaranteed on the result of `simulate`! Correct execution is entirely optional and left up to the client that handles this request. +::: + +#### `prove` + +This creates and returns a transaction request, which includes proof of correct private execution and side-efects. The request is not broadcast however, and no gas is spent. It is typically used in testing contexts to inspect transaction parameters or check private execution failure. + +#include_code local-tx-fails /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript + +#### `send` + +This is the same as [`prove`](#prove) except it also broadcasts the transaction and returns a receipt. This is how transactions are sent, getting them to be included in blocks and spending gas. It is similar to [`eth_sendTransaction`](#eth_sendtransaction), except it also performs some work on the user's device, namely the production of the proof for the private part of the transaction. + +#include_code send_tx yarn-project/end-to-end/src/e2e_card_game.test.ts typescript + +Unlike many Ethereum client libraries, `send` does not first `simulate` the call, so when testing for expected public execution failure use `simulate` as `send` will broadcast a valid transaction that reverts. \ No newline at end of file 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 eb316f301867..f54ce962bf37 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 @@ -47,7 +47,9 @@ contract AppSubscription { note.remaining_txs -= 1; storage.subscriptions.at(user_address).replace(&mut note, true); + // docs:start:enqueue GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context); + // docs:end:enqueue-public context.capture_min_revertible_side_effect_counter(); diff --git a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr index 04385202409e..20cc6ebacceb 100644 --- a/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/auth_contract/src/main.nr @@ -29,10 +29,12 @@ contract Auth { storage.authorized.schedule_value_change(authorized); } + // docs:start:public_getter #[aztec(public)] fn get_authorized() -> AztecAddress { storage.authorized.get_current_value_in_public() } + // docs:end:public_getter #[aztec(public)] fn get_scheduled_authorized() -> AztecAddress { 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 71102fda1a9e..ff8a8a4f3da1 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -49,7 +49,9 @@ contract Crowdfunding { #[aztec(private)] fn donate(amount: u64) { // 1) Check that the deadline has not passed + // docs:start:enqueue_check Crowdfunding::at(context.this_address())._check_deadline().enqueue(&mut context); + // docs:end:enqueue_check // 2) Transfer the donation tokens from donor to this contract Token::at(storage.donation_token.read_private()).transfer( diff --git a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr index f7636711d0e3..c877e8c7ff07 100644 --- a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr @@ -40,7 +40,9 @@ contract FPC { #[aztec(public)] #[aztec(internal)] fn pay_fee(refund_address: AztecAddress, amount: Field, asset: AztecAddress) { + // docs:start:public_call let refund = GasToken::at(storage.gas_token_address.read_public()).pay_fee(amount).call(&mut context); + // docs:end:public_call // Just do public refunds for the present Token::at(asset).transfer_public(context.this_address(), refund_address, refund, 0).call(&mut context); } 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 c2c05f58a093..3111c39d1caf 100644 --- a/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/lending_contract/src/main.nr @@ -236,7 +236,9 @@ contract Lending { stable_coin: AztecAddress ) { let on_behalf_of = compute_identifier(secret, on_behalf_of, context.msg_sender().to_field()); + // docs:start:private_call let _ = Token::at(stable_coin).burn(from, amount, nonce).call(&mut context); + // docs:end:private_call let _ = Lending::at(context.this_address())._repay(AztecAddress::from_field(on_behalf_of), amount, stable_coin).enqueue(&mut context); } diff --git a/yarn-project/end-to-end/src/e2e_auth_contract.test.ts b/yarn-project/end-to-end/src/e2e_auth_contract.test.ts index 099885c1c9a7..ba103f3565db 100644 --- a/yarn-project/end-to-end/src/e2e_auth_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_auth_contract.test.ts @@ -51,6 +51,10 @@ describe('e2e_auth_contract', () => { expect(await contract.methods.get_authorized().simulate()).toEqual(AztecAddress.ZERO); }); + it('non-admin canoot set authorized', async () => { + await expect(contract.withWallet(admin).methods.set_authorized(authorized.getAddress()).send().wait()).rejects.toThrow('caller_is_n'); + }); + it('admin sets authorized', async () => { await contract.withWallet(admin).methods.set_authorized(authorized.getAddress()).send().wait(); @@ -68,7 +72,9 @@ describe('e2e_auth_contract', () => { it('after a while the scheduled change is effective and can be used with max block restriction', async () => { await mineBlocks(DELAY); // This gets us past the block of change + // docs:start:simulate_public_getter expect(await contract.methods.get_authorized().simulate()).toEqual(authorized.getAddress()); + // docs:end:simulate_public_getter const interaction = contract.withWallet(authorized).methods.do_private_authorized_thing(); diff --git a/yarn-project/end-to-end/src/e2e_card_game.test.ts b/yarn-project/end-to-end/src/e2e_card_game.test.ts index 6342654d9edf..41aaad5a37d6 100644 --- a/yarn-project/end-to-end/src/e2e_card_game.test.ts +++ b/yarn-project/end-to-end/src/e2e_card_game.test.ts @@ -144,7 +144,9 @@ describe('e2e_card_game', () => { it('should be able to buy packs', async () => { const seed = 27n; + // docs:start:send_tx await contract.methods.buy_pack(seed).send().wait(); + // docs:end:send_tx const collection = await contract.methods.view_collection_cards(firstPlayer, 0).simulate({ from: firstPlayer }); const expected = getPackedCards(0, seed); expect(unwrapOptions(collection)).toMatchObject(expected); From 93f717de28f7a995981ccdf83fb0e0cda4acbd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 30 Apr 2024 15:53:33 +0000 Subject: [PATCH 6/9] Minor adjustments --- docs/docs/misc/glossary/call_types.md | 12 ++++++------ .../end-to-end/src/e2e_auth_contract.test.ts | 4 +++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 79af91efaa02..80d6067bd7a3 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -100,7 +100,7 @@ Contract functions marked with `#[aztec(private)]` can only be called privately, #### Private Calls -Private functions from other contracts can be called either regularly or statically by using the `.call()` and `.static_call` functions. They will also be 'executed' (i.e. proved) in the user's device, and `static_call` will fail if any state changes are attempted (like the EVM's `STATICCALL`). +Private functions from other contracts can be called either regularly or statically by using the `.call()` and `.static_call` functions. They will also be 'executed' (i.e. proved) in the user's device, and `static_call` will fail if any state changes are attempted (like the EVM's `STATICCALL`). #include_code private_call /noir-projects/noir-contracts/contracts/lending_contract/src/main.nr rust @@ -108,9 +108,9 @@ Unlike the EVM however, private execution doesn't revert in the traditional way: #### Public Calls -Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to _enqueue_ a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. +Since public execution can only be performed by the sequencer, public functions cannot be executed in a private context. It is possible however to _enqueue_ a public function call during private execution, requesting the sequencer to run it during inclusion of the transaction. It will be [executed in public](#public-execution) normally, including the possibility to enqueue static public calls. -Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted inncluding state changes caused by the private part, such as new commitments or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. +Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted inncluding state changes caused by the private part, such as new notes or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. #include_code enqueue /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust @@ -128,9 +128,9 @@ To learn about alternative ways to acess public state privately, look into [Shar ### Public Execution -Contract functions marked with `#[aztec(public)]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. +Contract functions marked with `#[aztec(public)]` can only be called publicly, and are executed by the sequencer. The computation model is very similar to the EVM: all state, parameters, etc. are known to the entire network, and no data is private. Static execution like the EVM's `STATICCALL` is possible too, with similar semantics (state can be accessed but not modified, etc.). -Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is know about it is its correctness and side-effects (new commitments and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. +Since private calls are always run in a user's device, it is not possible to perform any private execution from a public context. A reasonably good mental model for public execution is that of an EVM in which some work has already been done privately, and all that is know about it is its correctness and side-effects (new notes and nullifiers, enqueued public calls, etc.). A reverted public execution will also revert the private side-effects. Public functions in other contracts can be called both regularly and statically, just like on the EVM. @@ -174,4 +174,4 @@ This is the same as [`prove`](#prove) except it also broadcasts the transaction #include_code send_tx yarn-project/end-to-end/src/e2e_card_game.test.ts typescript -Unlike many Ethereum client libraries, `send` does not first `simulate` the call, so when testing for expected public execution failure use `simulate` as `send` will broadcast a valid transaction that reverts. \ No newline at end of file +Unlike many Ethereum client libraries, `send` does not first `simulate` the call, so when testing for expected public execution failure use `simulate` as `send` will broadcast a valid transaction that reverts. diff --git a/yarn-project/end-to-end/src/e2e_auth_contract.test.ts b/yarn-project/end-to-end/src/e2e_auth_contract.test.ts index ba103f3565db..c79d5ca8c334 100644 --- a/yarn-project/end-to-end/src/e2e_auth_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_auth_contract.test.ts @@ -52,7 +52,9 @@ describe('e2e_auth_contract', () => { }); it('non-admin canoot set authorized', async () => { - await expect(contract.withWallet(admin).methods.set_authorized(authorized.getAddress()).send().wait()).rejects.toThrow('caller_is_n'); + await expect( + contract.withWallet(other).methods.set_authorized(authorized.getAddress()).send().wait(), + ).rejects.toThrow('caller is not admin'); }); it('admin sets authorized', async () => { From 554ba92b722e791509dbb5ccee441e8a751973aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Tue, 30 Apr 2024 21:26:57 +0000 Subject: [PATCH 7/9] Fix links --- docs/docs/misc/glossary/call_types.md | 4 ++-- .../contracts/app_subscription_contract/src/main.nr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 80d6067bd7a3..5263fcca9f09 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -112,7 +112,7 @@ Since public execution can only be performed by the sequencer, public functions Since the public call is made asynchronously, any return values or side effects are not available during private execution. If the public function fails once executed, the entire transaction is reverted inncluding state changes caused by the private part, such as new notes or nullifiers. Note that this does result in gas being spent, like in the case of the EVM. -#include_code enqueue /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust +#include_code enqueue_public /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust It is also possible to create public functions that can _only_ be invoked by privately enqueing a call from the same contract, which can very useful to update public state after private exection (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[aztec(internal)]`. @@ -134,7 +134,7 @@ Since private calls are always run in a user's device, it is not possible to per Public functions in other contracts can be called both regularly and statically, just like on the EVM. -#include_code public_call /noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr rust +#include_code public_call /noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr rust :::note This is the same function that was called by privately enqueuing a call to it! Public functions can be called either directly in a public context, or asynchronously by enqueuing in a private context. 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 89505fc906ef..9958d456807c 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 @@ -47,9 +47,9 @@ contract AppSubscription { note.remaining_txs -= 1; storage.subscriptions.at(user_address).replace(&mut note, true); - // docs:start:enqueue + // docs:start:enqueue_public GasToken::at(storage.gas_token_address.read_private()).pay_fee(42).enqueue(&mut context); - // docs:end:enqueue-public + // docs:end:enqueue_public context.end_setup(); From 06f8a7318eab91b4d7e9ece50a2d8a7d251de7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 6 May 2024 22:09:38 +0000 Subject: [PATCH 8/9] Improve static enqueue section --- docs/docs/misc/glossary/call_types.md | 12 ++++++------ .../contracts/crowdfunding_contract/src/main.nr | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 5263fcca9f09..9f86d4681d1c 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -116,15 +116,15 @@ Since the public call is made asynchronously, any return values or side effects It is also possible to create public functions that can _only_ be invoked by privately enqueing a call from the same contract, which can very useful to update public state after private exection (e.g. update a token's supply after privately minting). This is achieved by annotating functions with `#[aztec(internal)]`. -:::warning -Calling public functions privately leaks some privacy! The caller of the function and all arguments will be revelead, so exercise care when mixing the private and public domains. -::: - A common pattern is to enqueue public calls to check some validity condition on public state, e.g. that a deadline has not expired or that some public value is set. -#include_code enqueue_check /noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust +#include_code call-check-deadline /noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust -To learn about alternative ways to acess public state privately, look into [Shared State](../../developers/contracts/references/storage/shared_state.md). +#include_code deadline /noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust + +:::warning +Calling public functions privately leaks some privacy! The caller of the function and all arguments will be revelead, so exercise care when mixing the private and public domains. To learn about alternative ways to access public state privately, look into [Shared State](../../developers/contracts/references/storage/shared_state.md). +::: ### Public Execution 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 bf6cb64aab70..3b1f71147721 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -68,9 +68,7 @@ contract Crowdfunding { #[aztec(private)] fn donate(amount: u64) { // 1) Check that the deadline has not passed - // docs:start:enqueue_check Crowdfunding::at(context.this_address())._check_deadline().enqueue(&mut context); - // docs:end:enqueue_check // docs:end:call-check-deadline // docs:start:do-transfer From becbe36ca3221d9a209911ef41d732c15f8e7bef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 6 May 2024 22:16:05 +0000 Subject: [PATCH 9/9] Clarify prove for public sim --- docs/docs/misc/glossary/call_types.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/misc/glossary/call_types.md b/docs/docs/misc/glossary/call_types.md index 9f86d4681d1c..3de6d61d8344 100644 --- a/docs/docs/misc/glossary/call_types.md +++ b/docs/docs/misc/glossary/call_types.md @@ -164,14 +164,14 @@ No correctness is guaranteed on the result of `simulate`! Correct execution is e #### `prove` -This creates and returns a transaction request, which includes proof of correct private execution and side-efects. The request is not broadcast however, and no gas is spent. It is typically used in testing contexts to inspect transaction parameters or check private execution failure. +This creates and returns a transaction request, which includes proof of correct private execution and side-efects. The request is not broadcast however, and no gas is spent. It is typically used in testing contexts to inspect transaction parameters or to check for execution failure. #include_code local-tx-fails /yarn-project/end-to-end/src/guides/dapp_testing.test.ts typescript +Like most Ethereum libraries, `prove` also simulates public execution to try to detect runtime errors that would only occur once the transaction is picked up by the sequencer. This makes `prove` very useful in testing environments, but users shuld be wary of both false positives and negatives in production environments, particularly if the node's data is stale. Public simulation can be skipped by setting the `skipPublicSimulation` flag. + #### `send` This is the same as [`prove`](#prove) except it also broadcasts the transaction and returns a receipt. This is how transactions are sent, getting them to be included in blocks and spending gas. It is similar to [`eth_sendTransaction`](#eth_sendtransaction), except it also performs some work on the user's device, namely the production of the proof for the private part of the transaction. #include_code send_tx yarn-project/end-to-end/src/e2e_card_game.test.ts typescript - -Unlike many Ethereum client libraries, `send` does not first `simulate` the call, so when testing for expected public execution failure use `simulate` as `send` will broadcast a valid transaction that reverts.