Skip to content

Commit

Permalink
docs: document stable public state usage (#4324)
Browse files Browse the repository at this point in the history
Fixes #4325
  • Loading branch information
LHerskind authored Jan 31, 2024
1 parent 2505692 commit 13f709b
Show file tree
Hide file tree
Showing 5 changed files with 184 additions and 46 deletions.
42 changes: 42 additions & 0 deletions docs/docs/developers/contracts/syntax/storage/public_state.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,45 @@ We have a `write` method on the `PublicState` struct that takes the value to wri
#### Writing to our `minters` example

#include_code write_minter /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust

---

## Stable Public State

`StablePublicState` is a special type of `PublicState` that can be read from both public and private!

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.

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.

Just like the `PublicState` it is generic over the variable type `T`. The type `MUST` implement Serialize and Deserialize traits.

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).

### `new`
Is done exactly like the `PublicState` struct, but with the `StablePublicState` struct.

#include_code storage_decimals /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust

#include_code storage_decimals_init /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust

### `initialize`

#include_code initialize_decimals /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust

:::warning Should only be called as part of the deployment.
If this is called outside the deployment transaction multiple values could be used down the line, potentially breaking the contract.

Currently this is not constrained as we are in the middle of changing deployments.
:::

### `read_public`

Reading the value is like `PublicState`, simply with `read_public` instead of `read`.
#include_code read_decimals_public /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust


### `read_private`
Reading the value is like `PublicState`, simply with `read_private` instead of `read`. This part can only be executed in private.

#include_code read_decimals_private /yarn-project/noir-contracts/contracts/token_contract/src/main.nr rust
98 changes: 71 additions & 27 deletions yarn-project/end-to-end/src/e2e_token_contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,43 +87,87 @@ describe('e2e_token_contract', () => {
reader = await ReaderContract.deploy(wallets[0]).send().deployed();
});

