diff --git a/docs/docs/guides/smart_contracts/testing_contracts/_category_.json b/docs/docs/guides/smart_contracts/testing_contracts/_category_.json new file mode 100644 index 000000000000..ef2f92a9a61b --- /dev/null +++ b/docs/docs/guides/smart_contracts/testing_contracts/_category_.json @@ -0,0 +1,6 @@ +{ + "position": 1, + "collapsible": true, + "collapsed": true, + "label": "Testing Contracts" +} diff --git a/docs/docs/guides/smart_contracts/testing_contracts/index.md b/docs/docs/guides/smart_contracts/testing_contracts/index.md new file mode 100644 index 000000000000..97182e0925be --- /dev/null +++ b/docs/docs/guides/smart_contracts/testing_contracts/index.md @@ -0,0 +1,190 @@ +--- +title: Testing Contracts +--- + +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). + +## Pure Noir tests + +Noir supports the `#[test]` annotation which can be used to write simple logic tests on isolated utility functions. These tests only make assertions on algorithms and cannot interact with protocol-specific constructs such as `storage` or `context`, but are extremely fast and can be useful in certain scenarios. + +#include_code pure_noir_testing /noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr rust + +To learn more about Noir testing, please refer to [the Noir docs](https://Noir-lang.org/docs/tooling/testing/). + +## TXE (pronounced "trixie") + +In order to interact with the protocol, Aztec contracts leverage the power of oracles: functions that reach out to the outside world and are able to query and manipulate data outside of itself. The values returned by oracles are then constrained inside Noir and modifications to the blockchain state are later verified to adhere to the protocol rules by our kernel circuits. + +However, all of this is often not necessary to ensure the contract logic itself is sound. All that we need is an entity to provide values consistent with real execution. This is where our TXE (Testing eXecution Environment, pronounced "trixie") comes in! + +TXE is a JSON RPC server much like PXE, but provides an extra set of oracle functions called `cheatcodes` that allow developers to manipulate the state of the chain and simulate contract execution. Since TXE skips most of the checks, block building and other intricacies of the Aztec protocol, it is much faster to run than simulating everything in the sandbox. + +## 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). + +The TXE is a super fast framework in Noir to quickly test your smart contract code. + +So to summarize: +* End-to-end tests are written in Typescript. TXE in Noir. +* End-to-end tests are most similar to using mocha + ethers.js to test Solidity Contracts. TXE is like foundry (fast tests in solidity) + +### Running TXE + +In order to use the TXE, it must be running on a known address. By default, TXE runs at `http://localhost:8080`. So you can run contracts tests with: + +`aztec-nargo test --oracle-resolver http://localhost:8080` + +:::warning +Since TXE tests are written in Noir and executed with `aztec-nargo`, they all run in parallel. This also means every test creates their own isolated environment, so state modifications are local to each one of them. + +Executing many tests in parallel might slow processing of the RPC calls down to the point of making them timeout. To control this timeout the `NARGO_FOREIGN_CALL_TIMEOUT` env variable is used. +::: + +### Writing TXE tests + +`aztec-nr` provides an utility class called `TestEnvironment`, that should take care of the most common operations needed to setup contract testing. Setting up a new test environment with `TestEnvironment::new()` **will reset the current test's TXE state**. + +:::tip +You can find all of the methods available in the `TestEnvironment` [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr). +::: + +#include_code txe_test_increment /noir-projects/noir-contracts/contracts/counter_contract/src/main.nr rust + +:::warning +Tests run significantly faster as `unconstrained` functions. This means we generate bytecode (Brillig) and not circuits (ACIR), which _should_ yield exactly the same results. Any other behavior is considered a bug. +::: + +### Imports + +Writing tests in contracts requires importing additional modules from Aztec.nr. Here are the modules that are needed for testing the increment function in the counter contract. + +#include_code test_imports /noir-projects/noir-contracts/contracts/counter_contract/src/main.nr rust + +### Deploying contracts + +```rust +let deployer = env.deploy("path_to_contract_ts_interface"); + +// Now one of these can be called, depending on the contract and their possible initialization options. +// Remember a contract can only be initialized once. + +let my_private_initializer_call_interface = MyContract::interface().private_constructor(...); +let my_contract_instance = deployer.with_private_initializer(my_private_initializer_call_interface); + +// or + +let my_public_initializer_call_interface = MyContract::interface().public_constructor(...); +let my_contract_instance = deployer.with_public_initializer(my_public_initializer_call_interface); + +// or + +let my_contract_instance = deployer.without_initializer(); +``` + +:::warning +At the moment, TXE uses the generated contract TypeScript interfaces to perform deployments, and they must be provided as either an absolute path, a relative path to TXE's location or a module in an npm direct dependency such as `@aztec/noir-contracts.js`. It is not always necessary to deploy a contract in order to test it, but sometimes it's inevitable (when testing functions that depend on the contract being initialized, or contracts that call others for example) **It is important to keep them up to date**, as TXE cannot recompile them on changes. This will be improved in the future. +::: + +### Calling functions + +Our test environment is capable of utilizing the autogenerated contract interfaces to abstract calls, but without going through the usual external call flow (meaning much faster execution). + +#### Private + +For example, to call the private `transfer` function on the token contract: + +#include_code txe_test_transfer_private /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr rust + +#### Public + +To call the public `transfer_public` function: + +#include_code call_public /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr rust + +#### Unconstrained + +Unconstrained functions can be directly called from the contract interface. Notice that we need to set the contract address to the specific token contract that we are calling before making the call. This is to ensure that `view_notes` works properly. + +#include_code txe_test_call_unconstrained /noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr rust + +### Creating accounts + +The test environment provides two different ways of creating accounts, depending on the testing needs. For most cases, it is only necessary to obtain a valid `AztecAddress` that represents the user's account contract. For this, is is enough to do: + +```rust +let mocked_account_address = env.create_account(); +``` + +These accounts also create the necessary keys to ensure notes can be created/nullified, etc. + +For more advanced flows, such as authwits, it is necessary to create a real `AccountContract`, with valid signing keys that gets actually deployed to TXE. For that you can use: + +```rust +let real_account_address = env.create_account_contract(secret); +``` + +Besides deploying a complete `SchnorrAccountContract`, key derivation is performed so that authwits can be signed. It is slightly slower than the mocked version. + +Once accounts have been created, you can impersonate them in your test by calling: + +```rust +env.impersonate(account_address); +``` + +### Checking state + +It is possible to use the regular oracles in tests in order to retrieve public and private state and make assertions about them. + +:::warning +Remember to switch to the current contract's address in order to be able to read it's siloed state! +::: + +Reading public state: +#include_code txe_test_read_public /noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr rust + +Reading notes: +#include_code txe_test_read_notes /noir-projects/noir-contracts/contracts/counter_contract/src/main.nr rust + +### Authwits + +#### Private + +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_private.nr rust + +#### Public + +#include_code public_authwit /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr rust + +### Storing notes in cache + +Sometimes we have to tell TXE about notes that are not generated by ourselves, but someone else. This allows us to check if we are able to decrypt them: + +#include_code txe_test_store_note /noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr rust + +### Time traveling + +TXE can force the generation of "new blocks" very quickly using: + +```rust +env.advance_block_by(n_blocks); +``` + +This will effectively consolidate state transitions into TXE's internal trees, allowing things such as reading "historical state" from private, generating inclusion proofs, etc. + +### Failing cases + +You can test functions that you expect to fail generically, with the `#[test(should_fail)]` annotation, or that it should fail with a specific message with `#[test(should_fail_with = "Failure message")]`. + +For example: + +#include_code fail_with_message /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr rust + +You can also use the `assert_public_call_fails` or `assert_private_call_fails` methods on the `TestEnvironment` to check that a call fails. + +#include_code assert_public_fail /noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr rust diff --git a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr index e045ca4b8bc5..eb087b7436cb 100644 --- a/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr +++ b/noir-projects/aztec-nr/aztec/src/test/helpers/test_environment.nr @@ -34,6 +34,10 @@ impl TestEnvironment { cheatcodes::get_contract_address() } + fn impersonate(self, address: AztecAddress) { + cheatcodes::set_contract_address(address) + } + fn advance_block_to(&mut self, block_number: u32) { let difference = block_number - cheatcodes::get_block_number(); self.advance_block_by(difference); diff --git a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr index a7c265c45281..f762cf86db6d 100644 --- a/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr +++ b/noir-projects/noir-contracts/contracts/card_game_contract/src/cards.nr @@ -38,12 +38,14 @@ impl Serialize<2> for Card { } } +// docs:start:pure_noir_testing #[test] fn test_to_from_field() { let field = 1234567890; let card = Card::from_field(field); assert(card.to_field() == field); } +// docs:end:pure_noir_testing struct CardNote { card: Card, diff --git a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr index 27631ddbe726..011ebdd5d087 100644 --- a/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/counter_contract/src/main.nr @@ -38,11 +38,14 @@ contract Counter { } // docs:end:get_counter + // docs:start:test_imports use dep::aztec::test::{helpers::{cheatcodes, test_environment::TestEnvironment}}; use dep::aztec::protocol_types::storage::map::derive_storage_slot_in_map; use dep::aztec::note::note_getter::{MAX_NOTES_PER_PAGE, view_notes}; use dep::aztec::note::note_viewer_options::NoteViewerOptions; + // docs:end:test_imports + // docs:start:txe_test_increment #[test] fn test_increment() { // Setup env, generate keys @@ -50,16 +53,16 @@ contract Counter { let owner = env.create_account(); let outgoing_viewer = env.create_account(); let initial_value: Field = 5; - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Deploy contract and initialize let initializer = Counter::interface().initialize(initial_value as u64, owner, outgoing_viewer); let counter_contract = env.deploy("@aztec/noir-contracts.js/Counter").with_private_initializer(initializer); let contract_address = counter_contract.to_address(); + // docs:start:txe_test_read_notes // Read the stored value in the note - - cheatcodes::set_contract_address(contract_address); + env.impersonate(contract_address); let counter_slot = Counter::storage().counters.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); @@ -68,14 +71,17 @@ contract Counter { assert( initial_note_value == initial_value, f"Expected {initial_value} but got {initial_note_value}" ); + // docs:end:txe_test_read_notes // Increment the counter let increment_call_interface = Counter::at(contract_address).increment(owner, outgoing_viewer); env.call_private_void(increment_call_interface); + // get_counter is an unconstrained function, so we call it directly (we're in the same module) let current_value_for_owner = get_counter(owner); let expected_current_value = initial_value + 1; assert( expected_current_value == current_value_for_owner, f"Expected {expected_current_value} but got {current_value_for_owner}" ); } + // docs:end:txe_test_increment } diff --git a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr index efeae7bcda8f..2747bfed0043 100644 --- a/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/parent_contract/src/main.nr @@ -271,7 +271,7 @@ contract Parent { let result: Field = env.call_private(parent_private_set_call_interface); assert(result == value_to_set); // Read the stored value in the note. We have to change the contract address to the child contract in order to read its notes - cheatcodes::set_contract_address(child_contract_address); + env.impersonate(child_contract_address); let counter_slot = Child::storage().a_map_with_private_values.slot; let owner_slot = derive_storage_slot_in_map(counter_slot, owner); let mut options = NoteViewerOptions::new(); diff --git a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/basic.nr b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/basic.nr index 97f49cdb5ca1..dc593bf10852 100644 --- a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/basic.nr +++ b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/basic.nr @@ -17,7 +17,7 @@ unconstrained fn transfer_success() { authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, transfer_private_from_call_interface); - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_private_void(transfer_private_from_call_interface); // Check balances @@ -55,7 +55,7 @@ unconstrained fn setup_refund_success() { authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, setup_refund_from_call_interface); - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.call_private_void(setup_refund_from_call_interface); let mut context = env.private(); diff --git a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr index ab6a658ecc90..fb660d6ef45e 100644 --- a/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/private_token_contract/src/test/utils.nr @@ -24,7 +24,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres }; // Start the test in the account contract address - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Deploy token contract let initializer_call_interface = PrivateToken::interface().constructor( diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr index 37a84e09a7b6..588cc71d9a42 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/access_control.nr @@ -17,7 +17,7 @@ unconstrained fn access_control() { assert(admin == recipient.to_field()); // Impersonate new admin - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Check new admin is not a minter let is_minter_call_interface = Token::at(token_contract_address).is_minter(recipient); @@ -40,7 +40,7 @@ unconstrained fn access_control() { assert(is_minter == false); // Impersonate original admin - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Try to set ourselves as admin, fail miserably let set_admin_call_interface = Token::at(token_contract_address).set_admin(recipient); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr index af0e6cb3c31b..a0a2c08a492d 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/burn.nr @@ -23,7 +23,7 @@ unconstrained fn burn_public_on_behalf_of_other() { let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Burn tokens env.call_public(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount - burn_amount); @@ -59,7 +59,7 @@ unconstrained fn burn_public_failure_on_behalf_of_other_without_approval() { let burn_amount = mint_amount / 10; let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.assert_public_call_fails(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount); @@ -67,7 +67,7 @@ unconstrained fn burn_public_failure_on_behalf_of_other_without_approval() { let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.assert_public_call_fails(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount); } @@ -81,7 +81,7 @@ unconstrained fn burn_public_failure_on_behalf_of_other_wrong_caller() { let burn_call_interface = Token::at(token_contract_address).burn_public(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.assert_public_call_fails(burn_call_interface); utils::check_public_balance(token_contract_address, owner, mint_amount); } @@ -106,7 +106,7 @@ unconstrained fn burn_private_on_behalf_of_other() { let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Burn tokens env.call_private_void(burn_call_interface); utils::check_private_balance(token_contract_address, owner, mint_amount - burn_amount); @@ -144,7 +144,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_more_than_balance() { let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } @@ -158,7 +158,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_without_approval() { // Burn on behalf of other let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } @@ -173,7 +173,7 @@ unconstrained fn burn_private_failure_on_behalf_of_other_wrong_designated_caller let burn_call_interface = Token::at(token_contract_address).burn(owner, burn_amount, unsafe_rand()); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, burn_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); env.call_private_void(burn_call_interface); // Private doesnt revert, so we cannot check balances here since notes have already been nullified. Test is done. } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index 4e92489a59ad..3888722febde 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -26,13 +26,13 @@ unconstrained fn mint_public_failures() { // As non-minter let mint_amount = 10000; - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); let mint_public_call_interface = Token::at(token_contract_address).mint_public(owner, mint_amount); env.assert_public_call_fails(mint_public_call_interface); utils::check_public_balance(token_contract_address, owner, 0); - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Overflow recipient @@ -129,7 +129,7 @@ unconstrained fn mint_private_failure_non_minter() { let (env, token_contract_address, _, recipient) = utils::setup(/* with_account_contracts */ false); let mint_amount = 10000; // Try to mint some tokens impersonating recipient - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); let secret = unsafe_rand(); let secret_hash = compute_secret_hash(secret); @@ -217,19 +217,19 @@ unconstrained fn mint_private_failure_overflow_total_supply() { ); // Redeem owner's shielded tokens - cheatcodes::set_contract_address(owner); + env.impersonate(owner); let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret_owner); env.call_private_void(redeem_shield_call_interface); // Redeem recipient's shielded tokens - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret_recipient); env.call_private_void(redeem_shield_call_interface); utils::check_private_balance(token_contract_address, owner, mint_amount); utils::check_private_balance(token_contract_address, recipient, mint_amount); - cheatcodes::set_contract_address(owner); + env.impersonate(owner); let mint_amount = 2.pow_32(128) - 2 * mint_amount; // Try to mint some tokens let secret = unsafe_rand(); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr index 66280304481a..a1f9212d8667 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr @@ -41,12 +41,12 @@ unconstrained fn shielding_on_behalf_of_other() { let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, shield_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Shield tokens env.call_public(shield_call_interface); // Become owner again - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Store a note in the cache so we can redeem it env.store_note_in_cache( &mut TransparentNote::new(shield_amount, secret_hash), @@ -106,7 +106,7 @@ unconstrained fn shielding_failure_on_behalf_of_other_more_than_balance() { let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, shield_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Shield tokens env.assert_public_call_fails(shield_call_interface); @@ -126,7 +126,7 @@ unconstrained fn shielding_failure_on_behalf_of_other_wrong_caller() { let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, shield_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Shield tokens env.assert_public_call_fails(shield_call_interface); @@ -145,7 +145,7 @@ unconstrained fn shielding_failure_on_behalf_of_other_without_approval() { let shield_amount = mint_amount + 1; let shield_call_interface = Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Shield tokens env.assert_public_call_fails(shield_call_interface); diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr index 47e048091144..79ab994aeb50 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_private.nr @@ -7,10 +7,13 @@ use crate::Token; unconstrained fn transfer_private() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); + + // docs:start:txe_test_transfer_private // Transfer tokens let transfer_amount = 1000; let transfer_private_call_interface = Token::at(token_contract_address).transfer(recipient, transfer_amount); env.call_private_void(transfer_private_call_interface); + // docs:end:txe_test_transfer_private // Check balances utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); @@ -50,13 +53,15 @@ unconstrained fn transfer_private_on_behalf_of_other() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); // Add authwit + // docs:start:private_authwit let transfer_amount = 1000; let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, transfer_private_from_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_private_void(transfer_private_from_call_interface); + // docs:end:private_authwit // Check balances utils::check_private_balance(token_contract_address, owner, mint_amount - transfer_amount); utils::check_private_balance(token_contract_address, recipient, transfer_amount); @@ -72,6 +77,7 @@ unconstrained fn transfer_private_failure_more_than_balance() { env.call_private_void(transfer_private_call_interface); } +// docs:start:fail_with_message #[test(should_fail_with="invalid nonce")] unconstrained fn transfer_private_failure_on_behalf_of_self_non_zero_nonce() { // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. @@ -82,6 +88,7 @@ unconstrained fn transfer_private_failure_on_behalf_of_self_non_zero_nonce() { // Transfer tokens env.call_private_void(transfer_private_from_call_interface); } +// docs:end:fail_with_message #[test(should_fail_with="Balance too low")] unconstrained fn transfer_private_failure_on_behalf_of_more_than_balance() { @@ -92,7 +99,7 @@ unconstrained fn transfer_private_failure_on_behalf_of_more_than_balance() { let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, transfer_private_from_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_private_void(transfer_private_from_call_interface); } @@ -105,7 +112,7 @@ unconstrained fn transfer_private_failure_on_behalf_of_other_without_approval() let transfer_amount = 1000; let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_private_void(transfer_private_from_call_interface); // Check balances @@ -122,7 +129,7 @@ unconstrained fn transfer_private_failure_on_behalf_of_other_wrong_caller() { let transfer_private_from_call_interface = Token::at(token_contract_address).transfer_from(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, transfer_private_from_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_private_void(transfer_private_from_call_interface); // Check balances diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr index ae0b631ce374..aae666f6bd3d 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_public.nr @@ -23,8 +23,10 @@ unconstrained fn public_transfer_to_self() { let (env, token_contract_address, owner, _, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens let transfer_amount = mint_amount / 10; + // docs:start:call_public let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, owner, transfer_amount, 0); env.call_public(public_transfer_call_interface); + // docs:end:call_public // Check balances utils::check_public_balance(token_contract_address, owner, mint_amount); @@ -38,7 +40,7 @@ unconstrained fn public_transfer_on_behalf_of_other() { let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Transfer tokens env.call_public(public_transfer_from_call_interface); // Check balances @@ -52,10 +54,11 @@ unconstrained fn public_transfer_failure_more_than_balance() { let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ false); // Transfer tokens let transfer_amount = mint_amount + 1; + // docs:start:assert_public_fail let public_transfer_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 0); // Try to transfer tokens env.assert_public_call_fails(public_transfer_call_interface); - + // docs:end:assert_public_fail // Check balances utils::check_public_balance(token_contract_address, owner, mint_amount); } @@ -81,7 +84,7 @@ unconstrained fn public_transfer_failure_on_behalf_of_other_without_approval() { let transfer_amount = mint_amount / 10; let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Try to transfer tokens env.assert_public_call_fails(public_transfer_from_call_interface); // Check balances @@ -94,10 +97,12 @@ unconstrained fn public_transfer_failure_on_behalf_of_other_more_than_balance() // Setup with account contracts. Slower since we actually deploy them, but needed for authwits. let (env, token_contract_address, owner, recipient, mint_amount) = utils::setup_and_mint(/* with_account_contracts */ true); let transfer_amount = mint_amount + 1; + // docs:start:public_authwit let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, recipient, public_transfer_from_call_interface); + // docs:end:public_authwit // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Try to transfer tokens env.assert_public_call_fails(public_transfer_from_call_interface); // Check balances @@ -113,7 +118,7 @@ unconstrained fn public_transfer_failure_on_behalf_of_other_wrong_caller() { let public_transfer_from_call_interface = Token::at(token_contract_address).transfer_public(owner, recipient, transfer_amount, 1); authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, public_transfer_from_call_interface); // Impersonate recipient to perform the call - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Try to transfer tokens env.assert_public_call_fails(public_transfer_from_call_interface); // Check balances diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr index 52987cb17367..d127046d382f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/unshielding.nr @@ -23,7 +23,7 @@ unconstrained fn unshield_on_behalf_of_other() { let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, unshield_call_interface); // Impersonate recipient - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Unshield tokens env.call_private_void(unshield_call_interface); utils::check_private_balance(token_contract_address, owner, mint_amount - unshield_amount); @@ -58,7 +58,7 @@ unconstrained fn unshield_failure_on_behalf_of_other_more_than_balance() { let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, recipient, unshield_call_interface); // Impersonate recipient - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Unshield tokens env.call_private_void(unshield_call_interface); } @@ -71,7 +71,7 @@ unconstrained fn unshield_failure_on_behalf_of_other_invalid_designated_caller() let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); authwit_cheatcodes::add_private_authwit_from_call_interface(owner, owner, unshield_call_interface); // Impersonate recipient - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Unshield tokens env.call_private_void(unshield_call_interface); } @@ -83,7 +83,7 @@ unconstrained fn unshield_failure_on_behalf_of_other_no_approval() { let unshield_amount = mint_amount + 1; let unshield_call_interface = Token::at(token_contract_address).unshield(owner, recipient, unshield_amount, 0); // Impersonate recipient - cheatcodes::set_contract_address(recipient); + env.impersonate(recipient); // Unshield tokens env.call_private_void(unshield_call_interface); } diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr index 5431b94bc108..c82d4246ceb7 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/utils.nr @@ -24,7 +24,7 @@ pub fn setup(with_account_contracts: bool) -> (&mut TestEnvironment, AztecAddres }; // Start the test in the account contract address - cheatcodes::set_contract_address(owner); + env.impersonate(owner); // Deploy token contract let initializer_call_interface = Token::interface().constructor( @@ -55,12 +55,14 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az // Time travel so we can read keys from the registry env.advance_block_by(6); + // docs:start:txe_test_store_note // Store a note in the cache so we can redeem it env.store_note_in_cache( &mut TransparentNote::new(mint_amount, secret_hash), Token::storage().pending_shields.slot, token_contract_address ); + // docs:end:txe_test_store_note // Redeem our shielded tokens let redeem_shield_call_interface = Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret); @@ -69,6 +71,7 @@ pub fn setup_and_mint(with_account_contracts: bool) -> (&mut TestEnvironment, Az (env, token_contract_address, owner, recipient, mint_amount) } +// docs:start:txe_test_read_public pub fn check_public_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { let current_contract_address = cheatcodes::get_contract_address(); cheatcodes::set_contract_address(token_contract_address); @@ -80,11 +83,16 @@ pub fn check_public_balance(token_contract_address: AztecAddress, address: Aztec assert(amount.to_field() == address_amount, "Public balance is not correct"); cheatcodes::set_contract_address(current_contract_address); } +// docs:end:txe_test_read_public +// docs:start:txe_test_call_unconstrained pub fn check_private_balance(token_contract_address: AztecAddress, address: AztecAddress, address_amount: Field) { let current_contract_address = cheatcodes::get_contract_address(); cheatcodes::set_contract_address(token_contract_address); + // Direct call to unconstrained let balance_of_private = Token::balance_of_private(address); assert(balance_of_private == address_amount, "Private balance is not correct"); cheatcodes::set_contract_address(current_contract_address); } +// docs:end:txe_test_call_unconstrained +