From df9e9f3552e6fb91b31d258dc213f96929b8968a Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Fri, 26 Jan 2024 23:05:25 +0900 Subject: [PATCH 01/12] unconstrained function docs --- docs/docs/concepts/advanced/acir_simulator.md | 2 +- .../advanced/private_execution_environment.md | 2 +- docs/docs/dev_docs/contracts/compiling.md | 4 +- .../dev_docs/contracts/syntax/context.mdx | 2 +- docs/docs/dev_docs/contracts/syntax/events.md | 4 +- .../dev_docs/contracts/syntax/functions.md | 314 ------------------ .../syntax/functions/calling_functions.md | 134 ++++++++ .../contracts/syntax/functions/constructor.md | 20 ++ .../syntax/functions/inner_workings.md | 99 ++++++ .../contracts/syntax/functions/main.md | 19 ++ .../contracts/syntax/functions/oracles.md | 24 ++ .../functions/public_private_unconstrained.md | 31 ++ .../contracts/syntax/functions/visibility.md | 23 ++ docs/docs/dev_docs/contracts/syntax/main.md | 2 +- .../contracts/syntax/storage/private_state.md | 4 +- .../aztecnr-getting-started.md | 2 +- docs/docs/dev_docs/limitations/main.md | 8 +- .../writing_private_voting_contract.md | 2 +- .../src/client/unconstrained_execution.ts | 2 + 19 files changed, 368 insertions(+), 330 deletions(-) delete mode 100644 docs/docs/dev_docs/contracts/syntax/functions.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/constructor.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/main.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/oracles.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md create mode 100644 docs/docs/dev_docs/contracts/syntax/functions/visibility.md diff --git a/docs/docs/concepts/advanced/acir_simulator.md b/docs/docs/concepts/advanced/acir_simulator.md index bae95f54ff1..59d0dac51fc 100644 --- a/docs/docs/concepts/advanced/acir_simulator.md +++ b/docs/docs/concepts/advanced/acir_simulator.md @@ -14,7 +14,7 @@ It simulates three types of functions: Private functions are simulated and proved client-side, and verified client-side in the private kernel circuit. -They are run with the assistance of a DB oracle that provides any private data requested by the function. You can read more about oracle functions in the smart contract section [here](../../dev_docs/contracts/syntax/functions.md#oracle-functions). +They are run with the assistance of a DB oracle that provides any private data requested by the function. You can read more about oracle functions in the smart contract section [here](../../dev_docs/contracts/syntax/functions/oracles.md). Private functions can call other private functions and can request to call a public function. The public function execution will be performed by the sequencer asynchronously, so private functions don't have direct access to the return values of public functions. diff --git a/docs/docs/concepts/advanced/private_execution_environment.md b/docs/docs/concepts/advanced/private_execution_environment.md index 50eadfa4820..2d789a8a998 100644 --- a/docs/docs/concepts/advanced/private_execution_environment.md +++ b/docs/docs/concepts/advanced/private_execution_environment.md @@ -62,7 +62,7 @@ The keystore is a secure storage for private and public keys. ## Oracles -Oracles are pieces of data that are injected into a smart contract function from the client side. You can read more about why and how they work in the [functions section](../../dev_docs/contracts/syntax/functions.md). +Oracles are pieces of data that are injected into a smart contract function from the client side. You can read more about why and how they work in the [functions section](../../dev_docs/contracts/syntax/functions/oracles.md). ## For developers To learn how to develop on top of the PXE, refer to these guides: diff --git a/docs/docs/dev_docs/contracts/compiling.md b/docs/docs/dev_docs/contracts/compiling.md index 8d19f7811a8..e635f0567b4 100644 --- a/docs/docs/dev_docs/contracts/compiling.md +++ b/docs/docs/dev_docs/contracts/compiling.md @@ -115,7 +115,7 @@ Read more about interacting with contracts using `aztec.js` [here](../getting_st ### Aztec.nr interfaces -An Aztec.nr contract can [call a function](./syntax/functions.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. +An Aztec.nr contract can [call a function](./syntax/functions/main.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. To make this easier, the compiler can generate contract interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one. For each contract, two interface structs are generated: one to be used from private functions with a `PrivateContext`, and one to be used from open functions with a `PublicContext`. @@ -209,7 +209,7 @@ impl TokenPublicContextInterface { } ``` -Read more about how to use the Aztec.nr interfaces [here](./syntax/functions.md#contract-interface). +Read more about how to use the Aztec.nr interfaces [here](./syntax/functions/main.md). :::info At the moment, the compiler generates these interfaces from already compiled ABIs, and not from source code. This means that you should not import a generated interface from within the same project as its source contract, or you risk circular references. diff --git a/docs/docs/dev_docs/contracts/syntax/context.mdx b/docs/docs/dev_docs/contracts/syntax/context.mdx index 3321cd91c4a..317ce147e01 100644 --- a/docs/docs/dev_docs/contracts/syntax/context.mdx +++ b/docs/docs/dev_docs/contracts/syntax/context.mdx @@ -97,7 +97,7 @@ The `args_hash` is the result of pedersen hashing all of a function's inputs. ### Return Values -The return values are a set of values that are returned from an applications execution to be passed to other functions through the kernel. Developers do not need to worry about passing their function return values to the `context` directly as `Aztec.nr` takes care of it for you. See the documentation surrounding `Aztec.nr` [macro expansion](./functions.md#after-expansion) for more details. +The return values are a set of values that are returned from an applications execution to be passed to other functions through the kernel. Developers do not need to worry about passing their function return values to the `context` directly as `Aztec.nr` takes care of it for you. See the documentation surrounding `Aztec.nr` [macro expansion](./functions/inner_workings.md#after-expansion) for more details. return_values : BoundedVec, diff --git a/docs/docs/dev_docs/contracts/syntax/events.md b/docs/docs/dev_docs/contracts/syntax/events.md index 2ccc3c0be38..234e978544f 100644 --- a/docs/docs/dev_docs/contracts/syntax/events.md +++ b/docs/docs/dev_docs/contracts/syntax/events.md @@ -61,7 +61,7 @@ In the future we will allow emitting arbitrary information. (If you currently emit arbitrary information, PXE will fail to decrypt, process and store this data, so it will not be queryable). ::: -To emit encrypted logs first import the `emit_encrypted_log` utility function which wraps an [oracle](./functions.md#oracle-functions): +To emit encrypted logs first import the `emit_encrypted_log` utility function which wraps an [oracle](./functions/oracles.md): #include_code encrypted_import /yarn-project/aztec-nr/address-note/src/address_note.nr rust @@ -95,7 +95,7 @@ They can be emitted by both public and private functions. :::danger - Emitting unencrypted events from private function is a significant privacy leak and it should be considered by the developer whether it is acceptable. -- Unencrypted events are currently **NOT** linked to the contract emitting them, so it is practically a [`debug_log`](./functions.md#a-few-useful-inbuilt-oracles). +- Unencrypted events are currently **NOT** linked to the contract emitting them, so it is practically a [`debug_log`](./functions/oracles.md#a-few-useful-inbuilt-oracles). ::: To emit unencrypted logs first import the `emit_unencrypted_log` utility function inside your contract: diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md deleted file mode 100644 index 7bda2b14e87..00000000000 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ /dev/null @@ -1,314 +0,0 @@ ---- -title: Functions -description: This page covers functions, private and public functions composability, as well as their differences. ---- - -Functions serve as the building blocks of smart contracts. Functions can be either public, ie they can interact with other contracts and the blockchain, or private for internal contract use. Every smart contract also has a private `constructor` function which is called when the contract is deployed. There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. - -On this page, you’ll learn more about: - -- How function visibility works in Aztec -- A detailed understanding of public, private, and unconstrained functions, and how to write them -- How constructors work and remain private -- The process of calling functions from within the same smart contract and from different contracts, including calling private functions from private functions, public from public, and even private from public -- What oracles and how Aztec smart contracts might use them - -## Visibility - -In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. - -### Data Visibility - -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../concepts/foundation/communication/public_private_calls/main.md). - -In the following sections, we are going to see how these two "types" co-exists and interact. - -### Function visibility - -This is the kind of visibility you are more used to seeing in Solidity and more traditional programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. - -By default, all functions are callable from other contracts, similarly to the Solidity `public` visibility. To make them only callable from the contract itself, you can mark them as `internal`. Contrary to solidity, we don't have the `external` nor `private` keywords. `external` since it is limited usage when we don't support inheritance, and `private` since we don't support inheritance and it would also be confusing with multiple types of `private`. - -A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. - -:::danger -Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. -::: - -## Mutability - -Currently, any function is "mutable" in the sense that it might alter state. In the future, we will support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). This is useful for when you want to call a function in a separate contract, but ensure that it cannot alter state, or call other functions that might alter state (such as re-entering). - -Similarly, a special case of a mutating call is the `delegatecall` where the function executed might not be in the same contract as the state being altered. It is at this moment, not certain if `delegatecall`s should become a fully fledged feature. - -:::danger No `staticcall` or `delegatecall` support -While `staticcall` and `delegatecall` both have flags in the call context, they are currently not supported and will not behave as one would expect if usage is attempted. -::: - -## `constructor` - -- A special `constructor` function MUST be declared within a contract's scope. -- A constructor doesn't have a name, because its purpose is clear: to initialize contract state. -- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). -- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. - -An example of a somewhat boring constructor is as follows: - -#include_code empty-constructor /yarn-project/noir-contracts/contracts/test_contract/src/main.nr rust - -Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. - -#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust - -:::danger -It is not possible to call public functions from within a constructor. Beware that the compiler will not throw an error if you do, but the execution will fail! -::: - -## `Public` Functions - -A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. - -To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](./context.mdx#public-context) available within your current function's execution scope. - -#include_code set_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -## `Private` Functions - -As alluded to earlier, a private function operates on private information, and is executed by the user. To tell the compiler that this is the kind of function we are creating annotate it with the `#[aztec(private)]` attribute. This will make the [private context](./context.mdx#private-context-broken-down) available within your current function's execution scope. - -#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -## `unconstrained` functions - -Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seen as untrusted! That they are untrusted means that, for security, the developer must make sure to constrain them when used! - -Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. - -#include_code balance_of_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -:::info -Note, that unconstrained functions can have access to both public and private data when executed on the user's device. This is possible since it is not actually part of the circuits that are executed in contract execution. -::: - -## Oracle functions - -An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. - -While this is one type of oracle, the more general oracle, allows us to get "some" data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be embedded at any point in the circuit, and thus don't need to be an input parameter. - -**Why is this useful? Why don't just pass them as input parameters?** -In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, only commitments sit in there 😱. The pre-images (content) of your notes need to be provided to the function to prove that you actually allowed to spend them. - -You could of course provide them to your function as inputs, but then functions that have different underlying notes would end up with different function signatures and thus selectors. This means that integrating with many different tokens (with different underlying notes) would become a pain for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. - -If we are instead fetching the notes using an oracle call, we can keep the function signature independent of the underlying notes and thus make it much easier to integrate with! A similar idea, but applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation making integrations a breeze, see [AuthWit](../../wallets/main#authorizing-actions) for more information on this. - -Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! - -`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: -#include_code oracles-module /yarn-project/aztec-nr/aztec/src/oracle.nr rust - -You can learn how to use oracles in your smart contracts [here](../syntax/oracles.md). - -## Calling functions from other functions - -### Private -> Private - -In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../concepts/advanced/circuits/kernels/private_kernel.md), and take place on the user's device. -Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../wallets/writing_an_account_contract.md)). - -Take, for example, the following call stack: - -``` -AccountContract::entrypoint - |-> Foo::example_call - | -> Bar::nested_call - |-> Baz::example_call -``` - -In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the account contract makes one last call to `Baz::example_call`. - -Lets further illustrate what these examples could look like - - - -```rust -// Foo contains a singular function that returns the result of Bar::nested_call -contract Foo { - #[aztec(private)] - fn example_call(input: Field) -> pub Field { - Bar::at().nested_call(input) - } -} - -// Bar contains a singular function that returns a `input + 1` -contract Bar { - #[aztec(private)] - fn nested_call(input: Field) -> pub Field { - input + 1 - } -} - -// Baz contains a singular function that simply returns `10` -contract Baz { - #[aztec(private)] - fn example_call() -> pub Field { - 10 - } -} -``` - -When simulating the following call stack, we can expect execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `Foo::example_call`, then begin to execute the code there. When the simulator executes the code in contract `Foo`, it will find the further nested call to contract `Bar::nested_call`. It will execute the code in `Bar`, bringing the return value back to contract `Foo`. -The same process will be followed for contract `Baz`. - -So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. - -Those of you who have written circuits before may see an issue here. The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `Bar` in contract `Foo`? This value will not be constrained. - -This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to constrain the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. - -The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. - - - -With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the `token_interface`s burn function below. This struct is just providing us a clean way to call function, but we could also just call the function directly as it is done in this function. - -:::info -Note that the function selector is computed using one of the oracles from earlier, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--`AztecAddress` becomes `(Field)`. -::: - -#include_code private_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust - -Using this interface, we can then call it as seen below. All the way down at the bottom we can see that we are calling the `burn` function from the `token_interface` struct. - -The following snippet is from a token bridge that is burning the underlying token and creating a message for L1 to mint some assets to the `recipient` on Ethereum. - -#include_code exit_to_l1_private /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust - -### Public -> Public - -The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../concepts/advanced/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. - -Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. - -The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). - -This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../concepts/advanced/public_vm.md). -The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. - -Calling a public function from another public function is quite similar to what we saw for private to private, with the keyword private swapped for public. - -#include_code public_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust -#include_code exit_to_l1_public /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust - -### Private -> Public - -As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../concepts/foundation/communication/public_private_calls/main.md). - -Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. -When the sequencer receives the messages, it will take over and execute the public parts of the transaction. - -As a consequence a private function _CANNOT_ accept a return value from a public function. It can only dispatch it. - -The code required to dispatch a public function call from a private function is actually quite similar to private to private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain, essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). - -#include_code unshield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust -#include_code increase_public_balance /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -As we can see above, the private to public transaction flow looks very similar to the others in snippets, with the practicality being a bit different behind the scenes. - - - -### Public -> Private - -Wait, I thought you said we could not do this? Well, you are right, we cannot do this directly. However, we can do this indirectly if we are a little cheeky. - -While we cannot directly call a private function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still pretty useful. - -In the snippet below, we insert a custom note, the transparent note, into the commitments tree from public such that it can later be consumed in private. - -#include_code shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -If you recall the `redeem_shield` from back in the [private function section](#private-functions), you might remember it removing a `TransparentNote` from `pending_shields`. This is the note that we just inserted from public! - -#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../portals/main.md) we are going to see this pattern again. - -:::danger -Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). -::: - ---- - -## Deep dive - -Below, we go more into depth of what is happening under the hood when you create a function in Aztec.nr and what the attributes are really doing. - -### Function type attributes explained. - -Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. - -However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../concepts/advanced/circuits/kernels/private_kernel.md). - -To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. - -#### Before expansion - -#include_code simple_macro_example /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -#### After expansion - -#include_code simple_macro_example_expanded /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -#### The expansion broken down? - -Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../concepts/advanced/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. - -**Receiving context from the kernel.** -#include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../concepts/advanced/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. -For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. - -The kernel can then check that all of the values passed to each circuit in a function call are the same. - -**Returning the context to the kernel.** -#include_code context-example-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Just as the kernel passes information into the app circuits, the application must return information about the executed app back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. - -> _Why is it called the `PrivateCircuitPublicInputs`_ -> It is commonly asked why the return values of a function in a circuit are labeled as the `Public Inputs`. Common intuition from other programming paradigms suggests that the return values and public inputs should be distinct. -> However; In the eyes of the circuit, anything that is publicly viewable (or checkable) is a public input. Hence in this case, the return values are also public inputs. - -This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the actual return values of the function! - -**Hashing the function inputs.** -#include_code context-example-hasher /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -_What is the hasher and why is it needed?_ - -Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value. - -**Creating the function's context.** -#include_code context-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Each Aztec function has access to a [context](./context.mdx) object. This object although ergonomically a global variable, is local. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. - -#include_code context-example-context-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -As previously mentioned we use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). -We achieve this by pushing return values to the execution context, which we then pass to the kernel. - -**Making the contract's storage available** -#include_code storage-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -When a [`Storage` struct](./storage/main.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. - -Any state variables declared in the `Storage` struct can now be accessed as normal struct members. - -**Returning the function context to the kernel.** -#include_code context-example-finish /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit. diff --git a/docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md b/docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md new file mode 100644 index 00000000000..0d02cb00f0a --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md @@ -0,0 +1,134 @@ +--- +title: Calling Functions from Other Functions +--- + +This page talks about how functions call other functions. For a more practical guide into calling functions from other functions, follow the [token tutorial](../../../tutorials/writing_token_contract.md). + +### Private -> Private + +In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../concepts/advanced/circuits/kernels/private_kernel.md), and take place on the user's device. +Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../../wallets/writing_an_account_contract.md)). + +Take, for example, the following call stack: + +``` +AccountContract::entrypoint + |-> Foo::example_call + | -> Bar::nested_call + |-> Baz::example_call +``` + +In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the account contract makes one last call to `Baz::example_call`. + +Lets further illustrate what these examples could look like + + + +```rust +// Foo contains a singular function that returns the result of Bar::nested_call +contract Foo { + #[aztec(private)] + fn example_call(input: Field) -> pub Field { + Bar::at().nested_call(input) + } +} + +// Bar contains a singular function that returns a `input + 1` +contract Bar { + #[aztec(private)] + fn nested_call(input: Field) -> pub Field { + input + 1 + } +} + +// Baz contains a singular function that simply returns `10` +contract Baz { + #[aztec(private)] + fn example_call() -> pub Field { + 10 + } +} +``` + +When simulating the following call stack, we can expect execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `Foo::example_call`, then begin to execute the code there. When the simulator executes the code in contract `Foo`, it will find the further nested call to contract `Bar::nested_call`. It will execute the code in `Bar`, bringing the return value back to contract `Foo`. +The same process will be followed for contract `Baz`. + +So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. + +Those of you who have written circuits before may see an issue here. The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `Bar` in contract `Foo`? This value will not be constrained. + +This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to constrain the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. + +The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. + + + +With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the `token_interface`s burn function below. This struct is just providing us a clean way to call function, but we could also just call the function directly as it is done in this function. + +:::info +Note that the function selector is computed using one of the oracles from earlier, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--`AztecAddress` becomes `(Field)`. +::: + +#include_code private_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust + +Using this interface, we can then call it as seen below. All the way down at the bottom we can see that we are calling the `burn` function from the `token_interface` struct. + +The following snippet is from a token bridge that is burning the underlying token and creating a message for L1 to mint some assets to the `recipient` on Ethereum. + +#include_code exit_to_l1_private /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust + +### Public -> Public + +The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../../concepts/advanced/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. + +Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. + +The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). + +This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../../concepts/advanced/public_vm.md). +The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. + +Calling a public function from another public function is quite similar to what we saw for private to private, with the keyword private swapped for public. + +#include_code public_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust +#include_code exit_to_l1_public /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust + +### Private -> Public + +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../../concepts/foundation/communication/public_private_calls/main.md). + +Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. +When the sequencer receives the messages, it will take over and execute the public parts of the transaction. + +As a consequence a private function _CANNOT_ accept a return value from a public function. It can only dispatch it. + +The code required to dispatch a public function call from a private function is actually quite similar to private to private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain, essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). + +#include_code unshield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code increase_public_balance /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +As we can see above, the private to public transaction flow looks very similar to the others in snippets, with the practicality being a bit different behind the scenes. + + + +### Public -> Private + +Wait, I thought you said we could not do this? Well, you are right, we cannot do this directly. However, we can do this indirectly if we are a little cheeky. + +While we cannot directly call a private function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still pretty useful. + +In the snippet below, we insert a custom note, the transparent note, into the commitments tree from public such that it can later be consumed in private. + +#include_code shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +If you recall the `redeem_shield` from back in the [private function section](./public_private_unconstrained.md#private-functions), you might remember it removing a `TransparentNote` from `pending_shields`. This is the note that we just inserted from public! + +#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../../../../concepts/foundation/communication/cross_chain_calls.md) we are going to see this pattern again. + +:::danger +Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). +::: + +--- diff --git a/docs/docs/dev_docs/contracts/syntax/functions/constructor.md b/docs/docs/dev_docs/contracts/syntax/functions/constructor.md new file mode 100644 index 00000000000..fb5ad0346e0 --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/constructor.md @@ -0,0 +1,20 @@ +--- +title: Constructor +--- + +A special `constructor` function **must** be declared within a contract's scope. +- A constructor doesn't have a name, because its purpose is clear: to initialize contract state. +- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). +- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. + +An example of a somewhat boring constructor is as follows: + +#include_code empty-constructor /yarn-project/noir-contracts/contracts/test_contract/src/main.nr rust + +Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. + +#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust + +:::danger +It is not possible to call public functions from within a constructor. Beware that the compiler will not throw an error if you do, but the execution will fail! +::: \ No newline at end of file diff --git a/docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md b/docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md new file mode 100644 index 00000000000..518ed14855a --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md @@ -0,0 +1,99 @@ +--- +title: Inner Workings of Functions +--- + +Below, we go more into depth of what is happening under the hood when you create a function in Aztec.nr and what the attributes are really doing. + +## Private functions + +Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. + +However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../concepts/advanced/circuits/kernels/private_kernel.md). + +To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. + +#### Before expansion + +#include_code simple_macro_example /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +#### After expansion + +#include_code simple_macro_example_expanded /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +#### The expansion broken down? + +Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../concepts/advanced/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. + +**Receiving context from the kernel.** +#include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../concepts/advanced/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. +For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. + +The kernel can then check that all of the values passed to each circuit in a function call are the same. + +**Returning the context to the kernel.** +#include_code context-example-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Just as the kernel passes information into the app circuits, the application must return information about the executed app back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. + +> _Why is it called the `PrivateCircuitPublicInputs`_ +> It is commonly asked why the return values of a function in a circuit are labeled as the `Public Inputs`. Common intuition from other programming paradigms suggests that the return values and public inputs should be distinct. +> However; In the eyes of the circuit, anything that is publicly viewable (or checkable) is a public input. Hence in this case, the return values are also public inputs. + +This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the actual return values of the function! + +**Hashing the function inputs.** +#include_code context-example-hasher /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +_What is the hasher and why is it needed?_ + +Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value. + +**Creating the function's context.** +#include_code context-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Each Aztec function has access to a [context](../context.mdx) object. This object although ergonomically a global variable, is local. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. + +#include_code context-example-context-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +As previously mentioned we use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). +We achieve this by pushing return values to the execution context, which we then pass to the kernel. + +**Making the contract's storage available** +#include_code storage-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +When a [`Storage` struct](../storage/main.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. + +Any state variables declared in the `Storage` struct can now be accessed as normal struct members. + +**Returning the function context to the kernel.** +#include_code context-example-finish /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit. + +## Unconstrained functions + +Defining a function as `unconstrained` tells Aztec to simulate it completely client-side in the [ACIR simulator](../../../../concepts/advanced/acir_simulator.md) without generating proofs. They are useful for extracting information from a user through an [oracle](./oracles.md). + +When an unconstrained function is called, it prompts the ACIR simulator to + +1. generate the execution environment +2. execute the function within this environment + +To generate the environment, the simulator gets the blockheader from the [PXE database](../../../../concepts/advanced/private_execution_environment.md#database) and passes it along with the contract address to `ViewDataOracle`. This creates a context that simulates the state of the blockchain at a specific block, allowing the unconstrained function to access and interact with blockchain data as it would appear in that block, but without affecting the actual blockchain state. + +Once the execution environment is created, `execute_unconstrained_function` is invoked: + +#include_code execute_unconstrained_function yarn-project/acir-simulator/src/client/unconstrained_execution.ts typescript + +This: + +1. Prepares the ACIR for execution +2. Converts `args` into a format suitable for the ACVM (Aztec's virtual machine), creating an initial witness (witness = set of inputs required to compute the function). `args` might be an oracle to request a user's balance +3. Executes the function in the ACVM, which involves running the ACIR with the initial witness and the context. If requesting a user's balance, this would query the balance from the PXE database +4. Extracts the return values from the `partialWitness` and decodes them based on the artifact to get the final function output. The [artifact](../../artifacts.md) is the compiled output of the contract, and has information like the function signature, parameter types, and return types + + + + diff --git a/docs/docs/dev_docs/contracts/syntax/functions/main.md b/docs/docs/dev_docs/contracts/syntax/functions/main.md new file mode 100644 index 00000000000..d5189489b4c --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/main.md @@ -0,0 +1,19 @@ +--- +title: Functions +--- + +Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they can interact with other contracts and the blockchain, or **private** for internal contract use. + +For a more practical guide of using multiple types of functions, follow the [token tutorial](../../../tutorials/writing_token_contract.md). + +Every smart contract has a private `constructor` function which is called when the contract is deployed. There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. + +Currently, any function is "mutable" in the sense that it might alter state. In the future, we will support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). + +Explore this section to learn: + +- [How function visibility works in Aztec](./visibility.md) +- [Public, private, and unconstrained functions](./public_private_unconstrained.md), and how to write them +- How [constructors](./constructor.md) work and remain private +- [Calling functions from within the same smart contract and from different contracts](./calling_functions.md), including calling private functions from private functions, public from public, and even private from public +- [Oracles](./oracles) and how Aztec smart contracts might use them \ No newline at end of file diff --git a/docs/docs/dev_docs/contracts/syntax/functions/oracles.md b/docs/docs/dev_docs/contracts/syntax/functions/oracles.md new file mode 100644 index 00000000000..50d7f23a642 --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/oracles.md @@ -0,0 +1,24 @@ +--- +title: Oracle Functions +--- + +This page goes over what oracles are in Aztec and how they work. + +Looking for a practical guide? You can learn how to use oracles in a smart contract [here](../oracles.md). + +An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. + +While this is one type of oracle, the more general oracle, allows us to get "some" data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be embedded at any point in the circuit, and thus don't need to be an input parameter. + +**Why is this useful? Why don't just pass them as input parameters?** +In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, only commitments sit in there 😱. The pre-images (content) of your notes need to be provided to the function to prove that you actually allowed to spend them. + +You could of course provide them to your function as inputs, but then functions that have different underlying notes would end up with different function signatures and thus selectors. This means that integrating with many different tokens (with different underlying notes) would become a pain for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. + +If we are instead fetching the notes using an oracle call, we can keep the function signature independent of the underlying notes and thus make it much easier to integrate with! A similar idea, but applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation making integrations a breeze, see [AuthWit](../../../wallets/main#authorizing-actions) for more information on this. + +Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! + +`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: +#include_code oracles-module /yarn-project/aztec-nr/aztec/src/oracle.nr rust + diff --git a/docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md b/docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md new file mode 100644 index 00000000000..121ecf7a2c5 --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md @@ -0,0 +1,31 @@ +--- +title: Public, Private, and Unconstrained Functions +--- + +This page explains the three types of functions that exist on Aztec - public, private, and unconstrained. For a deeper dive into how these functions work under the hood, check out the [Inner Workings](./inner_workings.md) page. + +## `Public` Functions + +A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. + +To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](../context.mdx) available within your current function's execution scope. + +#include_code set_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +## `Private` Functions + +As alluded to earlier, a private function operates on private information, and is executed by the user. To tell the compiler that this is the kind of function we are creating annotate it with the `#[aztec(private)]` attribute. This will make the [private context](../context.mdx#private-context-broken-down) available within your current function's execution scope. + +#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +## `unconstrained` functions + +Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seen as untrusted! That they are untrusted means that, for security, the developer must make sure to constrain them when used! + +Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. + +#include_code balance_of_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +:::info +Note, that unconstrained functions can have access to both public and private data when executed on the user's device. This is possible since it is not actually part of the circuits that are executed in contract execution. +::: \ No newline at end of file diff --git a/docs/docs/dev_docs/contracts/syntax/functions/visibility.md b/docs/docs/dev_docs/contracts/syntax/functions/visibility.md new file mode 100644 index 00000000000..2f4ec0ed01c --- /dev/null +++ b/docs/docs/dev_docs/contracts/syntax/functions/visibility.md @@ -0,0 +1,23 @@ +--- +title: Visibility +--- + +In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. + +### Data Visibility + +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../concepts/foundation/communication/public_private_calls/main.md). + +### Function visibility + +This is the kind of visibility you are more used to seeing in Solidity and more traditional programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. + +By default, all functions are callable from other contracts, similarly to the Solidity `public` visibility. To make them only callable from the contract itself, you can mark them as `internal`. Contrary to solidity, we don't have the `external` nor `private` keywords. `external` since it is limited usage when we don't support inheritance, and `private` since we don't support inheritance and it would also be confusing with multiple types of `private`. + +A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. + +:::danger +Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. +::: + +For a practical guide of using multiple types of data visibility and `internal` functions,follow the [token tutorial](../../../tutorials/writing_token_contract.md). diff --git a/docs/docs/dev_docs/contracts/syntax/main.md b/docs/docs/dev_docs/contracts/syntax/main.md index 7157b46764c..73a6c7ffddc 100644 --- a/docs/docs/dev_docs/contracts/syntax/main.md +++ b/docs/docs/dev_docs/contracts/syntax/main.md @@ -12,7 +12,7 @@ Aztec.nr contains abstractions which remove the need to understand the low-level - Public and private [state variable types](./storage/main.md) - Some pre-designed notes - Functions for [emitting](./events.md) encrypted and unencrypted logs -- [Oracle functions](./functions.md#oracle-functions) for accessing: +- [Oracle functions](./functions/oracles.md) for accessing: - private state - secrets - Functions for communicating with [Ethereum L1](../portals/main.md) diff --git a/docs/docs/dev_docs/contracts/syntax/storage/private_state.md b/docs/docs/dev_docs/contracts/syntax/storage/private_state.md index c663f7c187f..aa0dc87f51f 100644 --- a/docs/docs/dev_docs/contracts/syntax/storage/private_state.md +++ b/docs/docs/dev_docs/contracts/syntax/storage/private_state.md @@ -180,7 +180,7 @@ Set is used for managing a collection of notes. All notes in a Set are of the sa You can view the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/yarn-project/aztec-nr/aztec/src/state_vars/set.nr). -And can be added to the `Storage` struct as follows. Here adding a set for a custom note, the TransparentNote (useful for [public -> private communication](../functions.md#public---private)). +And can be added to the `Storage` struct as follows. Here adding a set for a custom note, the TransparentNote (useful for [public -> private communication](../functions/calling_functions.md#public---private)). #include_code storage_pending_shields /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust @@ -244,7 +244,7 @@ This function requires a `NoteViewerOptions`. The `NoteViewerOptions` is essenti ### NoteGetterOptions -`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a [data oracle](../functions.md#oracle-functions). Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. +`NoteGetterOptions` encapsulates a set of configurable options for filtering and retrieving a selection of notes from a [data oracle](../functions/oracles.md). Developers can design instances of `NoteGetterOptions`, to determine how notes should be filtered and returned to the functions of their smart contracts. You can view the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/yarn-project/aztec-nr/aztec/src/note/note_getter_options.nr). diff --git a/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md b/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md index 27357e5640e..52d2c8fe36e 100644 --- a/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md +++ b/docs/docs/dev_docs/getting_started/aztecnr-getting-started.md @@ -114,7 +114,7 @@ Let’s create a `constructor` method to run on deployment that assigns an initi This function accesses the counts from storage. Then it assigns the passed initial counter to the `owner`'s counter privately using `at().add()`. -We have annotated this and other functions with `#[aztec(private)]` which are ABI macros so the compiler understands it will handle private inputs. Learn more about functions and annotations [here](../contracts/syntax/functions.md). +We have annotated this and other functions with `#[aztec(private)]` which are ABI macros so the compiler understands it will handle private inputs. Learn more about functions and annotations [here](../contracts/syntax/functions/main.md). ## Incrementing our counter diff --git a/docs/docs/dev_docs/limitations/main.md b/docs/docs/dev_docs/limitations/main.md index bc28d3fd480..27832e18772 100644 --- a/docs/docs/dev_docs/limitations/main.md +++ b/docs/docs/dev_docs/limitations/main.md @@ -29,13 +29,13 @@ Help shape and define: - It is a testing environment, it is insecure, unaudited and does not generate any proofs, its only for testing purposes; - Constructors can not call nor alter public state - - The constructor is executed exclusively in private domain, WITHOUT the ability to call public functions or alter public state. This means to set initial storage values, you need to follow a pattern similar to [proxies in Ethereum](https://blog.openzeppelin.com/proxy-patterns), where you `initialize` the contract with values after it have been deployed, see [constructor](../contracts/syntax/functions.md#constructor). -- No static nor delegate calls (see [mutability](../contracts/syntax/functions.md#mutability)). + - The constructor is executed exclusively in private domain, WITHOUT the ability to call public functions or alter public state. This means to set initial storage values, you need to follow a pattern similar to [proxies in Ethereum](https://blog.openzeppelin.com/proxy-patterns), where you `initialize` the contract with values after it have been deployed, see [constructor](../contracts/syntax/functions/constructor.md). +- No static nor delegate calls (see [mutability](../contracts/syntax/functions/main.md)). - These values are unused in the call-context. - Beware that what you think of as a `view` could alter state ATM! Notably the account could alter state or re-enter whenever the account contract's `is_valid` function is called. - `msg_sender` is currently leaking when doing private -> public calls - The `msg_sender` will always be set, if you call a public function from the private world, the `msg_sender` will be set to the private caller's address. See [function context](../contracts/syntax/context.mdx). -- The initial `msg_sender` is 0, which can be problematic for some contracts, see [function visibility](../contracts/syntax/functions.md#function-visibility). +- The initial `msg_sender` is 0, which can be problematic for some contracts, see [function visibility](../contracts/syntax/functions/visibility.md). - Unencrypted logs don't link to the contract that emitted it, so essentially just a `debug_log`` that you can match values against. - A note that is created and nullified in the same transaction will still emit an encrypted log. - A limited amount of new commitments, nullifiers and calls that are supported by a transaction, see [circuit limitations](#circuit-limitations). @@ -193,7 +193,7 @@ Here are the current constants: #### What are the consequences? -When you write an [Aztec.nr](../contracts/main.md) [function](../contracts/syntax/functions.md), there will be upper bounds on the following: +When you write an [Aztec.nr](../contracts/main.md) [function](../contracts/syntax/functions/main.md), there will be upper bounds on the following: - The number of public state reads and writes; - The number of note reads and nullifications; diff --git a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md index c8ab1e70f64..a75a165fde7 100644 --- a/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md +++ b/docs/docs/dev_docs/tutorials/writing_private_voting_contract.md @@ -143,7 +143,7 @@ Create a private function called `cast_vote`: In this function, we do not create a nullifier with the address directly. This would leak privacy as it would be easy to reverse-engineer. We must add some randomness or some form of secret, like [nullifier secrets](../../concepts/foundation/accounts/keys.md#nullifier-secrets). -To do this, we make an [oracle call](../contracts/syntax/functions.md#oracle-functions) to fetch the caller's secret key, hash it to create a nullifier, and push the nullifier to Aztec. The `secret.high` and `secret.low` values here refer to how we divide a large [Grumpkin scalar](https://github.com/AztecProtocol/aztec-packages/blob/7fb35874eae3f2cad5cb922282a619206573592c/noir/noir_stdlib/src/grumpkin_scalar.nr) value into its higher and lower parts. This allows for faster cryptographic computations so our hash can still be secure but is calculated faster. +To do this, we make an [oracle call](../contracts/syntax/functions/oracles.md) to fetch the caller's secret key, hash it to create a nullifier, and push the nullifier to Aztec. The `secret.high` and `secret.low` values here refer to how we divide a large [Grumpkin scalar](https://github.com/AztecProtocol/aztec-packages/blob/7fb35874eae3f2cad5cb922282a619206573592c/noir/noir_stdlib/src/grumpkin_scalar.nr) value into its higher and lower parts. This allows for faster cryptographic computations so our hash can still be secure but is calculated faster. After pushing the nullifier, we update the `tally` to reflect this vote. As we know from before, a private function cannot update public state directly, so we are calling a public function. diff --git a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts index 26fa87f0846..62f934d41eb 100644 --- a/yarn-project/acir-simulator/src/client/unconstrained_execution.ts +++ b/yarn-project/acir-simulator/src/client/unconstrained_execution.ts @@ -10,6 +10,7 @@ import { ExecutionError } from '../common/errors.js'; import { AcirSimulator } from '../index.js'; import { ViewDataOracle } from './view_data_oracle.js'; +// docs:start:execute_unconstrained_function /** * Execute an unconstrained function and return the decoded values. */ @@ -45,3 +46,4 @@ export async function executeUnconstrainedFunction( return decodeReturnValues(artifact, extractReturnWitness(acir, partialWitness)); } +// docs:end:execute_unconstrained_function From 30da9212cf603eeb9ab129c57251e9c1720c1d71 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Fri, 26 Jan 2024 23:13:17 +0900 Subject: [PATCH 02/12] sidebar --- docs/sidebars.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index 0829e9fca27..3b622eae583 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -334,7 +334,22 @@ const sidebars = { ], }, "dev_docs/contracts/syntax/events", - "dev_docs/contracts/syntax/functions", + { + label: "Functions", + type: "category", + link: { + type: "doc", + id: "dev_docs/contracts/syntax/functions/main", + }, + items: [ + "dev_docs/contracts/syntax/functions/visibility", + "dev_docs/contracts/syntax/functions/public_private_unconstrained", + "dev_docs/contracts/syntax/functions/constructor", + "dev_docs/contracts/syntax/functions/calling_functions", + "dev_docs/contracts/syntax/functions/oracles", + "dev_docs/contracts/syntax/functions/inner_workings", + ], + }, "dev_docs/contracts/syntax/oracles", { label: "Proving Historical Blockchain Data", From 13dbceda800c1b0357be93b24cdf29632bc98bfd Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Fri, 26 Jan 2024 23:27:08 +0900 Subject: [PATCH 03/12] build error --- docs/docs/dev_docs/contracts/layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/dev_docs/contracts/layout.md b/docs/docs/dev_docs/contracts/layout.md index 084a3a9c1fc..48fbc9a7030 100644 --- a/docs/docs/dev_docs/contracts/layout.md +++ b/docs/docs/dev_docs/contracts/layout.md @@ -2,7 +2,7 @@ title: Structure --- -A contract is a collection of persistent [state variables](./syntax/storage/main.md), and [functions](./syntax/functions) which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. +A contract is a collection of persistent [state variables](./syntax/storage/main.md), and [functions](./syntax/functions/main.md) which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. # Contract From cbd1fe18b56ca42b52044d139f3858f01cea973f Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Mon, 29 Jan 2024 21:29:09 +0900 Subject: [PATCH 04/12] merge conflicts and build errors --- docs/docs/developers/aztecjs/main.md | 2 +- .../resources/common_patterns/authwit.md | 2 +- .../syntax/functions/calling_functions.md | 10 +++--- .../contracts/syntax/functions/constructor.md | 0 .../syntax/functions/inner_workings.md | 10 +++--- .../contracts/syntax/functions/main.md | 0 .../contracts/syntax/functions/oracles.md | 0 .../functions/public_private_unconstrained.md | 0 .../contracts/syntax/functions/visibility.md | 2 +- .../developers/debugging/aztecnr-errors.md | 2 +- docs/docs/developers/getting_started/main.md | 2 +- .../developers/getting_started/quickstart.md | 2 +- .../learn/about_aztec/technical_overview.md | 2 +- .../docs/learn/concepts/pxe/acir_simulator.md | 2 +- docs/docs/learn/concepts/pxe/main.md | 2 +- docs/sidebars.js | 33 +++++++++---------- 16 files changed, 35 insertions(+), 36 deletions(-) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/calling_functions.md (93%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/constructor.md (100%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/inner_workings.md (84%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/main.md (100%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/oracles.md (100%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/public_private_unconstrained.md (100%) rename docs/docs/{dev_docs => developers}/contracts/syntax/functions/visibility.md (94%) diff --git a/docs/docs/developers/aztecjs/main.md b/docs/docs/developers/aztecjs/main.md index 531754b2992..49487fa1c28 100644 --- a/docs/docs/developers/aztecjs/main.md +++ b/docs/docs/developers/aztecjs/main.md @@ -2,7 +2,7 @@ title: Aztec.js --- -If you are looking for the API reference, go [here](../../apis/aztec-js/index.md). +If you are looking for the API reference, go [here](../../developers/aztecjs/main.md). ## Introduction diff --git a/docs/docs/developers/contracts/resources/common_patterns/authwit.md b/docs/docs/developers/contracts/resources/common_patterns/authwit.md index 5700644561b..0858b4cbebb 100644 --- a/docs/docs/developers/contracts/resources/common_patterns/authwit.md +++ b/docs/docs/developers/contracts/resources/common_patterns/authwit.md @@ -11,7 +11,7 @@ description: Developer Documentation to use Authentication Witness for authentic Authentication Witness is a scheme for authentication actions on Aztec, so users can allow third-parties (eg protocols or other users) to execute an action on their behalf. -How it works logically is explained in the [foundational concepts](./../../../../learn/concepts/accounts/authwit.md) but we will do a short recap here. +How it works logically is explained in the [concepts](./../../../../learn/concepts/accounts/authwit.md) but we will do a short recap here. An authentication witness is defined for a specific action, such as allowing a Defi protocol to transfer funds on behalf of the user. An action is here something that could be explained as `A is allowed to perform X operation on behalf of B` and we define it as a hash computed as such: diff --git a/docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md b/docs/docs/developers/contracts/syntax/functions/calling_functions.md similarity index 93% rename from docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md rename to docs/docs/developers/contracts/syntax/functions/calling_functions.md index 0d02cb00f0a..c178167b35e 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions/calling_functions.md +++ b/docs/docs/developers/contracts/syntax/functions/calling_functions.md @@ -6,7 +6,7 @@ This page talks about how functions call other functions. For a more practical g ### Private -> Private -In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../concepts/advanced/circuits/kernels/private_kernel.md), and take place on the user's device. +In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md), and take place on the user's device. Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../../wallets/writing_an_account_contract.md)). Take, for example, the following call stack: @@ -79,13 +79,13 @@ The following snippet is from a token bridge that is burning the underlying toke ### Public -> Public -The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../../concepts/advanced/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. +The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../../learn/concepts/hybrid_state/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). -This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../../concepts/advanced/public_vm.md). +This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../../learn/concepts/hybrid_state/public_vm.md). The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. Calling a public function from another public function is quite similar to what we saw for private to private, with the keyword private swapped for public. @@ -95,7 +95,7 @@ Calling a public function from another public function is quite similar to what ### Private -> Public -As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../../concepts/foundation/communication/public_private_calls/main.md). +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the concepts [here](../../../../learn/concepts/communication/public_private_calls/main.md). Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. When the sequencer receives the messages, it will take over and execute the public parts of the transaction. @@ -125,7 +125,7 @@ If you recall the `redeem_shield` from back in the [private function section](./ #include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust -When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../../../../concepts/foundation/communication/cross_chain_calls.md) we are going to see this pattern again. +When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](./../../../../learn/concepts/communication/cross_chain_calls.md) we are going to see this pattern again. :::danger Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). diff --git a/docs/docs/dev_docs/contracts/syntax/functions/constructor.md b/docs/docs/developers/contracts/syntax/functions/constructor.md similarity index 100% rename from docs/docs/dev_docs/contracts/syntax/functions/constructor.md rename to docs/docs/developers/contracts/syntax/functions/constructor.md diff --git a/docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md b/docs/docs/developers/contracts/syntax/functions/inner_workings.md similarity index 84% rename from docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md rename to docs/docs/developers/contracts/syntax/functions/inner_workings.md index 518ed14855a..c00fa4294bd 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions/inner_workings.md +++ b/docs/docs/developers/contracts/syntax/functions/inner_workings.md @@ -8,7 +8,7 @@ Below, we go more into depth of what is happening under the hood when you create Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. -However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../concepts/advanced/circuits/kernels/private_kernel.md). +However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. @@ -22,12 +22,12 @@ To help illustrate how this interacts with the internals of Aztec and its kernel #### The expansion broken down? -Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../concepts/advanced/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. +Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. **Receiving context from the kernel.** #include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust -Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../concepts/advanced/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. +Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. The kernel can then check that all of the values passed to each circuit in a function call are the same. @@ -74,14 +74,14 @@ This function takes the application context, and converts it into the `PrivateCi ## Unconstrained functions -Defining a function as `unconstrained` tells Aztec to simulate it completely client-side in the [ACIR simulator](../../../../concepts/advanced/acir_simulator.md) without generating proofs. They are useful for extracting information from a user through an [oracle](./oracles.md). +Defining a function as `unconstrained` tells Aztec to simulate it completely client-side in the [ACIR simulator](../../../../learn/concepts/pxe/acir_simulator.md) without generating proofs. They are useful for extracting information from a user through an [oracle](./oracles.md). When an unconstrained function is called, it prompts the ACIR simulator to 1. generate the execution environment 2. execute the function within this environment -To generate the environment, the simulator gets the blockheader from the [PXE database](../../../../concepts/advanced/private_execution_environment.md#database) and passes it along with the contract address to `ViewDataOracle`. This creates a context that simulates the state of the blockchain at a specific block, allowing the unconstrained function to access and interact with blockchain data as it would appear in that block, but without affecting the actual blockchain state. +To generate the environment, the simulator gets the blockheader from the [PXE database](../../../../learn/concepts/pxe/main.md#database) and passes it along with the contract address to `ViewDataOracle`. This creates a context that simulates the state of the blockchain at a specific block, allowing the unconstrained function to access and interact with blockchain data as it would appear in that block, but without affecting the actual blockchain state. Once the execution environment is created, `execute_unconstrained_function` is invoked: diff --git a/docs/docs/dev_docs/contracts/syntax/functions/main.md b/docs/docs/developers/contracts/syntax/functions/main.md similarity index 100% rename from docs/docs/dev_docs/contracts/syntax/functions/main.md rename to docs/docs/developers/contracts/syntax/functions/main.md diff --git a/docs/docs/dev_docs/contracts/syntax/functions/oracles.md b/docs/docs/developers/contracts/syntax/functions/oracles.md similarity index 100% rename from docs/docs/dev_docs/contracts/syntax/functions/oracles.md rename to docs/docs/developers/contracts/syntax/functions/oracles.md diff --git a/docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md b/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md similarity index 100% rename from docs/docs/dev_docs/contracts/syntax/functions/public_private_unconstrained.md rename to docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md diff --git a/docs/docs/dev_docs/contracts/syntax/functions/visibility.md b/docs/docs/developers/contracts/syntax/functions/visibility.md similarity index 94% rename from docs/docs/dev_docs/contracts/syntax/functions/visibility.md rename to docs/docs/developers/contracts/syntax/functions/visibility.md index 2f4ec0ed01c..0da1e073e82 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions/visibility.md +++ b/docs/docs/developers/contracts/syntax/functions/visibility.md @@ -6,7 +6,7 @@ In Aztec there are multiple different types of visibility that can be applied to ### Data Visibility -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../concepts/foundation/communication/public_private_calls/main.md). +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../learn/concepts/communication/main.md). ### Function visibility diff --git a/docs/docs/developers/debugging/aztecnr-errors.md b/docs/docs/developers/debugging/aztecnr-errors.md index 5d7e14aea2a..38e5bf757e9 100644 --- a/docs/docs/developers/debugging/aztecnr-errors.md +++ b/docs/docs/developers/debugging/aztecnr-errors.md @@ -57,7 +57,7 @@ This error occurs when you are trying to interact with a smart contract via an P To execute a transaction, the PXE needs to know the complete address of a contract, portal address (if portal is used) and contract artifacts. -To address the error, add the contract to the PXE by calling [`pxe.addContracts(...)`](../../apis/pxe/interfaces/PXE#addcontracts). +To address the error, add the contract to the PXE by calling [`pxe.addContracts(...)`](../../developers/apis/pxe/interfaces/PXE#addcontracts). #### `Simulation error: No public key registered for address 0x0. Register it by calling pxe.registerRecipient(...) or pxe.registerAccount(...)` diff --git a/docs/docs/developers/getting_started/main.md b/docs/docs/developers/getting_started/main.md index 0ee5ab59990..0e6fa1844ed 100644 --- a/docs/docs/developers/getting_started/main.md +++ b/docs/docs/developers/getting_started/main.md @@ -12,7 +12,7 @@ If this is your first time using Aztec, and you want to get started by learning ## Learn -If you want to read more about the high level concepts of Aztec before writing some code, head to the [Foundational Concepts section](../../learn/about_aztec/technical_overview.md). +If you want to read more about the high level concepts of Aztec before writing some code, head to the [Concepts section](../../learn/about_aztec/technical_overview.md). ## In this section diff --git a/docs/docs/developers/getting_started/quickstart.md b/docs/docs/developers/getting_started/quickstart.md index 62b56e10afe..ba159b50a8b 100644 --- a/docs/docs/developers/getting_started/quickstart.md +++ b/docs/docs/developers/getting_started/quickstart.md @@ -77,7 +77,7 @@ Note that the deployed contract address is exported, so we can use it as `$CONTR Alice is set up as the contract admin and token minter in the `_initialize` function. Let's get Alice some private tokens. -We need to export the `SECRET` and `SECRET_HASH` values in order to privately mint tokens. Private tokens are claimable by anyone with the pre-image to a provided hash, see more about how the token contract works in the [token contract tutorial](../tutorials/writing_token_contract.md). After the tokens have been minted, the notes will have to added to the [Private Execution Environment](../../apis/pxe/interfaces/PXE) (PXE) to be consumed by private functions. Once added, Alice can claim them with the `redeem_shield` function. After this, Alice should have 1000 tokens in their private balance. +We need to export the `SECRET` and `SECRET_HASH` values in order to privately mint tokens. Private tokens are claimable by anyone with the pre-image to a provided hash, see more about how the token contract works in the [token contract tutorial](../tutorials/writing_token_contract.md). After the tokens have been minted, the notes will have to added to the [Private Execution Environment](../../developers/apis/pxe/interfaces/PXE) (PXE) to be consumed by private functions. Once added, Alice can claim them with the `redeem_shield` function. After this, Alice should have 1000 tokens in their private balance. #include_code mint-private yarn-project/end-to-end/src/guides/up_quick_start.sh bash diff --git a/docs/docs/learn/about_aztec/technical_overview.md b/docs/docs/learn/about_aztec/technical_overview.md index 8a5e3ac8638..8533690e4d8 100644 --- a/docs/docs/learn/about_aztec/technical_overview.md +++ b/docs/docs/learn/about_aztec/technical_overview.md @@ -36,7 +36,7 @@ An overview of the Aztec network architecture will help contextualize the concep ### Aztec.js -A user of the Aztec network will interact with the network through Aztec.js. Aztec.js is a library that provides APIs for managing accounts and interacting with smart contracts (including account contracts) on the Aztec network. It communicates with the [Private eXecution Environment (PXE)](../../apis/pxe/interfaces/PXE) through a `PXE` implementation, allowing developers to easily register new accounts, deploy contracts, view functions, and send transactions. +A user of the Aztec network will interact with the network through Aztec.js. Aztec.js is a library that provides APIs for managing accounts and interacting with smart contracts (including account contracts) on the Aztec network. It communicates with the [Private eXecution Environment (PXE)](../../developers/apis/pxe/interfaces/PXE) through a `PXE` implementation, allowing developers to easily register new accounts, deploy contracts, view functions, and send transactions. ### Private Execution Environment diff --git a/docs/docs/learn/concepts/pxe/acir_simulator.md b/docs/docs/learn/concepts/pxe/acir_simulator.md index c6d499e5665..0b794773205 100644 --- a/docs/docs/learn/concepts/pxe/acir_simulator.md +++ b/docs/docs/learn/concepts/pxe/acir_simulator.md @@ -14,7 +14,7 @@ It simulates three types of functions: Private functions are simulated and proved client-side, and verified client-side in the private kernel circuit. -They are run with the assistance of a DB oracle that provides any private data requested by the function. You can read more about oracle functions in the smart contract section [here](../../dev_docs/contracts/syntax/functions/oracles.md). +They are run with the assistance of a DB oracle that provides any private data requested by the function. You can read more about oracle functions in the smart contract section [here](../../../developers/contracts/syntax/functions/oracles.md). Private functions can call other private functions and can request to call a public function. The public function execution will be performed by the sequencer asynchronously, so private functions don't have direct access to the return values of public functions. diff --git a/docs/docs/learn/concepts/pxe/main.md b/docs/docs/learn/concepts/pxe/main.md index 5cde4c40d1e..039329e4a15 100644 --- a/docs/docs/learn/concepts/pxe/main.md +++ b/docs/docs/learn/concepts/pxe/main.md @@ -62,7 +62,7 @@ The keystore is a secure storage for private and public keys. ## Oracles -Oracles are pieces of data that are injected into a smart contract function from the client side. You can read more about why and how they work in the [functions section](../../dev_docs/contracts/syntax/functions/oracles.md). +Oracles are pieces of data that are injected into a smart contract function from the client side. You can read more about why and how they work in the [functions section](../../../developers/contracts/syntax/functions/oracles.md). ## For developers To learn how to develop on top of the PXE, refer to these guides: diff --git a/docs/sidebars.js b/docs/sidebars.js index f397f0589ae..8d9435c1503 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -298,38 +298,37 @@ const sidebars = { }, items: ["developers/contracts/syntax/storage/storage_slots"], }, - "dev_docs/contracts/syntax/events", + "developers/contracts/syntax/events", { - label: "Functions", + label: "Storage", type: "category", link: { type: "doc", - id: "dev_docs/contracts/syntax/functions/main", + id: "developers/contracts/syntax/storage/main", }, items: [ - "dev_docs/contracts/syntax/storage/public_state", - "dev_docs/contracts/syntax/storage/private_state", - "dev_docs/contracts/syntax/storage/storage_slots", + "developers/contracts/syntax/storage/public_state", + "developers/contracts/syntax/storage/private_state", + "developers/contracts/syntax/storage/storage_slots", ], }, - "dev_docs/contracts/syntax/events", { label: "Functions", type: "category", link: { type: "doc", - id: "dev_docs/contracts/syntax/functions/main", + id: "developers/contracts/syntax/functions/main", }, items: [ - "dev_docs/contracts/syntax/functions/visibility", - "dev_docs/contracts/syntax/functions/public_private_unconstrained", - "dev_docs/contracts/syntax/functions/constructor", - "dev_docs/contracts/syntax/functions/calling_functions", - "dev_docs/contracts/syntax/functions/oracles", - "dev_docs/contracts/syntax/functions/inner_workings", + "developers/contracts/syntax/functions/visibility", + "developers/contracts/syntax/functions/public_private_unconstrained", + "developers/contracts/syntax/functions/constructor", + "developers/contracts/syntax/functions/calling_functions", + "developers/contracts/syntax/functions/oracles", + "developers/contracts/syntax/functions/inner_workings", ], }, - "dev_docs/contracts/syntax/oracles", + "developers/contracts/syntax/oracles", { label: "Proving Historical Blockchain Data", type: "category", @@ -468,12 +467,12 @@ const sidebars = { { label: "Aztec.js", type: "category", - items: [{ dirName: "apis/aztec-js", type: "autogenerated" }], + items: [{ dirName: "developers/apis/aztec-js", type: "autogenerated" }], }, { label: "Accounts", type: "category", - items: [{ dirName: "apis/accounts", type: "autogenerated" }], + items: [{ dirName: "developers/apis/accounts", type: "autogenerated" }], }, ], }, From fe9499a8b475a2ba84823626b6cf328417efc25d Mon Sep 17 00:00:00 2001 From: Josh Crites Date: Mon, 29 Jan 2024 14:48:01 -0500 Subject: [PATCH 05/12] remove developers from api path --- docs/sidebars.js | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index d97cf26d61a..38e004d8ed2 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -32,7 +32,7 @@ const sidebars = { "learn/about_aztec/what_is_aztec", "learn/about_aztec/vision", "learn/about_aztec/technical_overview", - + { type: "html", value: '', @@ -55,9 +55,7 @@ const sidebars = { type: "doc", id: "learn/concepts/hybrid_state/main", }, - items: [ - "learn/concepts/hybrid_state/public_vm", - ], + items: ["learn/concepts/hybrid_state/public_vm"], }, { label: "Storage", @@ -70,9 +68,7 @@ const sidebars = { type: "doc", id: "learn/concepts/storage/trees/main", }, - items: [ - "learn/concepts/storage/trees/indexed_merkle_tree", - ], + items: ["learn/concepts/storage/trees/indexed_merkle_tree"], }, "learn/concepts/storage/storage_slots", ], @@ -97,9 +93,7 @@ const sidebars = { type: "doc", id: "learn/concepts/smart_contracts/main", }, - items: [ - "learn/concepts/smart_contracts/contract_creation", - ], + items: ["learn/concepts/smart_contracts/contract_creation"], }, { label: "Communication", @@ -126,9 +120,7 @@ const sidebars = { type: "doc", id: "learn/concepts/pxe/main", }, - items: [ - "learn/concepts/pxe/acir_simulator", - ], + items: ["learn/concepts/pxe/acir_simulator"], }, { label: "Circuits", @@ -267,7 +259,7 @@ const sidebars = { items: [ "developers/cli/cli-commands", "developers/cli/sandbox-reference", - "developers/cli/run_more_than_one_pxe_sandbox" + "developers/cli/run_more_than_one_pxe_sandbox", ], }, { @@ -297,10 +289,10 @@ const sidebars = { id: "developers/contracts/syntax/storage/main", }, items: [ - "developers/contracts/syntax/storage/private_state", - "developers/contracts/syntax/storage/public_state", - "developers/contracts/syntax/storage/storage_slots", - ], + "developers/contracts/syntax/storage/private_state", + "developers/contracts/syntax/storage/public_state", + "developers/contracts/syntax/storage/storage_slots", + ], }, "developers/contracts/syntax/events", { @@ -339,10 +331,10 @@ const sidebars = { items: [ "developers/contracts/syntax/historical_access/how_to_prove_history", "developers/contracts/syntax/historical_access/history_lib_reference", - ], + ], }, "developers/contracts/syntax/slow_updates_tree", - + "developers/contracts/syntax/context", "developers/contracts/syntax/globals", ], @@ -471,12 +463,12 @@ const sidebars = { { label: "Aztec.js", type: "category", - items: [{ dirName: "developers/apis/aztec-js", type: "autogenerated" }], + items: [{ dirName: "apis/aztec-js", type: "autogenerated" }], }, { label: "Accounts", type: "category", - items: [{ dirName: "developers/apis/accounts", type: "autogenerated" }], + items: [{ dirName: "apis/accounts", type: "autogenerated" }], }, ], }, @@ -519,4 +511,4 @@ const sidebars = { ], }; -module.exports = sidebars; \ No newline at end of file +module.exports = sidebars; From 67b15671668de81c498fee646b33ae1f7130063e Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Tue, 30 Jan 2024 06:31:31 +0000 Subject: [PATCH 06/12] josh's suggestions Co-authored-by: josh crites --- .../syntax/functions/calling_functions.md | 30 +++++++++---------- .../contracts/syntax/functions/constructor.md | 2 -- .../syntax/functions/inner_workings.md | 27 ++++++++--------- .../contracts/syntax/functions/oracles.md | 7 ++--- .../functions/public_private_unconstrained.md | 10 +++++-- .../contracts/syntax/functions/visibility.md | 2 +- 6 files changed, 38 insertions(+), 40 deletions(-) diff --git a/docs/docs/developers/contracts/syntax/functions/calling_functions.md b/docs/docs/developers/contracts/syntax/functions/calling_functions.md index c178167b35e..873b4ac1abc 100644 --- a/docs/docs/developers/contracts/syntax/functions/calling_functions.md +++ b/docs/docs/developers/contracts/syntax/functions/calling_functions.md @@ -2,12 +2,12 @@ title: Calling Functions from Other Functions --- -This page talks about how functions call other functions. For a more practical guide into calling functions from other functions, follow the [token tutorial](../../../tutorials/writing_token_contract.md). +This page talks about how functions call other functions. For a more hands-on guide into calling functions from other functions, follow the [token tutorial](../../../tutorials/writing_token_contract.md). ### Private -> Private In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md), and take place on the user's device. -Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../../wallets/writing_an_account_contract.md)). +Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of private-to-private interaction is calling a private function on another contract from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../../wallets/writing_an_account_contract.md)). Take, for example, the following call stack: @@ -18,7 +18,7 @@ AccountContract::entrypoint |-> Baz::example_call ``` -In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the account contract makes one last call to `Baz::example_call`. +In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the Account Contract makes one last call to `Baz::example_call`. Lets further illustrate what these examples could look like @@ -55,18 +55,18 @@ The same process will be followed for contract `Baz`. So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. -Those of you who have written circuits before may see an issue here. The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `Bar` in contract `Foo`? This value will not be constrained. +Aztec differs from Ethereum in that these function calls are really executing zk programs (or circuits). The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which are not directly aware of one another in the way that Ethereum contracts are. How is it possible to use a value from contract `Bar` in contract `Foo`? `Foo` cannot guarantee claims about the execution of `Bar`. -This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to constrain the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. +This is where the `kernel` circuit comes in. Once the execution of all of the contract functions has completed, it can prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to guarantee the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. -The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. +The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially aggregates each of our function's execution proofs, resulting in one proof that proves all function execution. -With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the `token_interface`s burn function below. This struct is just providing us a clean way to call function, but we could also just call the function directly as it is done in this function. +With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the token interface `burn` function below. This struct is providing us a clean way to call function, but we could also just call the function directly as it is done in this function. :::info -Note that the function selector is computed using one of the oracles from earlier, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--`AztecAddress` becomes `(Field)`. +Note that the function selector is computed using Oracles, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--e.g. `AztecAddress` becomes `(Field)`. ::: #include_code private_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust @@ -81,7 +81,7 @@ The following snippet is from a token bridge that is burning the underlying toke The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../../learn/concepts/hybrid_state/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. -Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. +Using the same example code and call stack from the section [above](#private---private-function-calls), we will walk through how it gets executed in public. The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). @@ -95,27 +95,26 @@ Calling a public function from another public function is quite similar to what ### Private -> Public -As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the concepts [here](../../../../learn/concepts/communication/public_private_calls/main.md). +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times. We can achieve composability between the two contexts via asynchronicity. Further reading can be found in the concepts [here](../../../../learn/concepts/communication/public_private_calls/main.md). Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. -When the sequencer receives the messages, it will take over and execute the public parts of the transaction. +When the sequencer receives the messages, it will execute the public parts of the transaction. As a consequence a private function _CANNOT_ accept a return value from a public function. It can only dispatch it. -The code required to dispatch a public function call from a private function is actually quite similar to private to private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain, essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). +The code required to dispatch a public function call from a private function is similar to private-to-private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain. This is essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). #include_code unshield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust #include_code increase_public_balance /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust -As we can see above, the private to public transaction flow looks very similar to the others in snippets, with the practicality being a bit different behind the scenes. +As we can see above, in the code the private to public transaction flow looks very similar to the others in snippets, with the transaction data flow being a bit different behind the scenes. ### Public -> Private -Wait, I thought you said we could not do this? Well, you are right, we cannot do this directly. However, we can do this indirectly if we are a little cheeky. -While we cannot directly call a private function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still pretty useful. +While we cannot directly call a private function from a public function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still useful. In the snippet below, we insert a custom note, the transparent note, into the commitments tree from public such that it can later be consumed in private. @@ -131,4 +130,3 @@ When the note is removed, it emits a nullifier so that it cannot be used again. Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). ::: ---- diff --git a/docs/docs/developers/contracts/syntax/functions/constructor.md b/docs/docs/developers/contracts/syntax/functions/constructor.md index fb5ad0346e0..50a41658156 100644 --- a/docs/docs/developers/contracts/syntax/functions/constructor.md +++ b/docs/docs/developers/contracts/syntax/functions/constructor.md @@ -7,9 +7,7 @@ A special `constructor` function **must** be declared within a contract's scope. - In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). - A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. -An example of a somewhat boring constructor is as follows: -#include_code empty-constructor /yarn-project/noir-contracts/contracts/test_contract/src/main.nr rust Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. diff --git a/docs/docs/developers/contracts/syntax/functions/inner_workings.md b/docs/docs/developers/contracts/syntax/functions/inner_workings.md index c00fa4294bd..4ac086e6f3b 100644 --- a/docs/docs/developers/contracts/syntax/functions/inner_workings.md +++ b/docs/docs/developers/contracts/syntax/functions/inner_workings.md @@ -2,13 +2,13 @@ title: Inner Workings of Functions --- -Below, we go more into depth of what is happening under the hood when you create a function in Aztec.nr and what the attributes are really doing. +Below, we go more into depth of what is happening under the hood when you create a function in an Aztec contract and what the attributes are really doing. ## Private functions -Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. +Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this is a private function that will be executed on a users device. The compiler will create a circuit to define this function. -However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). +`#aztec(private)` is just syntactic sugar. At compile time, the Aztec.nr framework inserts code that allows the function to interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. @@ -22,26 +22,25 @@ To help illustrate how this interacts with the internals of Aztec and its kernel #### The expansion broken down? -Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. +Viewing the expanded Aztec contract uncovers a lot about how Aztec contracts interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. **Receiving context from the kernel.** #include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust -Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. -For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. +Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each contract function (recall each contract function is a circuit). This information then becomes part of the private context. +For example, within each private function we can access some global variables. To access them we can call on the `context`, e.g. `context.chain_id()`. The value of the chain ID comes from the values passed into the circuit from the kernel. -The kernel can then check that all of the values passed to each circuit in a function call are the same. +The kernel checks that all of the values passed to each circuit in a function call are the same. **Returning the context to the kernel.** #include_code context-example-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust -Just as the kernel passes information into the app circuits, the application must return information about the executed app back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. +The contract function must return information about the execution back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. -> _Why is it called the `PrivateCircuitPublicInputs`_ -> It is commonly asked why the return values of a function in a circuit are labeled as the `Public Inputs`. Common intuition from other programming paradigms suggests that the return values and public inputs should be distinct. -> However; In the eyes of the circuit, anything that is publicly viewable (or checkable) is a public input. Hence in this case, the return values are also public inputs. +> _Why is it called the `PrivateCircuitPublicInputs`?_ +> When verifying zk programs, return values are not computed at verification runtime, rather expected return values are provided as inputs and checked for correctness. Hence, the return values are considered public inputs. -This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the actual return values of the function! +This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the return values of the function. **Hashing the function inputs.** #include_code context-example-hasher /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust @@ -53,11 +52,11 @@ Inside the kernel circuits, the inputs to functions are reduced to a single valu **Creating the function's context.** #include_code context-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust -Each Aztec function has access to a [context](../context.mdx) object. This object although ergonomically a global variable, is local. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. +Each Aztec function has access to a [context](../context.mdx) object. This object, although labelled a global variable, is created locally on a users' device. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. #include_code context-example-context-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust -As previously mentioned we use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). +We use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). We achieve this by pushing return values to the execution context, which we then pass to the kernel. **Making the contract's storage available** diff --git a/docs/docs/developers/contracts/syntax/functions/oracles.md b/docs/docs/developers/contracts/syntax/functions/oracles.md index 50d7f23a642..f9cf30bfa1d 100644 --- a/docs/docs/developers/contracts/syntax/functions/oracles.md +++ b/docs/docs/developers/contracts/syntax/functions/oracles.md @@ -8,14 +8,13 @@ Looking for a practical guide? You can learn how to use oracles in a smart contr An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. -While this is one type of oracle, the more general oracle, allows us to get "some" data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be embedded at any point in the circuit, and thus don't need to be an input parameter. +While this is one type of oracle, the more general oracle, allows us to get any data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be fetched at any point in the circuit, and don't need to be an input parameter. **Why is this useful? Why don't just pass them as input parameters?** -In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, only commitments sit in there 😱. The pre-images (content) of your notes need to be provided to the function to prove that you actually allowed to spend them. +In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, because there are only commitments (e.g. hashes) there. The pre-images (content) of your commitments need to be provided to the function to prove that you actually allowed to modify them. -You could of course provide them to your function as inputs, but then functions that have different underlying notes would end up with different function signatures and thus selectors. This means that integrating with many different tokens (with different underlying notes) would become a pain for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. -If we are instead fetching the notes using an oracle call, we can keep the function signature independent of the underlying notes and thus make it much easier to integrate with! A similar idea, but applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation making integrations a breeze, see [AuthWit](../../../wallets/main#authorizing-actions) for more information on this. +If we fetch the notes using an oracle call, we can keep the function signature independent of the underlying data and make it easier to use. A similar idea, applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation, see [AuthWit](../../../wallets/main#authorizing-actions) for more information on this. Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! diff --git a/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md b/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md index 121ecf7a2c5..e8216d9d3d8 100644 --- a/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md +++ b/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md @@ -8,19 +8,23 @@ This page explains the three types of functions that exist on Aztec - public, pr A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. -To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](../context.mdx) available within your current function's execution scope. +:::note +All data inserted into private storage from a public function will be publicly viewable (not private). +::: + +To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](../context.mdx) available within the function's execution scope. #include_code set_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust ## `Private` Functions -As alluded to earlier, a private function operates on private information, and is executed by the user. To tell the compiler that this is the kind of function we are creating annotate it with the `#[aztec(private)]` attribute. This will make the [private context](../context.mdx#private-context-broken-down) available within your current function's execution scope. +A private function operates on private information, and is executed by the user. Annotate the function with the `#[aztec(private)]` attribute to tell the compiler it's a private function. This will make the [private context](../context.mdx#private-context-broken-down) available within the function's execution scope. #include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust ## `unconstrained` functions -Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seen as untrusted! That they are untrusted means that, for security, the developer must make sure to constrain them when used! +Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). In short, they are functions which are not directly constrained and therefore should be seen as un-trusted. That they are un-trusted means that the developer must make sure to constrain their return values when used. Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. diff --git a/docs/docs/developers/contracts/syntax/functions/visibility.md b/docs/docs/developers/contracts/syntax/functions/visibility.md index 0da1e073e82..70e2c3ab077 100644 --- a/docs/docs/developers/contracts/syntax/functions/visibility.md +++ b/docs/docs/developers/contracts/syntax/functions/visibility.md @@ -6,7 +6,7 @@ In Aztec there are multiple different types of visibility that can be applied to ### Data Visibility -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../learn/concepts/communication/main.md). +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). ### Function visibility From 80dcc0d6eb2b662ba7348c42816d3a9bac518b2a Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Tue, 30 Jan 2024 15:59:00 +0900 Subject: [PATCH 07/12] fixing broken links and sidebar --- docs/docs/developers/aztecjs/main.md | 2 +- .../contracts/syntax/functions/constructor.md | 5 +++-- .../contracts/syntax/functions/inner_workings.md | 2 ++ .../developers/contracts/syntax/functions/main.md | 5 +++-- .../contracts/syntax/functions/oracles.md | 2 +- .../contracts/syntax/functions/visibility.md | 6 ++++-- docs/sidebars.js | 15 +-------------- 7 files changed, 15 insertions(+), 22 deletions(-) diff --git a/docs/docs/developers/aztecjs/main.md b/docs/docs/developers/aztecjs/main.md index 49487fa1c28..531754b2992 100644 --- a/docs/docs/developers/aztecjs/main.md +++ b/docs/docs/developers/aztecjs/main.md @@ -2,7 +2,7 @@ title: Aztec.js --- -If you are looking for the API reference, go [here](../../developers/aztecjs/main.md). +If you are looking for the API reference, go [here](../../apis/aztec-js/index.md). ## Introduction diff --git a/docs/docs/developers/contracts/syntax/functions/constructor.md b/docs/docs/developers/contracts/syntax/functions/constructor.md index 50a41658156..9e5bd916459 100644 --- a/docs/docs/developers/contracts/syntax/functions/constructor.md +++ b/docs/docs/developers/contracts/syntax/functions/constructor.md @@ -1,14 +1,15 @@ --- title: Constructor --- +This page talks about declaring constructors in functions. + +For a more hands-on guide into declaring a constructor, follow the [private voting tutorial](../../../tutorials/writing_private_voting_contract.md). A special `constructor` function **must** be declared within a contract's scope. - A constructor doesn't have a name, because its purpose is clear: to initialize contract state. - In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). - A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. - - Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. #include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust diff --git a/docs/docs/developers/contracts/syntax/functions/inner_workings.md b/docs/docs/developers/contracts/syntax/functions/inner_workings.md index 4ac086e6f3b..a0d1c72fb1d 100644 --- a/docs/docs/developers/contracts/syntax/functions/inner_workings.md +++ b/docs/docs/developers/contracts/syntax/functions/inner_workings.md @@ -4,6 +4,8 @@ title: Inner Workings of Functions Below, we go more into depth of what is happening under the hood when you create a function in an Aztec contract and what the attributes are really doing. +To get a more practical understanding about functions, read [the rest of this section](./main.md). + ## Private functions Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this is a private function that will be executed on a users device. The compiler will create a circuit to define this function. diff --git a/docs/docs/developers/contracts/syntax/functions/main.md b/docs/docs/developers/contracts/syntax/functions/main.md index d5189489b4c..17de123e35b 100644 --- a/docs/docs/developers/contracts/syntax/functions/main.md +++ b/docs/docs/developers/contracts/syntax/functions/main.md @@ -2,7 +2,7 @@ title: Functions --- -Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they can interact with other contracts and the blockchain, or **private** for internal contract use. +Functions serve as the building blocks of smart contracts. Functions can be either **public**, ie they are publicly available for anyone to see and can directly interact with public state, or **private**, meaning they are executed completely client-side in the [PXE](../../../../learn/concepts/pxe/main.md). Read more about how private functions work [here](./inner_workings.md#private-functions). For a more practical guide of using multiple types of functions, follow the [token tutorial](../../../tutorials/writing_token_contract.md). @@ -16,4 +16,5 @@ Explore this section to learn: - [Public, private, and unconstrained functions](./public_private_unconstrained.md), and how to write them - How [constructors](./constructor.md) work and remain private - [Calling functions from within the same smart contract and from different contracts](./calling_functions.md), including calling private functions from private functions, public from public, and even private from public -- [Oracles](./oracles) and how Aztec smart contracts might use them \ No newline at end of file +- [Oracles](./oracles) and how Aztec smart contracts might use them +- [How functions work under the hood](./inner_workings.md) \ No newline at end of file diff --git a/docs/docs/developers/contracts/syntax/functions/oracles.md b/docs/docs/developers/contracts/syntax/functions/oracles.md index f9cf30bfa1d..28a57bed1d6 100644 --- a/docs/docs/developers/contracts/syntax/functions/oracles.md +++ b/docs/docs/developers/contracts/syntax/functions/oracles.md @@ -4,7 +4,7 @@ title: Oracle Functions This page goes over what oracles are in Aztec and how they work. -Looking for a practical guide? You can learn how to use oracles in a smart contract [here](../oracles.md). +Looking for a hands-on guide? You can learn how to use oracles in a smart contract [here](../oracles.md). An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. diff --git a/docs/docs/developers/contracts/syntax/functions/visibility.md b/docs/docs/developers/contracts/syntax/functions/visibility.md index 70e2c3ab077..3cfa46a5507 100644 --- a/docs/docs/developers/contracts/syntax/functions/visibility.md +++ b/docs/docs/developers/contracts/syntax/functions/visibility.md @@ -2,7 +2,9 @@ title: Visibility --- -In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. +In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. This page explains these types of visibility. + +For a practical guide of using multiple types of data and function visibility,follow the [token tutorial](../../../tutorials/writing_token_contract.md). ### Data Visibility @@ -20,4 +22,4 @@ A good place to use `internal` is when you want a private function to be able to Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. ::: -For a practical guide of using multiple types of data visibility and `internal` functions,follow the [token tutorial](../../../tutorials/writing_token_contract.md). +To understand how visibility works under the hood, check out the [Inner Workings page](./inner_workings.md). \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index 38e004d8ed2..88dc7f7bd73 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -295,19 +295,6 @@ const sidebars = { ], }, "developers/contracts/syntax/events", - { - label: "Storage", - type: "category", - link: { - type: "doc", - id: "developers/contracts/syntax/storage/main", - }, - items: [ - "developers/contracts/syntax/storage/public_state", - "developers/contracts/syntax/storage/private_state", - "developers/contracts/syntax/storage/storage_slots", - ], - }, { label: "Functions", type: "category", @@ -316,8 +303,8 @@ const sidebars = { id: "developers/contracts/syntax/functions/main", }, items: [ - "developers/contracts/syntax/functions/visibility", "developers/contracts/syntax/functions/public_private_unconstrained", + "developers/contracts/syntax/functions/visibility", "developers/contracts/syntax/functions/constructor", "developers/contracts/syntax/functions/calling_functions", "developers/contracts/syntax/functions/oracles", From 0c15f305afec9537635f29d6abf871ef7ab07d9e Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Tue, 30 Jan 2024 16:03:39 +0900 Subject: [PATCH 08/12] link to entrypoint docs --- docs/docs/developers/contracts/syntax/functions/visibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/developers/contracts/syntax/functions/visibility.md b/docs/docs/developers/contracts/syntax/functions/visibility.md index 3cfa46a5507..61756bff01e 100644 --- a/docs/docs/developers/contracts/syntax/functions/visibility.md +++ b/docs/docs/developers/contracts/syntax/functions/visibility.md @@ -19,7 +19,7 @@ By default, all functions are callable from other contracts, similarly to the So A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. :::danger -Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. +Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. You can learn more about this in the [Accounts concept page](../../../../learn/concepts/accounts/main.md#entrypoint-restrictions). ::: To understand how visibility works under the hood, check out the [Inner Workings page](./inner_workings.md). \ No newline at end of file From 1ed44e10db52a1a9df42c2ed433a58e49a118e68 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Wed, 31 Jan 2024 16:24:27 +0900 Subject: [PATCH 09/12] maddiaa0 comments --- .../syntax/functions/calling_functions.md | 4 +- .../syntax/functions/inner_workings.md | 2 +- .../functions/public_private_unconstrained.md | 2 +- .../writing_a_contract/functions/main.md | 310 ++++++++++++++++++ 4 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 docs/docs/developers/contracts/writing_a_contract/functions/main.md diff --git a/docs/docs/developers/contracts/syntax/functions/calling_functions.md b/docs/docs/developers/contracts/syntax/functions/calling_functions.md index 873b4ac1abc..4b24e32a9c2 100644 --- a/docs/docs/developers/contracts/syntax/functions/calling_functions.md +++ b/docs/docs/developers/contracts/syntax/functions/calling_functions.md @@ -83,7 +83,7 @@ The public execution environment in Aztec takes place on the sequencer through a Using the same example code and call stack from the section [above](#private---private-function-calls), we will walk through how it gets executed in public. -The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). +The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode`. This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../../learn/concepts/hybrid_state/public_vm.md). The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. @@ -124,7 +124,7 @@ If you recall the `redeem_shield` from back in the [private function section](./ #include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust -When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](./../../../../learn/concepts/communication/cross_chain_calls.md) we are going to see this pattern again. +When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the nullifier tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](./../../../../learn/concepts/communication/cross_chain_calls.md) we are going to see this pattern again. :::danger Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). diff --git a/docs/docs/developers/contracts/syntax/functions/inner_workings.md b/docs/docs/developers/contracts/syntax/functions/inner_workings.md index a0d1c72fb1d..3b1a093e00a 100644 --- a/docs/docs/developers/contracts/syntax/functions/inner_workings.md +++ b/docs/docs/developers/contracts/syntax/functions/inner_workings.md @@ -91,7 +91,7 @@ Once the execution environment is created, `execute_unconstrained_function` is i This: 1. Prepares the ACIR for execution -2. Converts `args` into a format suitable for the ACVM (Aztec's virtual machine), creating an initial witness (witness = set of inputs required to compute the function). `args` might be an oracle to request a user's balance +2. Converts `args` into a format suitable for the ACVM (Abstract Circuit Virtual Machine), creating an initial witness (witness = set of inputs required to compute the function). `args` might be an oracle to request a user's balance 3. Executes the function in the ACVM, which involves running the ACIR with the initial witness and the context. If requesting a user's balance, this would query the balance from the PXE database 4. Extracts the return values from the `partialWitness` and decodes them based on the artifact to get the final function output. The [artifact](../../artifacts.md) is the compiled output of the contract, and has information like the function signature, parameter types, and return types diff --git a/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md b/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md index e8216d9d3d8..678fc7ad9d3 100644 --- a/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md +++ b/docs/docs/developers/contracts/syntax/functions/public_private_unconstrained.md @@ -24,7 +24,7 @@ A private function operates on private information, and is executed by the user. ## `unconstrained` functions -Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). In short, they are functions which are not directly constrained and therefore should be seen as un-trusted. That they are un-trusted means that the developer must make sure to constrain their return values when used. +Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). In short, they are functions which are not directly constrained and therefore should be seen as un-trusted. That they are un-trusted means that the developer must make sure to constrain their return values when used. Note: Calling an unconstrained function from a private function means that you are injecting unconstrained values. Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. diff --git a/docs/docs/developers/contracts/writing_a_contract/functions/main.md b/docs/docs/developers/contracts/writing_a_contract/functions/main.md new file mode 100644 index 00000000000..b76aec26f9d --- /dev/null +++ b/docs/docs/developers/contracts/writing_a_contract/functions/main.md @@ -0,0 +1,310 @@ +--- +title: Functions +description: This page covers functions, private and public functions composability, as well as their differences. +--- + +Functions serve as the building blocks of smart contracts. Functions can be either public, ie they can interact with other contracts and the blockchain, or private for internal contract use. Every smart contract also has a private `constructor` function which is called when the contract is deployed. There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. + +On this page, you’ll learn more about: + +- How function visibility works in Aztec +- A detailed understanding of public, private, and unconstrained functions, and how to write them +- How constructors work and remain private +- The process of calling functions from within the same smart contract and from different contracts, including calling private functions from private functions, public from public, and even private from public +- What oracles and how Aztec smart contracts might use them + +## Visibility + +In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. + +### Data Visibility + +Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../learn/concepts/communication/public_private_calls/main.md). + +In the following sections, we are going to see how these two "types" co-exists and interact. + +### Function visibility + +This is the kind of visibility you are more used to seeing in Solidity and more traditional programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. + +By default, all functions are callable from other contracts, similarly to the Solidity `public` visibility. To make them only callable from the contract itself, you can mark them as `internal`. Contrary to solidity, we don't have the `external` nor `private` keywords. `external` since it is limited usage when we don't support inheritance, and `private` since we don't support inheritance and it would also be confusing with multiple types of `private`. + +A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. + +:::danger +Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. +::: + +## Mutability + +Currently, any function is "mutable" in the sense that it might alter state. In the future, we will support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). This is useful for when you want to call a function in a separate contract, but ensure that it cannot alter state, or call other functions that might alter state (such as re-entering). + +Similarly, a special case of a mutating call is the `delegatecall` where the function executed might not be in the same contract as the state being altered. It is at this moment, not certain if `delegatecall`s should become a fully fledged feature. + +:::danger No `staticcall` or `delegatecall` support +While `staticcall` and `delegatecall` both have flags in the call context, they are currently not supported and will not behave as one would expect if usage is attempted. +::: + +## `constructor` + +- A special `constructor` function MUST be declared within a contract's scope. +- A constructor doesn't have a name, because its purpose is clear: to initialize contract state. +- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). +- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. + +An example of a somewhat boring constructor is as follows: + +#include_code empty-constructor /yarn-project/noir-contracts/contracts/test_contract/src/main.nr rust + +Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. + +#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust + +## `Public` Functions + +A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. + +To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](./context.md#public-context) available within your current function's execution scope. + +#include_code set_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +## `Private` Functions + +As alluded to earlier, a private function operates on private information, and is executed by the user. To tell the compiler that this is the kind of function we are creating annotate it with the `#[aztec(private)]` attribute. This will make the [private context](./context.md#private-context-broken-down) available within your current function's execution scope. + +#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +## `unconstrained` functions + +Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seen as untrusted! That they are untrusted means that, for security, the developer must make sure to constrain them when used! + +Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. + +#include_code balance_of_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +:::info +Note, that unconstrained functions can have access to both public and private data when executed on the user's device. This is possible since it is not actually part of the circuits that are executed in contract execution. +::: + +## Oracle functions + +An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. + +While this is one type of oracle, the more general oracle, allows us to get "some" data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be embedded at any point in the circuit, and thus don't need to be an input parameter. + +**Why is this useful? Why don't just pass them as input parameters?** +In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, only commitments sit in there 😱. The pre-images (content) of your notes need to be provided to the function to prove that you actually allowed to spend them. + +You could of course provide them to your function as inputs, but then functions that have different underlying notes would end up with different function signatures and thus selectors. This means that integrating with many different tokens (with different underlying notes) would become a pain for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. + +If we are instead fetching the notes using an oracle call, we can keep the function signature independent of the underlying notes and thus make it much easier to integrate with! A similar idea, but applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation making integrations a breeze, see [AuthWit](../../wallets/main#authorizing-actions) for more information on this. + +Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! + +`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: +#include_code oracles-module /yarn-project/aztec-nr/aztec/src/oracle.nr rust + +You can learn how to use oracles in your smart contracts [here](../functions/main.md#oracle-functions). + +## Calling functions from other functions + +### Private -> Private + +In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md), and take place on the user's device. +Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../writing_a_contract/accounts/write_accounts_contract.md)). + +Take, for example, the following call stack: + +``` +AccountContract::entrypoint + |-> Foo::example_call + | -> Bar::nested_call + |-> Baz::example_call +``` + +In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the account contract makes one last call to `Baz::example_call`. + +Lets further illustrate what these examples could look like + + + +```rust +// Foo contains a singular function that returns the result of Bar::nested_call +contract Foo { + #[aztec(private)] + fn example_call(input: Field) -> pub Field { + Bar::at().nested_call(input) + } +} + +// Bar contains a singular function that returns a `input + 1` +contract Bar { + #[aztec(private)] + fn nested_call(input: Field) -> pub Field { + input + 1 + } +} + +// Baz contains a singular function that simply returns `10` +contract Baz { + #[aztec(private)] + fn example_call() -> pub Field { + 10 + } +} +``` + +When simulating the following call stack, we can expect execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `Foo::example_call`, then begin to execute the code there. When the simulator executes the code in contract `Foo`, it will find the further nested call to contract `Bar::nested_call`. It will execute the code in `Bar`, bringing the return value back to contract `Foo`. +The same process will be followed for contract `Baz`. + +So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. + +Those of you who have written circuits before may see an issue here. The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `Bar` in contract `Foo`? This value will not be constrained. + +This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to constrain the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. + +The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. + + + +With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the `token_interface`s burn function below. This struct is just providing us a clean way to call function, but we could also just call the function directly as it is done in this function. + +:::info +Note that the function selector is computed using one of the oracles from earlier, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--`AztecAddress` becomes `(Field)`. +::: + +#include_code private_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust + +Using this interface, we can then call it as seen below. All the way down at the bottom we can see that we are calling the `burn` function from the `token_interface` struct. + +The following snippet is from a token bridge that is burning the underlying token and creating a message for L1 to mint some assets to the `recipient` on Ethereum. + +#include_code exit_to_l1_private /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust + +### Public -> Public + +The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../learn/concepts/hybrid_state/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. + +Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. + +The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). + +This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../learn/concepts/hybrid_state/public_vm.md). +The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. + +Calling a public function from another public function is quite similar to what we saw for private to private, with the keyword private swapped for public. + +#include_code public_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust +#include_code exit_to_l1_public /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust + +### Private -> Public + +As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../../learn/concepts/communication/public_private_calls/main.md)). + +Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. +When the sequencer receives the messages, it will take over and execute the public parts of the transaction. + +As a consequence a private function _CANNOT_ accept a return value from a public function. It can only dispatch it. + +The code required to dispatch a public function call from a private function is actually quite similar to private to private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain, essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). + +#include_code unshield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust +#include_code increase_public_balance /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +As we can see above, the private to public transaction flow looks very similar to the others in snippets, with the practicality being a bit different behind the scenes. + + + +### Public -> Private + +Wait, I thought you said we could not do this? Well, you are right, we cannot do this directly. However, we can do this indirectly if we are a little cheeky. + +While we cannot directly call a private function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still pretty useful. + +In the snippet below, we insert a custom note, the transparent note, into the commitments tree from public such that it can later be consumed in private. + +#include_code shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +If you recall the `redeem_shield` from back in the [private function section](#private-functions), you might remember it removing a `TransparentNote` from `pending_shields`. This is the note that we just inserted from public! + +#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust + +When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../portals/main.md) we are going to see this pattern again. + +:::danger +Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). +::: + +--- + +## Deep dive + +Below, we go more into depth of what is happening under the hood when you create a function in Aztec.nr and what the attributes are really doing. + +### Function type attributes explained. + +Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. + +However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). + +To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. + +#### Before expansion + +#include_code simple_macro_example /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +#### After expansion + +#include_code simple_macro_example_expanded /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +#### The expansion broken down? + +Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. + +**Receiving context from the kernel.** +#include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. +For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. + +The kernel can then check that all of the values passed to each circuit in a function call are the same. + +**Returning the context to the kernel.** +#include_code context-example-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Just as the kernel passes information into the app circuits, the application must return information about the executed app back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. + +> _Why is it called the `PrivateCircuitPublicInputs`_ +> It is commonly asked why the return values of a function in a circuit are labeled as the `Public Inputs`. Common intuition from other programming paradigms suggests that the return values and public inputs should be distinct. +> However; In the eyes of the circuit, anything that is publicly viewable (or checkable) is a public input. Hence in this case, the return values are also public inputs. + +This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the actual return values of the function! + +**Hashing the function inputs.** +#include_code context-example-hasher /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +_What is the hasher and why is it needed?_ + +Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value. + +**Creating the function's context.** +#include_code context-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +Each Aztec function has access to a [context](./context.md) object. This object although ergonomically a global variable, is local. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. + +#include_code context-example-context-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +As previously mentioned we use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). +We achieve this by pushing return values to the execution context, which we then pass to the kernel. + +**Making the contract's storage available** +#include_code storage-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +When a [`Storage` struct](./storage/main.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. + +Any state variables declared in the `Storage` struct can now be accessed as normal struct members. + +**Returning the function context to the kernel.** +#include_code context-example-finish /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust + +This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit. From f372eedda98c7b61759d4ae85ce490de0a068ad2 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Wed, 31 Jan 2024 16:25:14 +0900 Subject: [PATCH 10/12] maddiaa0 comments --- .../developers/contracts/syntax/functions/constructor.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/docs/developers/contracts/syntax/functions/constructor.md b/docs/docs/developers/contracts/syntax/functions/constructor.md index 9e5bd916459..8bc9471a442 100644 --- a/docs/docs/developers/contracts/syntax/functions/constructor.md +++ b/docs/docs/developers/contracts/syntax/functions/constructor.md @@ -12,8 +12,4 @@ A special `constructor` function **must** be declared within a contract's scope. Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. -#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust - -:::danger -It is not possible to call public functions from within a constructor. Beware that the compiler will not throw an error if you do, but the execution will fail! -::: \ No newline at end of file +#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust \ No newline at end of file From fa4acad98b068b86a2998f74fa96e81a4d5346f0 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Wed, 31 Jan 2024 07:39:37 +0000 Subject: [PATCH 11/12] forcing ci rerun --- .../docs/developers/contracts/syntax/functions/constructor.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/developers/contracts/syntax/functions/constructor.md b/docs/docs/developers/contracts/syntax/functions/constructor.md index 8bc9471a442..d0a9c7d0c3b 100644 --- a/docs/docs/developers/contracts/syntax/functions/constructor.md +++ b/docs/docs/developers/contracts/syntax/functions/constructor.md @@ -8,8 +8,8 @@ For a more hands-on guide into declaring a constructor, follow the [private voti A special `constructor` function **must** be declared within a contract's scope. - A constructor doesn't have a name, because its purpose is clear: to initialize contract state. - In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). -- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. +- A constructor behaves almost identically to any other function. It is just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. -#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust \ No newline at end of file +#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust From 2f4ccbed40cf80e935e2947dcb5c91ef4359aa62 Mon Sep 17 00:00:00 2001 From: Cat McGee Date: Wed, 31 Jan 2024 07:41:46 +0000 Subject: [PATCH 12/12] removed page accidentally added --- .../writing_a_contract/functions/main.md | 310 ------------------ 1 file changed, 310 deletions(-) delete mode 100644 docs/docs/developers/contracts/writing_a_contract/functions/main.md diff --git a/docs/docs/developers/contracts/writing_a_contract/functions/main.md b/docs/docs/developers/contracts/writing_a_contract/functions/main.md deleted file mode 100644 index b76aec26f9d..00000000000 --- a/docs/docs/developers/contracts/writing_a_contract/functions/main.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -title: Functions -description: This page covers functions, private and public functions composability, as well as their differences. ---- - -Functions serve as the building blocks of smart contracts. Functions can be either public, ie they can interact with other contracts and the blockchain, or private for internal contract use. Every smart contract also has a private `constructor` function which is called when the contract is deployed. There are also special oracle functions, which can get data from outside of the smart contract. In the context of Aztec, oracles are often used to get user-provided inputs. - -On this page, you’ll learn more about: - -- How function visibility works in Aztec -- A detailed understanding of public, private, and unconstrained functions, and how to write them -- How constructors work and remain private -- The process of calling functions from within the same smart contract and from different contracts, including calling private functions from private functions, public from public, and even private from public -- What oracles and how Aztec smart contracts might use them - -## Visibility - -In Aztec there are multiple different types of visibility that can be applied to functions. Namely we have `data visibility` and `function visibility`. - -### Data Visibility - -Data visibility is used to describe whether the data (or state) used in a function is generally accessible (public) or on a need to know basis (private). Functions with public data visibility are executed by the sequencer, and functions with private data visibility are executed by the user. For more information on why this is the case, see [communication](../../../../learn/concepts/communication/public_private_calls/main.md). - -In the following sections, we are going to see how these two "types" co-exists and interact. - -### Function visibility - -This is the kind of visibility you are more used to seeing in Solidity and more traditional programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. - -By default, all functions are callable from other contracts, similarly to the Solidity `public` visibility. To make them only callable from the contract itself, you can mark them as `internal`. Contrary to solidity, we don't have the `external` nor `private` keywords. `external` since it is limited usage when we don't support inheritance, and `private` since we don't support inheritance and it would also be confusing with multiple types of `private`. - -A good place to use `internal` is when you want a private function to be able to alter public state. As mentioned above, private functions cannot do this directly. They are able to call public functions and by making these internal we can ensure that this state manipulating function is only callable from our private function. - -:::danger -Note that non-internal functions could be used directly as an entry-point, which currently means that the `msg_sender` would be `0`, so for now, using address `0` as a burn address is not recommended. -::: - -## Mutability - -Currently, any function is "mutable" in the sense that it might alter state. In the future, we will support static calls, similarly to EVM. A static call is essentially a call that does not alter state (it keeps state static). This is useful for when you want to call a function in a separate contract, but ensure that it cannot alter state, or call other functions that might alter state (such as re-entering). - -Similarly, a special case of a mutating call is the `delegatecall` where the function executed might not be in the same contract as the state being altered. It is at this moment, not certain if `delegatecall`s should become a fully fledged feature. - -:::danger No `staticcall` or `delegatecall` support -While `staticcall` and `delegatecall` both have flags in the call context, they are currently not supported and will not behave as one would expect if usage is attempted. -::: - -## `constructor` - -- A special `constructor` function MUST be declared within a contract's scope. -- A constructor doesn't have a name, because its purpose is clear: to initialize contract state. -- In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function). -- A constructor behaves almost identically to any other function. It's just important for Aztec to be able to identify this function as special: it may only be called once, and will not be deployed as part of the contract. - -An example of a somewhat boring constructor is as follows: - -#include_code empty-constructor /yarn-project/noir-contracts/contracts/test_contract/src/main.nr rust - -Although you can have a constructor that does nothing, you might want to do something with it, such as setting the deployer as an owner. - -#include_code constructor /yarn-project/noir-contracts/contracts/escrow_contract/src/main.nr rust - -## `Public` Functions - -A public function is executed by the sequencer and has access to a state model that is very similar to that of the EVM and Ethereum. Even though they work in an EVM-like model for public transactions, they are able to write data into private storage that can be consumed later by a private function. - -To create a public function you can annotate it with the `#[aztec(public)]` attribute. This will make the [public context](./context.md#public-context) available within your current function's execution scope. - -#include_code set_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -## `Private` Functions - -As alluded to earlier, a private function operates on private information, and is executed by the user. To tell the compiler that this is the kind of function we are creating annotate it with the `#[aztec(private)]` attribute. This will make the [private context](./context.md#private-context-broken-down) available within your current function's execution scope. - -#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -## `unconstrained` functions - -Unconstrained functions are an underlying part of Noir - a deeper explanation can be found [here](https://noir-lang.org/docs/noir/syntax/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seen as untrusted! That they are untrusted means that, for security, the developer must make sure to constrain them when used! - -Beyond using them inside your other functions, they are convenient for providing an interface that reads storage, applies logic and returns values to a UI or test. Below is a snippet from exposing the `balance_of_private` function from a token implementation, which allows a user to easily read their balance, similar to the `balanceOf` function in the ERC20 standard. - -#include_code balance_of_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -:::info -Note, that unconstrained functions can have access to both public and private data when executed on the user's device. This is possible since it is not actually part of the circuits that are executed in contract execution. -::: - -## Oracle functions - -An oracle is something that allows us to get data from the outside world into our contracts. The most widely-known types of oracles in blockchain systems are probably Chainlink price feeds, which allow us to get the price of an asset in USD taking non-blockchain data into account. - -While this is one type of oracle, the more general oracle, allows us to get "some" data into the contract. In the context of oracle functions or oracle calls in Aztec, it can essentially be seen as user-provided arguments, that can be embedded at any point in the circuit, and thus don't need to be an input parameter. - -**Why is this useful? Why don't just pass them as input parameters?** -In the world of EVM, you would just read the values directly from storage and call it a day. However, when we are working with circuits for private execution, this becomes more tricky as you cannot just read the storage directly from your state tree, only commitments sit in there 😱. The pre-images (content) of your notes need to be provided to the function to prove that you actually allowed to spend them. - -You could of course provide them to your function as inputs, but then functions that have different underlying notes would end up with different function signatures and thus selectors. This means that integrating with many different tokens (with different underlying notes) would become a pain for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. - -If we are instead fetching the notes using an oracle call, we can keep the function signature independent of the underlying notes and thus make it much easier to integrate with! A similar idea, but applied to the authentication mechanism is used for the Authentication Witnesses that allow us to have a single function signature for any wallet implementation making integrations a breeze, see [AuthWit](../../wallets/main#authorizing-actions) for more information on this. - -Oracles introduce **non-determinism** into a circuit, and thus are `unconstrained`. It is important that any information that is injected into a circuit through an oracle is later constrained for correctness. Otherwise, the circuit will be **under-constrained** and potentially insecure! - -`Aztec.nr` has a module dedicated to its oracles. If you are interested, you can view them by following the link below: -#include_code oracles-module /yarn-project/aztec-nr/aztec/src/oracle.nr rust - -You can learn how to use oracles in your smart contracts [here](../functions/main.md#oracle-functions). - -## Calling functions from other functions - -### Private -> Private - -In Aztec Private to Private function calls are handled by the [private kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md), and take place on the user's device. -Behind the scenes, the `Private Execution Environment (PXE)` (the beating heart of Aztec that runs in your wallet) will execute all of the functions in the desired order "simulating" them in sequence. For example, a very common use-case of Private to Private interaction is calling another private function from an `account contract` (Account contracts are a general concept, more information about them can be found [here](../../writing_a_contract/accounts/write_accounts_contract.md)). - -Take, for example, the following call stack: - -``` -AccountContract::entrypoint - |-> Foo::example_call - | -> Bar::nested_call - |-> Baz::example_call -``` - -In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `Foo::example_call` a further nested call is performed to `Bar::nested_call`. Finally the account contract makes one last call to `Baz::example_call`. - -Lets further illustrate what these examples could look like - - - -```rust -// Foo contains a singular function that returns the result of Bar::nested_call -contract Foo { - #[aztec(private)] - fn example_call(input: Field) -> pub Field { - Bar::at().nested_call(input) - } -} - -// Bar contains a singular function that returns a `input + 1` -contract Bar { - #[aztec(private)] - fn nested_call(input: Field) -> pub Field { - input + 1 - } -} - -// Baz contains a singular function that simply returns `10` -contract Baz { - #[aztec(private)] - fn example_call() -> pub Field { - 10 - } -} -``` - -When simulating the following call stack, we can expect execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `Foo::example_call`, then begin to execute the code there. When the simulator executes the code in contract `Foo`, it will find the further nested call to contract `Bar::nested_call`. It will execute the code in `Bar`, bringing the return value back to contract `Foo`. -The same process will be followed for contract `Baz`. - -So far the provided example is identical to other executions. Ethereum execution occurs in a similar way, during execution the EVM will execute instructions until it reaches an external call, where it will hop into a new context and execute code there, returning back when it is complete, bringing with it return values from the foreign execution. - -Those of you who have written circuits before may see an issue here. The account contract, contract `Foo`, `Bar` and `Baz` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `Bar` in contract `Foo`? This value will not be constrained. - -This is where the `kernel` circuit comes in. Once the execution of all of our functions has completed, we can just prove the execution of each of them independently. It is the job of the `kernel` circuit to constrain that the input parameters in a cross function call are correct, as well as the return values. The kernel will constrain that the value returned from `Foo::example_call` is the same value that is returned from `Bar::nested_call`, it will also be able to constrain the value returned by `Bar::nested_call` is the inputs to `Foo::example_call` + 1. - -The orchestration of these calls has an added benefit. All of the nested calls are **recursively proven**. This means that the kernel circuit essentially gobbles up each of our function's execution proofs. Condensing the size of the final proof to just be one. - - - -With this intuition in place, lets see how we actually perform the call. To make things easier, we can make a small struct that wraps the calls to something as seen in the `token_interface`s burn function below. This struct is just providing us a clean way to call function, but we could also just call the function directly as it is done in this function. - -:::info -Note that the function selector is computed using one of the oracles from earlier, and that the first `Field` is wrapped in parenthesis. Structs are outlined in tuple-form for selector computation, so they are wrapped in parenthesis--`AztecAddress` becomes `(Field)`. -::: - -#include_code private_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust - -Using this interface, we can then call it as seen below. All the way down at the bottom we can see that we are calling the `burn` function from the `token_interface` struct. - -The following snippet is from a token bridge that is burning the underlying token and creating a message for L1 to mint some assets to the `recipient` on Ethereum. - -#include_code exit_to_l1_private /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust - -### Public -> Public - -The public execution environment in Aztec takes place on the sequencer through a [Public VM](../../../learn/concepts/hybrid_state/public_vm.md). This execution model is conceptually much simpler than the private transaction model as code is executed and proven on the sequencer. - -Using the same example code and call stack from the section [above](#private----private-function-calls), we will walk through how it gets executed in public. - -The first key difference is that public functions are not compiled to circuits, rather they are compiled to `Aztec Bytecode` (might also be referred to as brillig). - -This bytecode is run by the sequencer in the `Aztec VM`, which is in turn proven by the [`Aztec VM circuit`](../../../learn/concepts/hybrid_state/public_vm.md). -The mental model for public execution carries many of the same idea as are carried by Ethereum. Programs are compiled into a series of opcodes (known as bytecode). This bytecode is then executed. The extra step for the Aztec VM is that each opcode is then proven for correctness. - -Calling a public function from another public function is quite similar to what we saw for private to private, with the keyword private swapped for public. - -#include_code public_burn_interface /yarn-project/noir-contracts/contracts/token_bridge_contract/src/token_interface.nr rust -#include_code exit_to_l1_public /yarn-project/noir-contracts/contracts/token_bridge_contract/src/main.nr rust - -### Private -> Public - -As discussed above, private function execution and calls take place on the user's device, while public function execution and calls take place on a sequencer, in two different places at two different times, it is natural to question how we can achieve composability between the two. The solution is asynchronicity. Further reading can be found in the foundational concepts [here](../../../../learn/concepts/communication/public_private_calls/main.md)). - -Private function execution takes place on the users device, where it keeps track of any public function calls that have been made. Whenever private execution completes, and a kernel proof is produced, the transaction sent to the network will include all of the public calls that were dispatched. -When the sequencer receives the messages, it will take over and execute the public parts of the transaction. - -As a consequence a private function _CANNOT_ accept a return value from a public function. It can only dispatch it. - -The code required to dispatch a public function call from a private function is actually quite similar to private to private calls. As an example, we will look at the token contract, where users can unshield assets from private to public domain, essentially a transfer from a private account to a public one (often used for depositing privately into DeFi etc). - -#include_code unshield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust -#include_code increase_public_balance /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -As we can see above, the private to public transaction flow looks very similar to the others in snippets, with the practicality being a bit different behind the scenes. - - - -### Public -> Private - -Wait, I thought you said we could not do this? Well, you are right, we cannot do this directly. However, we can do this indirectly if we are a little cheeky. - -While we cannot directly call a private function, we can indirectly call it by adding a commitment to the note hash tree. This commitment can then be consumed by a private function later, to "finish" the execution. So while it is not practically a call, we can ensure that it could only happen as an effect of a public function call, which is still pretty useful. - -In the snippet below, we insert a custom note, the transparent note, into the commitments tree from public such that it can later be consumed in private. - -#include_code shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -If you recall the `redeem_shield` from back in the [private function section](#private-functions), you might remember it removing a `TransparentNote` from `pending_shields`. This is the note that we just inserted from public! - -#include_code redeem_shield /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust - -When the note is removed, it emits a nullifier so that it cannot be used again. This nullifier is then added to the note hash tree, and can be used to prove that the note was removed from the pending shields. Interestingly, we can generate the nullifier such that no-one who saw the public execution will know that it have been consumed. When sending messages between L1 and L2 in [portals](../portals/main.md) we are going to see this pattern again. - -:::danger -Something to be mindful of when inserting from public. Everyone can see the insertion and what happens in public, so if you are including a secret directly anyone would be able to see it. This is why the hash of the secret is used in the snippet above (`secret_hash`). -::: - ---- - -## Deep dive - -Below, we go more into depth of what is happening under the hood when you create a function in Aztec.nr and what the attributes are really doing. - -### Function type attributes explained. - -Aztec.nr uses an attribute system to annotate a function's type. Annotating a function with the `#[aztec(private)]` attribute tells the framework that this will be a private function that will be executed on a users device. Thus the compiler will create a circuit to define this function. - -However; `#aztec(private)` is just syntactic sugar. At compile time, the framework inserts code that allows the function to interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). - -To help illustrate how this interacts with the internals of Aztec and its kernel circuits, we can take an example private function, and explore what it looks like after Aztec.nr's macro expansion. - -#### Before expansion - -#include_code simple_macro_example /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -#### After expansion - -#include_code simple_macro_example_expanded /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -#### The expansion broken down? - -Viewing the expanded noir contract uncovers a lot about how noir contracts interact with the [kernel](../../../../learn/concepts/circuits/kernels/private_kernel.md). To aid with developing intuition, we will break down each inserted line. - -**Receiving context from the kernel.** -#include_code context-example-inputs /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Private function calls are able to interact with each other through orchestration from within the [kernel circuit](../../../../learn/concepts/circuits/kernels/private_kernel.md). The kernel circuit forwards information to each app circuit. This information then becomes part of the private context. -For example, within each circuit we can access some global variables. To access them we can call `context.chain_id()`. The value of this chain ID comes from the values passed into the circuit from the kernel. - -The kernel can then check that all of the values passed to each circuit in a function call are the same. - -**Returning the context to the kernel.** -#include_code context-example-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Just as the kernel passes information into the app circuits, the application must return information about the executed app back to the kernel. This is done through a rigid structure we call the `PrivateCircuitPublicInputs`. - -> _Why is it called the `PrivateCircuitPublicInputs`_ -> It is commonly asked why the return values of a function in a circuit are labeled as the `Public Inputs`. Common intuition from other programming paradigms suggests that the return values and public inputs should be distinct. -> However; In the eyes of the circuit, anything that is publicly viewable (or checkable) is a public input. Hence in this case, the return values are also public inputs. - -This structure contains a host of information about the executed program. It will contain any newly created nullifiers, any messages to be sent to l2 and most importantly it will contain the actual return values of the function! - -**Hashing the function inputs.** -#include_code context-example-hasher /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -_What is the hasher and why is it needed?_ - -Inside the kernel circuits, the inputs to functions are reduced to a single value; the inputs hash. This prevents the need for multiple different kernel circuits; each supporting differing numbers of inputs. The hasher abstraction that allows us to create an array of all of the inputs that can be reduced to a single value. - -**Creating the function's context.** -#include_code context-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -Each Aztec function has access to a [context](./context.md) object. This object although ergonomically a global variable, is local. It is initialized from the inputs provided by the kernel, and a hash of the function's inputs. - -#include_code context-example-context-return /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -As previously mentioned we use the kernel to pass information between circuits. This means that the return values of functions must also be passed to the kernel (where they can be later passed on to another function). -We achieve this by pushing return values to the execution context, which we then pass to the kernel. - -**Making the contract's storage available** -#include_code storage-example-context /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -When a [`Storage` struct](./storage/main.md) is declared within a contract, the `storage` keyword is made available. As shown in the macro expansion above, this calls the init function on the storage struct with the current function's context. - -Any state variables declared in the `Storage` struct can now be accessed as normal struct members. - -**Returning the function context to the kernel.** -#include_code context-example-finish /yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr rust - -This function takes the application context, and converts it into the `PrivateCircuitPublicInputs` structure. This structure is then passed to the kernel circuit.