From 98a4f0ec8ba4ab29ac56d71a4c763d2ee773bb87 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 1 Mar 2024 18:25:44 -0300 Subject: [PATCH 1/6] feat: Add init check by default to public fns --- .../contracts/stateful_test_contract/src/main.nr | 2 +- noir/noir-repo/aztec_macros/src/lib.rs | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr index 6e51a861eea..031d5422dcf 100644 --- a/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/stateful_test_contract/src/main.nr @@ -63,12 +63,12 @@ contract StatefulTest { #[aztec(public)] fn increment_public_value(owner: AztecAddress, value: Field) { - assert_is_initialized(&mut context); let loc = storage.public_values.at(owner); loc.write(loc.read() + value); } #[aztec(public)] + #[aztec(noinitcheck)] fn increment_public_value_no_init_check(owner: AztecAddress, value: Field) { let loc = storage.public_values.at(owner); loc.write(loc.read() + value); diff --git a/noir/noir-repo/aztec_macros/src/lib.rs b/noir/noir-repo/aztec_macros/src/lib.rs index 7d943c90920..4c6ef8a432c 100644 --- a/noir/noir-repo/aztec_macros/src/lib.rs +++ b/noir/noir-repo/aztec_macros/src/lib.rs @@ -451,7 +451,6 @@ fn transform_module( is_internal = true; } else if is_custom_attribute(&secondary_attribute, "aztec(public)") { is_public = true; - insert_init_check = false; } else if is_custom_attribute(&secondary_attribute, "aztec(public-vm)") { is_public_vm = true; } @@ -673,15 +672,6 @@ fn transform_function( // Add initialization check if insert_init_check { - if ty == "Public" { - let error = AztecMacroError::UnsupportedAttributes { - span: func.def.name.span(), - secondary_message: Some( - "public functions do not yet support initialization check".to_owned(), - ), - }; - return Err(error); - } let init_check = create_init_check(); func.def.body.0.insert(0, init_check); } From 8ad983a7e310d409941cf753bf988302dec87e8f Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 1 Mar 2024 19:16:50 -0300 Subject: [PATCH 2/6] Add noinitcheck to public functions used as initializers --- .../contracts/app_subscription_contract/src/main.nr | 1 + .../contracts/easy_private_voting_contract/src/main.nr | 5 +++-- .../noir-contracts/contracts/fpc_contract/src/main.nr | 1 + .../contracts/inclusion_proofs_contract/src/main.nr | 1 + .../contracts/token_blacklist_contract/src/main.nr | 1 + .../contracts/token_bridge_contract/src/main.nr | 1 + .../noir-contracts/contracts/token_contract/src/main.nr | 1 + 7 files changed, 9 insertions(+), 2 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr index b8312729da1..f983dbf3836 100644 --- a/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/app_subscription_contract/src/main.nr @@ -87,6 +87,7 @@ contract AppSubscription { #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn init( target_address: AztecAddress, subscription_token_address: AztecAddress, diff --git a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr index 76028481588..60cbf894ef1 100644 --- a/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/easy_private_voting_contract/src/main.nr @@ -28,8 +28,9 @@ contract EasyPrivateVoting { // docs:end:constructor // docs:start:initialize #[aztec(public)] // annotation to mark function as public and expose public context - #[aztec(internal)] - fn _initialize(admin: AztecAddress) { // internal - can only be called by contract + #[aztec(internal)] // internal - can only be called by contract + #[aztec(noinitcheck)] + fn _initialize(admin: AztecAddress) { storage.admin.write(admin); storage.voteEnded.write(false); } diff --git a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr index 9ab6846b076..7207f0ef308 100644 --- a/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/fpc_contract/src/main.nr @@ -24,6 +24,7 @@ contract FPC { #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn _initialize(other_asset: AztecAddress, fee_asset: AztecAddress) { storage.other_asset.initialize(other_asset); storage.fee_asset.initialize(fee_asset); diff --git a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr index 0deda6433a1..b026448a1b0 100644 --- a/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/inclusion_proofs_contract/src/main.nr @@ -38,6 +38,7 @@ contract InclusionProofs { #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn _initialize(value: Field) { storage.public_value.write(value); } diff --git a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr index fe9bec5f4fe..1e26124a5bb 100644 --- a/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_blacklist_contract/src/main.nr @@ -78,6 +78,7 @@ contract TokenBlacklist { /////// #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn _initialize(new_admin: AztecAddress, slow_updates_contract: AztecAddress) { assert(!new_admin.is_zero(), "invalid admin"); storage.admin.write(new_admin); diff --git a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr index e6d37db870e..1870537b2b9 100644 --- a/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_bridge_contract/src/main.nr @@ -139,6 +139,7 @@ contract TokenBridge { #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn _initialize(token: AztecAddress) { storage.token.write(token); } 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 1321ad290f2..ed6ff0dfceb 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -314,6 +314,7 @@ contract Token { // docs:start:initialize #[aztec(public)] #[aztec(internal)] + #[aztec(noinitcheck)] fn _initialize( new_admin: AztecAddress, name: FieldCompressedString, From 71bb022b91fe1a2c72323ae2e357dda9c6958b19 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Fri, 1 Mar 2024 19:43:15 -0300 Subject: [PATCH 3/6] Add TestToken without init checks for testing purposes --- noir-projects/noir-contracts/Nargo.toml | 1 + .../contracts/test_token_contract/Nargo.toml | 10 + .../contracts/test_token_contract/src/main.nr | 314 ++++++++++++++++++ .../test_token_contract/src/types.nr | 3 + .../src/types/balances_map.nr | 118 +++++++ .../src/types/token_note.nr | 124 +++++++ .../src/types/transparent_note.nr | 89 +++++ .../simulator/src/public/index.test.ts | 8 +- 8 files changed, 663 insertions(+), 4 deletions(-) create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr create mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index 08b4006f56b..d6b7aadd779 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -29,6 +29,7 @@ members = [ "contracts/slow_tree_contract", "contracts/stateful_test_contract", "contracts/test_contract", + "contracts/test_token_contract", "contracts/token_contract", "contracts/token_blacklist_contract", "contracts/token_bridge_contract", diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml new file mode 100644 index 00000000000..b31f13c0b44 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml @@ -0,0 +1,10 @@ +[package] +name = "test_token_contract" +authors = [""] +compiler_version = ">=0.18.0" +type = "contract" + +[dependencies] +aztec = { path = "../../../aztec-nr/aztec" } +compressed_string = { path = "../../../aztec-nr/compressed-string" } +authwit = { path = "../../../aztec-nr/authwit" } diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr new file mode 100644 index 00000000000..60eb8b5e396 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr @@ -0,0 +1,314 @@ +mod types; + +// Copy of the TokenContract that skips initializer checks for all public functions. +// Meant to be used for public simulator tests at simulator/src/public/index.test.ts. + +contract TestToken { + // Libs + use dep::std::option::Option; + + use dep::compressed_string::FieldCompressedString; + + use dep::aztec::{ + note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader, utils as note_utils}, + hash::{compute_secret_hash}, state_vars::{Map, PublicMutable, SharedImmutable, PrivateSet}, + protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress} + }; + + use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; + + use crate::types::{transparent_note::TransparentNote, token_note::{TokenNote, TOKEN_NOTE_LEN}, balances_map::BalancesMap}; + + struct Storage { + admin: PublicMutable, + minters: Map>, + balances: BalancesMap, + total_supply: PublicMutable, + pending_shields: PrivateSet, + public_balances: Map>, + symbol: SharedImmutable, + name: SharedImmutable, + decimals: SharedImmutable, + } + + #[aztec(private)] + #[aztec(initializer)] + 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] + ); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn set_admin(new_admin: AztecAddress) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.admin.write(new_admin); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + 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)] + #[aztec(noinitcheck)] + 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)] + #[aztec(noinitcheck)] + fn public_get_decimals() -> pub u8 { + storage.decimals.read_public() + } + + #[aztec(private)] + fn private_get_decimals() -> pub u8 { + storage.decimals.read_private() + } + + unconstrained fn un_get_decimals() -> pub u8 { + storage.decimals.read_public() + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn set_minter(minter: AztecAddress, approve: bool) { + assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); + storage.minters.at(minter).write(approve); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn mint_public(to: AztecAddress, amount: Field) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + let amount = U128::from_integer(amount); + let new_balance = storage.public_balances.at(to).read().add(amount); + let supply = storage.total_supply.read().add(amount); + + storage.public_balances.at(to).write(new_balance); + storage.total_supply.write(supply); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn mint_private(amount: Field, secret_hash: Field) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + let pending_shields = storage.pending_shields; + let mut note = TransparentNote::new(amount, secret_hash); + let supply = storage.total_supply.read().add(U128::from_integer(amount)); + + storage.total_supply.write(supply); + pending_shields.insert_from_public(&mut note); + } + + #[aztec(private)] + fn privately_mint_private_note(amount: Field) { + storage.balances.add(context.msg_sender(), U128::from_integer(amount)); + let selector = FunctionSelector::from_signature("assert_minter_and_mint((Field),Field)"); + let _void = context.call_public_function( + context.this_address(), + selector, + [context.msg_sender().to_field(), amount] + ); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + internal fn assert_minter_and_mint(minter: AztecAddress, amount: Field) { + assert(storage.minters.at(minter).read(), "caller is not minter"); + let supply = storage.total_supply.read() + U128::from_integer(amount); + storage.total_supply.write(supply); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + let amount = U128::from_integer(amount); + let from_balance = storage.public_balances.at(from).read().sub(amount); + + let pending_shields = storage.pending_shields; + let mut note = TransparentNote::new(amount.to_integer(), secret_hash); + + storage.public_balances.at(from).write(from_balance); + pending_shields.insert_from_public(&mut note); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + let amount = U128::from_integer(amount); + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + + let to_balance = storage.public_balances.at(to).read().add(amount); + storage.public_balances.at(to).write(to_balance); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit_public(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + let amount = U128::from_integer(amount); + let from_balance = storage.public_balances.at(from).read().sub(amount); + storage.public_balances.at(from).write(from_balance); + + let new_supply = storage.total_supply.read().sub(amount); + storage.total_supply.write(new_supply); + } + + #[aztec(private)] + 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)) 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 + pending_shields.remove(note); + + // Add the token note to user's balances set + storage.balances.add(to, U128::from_integer(amount)); + } + + #[aztec(private)] + fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + storage.balances.sub(from, U128::from_integer(amount)); + + let selector = FunctionSelector::from_signature("_increase_public_balance((Field),Field)"); + let _void = context.call_public_function(context.this_address(), selector, [to.to_field(), amount]); + } + + #[aztec(private)] + fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + let amount = U128::from_integer(amount); + storage.balances.sub(from, amount); + storage.balances.add(to, amount); + } + + #[aztec(private)] + fn burn(from: AztecAddress, amount: Field, nonce: Field) { + if (!from.eq(context.msg_sender())) { + assert_current_call_valid_authwit(&mut context, from); + } else { + assert(nonce == 0, "invalid nonce"); + } + + storage.balances.sub(from, U128::from_integer(amount)); + + let selector = FunctionSelector::from_signature("_reduce_total_supply(Field)"); + let _void = context.call_public_function(context.this_address(), selector, [amount]); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + 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); + storage.decimals.initialize(decimals); + } + + /// Internal /// + + #[aztec(public)] + #[aztec(noinitcheck)] + internal fn _increase_public_balance(to: AztecAddress, amount: Field) { + let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); + storage.public_balances.at(to).write(new_balance); + } + + #[aztec(public)] + #[aztec(noinitcheck)] + internal fn _reduce_total_supply(amount: Field) { + // Only to be called from burn. + let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); + storage.total_supply.write(new_supply); + } + + /// Unconstrained /// + + unconstrained fn admin() -> pub Field { + storage.admin.read().to_field() + } + + unconstrained fn is_minter(minter: AztecAddress) -> pub bool { + storage.minters.at(minter).read() + } + + unconstrained fn total_supply() -> pub Field { + storage.total_supply.read().to_integer() + } + + unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field { + storage.balances.balance_of(owner).to_integer() + } + + unconstrained fn balance_of_public(owner: AztecAddress) -> pub Field { + storage.public_balances.at(owner).read().to_integer() + } +} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr new file mode 100644 index 00000000000..62a1bb2a363 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr @@ -0,0 +1,3 @@ +mod transparent_note; +mod balances_map; +mod token_note; diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr new file mode 100644 index 00000000000..740d328413f --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr @@ -0,0 +1,118 @@ +use dep::std::option::Option; +use dep::aztec::{ + context::{PrivateContext, PublicContext, Context}, hash::pedersen_hash, + protocol_types::{address::AztecAddress, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, + state_vars::{PrivateSet, Map}, + note::{ + note_getter::view_notes, note_getter_options::{NoteGetterOptions, SortOrder}, + note_viewer_options::NoteViewerOptions, note_header::NoteHeader, note_interface::NoteInterface +} +}; +use crate::types::token_note::{TokenNote, OwnedNote}; + +struct BalancesMap { + map: Map> +} + +impl BalancesMap { + pub fn new(context: Context, storage_slot: Field) -> Self { + assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); + Self { + map: Map::new( + context, + storage_slot, + |context, slot| PrivateSet::new(context, slot) + ) + } + } + + unconstrained pub fn balance_of(self: Self, owner: AztecAddress) -> U128 where T: NoteInterface + OwnedNote { + self.balance_of_with_offset(owner, 0) + } + + unconstrained pub fn balance_of_with_offset( + self: Self, + owner: AztecAddress, + offset: u32 + ) -> U128 where T: NoteInterface + OwnedNote { + let mut balance = U128::from_integer(0); + // docs:start:view_notes + let options = NoteViewerOptions::new().set_offset(offset); + let opt_notes = self.map.at(owner).view_notes(options); + // docs:end:view_notes + let len = opt_notes.len(); + for i in 0..len { + if opt_notes[i].is_some() { + balance = balance + opt_notes[i].unwrap_unchecked().get_amount(); + } + } + if (opt_notes[len - 1].is_some()) { + balance = balance + self.balance_of_with_offset(owner, offset + opt_notes.len() as u32); + } + + balance + } + + pub fn add( + self: Self, + owner: AztecAddress, + addend: U128 + ) where T: NoteInterface + OwnedNote { + let mut addend_note = T::new(addend, owner); + + // docs:start:insert + self.map.at(owner).insert(&mut addend_note, true); + // docs:end:insert + } + + pub fn sub( + self: Self, + owner: AztecAddress, + subtrahend: U128 + ) where T: NoteInterface + OwnedNote { + // docs:start:get_notes + let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); + let maybe_notes = self.map.at(owner).get_notes(options); + // docs:end:get_notes + + let mut minuend: U128 = U128::from_integer(0); + for i in 0..maybe_notes.len() { + if maybe_notes[i].is_some() { + let note = maybe_notes[i].unwrap_unchecked(); + + // Removes the note from the owner's set of notes. + // This will call the the `compute_nullifer` function of the `token_note` + // which require knowledge of the secret key (currently the users encryption key). + // The contract logic must ensure that the spending key is used as well. + // docs:start:remove + self.map.at(owner).remove(note); + // docs:end:remove + + minuend = minuend + note.get_amount(); + } + } + + // This is to provide a nicer error msg, + // without it minuend-subtrahend would still catch it, but more generic error then. + // without the == true, it includes 'minuend.ge(subtrahend)' as part of the error. + assert(minuend >= subtrahend, "Balance too low"); + + self.add(owner, minuend - subtrahend); + } +} + +pub fn filter_notes_min_sum( + notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], + min_sum: U128 +) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { + let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; + let mut sum = U128::from_integer(0); + for i in 0..notes.len() { + if notes[i].is_some() & sum < min_sum { + let note = notes[i].unwrap_unchecked(); + selected[i] = Option::some(note); + sum = sum.add(note.get_amount()); + } + } + selected +} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr new file mode 100644 index 00000000000..c9233038f30 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr @@ -0,0 +1,124 @@ +use dep::aztec::{ + protocol_types::address::AztecAddress, + note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, + context::PrivateContext, log::emit_encrypted_log, hash::pedersen_hash +}; +use dep::aztec::oracle::{rand::rand, nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key}; +use dep::std::option::Option; + +trait OwnedNote { + fn new(amount: U128, owner: AztecAddress) -> Self; + fn get_amount(self) -> U128; + fn get_owner(self) -> AztecAddress; +} + +global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header. + +struct TokenNote { + // the amount of tokens in the note + amount: U128, + // the provider of secrets for the nullifier. The owner (recipient) to ensure that the note + // can be privately spent. When nullifier secret and encryption private key is same + // we can simply use the owner for this one. + owner: AztecAddress, + // randomness of the note to hide contents. + randomness: Field, + // the note header (contract_address, nonce, storage_slot) + // included in the note such that it becomes part of encrypted logs for later use. + header: NoteHeader, +} + +impl NoteInterface for TokenNote { + fn serialize_content(self) -> [Field; TOKEN_NOTE_LEN] { + [self.amount.to_field(), self.owner.to_field(), self.randomness] + } + + fn deserialize_content(serialized_note: [Field; TOKEN_NOTE_LEN]) -> Self { + Self { + // TODO: check this type cast is right + amount: U128::from_integer(serialized_note[0]), + owner: AztecAddress::from_field(serialized_note[1]), + randomness: serialized_note[2], + header: NoteHeader::empty(), + } + } + + fn compute_note_content_hash(self) -> Field { + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash(self.serialize_content(), 0) + } + + // docs:start:nullifier + fn compute_nullifier(self, context: &mut PrivateContext) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = context.request_nullifier_secret_key(self.owner); + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + // docs:end:nullifier + + fn compute_nullifier_without_context(self) -> Field { + let note_hash_for_nullify = compute_note_hash_for_consumption(self); + let secret = get_nullifier_secret_key(self.owner); + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash([ + note_hash_for_nullify, + secret.low, + secret.high, + ],0) + } + + fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + fn get_header(self) -> NoteHeader { + self.header + } + + // Broadcasts the note as an encrypted log on L1. + fn broadcast(self, context: &mut PrivateContext, slot: Field) { + // We only bother inserting the note if non-empty to save funds on gas. + if !(self.amount == U128::from_integer(0)) { + let encryption_pub_key = get_public_key(self.owner); + emit_encrypted_log( + 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 { + fn new(amount: U128, owner: AztecAddress) -> Self { + Self { + amount, + owner, + randomness: rand(), + header: NoteHeader::empty(), + } + } + + fn get_amount(self) -> U128 { + self.amount + } + + fn get_owner(self) -> AztecAddress { + self.owner + } + +} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr new file mode 100644 index 00000000000..a444d27df26 --- /dev/null +++ b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr @@ -0,0 +1,89 @@ +// docs:start:token_types_all +use dep::aztec::{ + note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, + hash::{compute_secret_hash, pedersen_hash}, context::PrivateContext +}; + +global TRANSPARENT_NOTE_LEN: Field = 2; + +// Transparent note represents a note that is created in the clear (public execution), +// but can only be spent by those that know the preimage of the "secret_hash" +struct TransparentNote { + amount: Field, + secret_hash: Field, + // the secret is just here for ease of use and won't be (de)serialized + secret: Field, + // header is just here to satisfy the NoteInterface + header: NoteHeader, +} + +impl NoteInterface for TransparentNote { + fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { + [self.amount, self.secret_hash] + } + + fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { + TransparentNote { + amount: serialized_note[0], + secret_hash: serialized_note[1], + secret: 0, + header: NoteHeader::empty(), + } + } + + fn compute_note_content_hash(self) -> Field { + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash(self.serialize_content(), 0) + } + + fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { + self.compute_nullifier_without_context() + } + + fn compute_nullifier_without_context(self) -> Field { + let siloed_note_hash = compute_note_hash_for_consumption(self); + // TODO(#1205) Should use a non-zero generator index. + pedersen_hash([self.secret, siloed_note_hash],0) + } + + fn set_header(&mut self, header: NoteHeader) { + self.header = header; + } + + fn get_header(self) -> NoteHeader { + self.header + } + + 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 { + + // CONSTRUCTORS + + pub fn new(amount: Field, secret_hash: Field) -> Self { + TransparentNote { amount, secret_hash, secret: 0, header: NoteHeader::empty() } + } + + // new oracle call primitive + // get me the secret corresponding to this hash + pub fn new_from_secret(amount: Field, secret: Field) -> Self { + TransparentNote { amount, secret_hash: compute_secret_hash(secret), secret, header: NoteHeader::empty() } + } + + // CUSTOM FUNCTIONS FOR THIS NOTE TYPE + + pub fn knows_secret(self, secret: Field) { + let hash = compute_secret_hash(secret); + assert(self.secret_hash == hash); + } +} +// docs:end:token_types_all \ No newline at end of file diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index 810c0745c01..162983c4e9d 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -17,7 +17,7 @@ import { Fr } from '@aztec/foundation/fields'; import { ChildContractArtifact } from '@aztec/noir-contracts.js/Child'; import { ParentContractArtifact } from '@aztec/noir-contracts.js/Parent'; import { TestContractArtifact } from '@aztec/noir-contracts.js/Test'; -import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; +import { TestTokenContractArtifact } from '@aztec/noir-contracts.js/TestToken'; import { MockProxy, mock } from 'jest-mock-extended'; import { type MemDown, default as memdown } from 'memdown'; @@ -60,7 +60,7 @@ describe('ACIR public execution simulator', () => { describe('mint', () => { it('should run the mint_public function', async () => { const contractAddress = AztecAddress.random(); - const mintArtifact = TokenContractArtifact.functions.find(f => f.name === 'mint_public')!; + const mintArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'mint_public')!; const functionData = FunctionData.fromAbi(mintArtifact); const mintAmount = 140n; @@ -138,7 +138,7 @@ describe('ACIR public execution simulator', () => { beforeEach(() => { contractAddress = AztecAddress.random(); - transferArtifact = TokenContractArtifact.functions.find(f => f.name === 'transfer_public')!; + transferArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'transfer_public')!; functionData = new FunctionData(FunctionSelector.empty(), false, false, false); sender = AztecAddress.random(); args = encodeArguments(transferArtifact, [sender, recipient, 140n, 0n]); @@ -303,7 +303,7 @@ describe('ACIR public execution simulator', () => { }); it('Should be able to create a note hash from the public context', async () => { - const shieldArtifact = TokenContractArtifact.functions.find(f => f.name === 'shield')!; + const shieldArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'shield')!; const msgSender = AztecAddress.random(); const secretHash = Fr.random(); From 3f1f476c55ab860ca76726f57557e174a26dc77d Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 4 Mar 2024 15:58:26 -0300 Subject: [PATCH 4/6] Mock init nullifier in public execution unit tests --- yarn-project/merkle-tree/src/index.ts | 1 + .../test/standard_indexed_tree_with_append.ts | 2 +- .../simulator/src/public/index.test.ts | 55 +++++++++++++++---- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/yarn-project/merkle-tree/src/index.ts b/yarn-project/merkle-tree/src/index.ts index 45ae1771bbf..ca08070bc8e 100644 --- a/yarn-project/merkle-tree/src/index.ts +++ b/yarn-project/merkle-tree/src/index.ts @@ -5,6 +5,7 @@ export * from './interfaces/update_only_tree.js'; export * from './pedersen.js'; export * from './sparse_tree/sparse_tree.js'; export { StandardIndexedTree } from './standard_indexed_tree/standard_indexed_tree.js'; +export { StandardIndexedTreeWithAppend } from './standard_indexed_tree/test/standard_indexed_tree_with_append.js'; export * from './standard_tree/standard_tree.js'; export { INITIAL_LEAF, getTreeMeta } from './tree_base.js'; export { newTree } from './new_tree.js'; diff --git a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts index 60ff4a9ecca..a772f1fc5fb 100644 --- a/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts +++ b/yarn-project/merkle-tree/src/standard_indexed_tree/test/standard_indexed_tree_with_append.ts @@ -1,4 +1,4 @@ -import { StandardIndexedTree } from '../../index.js'; +import { StandardIndexedTree } from '../standard_indexed_tree.js'; /** * A testing utility which is here to store the original implementation of StandardIndexedTree.appendLeaves method diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index 162983c4e9d..88b5d2befd2 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -1,4 +1,4 @@ -import { L1ToL2Message, SiblingPath } from '@aztec/circuit-types'; +import { L1ToL2Message, NullifierMembershipWitness, SiblingPath } from '@aztec/circuit-types'; import { AppendOnlyTreeSnapshot, CallContext, @@ -7,17 +7,23 @@ import { Header, L1_TO_L2_MSG_TREE_HEIGHT, L2ToL1Message, + NULLIFIER_TREE_HEIGHT, + NullifierLeaf, + NullifierLeafPreimage, } from '@aztec/circuits.js'; +import { siloNullifier } from '@aztec/circuits.js/hash'; import { makeHeader } from '@aztec/circuits.js/testing'; import { FunctionArtifact, FunctionSelector, encodeArguments } from '@aztec/foundation/abi'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { pedersenHash } from '@aztec/foundation/crypto'; import { EthAddress } from '@aztec/foundation/eth-address'; import { Fr } from '@aztec/foundation/fields'; +import { openTmpStore } from '@aztec/kv-store/utils'; +import { Pedersen, StandardIndexedTreeWithAppend } from '@aztec/merkle-tree'; import { ChildContractArtifact } from '@aztec/noir-contracts.js/Child'; import { ParentContractArtifact } from '@aztec/noir-contracts.js/Parent'; import { TestContractArtifact } from '@aztec/noir-contracts.js/Test'; -import { TestTokenContractArtifact } from '@aztec/noir-contracts.js/TestToken'; +import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { MockProxy, mock } from 'jest-mock-extended'; import { type MemDown, default as memdown } from 'memdown'; @@ -50,17 +56,47 @@ describe('ACIR public execution simulator', () => { executor = new PublicExecutor(publicState, publicContracts, commitmentsDb, header); }, 10000); + const mockInitializationNullifierCallback = async (contractAddress: AztecAddress) => { + // We create a nullifier tree just to get the membership witness for the token contract + // initialization nullifier, which is checked by all of the Token contract functions. + const nullifierTree = new StandardIndexedTreeWithAppend( + openTmpStore(), + new Pedersen(), + 'nullifier', + NULLIFIER_TREE_HEIGHT, + 0n, + NullifierLeafPreimage, + NullifierLeaf, + ); + await nullifierTree.init(1); + const initializationNullifier = siloNullifier(contractAddress, contractAddress.toField()); + nullifierTree.appendLeaves([initializationNullifier.toBuffer()]); + header.state.partial.nullifierTree.root = Fr.fromBuffer(nullifierTree.getRoot(true)); + commitmentsDb.getNullifierMembershipWitnessAtLatestBlock.mockImplementation(async nullifier => { + if (nullifier.equals(initializationNullifier)) { + const index = 1n; + const preimage = nullifierTree.getLatestLeafPreimageCopy(index, true); + const siblingPath = await nullifierTree.getSiblingPath(index, true); + return new NullifierMembershipWitness(index, preimage as NullifierLeafPreimage, siblingPath); + } else { + throw new Error(`Unexpected nullifier witness request for ${nullifier}`); + } + }); + }; + describe('Token contract', () => { let recipient: AztecAddress; + let contractAddress: AztecAddress; - beforeEach(() => { + beforeEach(async () => { recipient = AztecAddress.random(); + contractAddress = AztecAddress.random(); + await mockInitializationNullifierCallback(contractAddress); }); describe('mint', () => { it('should run the mint_public function', async () => { - const contractAddress = AztecAddress.random(); - const mintArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'mint_public')!; + const mintArtifact = TokenContractArtifact.functions.find(f => f.name === 'mint_public')!; const functionData = FunctionData.fromAbi(mintArtifact); const mintAmount = 140n; @@ -126,7 +162,6 @@ describe('ACIR public execution simulator', () => { }); describe('transfer', () => { - let contractAddress: AztecAddress; let transferArtifact: FunctionArtifact; let functionData: FunctionData; let args: Fr[]; @@ -137,8 +172,7 @@ describe('ACIR public execution simulator', () => { let execution: PublicExecution; beforeEach(() => { - contractAddress = AztecAddress.random(); - transferArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'transfer_public')!; + transferArtifact = TokenContractArtifact.functions.find(f => f.name === 'transfer_public')!; functionData = new FunctionData(FunctionSelector.empty(), false, false, false); sender = AztecAddress.random(); args = encodeArguments(transferArtifact, [sender, recipient, 140n, 0n]); @@ -295,15 +329,16 @@ describe('ACIR public execution simulator', () => { let amount: Fr; let params: Fr[]; - beforeEach(() => { + beforeEach(async () => { contractAddress = AztecAddress.random(); + await mockInitializationNullifierCallback(contractAddress); functionData = new FunctionData(FunctionSelector.empty(), false, false, false); amount = new Fr(1); params = [amount, new Fr(1)]; }); it('Should be able to create a note hash from the public context', async () => { - const shieldArtifact = TestTokenContractArtifact.functions.find(f => f.name === 'shield')!; + const shieldArtifact = TokenContractArtifact.functions.find(f => f.name === 'shield')!; const msgSender = AztecAddress.random(); const secretHash = Fr.random(); From c42fd641d503d754011e1fd5f1f0b504e7ee42d4 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 4 Mar 2024 15:59:18 -0300 Subject: [PATCH 5/6] Revert "Add TestToken without init checks for testing purposes" This reverts commit 521a25868e84cd6c44a676e4ecbc0af1e8f876c4. --- noir-projects/noir-contracts/Nargo.toml | 1 - .../contracts/test_token_contract/Nargo.toml | 10 - .../contracts/test_token_contract/src/main.nr | 314 ------------------ .../test_token_contract/src/types.nr | 3 - .../src/types/balances_map.nr | 118 ------- .../src/types/token_note.nr | 124 ------- .../src/types/transparent_note.nr | 89 ----- 7 files changed, 659 deletions(-) delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr delete mode 100644 noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr diff --git a/noir-projects/noir-contracts/Nargo.toml b/noir-projects/noir-contracts/Nargo.toml index d6b7aadd779..08b4006f56b 100644 --- a/noir-projects/noir-contracts/Nargo.toml +++ b/noir-projects/noir-contracts/Nargo.toml @@ -29,7 +29,6 @@ members = [ "contracts/slow_tree_contract", "contracts/stateful_test_contract", "contracts/test_contract", - "contracts/test_token_contract", "contracts/token_contract", "contracts/token_blacklist_contract", "contracts/token_bridge_contract", diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml b/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml deleted file mode 100644 index b31f13c0b44..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/Nargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "test_token_contract" -authors = [""] -compiler_version = ">=0.18.0" -type = "contract" - -[dependencies] -aztec = { path = "../../../aztec-nr/aztec" } -compressed_string = { path = "../../../aztec-nr/compressed-string" } -authwit = { path = "../../../aztec-nr/authwit" } diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr deleted file mode 100644 index 60eb8b5e396..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/src/main.nr +++ /dev/null @@ -1,314 +0,0 @@ -mod types; - -// Copy of the TokenContract that skips initializer checks for all public functions. -// Meant to be used for public simulator tests at simulator/src/public/index.test.ts. - -contract TestToken { - // Libs - use dep::std::option::Option; - - use dep::compressed_string::FieldCompressedString; - - use dep::aztec::{ - note::{note_getter_options::NoteGetterOptions, note_header::NoteHeader, utils as note_utils}, - hash::{compute_secret_hash}, state_vars::{Map, PublicMutable, SharedImmutable, PrivateSet}, - protocol_types::{abis::function_selector::FunctionSelector, address::AztecAddress} - }; - - use dep::authwit::{auth::{assert_current_call_valid_authwit, assert_current_call_valid_authwit_public}}; - - use crate::types::{transparent_note::TransparentNote, token_note::{TokenNote, TOKEN_NOTE_LEN}, balances_map::BalancesMap}; - - struct Storage { - admin: PublicMutable, - minters: Map>, - balances: BalancesMap, - total_supply: PublicMutable, - pending_shields: PrivateSet, - public_balances: Map>, - symbol: SharedImmutable, - name: SharedImmutable, - decimals: SharedImmutable, - } - - #[aztec(private)] - #[aztec(initializer)] - 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] - ); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn set_admin(new_admin: AztecAddress) { - assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); - storage.admin.write(new_admin); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - 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)] - #[aztec(noinitcheck)] - 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)] - #[aztec(noinitcheck)] - fn public_get_decimals() -> pub u8 { - storage.decimals.read_public() - } - - #[aztec(private)] - fn private_get_decimals() -> pub u8 { - storage.decimals.read_private() - } - - unconstrained fn un_get_decimals() -> pub u8 { - storage.decimals.read_public() - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn set_minter(minter: AztecAddress, approve: bool) { - assert(storage.admin.read().eq(context.msg_sender()), "caller is not admin"); - storage.minters.at(minter).write(approve); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn mint_public(to: AztecAddress, amount: Field) { - assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); - let amount = U128::from_integer(amount); - let new_balance = storage.public_balances.at(to).read().add(amount); - let supply = storage.total_supply.read().add(amount); - - storage.public_balances.at(to).write(new_balance); - storage.total_supply.write(supply); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn mint_private(amount: Field, secret_hash: Field) { - assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount, secret_hash); - let supply = storage.total_supply.read().add(U128::from_integer(amount)); - - storage.total_supply.write(supply); - pending_shields.insert_from_public(&mut note); - } - - #[aztec(private)] - fn privately_mint_private_note(amount: Field) { - storage.balances.add(context.msg_sender(), U128::from_integer(amount)); - let selector = FunctionSelector::from_signature("assert_minter_and_mint((Field),Field)"); - let _void = context.call_public_function( - context.this_address(), - selector, - [context.msg_sender().to_field(), amount] - ); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - internal fn assert_minter_and_mint(minter: AztecAddress, amount: Field) { - assert(storage.minters.at(minter).read(), "caller is not minter"); - let supply = storage.total_supply.read() + U128::from_integer(amount); - storage.total_supply.write(supply); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn shield(from: AztecAddress, amount: Field, secret_hash: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - // The redeem is only spendable once, so we need to ensure that you cannot insert multiple shields from the same message. - assert_current_call_valid_authwit_public(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = U128::from_integer(amount); - let from_balance = storage.public_balances.at(from).read().sub(amount); - - let pending_shields = storage.pending_shields; - let mut note = TransparentNote::new(amount.to_integer(), secret_hash); - - storage.public_balances.at(from).write(from_balance); - pending_shields.insert_from_public(&mut note); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - assert_current_call_valid_authwit_public(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = U128::from_integer(amount); - let from_balance = storage.public_balances.at(from).read().sub(amount); - storage.public_balances.at(from).write(from_balance); - - let to_balance = storage.public_balances.at(to).read().add(amount); - storage.public_balances.at(to).write(to_balance); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - assert_current_call_valid_authwit_public(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = U128::from_integer(amount); - let from_balance = storage.public_balances.at(from).read().sub(amount); - storage.public_balances.at(from).write(from_balance); - - let new_supply = storage.total_supply.read().sub(amount); - storage.total_supply.write(new_supply); - } - - #[aztec(private)] - 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)) 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 - pending_shields.remove(note); - - // Add the token note to user's balances set - storage.balances.add(to, U128::from_integer(amount)); - } - - #[aztec(private)] - fn unshield(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - assert_current_call_valid_authwit(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - storage.balances.sub(from, U128::from_integer(amount)); - - let selector = FunctionSelector::from_signature("_increase_public_balance((Field),Field)"); - let _void = context.call_public_function(context.this_address(), selector, [to.to_field(), amount]); - } - - #[aztec(private)] - fn transfer(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - assert_current_call_valid_authwit(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - let amount = U128::from_integer(amount); - storage.balances.sub(from, amount); - storage.balances.add(to, amount); - } - - #[aztec(private)] - fn burn(from: AztecAddress, amount: Field, nonce: Field) { - if (!from.eq(context.msg_sender())) { - assert_current_call_valid_authwit(&mut context, from); - } else { - assert(nonce == 0, "invalid nonce"); - } - - storage.balances.sub(from, U128::from_integer(amount)); - - let selector = FunctionSelector::from_signature("_reduce_total_supply(Field)"); - let _void = context.call_public_function(context.this_address(), selector, [amount]); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - 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); - storage.decimals.initialize(decimals); - } - - /// Internal /// - - #[aztec(public)] - #[aztec(noinitcheck)] - internal fn _increase_public_balance(to: AztecAddress, amount: Field) { - let new_balance = storage.public_balances.at(to).read().add(U128::from_integer(amount)); - storage.public_balances.at(to).write(new_balance); - } - - #[aztec(public)] - #[aztec(noinitcheck)] - internal fn _reduce_total_supply(amount: Field) { - // Only to be called from burn. - let new_supply = storage.total_supply.read().sub(U128::from_integer(amount)); - storage.total_supply.write(new_supply); - } - - /// Unconstrained /// - - unconstrained fn admin() -> pub Field { - storage.admin.read().to_field() - } - - unconstrained fn is_minter(minter: AztecAddress) -> pub bool { - storage.minters.at(minter).read() - } - - unconstrained fn total_supply() -> pub Field { - storage.total_supply.read().to_integer() - } - - unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field { - storage.balances.balance_of(owner).to_integer() - } - - unconstrained fn balance_of_public(owner: AztecAddress) -> pub Field { - storage.public_balances.at(owner).read().to_integer() - } -} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr deleted file mode 100644 index 62a1bb2a363..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/src/types.nr +++ /dev/null @@ -1,3 +0,0 @@ -mod transparent_note; -mod balances_map; -mod token_note; diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr deleted file mode 100644 index 740d328413f..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/balances_map.nr +++ /dev/null @@ -1,118 +0,0 @@ -use dep::std::option::Option; -use dep::aztec::{ - context::{PrivateContext, PublicContext, Context}, hash::pedersen_hash, - protocol_types::{address::AztecAddress, constants::MAX_NOTE_HASH_READ_REQUESTS_PER_CALL}, - state_vars::{PrivateSet, Map}, - note::{ - note_getter::view_notes, note_getter_options::{NoteGetterOptions, SortOrder}, - note_viewer_options::NoteViewerOptions, note_header::NoteHeader, note_interface::NoteInterface -} -}; -use crate::types::token_note::{TokenNote, OwnedNote}; - -struct BalancesMap { - map: Map> -} - -impl BalancesMap { - pub fn new(context: Context, storage_slot: Field) -> Self { - assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1."); - Self { - map: Map::new( - context, - storage_slot, - |context, slot| PrivateSet::new(context, slot) - ) - } - } - - unconstrained pub fn balance_of(self: Self, owner: AztecAddress) -> U128 where T: NoteInterface + OwnedNote { - self.balance_of_with_offset(owner, 0) - } - - unconstrained pub fn balance_of_with_offset( - self: Self, - owner: AztecAddress, - offset: u32 - ) -> U128 where T: NoteInterface + OwnedNote { - let mut balance = U128::from_integer(0); - // docs:start:view_notes - let options = NoteViewerOptions::new().set_offset(offset); - let opt_notes = self.map.at(owner).view_notes(options); - // docs:end:view_notes - let len = opt_notes.len(); - for i in 0..len { - if opt_notes[i].is_some() { - balance = balance + opt_notes[i].unwrap_unchecked().get_amount(); - } - } - if (opt_notes[len - 1].is_some()) { - balance = balance + self.balance_of_with_offset(owner, offset + opt_notes.len() as u32); - } - - balance - } - - pub fn add( - self: Self, - owner: AztecAddress, - addend: U128 - ) where T: NoteInterface + OwnedNote { - let mut addend_note = T::new(addend, owner); - - // docs:start:insert - self.map.at(owner).insert(&mut addend_note, true); - // docs:end:insert - } - - pub fn sub( - self: Self, - owner: AztecAddress, - subtrahend: U128 - ) where T: NoteInterface + OwnedNote { - // docs:start:get_notes - let options = NoteGetterOptions::with_filter(filter_notes_min_sum, subtrahend); - let maybe_notes = self.map.at(owner).get_notes(options); - // docs:end:get_notes - - let mut minuend: U128 = U128::from_integer(0); - for i in 0..maybe_notes.len() { - if maybe_notes[i].is_some() { - let note = maybe_notes[i].unwrap_unchecked(); - - // Removes the note from the owner's set of notes. - // This will call the the `compute_nullifer` function of the `token_note` - // which require knowledge of the secret key (currently the users encryption key). - // The contract logic must ensure that the spending key is used as well. - // docs:start:remove - self.map.at(owner).remove(note); - // docs:end:remove - - minuend = minuend + note.get_amount(); - } - } - - // This is to provide a nicer error msg, - // without it minuend-subtrahend would still catch it, but more generic error then. - // without the == true, it includes 'minuend.ge(subtrahend)' as part of the error. - assert(minuend >= subtrahend, "Balance too low"); - - self.add(owner, minuend - subtrahend); - } -} - -pub fn filter_notes_min_sum( - notes: [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL], - min_sum: U128 -) -> [Option; MAX_NOTE_HASH_READ_REQUESTS_PER_CALL] where T: NoteInterface + OwnedNote { - let mut selected = [Option::none(); MAX_NOTE_HASH_READ_REQUESTS_PER_CALL]; - let mut sum = U128::from_integer(0); - for i in 0..notes.len() { - if notes[i].is_some() & sum < min_sum { - let note = notes[i].unwrap_unchecked(); - selected[i] = Option::some(note); - sum = sum.add(note.get_amount()); - } - } - selected -} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr deleted file mode 100644 index c9233038f30..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/token_note.nr +++ /dev/null @@ -1,124 +0,0 @@ -use dep::aztec::{ - protocol_types::address::AztecAddress, - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, - context::PrivateContext, log::emit_encrypted_log, hash::pedersen_hash -}; -use dep::aztec::oracle::{rand::rand, nullifier_key::get_nullifier_secret_key, get_public_key::get_public_key}; -use dep::std::option::Option; - -trait OwnedNote { - fn new(amount: U128, owner: AztecAddress) -> Self; - fn get_amount(self) -> U128; - fn get_owner(self) -> AztecAddress; -} - -global TOKEN_NOTE_LEN: Field = 3; // 3 plus a header. - -struct TokenNote { - // the amount of tokens in the note - amount: U128, - // the provider of secrets for the nullifier. The owner (recipient) to ensure that the note - // can be privately spent. When nullifier secret and encryption private key is same - // we can simply use the owner for this one. - owner: AztecAddress, - // randomness of the note to hide contents. - randomness: Field, - // the note header (contract_address, nonce, storage_slot) - // included in the note such that it becomes part of encrypted logs for later use. - header: NoteHeader, -} - -impl NoteInterface for TokenNote { - fn serialize_content(self) -> [Field; TOKEN_NOTE_LEN] { - [self.amount.to_field(), self.owner.to_field(), self.randomness] - } - - fn deserialize_content(serialized_note: [Field; TOKEN_NOTE_LEN]) -> Self { - Self { - // TODO: check this type cast is right - amount: U128::from_integer(serialized_note[0]), - owner: AztecAddress::from_field(serialized_note[1]), - randomness: serialized_note[2], - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - - // docs:start:nullifier - fn compute_nullifier(self, context: &mut PrivateContext) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = context.request_nullifier_secret_key(self.owner); - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash([ - note_hash_for_nullify, - secret.low, - secret.high, - ],0) - } - // docs:end:nullifier - - fn compute_nullifier_without_context(self) -> Field { - let note_hash_for_nullify = compute_note_hash_for_consumption(self); - let secret = get_nullifier_secret_key(self.owner); - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash([ - note_hash_for_nullify, - secret.low, - secret.high, - ],0) - } - - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - - // Broadcasts the note as an encrypted log on L1. - fn broadcast(self, context: &mut PrivateContext, slot: Field) { - // We only bother inserting the note if non-empty to save funds on gas. - if !(self.amount == U128::from_integer(0)) { - let encryption_pub_key = get_public_key(self.owner); - emit_encrypted_log( - 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 { - fn new(amount: U128, owner: AztecAddress) -> Self { - Self { - amount, - owner, - randomness: rand(), - header: NoteHeader::empty(), - } - } - - fn get_amount(self) -> U128 { - self.amount - } - - fn get_owner(self) -> AztecAddress { - self.owner - } - -} diff --git a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr deleted file mode 100644 index a444d27df26..00000000000 --- a/noir-projects/noir-contracts/contracts/test_token_contract/src/types/transparent_note.nr +++ /dev/null @@ -1,89 +0,0 @@ -// docs:start:token_types_all -use dep::aztec::{ - note::{note_header::NoteHeader, note_interface::NoteInterface, utils::compute_note_hash_for_consumption}, - hash::{compute_secret_hash, pedersen_hash}, context::PrivateContext -}; - -global TRANSPARENT_NOTE_LEN: Field = 2; - -// Transparent note represents a note that is created in the clear (public execution), -// but can only be spent by those that know the preimage of the "secret_hash" -struct TransparentNote { - amount: Field, - secret_hash: Field, - // the secret is just here for ease of use and won't be (de)serialized - secret: Field, - // header is just here to satisfy the NoteInterface - header: NoteHeader, -} - -impl NoteInterface for TransparentNote { - fn serialize_content(self) -> [Field; TRANSPARENT_NOTE_LEN] { - [self.amount, self.secret_hash] - } - - fn deserialize_content(serialized_note: [Field; TRANSPARENT_NOTE_LEN]) -> Self { - TransparentNote { - amount: serialized_note[0], - secret_hash: serialized_note[1], - secret: 0, - header: NoteHeader::empty(), - } - } - - fn compute_note_content_hash(self) -> Field { - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash(self.serialize_content(), 0) - } - - fn compute_nullifier(self, _context: &mut PrivateContext) -> Field { - self.compute_nullifier_without_context() - } - - fn compute_nullifier_without_context(self) -> Field { - let siloed_note_hash = compute_note_hash_for_consumption(self); - // TODO(#1205) Should use a non-zero generator index. - pedersen_hash([self.secret, siloed_note_hash],0) - } - - fn set_header(&mut self, header: NoteHeader) { - self.header = header; - } - - fn get_header(self) -> NoteHeader { - self.header - } - - 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 { - - // CONSTRUCTORS - - pub fn new(amount: Field, secret_hash: Field) -> Self { - TransparentNote { amount, secret_hash, secret: 0, header: NoteHeader::empty() } - } - - // new oracle call primitive - // get me the secret corresponding to this hash - pub fn new_from_secret(amount: Field, secret: Field) -> Self { - TransparentNote { amount, secret_hash: compute_secret_hash(secret), secret, header: NoteHeader::empty() } - } - - // CUSTOM FUNCTIONS FOR THIS NOTE TYPE - - pub fn knows_secret(self, secret: Field) { - let hash = compute_secret_hash(secret); - assert(self.secret_hash == hash); - } -} -// docs:end:token_types_all \ No newline at end of file From 7a418824305e24410fcc3edc4cd79a9a237d7107 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Mon, 4 Mar 2024 16:30:46 -0300 Subject: [PATCH 6/6] Lint --- yarn-project/simulator/src/public/index.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/simulator/src/public/index.test.ts b/yarn-project/simulator/src/public/index.test.ts index 88b5d2befd2..3c8396547c9 100644 --- a/yarn-project/simulator/src/public/index.test.ts +++ b/yarn-project/simulator/src/public/index.test.ts @@ -70,7 +70,7 @@ describe('ACIR public execution simulator', () => { ); await nullifierTree.init(1); const initializationNullifier = siloNullifier(contractAddress, contractAddress.toField()); - nullifierTree.appendLeaves([initializationNullifier.toBuffer()]); + await nullifierTree.appendLeaves([initializationNullifier.toBuffer()]); header.state.partial.nullifierTree.root = Fr.fromBuffer(nullifierTree.getRoot(true)); commitmentsDb.getNullifierMembershipWitnessAtLatestBlock.mockImplementation(async nullifier => { if (nullifier.equals(initializationNullifier)) {