diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/address_note.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/address_note.md index 8a2c8db47fc..3c831979e22 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/address_note.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/address_note.md @@ -25,12 +25,12 @@ address_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#i ## Working with AddressNote -### Creating a new note +### Creating a new note Creating a new `AddressNote` takes the following args: - `address` (`AztecAddress`): the address to store in the AddressNote -- `npk_m_hash` (`Field`): the master nullifier public key hash of the user +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note #include_code addressnote_new noir-projects/noir-contracts/contracts/escrow_contract/src/main.nr rust diff --git a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/value_note.md b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/value_note.md index 6cd82547c1a..c0511785dba 100644 --- a/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/value_note.md +++ b/docs/docs/guides/developer_guides/smart_contracts/writing_contracts/notes/value_note.md @@ -21,7 +21,7 @@ value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#inc ### In your contract -#include_code import_valuenote noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust +#include_code import_valuenote noir-projects/noir-contracts/contracts/child_contract/src/main.nr rust ## Working with ValueNote @@ -30,11 +30,9 @@ value_note = { git="https://github.com/AztecProtocol/aztec-packages/", tag="#inc Creating a new `ValueNote` takes the following args: - `value` (`Field`): the value of the ValueNote -- `npk_m_hash` (`Field`): the master nullifier public key hash of the user +- `owner` (`AztecAddress`): owner is the party whose nullifying key can be used to spend the note -#include_code valuenote_new noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr rust - -In this example, `amount` is the `value` and the `npk_m_hash` of the donor was computed earlier. +#include_code valuenote_new noir-projects/noir-contracts/contracts/child_contract/src/main.nr rust ### Getting a balance diff --git a/noir-projects/aztec-nr/uint-note/src/uint_note.nr b/noir-projects/aztec-nr/uint-note/src/uint_note.nr index 368f57bf30d..4da883eb0e2 100644 --- a/noir-projects/aztec-nr/uint-note/src/uint_note.nr +++ b/noir-projects/aztec-nr/uint-note/src/uint_note.nr @@ -6,12 +6,13 @@ use dep::aztec::{ prelude::{NoteHeader, NullifiableNote, PrivateContext}, protocol_types::{ address::AztecAddress, constants::GENERATOR_INDEX__NOTE_NULLIFIER, - hash::poseidon2_hash_with_separator, + hash::poseidon2_hash_with_separator, traits::Serialize, }, }; // docs:start:UintNote #[partial_note(quote {value})] +#[derive(Serialize)] pub struct UintNote { // The amount of tokens in the note value: U128, diff --git a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr index e7658298f0a..d4aa7cc591f 100644 --- a/noir-projects/noir-contracts/contracts/child_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/child_contract/src/main.nr @@ -11,7 +11,9 @@ contract Child { note::note_getter_options::NoteGetterOptions, utils::comparison::Comparator, }; + // docs:start:import_valuenote use dep::value_note::value_note::ValueNote; + // docs:end:import_valuenote #[storage] struct Storage { @@ -55,7 +57,10 @@ contract Child { #[private] fn private_set_value(new_value: Field, owner: AztecAddress) -> Field { + // docs:start:valuenote_new let mut note = ValueNote::new(new_value, owner); + // docs:end:valuenote_new + storage.a_map_with_private_values.at(owner).insert(&mut note).emit(encode_and_encrypt_note( &mut context, owner, diff --git a/noir-projects/noir-contracts/contracts/claim_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/claim_contract/Nargo.toml index 7d81995453f..be7b8d3d067 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/claim_contract/Nargo.toml @@ -6,5 +6,5 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } -value_note = { path = "../../../aztec-nr/value-note" } +uint_note = { path = "../../../aztec-nr/uint-note" } token = { path = "../token_contract" } diff --git a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr index 50feeca54f3..8d368e20759 100644 --- a/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/claim_contract/src/main.nr @@ -8,7 +8,7 @@ contract Claim { protocol_types::address::AztecAddress, state_vars::PublicImmutable, }; - use dep::value_note::value_note::ValueNote; + use dep::uint_note::uint_note::UintNote; use token::Token; #[storage] @@ -27,13 +27,14 @@ contract Claim { } #[private] - fn claim(proof_note: ValueNote, recipient: AztecAddress) { + fn claim(proof_note: UintNote, recipient: AztecAddress) { // 1) Check that the note corresponds to the target contract and belongs to the sender let target_address = storage.target_contract.read(); assert( target_address == proof_note.header.contract_address, "Note does not correspond to the target contract", ); + assert_eq(proof_note.owner, context.msg_sender(), "Note does not belong to the sender"); // 2) Prove that the note hash exists in the note hash tree let header = context.get_block_header(); @@ -51,9 +52,8 @@ contract Claim { context.push_nullifier(nullifier); // 4) Finally we mint the reward token to the sender of the transaction - // TODO(benesjan): Instead of ValueNote use UintNote to avoid the conversion to U128 below. - Token::at(storage.reward_token.read()) - .mint_to_public(recipient, U128::from_integer(proof_note.value)) - .enqueue(&mut context); + Token::at(storage.reward_token.read()).mint_to_public(recipient, proof_note.value).enqueue( + &mut context, + ); } } diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/crowdfunding_contract/Nargo.toml index 69185126c38..70f790e0e3b 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/Nargo.toml +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/Nargo.toml @@ -6,6 +6,6 @@ type = "contract" [dependencies] aztec = { path = "../../../aztec-nr/aztec" } -value_note = { path = "../../../aztec-nr/value-note" } +uint_note = { path = "../../../aztec-nr/uint-note" } token = { path = "../token_contract" } router = { path = "../router_contract" } diff --git a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr index 46a48127d47..d1963ad50af 100644 --- a/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/crowdfunding_contract/src/main.nr @@ -18,11 +18,9 @@ contract Crowdfunding { unencrypted_logs::unencrypted_event_emission::encode_event, utils::comparison::Comparator, }; - use std::meta::derive; - // docs:start:import_valuenote - use dep::value_note::value_note::ValueNote; - // docs:end:import_valuenote + use dep::uint_note::uint_note::UintNote; use router::utils::privately_check_timestamp; + use std::meta::derive; use token::Token; // docs:end:all-deps @@ -43,7 +41,7 @@ contract Crowdfunding { // End of the crowdfunding campaign after which no more donations are accepted deadline: PublicImmutable, // Notes emitted to donors when they donate (can be used as proof to obtain rewards, eg in Claim contracts) - donation_receipts: PrivateSet, + donation_receipts: PrivateSet, } // docs:end:storage @@ -81,11 +79,8 @@ contract Crowdfunding { // docs:end:do-transfer // 3) Create a value note for the donor so that he can later on claim a rewards token in the Claim // contract by proving that the hash of this note exists in the note hash tree. - // docs:start:valuenote_new - // TODO(benesjan): Instead of ValueNote use UintNote to avoid the conversion to a Field below. - let mut note = ValueNote::new(amount.to_field(), donor); + let mut note = UintNote::new(amount, donor); - // docs:end:valuenote_new storage.donation_receipts.insert(&mut note).emit(encode_and_encrypt_note( &mut context, donor, 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 d9cfb864482..0b7ed8c8f66 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,7 +1,5 @@ -import { createAccounts } from '@aztec/accounts/testing'; import { type AccountWallet, - type AztecNode, type CheatCodes, Fr, HashedValues, @@ -21,7 +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'; +import { setup } from './fixtures/utils.js'; jest.setTimeout(200_000); @@ -39,10 +37,8 @@ describe('e2e_crowdfunding_and_claim', () => { decimals: 18n, }; - let teardownA: () => Promise; - let teardownB: () => Promise; + let teardown: () => Promise; - let aztecNode: AztecNode; let operatorWallet: AccountWallet; let donorWallets: AccountWallet[]; let wallets: AccountWallet[]; @@ -59,10 +55,10 @@ describe('e2e_crowdfunding_and_claim', () => { let cheatCodes: CheatCodes; let deadline: number; // end of crowdfunding period - let valueNote!: any; + let uintNote!: any; beforeAll(async () => { - ({ cheatCodes, teardown: teardownA, logger, pxe, wallets, aztecNode } = await setup(3)); + ({ cheatCodes, teardown, logger, pxe, wallets } = await setup(3)); operatorWallet = wallets[0]; donorWallets = wallets.slice(1); @@ -126,8 +122,7 @@ describe('e2e_crowdfunding_and_claim', () => { }); afterAll(async () => { - await teardownA(); - await teardownB?.(); + await teardown(); }); // Processes unique note such that it can be passed to a claim function of Claim contract @@ -142,7 +137,7 @@ describe('e2e_crowdfunding_and_claim', () => { note_hash_counter: 0, // set as 0 as note is not transient nonce: uniqueNote.nonce, }, - value: uniqueNote.note.items[0], + value: uniqueNote.note.items[0].toBigInt(), // We convert to bigint as Fr is not serializable to U128 // eslint-disable-next-line camelcase owner: AztecAddress.fromField(uniqueNote.note.items[1]), randomness: uniqueNote.note.items[2], @@ -171,14 +166,14 @@ describe('e2e_crowdfunding_and_claim', () => { debug: true, }); - // Get the notes emitted by the Crowdfunding contract and check that only 1 was emitted (the value note) + // Get the notes emitted by the Crowdfunding contract and check that only 1 was emitted (the UintNote) await crowdfundingContract.withWallet(donorWallets[0]).methods.sync_notes().simulate(); const notes = await donorWallets[0].getNotes({ txHash: donateTxReceipt.txHash }); const filteredNotes = notes.filter(x => x.contractAddress.equals(crowdfundingContract.address)); expect(filteredNotes!.length).toEqual(1); - // Set the value note in a format which can be passed to claim function - valueNote = processUniqueNote(filteredNotes![0]); + // Set the UintNote in a format which can be passed to claim function + uintNote = processUniqueNote(filteredNotes![0]); } // 3) We claim the reward token via the Claim contract @@ -188,7 +183,7 @@ describe('e2e_crowdfunding_and_claim', () => { await claimContract .withWallet(donorWallets[0]) - .methods.claim(valueNote, donorWallets[0].getAddress()) + .methods.claim(uintNote, donorWallets[0].getAddress()) .send() .wait(); } @@ -218,66 +213,58 @@ describe('e2e_crowdfunding_and_claim', () => { it('cannot claim twice', async () => { // The first claim was executed in the previous test await expect( - claimContract.withWallet(donorWallets[0]).methods.claim(valueNote, donorWallets[0].getAddress()).send().wait(), + claimContract.withWallet(donorWallets[0]).methods.claim(uintNote, donorWallets[0].getAddress()).send().wait(), ).rejects.toThrow(); }); - it('cannot claim without access to the nsk_app tied to the npk_m specified in the proof note', async () => { + it('cannot claim with a different address than the one that donated', async () => { const donationAmount = 1000n; + + const donorWallet = donorWallets[1]; + const unrelatedWallet = donorWallets[0]; + + // 1) We permit the crowdfunding contract to pull the donation amount from the donor's wallet { const action = donationToken - .withWallet(donorWallets[1]) - .methods.transfer_in_private(donorWallets[1].getAddress(), crowdfundingContract.address, donationAmount, 0); - const witness = await donorWallets[1].createAuthWit({ caller: crowdfundingContract.address, action }); - await donorWallets[1].addAuthWitness(witness); + .withWallet(donorWallet) + .methods.transfer_in_private(donorWallet.getAddress(), crowdfundingContract.address, donationAmount, 0); + const witness = await donorWallet.createAuthWit({ caller: crowdfundingContract.address, action }); + await donorWallet.addAuthWitness(witness); } // 2) We donate to the crowdfunding contract - const donateTxReceipt = await crowdfundingContract - .withWallet(donorWallets[1]) + .withWallet(donorWallet) .methods.donate(donationAmount) .send() .wait({ debug: true, }); - // Get the notes emitted by the Crowdfunding contract and check that only 1 was emitted (the value note) - await crowdfundingContract.withWallet(donorWallets[0]).methods.sync_notes().simulate(); - const notes = await donorWallets[0].getNotes({ txHash: donateTxReceipt.txHash }); + // Get the notes emitted by the Crowdfunding contract and check that only 1 was emitted (the UintNote) + await crowdfundingContract.withWallet(unrelatedWallet).methods.sync_notes().simulate(); + const notes = await unrelatedWallet.getNotes({ txHash: donateTxReceipt.txHash }); const filtered = notes.filter(x => x.contractAddress.equals(crowdfundingContract.address)); expect(filtered!.length).toEqual(1); - // Set the value note in a format which can be passed to claim function + // Set the UintNote in a format which can be passed to claim function const anotherDonationNote = processUniqueNote(filtered![0]); - // We create an unrelated pxe and wallet without access to the nsk_app that correlates to the npk_m specified in the proof note. - let unrelatedWallet: AccountWallet; - { - const { pxe: pxeB, teardown: _teardown } = await setupPXEService(aztecNode!, {}, undefined, true); - teardownB = _teardown; - [unrelatedWallet] = await createAccounts(pxeB, 1); - await pxeB.registerContract({ - artifact: ClaimContract.artifact, - instance: claimContract.instance, - }); - } - // 3) We try to claim the reward token via the Claim contract with the unrelated wallet { await expect( claimContract .withWallet(unrelatedWallet) - .methods.claim(anotherDonationNote, unrelatedWallet.getAddress()) + .methods.claim(anotherDonationNote, donorWallet.getAddress()) .send() .wait(), - ).rejects.toThrow('No public key registered for address'); + ).rejects.toThrow('Note does not belong to the sender'); } }); it('cannot claim with a non-existent note', async () => { - // We get a non-existent note by copy the value note and change the randomness to a random value - const nonExistentNote = { ...valueNote }; + // We get a non-existent note by copy the UintNote and change the randomness to a random value + const nonExistentNote = { ...uintNote }; nonExistentNote.randomness = Fr.random(); await expect(