Skip to content

Commit

Permalink
feat!: note type ids (AztecProtocol#4500)
Browse files Browse the repository at this point in the history
Closes AztecProtocol#2820.
Additional context and design discussion can be found [here](https://hackmd.io/@57cqwgSpQY23bzke_Ch2ig/rJsw1tS9T).
  • Loading branch information
nventuro authored Feb 12, 2024
1 parent db803bd commit e1da2fd
Show file tree
Hide file tree
Showing 91 changed files with 739 additions and 127 deletions.
1 change: 1 addition & 0 deletions boxes/blank/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ contract Blank {
contract_address: AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; 0]
) -> pub [Field; 4] {
[0, 0, 0, 0]
Expand Down
4 changes: 2 additions & 2 deletions boxes/token/src/contracts/Nargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ compiler_version = ">=0.18.0"
type = "contract"

[dependencies]
value_note = { path = "../../../../yarn-project/aztec-nr/value-note"}
aztec = { path = "../../../../yarn-project/aztec-nr/aztec" }
safe_math = { path = "../../../../yarn-project/aztec-nr/safe-math" }
compressed_string = {path = "../../../../yarn-project/aztec-nr/compressed-string"}
authwit = { path = "../../../../yarn-project/aztec-nr/authwit" }
aztec = { path = "../../../../yarn-project/aztec-nr/aztec" }
101 changes: 84 additions & 17 deletions boxes/token/src/contracts/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ contract Token {
use dep::std::option::Option;

use dep::safe_math::SafeU120;
use dep::compressed_string::FieldCompressedString;

use dep::aztec::{
note::{
Expand All @@ -22,10 +23,10 @@ contract Token {
utils as note_utils,
},
hash::{compute_secret_hash},
state_vars::{map::Map, public_state::PublicState, set::Set},
state_vars::{map::Map, public_state::PublicState, stable_public_state::StablePublicState, set::Set},
protocol_types::{
abis::function_selector::FunctionSelector,
address::AztecAddress,
address::AztecAddress
}
};

Expand All @@ -41,7 +42,7 @@ contract Token {
use crate::types::{
transparent_note::TransparentNote,
token_note::{TokenNote, TOKEN_NOTE_LEN},
balances_map::{BalancesMap},
balances_map::BalancesMap
};
// docs:end::imports

Expand All @@ -51,24 +52,35 @@ contract Token {
admin: PublicState<AztecAddress>,
// docs:end:storage_admin
// docs:start:storage_minters
minters: Map<AztecAddress, PublicState<bool>>,
minters: Map<AztecAddress, PublicState<bool>>,
// docs:end:storage_minters
// docs:start:storage_balances
balances: BalancesMap<TokenNote>,
// docs:end:storage_balances
total_supply: PublicState<SafeU120>,
total_supply: PublicState<SafeU120>,
// docs:start:storage_pending_shields
pending_shields: Set<TransparentNote>,
pending_shields: Set<TransparentNote>,
// docs:end:storage_pending_shields
public_balances: Map<AztecAddress, PublicState<SafeU120>>,
symbol: StablePublicState<FieldCompressedString>,
name: StablePublicState<FieldCompressedString>,
// docs:start:storage_decimals
decimals: StablePublicState<u8>,
// docs:end:storage_decimals
}
// docs:end:storage_struct

// docs:start:constructor
#[aztec(private)]
fn constructor(admin: AztecAddress) {
let selector = FunctionSelector::from_signature("_initialize((Field))");
context.call_public_function(context.this_address(), selector, [admin.to_field()]);
fn constructor(admin: AztecAddress, name: str<31>, symbol: str<31>, decimals: u8) {
let selector = FunctionSelector::from_signature("_initialize((Field),(Field),(Field),u8)");
let name_s = FieldCompressedString::from_string(name);
let symbol_s = FieldCompressedString::from_string(symbol);
context.call_public_function(
context.this_address(),
selector,
[admin.to_field(), name_s.serialize()[0], symbol_s.serialize()[0], decimals as Field]
);
}
// docs:end:constructor

Expand All @@ -82,6 +94,52 @@ contract Token {
}
// docs:end:set_admin

#[aztec(public)]
fn public_get_name() -> pub FieldCompressedString {
storage.name.read_public()
}

#[aztec(private)]
fn private_get_name() -> pub FieldCompressedString {
storage.name.read_private()
}

unconstrained fn un_get_name() -> pub [u8; 31] {
storage.name.read_public().to_bytes()
}

#[aztec(public)]
fn public_get_symbol() -> pub FieldCompressedString {
storage.symbol.read_public()
}

#[aztec(private)]
fn private_get_symbol() -> pub FieldCompressedString {
storage.symbol.read_private()
}

unconstrained fn un_get_symbol() -> pub [u8; 31] {
storage.symbol.read_public().to_bytes()
}

#[aztec(public)]
fn public_get_decimals() -> pub u8 {
// docs:start:read_decimals_public
storage.decimals.read_public()
// docs:end:read_decimals_public
}

#[aztec(private)]
fn private_get_decimals() -> pub u8 {
// docs:start:read_decimals_private
storage.decimals.read_private()
// docs:end:read_decimals_private
}

unconstrained fn un_get_decimals() -> pub u8 {
storage.decimals.read_public()
}

// docs:start:set_minter
#[aztec(public)]
fn set_minter(minter: AztecAddress, approve: bool) {
Expand Down Expand Up @@ -188,12 +246,12 @@ contract Token {
fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) {
let pending_shields = storage.pending_shields;
let secret_hash = compute_secret_hash(secret);
// Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount, Option::none())) and secret_hash
// stored in field with index 1 (select(1, secret_hash, Option::none())).
// Get 1 note (set_limit(1)) which has amount stored in field with index 0 (select(0, amount)) and secret_hash
// stored in field with index 1 (select(1, secret_hash)).
let options = NoteGetterOptions::new().select(0, amount, Option::none()).select(1, secret_hash, Option::none()).set_limit(1);
let notes = pending_shields.get_notes(options);
let note = notes[0].unwrap_unchecked();
// Remove the note from the pending shields set
// Remove the note from the pending shields set
pending_shields.remove(note);

// Add the token note to user's balances set
Expand Down Expand Up @@ -254,10 +312,20 @@ contract Token {

// docs:start:initialize
#[aztec(public)]
internal fn _initialize(new_admin: AztecAddress) {
internal fn _initialize(
new_admin: AztecAddress,
name: FieldCompressedString,
symbol: FieldCompressedString,
decimals: u8
) {
assert(!new_admin.is_zero(), "invalid admin");
storage.admin.write(new_admin);
storage.minters.at(new_admin).write(true);
storage.name.initialize(name);
storage.symbol.initialize(symbol);
// docs:start:initialize_decimals
storage.decimals.initialize(decimals);
// docs:end:initialize_decimals
}
// docs:end:initialize

Expand Down Expand Up @@ -312,9 +380,6 @@ contract Token {
}
// docs:end:balance_of_public

// Below this point is the stuff of nightmares.
// This should ideally not be required. What do we do if vastly different types of serialized_notes?

// docs:start:compute_note_hash_and_nullifier
// Computes note hash and nullifier.
// Note 1: Needs to be defined by every contract producing logs.
Expand All @@ -323,10 +388,12 @@ contract Token {
contract_address: AztecAddress,
nonce: Field,
storage_slot: Field,
note_type_id: Field,
serialized_note: [Field; TOKEN_NOTE_LEN]
) -> pub [Field; 4] {
let note_header = NoteHeader::new(contract_address, nonce, storage_slot);
if (storage_slot == storage.pending_shields.get_storage_slot()) {

if (note_type_id == TransparentNote::get_note_type_id()) {
note_utils::compute_note_hash_and_nullifier(
TransparentNote::deserialize_content,
note_header,
Expand Down
1 change: 0 additions & 1 deletion boxes/token/src/contracts/src/types/balances_map.nr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use dep::std::option::Option;
use dep::safe_math::SafeU120;
use dep::aztec::{
context::Context,
hash::pedersen_hash,
protocol_types::{
address::AztecAddress,
constants::MAX_READ_REQUESTS_PER_CALL,
Expand Down
9 changes: 8 additions & 1 deletion boxes/token/src/contracts/src/types/token_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -103,11 +103,18 @@ impl NoteInterface<TOKEN_NOTE_LEN> for TokenNote {
context,
(*context).this_address(),
slot,
Self::get_note_type_id(),
encryption_pub_key,
self.serialize_content(),
);
}
}
}

fn get_note_type_id() -> Field {
// TODO(#4519): autogenerate
// python -c "print(int(''.join(str(ord(c)) for c in 'TokenNote')))"
8411110710111078111116101
}
}

impl OwnedNote for TokenNote {
Expand Down
6 changes: 6 additions & 0 deletions boxes/token/src/contracts/src/types/transparent_note.nr
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,12 @@ impl NoteInterface<TRANSPARENT_NOTE_LEN> for TransparentNote {
fn broadcast(self, context: &mut PrivateContext, slot: Field) {
assert(false, "TransparentNote does not support broadcast");
}

fn get_note_type_id() -> Field {
// TODO(#4519): autogenerate
// python -c "print(int(''.join(str(ord(c)) for c in 'TransparentNote')))"
84114971101151129711410111011678111116101
}
}

impl TransparentNote {
Expand Down
17 changes: 15 additions & 2 deletions boxes/token/src/tests/token.contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ import { afterEach, beforeAll, expect, jest } from '@jest/globals';
import { setupEnvironment } from '../environment/index.js';

const TIMEOUT = 60_000;
const TOKEN_NAME = 'Aztec Token';
const TOKEN_SYMBOL = 'AZT';
const TOKEN_DECIMALS = 18n;

describe('e2e_token_contract', () => {
jest.setTimeout(TIMEOUT);
Expand All @@ -36,8 +39,16 @@ describe('e2e_token_contract', () => {

const addPendingShieldNoteToPXE = async (accountIndex: number, amount: bigint, secretHash: Fr, txHash: TxHash) => {
const storageSlot = new Fr(5); // The storage slot of `pending_shields` is 5.
const noteTypeId = new Fr(84114971101151129711410111011678111116101n); // TransparentNote
const note = new Note([new Fr(amount), secretHash]);
const extendedNote = new ExtendedNote(note, accounts[accountIndex].address, asset.address, storageSlot, txHash);
const extendedNote = new ExtendedNote(
note,
accounts[accountIndex].address,
asset.address,
storageSlot,
noteTypeId,
txHash,
);
await wallets[accountIndex].addNote(extendedNote);
};

Expand All @@ -51,7 +62,9 @@ describe('e2e_token_contract', () => {
logger(`Accounts: ${accounts.map(a => a.toReadableString())}`);
logger(`Wallets: ${wallets.map(w => w.getAddress().toString())}`);

asset = await TokenContract.deploy(wallets[0], accounts[0].address).send().deployed();
asset = await TokenContract.deploy(wallets[0], accounts[0], TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS)
.send()
.deployed();
logger(`Token deployed to ${asset.address}`);
tokenSim = new TokenSimulator(
asset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ Add a new function into your contract as shown below:

#include_code nullifier /yarn-project/noir-contracts/contracts/counter_contract/src/main.nr rust

Here, we're computing both the note hash and the nullifier. The nullifier computation uses Aztec’s `compute_note_hash_and_nullifier` function, which takes details about the note's attributes eg contract address, nonce, storage slot, and preimage.
Here, we're computing both the note hash and the nullifier. The nullifier computation uses Aztec’s `compute_note_hash_and_nullifier` function, which takes details about the note's attributes eg contract address, nonce, storage slot, type id, and preimage.

## Getting a counter

Expand Down
2 changes: 1 addition & 1 deletion docs/docs/developers/tutorials/writing_token_contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ contract Token {

unconstrained fn balance_of_public(owner: AztecAddress) -> Field {}

unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] {}
unconstrained fn compute_note_hash_and_nullifier(contract_address: Field, nonce: Field, storage_slot: Field, note_type_id: Field, serialized_note: [Field; VALUE_NOTE_LEN]) -> [Field; 4] {}
}
```

Expand Down
Loading

0 comments on commit e1da2fd

Please sign in to comment.