From e8fadc799d015046016b16eeadbb55be929d20c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Bene=C5=A1?= Date: Wed, 30 Oct 2024 20:32:34 -0600 Subject: [PATCH] feat: token private mint optimization (#9606) Impl. of token `finalize_mint_to_private` --- .../contracts/nft_contract/src/main.nr | 4 +- .../token_bridge_contract/src/main.nr | 2 +- .../contracts/token_contract/src/main.nr | 74 ++++++++++++++++++- yarn-project/aztec/src/examples/token.ts | 12 +-- .../end-to-end/src/fixtures/token_utils.ts | 12 +-- .../writing_an_account_contract.test.ts | 10 +-- .../end-to-end/src/sample-dapp/index.mjs | 12 +-- yarn-project/end-to-end/src/shared/browser.ts | 5 +- .../src/protocol_contract_data.ts | 8 +- 9 files changed, 88 insertions(+), 51 deletions(-) diff --git a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr index f058088abc7..b9b7b610e6d 100644 --- a/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/nft_contract/src/main.nr @@ -256,7 +256,7 @@ contract NFT { fn _finalize_transfer_to_private( from: AztecAddress, token_id: Field, - note_transient_storage_slot: Field, + hiding_point_slot: Field, context: &mut PublicContext, storage: Storage<&mut PublicContext>, ) { @@ -268,7 +268,7 @@ contract NFT { // Finalize the partial note with the `token_id` let finalization_payload = - NFTNote::finalization_payload().new(context, note_transient_storage_slot, token_id); + NFTNote::finalization_payload().new(context, hiding_point_slot, token_id); // At last we emit the note hash and the final log finalization_payload.emit(); 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 8443c54e0f1..4ef9953c938 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 @@ -158,7 +158,7 @@ contract TokenBridge { #[public] #[internal] fn _call_mint_on_token(amount: Field, secret_hash: Field) { - Token::at(storage.token.read()).mint_private(amount, secret_hash).call(&mut context); + Token::at(storage.token.read()).mint_private_old(amount, secret_hash).call(&mut context); } // docs:end:call_mint_on_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 2e764587bbc..7392f85b3a8 100644 --- a/noir-projects/noir-contracts/contracts/token_contract/src/main.nr +++ b/noir-projects/noir-contracts/contracts/token_contract/src/main.nr @@ -208,8 +208,9 @@ contract Token { } // docs:end:mint_public // docs:start:mint_private + // TODO(benesjan): To be nuked in a followup PR. #[public] - fn mint_private(amount: Field, secret_hash: Field) { + fn mint_private_old(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); @@ -485,13 +486,15 @@ contract Token { // Transfers token `amount` from public balance of message sender to a private balance of `to`. #[private] fn transfer_to_private(to: AztecAddress, amount: Field) { + // We check the minter permissions in the enqueued call as that allows us to avoid the need for `SharedMutable` + // which is less efficient. let from = context.msg_sender(); let token = Token::at(context.this_address()); // We prepare the transfer. let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); - // At last we finalize the transfer. Usafe of the `unsafe` method here is safe because we set the `from` + // At last we finalize the transfer. Usage of the `unsafe` method here is safe because we set the `from` // function argument to a message sender, guaranteeing that he can transfer only his own tokens. token._finalize_transfer_to_private_unsafe(from, amount, hiding_point_slot).enqueue( &mut context, @@ -578,7 +581,7 @@ contract Token { fn _finalize_transfer_to_private( from: AztecAddress, amount: Field, - note_transient_storage_slot: Field, + hiding_point_slot: Field, context: &mut PublicContext, storage: Storage<&mut PublicContext>, ) { @@ -591,7 +594,70 @@ contract Token { // Then we finalize the partial note with the `amount` let finalization_payload = - UintNote::finalization_payload().new(context, note_transient_storage_slot, amount); + UintNote::finalization_payload().new(context, hiding_point_slot, amount); + + // At last we emit the note hash and the final log + finalization_payload.emit(); + } + + /// Mints token `amount` to a private balance of `to`. Message sender has to have minter permissions (checked + /// in the enqueud call). + #[private] + fn mint_to_private(to: AztecAddress, amount: Field) { + let from = context.msg_sender(); + let token = Token::at(context.this_address()); + + // We prepare the transfer. + let hiding_point_slot = _prepare_transfer_to_private(to, &mut context, storage); + + // At last we finalize the mint. Usage of the `unsafe` method here is safe because we set the `from` + // function argument to a message sender, guaranteeing that only a message sender with minter permissions + // can successfully execute the function. + token._finalize_mint_to_private_unsafe(from, amount, hiding_point_slot).enqueue(&mut context); + } + + /// Finalizes a mint of token `amount` to a private balance of `to`. The mint must be prepared by calling + /// `prepare_transfer_to_private` first and the resulting + /// `hiding_point_slot` must be passed as an argument to this function. + /// + /// Note: This function is only an optimization as it could be replaced by a combination of `mint_public` + /// and `finalize_transfer_to_private`. It is however used very commonly so it makes sense to optimize it + /// (e.g. used during token bridging, in AMM liquidity token etc.). + #[public] + fn finalize_mint_to_private(amount: Field, hiding_point_slot: Field) { + assert(storage.minters.at(context.msg_sender()).read(), "caller is not minter"); + + _finalize_mint_to_private(amount, hiding_point_slot, &mut context, storage); + } + + #[public] + #[internal] + fn _finalize_mint_to_private_unsafe( + from: AztecAddress, + amount: Field, + hiding_point_slot: Field, + ) { + // We check the minter permissions as it was not done in `mint_to_private` function. + assert(storage.minters.at(from).read(), "caller is not minter"); + _finalize_mint_to_private(amount, hiding_point_slot, &mut context, storage); + } + + #[contract_library_method] + fn _finalize_mint_to_private( + amount: Field, + hiding_point_slot: Field, + context: &mut PublicContext, + storage: Storage<&mut PublicContext>, + ) { + let amount = U128::from_integer(amount); + + // First we increase the total supply by the `amount` + let supply = storage.total_supply.read().add(amount); + storage.total_supply.write(supply); + + // Then we finalize the partial note with the `amount` + let finalization_payload = + UintNote::finalization_payload().new(context, hiding_point_slot, amount); // At last we emit the note hash and the final log finalization_payload.emit(); diff --git a/yarn-project/aztec/src/examples/token.ts b/yarn-project/aztec/src/examples/token.ts index 488377f79d7..754640d49e2 100644 --- a/yarn-project/aztec/src/examples/token.ts +++ b/yarn-project/aztec/src/examples/token.ts @@ -1,5 +1,5 @@ import { getSingleKeyAccount } from '@aztec/accounts/single_key'; -import { type AccountWallet, BatchCall, Fr, createPXEClient } from '@aztec/aztec.js'; +import { type AccountWallet, Fr, createPXEClient } from '@aztec/aztec.js'; import { createDebugLogger } from '@aztec/foundation/log'; import { TokenContract } from '@aztec/noir-contracts.js/Token'; @@ -41,15 +41,7 @@ async function main() { // Mint tokens to Alice logger.info(`Minting ${ALICE_MINT_BALANCE} more coins to Alice...`); - - // We don't have the functionality to mint to private so we mint to the Alice's address in public and transfer - // the tokens to private. We use BatchCall to speed the process up. - await new BatchCall(aliceWallet, [ - token.methods.mint_public(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), - token.methods.transfer_to_private(aliceWallet.getAddress(), ALICE_MINT_BALANCE).request(), - ]) - .send() - .wait(); + await tokenAlice.methods.mint_to_private(aliceWallet.getAddress(), ALICE_MINT_BALANCE).send().wait(); logger.info(`${ALICE_MINT_BALANCE} tokens were successfully minted by Alice and transferred to private`); diff --git a/yarn-project/end-to-end/src/fixtures/token_utils.ts b/yarn-project/end-to-end/src/fixtures/token_utils.ts index a0c570648a2..d557bbea2ff 100644 --- a/yarn-project/end-to-end/src/fixtures/token_utils.ts +++ b/yarn-project/end-to-end/src/fixtures/token_utils.ts @@ -1,4 +1,4 @@ -import { type AztecAddress, BatchCall, type DebugLogger, type Wallet, retryUntil } from '@aztec/aztec.js'; +import { type AztecAddress, type DebugLogger, type Wallet, retryUntil } from '@aztec/aztec.js'; import { TokenContract } from '@aztec/noir-contracts.js'; export async function deployToken(adminWallet: Wallet, initialAdminBalance: bigint, logger: DebugLogger) { @@ -23,14 +23,8 @@ export async function mintTokensToPrivate( recipient: AztecAddress, amount: bigint, ) { - // We don't have the functionality to mint to private so we mint to the minter address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. - await new BatchCall(minterWallet, [ - token.methods.mint_public(minterWallet.getAddress(), amount).request(), - token.methods.transfer_to_private(recipient, amount).request(), - ]) - .send() - .wait(); + const tokenAsMinter = await TokenContract.at(token.address, minterWallet); + await tokenAsMinter.methods.mint_to_private(recipient, amount).send().wait(); } const awaitUserSynchronized = async (wallet: Wallet, owner: AztecAddress) => { diff --git a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts index a73e8fbd0b8..e78d1973d38 100644 --- a/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts +++ b/yarn-project/end-to-end/src/guides/writing_an_account_contract.test.ts @@ -3,7 +3,6 @@ import { AccountManager, AuthWitness, type AuthWitnessProvider, - BatchCall, type CompleteAddress, Fr, GrumpkinScalar, @@ -64,15 +63,8 @@ describe('guides/writing_an_account_contract', () => { const token = await TokenContract.deploy(wallet, address, 'TokenName', 'TokenSymbol', 18).send().deployed(); logger.info(`Deployed token contract at ${token.address}`); - // We don't have the functionality to mint to private so we mint to the minter address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. const mintAmount = 50n; - await new BatchCall(wallet, [ - token.methods.mint_public(address, mintAmount).request(), - token.methods.transfer_to_private(address, mintAmount).request(), - ]) - .send() - .wait(); + await token.methods.mint_to_private(address, mintAmount).send().wait(); const balance = await token.methods.balance_of_private(address).simulate(); logger.info(`Balance of wallet is now ${balance}`); diff --git a/yarn-project/end-to-end/src/sample-dapp/index.mjs b/yarn-project/end-to-end/src/sample-dapp/index.mjs index 5b0d6822a4c..62f43ba68d0 100644 --- a/yarn-project/end-to-end/src/sample-dapp/index.mjs +++ b/yarn-project/end-to-end/src/sample-dapp/index.mjs @@ -1,6 +1,6 @@ // docs:start:imports import { getInitialTestAccountsWallets } from '@aztec/accounts/testing'; -import { BatchCall, createPXEClient, waitForPXE } from '@aztec/aztec.js'; +import { createPXEClient, waitForPXE } from '@aztec/aztec.js'; import { fileURLToPath } from '@aztec/foundation/url'; import { getToken } from './contracts.mjs'; @@ -38,15 +38,9 @@ async function mintPrivateFunds(pxe) { await showPrivateBalances(pxe); + // We mint tokens to the owner const mintAmount = 20n; - // We don't have the functionality to mint to private so we mint to the owner address in public and transfer - // the tokens to the recipient in private. We use BatchCall to speed the process up. - await new BatchCall(ownerWallet, [ - token.methods.mint_public(ownerWallet.getAddress(), mintAmount).request(), - token.methods.transfer_to_private(ownerWallet.getAddress(), mintAmount).request(), - ]) - .send() - .wait(); + await token.methods.mint_to_private(ownerWallet.getAddress(), mintAmount).send().wait(); await showPrivateBalances(pxe); } diff --git a/yarn-project/end-to-end/src/shared/browser.ts b/yarn-project/end-to-end/src/shared/browser.ts index d0d7363ca85..75e1019196e 100644 --- a/yarn-project/end-to-end/src/shared/browser.ts +++ b/yarn-project/end-to-end/src/shared/browser.ts @@ -251,9 +251,8 @@ export const browserTestSuite = ( console.log(`Contract Deployed: ${token.address}`); - // We don't use the `mintTokensToPrivate` util as it is not available here - await token.methods.mint_public(owner.getAddress(), initialBalance).send().wait(); - await token.methods.transfer_to_private(owner.getAddress(), initialBalance).send().wait(); + // We mint tokens to the owner + await token.methods.mint_to_private(owner.getAddress(), initialBalance).send().wait(); return [txHash.toString(), token.address.toString()]; }, diff --git a/yarn-project/protocol-contracts/src/protocol_contract_data.ts b/yarn-project/protocol-contracts/src/protocol_contract_data.ts index 12ee3e96237..d7f5390acb2 100644 --- a/yarn-project/protocol-contracts/src/protocol_contract_data.ts +++ b/yarn-project/protocol-contracts/src/protocol_contract_data.ts @@ -50,14 +50,14 @@ export const ProtocolContractAddress: Record }; export const ProtocolContractLeaf = { - AuthRegistry: Fr.fromString('0x16f00633c07cb18f29a819d16a8b5140dea24787ca2a030dc54379a8e6896e3e'), + AuthRegistry: Fr.fromString('0x2c8f0ae77bbed1244767430f7bf1badf98219c40a1dfc1bba1409caf1a9a01cd'), ContractInstanceDeployer: Fr.fromString('0x04a661c9d4d295fc485a7e0f3de40c09b35366343bce8ad229106a8ef4076fe5'), ContractClassRegisterer: Fr.fromString('0x147ba3294403576dbad10f86d3ffd4eb83fb230ffbcd5c8b153dd02942d0611f'), MultiCallEntrypoint: Fr.fromString('0x154b701b41d6cf6da7204fef36b2ee9578b449d21b3792a9287bf45eba48fd26'), - FeeJuice: Fr.fromString('0x2422b0101aba5f5050e8c086a6bdd62b185b188acfba58c77b0016769b96efd6'), - Router: Fr.fromString('0x1cedd0ce59239cb4d55408257d8942bdbd1ac6f0ddab9980157255391dbaa596'), + FeeJuice: Fr.fromString('0x14c807b45f74c73f4b7c7aa85162fc1ae78fafa81ece6c22426fec928edd9471'), + Router: Fr.fromString('0x2cf76c7258a26c77ec56a5b4903996b1e0a21313f78e166f408e955393144665'), }; export const protocolContractTreeRoot = Fr.fromString( - '0x21fb5ab0a0a9b4f282d47d379ec7b5fdf59457a19a593c17f7c29954e1e88dec', + '0x27d6a49f48a8f07db1170672df19cec71fcb71d97588503150628483943e2a14', );