it('name', async () => {
const t = toString(await asset.methods.un_get_name().view());
expect(t).toBe(TOKEN_NAME);
describe('name', () => {
it('private', async () => {
const t = toString(await asset.methods.un_get_name().view());
expect(t).toBe(TOKEN_NAME);

const tx = reader.methods.check_name(asset.address, TOKEN_NAME).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
const tx = reader.methods.check_name_private(asset.address, TOKEN_NAME).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(reader.methods.check_name_private(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
"Cannot satisfy constraint 'name.is_eq(_what)'",
);
});

it('public', async () => {
const t = toString(await asset.methods.un_get_name().view());
expect(t).toBe(TOKEN_NAME);

await expect(reader.methods.check_name(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'name.is_eq(_what)'",
);
const tx = reader.methods.check_name_public(asset.address, TOKEN_NAME).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(reader.methods.check_name_public(asset.address, 'WRONG_NAME').simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'name.is_eq(_what)'",
);
});
});

it('symbol', async () => {
const t = toString(await asset.methods.un_get_symbol().view());
expect(t).toBe(TOKEN_SYMBOL);
describe('symbol', () => {
it('private', async () => {
const t = toString(await asset.methods.un_get_symbol().view());
expect(t).toBe(TOKEN_SYMBOL);

const tx = reader.methods.check_symbol(asset.address, TOKEN_SYMBOL).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
const tx = reader.methods.check_symbol_private(asset.address, TOKEN_SYMBOL).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(
reader.methods.check_symbol_private(asset.address, 'WRONG_SYMBOL').simulate(),
).rejects.toThrowError("Cannot satisfy constraint 'symbol.is_eq(_what)'");
});
it('public', async () => {
const t = toString(await asset.methods.un_get_symbol().view());
expect(t).toBe(TOKEN_SYMBOL);

await expect(reader.methods.check_symbol(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'",
);
const tx = reader.methods.check_symbol_public(asset.address, TOKEN_SYMBOL).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(reader.methods.check_symbol_public(asset.address, 'WRONG_SYMBOL').simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'symbol.is_eq(_what)'",
);
});
});

it('decimals', async () => {
const t = await asset.methods.un_get_decimals().view();
expect(t).toBe(TOKEN_DECIMALS);
describe('decimals', () => {
it('private', async () => {
const t = await asset.methods.un_get_decimals().view();
expect(t).toBe(TOKEN_DECIMALS);

const tx = reader.methods.check_decimals(asset.address, TOKEN_DECIMALS).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);
const tx = reader.methods.check_decimals_private(asset.address, TOKEN_DECIMALS).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(reader.methods.check_decimals(asset.address, 99).simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'",
);
await expect(reader.methods.check_decimals_private(asset.address, 99).simulate()).rejects.toThrowError(
"Cannot satisfy constraint 'ret[0] as u8 == what'",
);
});

it('public', async () => {
const t = await asset.methods.un_get_decimals().view();
expect(t).toBe(TOKEN_DECIMALS);

const tx = reader.methods.check_decimals_public(asset.address, TOKEN_DECIMALS).send();
const receipt = await tx.wait();
expect(receipt.status).toBe(TxStatus.MINED);

await expect(reader.methods.check_decimals_public(asset.address, 99).simulate()).rejects.toThrowError(
"Failed to solve brillig function, reason: explicit trap hit in brillig 'ret[0] as u8 == what'",
);
});
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ contract DocsExample {
// docs:end:storage-map-singleton-declaration
test: Set<CardNote>,
imm_singleton: ImmutableSingleton<CardNote>,
// docs:start:start_vars_stable
stable_value: StablePublicState<Leader>,
// docs:end:start_vars_stable
}

impl Storage {
Expand Down
31 changes: 28 additions & 3 deletions yarn-project/noir-contracts/contracts/reader_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,52 @@ contract Reader {
fn constructor() {}

#[aztec(public)]
fn check_name(who: AztecAddress, what: str<31>) {
fn check_name_public(who: AztecAddress, what: str<31>) {
let selector = FunctionSelector::from_signature("public_get_name()");
let ret = context.call_public_function_no_args(who, selector);
let name = FieldCompressedString::from_field(ret[0]);
let _what = FieldCompressedString::from_string(what);
assert(name.is_eq(_what));
}

#[aztec(private)]
fn check_name_private(who: AztecAddress, what: str<31>) {
let selector = FunctionSelector::from_signature("private_get_name()");
let ret = context.call_private_function_no_args(who, selector);
let name = FieldCompressedString::from_field(ret[0]);
let _what = FieldCompressedString::from_string(what);
assert(name.is_eq(_what));
}

#[aztec(public)]
fn check_symbol(who: AztecAddress, what: str<31>) {
fn check_symbol_public(who: AztecAddress, what: str<31>) {
let selector = FunctionSelector::from_signature("public_get_symbol()");
let ret = context.call_public_function_no_args(who, selector);
let symbol = FieldCompressedString::from_field(ret[0]);
let _what = FieldCompressedString::from_string(what);
assert(symbol.is_eq(_what));
}

#[aztec(private)]
fn check_symbol_private(who: AztecAddress, what: str<31>) {
let selector = FunctionSelector::from_signature("private_get_symbol()");
let ret = context.call_private_function_no_args(who, selector);
let symbol = FieldCompressedString::from_field(ret[0]);
let _what = FieldCompressedString::from_string(what);
assert(symbol.is_eq(_what));
}

#[aztec(public)]
fn check_decimals(who: AztecAddress, what: u8) {
fn check_decimals_public(who: AztecAddress, what: u8) {
let selector = FunctionSelector::from_signature("public_get_decimals()");
let ret = context.call_public_function_no_args(who, selector);
assert(ret[0] as u8 == what);
}

#[aztec(private)]
fn check_decimals_private(who: AztecAddress, what: u8) {
let selector = FunctionSelector::from_signature("private_get_decimals()");
let ret = context.call_private_function_no_args(who, selector);
assert(ret[0] as u8 == what);
}
}
57 changes: 41 additions & 16 deletions yarn-project/noir-contracts/contracts/token_contract/src/main.nr
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract Token {
},
context::{PrivateContext, PublicContext, Context},
hash::{compute_secret_hash},
state_vars::{map::Map, public_state::PublicState, set::Set},
state_vars::{map::Map, public_state::PublicState, stable_public_state::StablePublicState, set::Set},
protocol_types::{
type_serialization::{
FIELD_SERIALIZED_LEN,
Expand Down Expand Up @@ -69,9 +69,11 @@ contract Token {
pending_shields: Set<TransparentNote>,
// docs:end:storage_pending_shields
public_balances: Map<AztecAddress, PublicState<SafeU120>>,
symbol: PublicState<FieldCompressedString>,
name: PublicState<FieldCompressedString>,
decimals: PublicState<u8>,
symbol: StablePublicState<FieldCompressedString>,
name: StablePublicState<FieldCompressedString>,
// docs:start:storage_decimals
decimals: StablePublicState<u8>,
// docs:end:storage_decimals
}
// docs:end:storage_struct

Expand Down Expand Up @@ -117,18 +119,20 @@ contract Token {
)
},
),
symbol: PublicState::new(
symbol: StablePublicState::new(
context,
7,
),
name: PublicState::new(
name: StablePublicState::new(
context,
8,
),
decimals: PublicState::new(
// docs:start:storage_decimals_init
decimals: StablePublicState::new(
context,
9,
),
// docs:end:storage_decimals_init
}
}
}
Expand Down Expand Up @@ -160,29 +164,48 @@ contract Token {

#[aztec(public)]
fn public_get_name() -> pub FieldCompressedString {
storage.name.read()
storage.name.read_public()
}

#[aztec(private)]
fn private_get_name() -> pub FieldCompressedString {
storage.name.read_private()
}

unconstrained fn un_get_name() -> pub [u8; 31] {
storage.name.read().to_bytes()
storage.name.read_public().to_bytes()
}

#[aztec(public)]
fn public_get_symbol() -> pub FieldCompressedString {
storage.symbol.read()
storage.symbol.read_public()
}

#[aztec(private)]
fn private_get_symbol() -> pub FieldCompressedString {
storage.symbol.read_private()
}

unconstrained fn un_get_symbol() -> pub [u8; 31] {
storage.symbol.read().to_bytes()
storage.symbol.read_public().to_bytes()
}

#[aztec(public)]
fn public_get_decimals() -> pub u8 {
storage.decimals.read()
// docs:start:read_decimals_public
storage.decimals.read_public()
// docs:end:read_decimals_public
}

#[aztec(private)]
fn private_get_decimals() -> pub u8 {
// docs:start:read_decimals_private
storage.decimals.read_private()
// docs:end:read_decimals_private
}

unconstrained fn un_get_decimals() -> pub u8 {
storage.decimals.read()
storage.decimals.read_public()
}

// docs:start:set_minter
Expand Down Expand Up @@ -366,9 +389,11 @@ contract Token {
assert(!new_admin.is_zero(), "invalid admin");
storage.admin.write(new_admin);
storage.minters.at(new_admin).write(true);
storage.name.write(name);
storage.symbol.write(symbol);
storage.decimals.write(decimals);
storage.name.initialize(name);
storage.symbol.initialize(symbol);
// docs:start:initialize_decimals
storage.decimals.initialize(decimals);
// docs:end:initialize_decimals
}
// docs:end:initialize

Expand Down

0 comments on commit 13f709b

Please sign in to comment.