|
| 1 | +--- |
| 2 | +title: Counter Contract |
| 3 | +description: Code-along tutorial for creating a simple counter contract on Aztec. |
| 4 | +sidebar_position: 0 |
| 5 | +references: ["docs/examples/contracts/counter_contract/src/main.nr"] |
| 6 | +--- |
| 7 | + |
| 8 | +import Image from "@theme/IdealImage"; |
| 9 | + |
| 10 | +In this guide, we will create our first Aztec.nr smart contract. We will build a simple private counter, where you can keep your own private counter - so no one knows what ID you are at or when you increment! This contract will get you started with the basic setup and syntax of Aztec.nr, but doesn't showcase all of the awesome stuff Aztec is capable of. |
| 11 | + |
| 12 | +This tutorial is compatible with the Aztec version `4.2.0-aztecnr-rc.2`. Install the correct version with `VERSION=4.2.0-aztecnr-rc.2 bash -i <(curl -sL https://install.aztec.network/4.2.0-aztecnr-rc.2)`. Or if you'd like to use a different version, you can find the relevant tutorial by clicking the version dropdown at the top of the page. |
| 13 | + |
| 14 | +## Prerequisites |
| 15 | + |
| 16 | +- You have followed the [quickstart](../../../getting_started_on_local_network.md) |
| 17 | +- Running Aztec local network |
| 18 | +- Installed [Noir LSP](../../aztec-nr/installation.md) (optional) |
| 19 | + |
| 20 | +## Set up a project |
| 21 | + |
| 22 | +Run this to create a new contract project: |
| 23 | + |
| 24 | +```bash |
| 25 | +aztec new counter |
| 26 | +``` |
| 27 | + |
| 28 | +Your structure should look like this: |
| 29 | + |
| 30 | +```tree |
| 31 | +. |
| 32 | +|-counter |
| 33 | +| |-src |
| 34 | +| | |-main.nr |
| 35 | +| |-Nargo.toml |
| 36 | +``` |
| 37 | + |
| 38 | +The file `main.nr` will soon turn into our smart contract! |
| 39 | + |
| 40 | +Add the following dependencies to `Nargo.toml` under the autogenerated content: |
| 41 | + |
| 42 | +```toml |
| 43 | +[dependencies] |
| 44 | +aztec = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.2.0-aztecnr-rc.2", directory="aztec" } |
| 45 | +balance_set = { git="https://github.com/AztecProtocol/aztec-nr/", tag="v4.2.0-aztecnr-rc.2", directory="balance-set" } |
| 46 | +``` |
| 47 | + |
| 48 | +## Define the functions |
| 49 | + |
| 50 | +Go to `main.nr`, and replace the boilerplate code with this contract initialization: |
| 51 | + |
| 52 | +```rust |
| 53 | +use aztec::macros::aztec; |
| 54 | + |
| 55 | +#[aztec] |
| 56 | +pub contract Counter { |
| 57 | +} |
| 58 | +``` |
| 59 | + |
| 60 | +This defines a contract called `Counter`. |
| 61 | + |
| 62 | +## Imports |
| 63 | + |
| 64 | +We need to define some imports. |
| 65 | + |
| 66 | +Write this inside your contract, ie inside these brackets: |
| 67 | + |
| 68 | +```rust |
| 69 | +pub contract Counter { |
| 70 | + // imports go here! |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +```rust title="imports" showLineNumbers |
| 75 | +use aztec::{ |
| 76 | + macros::{functions::{external, initializer}, storage::storage}, |
| 77 | + messages::message_delivery::MessageDelivery, |
| 78 | + oracle::logging::debug_log_format, |
| 79 | + protocol::{address::AztecAddress, traits::ToField}, |
| 80 | + state_vars::Owned, |
| 81 | +}; |
| 82 | +use balance_set::BalanceSet; |
| 83 | +``` |
| 84 | +> <sup><sub><a href="https://github.com/AztecProtocol/aztec-packages/blob/v4.2.0-aztecnr-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L7-L16" target="_blank" rel="noopener noreferrer">Source code: docs/examples/contracts/counter_contract/src/main.nr#L7-L16</a></sub></sup> |
| 85 | +
|
| 86 | + |
| 87 | +- `macros::{functions::{external, initializer}, storage::storage}` |
| 88 | + Imports the macros needed to define function types (`external`, `initializer`) and the `storage` macro for declaring contract storage structures. |
| 89 | + |
| 90 | +- `messages::message_delivery::MessageDelivery` |
| 91 | + Imports `MessageDelivery` for specifying how note delivery should be handled (e.g., constrained onchain delivery). |
| 92 | + |
| 93 | +- `oracle::debug_log::debug_log_format` |
| 94 | + Imports a debug logging utility for printing formatted messages during contract execution. |
| 95 | + |
| 96 | +- `protocol::{address::AztecAddress, traits::ToField}` |
| 97 | + Brings in `AztecAddress` (used to identify accounts/contracts) and traits for converting values to field elements, necessary for serialization and formatting inside Aztec. |
| 98 | + |
| 99 | +- `state_vars::Owned` |
| 100 | + Brings in `Owned`, a wrapper for state variables that have a single owner. |
| 101 | + |
| 102 | +- `use balance_set::BalanceSet` |
| 103 | + Imports `BalanceSet` from the `balance_set` dependency, which provides functionality for managing private balances (used for our counter). |
| 104 | + |
| 105 | +## Declare storage |
| 106 | + |
| 107 | +Add this below the imports. It declares the storage variables for our contract. We use an `Owned` state variable wrapping a `BalanceSet` to manage private balances for each owner. |
| 108 | + |
| 109 | +```rust title="storage_struct" showLineNumbers |
| 110 | +#[storage] |
| 111 | +struct Storage<Context> { |
| 112 | + counters: Owned<BalanceSet<Context>, Context>, |
| 113 | +} |
| 114 | +``` |
| 115 | +> <sup><sub><a href="https://github.com/AztecProtocol/aztec-packages/blob/v4.2.0-aztecnr-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L18-L23" target="_blank" rel="noopener noreferrer">Source code: docs/examples/contracts/counter_contract/src/main.nr#L18-L23</a></sub></sup> |
| 116 | +
|
| 117 | + |
| 118 | +## Keep the counter private |
| 119 | + |
| 120 | +Now we’ve got a mechanism for storing our private state, we can start using it to ensure the privacy of balances. |
| 121 | + |
| 122 | +Let’s create a constructor method to run on deployment that assigns an initial count to a specified owner. This function is called `initialize`, but behaves like a constructor. It is the `#[initializer]` decorator that specifies that this function behaves like a constructor. Write this: |
| 123 | + |
| 124 | +```rust title="constructor" showLineNumbers |
| 125 | +#[initializer] |
| 126 | +#[external("private")] |
| 127 | +// We can name our initializer anything we want as long as it's marked as aztec(initializer) |
| 128 | +fn initialize(headstart: u64, owner: AztecAddress) { |
| 129 | + self.storage.counters.at(owner).add(headstart as u128).deliver( |
| 130 | + MessageDelivery.ONCHAIN_CONSTRAINED, |
| 131 | + ); |
| 132 | +} |
| 133 | +``` |
| 134 | +> <sup><sub><a href="https://github.com/AztecProtocol/aztec-packages/blob/v4.2.0-aztecnr-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L25-L34" target="_blank" rel="noopener noreferrer">Source code: docs/examples/contracts/counter_contract/src/main.nr#L25-L34</a></sub></sup> |
| 135 | +
|
| 136 | + |
| 137 | +This function accesses the counters from storage. It adds the `headstart` value to the `owner`'s counter using `at().add()`, then calls `.deliver(MessageDelivery.ONCHAIN_CONSTRAINED)` to ensure the note is delivered onchain. |
| 138 | + |
| 139 | +We have annotated this and other functions with `#[external("private")]` which are ABI macros so the compiler understands it will handle private inputs. |
| 140 | + |
| 141 | +## Incrementing our counter |
| 142 | + |
| 143 | +Now let's implement an `increment` function to increase the counter. |
| 144 | + |
| 145 | +```rust title="increment" showLineNumbers |
| 146 | +#[external("private")] |
| 147 | +fn increment(owner: AztecAddress) { |
| 148 | + debug_log_format("Incrementing counter for owner {0}", [owner.to_field()]); |
| 149 | + self.storage.counters.at(owner).add(1).deliver(MessageDelivery.ONCHAIN_CONSTRAINED); |
| 150 | +} |
| 151 | +``` |
| 152 | +> <sup><sub><a href="https://github.com/AztecProtocol/aztec-packages/blob/v4.2.0-aztecnr-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L36-L42" target="_blank" rel="noopener noreferrer">Source code: docs/examples/contracts/counter_contract/src/main.nr#L36-L42</a></sub></sup> |
| 153 | +
|
| 154 | + |
| 155 | +The `increment` function works similarly to the `initialize` function. It logs a debug message, then adds 1 to the owner's counter and delivers the note onchain. |
| 156 | + |
| 157 | +## Getting a counter |
| 158 | + |
| 159 | +The last thing we need to implement is a function to retrieve a counter value. |
| 160 | + |
| 161 | +```rust title="get_counter" showLineNumbers |
| 162 | +#[external("utility")] |
| 163 | +unconstrained fn get_counter(owner: AztecAddress) -> pub u128 { |
| 164 | + self.storage.counters.at(owner).balance_of() |
| 165 | +} |
| 166 | +``` |
| 167 | +> <sup><sub><a href="https://github.com/AztecProtocol/aztec-packages/blob/v4.2.0-aztecnr-rc.2/docs/examples/contracts/counter_contract/src/main.nr#L44-L49" target="_blank" rel="noopener noreferrer">Source code: docs/examples/contracts/counter_contract/src/main.nr#L44-L49</a></sub></sup> |
| 168 | +
|
| 169 | + |
| 170 | +This is a `utility` function used to obtain the counter value outside of a transaction. We access the `owner`'s balance from the `counters` storage variable using `at(owner)`, then call `balance_of()` to retrieve the current count. This yields a private counter that only the owner can decrypt. |
| 171 | + |
| 172 | +## Compile |
| 173 | + |
| 174 | +Now we've written a simple Aztec.nr smart contract, we can compile it. |
| 175 | + |
| 176 | +### Compile the smart contract |
| 177 | + |
| 178 | +In the `./counter/` directory, run: |
| 179 | + |
| 180 | +```bash |
| 181 | +aztec compile |
| 182 | +``` |
| 183 | + |
| 184 | +This command compiles your Noir contract and creates a `target` folder with a `.json` artifact inside. |
| 185 | + |
| 186 | +After compiling, you can generate a TypeScript class using the `aztec codegen` command. |
| 187 | + |
| 188 | +In the same directory, run this: |
| 189 | + |
| 190 | +```bash |
| 191 | +aztec codegen -o src/artifacts target |
| 192 | +``` |
| 193 | + |
| 194 | +You can now use the artifact and/or the TS class in your Aztec.js! |
| 195 | + |
| 196 | +## Next Steps |
| 197 | + |
| 198 | +### Optional: Learn more about concepts mentioned here |
| 199 | + |
| 200 | +- [Functions and annotations like `#[external("private")]`](../../aztec-nr/framework-description/functions/function_transforms.md#private-functions) |
0 commit comments