Skip to content

Commit bb59b1d

Browse files
committed
Merge branch 'backport-to-v4-next-staging' into claudebox/backport-22902-aztec-up-shadow-fix
2 parents 99115f4 + 18b40a1 commit bb59b1d

85 files changed

Lines changed: 2166 additions & 1396 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci3.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ jobs:
121121
if [ -n "${SLACK_BOT_TOKEN}" ]; then
122122
read -r -d '' data <<EOF || true
123123
{
124-
"channel": "#team-alpha",
124+
"channel": "#backports",
125125
"text": "CI3 failed on backport PR: <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} - ${{ github.event.pull_request.title }}>\n<https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run>"
126126
}
127127
EOF

boxes/boxes/react/src/hooks/useContract.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ export function useContract() {
1515
setWait(true);
1616
const wallet = await deployerEnv.getWallet();
1717
const defaultAccountAddress = deployerEnv.getDefaultAccountAddress();
18-
const salt = Fr.random();
1918

2019
const { BoxReactContract } = await import('../../artifacts/BoxReact');
2120

2221
const deploymentPromise = BoxReactContract.deploy(wallet, Fr.random(), defaultAccountAddress).send({
2322
from: defaultAccountAddress,
24-
contractAddressSalt: salt,
2523
});
2624

2725
const { contract } = await toast.promise(deploymentPromise, {

boxes/boxes/vanilla/scripts/deploy.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,10 @@ async function deployContract(wallet: Wallet, deployer: AztecAddress) {
7272

7373
const sponsoredPFCContract = await getSponsoredPFCContract();
7474

75-
const { contract } = await PrivateVotingContract.deploy(
76-
wallet,
77-
deployer
78-
).send({
75+
const { contract } = await PrivateVotingContract.deploy(wallet, deployer, {
76+
salt,
77+
}).send({
7978
from: deployer,
80-
contractAddressSalt: salt,
8179
fee: {
8280
paymentMethod: new SponsoredFeePaymentMethod(
8381
sponsoredPFCContract.address

boxes/boxes/vite/src/hooks/useContract.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ export function useContract() {
1515
setWait(true);
1616
const wallet = await deployerEnv.getWallet();
1717
const defaultAccountAddress = deployerEnv.getDefaultAccountAddress();
18-
const salt = Fr.random();
1918

2019
const { BoxReactContract } = await import('../../artifacts/BoxReact');
2120

2221
const deploymentPromise = BoxReactContract.deploy(wallet, Fr.random(), defaultAccountAddress).send({
2322
from: defaultAccountAddress,
24-
contractAddressSalt: salt,
2523
});
2624

2725
const { contract } = await toast.promise(deploymentPromise, {

docs/docs-developers/docs/aztec-nr/debugging.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,33 @@ LOG_LEVEL="silent;debug:simulator"
9999
| `No public key registered for address` | Call `wallet.registerSender(...)` |
100100
| `Direct invocation of ... functions is not supported` | Use `self.call()`, `self.view()`, or `self.enqueue()` to [call contract functions](framework-description/calling_contracts.md) |
101101
| `Failed to solve brillig function` | Check function parameters and note validity |
102+
| `Cross-contract utility call denied` | Configure an `authorizeUtilityCall` [execution hook](#cross-contract-utility-call-denied) on your PXE |
103+
104+
#### Cross-contract utility call denied
105+
106+
When a contract executes a utility function that calls into a different contract, PXE asks an **execution hook** whether the call should be allowed. If no hook is configured, or the hook denies the request, you will see:
107+
108+
```
109+
Cross-contract utility call denied: <reason>. <caller> attempted to call <target>:<selector> (<name>).
110+
```
111+
112+
To fix this, pass an `authorizeUtilityCall` hook when creating your PXE:
113+
114+
```typescript
115+
import { PXE } from "@aztec/pxe/server";
116+
117+
const pxe = await PXE.create({
118+
// ...other options
119+
hooks: {
120+
authorizeUtilityCall: async (request) => {
121+
// Inspect request.caller, request.target, request.functionSelector, etc.
122+
return { authorized: true };
123+
},
124+
},
125+
});
126+
```
127+
128+
The hook receives a `UtilityCallAuthorizationRequest` with the caller address, target address, function selector, function name, arguments, and caller context (`'private'` or `'utility'`). Return `{ authorized: true }` to allow or `{ authorized: false, reason: '...' }` to deny with a message.
102129

103130
### Circuit Errors
104131

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,117 @@ If you relied on a bundled bare-name binary for general use:
4040

4141
If you set `Noir: Nargo Path` in the VS Code Noir extension to `$HOME/.aztec/current/bin/nargo`, change it to `$HOME/.aztec/current/bin/aztec-nargo` (the symlink is a drop-in for `nargo`). See the [Noir VSCode Extension guide](../aztec-nr/installation.md) for details.
4242

43+
### [Aztec.js] `DeployMethod` address-affecting parameters move to construction time
44+
45+
Salt, deployer, and public keys are now passed when the `DeployMethod` is constructed, not on every call to `send` / `simulate` / `request` / `getInstance`. This locks the contract address once it is determined and prevents the silent salt-cache poisoning bug where the address could change between calls.
46+
47+
`contractAddressSalt`, `deployer`, and `universalDeploy` have been removed from `DeployOptions`, `RequestDeployOptions`, and `SimulateDeployOptions`. They now live on a new `DeployInstantiationOptions` argument passed at construction. `deployer` and `universalDeploy` are mutually exclusive; passing both throws. `Contract.deployWithPublicKeys` and the generated `MyContract.deployWithPublicKeys(...)` factories have been removed; pass `publicKeys` via the `instantiation` argument of `deploy(...)` instead. The buggy synchronous `address` and `partialAddress` getters have been removed and replaced with `getAddress()` and `getPartialAddress()` (both `async`).
48+
49+
The compact form keeps working: `MyContract.deploy(wallet, ...args).send({ from: alice })` deploys with `deployer = alice` and `salt = random()`, exactly as before. The deployer is locked the first time `send` / `simulate` / `profile` is called (from `options.from`, with `NO_FROM` or undefined → universal) and cannot change after that:
50+
51+
- Subsequent `send` / `simulate` / `profile` calls with a `from` that would imply a different deployer throw, instead of silently producing a different address.
52+
- A lock to universal (`AztecAddress.ZERO`) is the only one compatible with any sender, since the universal address does not depend on `from`.
53+
- A lock to a concrete address only accepts that exact `from` on subsequent calls.
54+
55+
**Migration:**
56+
57+
Universal deployment with a fixed salt:
58+
59+
```diff
60+
- const deploy = MyContract.deploy(wallet, ...args);
61+
- await deploy.send({
62+
- from: alice,
63+
- contractAddressSalt: salt,
64+
- universalDeploy: true,
65+
- });
66+
+ const deploy = MyContract.deploy(wallet, ...args, { salt, universalDeploy: true });
67+
+ await deploy.send({ from: alice });
68+
```
69+
70+
Non-universal deploy where `from` doubles as the deployer:
71+
72+
```diff
73+
- const deploy = MyContract.deploy(wallet, ...args);
74+
- await deploy.send({ from: alice, contractAddressSalt: salt });
75+
+ const deploy = MyContract.deploy(wallet, ...args, { salt });
76+
+ await deploy.send({ from: alice });
77+
```
78+
79+
If you need to read the address before sending, lock the deployer at construction:
80+
81+
```typescript
82+
const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice });
83+
const address = await deploy.getAddress(); // resolves; deployer was locked at construction
84+
await deploy.send({ from: alice }); // deploys at the address `getAddress` returned
85+
```
86+
87+
Universal deploys can be sent by any account, since the universal address does not depend on `from`:
88+
89+
```typescript
90+
const deploy = MyContract.deploy(wallet, ...args, { universalDeploy: true });
91+
await deploy.send({ from: bob }); // OK, universal accepts any sender
92+
```
93+
94+
A lock to a concrete deployer rejects sending from a different account, instead of silently deploying at a different address:
95+
96+
```typescript
97+
const deploy = MyContract.deploy(wallet, ...args, { deployer: alice });
98+
await deploy.send({ from: bob }); // throws: deployer is locked to alice
99+
```
100+
101+
`deployWithPublicKeys` is gone; pass `publicKeys` in the instantiation options instead:
102+
103+
```diff
104+
- const deploy = MyContract.deployWithPublicKeys(publicKeys, wallet, ...args);
105+
+ const deploy = MyContract.deploy(wallet, ...args, { publicKeys });
106+
```
107+
108+
`ContractDeployer.deploy(...)` now takes the instantiation argument as its first parameter (pass `{}` to use defaults and rely on lazy locking from `from`):
109+
110+
```diff
111+
- const cd = new ContractDeployer(artifact, wallet);
112+
- await cd.deploy(...ctorArgs).send({ from: alice, contractAddressSalt: salt });
113+
+ const cd = new ContractDeployer(artifact, wallet);
114+
+ await cd.deploy(ctorArgs, { salt }).send({ from: alice });
115+
```
116+
117+
The synchronous `address` / `partialAddress` getters are gone:
118+
119+
```diff
120+
- const address = deploy.address; // sync, possibly undefined
121+
- const partial = await deploy.partialAddress; // sync getter wrapping async value
122+
+ const address = await deploy.getAddress(); // requires the deployer to be locked
123+
+ const partial = await deploy.getPartialAddress(); // requires the deployer to be locked
124+
```
125+
126+
`getInstance()` no longer takes options; use the construction-time instantiation instead:
127+
128+
```diff
129+
- const instance = await deploy.getInstance({ contractAddressSalt: salt });
130+
+ const deploy = MyContract.deploy(wallet, ...args, { salt, deployer: alice });
131+
+ const instance = await deploy.getInstance();
132+
```
133+
134+
### [Aztec.nr] TXE `call_public_incognito` no longer takes a `from` parameter
135+
136+
`TestEnvironment::call_public_incognito` previously accepted a `from` address that was silently ignored (the function always uses a null `msg_sender`). The `from` parameter has been removed.
137+
138+
```diff
139+
- env.call_public_incognito(sender, SampleContract::at(addr).some_function());
140+
+ env.call_public_incognito(SampleContract::at(addr).some_function());
141+
```
142+
143+
If you need to call a public function *with* a sender, use `call_public` instead.
144+
145+
### [Aztec.nr] TXE `view_public_incognito` is deprecated
146+
147+
`TestEnvironment::view_public_incognito` is now deprecated in favor of `view_public`, which has the same behavior (null `msg_sender`, static call).
148+
149+
```diff
150+
- env.view_public_incognito(SampleContract::at(addr).some_view());
151+
+ env.view_public(SampleContract::at(addr).some_view());
152+
```
153+
43154
### [PXE] `proveTx` takes an options bag
44155

45156
`PXE.proveTx` used to accept `scopes` as a positional argument; it now takes an options bag consistent with `simulateTx` and `profileTx`, and adds an optional `senderForTags` field. Update direct callers:

docs/examples/ts/aztecjs_advanced/index.ts

Lines changed: 64 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,22 @@ import { getPublicEvents } from "@aztec/aztec.js/events";
1616
import { GasSettings } from "@aztec/stdlib/gas";
1717

1818
// Setup: connect to network
19-
const node = createAztecNodeClient(process.env.AZTEC_NODE_URL ?? "http://localhost:8080");
19+
const node = createAztecNodeClient(
20+
process.env.AZTEC_NODE_URL ?? "http://localhost:8080",
21+
);
2022
await waitForNode(node);
2123
const wallet = await EmbeddedWallet.create(node, { ephemeral: true });
2224

2325
const testAccounts = await getInitialTestAccountsData();
2426
const [aliceAddress, bobAddress] = await Promise.all(
2527
testAccounts.slice(0, 2).map(async (account) => {
26-
return (await wallet.createSchnorrAccount(account.secret, account.salt, account.signingKey)).address;
28+
return (
29+
await wallet.createSchnorrAccount(
30+
account.secret,
31+
account.salt,
32+
account.signingKey,
33+
)
34+
).address;
2735
}),
2836
);
2937

@@ -45,8 +53,13 @@ const sponsoredFPCInstance = await getContractInstanceFromInstantiationParams(
4553
SponsoredFPCContract.artifact,
4654
{ salt: new Fr(0) },
4755
);
48-
await wallet.registerContract(sponsoredFPCInstance, SponsoredFPCContract.artifact);
49-
const sponsoredPaymentMethod = new SponsoredFeePaymentMethod(sponsoredFPCInstance.address);
56+
await wallet.registerContract(
57+
sponsoredFPCInstance,
58+
SponsoredFPCContract.artifact,
59+
);
60+
const sponsoredPaymentMethod = new SponsoredFeePaymentMethod(
61+
sponsoredFPCInstance.address,
62+
);
5063

5164
// wallet is from the connection guide; sponsoredPaymentMethod is from the fees guide
5265
const { contract: sponsoredContract } = await TokenContract.deploy(
@@ -68,9 +81,9 @@ const { contract: saltedContract } = await TokenContract.deploy(
6881
"SaltedToken",
6982
"SALT",
7083
18,
84+
{ salt: customSalt },
7185
).send({
7286
from: aliceAddress,
73-
contractAddressSalt: customSalt,
7487
});
7588
// docs:end:deploy_custom_salt
7689

@@ -84,8 +97,9 @@ const deployMethod = TokenContract.deploy(
8497
"PredictedToken",
8598
"PRED",
8699
18,
100+
{ salt: deploymentSalt, deployer: aliceAddress },
87101
);
88-
const instance = await deployMethod.getInstance({ contractAddressSalt: deploymentSalt });
102+
const instance = await deployMethod.getInstance();
89103
const predictedAddress = instance.address;
90104

91105
console.log(`Contract will deploy at: ${predictedAddress}`);
@@ -235,15 +249,21 @@ console.log(`Derived token at: ${derivedToken.address.toString()}`);
235249
// docs:start:parallel_deploy
236250
// Deploy contracts in parallel using Promise.all
237251
const contracts = await Promise.all([
238-
TokenContract.deploy(wallet, aliceAddress, "Token1", "T1", 18).send({
239-
from: aliceAddress,
240-
}).then(({ contract }) => contract),
241-
TokenContract.deploy(wallet, aliceAddress, "Token2", "T2", 18).send({
242-
from: aliceAddress,
243-
}).then(({ contract }) => contract),
244-
TokenContract.deploy(wallet, aliceAddress, "Token3", "T3", 18).send({
245-
from: aliceAddress,
246-
}).then(({ contract }) => contract),
252+
TokenContract.deploy(wallet, aliceAddress, "Token1", "T1", 18)
253+
.send({
254+
from: aliceAddress,
255+
})
256+
.then(({ contract }) => contract),
257+
TokenContract.deploy(wallet, aliceAddress, "Token2", "T2", 18)
258+
.send({
259+
from: aliceAddress,
260+
})
261+
.then(({ contract }) => contract),
262+
TokenContract.deploy(wallet, aliceAddress, "Token3", "T3", 18)
263+
.send({
264+
from: aliceAddress,
265+
})
266+
.then(({ contract }) => contract),
247267
]);
248268

249269
console.log(`Contract 1 at: ${contracts[0].address}`);
@@ -293,8 +313,12 @@ async function pollForTransferEvents() {
293313

294314
for (const { event, metadata } of events) {
295315
// Process each transfer event
296-
console.log(`Transfer: ${event.amount} from ${event.from} to ${event.to}`);
297-
console.log(` in block ${metadata.l2BlockNumber}, tx ${metadata.txHash}`);
316+
console.log(
317+
`Transfer: ${event.amount} from ${event.from} to ${event.to}`,
318+
);
319+
console.log(
320+
` in block ${metadata.l2BlockNumber}, tx ${metadata.txHash}`,
321+
);
298322
}
299323

300324
lastProcessedBlock = currentBlock;
@@ -307,7 +331,11 @@ await pollForTransferEvents();
307331

308332
// docs:start:connect_to_contract
309333
// wallet is from the connection guide; token is the contract deployed in the deploy guide
310-
const contract = await Contract.at(token.address, TokenContract.artifact, wallet);
334+
const contract = await Contract.at(
335+
token.address,
336+
TokenContract.artifact,
337+
wallet,
338+
);
311339
// docs:end:connect_to_contract
312340

313341
// docs:start:basic_send_transaction
@@ -333,7 +361,10 @@ console.log("DA gas limit:", metaResult.estimatedGas.gasLimits.daGas);
333361
// docs:end:simulate_with_metadata
334362

335363
// docs:start:read_public_logs
336-
const publicLogs = await node.getPublicLogs({ fromBlock: 1, toBlock: await node.getBlockNumber() + 1 });
364+
const publicLogs = await node.getPublicLogs({
365+
fromBlock: 1,
366+
toBlock: (await node.getBlockNumber()) + 1,
367+
});
337368
if (publicLogs.logs.length > 0) {
338369
const rawFields = publicLogs.logs[0].log.getEmittedFields(); // Fr[]
339370
console.log("Raw log fields:", rawFields.length);
@@ -385,24 +416,32 @@ const networkFees = await node.getCurrentMinFees();
385416
const gasSettings = GasSettings.from({
386417
gasLimits: { daGas: 100_000, l2Gas: 2_000_000 },
387418
teardownGasLimits: { daGas: 100_000, l2Gas: 2_000_000 },
388-
maxFeesPerGas: { feePerDaGas: networkFees.feePerDaGas * 2n, feePerL2Gas: networkFees.feePerL2Gas * 2n },
419+
maxFeesPerGas: {
420+
feePerDaGas: networkFees.feePerDaGas * 2n,
421+
feePerL2Gas: networkFees.feePerL2Gas * 2n,
422+
},
389423
maxPriorityFeesPerGas: { feePerDaGas: 0n, feePerL2Gas: 0n },
390424
});
391425
// docs:end:custom_gas_settings
392426

393427
// docs:start:send_with_gas_settings
394-
const { receipt: gsReceipt } = await token.methods.mint_to_public(aliceAddress, 1n).send({
395-
from: aliceAddress,
396-
fee: { gasSettings },
397-
});
428+
const { receipt: gsReceipt } = await token.methods
429+
.mint_to_public(aliceAddress, 1n)
430+
.send({
431+
from: aliceAddress,
432+
fee: { gasSettings },
433+
});
398434
// docs:end:send_with_gas_settings
399435

400436
// docs:start:read_logs_by_filter
401437
// Get logs for a specific transaction
402438
const txLogs = await node.getPublicLogs({ txHash: gsReceipt.txHash });
403439

404440
// Get logs for a block range
405-
const rangeLogs = await node.getPublicLogs({ fromBlock: 1, toBlock: await node.getBlockNumber() + 1 });
441+
const rangeLogs = await node.getPublicLogs({
442+
fromBlock: 1,
443+
toBlock: (await node.getBlockNumber()) + 1,
444+
});
406445
// docs:end:read_logs_by_filter
407446

408447
// docs:start:auto_gas_estimation
@@ -435,5 +474,4 @@ const { result: privateBalance } = await token.methods
435474
.simulate({ from: aliceAddress });
436475
// docs:end:simulate_private_access
437476

438-
439477
console.log("All advanced examples completed successfully");

0 commit comments

Comments
 (0)