Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -299,53 +299,66 @@ pub contract PendingNoteHashes {
//}

#[external("private")]
fn test_recursively_create_notes(owner: AztecAddress, how_many_recursions: u64) {
fn test_recursively_create_notes(recipients: [AztecAddress; 10], how_many_recursions: u64) {
let initial_offset: u64 = 0;
self.internal.create_max_notes(owner, initial_offset);
self.internal.create_max_notes(recipients, initial_offset);

let max_notes = self.internal.max_notes_per_call() as u64;
self.call_self.recursively_destroy_and_create_notes(owner, how_many_recursions, max_notes);
self.call_self.recursively_destroy_and_create_notes(
recipients,
how_many_recursions,
max_notes,
);
}

#[external("private")]
fn recursively_destroy_and_create_notes(
owner: AztecAddress,
recipients: [AztecAddress; 10],
executions_left: u64,
current_offset: u64,
) {
assert(executions_left > 0);

self.internal.destroy_max_notes(owner);
self.internal.create_max_notes(owner, current_offset);
self.internal.destroy_max_notes(recipients);
self.internal.create_max_notes(recipients, current_offset);

let executions_left = executions_left - 1;

if executions_left > 0 {
let max_notes = self.internal.max_notes_per_call() as u64;
self.call_self.recursively_destroy_and_create_notes(
owner,
recipients,
executions_left,
current_offset + max_notes,
);
}
}

#[internal("private")]
fn create_max_notes(owner: AztecAddress, offset: u64) {
let owner_balance = self.storage.balances.at(owner);
fn create_max_notes(recipients: [AztecAddress; 10], offset: u64) {
// Distribute notes across recipients using global offset to ensure
// no recipient receives more than 10 notes (UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN)
for i in 0..self.internal.max_notes_per_call() {
let note = FieldNote { value: (offset + i as u64) as Field };
// Skip deliver(): notes are created and nullified in the same tx (kernel squashing),
// so tagged log delivery is unnecessary. Delivering would also exceed
// UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN for the sender-recipient pair.
let _ = owner_balance.insert(note);
let global_index = offset + i as u64;
let recipient_index = (global_index % 10) as u32;
let recipient = recipients[recipient_index];
let recipient_balance = self.storage.balances.at(recipient);

let note = FieldNote { value: i as Field };
recipient_balance.insert(note).deliver(MessageDelivery.ONCHAIN_CONSTRAINED);
}
}

#[internal("private")]
fn destroy_max_notes(owner: AztecAddress) {
let owner_balance = self.storage.balances.at(owner);
let _ = owner_balance.pop_notes(NoteGetterOptions::new());
fn destroy_max_notes(recipients: [AztecAddress; 10]) {
// Pop notes from all recipients
for i in 0..10 {
let recipient = recipients[i];
let recipient_balance = self.storage.balances.at(recipient);
// Note that we're relying on PXE actually returning the notes, we're not constraining that any specific
// number of notes are deleted.
let _ = recipient_balance.pop_notes(NoteGetterOptions::new());
}
}

#[internal("private")]
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/cli-wallet/src/cmds/create_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export async function createAccount(
skipInstancePublication: !publicDeploy,
skipInitialization,
from,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [address],
fee: { paymentMethod, gasSettings },
};

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/cli-wallet/src/utils/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class CLIWallet extends BaseWallet {
increasedFee: InteractionFeeOptions,
): Promise<TxProvingResult> {
const cancellationTxRequest = await this.createCancellationTxExecutionRequest(from, txNonce, increasedFee);
return await this.pxe.proveTx(cancellationTxRequest, this.scopesFor(from));
return await this.pxe.proveTx(cancellationTxRequest, this.scopesFrom(from));
}

override async getAccountFromAddress(address: AztecAddress) {
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/end-to-end/src/composed/docs_examples.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ describe('docs_examples', () => {
const prefundedAccount = await wallet.createSchnorrAccount(accountData.secret, accountData.salt);
const newAccountManager = await wallet.createSchnorrAccount(secretKey, Fr.random(), signingPrivateKey);
const newAccountDeployMethod = await newAccountManager.getDeployMethod();
await newAccountDeployMethod.send({ from: prefundedAccount.address });
await newAccountDeployMethod.send({
from: prefundedAccount.address,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [newAccountManager.address],
});
const newAccountAddress = newAccountManager.address;
const defaultAccountAddress = prefundedAccount.address;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,8 @@ describe('e2e_local_network_example', () => {
return await Promise.all(
accountManagers.map(async x => {
const deployMethod = await x.getDeployMethod();
await deployMethod.send({ from: fundedAccount });
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
await deployMethod.send({ from: fundedAccount, additionalScopes: [x.address] });
return x;
}),
);
Expand Down
14 changes: 10 additions & 4 deletions yarn-project/end-to-end/src/e2e_crowdfunding_and_claim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ describe('e2e_crowdfunding_and_claim', () => {
expect(balanceDNTBeforeWithdrawal).toEqual(0n);

// 3) At last, we withdraw the raised funds from the crowdfunding contract to the operator's address
await crowdfundingContract.methods.withdraw(donationAmount).send({ from: operatorAddress });
await crowdfundingContract.methods
.withdraw(donationAmount)
// Withdraw nullifies the contract's own token notes, which requires its nullifier key.
.send({ from: operatorAddress, additionalScopes: [crowdfundingContract.address] });

const { result: balanceDNTAfterWithdrawal } = await donationToken.methods
.balance_of_private(operatorAddress)
Expand Down Expand Up @@ -285,9 +288,12 @@ describe('e2e_crowdfunding_and_claim', () => {
await crowdfundingContract.methods.donate(donationAmount).send({ from: donor2Address, authWitnesses: [witness] });

// The following should fail as msg_sender != operator
await expect(crowdfundingContract.methods.withdraw(donationAmount).send({ from: donor2Address })).rejects.toThrow(
'Assertion failed: Not an operator',
);
await expect(
crowdfundingContract.methods
.withdraw(donationAmount)
// Withdraw nullifies the contract's own token notes, which requires its nullifier key.
.send({ from: donor2Address, additionalScopes: [crowdfundingContract.address] }),
).rejects.toThrow('Assertion failed: Not an operator');
});

it('cannot donate after a deadline', async () => {
Expand Down
13 changes: 10 additions & 3 deletions yarn-project/end-to-end/src/e2e_escrow_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@ describe('e2e_escrow_contract', () => {
await expectTokenBalance(wallet, token, escrowContract.address, 100n, logger);

logger.info(`Withdrawing funds from token contract to ${recipient}`);
await escrowContract.methods.withdraw(token.address, 30, recipient).send({ from: owner });
await escrowContract.methods
.withdraw(token.address, 30, recipient)
// Withdraw nullifies the contract's own token notes, which requires its nullifier key.
.send({ from: owner, additionalScopes: [escrowContract.address] });

await expectTokenBalance(wallet, token, owner, 0n, logger);
await expectTokenBalance(wallet, token, recipient, 30n, logger);
Expand All @@ -75,7 +78,10 @@ describe('e2e_escrow_contract', () => {

it('refuses to withdraw funds as a non-owner', async () => {
await expect(
escrowContract.methods.withdraw(token.address, 30, recipient).simulate({ from: recipient }),
escrowContract.methods
.withdraw(token.address, 30, recipient)
// Withdraw nullifies the contract's own token notes, which requires its nullifier key.
.simulate({ from: recipient, additionalScopes: [escrowContract.address] }),
).rejects.toThrow();
});

Expand All @@ -90,7 +96,8 @@ describe('e2e_escrow_contract', () => {
await new BatchCall(wallet, [
token.methods.transfer(recipient, 10),
escrowContract.methods.withdraw(token.address, 20, recipient),
]).send({ from: owner });
// Withdraw nullifies the contract's own token notes, which requires its nullifier key.
]).send({ from: owner, additionalScopes: [escrowContract.address] });
await expectTokenBalance(wallet, token, recipient, 30n, logger);
});
});
15 changes: 14 additions & 1 deletion yarn-project/end-to-end/src/e2e_fees/account_init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ describe('e2e_fees account_init', () => {
const [bobsInitialGas] = await t.getGasBalanceFn(bobsAddress);
expect(bobsInitialGas).toEqual(mintAmount);

const { receipt: tx } = await bobsDeployMethod.send({ from: AztecAddress.ZERO, wait: { returnReceipt: true } });
const { receipt: tx } = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [bobsAddress],
wait: { returnReceipt: true },
});

expect(tx.transactionFee!).toBeGreaterThan(0n);
await expect(t.getGasBalanceFn(bobsAddress)).resolves.toEqual([bobsInitialGas - tx.transactionFee!]);
Expand All @@ -100,6 +105,8 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobsAddress, claim);
const { receipt: tx } = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [bobsAddress],
fee: { paymentMethod },
wait: { returnReceipt: true },
});
Expand All @@ -120,6 +127,8 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings);
const { receipt: tx } = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [bobsAddress],
fee: { paymentMethod },
wait: { returnReceipt: true },
});
Expand Down Expand Up @@ -149,6 +158,8 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings);
const { receipt: tx } = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [bobsAddress],
skipInstancePublication: false,
fee: { paymentMethod },
wait: { returnReceipt: true },
Expand Down Expand Up @@ -187,6 +198,8 @@ describe('e2e_fees account_init', () => {
bobsSigningPubKey.y,
).send({
from: aliceAddress,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [bobsAddress],
contractAddressSalt: bobsInstance.salt,
skipClassPublication: true,
skipInstancePublication: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ describe('e2e_fees Fee Juice payments', () => {

// Alice pays for Bob's account contract deployment.
const bobsDeployMethod = await bobsAccountManager.getDeployMethod();
await bobsDeployMethod.send({ from: aliceAddress });
bobAddress = bobsAccountManager.address;
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
await bobsDeployMethod.send({ from: aliceAddress, additionalScopes: [bobAddress] });
});

afterAll(async () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AztecAddress } from '@aztec/aztec.js/addresses';
import { Fr } from '@aztec/aztec.js/fields';
import { Fr, GrumpkinScalar } from '@aztec/aztec.js/fields';
import type { Logger } from '@aztec/aztec.js/log';
import type { AztecNode } from '@aztec/aztec.js/node';
import {
Expand Down Expand Up @@ -286,8 +286,19 @@ describe('e2e_pending_note_hashes_contract', () => {
const minToNeedReset = Math.min(MAX_NOTE_HASHES_PER_TX, MAX_NOTE_HASH_READ_REQUESTS_PER_TX) + 1;
const deployedContract = await deployContract();

// We use 10 different recipients to send private logs to in order to avoid exceeding
// UNFINALIZED_TAGGING_INDEXES_WINDOW_LEN logs emitted for any single sender-recipient pair.
const recipients = (
await Promise.all(
Array.from({ length: 10 }, () =>
wallet.createSchnorrAccount(Fr.random(), Fr.random(), GrumpkinScalar.random()),
),
)
).map(a => a.address);

await deployedContract.methods
.test_recursively_create_notes(owner, Math.ceil(minToNeedReset / notesPerIteration))
.send({ from: owner });
.test_recursively_create_notes(recipients, Math.ceil(minToNeedReset / notesPerIteration))
// Recipients need to be in scope so their keys are accessible for note creation.
.send({ from: owner, additionalScopes: recipients });
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ describe(`deploys and transfers a private only token`, () => {
await wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey);
const { contract: token } = await tokenDeployment.send({
from: deployerAddress,
// The contract constructor initializes private storage vars that need the contract's own nullifier key.
additionalScopes: [tokenInstance.address],
universalDeploy: true,
skipInstancePublication: true,
skipClassPublication: true,
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/fixtures/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,8 @@ export const deployAccounts =
const deployMethod = await accountManager.getDeployMethod();
await deployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [accountManager.address],
skipClassPublication: i !== 0, // Publish the contract class at most once.
});
}
Expand Down
3 changes: 2 additions & 1 deletion yarn-project/end-to-end/src/fixtures/token_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ export async function mintTokensToPrivate(
minter: AztecAddress,
recipient: AztecAddress,
amount: bigint,
additionalScopes?: AztecAddress[],
) {
await token.methods.mint_to_private(recipient, amount).send({ from: minter });
await token.methods.mint_to_private(recipient, amount).send({ from: minter, additionalScopes });
}

export async function expectTokenBalance(
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/end-to-end/src/shared/submit-transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ export const submitTxsTo = async (
times(numTxs, async () => {
const accountManager = await wallet.createSchnorrAccount(Fr.random(), Fr.random(), GrumpkinScalar.random());
const deployMethod = await accountManager.getDeployMethod();
const { txHash } = await deployMethod.send({ from: submitter, wait: NO_WAIT });
const { txHash } = await deployMethod.send({
from: submitter,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [accountManager.address],
wait: NO_WAIT,
});

logger.info(`Tx sent with hash ${txHash}`);
const receipt: TxReceipt = await wallet.getTxReceipt(txHash);
Expand Down
21 changes: 18 additions & 3 deletions yarn-project/end-to-end/src/spartan/setup_test_wallets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,23 @@ export async function deploySponsoredTestAccountsWithTokens(

const paymentMethod = new SponsoredFeePaymentMethod(await getSponsoredFPCAddress());
const recipientDeployMethod = await recipientAccount.getDeployMethod();
await recipientDeployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 2400 } });
await recipientDeployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [recipientAccount.address],
fee: { paymentMethod },
wait: { timeout: 2400 },
});
await Promise.all(
fundedAccounts.map(async a => {
const deployMethod = await a.getDeployMethod();
await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod }, wait: { timeout: 2400 } }); // increase timeout on purpose in order to account for two empty epochs
await deployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [a.address],
fee: { paymentMethod },
wait: { timeout: 2400 },
}); // increase timeout on purpose in order to account for two empty epochs
logger.info(`Account deployed at ${a.address}`);
}),
);
Expand Down Expand Up @@ -139,6 +151,8 @@ async function deployAccountWithDiagnostics(
}
const deployResult = await deployMethod.send({
from: AztecAddress.ZERO,
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
additionalScopes: [account.address],
fee: { paymentMethod, gasSettings },
wait: NO_WAIT,
});
Expand Down Expand Up @@ -261,7 +275,8 @@ export async function deployTestAccountsWithTokens(
fundedAccounts.map(async (a, i) => {
const paymentMethod = new FeeJuicePaymentMethodWithClaim(a.address, claims[i]);
const deployMethod = await a.getDeployMethod();
await deployMethod.send({ from: AztecAddress.ZERO, fee: { paymentMethod } });
// The account constructor initializes storage vars that need the contract's own nullifier key, so we need to add it to scopes.
await deployMethod.send({ from: AztecAddress.ZERO, additionalScopes: [a.address], fee: { paymentMethod } });
logger.info(`Account deployed at ${a.address}`);
}),
);
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/test-wallet/test_wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ export class TestWallet extends BaseWallet {
async proveTx(exec: ExecutionPayload, opts: Omit<SendOptions, 'wait'>): Promise<ProvenTx> {
const fee = await this.completeFeeOptions(opts.from, exec.feePayer, opts.fee?.gasSettings);
const txRequest = await this.createTxExecutionRequestFromPayloadAndFee(exec, opts.from, fee);
const txProvingResult = await this.pxe.proveTx(txRequest, this.scopesFor(opts.from));
const txProvingResult = await this.pxe.proveTx(txRequest, this.scopesFrom(opts.from, opts.additionalScopes));
return new ProvenTx(
this.aztecNode,
await txProvingResult.toTx(),
Expand Down
Loading
Loading