Skip to content

Commit b093bb8

Browse files
author
AztecBot
committed
Merge branch 'next' into merge-train/avm
2 parents 7bc9373 + 63c68c6 commit b093bb8

File tree

2 files changed

+152
-42
lines changed

2 files changed

+152
-42
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
11
//! Storage for contract state.
2+
//!
3+
//! Contracts store their state in _state variables_. In Solidity, a state variable is simply any value declared inside
4+
//! a contract, e.g. `contract Token { uint256 totalSupply; }`, and they can be one of many kinds (primitive values,
5+
//! mappings, arrays, immutable, etc.).
6+
//!
7+
//! Due to Aztec contracts being able to store both public and private state, there are many more different types of
8+
//! state variables, each with their nuance and use cases. Understanding these is key to understanding how a contract
9+
//! works.
210

311
pub mod map;
412
pub mod owned_state_variable;

noir-projects/aztec-nr/aztec/src/state_vars/public_mutable.nr

Lines changed: 144 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,80 @@ use crate::context::{PublicContext, UtilityContext};
22
use crate::state_vars::state_variable::StateVariable;
33
use dep::protocol_types::traits::Packable;
44

5-
/// # PublicMutable
5+
/// Mutable public values.
66
///
7-
/// PublicMutable is a public state variable type for values that can be read
8-
/// and written within public [external](crate::macros::functions::external) functions of your smart contract.
7+
/// This is the most basic public state variable. It is equivalent to a non-`immutable` non-`constant` Solidity state
8+
/// variable.
99
///
10-
/// You can declare a state variable of type PublicMutable within your contract's
11-
/// [storage](crate::macros::storage::storage) struct:
10+
/// ## Access Patterns
1211
///
13-
/// E.g.:
14-
/// `your_variable: PublicMutable<T, Context>`
15-
/// or:
16-
/// `your_mapping: Map<Field, PublicMutable<T, Context>>`
12+
/// A value stored in a `PublicMutable` can be read and written from public contract functions. It is not possible to
13+
/// read or write another contract's `PublicMutable` values directly: the only way to achieve this is to call a public
14+
/// function on that contract.
1715
///
18-
/// The methods of PublicMutable are:
19-
/// - `read`
20-
/// - `write`
21-
/// (see the methods' own doc comments for more info).
16+
/// It is not possible to read or write a `PublicMutable` from private contract functions. It is possible however for a
17+
/// private function to [enqueue a public call to itself](crate::contract_self::ContractSelf::enqueue_self) in which it
18+
/// performs the required operation.
2219
///
23-
/// ## Example.
20+
/// For an immutable variant which can be read from private functions, see
21+
/// [PublicImmutable](crate::state_vars::PublicImmutable).
2422
///
25-
/// A voting contract's proposal count can be represented as a PublicMutable<u64>.
26-
/// The count can be read by anyone to see how many proposals exist, and incremented
27-
/// when new proposals are submitted.
23+
/// For a mutable (with restrictions) variant which can be read from private functions see
24+
/// [DelayedPublicMutable](crate::state_vars::DelayedPublicMutable).
2825
///
29-
/// # Generic Parameters:
26+
/// ## Privacy
3027
///
31-
/// * `T` - The type of value stored (must implement Packable).
32-
/// * `Context` - The execution context (PublicContext or UtilityContext).
28+
/// `PublicMutable` provides zero privacy. All write and read operations are public: the entire network can see these
29+
/// accesses and the data involved.
3330
///
34-
/// # Advanced
31+
/// ## Use Cases
3532
///
36-
/// Unlike private state variables which use notes, PublicMutable stores values
37-
/// directly in Aztec's public data tree. This enables direct read and write
38-
/// access to the current state during public function execution.
33+
/// This is suitable for any kind of global state that needs to be accessible by everyone. For example, a token may have
34+
/// a public total supply, or a voting contract may have public vote tallies.
3935
///
40-
/// docs:start:public_mutable_struct
36+
/// Note that contracts having public values does not necessarily mean the the actions that update these values must
37+
/// themselves be wholly public. For example, the token could allow for private minting and burning, and casting a vote
38+
/// could be kept private: these private functions would enqueue a public function that writes to the `PublicMutable`.
39+
///
40+
/// Similarly, private functions can enqueue a public call in which the `PublicMutable` is checked to meet some
41+
/// condition. For example, a private action might be executable only if the vote count has exceeded some threshold, in
42+
/// which case the private function would enqueue a public function that reads from the `PublicMutable`.
43+
///
44+
/// Such patterns preserve the privacy of the account that executed the action, as well as details related to the
45+
/// private execution itself, but they _do_ reveal that the transaction interacted with the `PublicMutable` value (and
46+
/// hence that the contract was called), as all accesses to it are public. The
47+
/// [only_self](crate::macros::functions::only_self) attribute is very useful when implementing this.
48+
///
49+
/// ## Examples
50+
///
51+
/// Declaring a `PublicMutable` in the the contract's [storage](crate::macros::storage::storage) struct requires
52+
/// specifying the type `T` that is stored in the variable:
53+
///
54+
/// ```noir
55+
/// #[storage]
56+
/// struct Storage<Context> {
57+
/// total_supply: PublicMutable<u128, Context>,
58+
/// public_balances: Map<AztecAddress, PublicMutable<u128, Context>, Context>,
59+
///
60+
/// vote_tallies: Map<ElectionId, PublicMutable<u128, Context>, Context>,
61+
/// }
62+
/// ```
63+
///
64+
/// ## Requirements
65+
///
66+
/// The type `T` stored in the `PublicMutable` must implement the `Packable` trait.
67+
///
68+
/// ## Implementation Details
69+
///
70+
/// Values are packed and stored directly in the public storage tree, with no overhead. A `PublicMutable` therefore
71+
/// takes up as many storage slots as the packing length of the stored type `T`.
72+
///
73+
/// Private reads are not possible because private functions do not have access to the current network state, only the
74+
/// _past_ state at the anchor block. They _can_ perform historical reads of `PublicMutable` values at past times, but
75+
/// they have no way to guarantee that the value has not changed since then.
76+
/// [PublicImmutable](crate::state_vars::PublicImmutable) and
77+
/// [DelayedPublicMutable](crate::state_vars::DelayedPublicMutable) are examples of public state variables that _can_ be
78+
/// read privately by restricting mutation.
4179
pub struct PublicMutable<T, Context> {
4280
context: Context,
4381
storage_slot: Field,
@@ -58,33 +96,90 @@ where
5896
}
5997

