Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: document stable public state usage #4324

Merged
merged 2 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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, as mentioned earlier.
LHerskind marked this conversation as resolved.
Show resolved Hide resolved

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
Loading