Skip to content

Commit

Permalink
feat: token private mint optimization (#9606)
Browse files Browse the repository at this point in the history
Impl. of token `finalize_mint_to_private`
  • Loading branch information
benesjan authored Oct 31, 2024
1 parent 054fd67 commit e8fadc7
Show file tree
Hide file tree
Showing 9 changed files with 88 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
) {
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
74 changes: 70 additions & 4 deletions noir-projects/noir-contracts/contracts/token_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>,
) {
Expand All @@ -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();
Expand Down
12 changes: 2 additions & 10 deletions yarn-project/aztec/src/examples/token.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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`);

Expand Down
12 changes: 3 additions & 9 deletions yarn-project/end-to-end/src/fixtures/token_utils.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import {
AccountManager,
AuthWitness,
type AuthWitnessProvider,
BatchCall,
type CompleteAddress,
Fr,
GrumpkinScalar,
Expand Down Expand Up @@ -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}`);
Expand Down
12 changes: 3 additions & 9 deletions yarn-project/end-to-end/src/sample-dapp/index.mjs
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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);
}
Expand Down
5 changes: 2 additions & 3 deletions yarn-project/end-to-end/src/shared/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()];
},
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/protocol-contracts/src/protocol_contract_data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ export const ProtocolContractAddress: Record<ProtocolContractName, AztecAddress>
};

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',
);

0 comments on commit e8fadc7

Please sign in to comment.