Skip to content

Commit f0e431b

Browse files
authored
chore: Accumulated backports to v4-next (#21995)
BEGIN_COMMIT_OVERRIDE feat!: scoped capsules (backport #21533) (#21986) feat(aztec-nr): add initialization check to utility functions (#21751) refactor(aztec-nr): remove storage from init_test_contract (#21996) fix(p2p): check peer rate limit before global to prevent quota starvation (#21997) chore: remove claude file (#22012) fix: disallow infinite pubkeys (#22026) END_COMMIT_OVERRIDE
2 parents 0711b81 + 3785717 commit f0e431b

File tree

114 files changed

+2180
-1496
lines changed

Some content is hidden

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

114 files changed

+2180
-1496
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,4 @@ docs/docs/protocol-specs/public-vm/gen/
3333
__pycache__
3434

3535
*.local.md
36+
.claude/settings.local.json

.test_patterns.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,11 @@ tests:
293293
owners:
294294
- *palla
295295

296+
- regex: "src/e2e_offchain_payment\\.test\\.ts"
297+
error_regex: "reprocesses an offchain-delivered payment after an L1 reorg"
298+
owners:
299+
- *martin
300+
296301
- regex: "bb-micro-bench/wasm/chonk build-wasm-threads/bin/chonk_bench"
297302
error_regex: "core dumped"
298303
owners:

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

Lines changed: 110 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,115 @@ Aztec is in active development. Each version may introduce breaking changes that
99

1010
## TBD
1111

12+
### [Aztec.nr] Domain-separated tags on log emission
13+
14+
All logs emitted through the Aztec.nr framework now include a domain-separated tag at `fields[0]`. Each log category uses its own domain separator via `compute_log_tag(raw_tag, dom_sep)`:
15+
16+
- **Events** (`DOM_SEP__EVENT_LOG_TAG`): the event type ID is the raw tag.
17+
- **Message delivery** (`DOM_SEP__UNCONSTRAINED_MSG_LOG_TAG`): the discovery tag is the raw tag.
18+
- **Partial note completion logs** (`DOM_SEP__NOTE_COMPLETION_LOG_TAG`): the partial note's `commitment` field is the raw tag.
19+
20+
The low-level emit methods now take `tag` as an explicit first parameter and have been renamed with an `_unsafe` suffix. Previously the tag was included as `log[0]` — it has now been extracted into its own parameter, and `log` no longer contains it:
21+
22+
```diff
23+
- context.emit_private_log(log, length);
24+
+ context.emit_private_log_unsafe(tag, log, length);
25+
- context.emit_raw_note_log(log, length, note_hash_counter);
26+
+ context.emit_raw_note_log_unsafe(tag, log, length, note_hash_counter);
27+
- context.emit_public_log(log);
28+
+ context.emit_public_log_unsafe(tag, log);
29+
```
30+
31+
Prefer the higher-level APIs (`emit` for events, `MessageDelivery` for messages) which handle tagging automatically.
32+
33+
### [Aztec.nr] Public events no longer include the event type selector at the end of the payload
34+
35+
`emit_event_in_public` previously appended the event type selector as the last field. It now prepends a domain-separated tag at `fields[0]` instead. The payload after the tag contains only the serialized event fields.
36+
37+
If you were reading public event directly from node logs (i.e. via `node.getPublicLogs` and not via `wallet.getPublicEvents`), update your parsing:
38+
39+
```diff
40+
- // Old: fields = [serialized_event..., event_type_selector]
41+
- const selector = EventSelector.fromField(fields[fields.length - 1]);
42+
- const event = decodeFromAbi([abiType], fields);
43+
+ // New: fields = [domain_separated_tag, serialized_event...]
44+
+ const eventFields = log.getEmittedFieldsWithoutTag();
45+
+ const event = decodeFromAbi([abiType], eventFields);
46+
```
47+
48+
### [Aztec.nr] Capsule operations are now addressed by scope
49+
50+
All capsule operations (`store`, `load`, `delete`, `copy`) and `CapsuleArray` now require a `scope: AztecAddress` parameter. This scopes capsule storage by address, providing isolation between different accounts within the same PXE.
51+
52+
Contracts that use `CapsuleArray` directly also need to update.
53+
54+
**Migration:**
55+
56+
```diff
57+
- let array: CapsuleArray<Field> = CapsuleArray::at(contract_address, slot);
58+
+ let array: CapsuleArray<Field> = CapsuleArray::at(contract_address, slot, scope);
59+
```
60+
61+
The low-level capsule functions are similarly affected:
62+
63+
```diff
64+
- capsules::store(contract_address, slot, value);
65+
+ capsules::store(contract_address, slot, value, scope);
66+
67+
- capsules::load(contract_address, slot);
68+
+ capsules::load(contract_address, slot, scope);
69+
70+
- capsules::delete(contract_address, slot);
71+
+ capsules::delete(contract_address, slot, scope);
72+
73+
- capsules::copy(contract_address, src_slot, dst_slot, num_entries);
74+
+ capsules::copy(contract_address, src_slot, dst_slot, num_entries, scope);
75+
```
76+
77+
If you need to stick the old, scope-less behavior, and you are really sure that that's what you need to use, you can use `scope = AztecAddress::zero()`.
78+
79+
### [Aztec.nr] `process_message` utility function removed
80+
81+
The auto-generated `process_message` utility function has been removed. If you need to deliver offchain messages (messages not broadcast via onchain logs), use the `offchain_receive` utility function instead. This function is automatically injected by the `#[aztec]` macro and accepts messages into a persistent inbox scoped by recipient. These messages are then picked up and processed during `sync_state`.
82+
83+
**Impact**: Contracts that explicitly called `process_message` must switch to delivering messages via `offchain_receive` and letting `sync_state` handle processing.
84+
85+
### [Aztec.nr] `CustomMessageHandler` type signature changed
86+
87+
The `CustomMessageHandler` function type now receives an additional `scope: AztecAddress` parameter:
88+
89+
```diff
90+
type CustomMessageHandler = unconstrained fn(
91+
AztecAddress, // contract_address
92+
u64, // msg_type_id
93+
u64, // msg_metadata
94+
BoundedVec<Field, MAX_MESSAGE_CONTENT_LEN>, // msg_content
95+
MessageContext, // message_context
96+
+ AztecAddress, // scope
97+
);
98+
```
99+
100+
**Impact**: Contracts that implement a custom message handler must update the function signature.
101+
### [aztec.js] `DeployMethod.send()` always returns `{ contract, receipt, instance }`
102+
103+
The `returnReceipt` option in deploy wait options has been removed. `DeployMethod.send()` now always returns an object with `contract`, `receipt`, and `instance` at the top level, provided the user waits for the transaction to be included.
104+
105+
The `DeployTxReceipt` and `DeployWaitOptions` types have been removed.
106+
107+
**Migration:**
108+
109+
```diff
110+
- const {
111+
- receipt: { contract, instance },
112+
- } = await MyContract.deploy(wallet, ...args).send({
113+
- from: address,
114+
- wait: { returnReceipt: true },
115+
- });
116+
117+
+ const { contract, instance } = await MyContract.deploy(wallet, ...args).send({
118+
+ from: address,
119+
+ });
120+
```
12121
### [aztec.js] `isContractInitialized` is now `initializationStatus` tri-state enum
13122

14123
`ContractMetadata.isContractInitialized` has been renamed to `ContractMetadata.initializationStatus` and changed from `boolean | undefined` to a `ContractInitializationStatus` enum with values `INITIALIZED`, `UNINITIALIZED`, and `UNKNOWN`.
@@ -97,7 +206,7 @@ Most contracts are not affected, as the macro-generated `sync_state` and `proces
97206
**Migration:**
98207

99208
```diff
100-
attempt_note_discovery(
209+
attempt_note_discovery(
101210
contract_address,
102211
tx_hash,
103212
unique_note_hashes_in_tx,

noir-projects/aztec-nr/aztec/src/capsules/mod.nr

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,25 @@ pub struct CapsuleArray<T> {
1010
/// after the base slot. For example, with base slot 5: the length is at slot 5, the first element (index 0) is at
1111
/// slot 6, the second element (index 1) is at slot 7, and so on.
1212
base_slot: Field,
13+
/// Scope for capsule isolation. Capsule operations are scoped to the given address, allowing multiple independent
14+
/// namespaces within the same contract.
15+
scope: AztecAddress,
1316
}
1417

1518
impl<T> CapsuleArray<T> {
16-
/// Returns a CapsuleArray connected to a contract's capsules at a base slot. Array elements are stored in
17-
/// contiguous slots following the base slot, so there should be sufficient space between array base slots to
18-
/// accommodate elements. A reasonable strategy is to make the base slot a hash of a unique value.
19-
pub unconstrained fn at(contract_address: AztecAddress, base_slot: Field) -> Self {
20-
Self { contract_address, base_slot }
19+
/// Returns a CapsuleArray scoped to a specific address.
20+
///
21+
/// Array elements are stored in contiguous slots
22+
/// following the base slot, so there should be sufficient space between array base slots to accommodate elements.
23+
/// A reasonable strategy is to make the base slot a hash of a unique value.
24+
pub unconstrained fn at(contract_address: AztecAddress, base_slot: Field, scope: AztecAddress) -> Self {
25+
Self { contract_address, base_slot, scope }
2126
}
2227

2328
/// Returns the number of elements stored in the array.
2429
pub unconstrained fn len(self) -> u32 {
2530
// An uninitialized array defaults to a length of 0.
26-
capsules::load(self.contract_address, self.base_slot).unwrap_or(0) as u32
31+
capsules::load(self.contract_address, self.base_slot, self.scope).unwrap_or(0) as u32
2732
}
2833

2934
/// Stores a value at the end of the array.
@@ -35,11 +40,21 @@ impl<T> CapsuleArray<T> {
3540

3641
// The slot corresponding to the index `current_length` is the first slot immediately after the end of the
3742
// array, which is where we want to place the new value.
38-
capsules::store(self.contract_address, self.slot_at(current_length), value);
43+
capsules::store(
44+
self.contract_address,
45+
self.slot_at(current_length),
46+
value,
47+
self.scope,
48+
);
3949

4050
// Then we simply update the length.
4151
let new_length = current_length + 1;
42-
capsules::store(self.contract_address, self.base_slot, new_length);
52+
capsules::store(
53+
self.contract_address,
54+
self.base_slot,
55+
new_length,
56+
self.scope,
57+
);
4358
}
4459

4560
/// Retrieves the value stored in the array at `index`. Throws if the index is out of bounds.
@@ -49,7 +64,7 @@ impl<T> CapsuleArray<T> {
4964
{
5065
assert(index < self.len(), "Attempted to read past the length of a CapsuleArray");
5166

52-
capsules::load(self.contract_address, self.slot_at(index)).unwrap()
67+
capsules::load(self.contract_address, self.slot_at(index), self.scope).unwrap()
5368
}
5469

5570
/// Deletes the value stored in the array at `index`. Throws if the index is out of bounds.
@@ -67,13 +82,23 @@ impl<T> CapsuleArray<T> {
6782
self.slot_at(index + 1),
6883
self.slot_at(index),
6984
current_length - index - 1,
85+
self.scope,
7086
);
7187
}
7288

7389
// We can now delete the last element (which has either been copied to the slot immediately before it, or was
7490
// the element we meant to delete in the first place) and update the length.
75-
capsules::delete(self.contract_address, self.slot_at(current_length - 1));
76-
capsules::store(self.contract_address, self.base_slot, current_length - 1);
91+
capsules::delete(
92+
self.contract_address,
93+
self.slot_at(current_length - 1),
94+
self.scope,
95+
);
96+
capsules::store(
97+
self.contract_address,
98+
self.base_slot,
99+
current_length - 1,
100+
self.scope,
101+
);
77102
}
78103

79104
/// Calls a function on each element of the array.
@@ -124,18 +149,20 @@ impl<T> CapsuleArray<T> {
124149
}
125150

126151
mod test {
152+
use crate::protocol::address::AztecAddress;
127153
use crate::test::helpers::test_environment::TestEnvironment;
128154
use super::CapsuleArray;
129155

130156
global SLOT: Field = 1230;
157+
global SCOPE: AztecAddress = AztecAddress { inner: 0xface };
131158

132159
#[test]
133160
unconstrained fn empty_array() {
134161
let env = TestEnvironment::new();
135162
env.private_context(|context| {
136163
let contract_address = context.this_address();
137164

138-
let array: CapsuleArray<Field> = CapsuleArray::at(contract_address, SLOT);
165+
let array: CapsuleArray<Field> = CapsuleArray::at(contract_address, SLOT, SCOPE);
139166
assert_eq(array.len(), 0);
140167
});
141168
}
@@ -146,7 +173,7 @@ mod test {
146173
env.private_context(|context| {
147174
let contract_address = context.this_address();
148175

149-
let array = CapsuleArray::at(contract_address, SLOT);
176+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
150177
let _: Field = array.get(0);
151178
});
152179
}
@@ -157,7 +184,7 @@ mod test {
157184
env.private_context(|context| {
158185
let contract_address = context.this_address();
159186

160-
let array = CapsuleArray::at(contract_address, SLOT);
187+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
161188
array.push(5);
162189

163190
assert_eq(array.len(), 1);
@@ -171,7 +198,7 @@ mod test {
171198
env.private_context(|context| {
172199
let contract_address = context.this_address();
173200

174-
let array = CapsuleArray::at(contract_address, SLOT);
201+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
175202
array.push(5);
176203

177204
let _ = array.get(1);
@@ -184,7 +211,7 @@ mod test {
184211
env.private_context(|context| {
185212
let contract_address = context.this_address();
186213

187-
let array = CapsuleArray::at(contract_address, SLOT);
214+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
188215

189216
array.push(5);
190217
array.remove(0);
@@ -199,7 +226,7 @@ mod test {
199226
env.private_context(|context| {
200227
let contract_address = context.this_address();
201228

202-
let array = CapsuleArray::at(contract_address, SLOT);
229+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
203230

204231
array.push(7);
205232
array.push(8);
@@ -224,7 +251,7 @@ mod test {
224251
env.private_context(|context| {
225252
let contract_address = context.this_address();
226253

227-
let array = CapsuleArray::at(contract_address, SLOT);
254+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
228255

229256
array.push(7);
230257
array.push(8);
@@ -243,7 +270,7 @@ mod test {
243270
let env = TestEnvironment::new();
244271
env.private_context(|context| {
245272
let contract_address = context.this_address();
246-
let array = CapsuleArray::at(contract_address, SLOT);
273+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
247274

248275
array.push(4);
249276
array.push(5);
@@ -266,7 +293,7 @@ mod test {
266293
let env = TestEnvironment::new();
267294
env.private_context(|context| {
268295
let contract_address = context.this_address();
269-
let array = CapsuleArray::at(contract_address, SLOT);
296+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
270297

271298
array.push(4);
272299
array.push(5);
@@ -289,7 +316,7 @@ mod test {
289316
let env = TestEnvironment::new();
290317
env.private_context(|context| {
291318
let contract_address = context.this_address();
292-
let array = CapsuleArray::at(contract_address, SLOT);
319+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
293320

294321
array.push(4);
295322
array.push(5);
@@ -306,7 +333,7 @@ mod test {
306333
let env = TestEnvironment::new();
307334
env.private_context(|context| {
308335
let contract_address = context.this_address();
309-
let array = CapsuleArray::at(contract_address, SLOT);
336+
let array = CapsuleArray::at(contract_address, SLOT, SCOPE);
310337

311338
array.push(4);
312339
array.push(5);
@@ -321,4 +348,28 @@ mod test {
321348
assert_eq(mock.times_called(), 0);
322349
});
323350
}
351+
352+
#[test]
353+
unconstrained fn different_scopes_are_isolated() {
354+
let env = TestEnvironment::new();
355+
env.private_context(|context| {
356+
let contract_address = context.this_address();
357+
let scope_a = AztecAddress { inner: 0xaaa };
358+
let scope_b = AztecAddress { inner: 0xbbb };
359+
360+
let array_a = CapsuleArray::at(contract_address, SLOT, scope_a);
361+
let array_b = CapsuleArray::at(contract_address, SLOT, scope_b);
362+
363+
array_a.push(10);
364+
array_a.push(20);
365+
array_b.push(99);
366+
367+
assert_eq(array_a.len(), 2);
368+
assert_eq(array_a.get(0), 10);
369+
assert_eq(array_a.get(1), 20);
370+
371+
assert_eq(array_b.len(), 1);
372+
assert_eq(array_b.get(0), 99);
373+
});
374+
}
324375
}

0 commit comments

Comments
 (0)