Skip to content

Commit ee34260

Browse files
authored
docs: document stable public state usage (AztecProtocol#4324)
Fixes AztecProtocol#4325
1 parent 1478828 commit ee34260

File tree

5 files changed

+184
-46
lines changed

5 files changed

+184
-46
lines changed

docs/docs/developers/contracts/syntax/storage/public_state.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,45 @@ We have a `write` method on the `PublicState` struct that takes the value to wri
9090
#### Writing to our `minters` example
9191

9292
#include_code write_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
93+
94+
---
95+
96+
## Stable Public State
97+
98+
`StablePublicState` is a special type of `PublicState` that can be read from both public and private!
99+
100+
Since private execution is based on historical data, the user can pick ANY of its prior values to read from. This is why it `MUST` not be updated after the contract is deployed. The variable should be initialized at the constructor and then never changed.
101+
102+
This makes the stable public variables useful for stuff that you would usually have in `immutable` values in solidity. For example this can be the name of a token or its number of decimals.
103+
104+
Just like the `PublicState` it is generic over the variable type `T`. The type `MUST` implement Serialize and Deserialize traits.
105+
106+
You can find the details of `StablePublicState` in the implementation [here](https://github.com/AztecProtocol/aztec-packages/blob/#include_aztec_version/yarn-project/aztec-nr/aztec/src/state_vars/stable_public_state.nr).
107+
108+
### `new`
109+
Is done exactly like the `PublicState` struct, but with the `StablePublicState` struct.
110+
111+
#include_code storage_decimals /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
112+
113+
#include_code storage_decimals_init /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
114+
115+
### `initialize`
116+
117+
#include_code initialize_decimals /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
118+
119+
:::warning Should only be called as part of the deployment.
120+
If this is called outside the deployment transaction multiple values could be used down the line, potentially breaking the contract.
121+
122+
Currently this is not constrained as we are in the middle of changing deployments.
123+
:::
124+
125+
### `read_public`
126+
127+
Reading the value is like `PublicState`, simply with `read_public` instead of `read`.
128+
#include_code read_decimals_public /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
129+
130+
131+
### `read_private`
132+
Reading the value is like `PublicState`, simply with `read_private` instead of `read`. This part can only be executed in private.
133+
134+
#include_code read_decimals_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust

yarn-project/end-to-end/src/e2e_token_contract.test.ts

Lines changed: 71 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -87,43 +87,87 @@ describe('e2e_token_contract', () => {
8787
reader = await ReaderContract.deploy(wallets[0]).send().deployed();
8888
});
8989

90-
it('name', async () => {
91-
const t = toString(await asset.methods.un_get_name().view());
92-
expect(t).toBe(TOKEN_NAME);
90+
describe('name', () => {
91+
it('private', async () => {
92+
const t = toString(await asset.methods.un_get_name().view());
93+
expect(t).toBe(TOKEN_NAME);
9394

94-
const tx = reader.methods.check_name(asset.address, TOKEN_NAME).send();
95-
const receipt = await tx.wait();
96-
expect(receipt.status).toBe(TxStatus.MINED);
95+
const tx = reader.methods.check_name_private(asset.address, TOKEN_NAME).send();
96+
const receipt = await tx.wait();
97+
expect(receipt.status).toBe(TxStatus.MINED);
98+
99+
await expect(reader.methods.check_name_private(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
100+
"Cannot satisfy constraint 'name.is_eq(_what)'",
101+
);
102+
});
103+
104+
it('public', async () => {
105+
const t = toString(await asset.methods.un_get_name().view());
106+
expect(t).toBe(TOKEN_NAME);
97107

98-
await expect(reader.methods.check_name(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
99-
"Failed to solve brillig function, reason: explicit trap hit in brillig 'name.is_eq(_what)'",
100-
);
108+
const tx = reader.methods.check_name_public(asset.address, TOKEN_NAME).send();
109+
const receipt = await tx.wait();
110+
expect(receipt.status).toBe(TxStatus.MINED);
111+
112+
await expect(reader.methods.check_name_public(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
113+
"Failed to solve brillig function, reason: explicit trap hit in brillig 'name.is_eq(_what)'",
114+
);
115+
});
101116
});
102117

103-
it('symbol', async () => {
104-
const t = toString(await asset.methods.un_get_symbol().view());
105-
expect(t).toBe(TOKEN_SYMBOL);
118+
describe('symbol', () => {
119+
it('private', async () => {
120+
const t = toString(await asset.methods.un_get_symbol().view());
121+
expect(t).toBe(TOKEN_SYMBOL);
106122

107-
const tx = reader.methods.check_symbol(asset.address, TOKEN_SYMBOL).send();
108-
const receipt = await tx.wait();
109-
expect(receipt.status).toBe(TxStatus.MINED);
123+
const tx = reader.methods.check_symbol_private(asset.address, TOKEN_SYMBOL).send();
124+
const receipt = await tx.wait();
125+
expect(receipt.status).toBe(TxStatus.MINED);
126+
127+
await expect(
128+
reader.methods.check_symbol_private(asset.address, 'WRONG_SYMBOL').simulate(),
129+
).rejects.toThrowError("Cannot satisfy constraint 'symbol.is_eq(_what)'");
130+
});
131+
it('public', async () => {
132+
const t = toString(await asset.methods.un_get_symbol().view());
133+
expect(t).toBe(TOKEN_SYMBOL);
110134

111-
await expect(reader.methods.check_symbol(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrowError(
112-
"Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'",
113-
);
135+
const tx = reader.methods.check_symbol_public(asset.address, TOKEN_SYMBOL).send();
136+
const receipt = await tx.wait();
137+
expect(receipt.status).toBe(TxStatus.MINED);
138+
139+
await expect(reader.methods.check_symbol_public(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrowError(
140+
"Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'",
141+
);
142+
});
114143
});
115144

116-
it('decimals', async () => {
117-
const t = await asset.methods.un_get_decimals().view();
118-
expect(t).toBe(TOKEN_DECIMALS);
145+
describe('decimals', () => {
146+
it('private', async () => {
147+
const t = await asset.methods.un_get_decimals().view();
148+
expect(t).toBe(TOKEN_DECIMALS);
119149

120-
const tx = reader.methods.check_decimals(asset.address, TOKEN_DECIMALS).send();
121-
const receipt = await tx.wait();
122-
expect(receipt.status).toBe(TxStatus.MINED);
150+
const tx = reader.methods.check_decimals_private(asset.address, TOKEN_DECIMALS).send();
151+
const receipt = await tx.wait();
152+
expect(receipt.status).toBe(TxStatus.MINED);
123153

124-
await expect(reader.methods.check_decimals(asset.address, 99).simulate()).rejects.toThrowError(
125-
"Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'",
126-
);
154+
await expect(reader.methods.check_decimals_private(asset.address, 99).simulate()).rejects.toThrowError(
155+
"Cannot satisfy constraint 'ret[0] as u8 == what'",
156+
);
157+
});
158+
159+
it('public', async () => {
160+
const t = await asset.methods.un_get_decimals().view();
161+
expect(t).toBe(TOKEN_DECIMALS);
162+
163+
const tx = reader.methods.check_decimals_public(asset.address, TOKEN_DECIMALS).send();
164+
const receipt = await tx.wait();
165+
expect(receipt.status).toBe(TxStatus.MINED);
166+
167+
await expect(reader.methods.check_decimals_public(asset.address, 99).simulate()).rejects.toThrowError(
168+
"Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'",
169+
);
170+
});
127171
});
128172
});
129173

yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ contract DocsExample {
4646
// docs:end:storage-map-singleton-declaration
4747
test: Set<CardNote>,
4848
imm_singleton: ImmutableSingleton<CardNote>,
49+
// docs:start:start_vars_stable
4950
stable_value: StablePublicState<Leader>,
51+
// docs:end:start_vars_stable
5052
}
5153

5254
impl Storage {

yarn-project/noir-contracts/contracts/reader_contract/src/main.nr

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,52 @@ contract Reader {
1111
fn constructor() {}
1212

1313
#[aztec(public)]
14-
fn check_name(who: AztecAddress, what: str<31>) {
14+
fn check_name_public(who: AztecAddress, what: str<31>) {
1515
let selector = FunctionSelector::from_signature("public_get_name()");
1616
let ret = context.call_public_function_no_args(who, selector);
1717
let name = FieldCompressedString::from_field(ret[0]);
1818
let _what = FieldCompressedString::from_string(what);
1919
assert(name.is_eq(_what));
2020
}
2121

22+
#[aztec(private)]
23+
fn check_name_private(who: AztecAddress, what: str<31>) {
24+
let selector = FunctionSelector::from_signature("private_get_name()");
25+
let ret = context.call_private_function_no_args(who, selector);
26+
let name = FieldCompressedString::from_field(ret[0]);
27+
let _what = FieldCompressedString::from_string(what);
28+
assert(name.is_eq(_what));
29+
}
30+
2231
#[aztec(public)]
23-
fn check_symbol(who: AztecAddress, what: str<31>) {
32+
fn check_symbol_public(who: AztecAddress, what: str<31>) {
2433
let selector = FunctionSelector::from_signature("public_get_symbol()");
2534
let ret = context.call_public_function_no_args(who, selector);
2635
let symbol = FieldCompressedString::from_field(ret[0]);
2736
let _what = FieldCompressedString::from_string(what);
2837
assert(symbol.is_eq(_what));
2938
}
3039

40+
#[aztec(private)]
41+
fn check_symbol_private(who: AztecAddress, what: str<31>) {
42+
let selector = FunctionSelector::from_signature("private_get_symbol()");
43+
let ret = context.call_private_function_no_args(who, selector);
44+
let symbol = FieldCompressedString::from_field(ret[0]);
45+
let _what = FieldCompressedString::from_string(what);
46+
assert(symbol.is_eq(_what));
47+
}
48+
3149
#[aztec(public)]
32-
fn check_decimals(who: AztecAddress, what: u8) {
50+
fn check_decimals_public(who: AztecAddress, what: u8) {
3351
let selector = FunctionSelector::from_signature("public_get_decimals()");
3452
let ret = context.call_public_function_no_args(who, selector);
3553
assert(ret[0] as u8 == what);
3654
}
55+
56+
#[aztec(private)]
57+
fn check_decimals_private(who: AztecAddress, what: u8) {
58+
let selector = FunctionSelector::from_signature("private_get_decimals()");
59+
let ret = context.call_private_function_no_args(who, selector);
60+
assert(ret[0] as u8 == what);
61+
}
3762
}

yarn-project/noir-contracts/contracts/token_contract/src/main.nr

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ contract Token {
2424
},
2525
context::{PrivateContext, PublicContext, Context},
2626
hash::{compute_secret_hash},
27-
state_vars::{map::Map, public_state::PublicState, set::Set},
27+
state_vars::{map::Map, public_state::PublicState, stable_public_state::StablePublicState, set::Set},
2828
protocol_types::{
2929
type_serialization::{
3030
FIELD_SERIALIZED_LEN,
@@ -69,9 +69,11 @@ contract Token {
6969
pending_shields: Set<TransparentNote>,
7070
// docs:end:storage_pending_shields
7171
public_balances: Map<AztecAddress, PublicState<SafeU120>>,
72-
symbol: PublicState<FieldCompressedString>,
73-
name: PublicState<FieldCompressedString>,
74-
decimals: PublicState<u8>,
72+
symbol: StablePublicState<FieldCompressedString>,
73+
name: StablePublicState<FieldCompressedString>,
74+
// docs:start:storage_decimals
75+
decimals: StablePublicState<u8>,
76+
// docs:end:storage_decimals
7577
}
7678
// docs:end:storage_struct
7779

@@ -117,18 +119,20 @@ contract Token {
117119
)
118120
},
119121
),
120-
symbol: PublicState::new(
122+
symbol: StablePublicState::new(
121123
context,
122124
7,
123125
),
124-
name: PublicState::new(
126+
name: StablePublicState::new(
125127
context,
126128
8,
127129
),
128-
decimals: PublicState::new(
130+
// docs:start:storage_decimals_init
131+
decimals: StablePublicState::new(
129132
context,
130133
9,
131134
),
135+
// docs:end:storage_decimals_init
132136
}
133137
}
134138
}
@@ -160,29 +164,48 @@ contract Token {
160164

161165
#[aztec(public)]
162166
fn public_get_name() -> pub FieldCompressedString {
163-
storage.name.read()
167+
storage.name.read_public()
168+
}
169+
170+
#[aztec(private)]
171+
fn private_get_name() -> pub FieldCompressedString {
172+
storage.name.read_private()
164173
}
165174

166175
unconstrained fn un_get_name() -> pub [u8; 31] {
167-
storage.name.read().to_bytes()
176+
storage.name.read_public().to_bytes()
168177
}
169178

170179
#[aztec(public)]
171180
fn public_get_symbol() -> pub FieldCompressedString {
172-
storage.symbol.read()
181+
storage.symbol.read_public()
182+
}
183+
184+
#[aztec(private)]
185+
fn private_get_symbol() -> pub FieldCompressedString {
186+
storage.symbol.read_private()
173187
}
174188

175189
unconstrained fn un_get_symbol() -> pub [u8; 31] {
176-
storage.symbol.read().to_bytes()
190+
storage.symbol.read_public().to_bytes()
177191
}
178192

179193
#[aztec(public)]
180194
fn public_get_decimals() -> pub u8 {
181-
storage.decimals.read()
195+
// docs:start:read_decimals_public
196+
storage.decimals.read_public()
197+
// docs:end:read_decimals_public
198+
}
199+
200+
#[aztec(private)]
201+
fn private_get_decimals() -> pub u8 {
202+
// docs:start:read_decimals_private
203+
storage.decimals.read_private()
204+
// docs:end:read_decimals_private
182205
}
183206

184207
unconstrained fn un_get_decimals() -> pub u8 {
185-
storage.decimals.read()
208+
storage.decimals.read_public()
186209
}
187210

188211
// docs:start:set_minter
@@ -366,9 +389,11 @@ contract Token {
366389
assert(!new_admin.is_zero(), "invalid admin");
367390
storage.admin.write(new_admin);
368391
storage.minters.at(new_admin).write(true);
369-
storage.name.write(name);
370-
storage.symbol.write(symbol);
371-
storage.decimals.write(decimals);
392+
storage.name.initialize(name);
393+
storage.symbol.initialize(symbol);
394+
// docs:start:initialize_decimals
395+
storage.decimals.initialize(decimals);
396+
// docs:end:initialize_decimals
372397
}
373398
// docs:end:initialize
374399

0 commit comments

Comments
 (0)