From 89501325408abe6ba279454bac244b9330c0a01d Mon Sep 17 00:00:00 2001 From: LHerskind Date: Tue, 19 Sep 2023 16:43:53 +0000 Subject: [PATCH 1/6] feat: extend functions --- .../contracts/{ => syntax}/context.mdx | 10 +- .../dev_docs/contracts/syntax/functions.md | 219 ++++++++++++++++-- .../docs/dev_docs/contracts/syntax/globals.md | 19 +- docs/sidebars.js | 7 +- .../token_bridge_contract/src/main.nr | 4 + .../src/token_interface.nr | 5 + .../src/contracts/token_contract/src/main.nr | 10 + 7 files changed, 246 insertions(+), 28 deletions(-) rename docs/docs/dev_docs/contracts/{ => syntax}/context.mdx (89%) diff --git a/docs/docs/dev_docs/contracts/context.mdx b/docs/docs/dev_docs/contracts/syntax/context.mdx similarity index 89% rename from docs/docs/dev_docs/contracts/context.mdx rename to docs/docs/dev_docs/contracts/syntax/context.mdx index 8bf17d3c6d37..3ea5a60ab164 100644 --- a/docs/docs/dev_docs/contracts/context.mdx +++ b/docs/docs/dev_docs/contracts/syntax/context.mdx @@ -9,12 +9,12 @@ import Image from "@theme/IdealImage"; # The Function Context ## What is the context -The context is an object that is made available within every function in `Aztec.nr`. As mentioned in the [kernel circuit documentation](../../concepts/advanced/circuits/kernels/private_kernel.md). At the beginning of a function's execution, the context contains all of the kernel information that application needs to execute. During the lifecycle of a transaction, the function will update the context with each of it's side effects (created notes, nullifiers etc.). At the end of a function's execution the mutated context is returned to the kernel to be checked for validity. +The context is an object that is made available within every function in `Aztec.nr`. As mentioned in the [kernel circuit documentation](../../../concepts/advanced/circuits/kernels/private_kernel.md). At the beginning of a function's execution, the context contains all of the kernel information that application needs to execute. During the lifecycle of a transaction, the function will update the context with each of it's side effects (created notes, nullifiers etc.). At the end of a function's execution the mutated context is returned to the kernel to be checked for validity. Behind the scenes, Aztec noir will pass data the kernel needs to and from a circuit, this is abstracted away from the developer. In an developer's eyes; the context is a useful structure that allows access and mutate the state of the `Aztec` blockchain. ## Two context's one API -The `Aztec` blockchain contains two environments [public and private](../../concepts/foundation/state_model.md). +The `Aztec` blockchain contains two environments [public and private](../../../concepts/foundation/state_model.md). - Private, for private transactions taking place on user's devices. - Public, for public transactions taking place on the network's sequencers. @@ -52,7 +52,7 @@ The call context contains information about the current call being made: - This value is the address of the current context's contract address. This value will be the value of the current contract that is being executed except for when the current call is a delegate call (Warning: This is yet to be implemented). In this case the value will be that of the sending contract. 3. Portal Contract Address - - This value stores the current contract's linked [portal contract](./portals/main.md) address. As a quick recap, this value is the value of the contracts related ethereum l1 contract address, and will be the recipient of any messages that are created by this contract. + - This value stores the current contract's linked [portal contract](../portals/main.md) address. As a quick recap, this value is the value of the contracts related ethereum l1 contract address, and will be the recipient of any messages that are created by this contract. 4. Flags - Furthermore there are a series of flags that are stored within the application context: - is_delegate_call: Denotes whether the current call is a delegate call. If true, then the storage contract address will be the address of the sender. @@ -80,7 +80,7 @@ To allow for flexibility in the number of arguments supported by Aztec functions 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](./syntax/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.md#after-expansion) for more details. return_values : BoundedVec, @@ -105,7 +105,7 @@ The kernel circuit will orchestrate dispatching the calls and returning the valu The public call stack contains all of the external function calls that are created within the current context. Like the private call stack above, the calls are hashed and pushed to this stack. Unlike the private call stack, these calls are not executed client side. Whenever the function is sent to the network, it will have the public call stack attached to it. At this point the sequencer will take over and execute the transactions. ### New L2 to L1 msgs -New L2 to L1 messages contains messages that are delivered to the [l1 outbox](../../concepts/foundation/communication/cross_chain_calls.md) on the execution of each rollup. +New L2 to L1 messages contains messages that are delivered to the [l1 outbox](../../../concepts/foundation/communication/cross_chain_calls.md) on the execution of each rollup. ## Public Context The Public Context includes all of the information passed from the `Public VM` into the execution environment. It is very similar to the [Private Context](#the-private-context), however it has some minor differences (detailed below). diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index caabcb298e6a..214a94df86a4 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -2,6 +2,27 @@ title: Functions --- + +## 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 is 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.md). + +In the coming 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 "normal" 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`. + +A good place to use `internal` is when you want a private function to be able to alter public state. While 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. +::: + ## `constructor` - A special `constructor` function MUST be declared within a contract's scope. @@ -9,46 +30,206 @@ title: Functions - In Aztec terminology, a constructor is always a '`private` function' (i.e. it cannot be a `public` function, in the current version of the sandbox it cannot call public functions either). - 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 constructor is as follows: +An example of a somewhat boring constructor is as follows: + +#include_code empty-constructor /yarn-project/noir-contracts/src/contracts/public_token_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/src/contracts/escrow_contract/src/main.nr rust -In this example (taken from an escrow contract), the constructor sets the deployer as an `owner`. +:::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! +::: -Although constructors are always needed, they are not required to do anything. A empty constructor can be created as follows: +## `Public` Functions +A public function is executed by the sequencer and access a state model that is very similar to that of the EVM and Ethereum. Even though mainly working 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. -#include_code empty-constructor /yarn-project/noir-contracts/src/contracts/public_token_contract/src/main.nr rust +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 add_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ## `Private` Functions -To create a private function you can 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. +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 functions-SecretFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +#include_code redeem_shield /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust -## `Public` Functions +## `unconstrained` functions -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. +Unconstrained functions are are an underlying part of `Noir` and has a deeper explanation [here](https://noir-lang.org/language_concepts/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seem as untrusted! That they are untrusted means that for security the developer must make sure to constrain them when used! -#include_code functions-OpenFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +Beyond using them inside your other functions, they can however be very convenient for providing something that reads storage and apply some logic and returns values to a user interface or similar. Below is a snippet from exposing the `balance_of_private` function from a token implementation, allow a user to easily read their balance. Similar to the `balanceOf` function in the ERC20 standard. -## `unconstrained` functions +#include_code balance_of_private /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust -#include_code functions-UncontrainedFunction /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust +:::info +Note, that unconstrained functions can have access to both public and private data when executed on users device. This is possible since it is not actually part of the circuits that is executed in contract execution. +::: -## Visibility +## Oracle functions + +An oracle is something that allow 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. Meaning that integrating with many of this would become a pain in the neck 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 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 + +### A few useful inbuilt oracles + +- [`compute_selector`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/compute_selector.nr) - Computes the selector of a function. This is useful for when you want to call a function from within a circuit, but don't got an interface at hand and don't want to hardcode the selector in hex. +- [`debug_log`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/debug_log.nr) - Provides a couple of debug functions that can be used to log information to the console. +- [`auth_witness`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/auth_witness.nr) - Provides a way to fetch the authentication witness for a given address. This is useful when building account contracts to support approve-like functionality. +- [`get_l1_to_l2_message`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/get_l1_to_l2_message.nr) - Useful for application that receive messages from L1 to be consumed on L2, such as token bridges or other cross-chain applications. +- [`notes`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/notes.nr) - Provides a lot of functions related to notes, such as fetches notes from storage etc, used behind the scenes for value notes and other pre-build note implementations. + + +--- + +## 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 `Aztec RPC Server` (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 + |-> A::example_call + | -> B::nested_call + |-> C::example_call +``` + + +In the example above the Account Contract has been instructed to call two external functions. In the first function all, to `ContractA::example_call` a further nested call is performed to `ContractB::nested_call`. Finally the account contract makes one last call to `ContractC::example_call`. + +Lets further illustrate what these examples could look like + +```rust +// Contract A contains a singular function that returns the result of B::nested_call +contract A { + #[aztec(private)] + fn example_call(input: Field) -> pub Field { + B::at().nested_call(input) + } +} -### `internal` +// Contract B contains a singular function that returns a `input + 1` +contract B { + #[aztec(private)] + fn nested_call(input: Field) -> pub Field { + input + 1 + } +} -Similar to Solidity, internal functions and vars can be accessed within the contract itself. While technically callable from other contracts, there is a dynamic check that validates the caller to ensure it's the same contract. +// Contract C contains a singular function that simply returns `10` +contract C { + #[aztec(private)] + fn example_call() -> pub Field { + 10 + } +} +``` -### `external` +When simulating the following call stack, we can execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `A::example_call`, then begin to execute the code there. When the simulator executes the code in contract `A`, it will find the further nested call to contract `B::nested_call`. It will execute the code in B, bringing the return value back to contract `A`. +The same process will be followed for contract `C`. -External is not used explicitly as it is in Solidity, but things not marked as `internal` will be external. +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. -### `#[aztec(public)]` and `#[aztec(private)]` -These are used to annotate functions so that they are compliant with Aztec ABIs. They inject `PublicContext` and `PrivateContext` for use in contracts. +Those of you who have written circuits before may see an issue here. The account contract, contract `A`, `B` and `C` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `B` in contract `A`? 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 `A::example_call` is the same value that is returned from `B::nested_call`, it will also be able to constrain the value returned by `B::nested_call` is the inputs to `A::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. It is wrapped hence it is a struct and those are outlined in tuple-form for selector computation, `AztecAddress` becomes `(Field)`. +::: + +#include_code private_burn_interface /yarn-project/noir-contracts/src/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 snippets is 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/src/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/src/contracts/token_bridge_contract/src/token_interface.nr rust +#include_code exit_to_l1_public /yarn-project/noir-contracts/src/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.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/src/contracts/token_contract/src/main.nr rust +#include_code increase_public_balance /yarn-project/noir-contracts/src/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 private data 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/src/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/src/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 private data 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 [messages](./messaging.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. @@ -95,7 +276,7 @@ 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/src/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 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/src/contracts/docs_example_contract/src/main.nr rust diff --git a/docs/docs/dev_docs/contracts/syntax/globals.md b/docs/docs/dev_docs/contracts/syntax/globals.md index 30b6a0fae9c2..39dc32abdff6 100644 --- a/docs/docs/dev_docs/contracts/syntax/globals.md +++ b/docs/docs/dev_docs/contracts/syntax/globals.md @@ -14,10 +14,18 @@ For developers coming from solidity, this concept will be similar to how the glo The private global variables contain: ### Chain Id The chain id differs depending on which Aztec instance you are on ( NOT the Ethereum hardfork that the rollup is settling to ). On original deployment of the network, this value will be 1. +```rust +context.chain_id(); +``` ### Version The version number indicates which Aztec hardfork you are on. The Genesis block of the network will have the version number 1. +```rust +context.version(); +``` + + ## Public Global Variables #include_code public-global-variables /yarn-project/aztec-nr/aztec/src/abi.nr rust @@ -26,10 +34,19 @@ The public global variables contain the values present in the `private global va ### Timestamp The timestamp is the unix timestamp in which the block has been executed. The value is provided by the block's proposer (therefore can have variance). This value will always increase. +```rust +context.timestamp(); +``` + ### Block Number The block number is an sequential identifier that labels each individual block of the network. This value will be the block number of the block the accessing transaction is included in. The block number of the genesis block will be 1, with the number increasing by 1 for every block after. -> *Why do the available global variables differ per execution environment?* +```rust +context.block_number(); +``` + +:::info *Why do the available global variables differ per execution environment?* > The global variables are constrained by the proving environment. In the case of public functions, they are executed on a sequencer that will know the timestamp and number of the next block ( as they are the block producer ). > In the case of private functions, we cannot be sure which block our transaction will be included in, hence we can not guarantee values for the timestamp or block number. +::: \ No newline at end of file diff --git a/docs/sidebars.js b/docs/sidebars.js index 585b1d947e33..b5424636ddfb 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -105,9 +105,10 @@ const sidebars = { }, items: [ "dev_docs/contracts/syntax/contract", - "dev_docs/contracts/syntax/functions", "dev_docs/contracts/syntax/storage", "dev_docs/contracts/syntax/state_variables", + "dev_docs/contracts/syntax/functions", + "dev_docs/contracts/syntax/context", "dev_docs/contracts/syntax/globals", "dev_docs/contracts/syntax/messaging", ], @@ -124,8 +125,8 @@ const sidebars = { "dev_docs/contracts/portals/registry", "dev_docs/contracts/portals/inbox", "dev_docs/contracts/portals/outbox", - ] - } + ], + }, // { // label: "Resources", // type: "category", diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr index 07ac537644c4..748ab5d97391 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/main.nr @@ -66,6 +66,7 @@ contract TokenBridge { 1 } + // docs:start:exit_to_l1_public // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message publicly // Requires `msg.sender` to give approval to the bridge to burn tokens on their behalf using witness signatures #[aztec(public)] @@ -86,6 +87,7 @@ contract TokenBridge { 1 } + // docs:end:exit_to_l1_public // Consumes a L1->L2 message and calls the token contract to mint the appropriate amount in private assets // User needs to call token.redeem_shield() to get the private assets @@ -114,6 +116,7 @@ contract TokenBridge { 1 } + // docs:start:exit_to_l1_private // Burns the appropriate amount of tokens and creates a L2 to L1 withdraw message privately // Requires `msg.sender` (caller of the method) to give approval to the bridge to burn tokens on their behalf using witness signatures #[aztec(private)] @@ -136,6 +139,7 @@ contract TokenBridge { 1 } + /// docs:end:exit_to_l1_private // /// Unconstrained /// diff --git a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr index 647fca790eac..739e7742d331 100644 --- a/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr +++ b/yarn-project/noir-contracts/src/contracts/token_bridge_contract/src/token_interface.nr @@ -20,6 +20,7 @@ impl Token { ); } + // docs:start:public_burn_interface fn burn_public(self: Self, context: PublicContext, from: Field, amount: Field, nonce: Field) { let _return_values = context.call_public_function( self.address, @@ -27,6 +28,7 @@ impl Token { [from, amount, nonce] ); } + // docs:end:public_burn_interface fn mint_private(self: Self, context: PublicContext, amount: Field, secret_hash: Field) { let _return_values = context.call_public_function( @@ -36,6 +38,8 @@ impl Token { ); } + + // docs:start:private_burn_interface fn burn(self: Self, context: &mut PrivateContext, from: Field, amount: Field, nonce: Field) { let _return_values = context.call_private_function( self.address, @@ -43,4 +47,5 @@ impl Token { [from, amount, nonce] ); } + // docs:end:private_burn_interface } diff --git a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr index 76c6a88ecb40..6105bb8f6759 100644 --- a/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr +++ b/yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr @@ -111,6 +111,7 @@ contract Token { storage.admin.write(new_admin.address); } + // docs:start:add_minter #[aztec(public)] fn set_minter( minter: AztecAddress, @@ -120,6 +121,7 @@ contract Token { assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); storage.minters.at(minter.address).write(approve as Field); } + // docs:end:add_minter #[aztec(public)] fn mint_public( @@ -153,6 +155,7 @@ contract Token { 1 } + // docs:start:shield #[aztec(public)] fn shield( from: AztecAddress, @@ -181,6 +184,7 @@ contract Token { pending_shields.insert_from_public(&mut note); 1 } + // docs:end:shield #[aztec(public)] @@ -236,6 +240,7 @@ contract Token { 1 } + // docs:start:redeem_shield #[aztec(private)] fn redeem_shield( to: AztecAddress, @@ -252,7 +257,9 @@ contract Token { 1 } + // docs:end:redeem_shield + // docs:start:unshield #[aztec(private)] fn unshield( from: AztecAddress, @@ -278,6 +285,7 @@ contract Token { 1 } + // docs:end:unshield // docs:start:transfer #[aztec(private)] @@ -349,6 +357,7 @@ contract Token { /// Internal /// + // docs:start:increase_public_balance #[aztec(public)] internal fn _increase_public_balance( to: AztecAddress, @@ -358,6 +367,7 @@ contract Token { let new_balance = SafeU120::new(storage.public_balances.at(to.address).read()).add(SafeU120::new(amount)); storage.public_balances.at(to.address).write(new_balance.value as Field); } + // docs:end:increase_public_balance #[aztec(public)] internal fn _reduce_total_supply( From c49cf9509585b594ac69cc784d9030b01cd1de9f Mon Sep 17 00:00:00 2001 From: LHerskind Date: Tue, 19 Sep 2023 19:38:17 +0000 Subject: [PATCH 2/6] chore: address rahul comments --- .../dev_docs/contracts/syntax/functions.md | 43 +++++++++---------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index 214a94df86a4..0d69b4909a1e 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -10,12 +10,12 @@ 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 is 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.md). -In the coming sections, we are going to see how these two "types" co-exists and interact. +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 "normal" 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`. +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` as it is implided, and `private` since we don't support inheritance and 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. While 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. @@ -69,18 +69,18 @@ Note, that unconstrained functions can have access to both public and private da ## Oracle functions -An oracle is something that allow 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. +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. Meaning that integrating with many of this would become a pain in the neck for the developers, see some of the motivation behind [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626) for similar case in EVM. +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 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 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! +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 @@ -106,35 +106,34 @@ Behind the scenes, the `Aztec RPC Server` (the beating heart of Aztec that runs Take, for example, the following call stack: ``` AccountContract::entrypoint - |-> A::example_call - | -> B::nested_call - |-> C::example_call + |-> 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 `ContractA::example_call` a further nested call is performed to `ContractB::nested_call`. Finally the account contract makes one last call to `ContractC::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 -// Contract A contains a singular function that returns the result of B::nested_call -contract A { +// Foo contains a singular function that returns the result of Bar::nested_call +contract Foo { #[aztec(private)] fn example_call(input: Field) -> pub Field { - B::at().nested_call(input) + Bar::at().nested_call(input) } } -// Contract B contains a singular function that returns a `input + 1` -contract B { +// Bar contains a singular function that returns a `input + 1` +contract Bar { #[aztec(private)] fn nested_call(input: Field) -> pub Field { input + 1 } } -// Contract C contains a singular function that simply returns `10` -contract C { +// Baz contains a singular function that simply returns `10` +contract Baz { #[aztec(private)] fn example_call() -> pub Field { 10 @@ -142,14 +141,14 @@ contract C { } ``` -When simulating the following call stack, we can execution flow to continue procedurally. The simulator will begin at the account contract's entry point, find a call to `A::example_call`, then begin to execute the code there. When the simulator executes the code in contract `A`, it will find the further nested call to contract `B::nested_call`. It will execute the code in B, bringing the return value back to contract `A`. -The same process will be followed for contract `C`. +When simulating the following call stack, we can 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 `A`, `B` and `C` are all distinct circuits, which do not know anything about each other. How is it possible to use a value from contract `B` in contract `A`? This value will not be constrained. +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 `A::example_call` is the same value that is returned from `B::nested_call`, it will also be able to constrain the value returned by `B::nested_call` is the inputs to `A::example_call` + 1. +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. From 297074791d83e486bd9a003b99fdff3cbb07104f Mon Sep 17 00:00:00 2001 From: LHerskind Date: Tue, 19 Sep 2023 21:05:22 +0000 Subject: [PATCH 3/6] fix: change `external` comment. --- docs/docs/dev_docs/contracts/syntax/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index 0d69b4909a1e..f8af279f144c 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -15,7 +15,7 @@ In the following sections, we are going to see how these two "types" co-exists a ### Function visibility This is the kind of visibility you are more used to seeing in Solidity and more "normal" 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` as it is implided, and `private` since we don't support inheritance and would also be confusing with multiple types of `private`. +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. While 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. From d925a1e4bb8e3cfdd81b903585dd609b0c30c6fe Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Tue, 19 Sep 2023 22:06:53 +0100 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: josh crites Co-authored-by: Rahul Kothari --- docs/docs/dev_docs/contracts/syntax/functions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index f8af279f144c..d3324f5149f9 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -8,16 +8,16 @@ title: Functions 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 is 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.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](../../../concepts/foundation/communication/public_private_calls.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 "normal" programming languages. It is used to describe whether a function is callable from other contracts, or only from within the same contract. +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. While 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. +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. @@ -43,7 +43,7 @@ It is not possible to call public functions from within a constructor. Beware th ::: ## `Public` Functions -A public function is executed by the sequencer and access a state model that is very similar to that of the EVM and Ethereum. Even though mainly working 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. +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. @@ -57,7 +57,7 @@ As alluded to earlier, a private function operates on private information, and i ## `unconstrained` functions -Unconstrained functions are are an underlying part of `Noir` and has a deeper explanation [here](https://noir-lang.org/language_concepts/unconstrained). But in short, they are functions which are not directly constrained and therefore should be seem as untrusted! That they are untrusted means that for security the developer must make sure to constrain them when used! +Unconstrained functions are are an underlying part of Noir and has a deeper explanation [here](https://noir-lang.org/language_concepts/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 can however be very convenient for providing something that reads storage and apply some logic and returns values to a user interface or similar. Below is a snippet from exposing the `balance_of_private` function from a token implementation, allow a user to easily read their balance. Similar to the `balanceOf` function in the ERC20 standard. @@ -71,7 +71,7 @@ Note, that unconstrained functions can have access to both public and private da 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 "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. From a0fae49b61f85181454afc5f5e72133a30cf4631 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 20 Sep 2023 07:48:38 +0100 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: josh crites --- docs/docs/dev_docs/contracts/syntax/functions.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index d3324f5149f9..11299aa05495 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -59,7 +59,7 @@ As alluded to earlier, a private function operates on private information, and i Unconstrained functions are are an underlying part of Noir and has a deeper explanation [here](https://noir-lang.org/language_concepts/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 can however be very convenient for providing something that reads storage and apply some logic and returns values to a user interface or similar. Below is a snippet from exposing the `balance_of_private` function from a token implementation, allow a user to easily read their balance. Similar to the `balanceOf` function in the ERC20 standard. +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/src/contracts/token_contract/src/main.nr rust @@ -87,7 +87,7 @@ Oracles introduce **non-determinism** into a circuit, and thus are `unconstraine ### A few useful inbuilt oracles -- [`compute_selector`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/compute_selector.nr) - Computes the selector of a function. This is useful for when you want to call a function from within a circuit, but don't got an interface at hand and don't want to hardcode the selector in hex. +- [`compute_selector`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/compute_selector.nr) - Computes the selector of a function. This is useful for when you want to call a function from within a circuit, but don't have an interface at hand and don't want to hardcode the selector in hex. - [`debug_log`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/debug_log.nr) - Provides a couple of debug functions that can be used to log information to the console. - [`auth_witness`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/auth_witness.nr) - Provides a way to fetch the authentication witness for a given address. This is useful when building account contracts to support approve-like functionality. - [`get_l1_to_l2_message`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/get_l1_to_l2_message.nr) - Useful for application that receive messages from L1 to be consumed on L2, such as token bridges or other cross-chain applications. @@ -141,7 +141,7 @@ contract Baz { } ``` -When simulating the following call stack, we can 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`. +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. @@ -157,14 +157,14 @@ The orchestration of these calls has an added benefit. All of the nested calls a 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. It is wrapped hence it is a struct and those are outlined in tuple-form for selector computation, `AztecAddress` becomes `(Field)`. +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/src/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 snippets is a token bridge that is burning the underlying token and creating a message for L1 to mint some assets to the `recipient` on Ethereum. +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/src/contracts/token_bridge_contract/src/main.nr rust From e9dc67f76314911bac4d8dce864bef56990b7709 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Wed, 20 Sep 2023 11:40:25 +0000 Subject: [PATCH 6/6] chore: address rahul nits --- docs/docs/dev_docs/contracts/syntax/functions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index 11299aa05495..b68c18e515f7 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -57,14 +57,14 @@ As alluded to earlier, a private function operates on private information, and i ## `unconstrained` functions -Unconstrained functions are are an underlying part of Noir and has a deeper explanation [here](https://noir-lang.org/language_concepts/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/language_concepts/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. +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/src/contracts/token_contract/src/main.nr rust :::info -Note, that unconstrained functions can have access to both public and private data when executed on users device. This is possible since it is not actually part of the circuits that is executed in contract execution. +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