Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: token private mint optimization #9606

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
);
Loading