From 8ea020944e41beb2ba618a867e422fc482531898 Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 17 Dec 2024 16:48:47 -0500 Subject: [PATCH 1/4] create page --- docs/docs/guides/developer_guides/smart_contracts/index.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 docs/docs/guides/developer_guides/smart_contracts/index.md diff --git a/docs/docs/guides/developer_guides/smart_contracts/index.md b/docs/docs/guides/developer_guides/smart_contracts/index.md new file mode 100644 index 00000000000..00528052195 --- /dev/null +++ b/docs/docs/guides/developer_guides/smart_contracts/index.md @@ -0,0 +1,6 @@ +--- +title: Aztec.nr +tags: [aztec.nr] +--- + +Aztec.nr is the smart contract development framework for Aztec. It is a set of utilities that help you write Noir programs to deploy on the Aztec network. From e9b8c31ffb4e22d8c2cf9fedc7ed5b5dd079f051 Mon Sep 17 00:00:00 2001 From: josh crites Date: Tue, 17 Dec 2024 20:49:25 -0500 Subject: [PATCH 2/4] edits --- .../smart_contracts/index.mdx | 32 +++++++++++++++++++ .../writing_contracts/call_functions.md | 16 ++++------ .../writing_contracts/how_to_emit_event.md | 2 +- .../writing_contracts/index.mdx | 18 +++++++++++ .../writing_contracts/notes/index.md | 4 +-- .../writing_contracts/storage/index.md | 1 + 6 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 docs/docs/guides/developer_guides/smart_contracts/index.mdx create mode 100644 docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx diff --git a/docs/docs/guides/developer_guides/smart_contracts/index.mdx b/docs/docs/guides/developer_guides/smart_contracts/index.mdx new file mode 100644 index 00000000000..9191233b57a --- /dev/null +++ b/docs/docs/guides/developer_guides/smart_contracts/index.mdx @@ -0,0 +1,32 @@ +--- +title: Aztec.nr +tags: [aztec.nr] +--- + +import DocCardList from "@theme/DocCardList"; + +Aztec.nr is the smart contract development framework for Aztec. It is a set of utilities that +help you write Noir programs to deploy on the Aztec network. + +## Development Workflow + +1. Write your contract and specify your contract dependencies. Every contract written for Aztec will have + aztec-nr as a dependency. Add it to your `Nargo.toml` with + +```toml +# Nargo.toml +[dependencies] +aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/aztec-nr/aztec" } +``` + +2. [Write your contracts](./writing_contracts/index.mdx). +3. [Profile](./profiling_transactions.md) the functions in your contract to get + a sense of how long generating client side proofs will take +4. Write function tests [using the TXE](./testing_contracts/testing.md) and end-to-end + tests [with typescript](../js_apps/test.md) +5. [Compile](how_to_compile_contract.md) your contract +6. [Deploy](how_to_deploy_contract.md) your contract + +## Section Contents + + diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md index 2df88f48025..64f2366c0e1 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md @@ -1,11 +1,10 @@ --- title: Calling Other Functions -sidebar_position: 2 +sidebar_position: 4 tags: [functions, contracts] --- - -A contract is a collection of persistent state variables and functions which may manipulate these variables. +A contract is a collection of persistent state variables and functions which may manipulate these variables. Functions and state variables within a contract's scope are said to belong to that contract. A contract can only access and modify its own state. @@ -19,13 +18,14 @@ A contract may be declared and given a name using the `contract` keyword (see sn // highlight-next-line contract MyContract { - // Imports + // Imports - // Storage + // Storage // Functions } ``` + :::info A note for vanilla Noir devs There is no [`main()` (GitHub link)](https://noir-lang.org/docs/getting_started/project_breakdown/#mainnr) function within a Noir `contract` scope. More than one function can be an entrypoint. ::: @@ -53,7 +53,7 @@ To call the function, you need to - Specify the address of the contract with `Contract::at(contract_address)` - Call the function name with `.function_name()` - Pass the parameters into the function call, like `.function_name(param1,param2)` -- Specify the type of call you want to make and pass a mut reference to the context, like `.call(&mut context)` +- Specify the type of call you want to make and pass a mut reference to the context, like `.call(&mut context)` #### Private calls @@ -77,6 +77,4 @@ Public functions are always executed after private execution. To learn why, read #### Other call types -There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../../aztec/glossary/call_types.md). - - +There are other call types, for example to ensure no state changes are made. You can learn more about them in the [call types glossary](../../../../aztec/glossary/call_types.md). diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md index 47440c4fd1c..88e95a21d1e 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/how_to_emit_event.md @@ -1,6 +1,6 @@ --- title: Emitting Events -sidebar_position: 3 +sidebar_position: 4 tags: [contracts] --- diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx new file mode 100644 index 00000000000..4d3ab99e673 --- /dev/null +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx @@ -0,0 +1,18 @@ +--- +title: Writing Contracts +tags: [aztec.nr] +--- + +import DocCardList from "@theme/DocCardList"; + +To write a contract: + +1. Name your contract +2. Define imports, start with XXX +3. Declare your contract storage +4. Declare a constructor with `#[initializer]` +5. Declare your contract functions + +## Section Contents + + diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/index.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/index.md index ec9f829577b..b92782a21a2 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/index.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/index.md @@ -1,7 +1,7 @@ --- title: Notes -sidebar_position: 6 +sidebar_position: 3 tags: [contracts, notes] --- -Notes are the fundamental data structure in Aztec when working with private state. In this section there are guides about how to work with `AddressNote`, `ValueNote`, and custom notes in Aztec.nr. You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/state_model/index.md#private-state). \ No newline at end of file +Notes are the fundamental data structure in Aztec when working with private state. In this section there are guides about how to work with `AddressNote`, `ValueNote`, and custom notes in Aztec.nr. You can learn more about notes in the [concepts section](../../../../../aztec/concepts/storage/state_model/index.md#private-state). diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/index.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/index.md index 60ceb2a374a..f895fd3e10f 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/index.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/storage/index.md @@ -1,5 +1,6 @@ --- title: Declaring Storage +sidebar_position: 2 tags: [contracts, storage] --- From 94285fed93a7436299e0c4c68374e6a75ddc3767 Mon Sep 17 00:00:00 2001 From: josh crites Date: Wed, 18 Dec 2024 16:43:09 -0500 Subject: [PATCH 3/4] edits --- .../how_to_compile_contract.md | 2 +- .../smart_contracts/how_to_deploy_contract.md | 4 +-- .../developer_guides/smart_contracts/index.md | 6 ---- .../smart_contracts/index.mdx | 13 ++++++-- .../smart_contracts/profiling_transactions.md | 4 +-- .../{testing_contracts => }/testing.md | 13 ++++---- .../testing_contracts/_category_.json | 6 ---- .../{call_functions.md => call_contracts.md} | 2 +- .../writing_contracts/index.mdx | 32 ++++++++++++++++--- .../writing_contracts/initializers.md | 2 +- 10 files changed, 51 insertions(+), 33 deletions(-) delete mode 100644 docs/docs/guides/developer_guides/smart_contracts/index.md rename docs/docs/guides/developer_guides/smart_contracts/{testing_contracts => }/testing.md (95%) delete mode 100644 docs/docs/guides/developer_guides/smart_contracts/testing_contracts/_category_.json rename docs/docs/guides/developer_guides/smart_contracts/writing_contracts/{call_functions.md => call_contracts.md} (98%) diff --git a/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md b/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md index 056290ad93b..df41a986f68 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md +++ b/docs/docs/guides/developer_guides/smart_contracts/how_to_compile_contract.md @@ -448,7 +448,7 @@ Read more about interacting with contracts using `aztec.js` [by following this t ### Aztec.nr interfaces -An Aztec.nr contract can [call a function](./writing_contracts/call_functions.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. +An Aztec.nr contract can [call a function](./writing_contracts/call_contracts.md) in another contract via `context.call_private_function` or `context.call_public_function`. However, this requires manually assembling the function selector and manually serializing the arguments, which is not type-safe. To make this easier, the compiler automatically generates interface structs that expose a convenience method for each function listed in a given contract artifact. These structs are intended to be used from another contract project that calls into the current one. diff --git a/docs/docs/guides/developer_guides/smart_contracts/how_to_deploy_contract.md b/docs/docs/guides/developer_guides/smart_contracts/how_to_deploy_contract.md index 634be0fa19d..c6f7e9e57b4 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/how_to_deploy_contract.md +++ b/docs/docs/guides/developer_guides/smart_contracts/how_to_deploy_contract.md @@ -4,10 +4,10 @@ sidebar_position: 4 tags: [contracts, sandbox] --- -# Deploying contracts - Once you have [compiled](./how_to_compile_contract.md) your contracts you can proceed to deploying them using aztec.js which is a Typescript client to interact with the sandbox. +You can use this method to deploy your contracts to the sandbox or to a remote network. + ## Prerequisites - `aztec-nargo` installed (go to [Sandbox section](../../../reference/developer_references/sandbox_reference/sandbox-reference.md) for installation instructions) diff --git a/docs/docs/guides/developer_guides/smart_contracts/index.md b/docs/docs/guides/developer_guides/smart_contracts/index.md deleted file mode 100644 index 00528052195..00000000000 --- a/docs/docs/guides/developer_guides/smart_contracts/index.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Aztec.nr -tags: [aztec.nr] ---- - -Aztec.nr is the smart contract development framework for Aztec. It is a set of utilities that help you write Noir programs to deploy on the Aztec network. diff --git a/docs/docs/guides/developer_guides/smart_contracts/index.mdx b/docs/docs/guides/developer_guides/smart_contracts/index.mdx index 9191233b57a..5c0bbd8e7e5 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/index.mdx +++ b/docs/docs/guides/developer_guides/smart_contracts/index.mdx @@ -8,7 +8,14 @@ import DocCardList from "@theme/DocCardList"; Aztec.nr is the smart contract development framework for Aztec. It is a set of utilities that help you write Noir programs to deploy on the Aztec network. -## Development Workflow +## Contract Development + +### Prerequisites + +- Install [Aztec Sandbox and tooling](../../getting_started.md) +- Install the [Noir LSP](../local_env/installing_noir_lsp.md) for your editor. + +### Flow 1. Write your contract and specify your contract dependencies. Every contract written for Aztec will have aztec-nr as a dependency. Add it to your `Nargo.toml` with @@ -20,9 +27,9 @@ aztec = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_ ``` 2. [Write your contracts](./writing_contracts/index.mdx). -3. [Profile](./profiling_transactions.md) the functions in your contract to get +3. [Profile](./profiling_transactions.md) the private functions in your contract to get a sense of how long generating client side proofs will take -4. Write function tests [using the TXE](./testing_contracts/testing.md) and end-to-end +4. Write unit tests [using the TXE](testing.md) and end-to-end tests [with typescript](../js_apps/test.md) 5. [Compile](how_to_compile_contract.md) your contract 6. [Deploy](how_to_deploy_contract.md) your contract diff --git a/docs/docs/guides/developer_guides/smart_contracts/profiling_transactions.md b/docs/docs/guides/developer_guides/smart_contracts/profiling_transactions.md index 40aa27888fa..2f66fb70d1b 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/profiling_transactions.md +++ b/docs/docs/guides/developer_guides/smart_contracts/profiling_transactions.md @@ -1,11 +1,9 @@ --- title: Profiling Transactions -sidebar_position: 5 +sidebar_position: 1 tags: [contracts, profiling] --- -# Profiling Transactions - An Aztec transaction typically consists of a private and a public part. The private part is where the user executes contract logic within the PXE and generates a proof of execution, which is then sent to the sequencer. Since proof generation is an expensive operation that needs to be done on the client side, it is important to optimize the private contract logic. It is desirable to keep the gate count of circuits representing the private contract logic as low as possible. diff --git a/docs/docs/guides/developer_guides/smart_contracts/testing_contracts/testing.md b/docs/docs/guides/developer_guides/smart_contracts/testing.md similarity index 95% rename from docs/docs/guides/developer_guides/smart_contracts/testing_contracts/testing.md rename to docs/docs/guides/developer_guides/smart_contracts/testing.md index 42950468940..0a5c256c3c8 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/testing_contracts/testing.md +++ b/docs/docs/guides/developer_guides/smart_contracts/testing.md @@ -1,13 +1,14 @@ --- -title: Testing Contracts in the TXE +title: Testing Contracts tags: [contracts, tests, testing, txe] keywords: [tests, testing, txe] +sidebar_position: 2 importance: 1 --- Aztec contracts can be tested in a variety of ways depending on the needs of a particular application and the complexity of the interactions they must support. -To test individual contract functions, you can use the Testing eXecution Environment (TXE) described below. For more complex interactions that require checking that the protocol rules are enforced, you should [write end-to-end tests using TypeScript](../../js_apps/test.md). +To test individual contract functions, you can use the Testing eXecution Environment (TXE) described below. For more complex interactions that require checking that the protocol rules are enforced, you should [write end-to-end tests using TypeScript](../js_apps/test.md). ## Pure Noir tests @@ -27,7 +28,7 @@ TXE is a JSON RPC server much like PXE, but provides an extra set of oracle func ## TXE vs End-to-end tests -End-to-end tests are written in typescripts and use compiled Aztec contracts and generated Typescript interfaces, a private execution environment (PXE) and a simulated execution environment to process transactions, create blocks and apply state updates. This allows for advanced checks on state updates like generation the of logs, cross-chain messages and checking transaction status and also enforce the rules of the protocol (e.g. checks in our rollup circuits). If you need the rules of the protocol to be enforced or require complex interactions (such as with L1 contracts), please refer to [Testing Aztec.nr contracts with Typescript](../../js_apps/test.md). +End-to-end tests are written in typescripts and use compiled Aztec contracts and generated Typescript interfaces, a private execution environment (PXE) and a simulated execution environment to process transactions, create blocks and apply state updates. This allows for advanced checks on state updates like generation the of logs, cross-chain messages and checking transaction status and also enforce the rules of the protocol (e.g. checks in our rollup circuits). If you need the rules of the protocol to be enforced or require complex interactions (such as with L1 contracts), please refer to [Testing Aztec.nr contracts with Typescript](../js_apps/test.md). The TXE is a super fast framework in Noir to quickly test your smart contract code. @@ -38,7 +39,7 @@ So to summarize: ### Running TXE -If you have [the sandbox](../../../getting_started.md) installed, you can run TXE tests using: +If you have [the sandbox](../../getting_started.md) installed, you can run TXE tests using: `aztec test` @@ -176,7 +177,7 @@ Reading notes: #### Private -You can add [authwits](../writing_contracts/authwit.md) to the TXE. Here is an example of testing a private token transfer using authwits: +You can add [authwits](writing_contracts/authwit.md) to the TXE. Here is an example of testing a private token transfer using authwits: #include_code private_authwit /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_in_private.nr rust @@ -214,7 +215,7 @@ You can also use the `assert_public_call_fails` or `assert_private_call_fails` m ### Logging -You can use `aztec.nr`'s oracles as usual for debug logging, as explained [here](../../../../reference/developer_references/debugging.md) +You can use `aztec.nr`'s oracles as usual for debug logging, as explained [here](../../../reference/developer_references/debugging.md) :::warning Remember to set the following environment variables to activate debug logging: diff --git a/docs/docs/guides/developer_guides/smart_contracts/testing_contracts/_category_.json b/docs/docs/guides/developer_guides/smart_contracts/testing_contracts/_category_.json deleted file mode 100644 index ef2f92a9a61..00000000000 --- a/docs/docs/guides/developer_guides/smart_contracts/testing_contracts/_category_.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "position": 1, - "collapsible": true, - "collapsed": true, - "label": "Testing Contracts" -} diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md similarity index 98% rename from docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md rename to docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md index 64f2366c0e1..3b1c9774f80 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_functions.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md @@ -1,5 +1,5 @@ --- -title: Calling Other Functions +title: Calling Other Contracts sidebar_position: 4 tags: [functions, contracts] --- diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx index 4d3ab99e673..5e2c20005da 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx @@ -5,14 +5,38 @@ tags: [aztec.nr] import DocCardList from "@theme/DocCardList"; +## Overview + To write a contract: -1. Name your contract -2. Define imports, start with XXX -3. Declare your contract storage -4. Declare a constructor with `#[initializer]` +1. Import aztec.nr and declare your contract + +```rust +#include_code declaration /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr raw + // ... +} +``` + +2. Define imports in your contract block + +#include_code imports /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust + +3. Declare your contract storage below your imports + +#include_code storage_struct /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust + +4. Declare a constructor with `#[initializer]`. Constructors can be private or public functions. + +#include_code constructor /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust + 5. Declare your contract functions +#include_code cast_vote /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr rust + +There is a lot more detail and nuance to writing contracts, but this should give you a good starting point. +Read contents of this section for more details about authorizing contract to act on your behalf (authenticaion witnesses), +emitting events, calling functions on other contracts and other common patterns. + ## Section Contents diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/initializers.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/initializers.md index 19b525524f7..37fffe46db7 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/initializers.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/initializers.md @@ -37,7 +37,7 @@ Initializers are commonly used to set an admin, such as this example: #include_code constructor /noir-projects/noir-contracts/contracts/token_contract/src/main.nr rust -Here, the initializer is writing to storage. It can also call another function. Learn more about calling functions from functions [here](./call_functions.md). +Here, the initializer is writing to storage. It can also call another function. Learn more about calling functions from functions [here](./call_contracts.md). ## Multiple initializers From fc9df9d66c907492583bb9e6006fa5b49048ec86 Mon Sep 17 00:00:00 2001 From: josh crites Date: Wed, 18 Dec 2024 16:49:02 -0500 Subject: [PATCH 4/4] edits --- .../writing_contracts/call_contracts.md | 26 +++---------------- .../writing_contracts/index.mdx | 7 ++++- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md index 3b1c9774f80..6a47645f5c0 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/call_contracts.md @@ -10,31 +10,11 @@ Functions and state variables within a contract's scope are said to belong to th If a contract wishes to access or modify another contract's state, it must make a call to an external function of the other contract. For anything to happen on the Aztec network, an external function of a contract needs to be called. -### Defining a contract - -A contract may be declared and given a name using the `contract` keyword (see snippet below). By convention, contracts are named in `PascalCase`. - -```rust title="contract keyword" -// highlight-next-line -contract MyContract { - - // Imports - - // Storage - - // Functions -} -``` - -:::info A note for vanilla Noir devs -There is no [`main()` (GitHub link)](https://noir-lang.org/docs/getting_started/project_breakdown/#mainnr) function within a Noir `contract` scope. More than one function can be an entrypoint. -::: - -### Add as a dependency in Nargo.toml +### Add Contract as a Dependency Import the contract that you want to call into your `Nargo.toml` under `dependencies` like this: -``` +```toml token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_aztec_version", directory="noir-projects/noir-contracts/contracts/token_contract" } ``` @@ -42,7 +22,7 @@ token = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#include_ At the top of your contract, import the contract you want to call like this: -``` +```rust use token::Token; ``` diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx index 5e2c20005da..a85da746eba 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/index.mdx @@ -13,7 +13,12 @@ To write a contract: ```rust #include_code declaration /noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr raw - // ... + + // Imports + + // Storage + + // Functions } ```