From 9b65b404dc67eac916ef9f9b242c0aedb6ef150b Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 28 Oct 2024 21:00:25 +0000 Subject: [PATCH 01/28] refactor: token shielding leveraging partial notes --- .../contracts/token_contract/src/main.nr | 123 +++++++++++++++++- 1 file changed, 121 insertions(+), 2 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 78d1336cbc2..8251a2f2f93 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -35,8 +35,8 @@ contract Token { }, oracle::random::random, prelude::{ - AztecAddress, FunctionSelector, Map, NoteGetterOptions, PrivateSet, PublicMutable, - SharedImmutable, + AztecAddress, FunctionSelector, Map, NoteGetterOptions, PrivateSet, PublicContext, + PublicMutable, SharedImmutable, }, protocol_types::{point::Point, traits::Serialize}, utils::comparison::Comparator, @@ -481,6 +481,125 @@ contract Token { Token::at(context.this_address())._reduce_total_supply(amount).enqueue(&mut context); } // docs:end:burn + + // Transfers token `amount` from public balance of message sender to a private balance of `to`. + #[private] + fn transfer_to_private(to: AztecAddress, amount: Field) { + let from = context.msg_sender(); + + let nft = Token::at(context.this_address()); + + // We prepare the transfer. + let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); + + // At last we finalize the transfer. Usafe of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that he can transfer only his own NFTs. + nft._finalize_transfer_to_private_unsafe(from, amount, hiding_point_slot).enqueue( + &mut context, + ); + } + + /// Prepares a transfer to a private balance of `to`. The transfer then needs to be + /// finalized by calling `finalize_transfer_to_private`. Returns a hiding point slot. + #[private] + fn prepare_transfer_to_private(to: AztecAddress) -> Field { + _prepare_transfer_to_private(to, &mut context, storage) + } + + /// This function exists separately from `prepare_transfer_to_private` solely as an optimization as it allows + /// us to have it inlined in the `transfer_to_private` function which results in one less kernel iteration. + /// + /// TODO(#9180): Consider adding macro support for functions callable both as an entrypoint and as an internal + /// function. + #[contract_library_method] + fn _prepare_transfer_to_private( + to: AztecAddress, + context: &mut PrivateContext, + storage: Storage<&mut PrivateContext>, + ) -> Field { + let to_keys = get_public_keys(to); + let to_npk_m_hash = to_keys.npk_m.hash(); + let to_note_slot = storage.balances.at(to).set.storage_slot; + + // We create a setup payload with unpopulated/zero `amount` for 'to' + // TODO(#7775): Manually fetching the randomness here is not great. If we decide to include randomness in all + // notes we could just inject it in macros. + let note_randomness = unsafe { random() }; + let note_setup_payload = + UintNote::setup_payload().new(to_npk_m_hash, note_randomness, to_note_slot); + + // We encrypt the note log + let setup_log = note_setup_payload.encrypt_log(context, to_keys, to); + + // Using the x-coordinate as a hiding point slot is safe against someone else interfering with it because + // we have a guarantee that the public functions of the transaction are executed right after the private ones + // and for this reason the protocol guarantees that nobody can front-run us in consuming the hiding point. + // This guarantee would break if `finalize_transfer_to_private` was not called in the same transaction. This + // however is not the flow we are currently concerned with. To support the multi-transaction flow we could + // introduce a `from` function argument, hash the x-coordinate with it and then repeat the hashing in + // `finalize_transfer_to_private`. + // + // We can also be sure that the `hiding_point_slot` will not overwrite any other value in the storage because + // in our state variables we derive slots using a different hash function from multi scalar multiplication + // (MSM). + let hiding_point_slot = note_setup_payload.hiding_point.x; + + // We don't need to perform a check that the value overwritten by `_store_point_in_transient_storage_unsafe` + // is zero because the slot is the x-coordinate of the hiding point and hence we could only overwrite + // the value in the slot with the same value. This makes usage of the `unsafe` method safe. + Token::at(context.this_address()) + ._store_payload_in_transient_storage_unsafe( + hiding_point_slot, + note_setup_payload.hiding_point, + setup_log, + ) + .enqueue(context); + + hiding_point_slot + } + + /// Finalizes a transfer of token `amount` from public balance of `from` to a private balance of `to`. + /// The transfer must be prepared by calling `prepare_transfer_to_private` first and the resulting + /// `hiding_point_slot` must be passed as an argument to this function. + #[public] + fn finalize_transfer_to_private(amount: Field, hiding_point_slot: Field) { + let from = context.msg_sender(); + _finalize_transfer_to_private(from, amount, hiding_point_slot, &mut context, storage); + } + + #[public] + #[internal] + fn _finalize_transfer_to_private_unsafe( + from: AztecAddress, + amount: Field, + hiding_point_slot: Field, + ) { + _finalize_transfer_to_private(from, amount, hiding_point_slot, &mut context, storage); + } + + #[contract_library_method] + fn _finalize_transfer_to_private( + from: AztecAddress, + amount: Field, + note_transient_storage_slot: Field, + context: &mut PublicContext, + storage: Storage<&mut PublicContext>, + ) { + // TODO(#8271): Type the amount as U128 and nuke the ugly cast + let amount = U128::from_integer(amount); + + // First we subtract the `amount` from the public balance of `from` + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + + // Then we finalize the partial note with the `amount` + let finalization_payload = + UintNote::finalization_payload().new(context, note_transient_storage_slot, amount); + + // At last we emit the note hash and the final log + finalization_payload.emit(); + } + /// We need to use different randomness for the user and for the fee payer notes because if the randomness values /// were the same we could fingerprint the user by doing the following: /// 1) randomness_influence = fee_payer_point - G_npk * fee_payer_npk = From 340b87f82d82ce0b5a9ba46e0f80c10d0b2d74d9 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 28 Oct 2024 21:06:25 +0000 Subject: [PATCH 02/28] WIP --- .../contracts/token_contract/src/test.nr | 1 + .../src/test/transfer_to_private.nr | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 7d23b6d81c0..63753d9eaa7 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -3,6 +3,7 @@ mod burn; mod utils; mod transfer_public; mod transfer_private; +// mod transfer_to_private; mod refunds; mod unshielding; mod minting; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr new file mode 100644 index 00000000000..21e109704a7 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr @@ -0,0 +1,109 @@ +use crate::{NFT, test::utils, types::nft_note::NFTNote}; +use dep::aztec::{ + keys::getters::get_public_keys, + oracle::random::random, + prelude::{AztecAddress, NoteHeader}, + protocol_types::storage::map::derive_storage_slot_in_map, +}; +use std::test::OracleMock; + +/// Internal orchestration means that the calls to `prepare_transfer_to_private` +/// and `finalize_transfer_to_private` are done by the NFT contract itself. +/// In this test's case this is done by the `NFT::transfer_to_private(...)` function called +/// in `utils::setup_mint_and_transfer_to_private`. +#[test] +unconstrained fn transfer_to_private_internal_orchestration() { + // The transfer to private is done in `utils::setup_mint_and_transfer_to_private` and for this reason + // in this test we just call it and check the outcome. + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, nft_contract_address, user, _, token_id) = + utils::setup_mint_and_transfer_to_private(/* with_account_contracts */ false); + + // User should have the note in their private nfts + utils::assert_owns_private_nft(nft_contract_address, user, token_id); + + // Since the NFT was sent to private, the public owner should be zero address + utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id); +} + +/// External orchestration means that the calls to prepare and finalize are not done by the NFT contract. This flow +/// will typically be used by a DEX. +#[test] +unconstrained fn transfer_to_private_external_orchestration() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, nft_contract_address, _, recipient, token_id) = + utils::setup_and_mint(/* with_account_contracts */ false); + + let note_randomness = random(); + + // We mock the Oracle to return the note randomness such that later on we can manually add the note + let _ = OracleMock::mock("getRandomField").returns(note_randomness); + + // We prepare the transfer + let hiding_point_slot: Field = NFT::at(nft_contract_address) + .prepare_transfer_to_private(recipient) + .call(&mut env.private()); + + // Finalize the transfer of the NFT (message sender owns the NFT in public) + NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + &mut env.public(), + ); + + // TODO(#8771): We need to manually add the note because in the partial notes flow `notify_created_note_oracle` + // is not called and we don't have a `NoteProcessor` in TXE. + let recipient_npk_m_hash = get_public_keys(recipient).npk_m.hash(); + let private_nfts_recipient_slot = + derive_storage_slot_in_map(NFT::storage_layout().private_nfts.slot, recipient); + + env.add_note( + &mut NFTNote { + token_id, + npk_m_hash: recipient_npk_m_hash, + randomness: note_randomness, + header: NoteHeader::empty(), + }, + private_nfts_recipient_slot, + nft_contract_address, + ); + + // Recipient should have the note in their private nfts + utils::assert_owns_private_nft(nft_contract_address, recipient, token_id); + + // Since the NFT got transferred to private public owner should be zero address + utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id); +} + +#[test(should_fail_with = "transfer not prepared")] +unconstrained fn transfer_to_private_transfer_not_prepared() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, nft_contract_address, _, _, token_id) = + utils::setup_and_mint(/* with_account_contracts */ false); + + // Transfer was not prepared so we can use random value for the hiding point slot + let hiding_point_slot = random(); + + // Try finalizing the transfer without preparing it + NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + &mut env.public(), + ); +} + +#[test(should_fail_with = "invalid NFT owner")] +unconstrained fn transfer_to_private_failure_not_an_owner() { + // Setup without account contracts. We are not using authwits here, so dummy accounts are enough + let (env, nft_contract_address, _, not_owner, token_id) = + utils::setup_and_mint(/* with_account_contracts */ false); + + // (For this specific test we could set a random value for the commitment and not do the call to `prepare...` + // as the NFT owner check is before we use the value but that would made the test less robust against changes + // in the contract.) + let hiding_point_slot: Field = NFT::at(nft_contract_address) + .prepare_transfer_to_private(not_owner) + .call(&mut env.private()); + + // Try transferring someone else's public NFT + env.impersonate(not_owner); + NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + &mut env.public(), + ); +} From 6ff6457f7813b6e80389c16729867d937a010f59 Mon Sep 17 00:00:00 2001 From: benesjan Date: Mon, 28 Oct 2024 22:45:46 +0000 Subject: [PATCH 03/28] using partial flow in test private mint --- .../contracts/token_contract/src/main.nr | 6 ++-- .../token_contract/src/test/utils.nr | 32 ++++++++++--------- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 8251a2f2f93..a6132e9554d 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -487,14 +487,14 @@ contract Token { fn transfer_to_private(to: AztecAddress, amount: Field) { let from = context.msg_sender(); - let nft = Token::at(context.this_address()); + let token = Token::at(context.this_address()); // We prepare the transfer. let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); // At last we finalize the transfer. Usafe of the `unsafe` method here is safe because we set the `from` - // function argument to a message sender, guaranteeing that he can transfer only his own NFTs. - nft._finalize_transfer_to_private_unsafe(from, amount, hiding_point_slot).enqueue( + // function argument to a message sender, guaranteeing that he can transfer only his own tokens. + token._finalize_transfer_to_private_unsafe(from, amount, hiding_point_slot).enqueue( &mut context, ); } 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 34e731443b8..6f6dc62b631 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 @@ -1,5 +1,4 @@ use dep::aztec::{ - hash::compute_secret_hash, oracle::{ execution::{get_block_number, get_contract_address}, random::random, @@ -11,6 +10,8 @@ use dep::aztec::{ }; use crate::{Token, types::transparent_note::TransparentNote}; +use dep::uint_note::uint_note::UintNote; +use aztec::keys::getters::get_public_keys; pub unconstrained fn setup( with_account_contracts: bool, @@ -59,27 +60,28 @@ pub unconstrained fn setup_and_mint_public( pub unconstrained fn setup_and_mint_private( with_account_contracts: bool, ) -> (&mut TestEnvironment, AztecAddress, AztecAddress, AztecAddress, Field) { - // Setup - let (env, token_contract_address, owner, recipient) = setup(with_account_contracts); + // Setup the tokens and mint public balance + let (env, token_contract_address, owner, recipient) = + setup_and_mint_public(with_account_contracts); let mint_amount = 10000; - // Mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); + // Transfer the public balance to private + Token::at(token_contract_address).transfer_to_private(owner, mint_amount).call( + &mut env.private(), + ); // docs:start:txe_test_add_note - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. + // TODO(#8771): We need to manually add the note because in the partial notes flow `notify_created_note_oracle` + // is not called and we don't have a `NoteProcessor` in TXE. + let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); + let balances_owner_slot = + derive_storage_slot_in_map(Token::storage_layout().balances.slot, owner); + env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage_layout().pending_shields.slot, + &mut UintNote::new(U128::from_integer(mint_amount), owner_npk_m_hash), + balances_owner_slot, token_contract_address, ); // docs:end:txe_test_add_note - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret).call( - &mut env.private(), - ); - (env, token_contract_address, owner, recipient, mint_amount) } From 39f5d1c69e36d331a8802f19745e17d518e578ec Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 14:01:02 +0000 Subject: [PATCH 04/28] fixes after rebase --- .../noir-contracts/contracts/token_contract/src/main.nr | 4 +--- .../noir-contracts/contracts/token_contract/src/test/utils.nr | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index a6132e9554d..b5d638572e3 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -518,15 +518,13 @@ contract Token { storage: Storage<&mut PrivateContext>, ) -> Field { let to_keys = get_public_keys(to); - let to_npk_m_hash = to_keys.npk_m.hash(); let to_note_slot = storage.balances.at(to).set.storage_slot; // We create a setup payload with unpopulated/zero `amount` for 'to' // TODO(#7775): Manually fetching the randomness here is not great. If we decide to include randomness in all // notes we could just inject it in macros. let note_randomness = unsafe { random() }; - let note_setup_payload = - UintNote::setup_payload().new(to_npk_m_hash, note_randomness, to_note_slot); + let note_setup_payload = UintNote::setup_payload().new(to, note_randomness, to_note_slot); // We encrypt the note log let setup_log = note_setup_payload.encrypt_log(context, to_keys, to); 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 6f6dc62b631..c16207918e4 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 @@ -72,12 +72,11 @@ pub unconstrained fn setup_and_mint_private( // docs:start:txe_test_add_note // TODO(#8771): We need to manually add the note because in the partial notes flow `notify_created_note_oracle` // is not called and we don't have a `NoteProcessor` in TXE. - let owner_npk_m_hash = get_public_keys(owner).npk_m.hash(); let balances_owner_slot = derive_storage_slot_in_map(Token::storage_layout().balances.slot, owner); env.add_note( - &mut UintNote::new(U128::from_integer(mint_amount), owner_npk_m_hash), + &mut UintNote::new(U128::from_integer(mint_amount), owner), balances_owner_slot, token_contract_address, ); From 5837e1e3eda7c6455bf29183549fe1939e0b34f2 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 14:19:39 +0000 Subject: [PATCH 05/28] transfer_to_private test --- .../contracts/token_contract/src/test.nr | 2 +- .../src/test/transfer_to_private.nr | 70 ++++++++----------- 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 63753d9eaa7..a02821b3d5b 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -3,7 +3,7 @@ mod burn; mod utils; mod transfer_public; mod transfer_private; -// mod transfer_to_private; +mod transfer_to_private; mod refunds; mod unshielding; mod minting; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr index 21e109704a7..2d331708ab9 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr @@ -1,29 +1,25 @@ -use crate::{NFT, test::utils, types::nft_note::NFTNote}; +use crate::{test::utils, Token}; use dep::aztec::{ - keys::getters::get_public_keys, - oracle::random::random, - prelude::{AztecAddress, NoteHeader}, + keys::getters::get_public_keys, oracle::random::random, prelude::NoteHeader, protocol_types::storage::map::derive_storage_slot_in_map, }; +use dep::uint_note::uint_note::UintNote; use std::test::OracleMock; /// Internal orchestration means that the calls to `prepare_transfer_to_private` -/// and `finalize_transfer_to_private` are done by the NFT contract itself. -/// In this test's case this is done by the `NFT::transfer_to_private(...)` function called +/// and `finalize_transfer_to_private` are done by the TOKEN contract itself. +/// In this test's case this is done by the `Token::transfer_to_private(...)` function called /// in `utils::setup_mint_and_transfer_to_private`. #[test] unconstrained fn transfer_to_private_internal_orchestration() { - // The transfer to private is done in `utils::setup_mint_and_transfer_to_private` and for this reason + // The transfer to private is done in `utils::setup_and_mint_private` and for this reason // in this test we just call it and check the outcome. // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, nft_contract_address, user, _, token_id) = - utils::setup_mint_and_transfer_to_private(/* with_account_contracts */ false); + let (env, token_contract_address, user, _, amount) = + utils::setup_and_mint_private(/* with_account_contracts */ false); - // User should have the note in their private nfts - utils::assert_owns_private_nft(nft_contract_address, user, token_id); - - // Since the NFT was sent to private, the public owner should be zero address - utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id); + // User's private balance should be equal to the amount + utils::check_private_balance(token_contract_address, user, amount); } /// External orchestration means that the calls to prepare and finalize are not done by the NFT contract. This flow @@ -31,8 +27,8 @@ unconstrained fn transfer_to_private_internal_orchestration() { #[test] unconstrained fn transfer_to_private_external_orchestration() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, nft_contract_address, _, recipient, token_id) = - utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, _, recipient, amount) = + utils::setup_and_mint_public(/* with_account_contracts */ false); let note_randomness = random(); @@ -40,50 +36,46 @@ unconstrained fn transfer_to_private_external_orchestration() { let _ = OracleMock::mock("getRandomField").returns(note_randomness); // We prepare the transfer - let hiding_point_slot: Field = NFT::at(nft_contract_address) + let hiding_point_slot: Field = Token::at(token_contract_address) .prepare_transfer_to_private(recipient) .call(&mut env.private()); - // Finalize the transfer of the NFT (message sender owns the NFT in public) - NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + // Finalize the transfer of the tokens (message sender owns the tokens in public) + Token::at(token_contract_address).finalize_transfer_to_private(amount, hiding_point_slot).call( &mut env.public(), ); // TODO(#8771): We need to manually add the note because in the partial notes flow `notify_created_note_oracle` // is not called and we don't have a `NoteProcessor` in TXE. - let recipient_npk_m_hash = get_public_keys(recipient).npk_m.hash(); - let private_nfts_recipient_slot = - derive_storage_slot_in_map(NFT::storage_layout().private_nfts.slot, recipient); + let balances_owner_slot = + derive_storage_slot_in_map(Token::storage_layout().balances.slot, recipient); env.add_note( - &mut NFTNote { - token_id, - npk_m_hash: recipient_npk_m_hash, + &mut UintNote { + value: U128::from_integer(amount), + owner: recipient, randomness: note_randomness, header: NoteHeader::empty(), }, - private_nfts_recipient_slot, - nft_contract_address, + balances_owner_slot, + token_contract_address, ); - // Recipient should have the note in their private nfts - utils::assert_owns_private_nft(nft_contract_address, recipient, token_id); - - // Since the NFT got transferred to private public owner should be zero address - utils::assert_owns_public_nft(env, nft_contract_address, AztecAddress::zero(), token_id); + // Recipient's private balance should be equal to the amount + utils::check_private_balance(token_contract_address, recipient, amount); } #[test(should_fail_with = "transfer not prepared")] unconstrained fn transfer_to_private_transfer_not_prepared() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, nft_contract_address, _, _, token_id) = - utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, _, _, amount) = + utils::setup_and_mint_public(/* with_account_contracts */ false); // Transfer was not prepared so we can use random value for the hiding point slot let hiding_point_slot = random(); // Try finalizing the transfer without preparing it - NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + Token::at(token_contract_address).finalize_transfer_to_private(amount, hiding_point_slot).call( &mut env.public(), ); } @@ -91,19 +83,19 @@ unconstrained fn transfer_to_private_transfer_not_prepared() { #[test(should_fail_with = "invalid NFT owner")] unconstrained fn transfer_to_private_failure_not_an_owner() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, nft_contract_address, _, not_owner, token_id) = - utils::setup_and_mint(/* with_account_contracts */ false); + let (env, token_contract_address, _, not_owner, amount) = + utils::setup_and_mint_public(/* with_account_contracts */ false); // (For this specific test we could set a random value for the commitment and not do the call to `prepare...` // as the NFT owner check is before we use the value but that would made the test less robust against changes // in the contract.) - let hiding_point_slot: Field = NFT::at(nft_contract_address) + let hiding_point_slot: Field = Token::at(token_contract_address) .prepare_transfer_to_private(not_owner) .call(&mut env.private()); // Try transferring someone else's public NFT env.impersonate(not_owner); - NFT::at(nft_contract_address).finalize_transfer_to_private(token_id, hiding_point_slot).call( + Token::at(token_contract_address).finalize_transfer_to_private(amount, hiding_point_slot).call( &mut env.public(), ); } From 85dbb0966c0e2b918dd5f4401944980427cfbdef Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 15:14:53 +0000 Subject: [PATCH 06/28] fixed transfer to private test --- .../token_contract/src/test/transfer_to_private.nr | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr index 2d331708ab9..e5b3463d83b 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/transfer_to_private.nr @@ -15,14 +15,14 @@ unconstrained fn transfer_to_private_internal_orchestration() { // The transfer to private is done in `utils::setup_and_mint_private` and for this reason // in this test we just call it and check the outcome. // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, user, _, amount) = + let (_, token_contract_address, user, _, amount) = utils::setup_and_mint_private(/* with_account_contracts */ false); // User's private balance should be equal to the amount utils::check_private_balance(token_contract_address, user, amount); } -/// External orchestration means that the calls to prepare and finalize are not done by the NFT contract. This flow +/// External orchestration means that the calls to prepare and finalize are not done by the Token contract. This flow /// will typically be used by a DEX. #[test] unconstrained fn transfer_to_private_external_orchestration() { @@ -80,20 +80,20 @@ unconstrained fn transfer_to_private_transfer_not_prepared() { ); } -#[test(should_fail_with = "invalid NFT owner")] +#[test(should_fail_with = "Assertion failed: attempt to subtract with underflow 'hi == high'")] unconstrained fn transfer_to_private_failure_not_an_owner() { // Setup without account contracts. We are not using authwits here, so dummy accounts are enough let (env, token_contract_address, _, not_owner, amount) = utils::setup_and_mint_public(/* with_account_contracts */ false); // (For this specific test we could set a random value for the commitment and not do the call to `prepare...` - // as the NFT owner check is before we use the value but that would made the test less robust against changes + // as the token balance check is before we use the value but that would made the test less robust against changes // in the contract.) let hiding_point_slot: Field = Token::at(token_contract_address) .prepare_transfer_to_private(not_owner) .call(&mut env.private()); - // Try transferring someone else's public NFT + // Try transferring someone else's token balance env.impersonate(not_owner); Token::at(token_contract_address).finalize_transfer_to_private(amount, hiding_point_slot).call( &mut env.public(), From 07ad5bcd554991402881abb32fece34251422035 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 15:44:12 +0000 Subject: [PATCH 07/28] token utils --- .../end-to-end/src/e2e_2_pxes.test.ts | 138 +++++------------- yarn-project/end-to-end/src/fixtures/index.ts | 1 + .../end-to-end/src/fixtures/token_utils.ts | 61 ++++++++ 3 files changed, 95 insertions(+), 105 deletions(-) create mode 100644 yarn-project/end-to-end/src/fixtures/token_utils.ts diff --git a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts index 1ded4d5f9c8..d7dd074d5a0 100644 --- a/yarn-project/end-to-end/src/e2e_2_pxes.test.ts +++ b/yarn-project/end-to-end/src/e2e_2_pxes.test.ts @@ -4,12 +4,10 @@ import { type AztecAddress, type AztecNode, type DebugLogger, - ExtendedNote, + type ExtendedNote, Fr, - Note, type PXE, type Wallet, - computeSecretHash, retryUntil, sleep, } from '@aztec/aztec.js'; @@ -17,7 +15,8 @@ import { ChildContract, TestContract, TokenContract } from '@aztec/noir-contract import { expect, jest } from '@jest/globals'; -import { expectsNumOfNoteEncryptedLogsInTheLastBlockToBe, setup, setupPXEService } from './fixtures/utils.js'; +import { deployToken, expectTokenBalance, mintTokensToPrivate } from './fixtures/token_utils.js'; +import { setup, setupPXEService } from './fixtures/utils.js'; const TIMEOUT = 120_000; @@ -55,78 +54,12 @@ describe('e2e_2_pxes', () => { await teardownA(); }); - const awaitUserSynchronized = async (wallet: Wallet, owner: AztecAddress) => { - const isUserSynchronized = async () => { - return await wallet.isAccountStateSynchronized(owner); - }; - await retryUntil(isUserSynchronized, `synch of user ${owner.toString()}`, 10); - }; - - const expectTokenBalance = async ( - wallet: Wallet, - tokenAddress: AztecAddress, - owner: AztecAddress, - expectedBalance: bigint, - checkIfSynchronized = true, - ) => { - if (checkIfSynchronized) { - // First wait until the corresponding PXE has synchronized the account - await awaitUserSynchronized(wallet, owner); - } - - // Then check the balance - const contractWithWallet = await TokenContract.at(tokenAddress, wallet); - const balance = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner }); - logger.info(`Account ${owner} balance: ${balance}`); - expect(balance).toBe(expectedBalance); - }; - - const deployTokenContract = async (initialAdminBalance: bigint, admin: AztecAddress, pxe: PXE) => { - logger.info(`Deploying Token contract...`); - const contract = await TokenContract.deploy(walletA, admin, 'TokenName', 'TokenSymbol', 18).send().deployed(); - - if (initialAdminBalance > 0n) { - // Minter is minting to herself so contract as minter is the same as contract as recipient - await mintTokens(contract, contract, admin, initialAdminBalance, pxe); - } - - logger.info('L2 contract deployed'); - - return contract; - }; - - const mintTokens = async ( - contractAsMinter: TokenContract, - contractAsRecipient: TokenContract, - recipient: AztecAddress, - balance: bigint, - recipientPxe: PXE, - ) => { - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - - const receipt = await contractAsMinter.methods.mint_private(balance, secretHash).send().wait(); - - const note = new Note([new Fr(balance), secretHash]); - const extendedNote = new ExtendedNote( - note, - recipient, - contractAsMinter.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await recipientPxe.addNote(extendedNote, recipient); - - await contractAsRecipient.methods.redeem_shield(recipient, balance, secret).send().wait(); - }; - it('transfers funds from user A to B via PXE A followed by transfer from B to A via PXE B', async () => { const initialBalance = 987n; const transferAmount1 = 654n; const transferAmount2 = 323n; - const token = await deployTokenContract(initialBalance, walletA.getAddress(), pxeA); + const token = await deployToken(walletA, initialBalance, logger); // Add account B to wallet A await pxeA.registerRecipient(walletB.getCompleteAddress()); @@ -136,33 +69,31 @@ describe('e2e_2_pxes', () => { // Add token to PXE B (PXE A already has it because it was deployed through it) await pxeB.registerContract(token); - // Check initial balances and logs are as expected - await expectTokenBalance(walletA, token.address, walletA.getAddress(), initialBalance); - await expectTokenBalance(walletB, token.address, walletB.getAddress(), 0n); - await expectsNumOfNoteEncryptedLogsInTheLastBlockToBe(aztecNode, 1); + // Check initial balances are as expected + await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance, logger); + await expectTokenBalance(walletB, token, walletB.getAddress(), 0n, logger); // Transfer funds from A to B via PXE A const contractWithWalletA = await TokenContract.at(token.address, walletA); await contractWithWalletA.methods.transfer(walletB.getAddress(), transferAmount1).send().wait(); - // Check balances and logs are as expected - await expectTokenBalance(walletA, token.address, walletA.getAddress(), initialBalance - transferAmount1); - await expectTokenBalance(walletB, token.address, walletB.getAddress(), transferAmount1); - await expectsNumOfNoteEncryptedLogsInTheLastBlockToBe(aztecNode, 2); + // Check balances are as expected + await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance - transferAmount1, logger); + await expectTokenBalance(walletB, token, walletB.getAddress(), transferAmount1, logger); // Transfer funds from B to A via PXE B const contractWithWalletB = await TokenContract.at(token.address, walletB); await contractWithWalletB.methods.transfer(walletA.getAddress(), transferAmount2).send().wait({ interval: 0.1 }); - // Check balances and logs are as expected + // Check balances are as expected await expectTokenBalance( walletA, - token.address, + token, walletA.getAddress(), initialBalance - transferAmount1 + transferAmount2, + logger, ); - await expectTokenBalance(walletB, token.address, walletB.getAddress(), transferAmount1 - transferAmount2); - await expectsNumOfNoteEncryptedLogsInTheLastBlockToBe(aztecNode, 2); + await expectTokenBalance(walletB, token, walletB.getAddress(), transferAmount1 - transferAmount2, logger); }); const deployChildContractViaServerA = async () => { @@ -212,8 +143,7 @@ describe('e2e_2_pxes', () => { const userABalance = 100n; const userBBalance = 150n; - const token = await deployTokenContract(userABalance, walletA.getAddress(), pxeA); - const contractWithWalletA = await TokenContract.at(token.address, walletA); + const token = await deployToken(walletA, userABalance, logger); // Add account B to wallet A await pxeA.registerRecipient(walletB.getCompleteAddress()); @@ -224,21 +154,20 @@ describe('e2e_2_pxes', () => { await pxeB.registerContract(token); // Mint tokens to user B - const contractWithWalletB = await TokenContract.at(token.address, walletB); - await mintTokens(contractWithWalletA, contractWithWalletB, walletB.getAddress(), userBBalance, pxeB); + await mintTokensToPrivate(token, walletA, walletB.getAddress(), userBBalance); // Check that user A balance is 100 on server A - await expectTokenBalance(walletA, token.address, walletA.getAddress(), userABalance); + await expectTokenBalance(walletA, token, walletA.getAddress(), userABalance, logger); // Check that user B balance is 150 on server B - await expectTokenBalance(walletB, token.address, walletB.getAddress(), userBBalance); + await expectTokenBalance(walletB, token, walletB.getAddress(), userBBalance, logger); // CHECK THAT PRIVATE BALANCES ARE 0 WHEN ACCOUNT'S SECRET KEYS ARE NOT REGISTERED // Note: Not checking if the account is synchronized because it is not registered as an account (it would throw). const checkIfSynchronized = false; // Check that user A balance is 0 on server B - await expectTokenBalance(walletB, token.address, walletA.getAddress(), 0n, checkIfSynchronized); + await expectTokenBalance(walletB, token, walletA.getAddress(), 0n, logger, checkIfSynchronized); // Check that user B balance is 0 on server A - await expectTokenBalance(walletA, token.address, walletB.getAddress(), 0n, checkIfSynchronized); + await expectTokenBalance(walletA, token, walletB.getAddress(), 0n, logger, checkIfSynchronized); }); it('permits migrating an account from one PXE to another', async () => { @@ -263,28 +192,25 @@ describe('e2e_2_pxes', () => { const initialBalance = 987n; const transferAmount1 = 654n; - const token = await deployTokenContract(initialBalance, walletA.getAddress(), pxeA); - const tokenAddress = token.address; + const token = await deployToken(walletA, initialBalance, logger); // Add account B to wallet A await pxeA.registerRecipient(walletB.getCompleteAddress()); // Add account A to wallet B await pxeB.registerRecipient(walletA.getCompleteAddress()); - // Check initial balances and logs are as expected - await expectTokenBalance(walletA, tokenAddress, walletA.getAddress(), initialBalance); + // Check initial balances are as expected + await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance, logger); // don't check userB yet - await expectsNumOfNoteEncryptedLogsInTheLastBlockToBe(aztecNode, 1); - // Transfer funds from A to B via PXE A - const contractWithWalletA = await TokenContract.at(tokenAddress, walletA); + const contractWithWalletA = await TokenContract.at(token.address, walletA); await contractWithWalletA.methods.transfer(walletB.getAddress(), transferAmount1).send().wait(); // now add the contract and check balances await pxeB.registerContract(token); - await expectTokenBalance(walletA, tokenAddress, walletA.getAddress(), initialBalance - transferAmount1); - await expectTokenBalance(walletB, tokenAddress, walletB.getAddress(), transferAmount1); + await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance - transferAmount1, logger); + await expectTokenBalance(walletB, token, walletB.getAddress(), transferAmount1, logger); }); it('permits sending funds to a user, and spending them, before they have registered the contract', async () => { @@ -307,7 +233,7 @@ describe('e2e_2_pxes', () => { await pxeA.registerRecipient(walletB.getCompleteAddress()); // deploy the contract on PXE A - const token = await deployTokenContract(initialBalance, walletA.getAddress(), pxeA); + const token = await deployToken(walletA, initialBalance, logger); // Transfer funds from A to Shared Wallet via PXE A const contractWithWalletA = await TokenContract.at(token.address, walletA); @@ -318,12 +244,13 @@ describe('e2e_2_pxes', () => { await contractWithSharedWalletA.methods.transfer(walletB.getAddress(), transferAmount2).send().wait(); // check balances from PXE-A's perspective - await expectTokenBalance(walletA, token.address, walletA.getAddress(), initialBalance - transferAmount1); + await expectTokenBalance(walletA, token, walletA.getAddress(), initialBalance - transferAmount1, logger); await expectTokenBalance( sharedWalletOnA, - token.address, + token, sharedAccountAddress.address, transferAmount1 - transferAmount2, + logger, ); // now add the contract and check balances from PXE-B's perspective. @@ -332,13 +259,14 @@ describe('e2e_2_pxes', () => { // PXE-B adds the contract // PXE-B reprocesses the deferred notes, and sees the nullifier for A -> Shared await pxeB.registerContract(token); - await expectTokenBalance(walletB, token.address, walletB.getAddress(), transferAmount2); + await expectTokenBalance(walletB, token, walletB.getAddress(), transferAmount2, logger); await expect(sharedWalletOnB.isAccountStateSynchronized(sharedAccountAddress.address)).resolves.toBe(true); await expectTokenBalance( sharedWalletOnB, - token.address, + token, sharedAccountAddress.address, transferAmount1 - transferAmount2, + logger, ); }); diff --git a/yarn-project/end-to-end/src/fixtures/index.ts b/yarn-project/end-to-end/src/fixtures/index.ts index 146faa68d45..c2a32f7e035 100644 --- a/yarn-project/end-to-end/src/fixtures/index.ts +++ b/yarn-project/end-to-end/src/fixtures/index.ts @@ -1,3 +1,4 @@ export * from './fixtures.js'; export * from './logging.js'; export * from './utils.js'; +export * from './token_utils.js'; diff --git a/yarn-project/end-to-end/src/fixtures/token_utils.ts b/yarn-project/end-to-end/src/fixtures/token_utils.ts new file mode 100644 index 00000000000..a0c570648a2 --- /dev/null +++ b/yarn-project/end-to-end/src/fixtures/token_utils.ts @@ -0,0 +1,61 @@ +import { type AztecAddress, BatchCall, type DebugLogger, type Wallet, retryUntil } from '@aztec/aztec.js'; +import { TokenContract } from '@aztec/noir-contracts.js'; + +export async function deployToken(adminWallet: Wallet, initialAdminBalance: bigint, logger: DebugLogger) { + logger.info(`Deploying Token contract...`); + const contract = await TokenContract.deploy(adminWallet, adminWallet.getAddress(), 'TokenName', 'TokenSymbol', 18) + .send() + .deployed(); + + if (initialAdminBalance > 0n) { + // Minter is minting to herself so contract as minter is the same as contract as recipient + await mintTokensToPrivate(contract, adminWallet, adminWallet.getAddress(), initialAdminBalance); + } + + logger.info('L2 contract deployed'); + + return contract; +} + +export async function mintTokensToPrivate( + token: TokenContract, + minterWallet: Wallet, + recipient: AztecAddress, + amount: bigint, +) { + // We don't have the functionality to mint to private so we mint to the minter address in public and transfer + // the tokens to the recipient in private. We use BatchCall to speed the process up. + await new BatchCall(minterWallet, [ + token.methods.mint_public(minterWallet.getAddress(), amount).request(), + token.methods.transfer_to_private(recipient, amount).request(), + ]) + .send() + .wait(); +} + +const awaitUserSynchronized = async (wallet: Wallet, owner: AztecAddress) => { + const isUserSynchronized = async () => { + return await wallet.isAccountStateSynchronized(owner); + }; + await retryUntil(isUserSynchronized, `synch of user ${owner.toString()}`, 10); +}; + +export async function expectTokenBalance( + wallet: Wallet, + token: TokenContract, + owner: AztecAddress, + expectedBalance: bigint, + logger: DebugLogger, + checkIfSynchronized = true, +) { + if (checkIfSynchronized) { + // First wait until the corresponding PXE has synchronized the account + await awaitUserSynchronized(wallet, owner); + } + + // Then check the balance + const contractWithWallet = await TokenContract.at(token.address, wallet); + const balance = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner }); + logger.info(`Account ${owner} balance: ${balance}`); + expect(balance).toBe(expectedBalance); +} From 36917e500ea3e17779df579c106a6eaaaf3abaf6 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 16:26:58 +0000 Subject: [PATCH 08/28] WIP --- .../noir-contracts/contracts/token_contract/src/main.nr | 1 - 1 file changed, 1 deletion(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index b5d638572e3..58c31cf5ebe 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -486,7 +486,6 @@ contract Token { #[private] fn transfer_to_private(to: AztecAddress, amount: Field) { let from = context.msg_sender(); - let token = Token::at(context.this_address()); // We prepare the transfer. From 44a64a08d7e45fc0589fa4f66a76b4f95a9dbe66 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 16:27:29 +0000 Subject: [PATCH 09/28] WIP --- .../end-to-end/src/e2e_cheat_codes.test.ts | 34 ++++--------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts index 86d78c83674..0e6f12cc86e 100644 --- a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts +++ b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts @@ -1,13 +1,7 @@ import { type CheatCodes, type CompleteAddress, - EthAddress, - ExtendedNote, - Fr, - Note, - type PXE, - type Wallet, - computeSecretHash, + EthAddress, Fr, type Wallet } from '@aztec/aztec.js'; import { RollupAbi } from '@aztec/l1-artifacts'; import { TokenContract } from '@aztec/noir-contracts.js'; @@ -25,12 +19,12 @@ import { import type * as chains from 'viem/chains'; import { setup } from './fixtures/utils.js'; +import { mintTokensToPrivate } from './fixtures/token_utils.js'; describe('e2e_cheat_codes', () => { let wallet: Wallet; let admin: CompleteAddress; let cc: CheatCodes; - let pxe: PXE; let teardown: () => Promise; let rollup: GetContractReturnType>; @@ -40,7 +34,7 @@ describe('e2e_cheat_codes', () => { beforeAll(async () => { let deployL1ContractsValues; - ({ teardown, wallet, cheatCodes: cc, deployL1ContractsValues, pxe } = await setup()); + ({ teardown, wallet, cheatCodes: cc, deployL1ContractsValues } = await setup()); walletClient = deployL1ContractsValues.walletClient; publicClient = deployL1ContractsValues.publicClient; @@ -185,31 +179,17 @@ describe('e2e_cheat_codes', () => { }); it('load private', async () => { - // mint note and check if it exists in pending_shield. + // mint a token note and check it exists in balances. // docs:start:load_private_cheatcode const mintAmount = 100n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - // docs:start:pxe_add_note - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - admin.address, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await pxe.addNote(extendedNote); - // docs:end:pxe_add_note + + await mintTokensToPrivate(token, wallet, admin.address, mintAmount); // check if note was added to pending shield: const notes = await cc.aztec.loadPrivate( admin.address, token.address, - TokenContract.storage.pending_shields.slot, + TokenContract.storage.balances.slot, ); const values = notes.map(note => note.items[0]); const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); From a3acec0a9e1dd3c9c42b2a6bd4855aaafe7b834d Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 16:55:08 +0000 Subject: [PATCH 10/28] fix --- .../noir-contracts/contracts/token_contract/src/main.nr | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr index 58c31cf5ebe..2e764587bbc 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -516,7 +516,6 @@ contract Token { context: &mut PrivateContext, storage: Storage<&mut PrivateContext>, ) -> Field { - let to_keys = get_public_keys(to); let to_note_slot = storage.balances.at(to).set.storage_slot; // We create a setup payload with unpopulated/zero `amount` for 'to' @@ -525,8 +524,9 @@ contract Token { let note_randomness = unsafe { random() }; let note_setup_payload = UintNote::setup_payload().new(to, note_randomness, to_note_slot); - // We encrypt the note log - let setup_log = note_setup_payload.encrypt_log(context, to_keys, to); + // We set the ovpk to the message sender's ovpk and we encrypt the log. + let from_ovpk = get_public_keys(context.msg_sender()).ovpk_m; + let setup_log = note_setup_payload.encrypt_log(context, from_ovpk, to); // Using the x-coordinate as a hiding point slot is safe against someone else interfering with it because // we have a guarantee that the public functions of the transaction are executed right after the private ones From 35c9b3fd7a5d564b037c6de8a8e93b3f3b201cb6 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 18:17:00 +0000 Subject: [PATCH 11/28] updated cheatcodes test --- .../end-to-end/src/e2e_cheat_codes.test.ts | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts index 0e6f12cc86e..ceae5ad7c6b 100644 --- a/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts +++ b/yarn-project/end-to-end/src/e2e_cheat_codes.test.ts @@ -1,8 +1,4 @@ -import { - type CheatCodes, - type CompleteAddress, - EthAddress, Fr, type Wallet -} from '@aztec/aztec.js'; +import { type AztecAddress, type CheatCodes, EthAddress, Fr, type Wallet } from '@aztec/aztec.js'; import { RollupAbi } from '@aztec/l1-artifacts'; import { TokenContract } from '@aztec/noir-contracts.js'; @@ -18,12 +14,12 @@ import { } from 'viem'; import type * as chains from 'viem/chains'; -import { setup } from './fixtures/utils.js'; import { mintTokensToPrivate } from './fixtures/token_utils.js'; +import { setup } from './fixtures/utils.js'; describe('e2e_cheat_codes', () => { let wallet: Wallet; - let admin: CompleteAddress; + let admin: AztecAddress; let cc: CheatCodes; let teardown: () => Promise; @@ -38,7 +34,7 @@ describe('e2e_cheat_codes', () => { walletClient = deployL1ContractsValues.walletClient; publicClient = deployL1ContractsValues.publicClient; - admin = wallet.getCompleteAddress(); + admin = wallet.getAddress(); rollup = getContract({ address: deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString(), @@ -163,7 +159,7 @@ describe('e2e_cheat_codes', () => { describe('L2 cheatcodes', () => { it('load public', async () => { - expect(await cc.aztec.loadPublic(token.address, 1n)).toEqual(admin.address.toField()); + expect(await cc.aztec.loadPublic(token.address, 1n)).toEqual(admin.toField()); }); it('load public returns 0 for non existent value', async () => { @@ -172,7 +168,7 @@ describe('e2e_cheat_codes', () => { }); it('load private works as expected for no notes', async () => { - const notes = await cc.aztec.loadPrivate(admin.address, token.address, 5n); + const notes = await cc.aztec.loadPrivate(admin, token.address, 5n); const values = notes.map(note => note.items[0]); const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); expect(balance).toEqual(0n); @@ -183,14 +179,12 @@ describe('e2e_cheat_codes', () => { // docs:start:load_private_cheatcode const mintAmount = 100n; - await mintTokensToPrivate(token, wallet, admin.address, mintAmount); + await mintTokensToPrivate(token, wallet, admin, mintAmount); + + const balancesAdminSlot = cc.aztec.computeSlotInMap(TokenContract.storage.balances.slot, admin); // check if note was added to pending shield: - const notes = await cc.aztec.loadPrivate( - admin.address, - token.address, - TokenContract.storage.balances.slot, - ); + const notes = await cc.aztec.loadPrivate(admin, token.address, balancesAdminSlot); const values = notes.map(note => note.items[0]); const balance = values.reduce((sum, current) => sum + current.toBigInt(), 0n); expect(balance).toEqual(mintAmount); From 29b206615fb9f59897e0fa322f15c5c4991e1e25 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 18:25:45 +0000 Subject: [PATCH 12/28] updated crowdfunding test --- .../src/e2e_crowdfunding_and_claim.test.ts | 67 ++----------------- 1 file changed, 4 insertions(+), 63 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts index 2470786a0d0..96aad82e8c0 100644 --- a/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts +++ b/yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts @@ -1,19 +1,14 @@ import { createAccounts } from '@aztec/accounts/testing'; import { type AccountWallet, - type AztecAddress, type AztecNode, type CheatCodes, type DebugLogger, - ExtendedNote, Fr, - Note, type PXE, PackedValues, TxExecutionRequest, - type TxHash, type UniqueNote, - computeSecretHash, deriveKeys, } from '@aztec/aztec.js'; import { GasSettings, TxContext, computePartialAddress } from '@aztec/circuits.js'; @@ -24,6 +19,7 @@ import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { jest } from '@jest/globals'; +import { mintTokensToPrivate } from './fixtures/token_utils.js'; import { setup, setupPXEService } from './fixtures/utils.js'; jest.setTimeout(200_000); @@ -64,25 +60,6 @@ describe('e2e_crowdfunding_and_claim', () => { let valueNote!: any; - const addPendingShieldNoteToPXE = async ( - wallet: AccountWallet, - amount: bigint, - secretHash: Fr, - txHash: TxHash, - address: AztecAddress, - ) => { - const note = new Note([new Fr(amount), secretHash]); - const extendedNote = new ExtendedNote( - note, - wallet.getAddress(), - address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await wallet.addNote(extendedNote); - }; - beforeAll(async () => { ({ cheatCodes, teardown: teardownA, logger, pxe, wallets, aztecNode } = await setup(3)); operatorWallet = wallets[0]; @@ -135,7 +112,9 @@ describe('e2e_crowdfunding_and_claim', () => { await rewardToken.methods.set_minter(claimContract.address, true).send().wait(); - await mintDNTToDonors(); + // Now we mint DNT to donors + await mintTokensToPrivate(donationToken, operatorWallet, donorWallets[0].getAddress(), 1234n); + await mintTokensToPrivate(donationToken, operatorWallet, donorWallets[1].getAddress(), 2345n); }); afterAll(async () => { @@ -143,44 +122,6 @@ describe('e2e_crowdfunding_and_claim', () => { await teardownB?.(); }); - const mintDNTToDonors = async () => { - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - - const [txReceipt1, txReceipt2] = await Promise.all([ - donationToken.withWallet(operatorWallet).methods.mint_private(1234n, secretHash).send().wait(), - donationToken.withWallet(operatorWallet).methods.mint_private(2345n, secretHash).send().wait(), - ]); - - await addPendingShieldNoteToPXE( - donorWallets[0], - 1234n, - secretHash, - txReceipt1.txHash, - donationToken.withWallet(operatorWallet).address, - ); - await addPendingShieldNoteToPXE( - donorWallets[1], - 2345n, - secretHash, - txReceipt2.txHash, - donationToken.withWallet(operatorWallet).address, - ); - - await Promise.all([ - donationToken - .withWallet(donorWallets[0]) - .methods.redeem_shield(donorWallets[0].getAddress(), 1234n, secret) - .send() - .wait(), - donationToken - .withWallet(donorWallets[1]) - .methods.redeem_shield(donorWallets[1].getAddress(), 2345n, secret) - .send() - .wait(), - ]); - }; - // Processes unique note such that it can be passed to a claim function of Claim contract const processUniqueNote = (uniqueNote: UniqueNote) => { return { From 450346aaf9ecb14310c64e8655fedb771d77326e Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 18:41:11 +0000 Subject: [PATCH 13/28] WIP --- .../src/e2e_escrow_contract.test.ts | 49 +++---------------- 1 file changed, 7 insertions(+), 42 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index 610617d419d..244b3735a1a 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -3,17 +3,15 @@ import { type AztecAddress, BatchCall, type DebugLogger, - ExtendedNote, Fr, - Note, type PXE, - computeSecretHash, deriveKeys, } from '@aztec/aztec.js'; import { type PublicKeys, computePartialAddress } from '@aztec/circuits.js'; import { EscrowContract } from '@aztec/noir-contracts.js/Escrow'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { mintTokensToPrivate } from './fixtures/token_utils.js'; import { setup } from './fixtures/utils.js'; describe('e2e_escrow_contract', () => { @@ -56,25 +54,7 @@ describe('e2e_escrow_contract', () => { // Deploy Token contract and mint funds for the escrow contract token = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send().deployed(); - const mintAmount = 100n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - - const extendedNote = new ExtendedNote( - note, - owner, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await wallet.addNote(extendedNote); - - await token.methods.redeem_shield(escrowContract.address, mintAmount, secret).send().wait(); + await mintTokensToPrivate(token, wallet, escrowContract.address, 100n); // We allow our wallet to see the escrow contract's notes. wallet.setScopes([wallet.getAddress(), escrowContract.address]); @@ -112,32 +92,17 @@ describe('e2e_escrow_contract', () => { it('moves funds using multiple keys on the same tx (#1010)', async () => { logger.info(`Minting funds in token contract to ${owner}`); const mintAmount = 50n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - owner, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await wallet.addNote(extendedNote); - - await token.methods.redeem_shield(owner, mintAmount, secret).send().wait(); + await mintTokensToPrivate(token, wallet, owner, mintAmount); await expectBalance(owner, 50n); - const actions = [ + await new BatchCall(wallet, [ token.methods.transfer(recipient, 10).request(), escrowContract.methods.withdraw(token.address, 20, recipient).request(), - ]; - - await new BatchCall(wallet, actions).send().wait(); + ]) + .send() + .wait(); await expectBalance(recipient, 30n); }); }); From f2a7640c8bf91b1e675689585a66b9ee3d276149 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 18:49:07 +0000 Subject: [PATCH 14/28] WIP --- .../src/e2e_escrow_contract.test.ts | 24 +++++-------- .../e2e_multiple_accounts_1_enc_key.test.ts | 35 +++---------------- 2 files changed, 14 insertions(+), 45 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts index 244b3735a1a..9a40a99563c 100644 --- a/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_escrow_contract.test.ts @@ -11,7 +11,7 @@ import { type PublicKeys, computePartialAddress } from '@aztec/circuits.js'; import { EscrowContract } from '@aztec/noir-contracts.js/Escrow'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; -import { mintTokensToPrivate } from './fixtures/token_utils.js'; +import { expectTokenBalance, mintTokensToPrivate } from './fixtures/token_utils.js'; import { setup } from './fixtures/utils.js'; describe('e2e_escrow_contract', () => { @@ -64,23 +64,17 @@ describe('e2e_escrow_contract', () => { afterEach(() => teardown(), 30_000); - const expectBalance = async (who: AztecAddress, expectedBalance: bigint) => { - const balance = await token.methods.balance_of_private(who).simulate({ from: who }); - logger.info(`Account ${who} balance: ${balance}`); - expect(balance).toBe(expectedBalance); - }; - it('withdraws funds from the escrow contract', async () => { - await expectBalance(owner, 0n); - await expectBalance(recipient, 0n); - await expectBalance(escrowContract.address, 100n); + await expectTokenBalance(wallet, token, owner, 0n, logger); + await expectTokenBalance(wallet, token, recipient, 0n, logger); + await expectTokenBalance(wallet, token, escrowContract.address, 100n, logger); logger.info(`Withdrawing funds from token contract to ${recipient}`); await escrowContract.methods.withdraw(token.address, 30, recipient).send().wait(); - await expectBalance(owner, 0n); - await expectBalance(recipient, 30n); - await expectBalance(escrowContract.address, 70n); + await expectTokenBalance(wallet, token, owner, 0n, logger); + await expectTokenBalance(wallet, token, recipient, 30n, logger); + await expectTokenBalance(wallet, token, escrowContract.address, 70n, logger); }); it('refuses to withdraw funds as a non-owner', async () => { @@ -95,7 +89,7 @@ describe('e2e_escrow_contract', () => { await mintTokensToPrivate(token, wallet, owner, mintAmount); - await expectBalance(owner, 50n); + await expectTokenBalance(wallet, token, owner, 50n, logger); await new BatchCall(wallet, [ token.methods.transfer(recipient, 10).request(), @@ -103,6 +97,6 @@ describe('e2e_escrow_contract', () => { ]) .send() .wait(); - await expectBalance(recipient, 30n); + await expectTokenBalance(wallet, token, recipient, 30n, logger); }); }); diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index 51631259e12..08b083ab368 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -3,18 +3,13 @@ import { type AztecAddress, type AztecNode, type CompleteAddress, - type DebugLogger, - ExtendedNote, - Fr, - GrumpkinScalar, - Note, - type PXE, - type Wallet, - computeSecretHash, - deriveKeys, + type DebugLogger, Fr, + GrumpkinScalar, type PXE, + type Wallet, deriveKeys } from '@aztec/aztec.js'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { deployToken } from './fixtures/token_utils.js'; import { expectsNumOfNoteEncryptedLogsInTheLastBlockToBe, setup } from './fixtures/utils.js'; describe('e2e_multiple_accounts_1_enc_key', () => { @@ -53,28 +48,8 @@ describe('e2e_multiple_accounts_1_enc_key', () => { expect(account.publicKeys.masterIncomingViewingPublicKey).toEqual(encryptionPublicKey); } - logger.info(`Deploying Token...`); - const token = await TokenContract.deploy(wallets[0], accounts[0], 'TokenName', 'TokenSymbol', 18).send().deployed(); + const token = await deployToken(wallets[0], initialBalance, logger); tokenAddress = token.address; - logger.info(`Token deployed at ${tokenAddress}`); - - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - - const receipt = await token.methods.mint_private(initialBalance, secretHash).send().wait(); - - const note = new Note([new Fr(initialBalance), secretHash]); - const extendedNote = new ExtendedNote( - note, - accounts[0].address, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await wallets[0].addNote(extendedNote); - - await token.methods.redeem_shield(accounts[0], initialBalance, secret).send().wait(); }); afterEach(() => teardown()); From 2211d07ad6a54df0007bdc5a54b0f1799c509572 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 18:57:53 +0000 Subject: [PATCH 15/28] WIP --- .../e2e_multiple_accounts_1_enc_key.test.ts | 38 +++++++------------ .../end-to-end/src/e2e_synching.test.ts | 36 +++--------------- 2 files changed, 19 insertions(+), 55 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts index 08b083ab368..a2cce349b9c 100644 --- a/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts +++ b/yarn-project/end-to-end/src/e2e_multiple_accounts_1_enc_key.test.ts @@ -1,15 +1,17 @@ import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { - type AztecAddress, type AztecNode, type CompleteAddress, - type DebugLogger, Fr, - GrumpkinScalar, type PXE, - type Wallet, deriveKeys + type DebugLogger, + Fr, + GrumpkinScalar, + type PXE, + type Wallet, + deriveKeys, } from '@aztec/aztec.js'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; -import { deployToken } from './fixtures/token_utils.js'; +import { deployToken, expectTokenBalance } from './fixtures/token_utils.js'; import { expectsNumOfNoteEncryptedLogsInTheLastBlockToBe, setup } from './fixtures/utils.js'; describe('e2e_multiple_accounts_1_enc_key', () => { @@ -20,7 +22,7 @@ describe('e2e_multiple_accounts_1_enc_key', () => { let logger: DebugLogger; let teardown: () => Promise; - let tokenAddress: AztecAddress; + let token: TokenContract; const initialBalance = 987n; const numAccounts = 3; @@ -48,23 +50,11 @@ describe('e2e_multiple_accounts_1_enc_key', () => { expect(account.publicKeys.masterIncomingViewingPublicKey).toEqual(encryptionPublicKey); } - const token = await deployToken(wallets[0], initialBalance, logger); - tokenAddress = token.address; + token = await deployToken(wallets[0], initialBalance, logger); }); afterEach(() => teardown()); - const expectBalance = async (userIndex: number, expectedBalance: bigint) => { - const wallet = wallets[userIndex]; - const owner = accounts[userIndex]; - - // Then check the balance - const contractWithWallet = await TokenContract.at(tokenAddress, wallet); - const balance = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner.address }); - logger.info(`Account ${owner} balance: ${balance}`); - expect(balance).toBe(expectedBalance); - }; - const transfer = async ( senderIndex: number, receiverIndex: number, @@ -76,12 +66,12 @@ describe('e2e_multiple_accounts_1_enc_key', () => { const sender = accounts[senderIndex]; const receiver = accounts[receiverIndex]; - const contractWithWallet = await TokenContract.at(tokenAddress, wallets[senderIndex]); + const contractWithWallet = await TokenContract.at(token.address, wallets[senderIndex]); await contractWithWallet.methods.transfer(receiver, transferAmount).send().wait(); for (let i = 0; i < expectedBalances.length; i++) { - await expectBalance(i, expectedBalances[i]); + await expectTokenBalance(wallets[i], token, wallets[i].getAddress(), expectedBalances[i], logger); } await expectsNumOfNoteEncryptedLogsInTheLastBlockToBe(aztecNode, 2); @@ -97,9 +87,9 @@ describe('e2e_multiple_accounts_1_enc_key', () => { const transferAmount2 = 123n; // account 0 -> account 2 const transferAmount3 = 210n; // account 1 -> account 2 - await expectBalance(0, initialBalance); - await expectBalance(1, 0n); - await expectBalance(2, 0n); + await expectTokenBalance(wallets[0], token, wallets[0].getAddress(), initialBalance, logger); + await expectTokenBalance(wallets[1], token, wallets[1].getAddress(), 0n, logger); + await expectTokenBalance(wallets[2], token, wallets[2].getAddress(), 0n, logger); const expectedBalancesAfterTransfer1 = [initialBalance - transferAmount1, transferAmount1, 0n]; await transfer(0, 1, transferAmount1, expectedBalancesAfterTransfer1); diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index 387ebd6422d..bd25583ffce 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -43,10 +43,8 @@ import { type Contract, type DebugLogger, Fr, - GrumpkinScalar, - computeSecretHash, - createDebugLogger, - sleep, + GrumpkinScalar, createDebugLogger, + sleep } from '@aztec/aztec.js'; // eslint-disable-next-line no-restricted-imports import { ExtendedNote, L2Block, LogType, Note, type TxHash } from '@aztec/circuit-types'; @@ -64,6 +62,7 @@ import { getContract } from 'viem'; import { addAccounts } from './fixtures/snapshot_manager.js'; import { type EndToEndContext, getPrivateKeyFromIndex, setup, setupPXEService } from './fixtures/utils.js'; +import { mintTokensToPrivate } from './fixtures/token_utils.js'; const SALT = 420; const AZTEC_GENERATE_TEST_DATA = !!process.env.AZTEC_GENERATE_TEST_DATA; @@ -179,34 +178,9 @@ class TestVariant { // Mint tokens privately if needed if (this.txComplexity == TxComplexity.PrivateTransfer) { - const secrets: Fr[] = this.wallets.map(() => Fr.random()); - - const txs = await Promise.all( - this.wallets.map((w, i) => - this.token.methods.mint_private(MINT_AMOUNT, computeSecretHash(secrets[i])).send().wait({ timeout: 600 }), - ), - ); - - // We minted all of them and wait. Now we add them all. Do we need to wait for that to have happened? - await Promise.all( - this.wallets.map((wallet, i) => - this.addPendingShieldNoteToPXE({ - amount: MINT_AMOUNT, - secretHash: computeSecretHash(secrets[i]), - txHash: txs[i].txHash, - accountAddress: wallet.getAddress(), - assetAddress: this.token.address, - wallet: wallet, - }), - ), - ); - await Promise.all( - this.wallets.map(async (w, i) => - (await TokenContract.at(this.token.address, w)).methods - .redeem_shield(w.getAddress(), MINT_AMOUNT, secrets[i]) - .send() - .wait({ timeout: 600 }), + this.wallets.map((w, _) => + mintTokensToPrivate(this.token, w, w.getAddress(), MINT_AMOUNT), ), ); } From 443e9e4e7b74068c736211666a97a29b25807b6b Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 19:09:28 +0000 Subject: [PATCH 16/28] WIP --- yarn-project/aztec/src/examples/token.ts | 33 ++++++----------- .../src/e2e_lending_contract.test.ts | 36 ++++--------------- .../end-to-end/src/e2e_synching.test.ts | 13 +++---- 3 files changed, 22 insertions(+), 60 deletions(-) diff --git a/yarn-project/aztec/src/examples/token.ts b/yarn-project/aztec/src/examples/token.ts index 97fc5b244d2..488377f79d7 100644 --- a/yarn-project/aztec/src/examples/token.ts +++ b/yarn-project/aztec/src/examples/token.ts @@ -1,6 +1,5 @@ import { getSingleKeyAccount } from '@aztec/accounts/single_key'; -import { type AccountWallet, Fr, Note, computeSecretHash, createPXEClient } from '@aztec/aztec.js'; -import { ExtendedNote } from '@aztec/circuit-types'; +import { type AccountWallet, BatchCall, Fr, createPXEClient } from '@aztec/aztec.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; @@ -43,26 +42,16 @@ async function main() { // Mint tokens to Alice logger.info(`Minting ${ALICE_MINT_BALANCE} more coins to Alice...`); - // Create a secret and a corresponding hash that will be used to mint funds privately - const aliceSecret = Fr.random(); - const aliceSecretHash = computeSecretHash(aliceSecret); - const receipt = await tokenAlice.methods.mint_private(ALICE_MINT_BALANCE, aliceSecretHash).send().wait(); - - const note = new Note([new Fr(ALICE_MINT_BALANCE), aliceSecretHash]); - const extendedNote = new ExtendedNote( - note, - alice.address, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await pxe.addNote(extendedNote); - - // Make the tokens spendable by redeeming them using the secret (converts the "pending shield note" created above - // to a "token note") - await tokenAlice.methods.redeem_shield(alice, ALICE_MINT_BALANCE, aliceSecret).send().wait(); - logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted and redeemed by Alice`); + // We don't have the functionality to mint to private so we mint to the Alice's address in public and transfer + // the tokens to private. We use BatchCall to speed the process up. + await new BatchCall(aliceWallet, [ + token.methods.mint_public(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), + token.methods.transfer_to_private(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), + ]) + .send() + .wait(); + + logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted by Alice and transferred to private`); const balanceAfterMint = await tokenAlice.methods.balance_of_private(alice).simulate(); logger.info(`Tokens successfully minted. New Alice's balance: ${balanceAfterMint}`); diff --git a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts index c3b0e4e6c05..1687554c577 100644 --- a/yarn-project/end-to-end/src/e2e_lending_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_lending_contract.test.ts @@ -1,19 +1,11 @@ -import { - type AccountWallet, - type CheatCodes, - type DebugLogger, - type DeployL1Contracts, - ExtendedNote, - Fr, - Note, - computeSecretHash, -} from '@aztec/aztec.js'; +import { type AccountWallet, type CheatCodes, type DebugLogger, type DeployL1Contracts, Fr } from '@aztec/aztec.js'; import { RollupAbi } from '@aztec/l1-artifacts'; import { LendingContract, PriceFeedContract, TokenContract } from '@aztec/noir-contracts.js'; import { afterAll, jest } from '@jest/globals'; import { getContract } from 'viem'; +import { mintTokensToPrivate } from './fixtures/token_utils.js'; import { ensureAccountsPubliclyDeployed, setup } from './fixtures/utils.js'; import { LendingAccount, LendingSimulator, TokenSimulator } from './simulators/index.js'; @@ -105,26 +97,10 @@ describe('e2e_lending_contract', () => { const assets = [collateralAsset, stableCoin]; const mintAmount = 10000n; for (const asset of assets) { - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - - const a = asset.methods.mint_public(lendingAccount.address, mintAmount).send(); - const b = asset.methods.mint_private(mintAmount, secretHash).send(); - await Promise.all([a, b].map(tx => tx.wait())); - - const note = new Note([new Fr(mintAmount), secretHash]); - const txHash = await b.getTxHash(); - const extendedNote = new ExtendedNote( - note, - wallet.getAddress(), - asset.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - txHash, - ); - await wallet.addNote(extendedNote); - - await asset.methods.redeem_shield(lendingAccount.address, mintAmount, secret).send().wait(); + await Promise.all([ + asset.methods.mint_public(lendingAccount.address, mintAmount).send().wait(), + mintTokensToPrivate(asset, wallet, lendingAccount.address, mintAmount), + ]); } } diff --git a/yarn-project/end-to-end/src/e2e_synching.test.ts b/yarn-project/end-to-end/src/e2e_synching.test.ts index bd25583ffce..5be8f5d4397 100644 --- a/yarn-project/end-to-end/src/e2e_synching.test.ts +++ b/yarn-project/end-to-end/src/e2e_synching.test.ts @@ -43,8 +43,9 @@ import { type Contract, type DebugLogger, Fr, - GrumpkinScalar, createDebugLogger, - sleep + GrumpkinScalar, + createDebugLogger, + sleep, } from '@aztec/aztec.js'; // eslint-disable-next-line no-restricted-imports import { ExtendedNote, L2Block, LogType, Note, type TxHash } from '@aztec/circuit-types'; @@ -61,8 +62,8 @@ import * as fs from 'fs'; import { getContract } from 'viem'; import { addAccounts } from './fixtures/snapshot_manager.js'; -import { type EndToEndContext, getPrivateKeyFromIndex, setup, setupPXEService } from './fixtures/utils.js'; import { mintTokensToPrivate } from './fixtures/token_utils.js'; +import { type EndToEndContext, getPrivateKeyFromIndex, setup, setupPXEService } from './fixtures/utils.js'; const SALT = 420; const AZTEC_GENERATE_TEST_DATA = !!process.env.AZTEC_GENERATE_TEST_DATA; @@ -178,11 +179,7 @@ class TestVariant { // Mint tokens privately if needed if (this.txComplexity == TxComplexity.PrivateTransfer) { - await Promise.all( - this.wallets.map((w, _) => - mintTokensToPrivate(this.token, w, w.getAddress(), MINT_AMOUNT), - ), - ); + await Promise.all(this.wallets.map((w, _) => mintTokensToPrivate(this.token, w, w.getAddress(), MINT_AMOUNT))); } } From 7b90e62161a9bb943478380489ccbb9f8066dd23 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 19:44:31 +0000 Subject: [PATCH 17/28] WIP --- .../src/composed/e2e_persistence.test.ts | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index a49f7b97c78..49ffde4ec77 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -11,13 +11,16 @@ import { import { type Salt } from '@aztec/aztec.js/account'; import { type AztecAddress, type CompleteAddress, Fr, deriveSigningKey } from '@aztec/circuits.js'; import { type DeployL1Contracts } from '@aztec/ethereum'; -import { TokenContract } from '@aztec/noir-contracts.js/Token'; +// We use TokenBlacklist because we want to test the persistence of manually added notes and standard token no longer +// implements TransparentNote shield flow. +import { TokenBlacklistContract } from '@aztec/noir-contracts.js/TokenBlacklist'; import { jest } from '@jest/globals'; import { mkdtemp } from 'fs/promises'; import { tmpdir } from 'os'; import { join } from 'path'; +import { BlacklistTokenContractTest, Role } from '../e2e_blacklist_token_contract/blacklist_token_contract_test.js'; import { type EndToEndContext, setup } from '../fixtures/utils.js'; jest.setTimeout(60_000); @@ -64,12 +67,17 @@ describe('Aztec persistence', () => { ownerAddress = ownerWallet.getCompleteAddress(); ownerSalt = ownerWallet.salt; - const contract = await TokenContract.deploy(ownerWallet, ownerWallet.getAddress(), 'Test token', 'TEST', 2) - .send() - .deployed(); + const contract = await TokenBlacklistContract.deploy(ownerWallet, ownerWallet.getAddress()).send().deployed(); contractInstance = contract.instance; contractAddress = contract.address; + await progressBlocksPastDelay(contract); + + const adminMinterRole = new Role().withAdmin().withMinter(); + await contract.methods.update_roles(ownerWallet.getAddress(), adminMinterRole.toNoirStruct()).send().wait(); + + await progressBlocksPastDelay(contract); + const secret = Fr.random(); const mintTxReceipt = await contract.methods.mint_private(1000n, computeSecretHash(secret)).send().wait(); @@ -84,8 +92,16 @@ describe('Aztec persistence', () => { await contract.methods.redeem_shield(ownerAddress.address, 1000n, secret).send().wait(); + await progressBlocksPastDelay(contract); + await initialContext.teardown(); - }); + }, 180_000); + + const progressBlocksPastDelay = async (contract: TokenBlacklistContract) => { + for (let i = 0; i < BlacklistTokenContractTest.DELAY; ++i) { + await contract.methods.get_roles(ownerAddress.address).send().wait(); + } + }; describe.each([ [ @@ -102,13 +118,13 @@ describe('Aztec persistence', () => { ], ])('%s', (_, contextSetup, timeout) => { let ownerWallet: AccountWallet; - let contract: TokenContract; + let contract: TokenBlacklistContract; beforeEach(async () => { context = await contextSetup(); const signingKey = deriveSigningKey(ownerSecretKey); ownerWallet = await getUnsafeSchnorrWallet(context.pxe, ownerAddress.address, signingKey); - contract = await TokenContract.at(contractAddress, ownerWallet); + contract = await TokenBlacklistContract.at(contractAddress, ownerWallet); }, timeout); afterEach(async () => { @@ -151,7 +167,7 @@ describe('Aztec persistence', () => { const initialOwnerBalance = await contract.methods.balance_of_private(ownerWallet.getAddress()).simulate(); - await contract.methods.transfer(otherWallet.getAddress(), 500n).send().wait(); + await contract.methods.transfer(ownerWallet.getAddress(), otherWallet.getAddress(), 500n, 0).send().wait(); const [ownerBalance, targetBalance] = await Promise.all([ contract.methods.balance_of_private(ownerWallet.getAddress()).simulate(), @@ -196,43 +212,43 @@ describe('Aztec persistence', () => { await context.pxe.registerRecipient(ownerAddress); const wallet = await getUnsafeSchnorrAccount(context.pxe, Fr.random(), Fr.ZERO).waitSetup(); - await expect(TokenContract.at(contractAddress, wallet)).rejects.toThrow(/has not been registered/); + await expect(TokenBlacklistContract.at(contractAddress, wallet)).rejects.toThrow(/has not been registered/); }); it("pxe does not have owner's private notes", async () => { await context.pxe.registerContract({ - artifact: TokenContract.artifact, + artifact: TokenBlacklistContract.artifact, instance: contractInstance, }); await context.pxe.registerRecipient(ownerAddress); const wallet = await getUnsafeSchnorrAccount(context.pxe, Fr.random(), Fr.ZERO).waitSetup(); - const contract = await TokenContract.at(contractAddress, wallet); + const contract = await TokenBlacklistContract.at(contractAddress, wallet); await expect(contract.methods.balance_of_private(ownerAddress.address).simulate()).resolves.toEqual(0n); }); it('has access to public storage', async () => { await context.pxe.registerContract({ - artifact: TokenContract.artifact, + artifact: TokenBlacklistContract.artifact, instance: contractInstance, }); const wallet = await getUnsafeSchnorrAccount(context.pxe, Fr.random(), Fr.ZERO).waitSetup(); - const contract = await TokenContract.at(contractAddress, wallet); + const contract = await TokenBlacklistContract.at(contractAddress, wallet); await expect(contract.methods.total_supply().simulate()).resolves.toBeGreaterThan(0n); }); it('pxe restores notes after registering the owner', async () => { await context.pxe.registerContract({ - artifact: TokenContract.artifact, + artifact: TokenBlacklistContract.artifact, instance: contractInstance, }); const ownerAccount = getUnsafeSchnorrAccount(context.pxe, ownerSecretKey, ownerSalt); await ownerAccount.register(); const ownerWallet = await ownerAccount.getWallet(); - const contract = await TokenContract.at(contractAddress, ownerWallet); + const contract = await TokenBlacklistContract.at(contractAddress, ownerWallet); await waitForAccountSynch(context.pxe, ownerAddress, { interval: 1, timeout: 10 }); @@ -256,7 +272,7 @@ describe('Aztec persistence', () => { const temporaryContext = await setup(0, { deployL1ContractsValues }, {}); await temporaryContext.pxe.registerContract({ - artifact: TokenContract.artifact, + artifact: TokenBlacklistContract.artifact, instance: contractInstance, }); @@ -264,7 +280,7 @@ describe('Aztec persistence', () => { await ownerAccount.register(); const ownerWallet = await ownerAccount.getWallet(); - const contract = await TokenContract.at(contractAddress, ownerWallet); + const contract = await TokenBlacklistContract.at(contractAddress, ownerWallet); // mint some tokens with a secret we know and redeem later on a separate PXE secret = Fr.random(); @@ -281,13 +297,13 @@ describe('Aztec persistence', () => { }); let ownerWallet: AccountWallet; - let contract: TokenContract; + let contract: TokenBlacklistContract; beforeEach(async () => { context = await setup(0, { dataDirectory, deployL1ContractsValues }, { dataDirectory }); const signingKey = deriveSigningKey(ownerSecretKey); ownerWallet = await getUnsafeSchnorrWallet(context.pxe, ownerAddress.address, signingKey); - contract = await TokenContract.at(contractAddress, ownerWallet); + contract = await TokenBlacklistContract.at(contractAddress, ownerWallet); await waitForAccountSynch(context.pxe, ownerAddress, { interval: 0.1, timeout: 5 }); }); @@ -328,8 +344,8 @@ async function addPendingShieldNoteToPXE( note, wallet.getAddress(), asset, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, + TokenBlacklistContract.storage.pending_shields.slot, + TokenBlacklistContract.notes.TransparentNote.id, txHash, ); await wallet.addNote(extendedNote); From 862c8a2ac5fafe0f78ecffb60bc158aeb06d38ac Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 19:50:05 +0000 Subject: [PATCH 18/28] WIP --- .../src/composed/e2e_sandbox_example.test.ts | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts b/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts index 60ee5474a5f..4f696381f7f 100644 --- a/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts @@ -15,6 +15,7 @@ import { import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { format } from 'util'; +import { deployToken } from '../fixtures/token_utils.js'; const { PXE_URL = 'http://localhost:8080' } = process.env; // docs:end:imports @@ -58,44 +59,12 @@ describe('e2e_sandbox_example', () => { ////////////// DEPLOY OUR TOKEN CONTRACT ////////////// const initialSupply = 1_000_000n; - logger.info(`Deploying token contract...`); - // Deploy the contract and set Alice as the admin while doing so - const contract = await TokenContract.deploy(aliceWallet, alice, 'TokenName', 'TokenSymbol', 18).send().deployed(); - logger.info(`Contract successfully deployed at address ${contract.address.toShortString()}`); - - // Create the contract abstraction and link it to Alice's wallet for future signing - const tokenContractAlice = await TokenContract.at(contract.address, aliceWallet); - - // Create a secret and a corresponding hash that will be used to mint funds privately - const aliceSecret = Fr.random(); - const aliceSecretHash = computeSecretHash(aliceSecret); - - logger.info(`Minting tokens to Alice...`); - // Mint the initial supply privately "to secret hash" - const receipt = await tokenContractAlice.methods.mint_private(initialSupply, aliceSecretHash).send().wait(); - - // Add the newly created "pending shield" note to PXE - const note = new Note([new Fr(initialSupply), aliceSecretHash]); - await aliceWallet.addNote( - new ExtendedNote( - note, - alice, - contract.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ), - ); - - // Make the tokens spendable by redeeming them using the secret (converts the "pending shield note" created above - // to a "token note") - await tokenContractAlice.methods.redeem_shield(alice, initialSupply, aliceSecret).send().wait(); - logger.info(`${initialSupply} tokens were successfully minted and redeemed by Alice`); + const tokenContractAlice = await deployToken(aliceWallet, initialSupply, logger); // docs:end:Deployment // ensure that token contract is registered in PXE - expect(await pxe.getContracts()).toEqual(expect.arrayContaining([contract.address])); + expect(await pxe.getContracts()).toEqual(expect.arrayContaining([tokenContractAlice.address])); // docs:start:Balance From aa81cac52f3bf185139a1709076b25ebb3e99801 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 19:53:52 +0000 Subject: [PATCH 19/28] WIP --- .../src/composed/e2e_sandbox_example.test.ts | 37 ++----------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts b/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts index 4f696381f7f..ff72ad0b4fc 100644 --- a/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_sandbox_example.test.ts @@ -1,21 +1,11 @@ // docs:start:imports import { getSchnorrAccount } from '@aztec/accounts/schnorr'; import { getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; -import { - ExtendedNote, - Fr, - GrumpkinScalar, - Note, - type PXE, - computeSecretHash, - createDebugLogger, - createPXEClient, - waitForPXE, -} from '@aztec/aztec.js'; -import { TokenContract } from '@aztec/noir-contracts.js/Token'; +import { Fr, GrumpkinScalar, type PXE, createDebugLogger, createPXEClient, waitForPXE } from '@aztec/aztec.js'; import { format } from 'util'; -import { deployToken } from '../fixtures/token_utils.js'; + +import { deployToken, mintTokensToPrivate } from '../fixtures/token_utils.js'; const { PXE_URL = 'http://localhost:8080' } = process.env; // docs:end:imports @@ -112,27 +102,8 @@ describe('e2e_sandbox_example', () => { // Alice is nice and she adds Bob as a minter await tokenContractAlice.methods.set_minter(bob, true).send().wait(); - const bobSecret = Fr.random(); - const bobSecretHash = computeSecretHash(bobSecret); - // Bob now has a secret 🥷 - const mintQuantity = 10_000n; - logger.info(`Minting ${mintQuantity} tokens to Bob...`); - const mintPrivateReceipt = await tokenContractBob.methods.mint_private(mintQuantity, bobSecretHash).send().wait(); - - const bobPendingShield = new Note([new Fr(mintQuantity), bobSecretHash]); - await bobWallet.addNote( - new ExtendedNote( - bobPendingShield, - bob, - contract.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - mintPrivateReceipt.txHash, - ), - ); - - await tokenContractBob.methods.redeem_shield(bob, mintQuantity, bobSecret).send().wait(); + await mintTokensToPrivate(tokenContractBob, bobWallet, bob, mintQuantity); // Check the new balances aliceBalance = await tokenContractAlice.methods.balance_of_private(alice).simulate(); From ce58bda0fbe0bcbb1225382d6871da7fc8c447b6 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 20:55:38 +0000 Subject: [PATCH 20/28] WIP --- .../end-to-end/src/e2e_fees/fees_test.ts | 24 ++++--------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts index e2b6c4f01f9..3ce49b5aa87 100644 --- a/yarn-project/end-to-end/src/e2e_fees/fees_test.ts +++ b/yarn-project/end-to-end/src/e2e_fees/fees_test.ts @@ -10,7 +10,6 @@ import { type PXE, SignerlessWallet, type TxHash, - computeSecretHash, createDebugLogger, sleep, } from '@aztec/aztec.js'; @@ -34,6 +33,7 @@ import { getContract } from 'viem'; import { MNEMONIC } from '../fixtures/fixtures.js'; import { type ISnapshotManager, addAccounts, createSnapshotManager } from '../fixtures/snapshot_manager.js'; +import { mintTokensToPrivate } from '../fixtures/token_utils.js'; import { type BalancesFn, ensureAccountsPubliclyDeployed, @@ -130,27 +130,11 @@ export class FeesTest { /** Alice mints bananaCoin tokens privately to the target address and redeems them. */ async mintPrivateBananas(amount: bigint, address: AztecAddress) { const balanceBefore = await this.bananaCoin.methods.balance_of_private(address).simulate(); - const secret = await this.mintShieldedBananas(amount, address); - await this.redeemShieldedBananas(amount, address, secret); - const balanceAfter = await this.bananaCoin.methods.balance_of_private(address).simulate(); - expect(balanceAfter).toEqual(balanceBefore + amount); - } - /** Alice mints bananaCoin tokens privately to the target address but does not redeem them yet. */ - async mintShieldedBananas(amount: bigint, address: AztecAddress) { - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - this.logger.debug(`Minting ${amount} bananas privately for ${address} with secret ${secretHash.toString()}`); - const receipt = await this.bananaCoin.methods.mint_private(amount, secretHash).send().wait(); - await this.addPendingShieldNoteToPXE(this.aliceAddress, amount, secretHash, receipt.txHash); - return secret; - } + await mintTokensToPrivate(this.bananaCoin, this.aliceWallet, address, amount); - /** Redeemer (defaults to Alice) redeems shielded bananas for the target address. */ - async redeemShieldedBananas(amount: bigint, address: AztecAddress, secret: Fr, redeemer?: AccountWallet) { - this.logger.debug(`Redeeming ${amount} bananas for ${address}`); - const bananaCoin = redeemer ? this.bananaCoin.withWallet(redeemer) : this.bananaCoin; - await bananaCoin.methods.redeem_shield(address, amount, secret).send().wait(); + const balanceAfter = await this.bananaCoin.methods.balance_of_private(address).simulate(); + expect(balanceAfter).toEqual(balanceBefore + amount); } /** Adds a pending shield transparent node for the banana coin token contract to the pxe. */ From 0b950d58f779b68f6c26e112e428f745e34f1c24 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:06:20 +0000 Subject: [PATCH 21/28] WIP --- .../src/e2e_token_contract/minting.test.ts | 73 ------------------- .../e2e_token_contract/token_contract_test.ts | 17 ++--- 2 files changed, 5 insertions(+), 85 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts index 208cfe2902c..5ea762d8b80 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts @@ -1,4 +1,3 @@ -import { Fr, type TxHash, computeSecretHash } from '@aztec/aztec.js'; import { BITSIZE_TOO_BIG_ERROR, U128_OVERFLOW_ERROR } from '../fixtures/fixtures.js'; import { TokenContractTest } from './token_contract_test.js'; @@ -63,76 +62,4 @@ describe('e2e_token_contract minting', () => { }); }); }); - - describe('Private', () => { - const secret = Fr.random(); - const amount = 10000n; - let secretHash: Fr; - let txHash: TxHash; - - beforeAll(() => { - secretHash = computeSecretHash(secret); - }); - - describe('Mint flow', () => { - it('mint_private as minter', async () => { - const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(); - tokenSim.mintPrivate(amount); - txHash = receipt.txHash; - }); - - it('redeem as recipient', async () => { - await t.addPendingShieldNoteToPXE(0, amount, secretHash, txHash); - const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); - // docs:start:debug - const receiptClaim = await txClaim.wait({ debug: true }); - // docs:end:debug - tokenSim.redeemShield(accounts[0].address, amount); - // 1 note should be created containing `amount` of tokens - const { visibleIncomingNotes } = receiptClaim.debugInfo!; - expect(visibleIncomingNotes.length).toBe(1); - expect(visibleIncomingNotes[0].note.items[0].toBigInt()).toBe(amount); - }); - }); - - describe('failure cases', () => { - it('try to redeem as recipient (double-spend) [REVERTS]', async () => { - await expect(t.addPendingShieldNoteToPXE(0, amount, secretHash, txHash)).rejects.toThrow( - 'The note has been destroyed.', - ); - await expect(asset.methods.redeem_shield(accounts[0].address, amount, secret).simulate()).rejects.toThrow( - `Assertion failed: note not popped 'notes.len() == 1'`, - ); - }); - - it('mint_private as non-minter', async () => { - await expect(asset.withWallet(wallets[1]).methods.mint_private(amount, secretHash).simulate()).rejects.toThrow( - 'Assertion failed: caller is not minter', - ); - }); - - it('mint_private as non-minter, bypassing account entrypoint', async () => { - const request = await asset.withWallet(wallets[1]).methods.mint_private(amount, secretHash).create(); - await expect(wallets[1].simulateTx(request, true, accounts[0].address)).rejects.toThrow( - 'Assertion failed: Users cannot set msg_sender in first call', - ); - }); - - it('mint >u128 tokens to overflow', async () => { - const amount = 2n ** 128n; // U128::max() + 1; - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(BITSIZE_TOO_BIG_ERROR); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.balanceOfPrivate(accounts[0].address); - expect(amount).toBeLessThan(2n ** 128n); - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); - }); - - it('mint u128', async () => { - const amount = 2n ** 128n - tokenSim.totalSupply; - await expect(asset.methods.mint_private(amount, secretHash).simulate()).rejects.toThrow(U128_OVERFLOW_ERROR); - }); - }); - }); }); diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index e6597c2745c..7f830d3ab5e 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -6,9 +6,7 @@ import { ExtendedNote, Fr, Note, - type TxHash, - computeSecretHash, - createDebugLogger, + type TxHash, createDebugLogger } from '@aztec/aztec.js'; import { DocsExampleContract, TokenContract } from '@aztec/noir-contracts.js'; @@ -22,6 +20,7 @@ import { publicDeployAccounts, } from '../fixtures/snapshot_manager.js'; import { TokenSimulator } from '../simulators/token_simulator.js'; +import { mintTokensToPrivate } from '../fixtures/token_utils.js'; const { E2E_DATA_PATH: dataPath } = process.env; @@ -140,20 +139,14 @@ export class TokenContractTest { await this.snapshotManager.snapshot( 'mint', async () => { - const { asset, accounts } = this; + const { asset, wallets } = this; const amount = 10000n; this.logger.verbose(`Minting ${amount} publicly...`); - await asset.methods.mint_public(accounts[0].address, amount).send().wait(); + await asset.methods.mint_public(wallets[0].getAddress(), amount).send().wait(); this.logger.verbose(`Minting ${amount} privately...`); - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(); - - await this.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); - await txClaim.wait({ debug: true }); + await mintTokensToPrivate(asset, wallets[0], wallets[0].getAddress(), amount); this.logger.verbose(`Minting complete.`); return { amount }; From 56abdeddfb5965708e6cc3da6fb2a678c42f1a60 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:19:06 +0000 Subject: [PATCH 22/28] WIP --- .../end-to-end/src/e2e_token_contract/minting.test.ts | 1 - .../src/e2e_token_contract/token_contract_test.ts | 5 +++-- .../end-to-end/src/shared/cross_chain_test_harness.ts | 7 ++++--- yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts | 8 ++------ 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts index 5ea762d8b80..484f6288533 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts @@ -1,4 +1,3 @@ - import { BITSIZE_TOO_BIG_ERROR, U128_OVERFLOW_ERROR } from '../fixtures/fixtures.js'; import { TokenContractTest } from './token_contract_test.js'; diff --git a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts index 7f830d3ab5e..6ede484f6d2 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/token_contract_test.ts @@ -6,7 +6,8 @@ import { ExtendedNote, Fr, Note, - type TxHash, createDebugLogger + type TxHash, + createDebugLogger, } from '@aztec/aztec.js'; import { DocsExampleContract, TokenContract } from '@aztec/noir-contracts.js'; @@ -19,8 +20,8 @@ import { createSnapshotManager, publicDeployAccounts, } from '../fixtures/snapshot_manager.js'; -import { TokenSimulator } from '../simulators/token_simulator.js'; import { mintTokensToPrivate } from '../fixtures/token_utils.js'; +import { TokenSimulator } from '../simulators/token_simulator.js'; const { E2E_DATA_PATH: dataPath } = process.env; diff --git a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts index 3b104a99cf7..c611c233e46 100644 --- a/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts +++ b/yarn-project/end-to-end/src/shared/cross_chain_test_harness.ts @@ -35,6 +35,8 @@ import { getContract, } from 'viem'; +import { mintTokensToPrivate } from '../fixtures/token_utils.js'; + // docs:start:deployAndInitializeTokenAndBridgeContracts /** * Deploy L1 token and portal, initialize portal, deploy a non native l2 token contract, its L2 bridge contract and attach is to the portal. @@ -244,9 +246,8 @@ export class CrossChainTestHarness { await this.l2Token.methods.mint_public(this.ownerAddress, amount).send().wait(); } - async mintTokensPrivateOnL2(amount: bigint, secretHash: Fr) { - const receipt = await this.l2Token.methods.mint_private(amount, secretHash).send().wait(); - await this.addPendingShieldNoteToPXE(amount, secretHash, receipt.txHash); + async mintTokensPrivateOnL2(amount: bigint) { + await mintTokensToPrivate(this.l2Token, this.ownerWallet, this.ownerAddress, amount); } async sendL2PublicTransfer(transferAmount: bigint, receiverAddress: AztecAddress) { diff --git a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts index 203ceb9a501..c3ef0f64e31 100644 --- a/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts +++ b/yarn-project/end-to-end/src/shared/uniswap_l1_l2.ts @@ -644,9 +644,7 @@ export const uniswapL1L2TestSuite = ( it("can't swap if user passes a token different to what the bridge tracks", async () => { // 1. give user private funds on L2: - const [secretForRedeemingWeth, secretHashForRedeemingWeth] = generateClaimSecret(); - await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge, secretHashForRedeemingWeth); - await wethCrossChainHarness.redeemShieldPrivatelyOnL2(wethAmountToBridge, secretForRedeemingWeth); + await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge); await wethCrossChainHarness.expectPrivateBalanceOnL2(ownerAddress, wethAmountToBridge); // 2. owner gives uniswap approval to unshield funds: @@ -802,10 +800,8 @@ export const uniswapL1L2TestSuite = ( // tests when trying to mix private and public flows: it("can't call swap_public on L1 if called swap_private on L2", async () => { // get tokens on L2: - const [secretForRedeemingWeth, secretHashForRedeemingWeth] = generateClaimSecret(); logger.info('minting weth on L2'); - await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge, secretHashForRedeemingWeth); - await wethCrossChainHarness.redeemShieldPrivatelyOnL2(wethAmountToBridge, secretForRedeemingWeth); + await wethCrossChainHarness.mintTokensPrivateOnL2(wethAmountToBridge); // Owner gives uniswap approval to unshield funds to self on its behalf logger.info('Approving uniswap to unshield funds to self on my behalf'); From 41a2bdc478f17176156c6defd117e5d4ae4b8d26 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:35:21 +0000 Subject: [PATCH 23/28] WIP --- .../end-to-end/src/sample-dapp/index.test.mjs | 25 +++--------------- yarn-project/end-to-end/src/shared/browser.ts | 26 +++---------------- 2 files changed, 7 insertions(+), 44 deletions(-) diff --git a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs index 450eb4c0fc0..7e197a9a082 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.test.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.test.mjs @@ -1,6 +1,7 @@ import { createAccount } from '@aztec/accounts/testing'; -import { Contract, ExtendedNote, Fr, Note, computeSecretHash, createPXEClient, waitForPXE } from '@aztec/aztec.js'; -import { TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; +import { createDebugLogger, createPXEClient, waitForPXE } from '@aztec/aztec.js'; + +import { deployToken } from '../fixtures/token_utils'; const { PXE_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; @@ -14,26 +15,8 @@ describe('token', () => { owner = await createAccount(pxe); recipient = await createAccount(pxe); - token = await TokenContract.deploy(owner, owner.getAddress(), 'TokenName', 'TKN', 18).send().deployed(); - const initialBalance = 69; - const secret = Fr.random(); - const secretHash = await computeSecretHash(secret); - const receipt = await token.methods.mint_private(initialBalance, secretHash).send().wait(); - - const note = new Note([new Fr(initialBalance), secretHash]); - const extendedNote = new ExtendedNote( - note, - owner.getAddress(), - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - - await pxe.addNote(extendedNote, owner.getAddress()); - - await token.methods.redeem_shield(owner.getAddress(), initialBalance, secret).send().wait(); + await deployToken(owner, initialBalance, createDebugLogger('sample_dapp')); }, 120_000); // docs:end:setup diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index 5f2ee0a153e..d0d7363ca85 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -215,10 +215,6 @@ export const browserTestSuite = ( createPXEClient, getSchnorrAccount, Contract, - Fr, - ExtendedNote, - Note, - computeSecretHash, getDeployedTestAccountsWallets, INITIAL_TEST_SECRET_KEYS, INITIAL_TEST_SIGNING_KEYS, @@ -244,7 +240,6 @@ export const browserTestSuite = ( knownAccounts.push(newAccount); } const owner = knownAccounts[0]; - const ownerAddress = owner.getAddress(); const tx = new DeployMethod( owner.getCompleteAddress().publicKeys, owner, @@ -255,25 +250,10 @@ export const browserTestSuite = ( const { contract: token, txHash } = await tx.wait(); console.log(`Contract Deployed: ${token.address}`); - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const mintPrivateReceipt = await token.methods.mint_private(initialBalance, secretHash).send().wait(); - const storageSlot = token.artifact.storageLayout['pending_shields'].slot; - - const noteTypeId = token.artifact.notes['TransparentNote'].id; - const note = new Note([new Fr(initialBalance), secretHash]); - const extendedNote = new ExtendedNote( - note, - ownerAddress, - token.address, - storageSlot, - noteTypeId, - mintPrivateReceipt.txHash, - ); - await owner.addNote(extendedNote); - - await token.methods.redeem_shield(ownerAddress, initialBalance, secret).send().wait(); + // We don't use the `mintTokensToPrivate` util as it is not available here + await token.methods.mint_public(owner.getAddress(), initialBalance).send().wait(); + await token.methods.transfer_to_private(owner.getAddress(), initialBalance).send().wait(); return [txHash.toString(), token.address.toString()]; }, From 2501ca0735c24de52898ba4d86270f49e0b66ce0 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:42:28 +0000 Subject: [PATCH 24/28] WIP --- .../writing_an_account_contract.test.ts | 37 ++++++------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index 754bf3d8943..6030d9cf911 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -3,13 +3,9 @@ import { AccountManager, AuthWitness, type AuthWitnessProvider, - type CompleteAddress, - ExtendedNote, - Fr, - GrumpkinScalar, - Note, - Schnorr, - computeSecretHash, + BatchCall, + type CompleteAddress, Fr, + GrumpkinScalar, Schnorr } from '@aztec/aztec.js'; import { SchnorrHardcodedAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrHardcodedAccount'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; @@ -66,24 +62,15 @@ describe('guides/writing_an_account_contract', () => { const token = await TokenContract.deploy(wallet, address, 'TokenName', 'TokenSymbol', 18).send().deployed(); logger.info(`Deployed token contract at ${token.address}`); - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - + // We don't have the functionality to mint to private so we mint to the minter address in public and transfer + // the tokens to the recipient in private. We use BatchCall to speed the process up. const mintAmount = 50n; - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - address, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await wallet.addNote(extendedNote); - - await token.methods.redeem_shield(address, mintAmount, secret).send().wait(); + await new BatchCall(wallet, [ + token.methods.mint_public(address, mintAmount).request(), + token.methods.transfer_to_private(address, mintAmount).request(), + ]) + .send() + .wait(); const balance = await token.methods.balance_of_private(address).simulate(); logger.info(`Balance of wallet is now ${balance}`); @@ -98,7 +85,7 @@ describe('guides/writing_an_account_contract', () => { const tokenWithWrongWallet = token.withWallet(wrongWallet); try { - await tokenWithWrongWallet.methods.mint_private(200, secretHash).prove(); + await tokenWithWrongWallet.methods.mint_public(address, 200).prove(); } catch (err) { logger.info(`Failed to send tx: ${err}`); } From 0b2328a353a0e2ba7a163b38a6a1066f2a1ba6c6 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:45:08 +0000 Subject: [PATCH 25/28] WIOP --- .../end-to-end/src/sample-dapp/index.mjs | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/yarn-project/end-to-end/src/sample-dapp/index.mjs b/yarn-project/end-to-end/src/sample-dapp/index.mjs index 988b1a883f2..167cfec2dd8 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.mjs @@ -1,8 +1,7 @@ // docs:start:imports import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; -import { ExtendedNote, Fr, Note, computeSecretHash, createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { createPXEClient, waitForPXE } from '@aztec/aztec.js'; import { fileURLToPath } from '@aztec/foundation/url'; -import { TokenContract, TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { getToken } from './contracts.mjs'; @@ -40,23 +39,7 @@ async function mintPrivateFunds(pxe) { await showPrivateBalances(pxe); const mintAmount = 20n; - const secret = Fr.random(); - const secretHash = await computeSecretHash(secret); - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - owner.getAddress(), - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - - await pxe.addNote(extendedNote, owner.getAddress()); - - await token.withWallet(owner).methods.redeem_shield(owner.getAddress(), mintAmount, secret).send().wait(); + await mintTokensToPrivate(token, owner, owner.getAddress(), mintAmount); await showPrivateBalances(pxe); } From 4fc71ad89d23bf471bbdf1e0ef3b7074ecfcbde9 Mon Sep 17 00:00:00 2001 From: benesjan Date: Tue, 29 Oct 2024 21:59:34 +0000 Subject: [PATCH 26/28] WIP --- .../token_contract/src/test/minting.nr | 184 +----------------- .../src/e2e_prover/e2e_prover_test.ts | 22 +-- .../src/guides/dapp_testing.test.ts | 69 +------ .../writing_an_account_contract.test.ts | 6 +- 4 files changed, 24 insertions(+), 257 deletions(-) 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 6d2b21207ae..bc4f877d2a3 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 @@ -1,6 +1,6 @@ use crate::test::utils; use crate::{Token, types::transparent_note::TransparentNote}; -use dep::aztec::{hash::compute_secret_hash, oracle::random::random, test::helpers::cheatcodes}; +use dep::aztec::{oracle::random::random, test::helpers::cheatcodes}; #[test] unconstrained fn mint_public_success() { @@ -56,185 +56,3 @@ unconstrained fn mint_public_failures() { utils::check_public_balance(token_contract_address, recipient, mint_for_recipient_amount); utils::check_public_balance(token_contract_address, owner, 0); } - -#[test] -unconstrained fn mint_private_success() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); - let mint_amount = 10000; - // Mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); - - Token::at(token_contract_address).mint_public(owner, mint_amount).call(&mut env.public()); - - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret).call( - &mut env.private(), - ); - - utils::check_private_balance(token_contract_address, owner, mint_amount); -} - -#[test(should_fail_with = "note not popped")] -unconstrained fn mint_private_failure_double_spend() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient) = - utils::setup(/* with_account_contracts */ false); - let mint_amount = 10000; - // Mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); - - Token::at(token_contract_address).mint_public(owner, mint_amount).call(&mut env.public()); - - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret).call( - &mut env.private(), - ); - - utils::check_private_balance(token_contract_address, owner, mint_amount); - - // Attempt to double spend - Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret).call( - &mut env.private(), - ); -} - -#[test(should_fail_with = "caller is not minter")] -unconstrained fn mint_private_failure_non_minter() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, _, recipient) = - utils::setup(/* with_account_contracts */ false); - let mint_amount = 10000; - // Try to mint some tokens impersonating recipient - env.impersonate(recipient); - - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); -} - -#[test(should_fail_with = "call to assert_max_bit_size")] -unconstrained fn mint_private_failure_overflow() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, _, _) = utils::setup(/* with_account_contracts */ false); - - // Overflow recipient - let mint_amount = 2.pow_32(128); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); -} - -#[test(should_fail_with = "attempt to add with overflow")] -unconstrained fn mint_private_failure_overflow_recipient() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _) = utils::setup(/* with_account_contracts */ false); - let mint_amount = 10000; - // Mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret).call( - &mut env.private(), - ); - - utils::check_private_balance(token_contract_address, owner, mint_amount); - - let mint_amount = 2.pow_32(128) - mint_amount; - // Mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); -} - -#[test(should_fail_with = "attempt to add with overflow")] -unconstrained fn mint_private_failure_overflow_total_supply() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient) = - utils::setup(/* with_account_contracts */ false); - let mint_amount = 10000; - // Mint some tokens - let secret_owner = random(); - let secret_recipient = random(); - let secret_hash_owner = compute_secret_hash(secret_owner); - let secret_hash_recipient = compute_secret_hash(secret_recipient); - - Token::at(token_contract_address).mint_private(mint_amount, secret_hash_owner).call( - &mut env.public(), - ); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash_recipient).call( - &mut env.public(), - ); - - // Time travel so we can read keys from the registry - env.advance_block_by(6); - - // Store 2 notes in the cache so we can redeem it for owner and recipient - env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash_owner), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - env.add_note( - &mut TransparentNote::new(mint_amount, secret_hash_recipient), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem owner's shielded tokens - env.impersonate(owner); - Token::at(token_contract_address).redeem_shield(owner, mint_amount, secret_owner).call( - &mut env.private(), - ); - - // Redeem recipient's shielded tokens - env.impersonate(recipient); - Token::at(token_contract_address).redeem_shield(recipient, mint_amount, secret_recipient).call( - &mut env.private(), - ); - - utils::check_private_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, recipient, mint_amount); - - env.impersonate(owner); - let mint_amount = 2.pow_32(128) - 2 * mint_amount; - // Try to mint some tokens - let secret = random(); - let secret_hash = compute_secret_hash(secret); - Token::at(token_contract_address).mint_private(mint_amount, secret_hash).call(&mut env.public()); -} diff --git a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts index 1b7b29c6d9d..8d1bfb5b456 100644 --- a/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts +++ b/yarn-project/end-to-end/src/e2e_prover/e2e_prover_test.ts @@ -14,7 +14,6 @@ import { Note, type PXE, type TxHash, - computeSecretHash, createDebugLogger, deployL1Contract, } from '@aztec/aztec.js'; @@ -346,24 +345,23 @@ export class FullProverTest { 'mint', async () => { const { fakeProofsAsset: asset, accounts } = this; - const amount = 10000n; + const privateAmount = 10000n; + const publicAmount = 10000n; const waitOpts = { proven: false }; - this.logger.verbose(`Minting ${amount} publicly...`); - await asset.methods.mint_public(accounts[0].address, amount).send().wait(waitOpts); + this.logger.verbose(`Minting ${privateAmount + publicAmount} publicly...`); + await asset.methods + .mint_public(accounts[0].address, privateAmount + publicAmount) + .send() + .wait(waitOpts); - this.logger.verbose(`Minting ${amount} privately...`); - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await asset.methods.mint_private(amount, secretHash).send().wait(waitOpts); + this.logger.verbose(`Transferring ${privateAmount} to private...`); + await asset.methods.transfer_to_private(accounts[0].address, privateAmount).send().wait(waitOpts); - await this.addPendingShieldNoteToPXE(0, amount, secretHash, receipt.txHash); - const txClaim = asset.methods.redeem_shield(accounts[0].address, amount, secret).send(); - await txClaim.wait({ ...waitOpts, debug: true }); this.logger.verbose(`Minting complete.`); - return { amount }; + return { amount: publicAmount }; }, async ({ amount }) => { const { diff --git a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts index 4e326a20768..1fea3491017 100644 --- a/yarn-project/end-to-end/src/guides/dapp_testing.test.ts +++ b/yarn-project/end-to-end/src/guides/dapp_testing.test.ts @@ -1,17 +1,6 @@ // docs:start:imports import { createAccount, getDeployedTestAccountsWallets } from '@aztec/accounts/testing'; -import { - type AccountWallet, - CheatCodes, - ExtendedNote, - Fr, - Note, - type PXE, - TxStatus, - computeSecretHash, - createPXEClient, - waitForPXE, -} from '@aztec/aztec.js'; +import { type AccountWallet, CheatCodes, Fr, type PXE, TxStatus, createPXEClient, waitForPXE } from '@aztec/aztec.js'; // docs:end:imports // docs:start:import_contract import { TestContract } from '@aztec/noir-contracts.js/Test'; @@ -19,6 +8,7 @@ import { TestContract } from '@aztec/noir-contracts.js/Test'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; import { U128_UNDERFLOW_ERROR } from '../fixtures/fixtures.js'; +import { mintTokensToPrivate } from '../fixtures/token_utils.js'; const { PXE_URL = 'http://localhost:8080', ETHEREUM_HOST = 'http://localhost:8545' } = process.env; @@ -51,22 +41,8 @@ describe('guides/dapp/testing', () => { expect(await token.methods.balance_of_private(recipientAddress).simulate()).toEqual(0n); const mintAmount = 20n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - recipientAddress, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await owner.addNote(extendedNote); - - await token.methods.redeem_shield(recipientAddress, mintAmount, secret).send().wait(); + await mintTokensToPrivate(token, owner, recipientAddress, mintAmount); + expect(await token.withWallet(recipient).methods.balance_of_private(recipientAddress).simulate()).toEqual(20n); }); }); @@ -91,22 +67,9 @@ describe('guides/dapp/testing', () => { expect(await token.methods.balance_of_private(recipient.getAddress()).simulate()).toEqual(0n); const recipientAddress = recipient.getAddress(); const mintAmount = 20n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await token.methods.mint_private(mintAmount, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - recipientAddress, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await owner.addNote(extendedNote); - - await token.methods.redeem_shield(recipientAddress, mintAmount, secret).send().wait(); + + await mintTokensToPrivate(token, owner, recipientAddress, mintAmount); + expect(await token.withWallet(recipient).methods.balance_of_private(recipientAddress).simulate()).toEqual(20n); }); }); @@ -131,22 +94,8 @@ describe('guides/dapp/testing', () => { const ownerAddress = owner.getAddress(); const mintAmount = 100n; - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - const receipt = await token.methods.mint_private(100n, secretHash).send().wait(); - - const note = new Note([new Fr(mintAmount), secretHash]); - const extendedNote = new ExtendedNote( - note, - ownerAddress, - token.address, - TokenContract.storage.pending_shields.slot, - TokenContract.notes.TransparentNote.id, - receipt.txHash, - ); - await owner.addNote(extendedNote); - - await token.methods.redeem_shield(ownerAddress, 100n, secret).send().wait(); + + await mintTokensToPrivate(token, owner, ownerAddress, mintAmount); // docs:start:calc-slot cheats = await CheatCodes.create(ETHEREUM_HOST, pxe); diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index 6030d9cf911..a73e8fbd0b8 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -4,8 +4,10 @@ import { AuthWitness, type AuthWitnessProvider, BatchCall, - type CompleteAddress, Fr, - GrumpkinScalar, Schnorr + type CompleteAddress, + Fr, + GrumpkinScalar, + Schnorr, } from '@aztec/aztec.js'; import { SchnorrHardcodedAccountContractArtifact } from '@aztec/noir-contracts.js/SchnorrHardcodedAccount'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; From 3dee66f4da19d651c3935afcf5f025c1c4fa4681 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 30 Oct 2024 01:39:52 +0000 Subject: [PATCH 27/28] docs fixes --- docs/docs/guides/developer_guides/js_apps/test.md | 2 +- .../smart_contracts/writing_contracts/common_patterns/index.md | 2 +- yarn-project/end-to-end/src/composed/e2e_persistence.test.ts | 2 ++ .../src/e2e_token_contract/private_transfer_recursion.test.ts | 2 ++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docs/guides/developer_guides/js_apps/test.md b/docs/docs/guides/developer_guides/js_apps/test.md index 3c604d2e27f..a290623a51c 100644 --- a/docs/docs/guides/developer_guides/js_apps/test.md +++ b/docs/docs/guides/developer_guides/js_apps/test.md @@ -59,7 +59,7 @@ You can use the `debug` option in the `wait` method to get more information abou This debug information will be populated in the transaction receipt. You can log it to the console or use it to make assertions about the transaction. -#include_code debug /yarn-project/end-to-end/src/e2e_token_contract/minting.test.ts typescript +#include_code debug /yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts typescript You can also log directly from Aztec contracts. Read [this guide](../../../reference/developer_references/debugging.md#logging-in-aztecnr) for some more information. diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md index fdcc1af2b19..e4a9a3e5eb7 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/common_patterns/index.md @@ -93,7 +93,7 @@ When you send someone a note, the note hash gets added to the note hash tree. To 1. When sending someone a note, use `encrypt_and_emit_note` (the function encrypts the log in such a way that only a recipient can decrypt it). PXE then tries to decrypt all the encrypted logs, and stores the successfully decrypted one. [More info here](../how_to_emit_event.md) 2. Manually using `pxe.addNote()` - If you choose to not emit logs to save gas or when creating a note in the public domain and want to consume it in private domain (`encrypt_and_emit_note` shouldn't be called in the public domain because everything is public), like in the previous section where we created a TransparentNote in public. -#include_code pxe_add_note yarn-project/end-to-end/src/e2e_cheat_codes.test.ts typescript +#include_code pxe_add_note yarn-project/end-to-end/src/composed/e2e_persistence.test.ts typescript In the token contract, TransparentNotes are stored in a set called "pending_shields" which is in storage slot 5tutorials/tutorials/codealong/contract_tutorials/token_contract.md#contract-storage) diff --git a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts index 49ffde4ec77..783d5ca6fea 100644 --- a/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts +++ b/yarn-project/end-to-end/src/composed/e2e_persistence.test.ts @@ -339,6 +339,7 @@ async function addPendingShieldNoteToPXE( secretHash: Fr, txHash: TxHash, ) { + // docs:start:pxe_add_note const note = new Note([new Fr(amount), secretHash]); const extendedNote = new ExtendedNote( note, @@ -349,4 +350,5 @@ async function addPendingShieldNoteToPXE( txHash, ); await wallet.addNote(extendedNote); + // docs:end:pxe_add_note } diff --git a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts index cdb65ecca6a..fd7f8df6a22 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract/private_transfer_recursion.test.ts @@ -33,7 +33,9 @@ describe('e2e_token_contract private transfer recursion', () => { // itself to consume them all (since it retrieves 2 notes on the first pass and 8 in each subsequent pass). const totalNotes = 16; const totalBalance = await mintNotes(Array(totalNotes).fill(10n)); + // docs:start:debug const tx = await asset.methods.transfer(accounts[1].address, totalBalance).send().wait({ debug: true }); + // docs:end:debug // We should have nullified all notes, plus an extra nullifier for the transaction expect(tx.debugInfo?.nullifiers.length).toBe(totalNotes + 1); From 0b264e0c75dac949aaba9bd92d82ac958b6e2241 Mon Sep 17 00:00:00 2001 From: benesjan Date: Wed, 30 Oct 2024 02:39:34 +0000 Subject: [PATCH 28/28] cleanup --- .../contracts/token_contract/src/test/utils.nr | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 c16207918e4..d1a77dd924a 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 @@ -1,4 +1,7 @@ -use dep::aztec::{ +use crate::{Token, types::transparent_note::TransparentNote}; +use dep::uint_note::uint_note::UintNote; +use aztec::{ + keys::getters::get_public_keys, oracle::{ execution::{get_block_number, get_contract_address}, random::random, @@ -9,10 +12,6 @@ use dep::aztec::{ test::helpers::{cheatcodes, test_environment::TestEnvironment}, }; -use crate::{Token, types::transparent_note::TransparentNote}; -use dep::uint_note::uint_note::UintNote; -use aztec::keys::getters::get_public_keys; - pub unconstrained fn setup( with_account_contracts: bool, ) -> (&mut TestEnvironment, AztecAddress, AztecAddress, AztecAddress) {