1- use crate:: {context ::{PrivateContext , PublicContext , UtilityContext }, state_vars::StateVariable , utils::WithHash };
1+ use crate:: {
2+ context ::{PrivateContext , PublicContext , UtilityContext },
3+ nullifier::utils:: compute_nullifier_existence_request ,
4+ oracle::nullifiers:: check_nullifier_exists ,
5+ state_vars::StateVariable ,
6+ utils::WithHash ,
7+ };
28use crate::protocol:: {
39 constants::DOM_SEP__INITIALIZATION_NULLIFIER , hash:: poseidon2_hash_with_separator , traits::Packable ,
410};
511
12+ mod test ;
13+
614/// Immutable public values.
715///
816/// This is one of the most basic public state variables. It is similar to an `immutable` or `constant` Solidity state
@@ -249,9 +257,15 @@ impl<T> PublicImmutable<T, UtilityContext> {
249257 where
250258 T : Packable + Eq ,
251259 {
252- // TODO(#15703): this fn should fail if the variable is not initialized
260+ assert ( self . is_initialized (), "Trying to read from uninitialized PublicImmutable" );
253261 WithHash ::utility_public_storage_read (self .context , self .storage_slot )
254262 }
263+
264+ /// Returns true if the `PublicImmutable` has been initialized.
265+ pub unconstrained fn is_initialized (self ) -> bool {
266+ let nullifier = self .compute_initialization_inner_nullifier ();
267+ check_nullifier_exists (nullifier )
268+ }
255269}
256270
257271impl <T > PublicImmutable <T , &mut PrivateContext > {
@@ -270,10 +284,11 @@ impl<T> PublicImmutable<T, &mut PrivateContext> {
270284 ///
271285 /// ## Cost
272286 ///
273- /// A historical public storage read at the anchor block is performed for a single storage slot, **regardless of
274- /// `T`'s packed length**. This is because [PublicImmutable::initialize] stores not just the value but also its
275- /// hash: this function obtains the preimage from an oracle and proves that it matches the hash from public
276- /// storage.
287+ /// A nullifier existence request is pushed to the context, which will be verified by the kernel circuit.
288+ /// Additionally, a historical public storage read at the anchor block is performed for a single storage slot,
289+ /// **regardless of `T`'s packed length**. This is because [PublicImmutable::initialize] stores not just the value
290+ /// but also its hash: this function obtains the preimage from an oracle and proves that it matches the hash from
291+ /// public storage.
277292 ///
278293 /// Because of this reason it is convenient to group together all of a contract's public immutable values that are
279294 /// read privately in a single type `T`:
@@ -302,7 +317,17 @@ impl<T> PublicImmutable<T, &mut PrivateContext> {
302317 where
303318 T : Packable + Eq ,
304319 {
305- // TODO(#15703): this fn should fail if the variable is not initialized
320+ let nullifier = self .compute_initialization_inner_nullifier ();
321+
322+ // Safety: We use this check to be able to test this function works properly on synthetic envs
323+ // like TXE. We assert the returned value only to provide a clear error message. The actual
324+ // constrained check that the nullifier exists is done below with `assert_nullifier_exists`
325+ // We should improve our synthetic envs because this check forces an unnecesary roundtrip
326+ let nullifier_exists = unsafe { check_nullifier_exists (nullifier ) };
327+ assert (nullifier_exists , "Trying to read from uninitialized PublicImmutable" );
328+
329+ let nullifier_existence_request = compute_nullifier_existence_request (nullifier , self .context .this_address ());
330+ self .context .assert_nullifier_exists (nullifier_existence_request );
306331 WithHash ::historical_public_storage_read (
307332 self .context .get_anchor_block_header (),
308333 self .context .this_address (),
0 commit comments