6098
impl<T> PublicMutable<T, PublicContext> {
61-
/// Reads the current value stored in this PublicMutable state variable.
99+
/// Returns the current value.
62100
///
63-
/// # Returns
101+
/// If [write](PublicMutable::write) has never been called, then this returns the default empty public storage
102+
/// value, which is all zeroes - equivalent to `let t = T::unpack(std::mem::zeroed());`.
64103
///
65-
/// * `T` - The current value stored in this PublicMutable.
104+
/// It is not possible to detect if a `PublicMutable` has ever been initialized or not other than by testing for the
105+
/// zero sentinel value. For a more robust solution, store an `Option<T>` in the `PublicMutable`.
66106
///
67-
/// docs:start:public_mutable_struct_read
107+
/// ## Examples
108+
///
109+
/// A public getter that returns the current value:
110+
/// ```noir
111+
/// #[external("public")]
112+
/// fn get_total_supply() -> u128 {
113+
/// self.storage.total_supply.read()
114+
/// }
115+
/// ```
116+
///
117+
/// An [only_self](crate::macros::functions::only_self) helper that asserts a condition a private function requires:
118+
/// ```noir
119+
/// #[external("private")]
120+
/// fn execute_proposal(election_id: ElectionId) {
121+
/// self.enqueue_self._assert_vote_passed(election_id);
122+
///
123+
/// // execute the proposal - this remains private
124+
/// }
125+
///
126+
/// #[external("public")]
127+
/// #[only_self]
128+
/// fn _assert_vote_passed(election_id: ElectionId) {
129+
/// assert(self.storage.vote_tallies.at(election_id).read() >= VOTE_PASSED_THRESHOLD);
130+
/// }
131+
/// ```
132+
///
133+
/// ## Cost
134+
///
135+
/// The `SLOAD` AVM opcode is invoked a number of times equal to `T`'s packed length.
68136
pub fn read(self) -> T
69137
where
70138
T: Packable,
71139
{
72140
self.context.storage_read(self.storage_slot)
73141
}
74142

75-
/// Writes a new value to this PublicMutable state variable.
143+
/// Stores a new value.
144+
///
145+
/// The old value is overridden and cannot be recovered. The new value can be immediately retrieved by
146+
/// [read](PublicMutable::read).
147+
///
148+
/// ## Examples
149+
///
150+
/// A public setter that updates the current value:
151+
/// ```noir
152+
/// #[external("public")]
153+
/// fn mint_tokens(recipient: AztecAddress, amount: u128) {
154+
/// let current_recipient_balance = self.storage.public_balances.at(recipient).read();
155+
/// self.storage.public_balances.at(recipient).write(current_recipient_balance + amount);
156+
///
157+
/// let current_supply = self.storage.total_supply.read();
158+
/// self.storage.total_supply.write(current_supply + amount);
159+
/// }
160+
/// ```
76161
///
77-
/// # Arguments
162+
/// An [only_self](crate::macros::functions::only_self) helper that updates public state trigered by a private
163+
/// function:
164+
/// ```noir
165+
/// #[external("private")]
166+
/// fn vote_for_proposal(election_id: ElectionId, votes: u128) {
167+
/// // validate the sender can cast this many votes - this remains private
78168
///
79-
/// * `value` - The new value to store in this PublicMutable.
169+
/// self.enqueue_self._tally_vote(election_id, votes);
170+
/// }
80171
///
81-
/// # Advanced
172+
/// #[external("public")]
173+
/// #[only_self]
174+
/// fn _tally_vote(election_id: ElectionId, votes: u128) {
175+
/// let current = self.storage.vote_tallies.read();
176+
/// self.storage.vote_tallies.write(current + votes);
177+
/// }
178+
/// ```
82179
///
83-
/// This function updates the value stored in Aztec's public data tree.
84-
/// The new value becomes immediately available to subsequent reads within
85-
/// the same transaction.
180+
/// ## Cost
86181
///
87-
/// docs:start:public_mutable_struct_write
182+
/// The `SSTORE` AVM opcode is invoked a number of times equal to `T`'s packed length.
88183
pub fn write(self, value: T)
89184
where
90185
T: Packable,
@@ -94,15 +189,22 @@ impl<T> PublicMutable<T, PublicContext> {
94189
}
95190

96191
impl<T> PublicMutable<T, UtilityContext> {
97-
/// Reads the current value stored in this PublicMutable state variable.
192+
/// Returns the value at the anchor block.
98193
///
99-
/// Notice that this function is executable only within a UtilityContext, which
100-
/// is an unconstrained environment on the user's local device.
194+
/// If [write](PublicMutable::write) has never been called, then this returns the default empty public storage
195+
/// value, which is all zeroes - equivalent to `let t = T::unpack(std::mem::zeroed());`.
101196
///
102-
/// # Returns
197+
/// It is not possible to detect if a `PublicMutable` has ever been initialized or not other than by testing for the
198+
/// zero sentinel value. For a more robust solution, store an `Option<T>` in the `PublicMutable`.
103199
///
104-
/// * `T` - The current value stored in this PublicMutable.
200+
/// ## Examples
105201
///
202+
/// ```noir
203+
/// #[external("utility")]
204+
/// fn get_total_supply() -> u128 {
205+
/// self.storage.total_supply.read()
206+
/// }
207+
/// ```
106208
pub unconstrained fn read(self) -> T
107209
where
108210
T: Packable,

0 commit comments

Comments
 (0)