Struct PublicImmutable
pub struct PublicImmutable<T, Context>
{ /* private fields */ }
Implementations
impl<T> PublicImmutable<T, &mut PrivateContext>
pub fn read(self) -> T
Reads the permanent value.
This function throws if the PublicImmutable is not initialized.
Examples
#[external("private")]
fn get_decimals() -> u8 {
self.storage.decimals.read()
}
Cost
A nullifier existence request is pushed to the context, which will be verified by the kernel circuit.
Additionally, a historical public storage read at the anchor block is performed for a single storage slot,
regardless of T's packed length. This is because PublicImmutable::initialize stores not just the
value
but also its hash: this function obtains the preimage from an oracle and proves that it matches the hash from
public storage.
Because of this reason it is convenient to group together all of a contract's public immutable values that are
read privately in a single type T:
// Bad: reading both `decimals` and `symbol` will require two historical public storage reads
#[storage]
struct Storage<Context> {
decimals: PublicImmutable<u8, Context>,
symbol: PubicImmutable<FieldCompressedString, Context>,
}
// Good: both `decimals` and `symbol` are retrieved in a single historical public storage read
#[derive(Packable)]
struct Config {
decimals: u8,
symbol: FieldCompressedString,
}
#[storage]
struct Storage<Context> {
config: PublicImmutable<Config, Context>,
}impl<T> PublicImmutable<T, UtilityContext>
pub unconstrained fn read(self) -> T
Reads the permanent value.
This function throws if the PublicImmutable is not initialized.
Examples
#[external("utility")]
fn get_decimals() -> u8 {
self.storage.decimals.read()
}pub unconstrained fn is_initialized(self) -> bool
Returns true if the PublicImmutable has been initialized.
impl<Context, T> PublicImmutable<T, Context>
pub fn compute_initialization_inner_nullifier(self) -> Field
Returns the inner nullifier emitted during initialization.
impl<T> PublicImmutable<T, PublicContext>
pub fn initialize(self, value: T)
Stores a permanent value.
This function can only be called once on a given PublicImmutable: subsequent calls will fail with a duplicate
nullifier.
Examples
Contract initialization:
#[external("public")]
#[initializer]
fn initialize(decimals: u8) {
self.storage.decimals.iniitalize(decimals);
}
Non-initializer initialization:
// Can only be called once per account
#[external("public")]
fn set_account_type(account_type: AccountType) {
self.storage.account_types.at(self.msg_sender()).iniitalize(account_type);
}
Cost
The SSTORE AVM opcode is invoked a number of times equal to T's packed length plus one, and the
EMITNULLIFIER AVM opcode is invoked once.
pub fn read(self) -> T
Reads the permanent value.
This function reverts the transaction if the PublicImmutable is not initialized.
Examples
A public getter that returns the permanent value:
#[external("public")]
fn get_decimals() -> u8 {
self.storage.decimals.read()
}
Cost
The SLOAD AVM opcode is invoked a number of times equal to T's packed length, and the NULLIFIEREXISTS AVM
opcode is invoked once.
pub fn read_unsafe(self) -> T
Reads the permanent value, skipping the initialization check.
This is cheaper than PublicImmutable::read, but it should only be used if the PublicImmutable is known to
be initialized.
If the PublicImmutable is not initialized, this returns the default empty public storage value, which is all
zeroes - equivalent to let t = T::unpack(std::mem::zeroed());.
Examples
A public getter that returns the permanent value:
#[external("public")]
fn get_decimals() -> u8 {
// This call is safe because `decimals` is initialized in the contract's initializer function
self.storage.decimals.read_unsafe()
}
Cost
The SLOAD AVM opcode is invoked a number of times equal to T's packed length.
pub fn is_initialized(self) -> bool
Returns true if the PublicImmutable has been initialized.
Examples:
Conditional initialization:
#[external("public")]
fn set_account_type_if_not_set(account_type: AccountType) {
if !self.storage.account_types.at(self.msg_sender()).is_initialized() {
self.storage.account_types.at(self.msg_sender()).iniitalize(account_type);
}
}
Cost
The NULLIFIEREXISTS AVM opcode is invoked once.
Immutable public values.
This is one of the most basic public state variables. It is similar to an
immutableorconstantSolidity state variable.It represents a public value of type
Tthat can be initialized just once during the lifetime of the contract, allowing this single value to be read.Unlike Solidity's
immutableorconstant, aPublicImmutable's initialization does not need to occur during contract initialization - it can happen at any point in time (but only once). This also makes it possible to have aMapofPublicImmutables.Access Patterns
A value stored in a
PublicImmutablecan be read and initialized from public contract functions.Unlike
PublicMutableit is also possible to read aPublicImmutablefrom a private contract function, though it is not possible to initialize one. A common pattern is to have these functions enqueue a public self calls in which the initialization operation is performed.For a mutable (with restrictions) variant which also can be read from private functions see
DelayedPublicMutable.Privacy
PublicImmutableprovides zero privacy in terms of the value stored and any public accesses: the entire network can see these and the data involved.Reading a
PublicImmutablefrom a private contract function however is completely private, and leaks zero information about the fact that the value was read.Use Cases
This is suitable for any kind of fixed global configuration that needs to be accessible by private contract functions, such as token decimals, related contracts in a multi-contract configuration, etc.
It is also useful for fixed per-user configuration by combining it with a
Map, e.g. a registry of their account types.PublicImmutable's main limitation is the immutability, which in many cases leads toDelayedPublicMutablebeing used instead. But in those cases where fixed values are not a problem, this is a fine choice for storage.Examples
Declaring a
PublicImmutablein the the contract'sstoragestruct requires specifying the typeTthat is stored in the variable:Requirements
The type
Tstored in thePublicImmutablemust implement thePackabletrait.Implementation Details
Values are packed and stored directly in the public storage tree, along with the hash of the packed representation. A
PublicImmutabletherefore takes up as many storage slots as the packing length of the stored typeT, plus one. This hash allows for efficient private reads, such that only a single public storage value is read. For more details, seeWithHash.An initialization nullifier prevents re-initialization of the
PublicImmutable. This allows reading an initializedPublicImmutablefrom a private contract function, since the value is guaranteed to not change.Private contract functions however cannot determine that a
PublicImmutablehas not been initialized, as they do not have access to the current network state, only the past state at the anchor block. They can perform historical non-inclusion proofs of the initialization nullifier at past times, but they have no way to guarantee that it has not been emitted since then.