Struct PublicImmutable
pub struct PublicImmutable<T, Context>
{ /* private fields */ }
Implementations
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.
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()
}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 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<Context, T> PublicImmutable<T, Context>
pub fn compute_initialization_inner_nullifier(self) -> Field
Returns the inner nullifier emitted during initialization.
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 a Map ofPublicImmutables.Access Patterns
A value stored in a
PublicImmutablecan be read and initialized from public contract functions.Unlike PublicMutable it is also possible to read a
PublicImmutablefrom 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 to DelayedPublicMutable being 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's storage struct 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, see WithHash.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.