Skip to content
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) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We had to make these changes when we introduced scopes, because there was no way to inject multiple scopes. Since now there is, we can revert the change we made

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: 1 addition & 1 deletion yarn-project/aztec.js/src/contract/deploy_method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export type DeployOptionsWithoutWait = Omit<RequestDeployOptions, 'deployer'> &
* is mutually exclusive with "deployer"
*/
universalDeploy?: boolean;
} & Pick<SendInteractionOptionsWithoutWait, 'from' | 'fee'>;
} & Pick<SendInteractionOptionsWithoutWait, 'from' | 'fee' | 'additionalScopes'>;

/**
* Extends the deployment options with the required parameters to send the transaction.
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/aztec.js/src/contract/interaction_options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export type SendInteractionOptionsWithoutWait = RequestInteractionOptions & {
from: AztecAddress;
/** The fee options for the transaction. */
fee?: InteractionFeeOptions;
/**
* Additional addresses whose private state should be accessible during execution,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Additional addresses whose private state should be accessible during execution,
* Additional addresses whose private state and keys should be accessible during execution,

* beyond the sender's

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Think it would be useful to have here an example of why you would want this.

*/
additionalScopes?: AztecAddress[];
};

/**
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/aztec.js/src/wallet/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@ export const SendOptionsSchema = z.object({
capsules: optional(z.array(Capsule.schema)),
fee: optional(GasSettingsOptionSchema),
wait: optional(z.union([z.literal(NO_WAIT), WaitOptsSchema])),
additionalScopes: optional(z.array(schemas.AztecAddress)),
});

export const SimulateOptionsSchema = z.object({
Expand All @@ -313,6 +314,7 @@ export const SimulateOptionsSchema = z.object({
skipTxValidation: optional(z.boolean()),
skipFeeEnforcement: optional(z.boolean()),
includeMetadata: optional(z.boolean()),
additionalScopes: optional(z.array(schemas.AztecAddress)),
});

export const ProfileOptionsSchema = SimulateOptionsSchema.extend({
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/bot/src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,13 @@ export class Bot extends BaseBot {

const batch = new BatchCall(wallet, calls);
const opts = await this.getSendMethodOpts(batch);
const additionalScopes = isStandardTokenContract(token) ? undefined : [token.address];

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does the non standard token need additional scopes?


this.log.verbose(`Simulating transaction with ${calls.length}`, logCtx);
await batch.simulate({ from: this.defaultAccountAddress });
await batch.simulate({ from: this.defaultAccountAddress, additionalScopes });

this.log.verbose(`Sending transaction`, logCtx);
return batch.send({ ...opts, wait: NO_WAIT });
return batch.send({ ...opts, additionalScopes, wait: NO_WAIT });
}

public async getBalances() {
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/bot/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ export class BotFactory {
tokenInstance = await deploy.getInstance(deployOpts);
token = PrivateTokenContract.at(tokenInstance.address, this.wallet);
await this.wallet.registerContract(tokenInstance, PrivateTokenContract.artifact, tokenSecretKey);
deployOpts.additionalScopes = [tokenInstance.address];
} else {
throw new Error(`Unsupported token contract type: ${this.config.contract}`);
}
Expand Down Expand Up @@ -479,8 +480,9 @@ export class BotFactory {
return;
}

const additionalScopes = isStandardToken ? undefined : [token.address];
await this.withNoMinTxsPerBlock(async () => {
const txHash = await new BatchCall(token.wallet, calls).send({ from: minter, wait: NO_WAIT });
const txHash = await new BatchCall(token.wallet, calls).send({ from: minter, additionalScopes, wait: NO_WAIT });
this.log.info(`Sent token mint tx with hash ${txHash.toString()}`);
return waitForTx(this.aztecNode, txHash, { timeout: this.config.txMinedWaitSeconds });
});
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/bot/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ export async function getPrivateBalance(
who: AztecAddress,
from?: AztecAddress,
): Promise<bigint> {
const privateBalance = await token.methods.get_balance(who).simulate({ from: from ?? who });
const privateBalance = await token.methods
.get_balance(who)
.simulate({ from: from ?? who, additionalScopes: [token.address] });
return privateBalance;
}

Expand Down
50 changes: 35 additions & 15 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 @@ -43,7 +43,7 @@ describe('e2e_crowdfunding_and_claim', () => {
let crowdfundingContract: CrowdfundingContract;
let claimContract: ClaimContract;

let crowdfundingSecretKey;
let crowdfundingSecretKey: Fr;
let crowdfundingPublicKeys: PublicKeys;
let cheatCodes: CheatCodes;
let deadline: number; // end of crowdfunding period
Expand Down Expand Up @@ -94,7 +94,10 @@ describe('e2e_crowdfunding_and_claim', () => {
);
const crowdfundingInstance = await crowdfundingDeployment.getInstance();
await wallet.registerContract(crowdfundingInstance, CrowdfundingContract.artifact, crowdfundingSecretKey);
crowdfundingContract = await crowdfundingDeployment.send({ from: operatorAddress });
crowdfundingContract = await crowdfundingDeployment.send({
from: operatorAddress,
additionalScopes: [crowdfundingInstance.address],
});
logger.info(`Crowdfunding contract deployed at ${crowdfundingContract.address}`);

claimContract = await ClaimContract.deploy(wallet, crowdfundingContract.address, rewardToken.address).send({
Expand Down Expand Up @@ -125,13 +128,15 @@ describe('e2e_crowdfunding_and_claim', () => {
0,
);
const witness = await wallet.createAuthWit(donor1Address, { caller: crowdfundingContract.address, action });
await crowdfundingContract.methods.donate(donationAmount).send({ from: donor1Address, authWitnesses: [witness] });
await crowdfundingContract.methods
.donate(donationAmount)
.send({ from: donor1Address, additionalScopes: [crowdfundingContract.address], authWitnesses: [witness] });

// The donor should have exactly one note
const pageIndex = 0;
const notes = await crowdfundingContract.methods
.get_donation_notes(donor1Address, pageIndex)
.simulate({ from: donor1Address });
.simulate({ from: donor1Address, additionalScopes: [crowdfundingContract.address] });
expect(notes.len).toEqual(1n);
uintNote = notes.storage[0];
}
Expand All @@ -151,7 +156,9 @@ 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)
.send({ from: operatorAddress, additionalScopes: [crowdfundingContract.address] });

const balanceDNTAfterWithdrawal = await donationToken.methods
.balance_of_private(operatorAddress)
Expand Down Expand Up @@ -180,13 +187,15 @@ describe('e2e_crowdfunding_and_claim', () => {
0,
);
const witness = await wallet.createAuthWit(donorAddress, { caller: crowdfundingContract.address, action });
await crowdfundingContract.methods.donate(donationAmount).send({ from: donorAddress, authWitnesses: [witness] });
await crowdfundingContract.methods
.donate(donationAmount)
.send({ from: donorAddress, additionalScopes: [crowdfundingContract.address], authWitnesses: [witness] });

// The donor should have exactly one note
const pageIndex = 0;
const notes = await crowdfundingContract.methods
.get_donation_notes(donorAddress, pageIndex)
.simulate({ from: donorAddress });
.simulate({ from: donorAddress, additionalScopes: [crowdfundingContract.address] });
expect(notes.len).toEqual(1n);
const anotherDonationNote = notes.storage[0];

Expand Down Expand Up @@ -221,7 +230,12 @@ describe('e2e_crowdfunding_and_claim', () => {
deadline,
);

otherCrowdfundingContract = await otherCrowdfundingDeployment.send({ from: operatorAddress });
const otherCrowdfundingInstance = await otherCrowdfundingDeployment.getInstance();
await wallet.registerContract(otherCrowdfundingInstance, CrowdfundingContract.artifact, crowdfundingSecretKey);
otherCrowdfundingContract = await otherCrowdfundingDeployment.send({
from: operatorAddress,
additionalScopes: [otherCrowdfundingInstance.address],
});
logger.info(`Crowdfunding contract deployed at ${otherCrowdfundingContract.address}`);
}

Expand All @@ -237,13 +251,13 @@ describe('e2e_crowdfunding_and_claim', () => {
const witness = await wallet.createAuthWit(donor1Address, { caller: otherCrowdfundingContract.address, action });
await otherCrowdfundingContract.methods
.donate(donationAmount)
.send({ from: donor1Address, authWitnesses: [witness] });
.send({ from: donor1Address, additionalScopes: [otherCrowdfundingContract.address], authWitnesses: [witness] });

// 3) Get the donation note
const pageIndex = 0;
const notes = await otherCrowdfundingContract.methods
.get_donation_notes(donor1Address, pageIndex)
.simulate({ from: donor1Address });
.simulate({ from: donor1Address, additionalScopes: [otherCrowdfundingContract.address] });
expect(notes.len).toEqual(1n);
const otherContractNote = notes.storage[0];

Expand All @@ -266,12 +280,16 @@ describe('e2e_crowdfunding_and_claim', () => {
const witness = await wallet.createAuthWit(donor2Address, { caller: crowdfundingContract.address, action });

// 2) We donate to the crowdfunding contract
await crowdfundingContract.methods.donate(donationAmount).send({ from: donor2Address, authWitnesses: [witness] });
await crowdfundingContract.methods
.donate(donationAmount)
.send({ from: donor2Address, additionalScopes: [crowdfundingContract.address], 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)
.send({ from: donor2Address, additionalScopes: [crowdfundingContract.address] }),
).rejects.toThrow('Assertion failed: Not an operator');
});

it('cannot donate after a deadline', async () => {
Expand All @@ -292,7 +310,9 @@ describe('e2e_crowdfunding_and_claim', () => {

// 3) We donate to the crowdfunding contract
await expect(
crowdfundingContract.methods.donate(donationAmount).send({ from: donor2Address, authWitnesses: [witness] }),
crowdfundingContract.methods
.donate(donationAmount)
.send({ from: donor2Address, additionalScopes: [crowdfundingContract.address], authWitnesses: [witness] }),
).rejects.toThrow();
});
});
14 changes: 9 additions & 5 deletions yarn-project/end-to-end/src/e2e_escrow_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,13 @@ describe('e2e_escrow_contract', () => {
const escrowDeployment = EscrowContract.deployWithPublicKeys(escrowPublicKeys, wallet, owner);
const escrowInstance = await escrowDeployment.getInstance();
await wallet.registerContract(escrowInstance, EscrowContract.artifact, escrowSecretKey);
escrowContract = await escrowDeployment.send({ from: owner });
escrowContract = await escrowDeployment.send({ from: owner, additionalScopes: [escrowInstance.address] });
logger.info(`Escrow contract deployed at ${escrowContract.address}`);

// Deploy Token contract and mint funds for the escrow contract
token = await TokenContract.deploy(wallet, owner, 'TokenName', 'TokenSymbol', 18).send({ from: owner });

await mintTokensToPrivate(token, owner, escrowContract.address, 100n);
await mintTokensToPrivate(token, owner, escrowContract.address, 100n, [escrowContract.address]);

logger.info(`Token contract deployed at ${token.address}`);
});
Expand All @@ -60,7 +60,9 @@ 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)
.send({ from: owner, additionalScopes: [escrowContract.address] });

await expectTokenBalance(wallet, token, owner, 0n, logger);
await expectTokenBalance(wallet, token, recipient, 30n, logger);
Expand All @@ -69,7 +71,9 @@ 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)
.simulate({ from: recipient, additionalScopes: [escrowContract.address] }),
).rejects.toThrow();
});

Expand All @@ -84,7 +88,7 @@ describe('e2e_escrow_contract', () => {
await new BatchCall(wallet, [
token.methods.transfer(recipient, 10),
escrowContract.methods.withdraw(token.address, 20, recipient),
]).send({ from: owner });
]).send({ from: owner, additionalScopes: [escrowContract.address] });
await expectTokenBalance(wallet, token, recipient, 30n, logger);
});
});
10 changes: 9 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,11 @@ describe('e2e_fees account_init', () => {
const [bobsInitialGas] = await t.getGasBalanceFn(bobsAddress);
expect(bobsInitialGas).toEqual(mintAmount);

const tx = await bobsDeployMethod.send({ from: AztecAddress.ZERO, wait: { returnReceipt: true } });
const tx = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
additionalScopes: [bobsAddress],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only thing that worries me. It makes 100% total sense that account contract self-deployments need this, but for such a common operation it's getting very verbose. Maybe we should consider this a higher level abstraction and provide some sane defaults

wait: { returnReceipt: true },
});

expect(tx.transactionFee!).toBeGreaterThan(0n);
await expect(t.getGasBalanceFn(bobsAddress)).resolves.toEqual([bobsInitialGas - tx.transactionFee!]);
Expand All @@ -100,6 +104,7 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new FeeJuicePaymentMethodWithClaim(bobsAddress, claim);
const tx = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
additionalScopes: [bobsAddress],
fee: { paymentMethod },
wait: { returnReceipt: true },
});
Expand All @@ -120,6 +125,7 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new PrivateFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings);
const tx = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
additionalScopes: [bobsAddress],
fee: { paymentMethod },
wait: { returnReceipt: true },
});
Expand Down Expand Up @@ -149,6 +155,7 @@ describe('e2e_fees account_init', () => {
const paymentMethod = new PublicFeePaymentMethod(bananaFPC.address, bobsAddress, wallet, gasSettings);
const tx = await bobsDeployMethod.send({
from: AztecAddress.ZERO,
additionalScopes: [bobsAddress],
skipInstancePublication: false,
fee: { paymentMethod },
wait: { returnReceipt: true },
Expand Down Expand Up @@ -187,6 +194,7 @@ describe('e2e_fees account_init', () => {
bobsSigningPubKey.y,
).send({
from: aliceAddress,
additionalScopes: [bobsAddress],
contractAddressSalt: bobsInstance.salt,
skipClassPublication: true,
skipInstancePublication: true,
Expand Down
Loading
Loading