Skip to content

Commit

Permalink
Merge 612e2e8 into 6630e80
Browse files Browse the repository at this point in the history
  • Loading branch information
benesjan authored Jan 29, 2025
2 parents 6630e80 + 612e2e8 commit feed6a9
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down
3 changes: 2 additions & 1 deletion noir-projects/aztec-nr/uint-note/src/uint_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Context> {
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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();
Expand All @@ -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,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -43,7 +41,7 @@ contract Crowdfunding {
// End of the crowdfunding campaign after which no more donations are accepted
deadline: PublicImmutable<u64, Context>,
// Notes emitted to donors when they donate (can be used as proof to obtain rewards, eg in Claim contracts)
donation_receipts: PrivateSet<ValueNote, Context>,
donation_receipts: PrivateSet<UintNote, Context>,
}
// docs:end:storage

Expand Down Expand Up @@ -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,
Expand Down
73 changes: 30 additions & 43 deletions yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { createAccounts } from '@aztec/accounts/testing';
import {
type AccountWallet,
type AztecNode,
type CheatCodes,
Fr,
HashedValues,
Expand All @@ -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);

Expand All @@ -39,10 +37,8 @@ describe('e2e_crowdfunding_and_claim', () => {
decimals: 18n,
};

let teardownA: () => Promise<void>;
let teardownB: () => Promise<void>;
let teardown: () => Promise<void>;

let aztecNode: AztecNode;
let operatorWallet: AccountWallet;
let donorWallets: AccountWallet[];
let wallets: AccountWallet[];
Expand All @@ -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);

Expand Down Expand Up @@ -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
Expand All @@ -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],
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
Expand Down Expand Up @@ -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(
Expand Down

0 comments on commit feed6a9

Please sign in to comment.