From b58cfb55c192d3942c3eacecb74d6db28326055d Mon Sep 17 00:00:00 2001 From: josh crites Date: Wed, 20 Sep 2023 13:49:37 -0400 Subject: [PATCH] fix: Use #import_code in Token contract tutorial (#2438) This PR updates the token contract tutorial to use the #import_code macro in the docs. - It also modifies one of the import tags to match the name of the function it references. - Also adds a note to the contracts page. - Added a note to clarify what to copy/paste on the Sandbox page --- .../dev_docs/contracts/syntax/functions.md | 75 +-- .../getting_started/noir_contracts.md | 6 +- docs/docs/dev_docs/getting_started/sandbox.md | 6 +- .../token_contract_tutorial.md | 464 +----------------- .../src/contracts/token_contract/src/main.nr | 39 +- 5 files changed, 112 insertions(+), 478 deletions(-) diff --git a/docs/docs/dev_docs/contracts/syntax/functions.md b/docs/docs/dev_docs/contracts/syntax/functions.md index d810556d9c1..93e2f0a5887 100644 --- a/docs/docs/dev_docs/contracts/syntax/functions.md +++ b/docs/docs/dev_docs/contracts/syntax/functions.md @@ -3,17 +3,18 @@ title: Functions description: This page covers functions, private and public functions composability, as well as their differences. --- - ## 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`. ### 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.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`. @@ -25,9 +26,10 @@ Note that non-internal functions could be used directly as an entry-point, which ::: ## 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. +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. @@ -53,11 +55,12 @@ 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 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 add_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust +#include_code set_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ## `Private` Functions @@ -67,9 +70,9 @@ As alluded to earlier, a private function operates on private information, and i ## `unconstrained` functions -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! +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 @@ -79,12 +82,12 @@ Note, that unconstrained functions can have access to both public and private da ## 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. +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. +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. @@ -98,13 +101,12 @@ 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 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. +- [`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. - [`logs`](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/oracle/logs.nr) - Provides the to log encrypted and unencrypted data. - --- ## Calling functions from other functions @@ -115,6 +117,7 @@ In Aztec Private to Private function calls are handled by the [private kernel ci 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 |-> Foo::example_call @@ -125,7 +128,9 @@ AccountContract::entrypoint 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 { @@ -155,17 +160,17 @@ contract Baz { 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. +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. +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. +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)`. @@ -179,7 +184,6 @@ The following snippet is from a token bridge that is burning the underlying toke #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. @@ -188,10 +192,10 @@ Using the same example code and call stack from the section [above](#private---- 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`](../../../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. +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 @@ -200,10 +204,10 @@ Calling a public function from another public function is quite similar to what 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. +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. +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). @@ -214,11 +218,11 @@ As we can see above, the private to public transaction flow looks very similar t +### Public -> Private -### 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. +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. @@ -234,54 +238,57 @@ 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`). ::: ---- +--- ## 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. + +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/src/contracts/docs_example_contract/src/main.nr rust +#include_code simple_macro_example /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust #### After expansion + #include_code simple_macro_example_expanded /yarn-project/noir-contracts/src/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/src/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. +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. +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/src/contracts/docs_example_contract/src/main.nr rust -Just as the kernel passes information into the 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`. +Just as the kernel passes information into the 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`* +> _Why is it called the `PrivateCircuitPublicInputs`_ > It is commonly asked why the return values of a function in a circuit are labelled 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. +> 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/src/contracts/docs_example_contract/src/main.nr rust -*What is the hasher and why is it needed?* +_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. +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/src/contracts/docs_example_contract/src/main.nr rust @@ -290,8 +297,8 @@ Each Aztec function has access to a [context](./context.mdx) object. This object #include_code context-example-context-return /yarn-project/noir-contracts/src/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. +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. **Returning the function context to the kernel.** #include_code context-example-finish /yarn-project/noir-contracts/src/contracts/docs_example_contract/src/main.nr rust diff --git a/docs/docs/dev_docs/getting_started/noir_contracts.md b/docs/docs/dev_docs/getting_started/noir_contracts.md index 6daa1b15942..90d11598e92 100644 --- a/docs/docs/dev_docs/getting_started/noir_contracts.md +++ b/docs/docs/dev_docs/getting_started/noir_contracts.md @@ -67,9 +67,13 @@ compiler_version = "0.1" type = "contract" [dependencies] -aztec = { git="https://github.com/AztecProtocol/aztec-packages", tag="master", directory="yarn-project/aztec-nr/aztec" } +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="master", directory="yarn-project/aztec-nr/aztec" } ``` +:::note +You may need to update your dependencies depending on the contract that you are writing. For example, the token contract [imports more](../getting_started/token_contract_tutorial#project-setup). +::: + You are now ready to write your own contracts! You can replace the content of the generated file `example_contract/src/main.nr` with your contract code. diff --git a/docs/docs/dev_docs/getting_started/sandbox.md b/docs/docs/dev_docs/getting_started/sandbox.md index 3cb3bc71e45..46f9f5f5bae 100644 --- a/docs/docs/dev_docs/getting_started/sandbox.md +++ b/docs/docs/dev_docs/getting_started/sandbox.md @@ -249,9 +249,11 @@ We can break this down as follows: A token contract wouldn't be very useful if you aren't able to query the balance of an account. As part of the deployment, tokens were minted to Alice. We can now call the contract's `balance_of_private()` function to retrieve the balances of the accounts. +Here is the `balance_of_private` code from the contract (no need to paste it into `index.ts`): + #include_code balance_of_private /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust -Call the `balance_of_private` function using the following code: +Call the `balance_of_private` function using the following code (paste this): #include_code Balance /yarn-project/end-to-end/src/e2e_sandbox_example.test.ts typescript @@ -289,7 +291,7 @@ Now lets transfer some funds from Alice to Bob by calling the `transfer` functio 3. The quantity of tokens to be transferred. 4. The nonce for the [authentication witness](../../concepts//foundation/accounts/main.md#authorizing-actions), or 0 if msg.sender equal sender. -Here is the Noir code for the `transfer` function: +Here is the Noir code for the `transfer` function (don't paste this into `index.ts`): #include_code transfer /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust diff --git a/docs/docs/dev_docs/getting_started/token_contract_tutorial.md b/docs/docs/dev_docs/getting_started/token_contract_tutorial.md index d162ff8fafb..3b34a3f34d8 100644 --- a/docs/docs/dev_docs/getting_started/token_contract_tutorial.md +++ b/docs/docs/dev_docs/getting_started/token_contract_tutorial.md @@ -230,39 +230,7 @@ Before we can implement the functions, we need set up the contract storage, and Just below the contract definition, add the following imports: -```rust -mod types; -mod util; - -contract Token { - use dep::std::option::Option; - - use dep::safe_math::SafeU120; - - use dep::value_note::{ - balance_utils, - utils::{increment, decrement}, - value_note::{VALUE_NOTE_LEN, ValueNoteMethods, ValueNote}, - }; - - use dep::aztec::{ - note::{ - note_header::NoteHeader, - utils as note_utils, - }, - context::{PrivateContext, PublicContext, Context}, - state_vars::{map::Map, public_state::PublicState, set::Set}, - types::type_serialisation::field_serialisation::{ - FieldSerialisationMethods, FIELD_SERIALISED_LEN, - }, - oracle::compute_selector::compute_selector, - auth::{assert_valid_message_for, assert_valid_public_message_for} - types::address::AztecAddress, - }; - - use crate::types::{TransparentNote, TransparentNoteMethods, TRANSPARENT_NOTE_LEN}; - use crate::util::{compute_message_hash}; -``` +#include_code imports /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust We are importing the Option type, items from the `value_note` library to help manage private value storage, note utilities, context (for managing private and public execution contexts), `state_vars` for helping manage state, `types` for data manipulation and `oracle` for help passing data from the private to public execution context. We also import the `auth` [library](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/auth.nr) to handle token authorizations from [Account Contracts](../../concepts/foundation/accounts/main). Check out the Account Contract with AuthWitness [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/schnorr_auth_witness_account_contract/src/main.nr). @@ -284,16 +252,7 @@ Now that we have dependencies imported into our contract we can define the stora Below the dependencies, paste the following Storage struct: -```rust - struct Storage { - admin: PublicState, - minters: Map>, - balances: Map>, - total_supply: PublicState, - pending_shields: Set, - public_balances: Map>, - } -``` +#include_code storage_struct /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust Reading through the storage variables: @@ -312,60 +271,7 @@ Once we have Storage defined, we need to specify how to initialize it. The `init Also, the public storage variables define the type that they store by passing the methods by which they are serialized. Because all `PublicState` in this contract is storing Field elements, each storage variable takes `FieldSerialisationMethods`. -```rust - impl Storage { - fn init(context: Context) -> pub Self { - Storage { - // storage slot 1 - admin: PublicState::new( - context, - 1, - FieldSerialisationMethods, - ), - // storage slot 2 - minters: Map::new( - context, - 2, - |context, slot| { - PublicState::new( - context, - slot, - FieldSerialisationMethods, - ) - }, - ), - // storage slot 3 - balances: Map::new( - context, - 3, - |context, slot| { - Set::new(context, slot, ValueNoteMethods) - }, - ), - // storage slot 4 - total_supply: PublicState::new( - context, - 4, - FieldSerialisationMethods, - ), - // storage slot 5 - pending_shields: Set::new(context, 5, TransparentNoteMethods), - // storage slot 6 - public_balances: Map::new( - context, - 6, - |context, slot| { - PublicState::new( - context, - slot, - FieldSerialisationMethods, - ) - }, - ), - } - } - } -``` +#include_code storage_init /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ## Functions @@ -373,16 +279,9 @@ Copy and paste the body of each function into the appropriate place in your proj ### Constructor -In the source code, the constructor logic is commented out. I uncommented it here for legibility, but you should comment out the body of the function in your example, otherwise the contract may not successfully deploy. +In the source code, the constructor logic is commented out due to some limitations of the current state of the development. -```rust - #[aztec(private)] - fn constructor() { - // Currently not possible to execute public calls from constructor as code not yet available to sequencer. - let selector = compute_selector("_initialize((Field))"); - let _callStackItem = context.call_public_function(context.this_address(), selector, [context.msg_sender()]); - } -``` +#include_code constructor /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust The constructor is a private function. There isn't any private state to set up in this function, but there is public state to set up. The `context` is a global variable that is available to private and public functions, but the available methods differ based on the context. You can see the implementation details [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/aztec-nr/aztec/src/context.nr). The `context.call_public_function` allows a private function to call a public function on any contract. In this case, the constructor is passing the `msg_sender` as the argument to the `_initialize` function, which is also defined in this contract. @@ -390,11 +289,7 @@ The constructor is a private function. There isn't any private state to set up i Public functions are declared with the `#[aztec(public)]` macro above the function name like so: -```rust - #[aztec(public)] - fn set_admin( - new_admin: AztecAddress, -``` +#include_code set_admin /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust As described in the [execution contexts section above](#execution-contexts), public function logic and transaction information is transparent to the world. Public functions update public state, but can be used to prepare data to be used in a private context, as we will go over below (e.g. see the [shield](#shield) function). @@ -410,32 +305,13 @@ After this, storage is referenced as `storage.variable`. We won't go over this s After storage is initialized, the contract checks that the `msg_sender` is the `admin`. If not, the transaction will fail. If it is, the `new_admin` is saved as the `admin`. -```rust - #[aztec(public)] - fn set_admin( - new_admin: AztecAddress, - ) { - let storage = Storage::init(Context::public(&mut context)); - assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); - storage.admin.write(new_admin.address); - } -``` +#include_code set_admin /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `set_minter` This function allows the `admin` to add or a remove a `minter` from the public `minters` mapping. It checks that `msg_sender` is the `admin` and finally adds the `minter` to the `minters` mapping. -```rust - #[aztec(public)] - fn set_minter( - minter: AztecAddress, - approve: bool, - ) { - let storage = Storage::init(Context::public(&mut context)); - assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); - storage.minters.at(minter.address).write(approve as Field); - } -``` +#include_code set_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `mint_public` @@ -445,23 +321,7 @@ First, storage is initialized. Then the function checks that the `msg_sender` is The function returns 1 to indicate successful execution. -```rust - #[aztec(public)] - fn mint_public( - to: AztecAddress, - amount: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - assert(storage.minters.at(context.msg_sender()).read() == 1, "caller is not minter"); - let amount = SafeU120::new(amount); - let new_balance = SafeU120::new(storage.public_balances.at(to.address).read()).add(amount); - let supply = SafeU120::new(storage.total_supply.read()).add(amount); - - storage.public_balances.at(to.address).write(new_balance.value as Field); - storage.total_supply.write(supply.value as Field); - 1 - } -``` +#include_code mint_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `mint_private` @@ -469,23 +329,7 @@ This public function allows an account approved in the public `minters` mapping First, public storage is initialized. Then it checks that the `msg_sender` is an approved minter. Then a new `TransparentNote` is created with the specified `amount` and `secret_hash`. You can read the details of the `TransparentNote` in the `types.nr` file [here](https://github.com/AztecProtocol/aztec-packages/blob/master/yarn-project/noir-contracts/src/contracts/token_contract/src/types.nr#L61). The `amount` is added to the existing public `total_supply` and the storage value is updated. Then the new `TransparentNote` is added to the `pending_shields` using the `insert_from_public` function, which is accessible on the `Set` type. Then it's ready to be claimed by anyone with the `secret_hash` pre-image using the `redeem_shield` function. It returns `1` to indicate successful execution. -```rust - #[aztec(public)] - fn mint_private( - amount: Field, - secret_hash: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - assert(storage.minters.at(context.msg_sender()).read() == 1, "caller is not minter"); - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount, secret_hash); - let supply = SafeU120::new(storage.total_supply.read()).add(SafeU120::new(amount)); - - storage.total_supply.write(supply.value as Field); - pending_shields.insert_from_public(&mut note); - 1 - } -``` +#include_code mint_private /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `shield` @@ -501,36 +345,7 @@ If the `msg_sender` is the same as the account to debit tokens from, the authori It returns `1` to indicate successful execution. -```rust - #[aztec(public)] - fn shield( - from: AztecAddress, - amount: Field, - secret_hash: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - - if (from.address != context.msg_sender()) { - // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. - let selector = compute_selector("shield((Field),Field,Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, amount, secret_hash, nonce]); - assert_valid_public_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = SafeU120::new(amount); - let from_balance = SafeU120::new(storage.public_balances.at(from.address).read()).sub(amount); - - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount.value as Field, secret_hash); - - storage.public_balances.at(from.address).write(from_balance.value as Field); - pending_shields.insert_from_public(&mut note); - 1 - } -``` +#include_code shield /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `transfer_public` @@ -538,34 +353,7 @@ This public function enables public transfers between Aztec accounts. The sender After storage is initialized, the [authorization flow specified above](#authorizing-token-spends) is checked. Then the sender and recipient's balances are updated and saved to storage using the `SafeU120` library. -```rust - #[aztec(public)] - fn transfer_public( - from: AztecAddress, - to: AztecAddress, - amount: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - - if (from.address != context.msg_sender()) { - let selector = compute_selector("transfer_public((Field),(Field),Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, to.address, amount, nonce]); - assert_valid_public_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = SafeU120::new(amount); - let from_balance = SafeU120::new(storage.public_balances.at(from.address).read()).sub(amount); - storage.public_balances.at(from.address).write(from_balance.value as Field); - - let to_balance = SafeU120::new(storage.public_balances.at(to.address).read()).add(amount); - storage.public_balances.at(to.address).write(to_balance.value as Field); - - 1 - } -``` +#include_code transfer_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `burn_public` @@ -573,33 +361,7 @@ This public function enables public burning (destroying) of tokens from the send After storage is initialized, the [authorization flow specified above](#authorizing-token-spends) is checked. Then the sender's public balance and the `total_supply` are updated and saved to storage using the `SafeU120` library. -```rust - #[aztec(public)] - fn burn_public( - from: AztecAddress, - amount: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::public(&mut context)); - - if (from.address != context.msg_sender()) { - let selector = compute_selector("burn_public((Field),Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, amount, nonce]); - assert_valid_public_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = SafeU120::new(amount); - let from_balance = SafeU120::new(storage.public_balances.at(from.address).read()).sub(amount); - storage.public_balances.at(from.address).write(from_balance.value as Field); - - let new_supply = SafeU120::new(storage.total_supply.read()).sub(amount); - storage.total_supply.write(new_supply.value as Field); - - 1 - } -``` +#include_code burn_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ### Private function implementations @@ -628,24 +390,7 @@ Going through the function logic, first storage is initialized. Then it gets the The function returns `1` to indicate successful execution. -```rust - #[aztec(private)] - fn redeem_shield( - to: AztecAddress, - amount: Field, - secret: Field, - ) -> Field { - let storage = Storage::init(Context::private(&mut context)); - let pending_shields = storage.pending_shields; - let balance = storage.balances.at(to.address); - let mut public_note = TransparentNote::new_from_secret(amount, secret); - - pending_shields.assert_contains_and_remove_publicly_created(&mut public_note); - increment(balance, amount, to.address); - - 1 - } -``` +#include_code redeem_shield /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `unshield` @@ -655,33 +400,7 @@ After initializing storage, the function checks that the `msg_sender` is authori The function returns `1` to indicate successful execution. -```rust - #[aztec(private)] - fn unshield( - from: AztecAddress, - to: AztecAddress, - amount: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::private(&mut context)); - - if (from.address != context.msg_sender()) { - let selector = compute_selector("unshield((Field),(Field),Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, to.address, amount, nonce]); - assert_valid_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let from_balance = storage.balances.at(from.address); - decrement(from_balance, amount, from.address); - - let selector = compute_selector("_increase_public_balance((Field),Field)"); - let _void = context.call_public_function(context.this_address(), selector, [to.address, amount]); - - 1 - } -``` +#include_code unshield /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `transfer` @@ -689,33 +408,7 @@ This private function enables private token transfers between Aztec accounts. After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. See [the Authorizing token spends section](#authorizing-token-spends) above for more detail--the only difference being that `assert_valid_message_for` is modified to work specifically in the private context. After authorization, the function gets the current balances for the sender and recipient and decrements and increments them, respectively, using the `value_note` helper functions. -```rust - #[aztec(private)] - fn transfer( - from: AztecAddress, - to: AztecAddress, - amount: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::private(&mut context)); - - if (from.address != context.msg_sender()) { - let selector = compute_selector("transfer((Field),(Field),Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, to.address, amount, nonce]); - assert_valid_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let from_balance = storage.balances.at(from.address); - let to_balance = storage.balances.at(to.address); - - decrement(from_balance, amount, from.address); - increment(to_balance, amount, to.address); - - 1 - } -``` +#include_code transfer /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `burn` @@ -723,33 +416,7 @@ This private function enables accounts to privately burn (destroy) tokens. After initializing storage, the function checks that the `msg_sender` is authorized to spend tokens. Then it gets the sender's current balance and decrements it. Finally it stages a public function call to [`_reduce_total_supply`](#_reduce_total_supply). -```rust - #[aztec(private)] - fn burn( - from: AztecAddress, - amount: Field, - nonce: Field, - ) -> Field { - let storage = Storage::init(Context::private(&mut context)); - - if (from.address != context.msg_sender()) { - let selector = compute_selector("burn((Field),Field,Field)"); - let message_field = compute_message_hash([context.msg_sender(), context.this_address(), selector, from.address, amount, nonce]); - assert_valid_message_for(&mut context, from.address, message_field); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let from_balance = storage.balances.at(from.address); - - decrement(from_balance, amount, from.address); - - let selector = compute_selector("_reduce_total_supply(Field)"); - let _void = context.call_public_function(context.this_address(), selector, [amount]); - - 1 - } -``` +#include_code burn /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ### Internal function implementations @@ -761,51 +428,19 @@ This function is called via the [constructor](#constructor). Note that it is not This function sets the creator of the contract (passed as `msg_sender` from the constructor) as the admin and makes them a minter. -```rust - // We cannot do this from the constructor currently - // Since this should be internal, for now, we ignore the safety checks of it, as they are - // enforced by it being internal and only called from the constructor. - #[aztec(public)] - fn _initialize( - new_admin: AztecAddress, - ) { - let storage = Storage::init(Context::public(&mut context)); - storage.admin.write(new_admin.address); - storage.minters.at(new_admin.address).write(1); - } -``` +#include_code initialize /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `_increase_public_balance` This function is called from [`unshield`](#unshield). The account's private balance is decremented in `shield` and the public balance is increased in this function. -```rust - #[aztec(public)] - internal fn _increase_public_balance( - to: AztecAddress, - amount: Field, - ) { - let storage = Storage::init(Context::public(&mut context)); - 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); - } -``` +#include_code increase_public_balance /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `_reduce_total_supply` -This function is called from [`burn`](#burn). The account's private balance is decremened in `burn` and the public `total_supply` is reduced in this function. +This function is called from [`burn`](#burn). The account's private balance is decremented in `burn` and the public `total_supply` is reduced in this function. -```rust - #[aztec(public)] - internal fn _reduce_total_supply( - amount: Field, - ) { - // Only to be called from burn. - let storage = Storage::init(Context::public(&mut context)); - let new_supply = SafeU120::new(storage.total_supply.read()).sub(SafeU120::new(amount)); - storage.total_supply.write(new_supply.value as Field); - } -``` +#include_code reduce_total_supply /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ### Unconstrained function implementations @@ -815,64 +450,31 @@ Unconstrained functions are similar to `view` functions in Solidity in that they A getter function for reading the public `admin` value. -```rust - unconstrained fn admin() -> Field { - let storage = Storage::init(Context::none()); - storage.admin.read() - } -``` +#include_code admin /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `is_minter` A getter function for checking the value of associated with a `minter` in the public `minters` mapping. -```rust - unconstrained fn is_minter( - minter: AztecAddress, - ) -> bool { - let storage = Storage::init(Context::none()); - storage.minters.at(minter.address).read() as bool - } -``` +#include_code is_minter /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `total_supply` A getter function for checking the token `total_supply`. -```rust - unconstrained fn total_supply() -> Field { - let storage = Storage::init(Context::none()); - storage.total_supply.read() - } -``` +#include_code total_supply /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `balance_of_private` A getter function for checking the private balance of the provided Aztec account. Note that the [Aztec RPC Server](https://github.com/AztecProtocol/aztec-packages/tree/master/yarn-project/aztec-rpc) must have access to the `owner`s decryption keys in order to decrypt their notes. -```rust - unconstrained fn balance_of_private( - owner: AztecAddress, - ) -> Field { - let storage = Storage::init(Context::none()); - let owner_balance = storage.balances.at(owner.address); - - balance_utils::get_balance(owner_balance) - } -``` +#include_code balance_of_private /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `balance_of_public` A getter function for checking the public balance of the provided Aztec account. -```rust - unconstrained fn balance_of_public( - owner: AztecAddress, - ) -> Field { - let storage = Storage::init(Context::none()); - storage.public_balances.at(owner.address).read() - } -``` +#include_code balance_of_public /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust #### `compute_note_hash_and_nullifier` @@ -880,19 +482,7 @@ A getter function to compute the note hash and nullifier for notes in the contra This must be included in every contract because it depends on the storage slots, which are defined when we set up storage. -```rust - // Computes note hash and nullifier. - // Note 1: Needs to be defined by every contract producing logs. - // Note 2: Having it in all the contracts gives us the ability to compute the note hash and nullifier differently for different kind of notes. - unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, preimage: [Field; VALUE_NOTE_LEN]) -> [Field; 4] { - let note_header = NoteHeader { contract_address, nonce, storage_slot }; - if (storage_slot == 5) { - note_utils::compute_note_hash_and_nullifier(TransparentNoteMethods, note_header, preimage) - } else { - note_utils::compute_note_hash_and_nullifier(ValueNoteMethods, note_header, preimage) - } - } -``` +#include_code compute_note_hash_and_nullifier /yarn-project/noir-contracts/src/contracts/token_contract/src/main.nr rust ## Compiling 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 9fc38917054..5bdd2bfbb68 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 @@ -1,4 +1,5 @@ // docs:start:token_all +// docs:start:imports mod types; mod util; @@ -38,7 +39,9 @@ contract Token { use crate::types::{TransparentNote, TransparentNoteMethods, TRANSPARENT_NOTE_LEN}; use crate::util::{compute_message_hash}; + // docs:end::imports + // docs:start:storage_struct struct Storage { admin: PublicState, minters: Map>, @@ -47,7 +50,9 @@ contract Token { pending_shields: Set, public_balances: Map>, } + // docs:end:storage_struct + // docs:start:storage_init impl Storage { fn init(context: Context) -> pub Self { Storage { @@ -94,14 +99,18 @@ contract Token { } } } + // docs:end:storage_init + // docs:start:constructor #[aztec(private)] fn constructor() { // Currently not possible to execute public calls from constructor as code not yet available to sequencer. // let selector = compute_selector("_initialize((Field))"); // let _callStackItem = context.call_public_function(context.this_address(), selector, [context.msg_sender()]); } + // docs:end:constructor + // docs:start:set_admin #[aztec(public)] fn set_admin( new_admin: AztecAddress, @@ -110,8 +119,9 @@ contract Token { assert(storage.admin.read() == context.msg_sender(), "caller is not admin"); storage.admin.write(new_admin.address); } + // docs:end:set_admin - // docs:start:add_minter + // docs:start:set_minter #[aztec(public)] fn set_minter( minter: AztecAddress, @@ -121,8 +131,9 @@ 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 + // docs:end:set_minter + // docs:start:mint_public #[aztec(public)] fn mint_public( to: AztecAddress, @@ -138,7 +149,9 @@ contract Token { storage.total_supply.write(supply.value as Field); 1 } + // docs:end:mint_public + // docs:start:mint_private #[aztec(public)] fn mint_private( amount: Field, @@ -154,6 +167,7 @@ contract Token { pending_shields.insert_from_public(&mut note); 1 } + // docs:end:mint_private // docs:start:shield #[aztec(public)] @@ -186,7 +200,7 @@ contract Token { } // docs:end:shield - + // docs:start:transfer_public #[aztec(public)] fn transfer_public( from: AztecAddress, @@ -213,7 +227,9 @@ contract Token { 1 } + // docs:end:transfer_public + // docs:start:burn_public #[aztec(public)] fn burn_public( from: AztecAddress, @@ -239,6 +255,7 @@ contract Token { 1 } + // docs:end:burn_public // docs:start:redeem_shield #[aztec(private)] @@ -314,7 +331,8 @@ contract Token { 1 } // docs:end:transfer - + + // docs:start:burn #[aztec(private)] fn burn( from: AztecAddress, @@ -340,9 +358,11 @@ contract Token { 1 } + // docs:end:burn /// SHOULD BE Internal /// + // docs:start:initialize // We cannot do this from the constructor currently // Since this should be internal, for now, we ignore the safety checks of it, as they are // enforced by it being internal and only called from the constructor. @@ -354,6 +374,7 @@ contract Token { storage.admin.write(new_admin.address); storage.minters.at(new_admin.address).write(1); } + // docs:end:initialize /// Internal /// @@ -369,6 +390,7 @@ contract Token { } // docs:end:increase_public_balance + // docs:start:reduce_total_supply #[aztec(public)] internal fn _reduce_total_supply( amount: Field, @@ -378,25 +400,32 @@ contract Token { let new_supply = SafeU120::new(storage.total_supply.read()).sub(SafeU120::new(amount)); storage.total_supply.write(new_supply.value as Field); } + // docs:end:reduce_total_supply /// Unconstrained /// + // docs:start:admin unconstrained fn admin() -> Field { let storage = Storage::init(Context::none()); storage.admin.read() } + // docs:end:admin + // docs:start:is_minter unconstrained fn is_minter( minter: AztecAddress, ) -> bool { let storage = Storage::init(Context::none()); storage.minters.at(minter.address).read() as bool } + // docs:end:is_minter + // docs:start:total_supply unconstrained fn total_supply() -> Field { let storage = Storage::init(Context::none()); storage.total_supply.read() } + // docs:end:total_supply // docs:start:balance_of_private unconstrained fn balance_of_private( @@ -409,12 +438,14 @@ contract Token { } // docs:end:balance_of_private + // docs:start:balance_of_public unconstrained fn balance_of_public( owner: AztecAddress, ) -> Field { let storage = Storage::init(Context::none()); storage.public_balances.at(owner.address).read() } + // docs:end:balance_of_public // Below this point is the stuff of nightmares. // This should ideally not be required. What do we do if vastly different types of preimages?