Skip to content

Commit d79af8a

Browse files
authored
feat!: remove ALL_SCOPES (backport #22136) (#22161)
## Summary Backport of #22136 to v4-next, stacked on #22157 (backport of #22113 scoped capsules). Removes the `ALL_SCOPES` option and `AccessScopes` type, forcing all callers to explicitly specify which addresses are in scope via `AztecAddress[]`. This is a breaking change in the PXE/wallet interface. ## Stacking This PR is stacked on `claudebox/backport-22113-scoped-capsules` (#22157) which introduces CapsuleService. With that base in place, the cherry-pick applies cleanly with no conflicts.
2 parents 0939861 + 19daacc commit d79af8a

30 files changed

Lines changed: 253 additions & 271 deletions

docs/docs-developers/docs/resources/migration_notes.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12+
### [PXE] `simulateTx`, `executeUtility`, `profileTx`, and `proveTx` no longer accept `scopes: 'ALL_SCOPES'`
13+
14+
The `AccessScopes` type (`'ALL_SCOPES' | AztecAddress[]`) has been removed. The `scopes` field in `SimulateTxOpts`,
15+
`ExecuteUtilityOpts`, and `ProfileTxOpts` now requires an explicit `AztecAddress[]`. Callers that previously passed
16+
`'ALL_SCOPES'` must now specify which addresses will be in scope for the call.
17+
18+
**Migration:**
19+
20+
```diff
21+
+ const accounts = await pxe.getRegisteredAccounts();
22+
+ const scopes = accounts.map(a => a.address);
23+
24+
// simulateTx
25+
- await pxe.simulateTx(txRequest, { simulatePublic: true, scopes: 'ALL_SCOPES' });
26+
+ await pxe.simulateTx(txRequest, { simulatePublic: true, scopes });
27+
28+
// executeUtility
29+
- await pxe.executeUtility(call, { scopes: 'ALL_SCOPES' });
30+
+ await pxe.executeUtility(call, { scopes });
31+
32+
// profileTx
33+
- await pxe.profileTx(txRequest, { profileMode: 'full', scopes: 'ALL_SCOPES' });
34+
+ await pxe.profileTx(txRequest, { profileMode: 'full', scopes });
35+
36+
// proveTx
37+
- await pxe.proveTx(txRequest, 'ALL_SCOPES');
38+
+ await pxe.proveTx(txRequest, scopes);
39+
```
40+
41+
**Impact**: Any code passing `'ALL_SCOPES'` to `simulateTx`, `executeUtility`, `profileTx`, or `proveTx` will fail to compile. Replace with an explicit array of account addresses.
42+
1243
### [PXE] Capsule operations are now scope-enforced at the PXE level
1344

1445
The PXE now enforces that capsule operations can only access scopes that were authorized for the current execution. If a contract attempts to access a capsule scope that is not in its allowed scopes list, the PXE will throw an error:

yarn-project/cli-wallet/src/cmds/check_tx.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ContractArtifact } from '@aztec/aztec.js/abi';
2-
import type { AztecAddress } from '@aztec/aztec.js/addresses';
2+
import { AztecAddress } from '@aztec/aztec.js/addresses';
33
import { Fr } from '@aztec/aztec.js/fields';
44
import type { AztecNode } from '@aztec/aztec.js/node';
55
import { ProtocolContractAddress } from '@aztec/aztec.js/protocol';
@@ -87,12 +87,13 @@ async function inspectTx(wallet: CLIWallet, aztecNode: AztecNode, txHash: TxHash
8787
// Nullifiers
8888
const nullifierCount = effects.nullifiers.length;
8989
const { deployNullifiers, initNullifiers, classNullifiers } = await getKnownNullifiers(wallet, artifactMap);
90+
const accounts = (await wallet.getAccounts()).map(a => a.item);
9091
if (nullifierCount > 0) {
9192
log(' Nullifiers:');
9293
for (const nullifier of effects.nullifiers) {
9394
const deployed = deployNullifiers[nullifier.toString()];
9495
const note = deployed
95-
? (await wallet.getNotes({ siloedNullifier: nullifier, contractAddress: deployed, scopes: 'ALL_SCOPES' }))[0]
96+
? (await wallet.getNotes({ siloedNullifier: nullifier, contractAddress: deployed, scopes: accounts }))[0]
9697
: undefined;
9798
const initialized = initNullifiers[nullifier.toString()];
9899
const registered = classNullifiers[nullifier.toString()];

yarn-project/cli-wallet/src/utils/wallet.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ export class CLIWallet extends BaseWallet {
5656

5757
override async getAccounts(): Promise<Aliased<AztecAddress>[]> {
5858
const accounts = (await this.db?.listAliases('accounts')) ?? [];
59-
return Promise.resolve(accounts.map(({ key, value }) => ({ alias: value, item: AztecAddress.fromString(key) })));
59+
return Promise.resolve(
60+
accounts.map(({ key, value }) => {
61+
const alias = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key;
62+
return { alias, item: AztecAddress.fromString(value) };
63+
}),
64+
);
6065
}
6166

6267
private async createCancellationTxExecutionRequest(

yarn-project/pxe/src/access_scopes.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

yarn-project/pxe/src/contract_function_simulator/contract_function_simulator.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ import {
8989
getFinalMinRevertibleSideEffectCounter,
9090
} from '@aztec/stdlib/tx';
9191

92-
import type { AccessScopes } from '../access_scopes.js';
9392
import type { ContractSyncService } from '../contract_sync/contract_sync_service.js';
9493
import type { MessageContextService } from '../messages/message_context_service.js';
9594
import type { AddressStore } from '../storage/address_store/address_store.js';
@@ -123,7 +122,7 @@ export type ContractSimulatorRunOpts = {
123122
/** The address used as a tagging sender when emitting private logs. */
124123
senderForTags?: AztecAddress;
125124
/** The accounts whose notes we can access in this call. */
126-
scopes: AccessScopes;
125+
scopes: AztecAddress[];
127126
/** The job ID for staged writes. */
128127
jobId: string;
129128
};
@@ -320,7 +319,7 @@ export class ContractFunctionSimulator {
320319
call: FunctionCall,
321320
authwits: AuthWitness[],
322321
anchorBlockHeader: BlockHeader,
323-
scopes: AccessScopes,
322+
scopes: AztecAddress[],
324323
jobId: string,
325324
): Promise<{ result: Fr[]; offchainEffects: OffchainEffect[] }> {
326325
const entryPointArtifact = await this.contractStore.getFunctionArtifactWithDebugMetadata(call.to, call.selector);

yarn-project/pxe/src/contract_function_simulator/oracle/oracle_version_is_checked.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ describe('Oracle Version Check test suite', () => {
149149
anchorBlockHeader,
150150
senderForTags,
151151
jobId: 'test',
152-
scopes: 'ALL_SCOPES',
152+
scopes: [],
153153
});
154154

155155
expect(assertCompatibleOracleVersionSpy).toHaveBeenCalledTimes(1);
@@ -206,7 +206,7 @@ describe('Oracle Version Check test suite', () => {
206206
messageContextService,
207207
contractSyncService,
208208
jobId: 'test',
209-
scopes: 'ALL_SCOPES',
209+
scopes: [],
210210
});
211211
});
212212

yarn-project/pxe/src/contract_function_simulator/oracle/private_execution.test.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ describe('Private Execution test suite', () => {
219219
anchorBlockHeader,
220220
senderForTags,
221221
jobId: TEST_JOB_ID,
222-
scopes: 'ALL_SCOPES',
222+
scopes: [owner],
223223
});
224224
};
225225

@@ -324,8 +324,7 @@ describe('Private Execution test suite', () => {
324324
// Configure mock to actually perform sync_state calls (needed for nested call tests)
325325
contractSyncService.ensureContractSynced.mockImplementation(
326326
async (contractAddress, functionToInvokeAfterSync, utilityExecutor, anchorBlockHeader, jobId, scopes) => {
327-
const scopeAddresses = scopes === 'ALL_SCOPES' ? [owner] : scopes;
328-
for (const scope of scopeAddresses) {
327+
for (const scope of scopes) {
329328
await syncState(
330329
contractAddress,
331330
contractStore,
@@ -384,6 +383,19 @@ describe('Private Execution test suite', () => {
384383

385384
keyStore.getAccounts.mockResolvedValue([owner, recipient, senderForTags]);
386385

386+
keyStore.accountHasKey.mockImplementation(async (account: AztecAddress, pkMHash: Fr) => {
387+
if (account.equals(owner)) {
388+
return pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash());
389+
}
390+
if (account.equals(recipient)) {
391+
return pkMHash.equals(await recipientCompleteAddress.publicKeys.masterNullifierPublicKey.hash());
392+
}
393+
if (account.equals(senderForTags)) {
394+
return pkMHash.equals(await senderForTagsCompleteAddress.publicKeys.masterNullifierPublicKey.hash());
395+
}
396+
return false;
397+
});
398+
387399
keyStore.getKeyValidationRequest.mockImplementation(async (pkMHash: Fr, contractAddress: AztecAddress) => {
388400
if (pkMHash.equals(await ownerCompleteAddress.publicKeys.masterNullifierPublicKey.hash())) {
389401
return Promise.resolve(

yarn-project/pxe/src/contract_function_simulator/oracle/private_execution_oracle.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import {
2525
type TxContext,
2626
} from '@aztec/stdlib/tx';
2727

28-
import type { AccessScopes } from '../../access_scopes.js';
2928
import { NoteService } from '../../notes/note_service.js';
3029
import type { SenderTaggingStore } from '../../storage/tagging_store/sender_tagging_store.js';
3130
import { syncSenderTaggingIndexes } from '../../tagging/index.js';
@@ -43,7 +42,7 @@ export type PrivateExecutionOracleArgs = Omit<UtilityExecutionOracleArgs, 'contr
4342
txContext: TxContext;
4443
callContext: CallContext;
4544
/** Needed to trigger contract synchronization before nested calls */
46-
utilityExecutor: (call: FunctionCall, scopes: AccessScopes) => Promise<void>;
45+
utilityExecutor: (call: FunctionCall, scopes: AztecAddress[]) => Promise<void>;
4746
executionCache: HashedValuesCache;
4847
noteCache: ExecutionNoteCache;
4948
taggingIndexCache: ExecutionTaggingIndexCache;
@@ -76,7 +75,7 @@ export class PrivateExecutionOracle extends UtilityExecutionOracle implements IP
7675
private readonly argsHash: Fr;
7776
private readonly txContext: TxContext;
7877
private readonly callContext: CallContext;
79-
private readonly utilityExecutor: (call: FunctionCall, scopes: AccessScopes) => Promise<void>;
78+
private readonly utilityExecutor: (call: FunctionCall, scopes: AztecAddress[]) => Promise<void>;
8079
private readonly executionCache: HashedValuesCache;
8180
private readonly noteCache: ExecutionNoteCache;
8281
private readonly taggingIndexCache: ExecutionTaggingIndexCache;

yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution.test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -232,12 +232,16 @@ describe('Utility Execution test suite', () => {
232232
let utilityExecutionOracle: UtilityExecutionOracle;
233233
const syncedBlockNumber = 100;
234234

235+
let scope: AztecAddress;
236+
235237
beforeEach(async () => {
236238
contractAddress = await AztecAddress.random();
237239
anchorBlockHeader = BlockHeader.empty({
238240
globalVariables: GlobalVariables.empty({ blockNumber: BlockNumber(syncedBlockNumber) }),
239241
});
240242

243+
scope = await AztecAddress.random();
244+
241245
utilityExecutionOracle = new UtilityExecutionOracle({
242246
contractAddress,
243247
authWitnesses: [],
@@ -250,12 +254,12 @@ describe('Utility Execution test suite', () => {
250254
aztecNode,
251255
recipientTaggingStore,
252256
senderAddressBookStore,
253-
capsuleService: new CapsuleService(capsuleStore, 'ALL_SCOPES'),
257+
capsuleService: new CapsuleService(capsuleStore, [scope]),
254258
privateEventStore,
255259
messageContextService,
256260
contractSyncService,
257261
jobId: 'test-job-id',
258-
scopes: 'ALL_SCOPES',
262+
scopes: [scope],
259263
});
260264
});
261265

@@ -268,8 +272,7 @@ describe('Utility Execution test suite', () => {
268272
});
269273

270274
describe('capsules', () => {
271-
it('forwards scope to the capsule store', async () => {
272-
const scope = await AztecAddress.random();
275+
it('forwards scope to the capsule service', async () => {
273276
const slot = Fr.random();
274277
const srcSlot = Fr.random();
275278
const dstSlot = Fr.random();
@@ -317,12 +320,12 @@ describe('Utility Execution test suite', () => {
317320
aztecNode,
318321
recipientTaggingStore,
319322
senderAddressBookStore,
320-
capsuleService: new CapsuleService(capsuleStore, 'ALL_SCOPES'),
323+
capsuleService: new CapsuleService(capsuleStore, [scope]),
321324
privateEventStore,
322325
messageContextService,
323326
contractSyncService,
324327
jobId: 'test-job-id',
325-
scopes: 'ALL_SCOPES',
328+
scopes: [scope],
326329
});
327330

328331
capsuleStore.getCapsule.mockResolvedValueOnce(persisted);
@@ -358,7 +361,6 @@ describe('Utility Execution test suite', () => {
358361
describe('resolveMessageContexts', () => {
359362
const requestSlot = Fr.random();
360363
const responseSlot = Fr.random();
361-
const scope = AztecAddress.fromBigInt(42n);
362364

363365
it('throws when contractAddress does not match', async () => {
364366
const wrongAddress = await AztecAddress.random();
@@ -498,12 +500,12 @@ describe('Utility Execution test suite', () => {
498500
aztecNode,
499501
recipientTaggingStore,
500502
senderAddressBookStore,
501-
capsuleService: new CapsuleService(capsuleStore, 'ALL_SCOPES'),
503+
capsuleService: new CapsuleService(capsuleStore, []),
502504
privateEventStore,
503505
messageContextService,
504506
contractSyncService,
505507
jobId: 'test-job-id',
506-
scopes: 'ALL_SCOPES',
508+
scopes: [],
507509
});
508510

509511
const oracleA = makeOracle(contractAddressA);

yarn-project/pxe/src/contract_function_simulator/oracle/utility_execution_oracle.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import type { NoteStatus } from '@aztec/stdlib/note';
2121
import { MerkleTreeId, type NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
2222
import type { BlockHeader, Capsule, OffchainEffect } from '@aztec/stdlib/tx';
2323

24-
import type { AccessScopes } from '../../access_scopes.js';
2524
import { createContractLogger, logContractMessage, stripAztecnrLogPrefix } from '../../contract_logging.js';
2625
import type { ContractSyncService } from '../../contract_sync/contract_sync_service.js';
2726
import { EventService } from '../../events/event_service.js';
@@ -65,7 +64,7 @@ export type UtilityExecutionOracleArgs = {
6564
contractSyncService: ContractSyncService;
6665
jobId: string;
6766
log?: ReturnType<typeof createLogger>;
68-
scopes: AccessScopes;
67+
scopes: AztecAddress[];
6968
};
7069

7170
/**
@@ -96,7 +95,7 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
9695
protected readonly contractSyncService: ContractSyncService;
9796
protected readonly jobId: string;
9897
protected logger: ReturnType<typeof createLogger>;
99-
protected readonly scopes: AccessScopes;
98+
protected readonly scopes: AztecAddress[];
10099

101100
constructor(args: UtilityExecutionOracleArgs) {
102101
this.contractAddress = args.contractAddress;
@@ -166,18 +165,15 @@ export class UtilityExecutionOracle implements IMiscOracle, IUtilityExecutionOra
166165
* @throws If scopes are defined and the account is not in the scopes.
167166
*/
168167
public async getKeyValidationRequest(pkMHash: Fr): Promise<KeyValidationRequest> {
169-
// If scopes are defined, check that the key belongs to an account in the scopes.
170-
if (this.scopes !== 'ALL_SCOPES' && this.scopes.length > 0) {
171-
let hasAccess = false;
172-
for (let i = 0; i < this.scopes.length && !hasAccess; i++) {
173-
if (await this.keyStore.accountHasKey(this.scopes[i], pkMHash)) {
174-
hasAccess = true;
175-
}
176-
}
177-
if (!hasAccess) {
178-
throw new Error(`Key validation request denied: no scoped account has a key with hash ${pkMHash.toString()}.`);
168+
let hasAccess = false;
169+
for (let i = 0; i < this.scopes.length && !hasAccess; i++) {
170+
if (await this.keyStore.accountHasKey(this.scopes[i], pkMHash)) {
171+
hasAccess = true;
179172
}
180173
}
174+
if (!hasAccess) {
175+
throw new Error(`Key validation request denied: no scoped account has a key with hash ${pkMHash.toString()}.`);
176+
}
181177
return this.keyStore.getKeyValidationRequest(pkMHash, this.contractAddress);
182178
}
183179

0 commit comments

Comments
 (0)