Skip to content

Commit bc4993a

Browse files
authored
chore: Accumulated backports to v4-next (#22441)
BEGIN_COMMIT_OVERRIDE cherry-pick: fix(pxe): support custom PrivateKernelProver and unify EmbeddedWalletOptions (#22348) fix: update testnet compatibility test and bust cache on pinned artifact changes (#22429) fix(pxe): support custom PrivateKernelProver and unify EmbeddedWalletOptions (backport #22348) (#22391) refactor!: ephemeral arrays (#22162) END_COMMIT_OVERRIDE
2 parents c2f0a92 + 56670b6 commit bc4993a

File tree

48 files changed

+1337
-380
lines changed

Some content is hidden

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

48 files changed

+1337
-380
lines changed

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,22 @@ FPCs that use only Fee Juice still work on all networks, since FeeJuice is a pro
9191

9292
Similarly, the `fpc-public` and `fpc-private` CLI wallet payment methods use the reference Token-based FPC and will not work on public networks. Use `fee_juice` for direct Fee Juice payment, or `fpc-sponsored` on devnet and local network.
9393

94+
### [aztec.js] `EmbeddedWalletOptions` now uses a unified `pxe` field
95+
96+
The `pxeConfig` and `pxeOptions` fields on `EmbeddedWalletOptions` have been deprecated in favor of a single `pxe` field that accepts both PXE configuration and dependency overrides (custom prover, store, simulator):
97+
98+
```diff
99+
const wallet = await EmbeddedWallet.create(nodeUrl, {
100+
- pxeConfig: { proverEnabled: true },
101+
- pxeOptions: { proverOrOptions: myCustomProver },
102+
+ pxe: {
103+
+ proverEnabled: true,
104+
+ proverOrOptions: myCustomProver,
105+
+ },
106+
});
107+
```
108+
109+
The old fields still work but will be removed in a future release.
94110

95111
### [Aztec.nr] Domain-separated tags on log emission
96112

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
use crate::oracle::ephemeral;
2+
use crate::protocol::traits::{Deserialize, Serialize};
3+
4+
/// A dynamically sized array that exists only during a single contract call frame.
5+
///
6+
/// Ephemeral arrays are backed by in-memory storage on the PXE side rather than a persistent database. Each contract
7+
/// call frame gets its own isolated slot space of ephemeral arrays. Child simulations cannot see the parent's
8+
/// ephemeral arrays, and vice versa.
9+
///
10+
/// Each logical array operation (push, pop, get, etc.) is a single oracle call, making ephemeral arrays significantly
11+
/// cheaper than capsule arrays.
12+
///
13+
/// ## Use Cases
14+
///
15+
/// Ephemeral arrays are designed for passing data between PXE (TypeScript) and contracts (Noir) during simulation,
16+
/// for example, note validation requests or event validation responses. This data type is appropriate for data that
17+
/// is not supposed to be persisted.
18+
///
19+
/// For data that needs to persist across simulations, contract calls, etc, use
20+
/// [`CapsuleArray`](crate::capsules::CapsuleArray) instead.
21+
pub struct EphemeralArray<T> {
22+
pub slot: Field,
23+
}
24+
25+
impl<T> EphemeralArray<T> {
26+
/// Returns a handle to an ephemeral array at the given slot, which may already contain data (e.g. populated
27+
/// by an oracle).
28+
pub unconstrained fn at(slot: Field) -> Self {
29+
Self { slot }
30+
}
31+
32+
/// Returns the number of elements stored in the array.
33+
pub unconstrained fn len(self) -> u32 {
34+
ephemeral::len_oracle(self.slot)
35+
}
36+
37+
/// Stores a value at the end of the array.
38+
pub unconstrained fn push(self, value: T)
39+
where
40+
T: Serialize,
41+
{
42+
let serialized = value.serialize();
43+
let _ = ephemeral::push_oracle(self.slot, serialized);
44+
}
45+
46+
/// Removes and returns the last element. Panics if the array is empty.
47+
pub unconstrained fn pop(self) -> T
48+
where
49+
T: Deserialize,
50+
{
51+
let serialized = ephemeral::pop_oracle(self.slot);
52+
Deserialize::deserialize(serialized)
53+
}
54+
55+
/// Retrieves the value stored at `index`. Panics if the index is out of bounds.
56+
pub unconstrained fn get(self, index: u32) -> T
57+
where
58+
T: Deserialize,
59+
{
60+
let serialized = ephemeral::get_oracle(self.slot, index);
61+
Deserialize::deserialize(serialized)
62+
}
63+
64+
/// Overwrites the value stored at `index`. Panics if the index is out of bounds.
65+
pub unconstrained fn set(self, index: u32, value: T)
66+
where
67+
T: Serialize,
68+
{
69+
let serialized = value.serialize();
70+
ephemeral::set_oracle(self.slot, index, serialized);
71+
}
72+
73+
/// Removes the element at `index`, shifting subsequent elements backward. Panics if out of bounds.
74+
pub unconstrained fn remove(self, index: u32) {
75+
ephemeral::remove_oracle(self.slot, index);
76+
}
77+
78+
/// Removes all elements from the array and returns self for chaining (e.g. `EphemeralArray::at(slot).clear()`
79+
/// to get a guaranteed-empty array at a given slot).
80+
pub unconstrained fn clear(self) -> Self {
81+
ephemeral::clear_oracle(self.slot);
82+
self
83+
}
84+
85+
/// Calls a function on each element of the array.
86+
///
87+
/// The function `f` is called once with each array value and its corresponding index. Iteration proceeds
88+
/// backwards so that it is safe to remove the current element (and only the current element) inside the
89+
/// callback.
90+
///
91+
/// It is **not** safe to push new elements from inside the callback.
92+
pub unconstrained fn for_each<Env>(self, f: unconstrained fn[Env](u32, T) -> ())
93+
where
94+
T: Deserialize,
95+
{
96+
let mut i = self.len();
97+
while i > 0 {
98+
i -= 1;
99+
f(i, self.get(i));
100+
}
101+
}
102+
}
103+
104+
mod test {
105+
use crate::test::helpers::test_environment::TestEnvironment;
106+
use crate::test::mocks::MockStruct;
107+
use super::EphemeralArray;
108+
109+
global SLOT: Field = 1230;
110+
global OTHER_SLOT: Field = 5670;
111+
112+
#[test]
113+
unconstrained fn empty_array() {
114+
let env = TestEnvironment::new();
115+
env.utility_context(|_| {
116+
let array: EphemeralArray<Field> = EphemeralArray::at(SLOT);
117+
assert_eq(array.len(), 0);
118+
});
119+
}
120+
121+
#[test(should_fail_with = "out of bounds")]
122+
unconstrained fn empty_array_read() {
123+
let env = TestEnvironment::new();
124+
env.utility_context(|_| {
125+
let array = EphemeralArray::at(SLOT);
126+
let _: Field = array.get(0);
127+
});
128+
}
129+
130+
#[test(should_fail_with = "is empty")]
131+
unconstrained fn empty_array_pop() {
132+
let env = TestEnvironment::new();
133+
env.utility_context(|_| {
134+
let array = EphemeralArray::at(SLOT);
135+
let _: Field = array.pop();
136+
});
137+
}
138+
139+
#[test]
140+
unconstrained fn array_push() {
141+
let env = TestEnvironment::new();
142+
env.utility_context(|_| {
143+
let array = EphemeralArray::at(SLOT);
144+
array.push(5);
145+
146+
assert_eq(array.len(), 1);
147+
assert_eq(array.get(0), 5);
148+
});
149+
}
150+
151+
#[test(should_fail_with = "out of bounds")]
152+
unconstrained fn read_past_len() {
153+
let env = TestEnvironment::new();
154+
env.utility_context(|_| {
155+
let array = EphemeralArray::at(SLOT);
156+
array.push(5);
157+
158+
let _ = array.get(1);
159+
});
160+
}
161+
162+
#[test]
163+
unconstrained fn array_pop() {
164+
let env = TestEnvironment::new();
165+
env.utility_context(|_| {
166+
let array = EphemeralArray::at(SLOT);
167+
array.push(5);
168+
array.push(10);
169+
170+
let popped: Field = array.pop();
171+
assert_eq(popped, 10);
172+
assert_eq(array.len(), 1);
173+
assert_eq(array.get(0), 5);
174+
});
175+
}
176+
177+
#[test]
178+
unconstrained fn array_set() {
179+
let env = TestEnvironment::new();
180+
env.utility_context(|_| {
181+
let array = EphemeralArray::at(SLOT);
182+
array.push(5);
183+
array.set(0, 99);
184+
assert_eq(array.get(0), 99);
185+
});
186+
}
187+
188+
#[test]
189+
unconstrained fn array_remove_last() {
190+
let env = TestEnvironment::new();
191+
env.utility_context(|_| {
192+
let array = EphemeralArray::at(SLOT);
193+
array.push(5);
194+
array.remove(0);
195+
assert_eq(array.len(), 0);
196+
});
197+
}
198+
199+
#[test]
200+
unconstrained fn array_remove_some() {
201+
let env = TestEnvironment::new();
202+
env.utility_context(|_| {
203+
let array = EphemeralArray::at(SLOT);
204+
205+
array.push(7);
206+
array.push(8);
207+
array.push(9);
208+
209+
assert_eq(array.len(), 3);
210+
211+
array.remove(1);
212+
213+
assert_eq(array.len(), 2);
214+
assert_eq(array.get(0), 7);
215+
assert_eq(array.get(1), 9);
216+
});
217+
}
218+
219+
#[test]
220+
unconstrained fn array_remove_all() {
221+
let env = TestEnvironment::new();
222+
env.utility_context(|_| {
223+
let array = EphemeralArray::at(SLOT);
224+
225+
array.push(7);
226+
array.push(8);
227+
array.push(9);
228+
229+
array.remove(1);
230+
array.remove(1);
231+
array.remove(0);
232+
233+
assert_eq(array.len(), 0);
234+
});
235+
}
236+
237+
#[test]
238+
unconstrained fn for_each_called_with_all_elements() {
239+
let env = TestEnvironment::new();
240+
env.utility_context(|_| {
241+
let array = EphemeralArray::at(SLOT);
242+
243+
array.push(4);
244+
array.push(5);
245+
array.push(6);
246+
247+
let called_with = &mut BoundedVec::<(u32, Field), 3>::new();
248+
array.for_each(|index, value| { called_with.push((index, value)); });
249+
250+
assert_eq(called_with.len(), 3);
251+
assert(called_with.any(|(index, value)| (index == 0) & (value == 4)));
252+
assert(called_with.any(|(index, value)| (index == 1) & (value == 5)));
253+
assert(called_with.any(|(index, value)| (index == 2) & (value == 6)));
254+
});
255+
}
256+
257+
#[test]
258+
unconstrained fn for_each_remove_some() {
259+
let env = TestEnvironment::new();
260+
env.utility_context(|_| {
261+
let array = EphemeralArray::at(SLOT);
262+
263+
array.push(4);
264+
array.push(5);
265+
array.push(6);
266+
267+
array.for_each(|index, _| {
268+
if index == 1 {
269+
array.remove(index);
270+
}
271+
});
272+
273+
assert_eq(array.len(), 2);
274+
assert_eq(array.get(0), 4);
275+
assert_eq(array.get(1), 6);
276+
});
277+
}
278+
279+
#[test]
280+
unconstrained fn for_each_remove_all() {
281+
let env = TestEnvironment::new();
282+
env.utility_context(|_| {
283+
let array = EphemeralArray::at(SLOT);
284+
285+
array.push(4);
286+
array.push(5);
287+
array.push(6);
288+
289+
array.for_each(|index, _| { array.remove(index); });
290+
291+
assert_eq(array.len(), 0);
292+
});
293+
}
294+
295+
#[test]
296+
unconstrained fn different_slots_are_isolated() {
297+
let env = TestEnvironment::new();
298+
env.utility_context(|_| {
299+
let array_a = EphemeralArray::at(SLOT);
300+
let array_b = EphemeralArray::at(OTHER_SLOT);
301+
302+
array_a.push(10);
303+
array_a.push(20);
304+
array_b.push(99);
305+
306+
assert_eq(array_a.len(), 2);
307+
assert_eq(array_a.get(0), 10);
308+
assert_eq(array_a.get(1), 20);
309+
310+
assert_eq(array_b.len(), 1);
311+
assert_eq(array_b.get(0), 99);
312+
});
313+
}
314+
315+
#[test]
316+
unconstrained fn works_with_multi_field_type() {
317+
let env = TestEnvironment::new();
318+
env.utility_context(|_| {
319+
let array: EphemeralArray<MockStruct> = EphemeralArray::at(SLOT);
320+
321+
let a = MockStruct::new(5, 6);
322+
let b = MockStruct::new(7, 8);
323+
array.push(a);
324+
array.push(b);
325+
326+
assert_eq(array.len(), 2);
327+
assert_eq(array.get(0), a);
328+
assert_eq(array.get(1), b);
329+
330+
let popped: MockStruct = array.pop();
331+
assert_eq(popped, b);
332+
assert_eq(array.len(), 1);
333+
});
334+
}
335+
336+
#[test]
337+
unconstrained fn clear_returns_self() {
338+
let env = TestEnvironment::new();
339+
env.utility_context(|_| {
340+
let array: EphemeralArray<Field> = EphemeralArray::at(SLOT).clear();
341+
assert_eq(array.len(), 0);
342+
343+
array.push(42);
344+
assert_eq(array.len(), 1);
345+
assert_eq(array.get(0), 42);
346+
});
347+
}
348+
349+
#[test]
350+
unconstrained fn clear_wipes_previous_data() {
351+
let env = TestEnvironment::new();
352+
env.utility_context(|_| {
353+
let array: EphemeralArray<Field> = EphemeralArray::at(SLOT);
354+
array.push(1);
355+
array.push(2);
356+
array.push(3);
357+
assert_eq(array.len(), 3);
358+
359+
// Clear the same slot, previous data should be gone.
360+
let fresh: EphemeralArray<Field> = EphemeralArray::at(SLOT).clear();
361+
assert_eq(fresh.len(), 0);
362+
fresh.push(4);
363+
assert_eq(fresh.get(0), 4);
364+
});
365+
}
366+
}

noir-projects/aztec-nr/aztec/src/lib.nr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ pub mod nullifier;
3939
pub mod oracle;
4040
pub mod state_vars;
4141
pub mod capsules;
42+
pub mod ephemeral;
4243
pub mod event;
4344
pub mod messages;
4445
pub use protocol_types as protocol;

0 commit comments

Comments
 (0)