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 62c07a9f3fe..d526a8c1dab 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -22,11 +22,8 @@ contract Token { context::{PrivateCallInterface, PrivateContext}, encrypted_logs::{ encrypted_event_emission::encode_and_encrypt_event_unconstrained, - encrypted_note_emission::{ - encode_and_encrypt_note, encode_and_encrypt_note_unconstrained, - }, + encrypted_note_emission::encode_and_encrypt_note_unconstrained, }, - hash::compute_secret_hash, keys::getters::get_public_keys, macros::{ events::event, @@ -35,11 +32,9 @@ contract Token { }, oracle::random::random, prelude::{ - AztecAddress, FunctionSelector, Map, NoteGetterOptions, PrivateSet, PublicContext, - PublicMutable, SharedImmutable, + AztecAddress, FunctionSelector, Map, PublicContext, PublicMutable, SharedImmutable, }, protocol_types::{point::Point, traits::Serialize}, - utils::comparison::Comparator, }; use dep::uint_note::uint_note::UintNote; @@ -51,7 +46,7 @@ contract Token { }; // docs:end:import_authwit - use crate::types::{balance_set::BalanceSet, transparent_note::TransparentNote}; + use crate::types::balance_set::BalanceSet; // docs:end::imports @@ -85,9 +80,6 @@ contract Token { balances: Map, Context>, // docs:end:storage_balances total_supply: PublicMutable, - // docs:start:storage_pending_shields - pending_shields: PrivateSet, - // docs:end:storage_pending_shields public_balances: Map, Context>, symbol: SharedImmutable, name: SharedImmutable, @@ -111,6 +103,7 @@ contract Token { // docs:end:initialize_decimals } // docs:end:constructor + // docs:start:set_admin #[public] fn set_admin(new_admin: AztecAddress) { @@ -120,6 +113,7 @@ contract Token { // docs:end:write_admin } // docs:end:set_admin + #[public] #[view] fn public_get_name() -> FieldCompressedString { @@ -131,16 +125,19 @@ contract Token { fn private_get_name() -> FieldCompressedString { storage.name.read_private() } + #[public] #[view] fn public_get_symbol() -> pub FieldCompressedString { storage.symbol.read_public() } + #[private] #[view] fn private_get_symbol() -> pub FieldCompressedString { storage.symbol.read_private() } + #[public] #[view] fn public_get_decimals() -> pub u8 { @@ -155,6 +152,7 @@ contract Token { storage.decimals.read_private() // docs:end:read_decimals_private } + // docs:start:admin #[public] #[view] @@ -162,6 +160,7 @@ contract Token { storage.admin.read().to_field() } // docs:end:admin + // docs:start:is_minter #[public] #[view] @@ -169,6 +168,7 @@ contract Token { storage.minters.at(minter).read() } // docs:end:is_minter + // docs:start:total_supply #[public] #[view] @@ -176,6 +176,7 @@ contract Token { storage.total_supply.read().to_integer() } // docs:end:total_supply + // docs:start:balance_of_public #[public] #[view] @@ -183,6 +184,7 @@ contract Token { storage.public_balances.at(owner).read().to_integer() } // docs:end:balance_of_public + // docs:start:set_minter #[public] fn set_minter(minter: AztecAddress, approve: bool) { @@ -194,6 +196,7 @@ contract Token { // docs:end:write_minter } // docs:end:set_minter + // docs:start:mint_public #[public] fn mint_public(to: AztecAddress, amount: Field) { @@ -208,23 +211,6 @@ contract Token { } // docs:end:mint_public - // docs:start:shield - #[public] - 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_field(), secret_hash); - storage.public_balances.at(from).write(from_balance); - pending_shields.insert_from_public(&mut note); - } - // docs:end:shield // docs:start:transfer_public #[public] fn transfer_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -240,6 +226,7 @@ contract Token { storage.public_balances.at(to).write(to_balance); } // docs:end:transfer_public + // docs:start:burn_public #[public] fn burn_public(from: AztecAddress, amount: Field, nonce: Field) { @@ -257,32 +244,7 @@ contract Token { storage.total_supply.write(new_supply); } // docs:end:burn_public - // docs:start:redeem_shield - #[private] - fn redeem_shield(to: AztecAddress, amount: Field, secret: Field) { - let secret_hash = compute_secret_hash(secret); - // Pop 1 note (set_limit(1)) which has an amount stored in a field with index 0 (select(0, amount)) and - // a secret_hash stored in a field with index 1 (select(1, secret_hash)). - let mut options = NoteGetterOptions::new(); - options = options - .select(TransparentNote::properties().amount, Comparator.EQ, amount) - .select(TransparentNote::properties().secret_hash, Comparator.EQ, secret_hash) - .set_limit(1); - let notes = storage.pending_shields.pop_notes(options); - assert(notes.len() == 1, "note not popped"); - // Add the token note to user's balances set - // Note: Using context.msg_sender() as a sender below makes this incompatible with escrows because we send - // outgoing logs to that address and to send outgoing logs you need to get a hold of ovsk_m. - let from = context.msg_sender(); - let from_ovpk_m = get_public_keys(from).ovpk_m; - storage.balances.at(to).add(to, U128::from_integer(amount)).emit(encode_and_encrypt_note( - &mut context, - from_ovpk_m, - to, - context.msg_sender(), - )); - } - // docs:end:redeem_shield + // docs:start:transfer_to_public #[private] fn transfer_to_public(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -301,6 +263,7 @@ contract Token { Token::at(context.this_address())._increase_public_balance(to, amount).enqueue(&mut context); } // docs:end:transfer_to_public + // docs:start:transfer #[private] fn transfer(to: AztecAddress, amount: Field) { @@ -343,6 +306,7 @@ contract Token { ); } // docs:end:transfer + #[contract_library_method] fn subtract_balance( context: &mut PrivateContext, @@ -367,6 +331,7 @@ contract Token { compute_recurse_subtract_balance_call(*context, account, remaining).call(context) } } + // TODO(#7729): apply no_predicates to the contract interface method directly instead of having to use a wrapper // like we do here. #[no_predicates] @@ -378,6 +343,7 @@ contract Token { ) -> PrivateCallInterface<25, U128> { Token::at(context.this_address())._recurse_subtract_balance(account, remaining.to_field()) } + // TODO(#7728): even though the amount should be a U128, we can't have that type in a contract interface due to // serialization issues. #[internal] @@ -391,6 +357,7 @@ contract Token { RECURSIVE_TRANSFER_CALL_MAX_NOTES, ) } + /** * Cancel a private authentication witness. * @param inner_hash The inner hash of the authwit to cancel. @@ -403,6 +370,7 @@ contract Token { context.push_nullifier(nullifier); } // docs:end:cancel_authwit + // docs:start:transfer_from #[private] fn transfer_from(from: AztecAddress, to: AztecAddress, amount: Field, nonce: Field) { @@ -438,6 +406,7 @@ contract Token { )); } // docs:end:transfer_from + // docs:start:burn #[private] fn burn(from: AztecAddress, amount: Field, nonce: Field) { @@ -814,6 +783,7 @@ contract Token { storage.public_balances.at(to).write(new_balance); } // docs:end:increase_public_balance + // docs:start:reduce_total_supply #[public] #[internal] @@ -823,6 +793,7 @@ contract Token { storage.total_supply.write(new_supply); } // docs:end:reduce_total_supply + /// Unconstrained /// // docs:start:balance_of_private pub(crate) unconstrained fn balance_of_private(owner: AztecAddress) -> pub Field { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr index 35a4c1f1aff..b746bbe1f6c 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test.nr @@ -8,4 +8,3 @@ mod transfer_to_public; mod refunds; mod minting; mod reading_constants; -mod shielding; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr index bc4f877d2a3..1b572b872ff 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/test/minting.nr @@ -1,6 +1,4 @@ -use crate::test::utils; -use crate::{Token, types::transparent_note::TransparentNote}; -use dep::aztec::{oracle::random::random, test::helpers::cheatcodes}; +use crate::{test::utils, Token}; #[test] unconstrained fn mint_public_success() { diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr b/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr deleted file mode 100644 index aef1d98f9cc..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/test/shielding.nr +++ /dev/null @@ -1,179 +0,0 @@ -use crate::test::utils; -use crate::{Token, types::transparent_note::TransparentNote}; -use dep::authwit::cheatcodes as authwit_cheatcodes; -use dep::aztec::{hash::compute_secret_hash, oracle::random::random}; - -#[test] -unconstrained fn shielding_on_behalf_of_self() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ false); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount / 10; - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0).call( - &mut env.public(), - ); - - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret).call( - &mut env.private(), - ); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); - utils::check_private_balance(token_contract_address, owner, shield_amount); -} - -#[test] -unconstrained fn shielding_on_behalf_of_other() { - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - - // Shield tokens on behalf of owner - let shield_amount = 1000; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface( - owner, - recipient, - shield_call_interface, - ); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - shield_call_interface.call(&mut env.public()); - - // Become owner again - env.impersonate(owner); - // We need to manually add the note to TXE because `TransparentNote` does not support automatic note log delivery. - env.add_note( - &mut TransparentNote::new(shield_amount, secret_hash), - Token::storage_layout().pending_shields.slot, - token_contract_address, - ); - - // Redeem our shielded tokens - Token::at(token_contract_address).redeem_shield(owner, shield_amount, secret).call( - &mut env.private(), - ); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount - shield_amount); - utils::check_private_balance(token_contract_address, owner, shield_amount); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_self_more_than_balance() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_self_invalid_nonce() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, _, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens - let shield_amount = mint_amount / 10; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, random()); - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_more_than_balance() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface( - owner, - recipient, - shield_call_interface, - ); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_wrong_caller() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - authwit_cheatcodes::add_public_authwit_from_call_interface(owner, owner, shield_call_interface); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} - -#[test] -unconstrained fn shielding_failure_on_behalf_of_other_without_approval() { - // Setup without account contracts. We are not using authwits here, so dummy accounts are enough - let (env, token_contract_address, owner, recipient, mint_amount) = - utils::setup_and_mint_public(/* with_account_contracts */ true); - let secret = random(); - let secret_hash = compute_secret_hash(secret); - // Shield tokens on behalf of owner - let shield_amount = mint_amount + 1; - let shield_call_interface = - Token::at(token_contract_address).shield(owner, shield_amount, secret_hash, 0); - // Impersonate recipient to perform the call - env.impersonate(recipient); - // Shield tokens - env.assert_public_call_fails(shield_call_interface); - - // Check balances - utils::check_public_balance(token_contract_address, owner, mint_amount); - utils::check_private_balance(token_contract_address, owner, 0); -} diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types.nr index cad3fda646e..1bc5f644e4f 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/types.nr @@ -1,2 +1 @@ -pub(crate) mod transparent_note; pub(crate) mod balance_set; diff --git a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr b/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr deleted file mode 100644 index ed1f08e0d74..00000000000 --- a/noir-projects/noir-contracts/contracts/token_contract/src/types/transparent_note.nr +++ /dev/null @@ -1,63 +0,0 @@ -// docs:start:token_types_all -use dep::aztec::{ - macros::notes::note, - note::utils::compute_note_hash_for_nullify, - prelude::{NoteHeader, NullifiableNote, PrivateContext}, - protocol_types::{ - constants::GENERATOR_INDEX__NOTE_NULLIFIER, hash::poseidon2_hash_with_separator, - }, -}; - -use dep::std::mem::zeroed; - -// 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" (the secret). This is typically used when shielding a token balance. -// Owner of the tokens provides a "secret_hash" as an argument to the public "shield" function and then the tokens -// can be redeemed in private by presenting the preimage of the "secret_hash" (the secret). -#[note] -pub struct TransparentNote { - amount: Field, - secret_hash: Field, -} - -impl NullifiableNote for TransparentNote { - // Computing a nullifier in a transparent note is not guarded by making secret a part of the nullifier preimage (as - // is common in other cases) and instead is guarded by the functionality of "redeem_shield" function. There we do - // the following: - // 1) We pass the secret as an argument to the function and use it to compute a secret hash, - // 2) we fetch a note via the "get_notes" oracle which accepts the secret hash as an argument, - // 3) the "get_notes" oracle constrains that the secret hash in the returned note matches the one computed in - // circuit. - // This achieves that the note can only be spent by the party that knows the secret. - fn compute_nullifier( - self, - _context: &mut PrivateContext, - _note_hash_for_nullify: Field, - ) -> Field { - let note_hash_for_nullify = compute_note_hash_for_nullify(self); - poseidon2_hash_with_separator( - [note_hash_for_nullify], - GENERATOR_INDEX__NOTE_NULLIFIER as Field, - ) - } - - unconstrained fn compute_nullifier_without_context(self) -> Field { - // compute_nullifier ignores both of its parameters so we can reuse it here - self.compute_nullifier(zeroed(), zeroed()) - } -} - -impl TransparentNote { - // CONSTRUCTORS - pub fn new(amount: Field, secret_hash: Field) -> Self { - TransparentNote { amount, secret_hash, header: NoteHeader::empty() } - } -} - -impl Eq for TransparentNote { - fn eq(self, other: Self) -> bool { - (self.amount == other.amount) & (self.secret_hash == other.secret_hash) - } -} - -// docs:end:token_types_all