From 7a9fee426fafb32377146f23d35977d6f8b66885 Mon Sep 17 00:00:00 2001 From: LHerskind Date: Wed, 31 Jan 2024 12:14:45 +0000 Subject: [PATCH 1/2] docs: Add minimal doc on stable public state --- .../contracts/syntax/storage/public_state.md | 42 ++++++++ .../end-to-end/src/e2e_token_contract.test.ts | 98 ++++++++++++++----- .../docs_example_contract/src/main.nr | 2 + .../contracts/reader_contract/src/main.nr | 31 +++++- .../contracts/token_contract/src/main.nr | 57 ++++++++--- 5 files changed, 184 insertions(+), 46 deletions(-) diff --git a/docs/docs/developers/contracts/syntax/storage/public_state.md b/docs/docs/developers/contracts/syntax/storage/public_state.md index 3218d9a7bab..7e12b843c85 100644 --- a/docs/docs/developers/contracts/syntax/storage/public_state.md +++ b/docs/docs/developers/contracts/syntax/storage/public_state.md @@ -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. + +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 diff --git a/yarn-project/end-to-end/src/e2e_token_contract.test.ts b/yarn-project/end-to-end/src/e2e_token_contract.test.ts index 33ecf0da53a..bd40ce3faac 100644 --- a/yarn-project/end-to-end/src/e2e_token_contract.test.ts +++ b/yarn-project/end-to-end/src/e2e_token_contract.test.ts @@ -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'", + ); + }); }); }); diff --git a/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr b/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr index a87b7c960cc..ee28961aefd 100644 --- a/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/docs_example_contract/src/main.nr @@ -46,7 +46,9 @@ contract DocsExample { // docs:end:storage-map-singleton-declaration test: Set, imm_singleton: ImmutableSingleton, + // docs:start:start_vars_stable stable_value: StablePublicState, + // docs:end:start_vars_stable } impl Storage { diff --git a/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr b/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr index b8c65f95c67..928d80b908b 100644 --- a/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/reader_contract/src/main.nr @@ -11,7 +11,7 @@ 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]); @@ -19,8 +19,17 @@ contract Reader { 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]); @@ -28,10 +37,26 @@ contract Reader { 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); + } } diff --git a/yarn-project/noir-contracts/contracts/token_contract/src/main.nr b/yarn-project/noir-contracts/contracts/token_contract/src/main.nr index 06c09e335e0..9815719c26f 100644 --- a/yarn-project/noir-contracts/contracts/token_contract/src/main.nr +++ b/yarn-project/noir-contracts/contracts/token_contract/src/main.nr @@ -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, @@ -69,9 +69,11 @@ contract Token { pending_shields: Set, // docs:end:storage_pending_shields public_balances: Map>, - symbol: PublicState, - name: PublicState, - decimals: PublicState, + symbol: StablePublicState, + name: StablePublicState, + // docs:start:storage_decimals + decimals: StablePublicState, + // docs:end:storage_decimals } // docs:end:storage_struct @@ -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 } } } @@ -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 @@ -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 From 5f40a29b9888a4239a36ebf5c4fc5e6140e86a77 Mon Sep 17 00:00:00 2001 From: Lasse Herskind <16536249+LHerskind@users.noreply.github.com> Date: Wed, 31 Jan 2024 20:32:33 +0000 Subject: [PATCH 2/2] Update public_state.md --- docs/docs/developers/contracts/syntax/storage/public_state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/developers/contracts/syntax/storage/public_state.md b/docs/docs/developers/contracts/syntax/storage/public_state.md index 7e12b843c85..1b53e9dbaf9 100644 --- a/docs/docs/developers/contracts/syntax/storage/public_state.md +++ b/docs/docs/developers/contracts/syntax/storage/public_state.md @@ -101,7 +101,7 @@ Since private execution is based on historical data, the user can pick ANY of it 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. +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).