diff --git a/docusaurus/docs/advanced/04-storage-layout.md b/docusaurus/docs/advanced/04-storage-layout.md index 24937c23a..12f33b9b3 100644 --- a/docusaurus/docs/advanced/04-storage-layout.md +++ b/docusaurus/docs/advanced/04-storage-layout.md @@ -105,3 +105,48 @@ encoded in hexadecimal format. Before being stored in the storage, each value is serialized into bytes using the `CLType` serialization method and subsequently encapsulated with Casper's `Bytes` types. + +## Reading the storage from the outside +You do not have to compute the keys by hand. Every module gets a generated +`odra::schema::SchemaStorageLayout` implementation that describes the layout +shown above as data: the index of each field, what is stored under it and the +types involved. `odra::schema::resolve_storage` walks that description for a +dotted field path (e.g. `borrowers.balances`) and the mapping keys, and returns +the storage location together with the type of the value: + +```rust +use odra::casper_types::bytesrepr::ToBytes; +use odra::schema::{resolve_storage, SchemaStorageLayout, StorageLocation}; + +let layout = Loans::storage_kind(); +let key = account.to_bytes().unwrap(); +let query = resolve_storage(&layout, "borrowers.balances", &[key]).unwrap(); + +if let StorageLocation::State { key } = &query.location { + // `key` is the `state` dictionary item key - read it with the node RPC, + // or directly through the host environment: + let bytes = env.get_storage_value(&contract_address, key.as_bytes()); +} +``` + +`HostEnv` exposes `get_storage_value`, `get_named_value` and +`get_dictionary_value` for the three kinds of locations, and they work on +every backend - OdraVM, CasperVM and livenet. + +The [Odra CLI](../tutorials/odra-cli#storage-command) wraps all of this in the +`storage` command, which prints the layout of a contract and reads any field +by its path. + +Modules that store data outside of the layout described in this article, for +example under named keys, declare their layout explicitly with the `layout` +argument of the module attribute: + +```rust +#[odra::module(layout = odra::schema::named_key::("decimals"))] +pub struct Decimals; +``` + +The named-key storage macros (`single_value_storage!`, `key_value_storage!`, +`base64_encoded_key_value_storage!`, `dictionary_value_storage!` and +`compound_key_value_storage!`) do this +for you. diff --git a/docusaurus/docs/tutorials/odra-cli.md b/docusaurus/docs/tutorials/odra-cli.md index adc9c5d95..8c35e5834 100644 --- a/docusaurus/docs/tutorials/odra-cli.md +++ b/docusaurus/docs/tutorials/odra-cli.md @@ -835,6 +835,102 @@ Types are shown as the CLI value hints you would type on the command line, not a Running `inspect` without a contract name describes every registered contract. Combined with `--json`, this is a convenient way to feed the contract interface to other tooling. +### Storage command + +The `storage` command reads the state of a contract straight from the storage, without calling any +entry point. It is handy when a value has no getter, or when you want to see exactly what is stored +and where. + +Run it with a contract name only to print the storage layout - every field with its index in the +module and the kind of storage behind it, followed by the paths you can read: + +```bash +cargo run --bin odra_cli -- storage DogContract +``` + +```bash +💁 INFO : Storage layout of DogContract (DogContract): +💁 INFO : barks [1]: true|false +💁 INFO : weight [2]: UINT +💁 INFO : name [3]: TEXT +💁 INFO : walks [4]: UINT (repeatable) +💁 INFO : Read with `storage DogContract [-k ]...`: +💁 INFO : barks: true|false +💁 INFO : weight: UINT +💁 INFO : name: TEXT +💁 INFO : walks: UINT (repeatable) +``` + +The layout comes from the contract's code, so this works before anything is deployed. Submodules +are printed as nested trees, and the path of a field is the dotted chain of names down the tree +(`ownable.owner`). Pass a path to read the value: + +```bash +cargo run --bin odra_cli -- storage DogContract name +``` + +```bash +💁 INFO : Contract: DogContract +💁 INFO : Path: name +💁 INFO : Type: TEXT +💁 INFO : Location: state[d236fca2b92ca42da90327820d7fe73c8ad22ea13cd8d761adc6e98822195c77] +💁 INFO : Value: +``` + +`Location` tells where the value lives: an item of the Odra `state` dictionary (with its key), a +named key, or an item of a named dictionary. That is exactly what you would query with the node +RPC or a block explorer, and it is printed even when the contract is not deployed, as above. For a +deployed contract `Value` is what is stored there, or `` if nothing is. + +Whenever the path goes through a `Mapping`, a `List` item or a dictionary, provide the key with +`--key` (or `-k`), using the same value syntax as for entry point arguments. Repeat it for nested +containers, in path order. The layout lists the key each path needs. For the `Cep18` module, which +keeps its data under Casper named keys and dictionaries: + +```bash +💁 INFO : Read with `storage Cep18 [-k ]...`: +💁 INFO : decimals: 0-255|0x00|0b00000000 +💁 INFO : symbol: TEXT +💁 INFO : name: TEXT +💁 INFO : total_supply: DECIMAL +💁 INFO : balances -k : DECIMAL +💁 INFO : allowances -k : DECIMAL +``` + +```bash +# A value under a named key. +cargo run --bin odra_cli -- storage Cep18 total_supply +# A dictionary entry. +cargo run --bin odra_cli -- storage Cep18 balances -k account-hash-... +# An entry with a tuple key: the parts are joined with `:`. +cargo run --bin odra_cli -- storage Cep18 allowances -k account-hash-...:account-hash-... +``` + +For a `List` field, `.len` is the number of items and `-k ` reads one of them. + +[`inspect`](#inspect-command) prints the same storage layout next to the entry points, and the +[Storage Layout](../advanced/04-storage-layout.md#reading-the-storage-from-the-outside) article shows +how to resolve a path in your own code. + +Add `--raw` to print the stored bytes hex-encoded instead of decoding them, and `--json` to get a +machine-readable report: + +```bash +cargo run --bin odra_cli -- storage DogContract name --json +``` + +```json +{ + "contract": "DogContract", + "path": "name", + "type": "TEXT", + "location": "state", + "key": "d236fca2b92ca42da90327820d7fe73c8ad22ea13cd8d761adc6e98822195c77", + "deployed": false, + "value": null +} +``` + ### Config command The `config` command prints the livenet configuration the CLI resolved, so you can confirm which network and account you are about to talk to before sending anything. diff --git a/docusaurus/versioned_docs/version-2.10/advanced/04-storage-layout.md b/docusaurus/versioned_docs/version-2.10/advanced/04-storage-layout.md index 24937c23a..12f33b9b3 100644 --- a/docusaurus/versioned_docs/version-2.10/advanced/04-storage-layout.md +++ b/docusaurus/versioned_docs/version-2.10/advanced/04-storage-layout.md @@ -105,3 +105,48 @@ encoded in hexadecimal format. Before being stored in the storage, each value is serialized into bytes using the `CLType` serialization method and subsequently encapsulated with Casper's `Bytes` types. + +## Reading the storage from the outside +You do not have to compute the keys by hand. Every module gets a generated +`odra::schema::SchemaStorageLayout` implementation that describes the layout +shown above as data: the index of each field, what is stored under it and the +types involved. `odra::schema::resolve_storage` walks that description for a +dotted field path (e.g. `borrowers.balances`) and the mapping keys, and returns +the storage location together with the type of the value: + +```rust +use odra::casper_types::bytesrepr::ToBytes; +use odra::schema::{resolve_storage, SchemaStorageLayout, StorageLocation}; + +let layout = Loans::storage_kind(); +let key = account.to_bytes().unwrap(); +let query = resolve_storage(&layout, "borrowers.balances", &[key]).unwrap(); + +if let StorageLocation::State { key } = &query.location { + // `key` is the `state` dictionary item key - read it with the node RPC, + // or directly through the host environment: + let bytes = env.get_storage_value(&contract_address, key.as_bytes()); +} +``` + +`HostEnv` exposes `get_storage_value`, `get_named_value` and +`get_dictionary_value` for the three kinds of locations, and they work on +every backend - OdraVM, CasperVM and livenet. + +The [Odra CLI](../tutorials/odra-cli#storage-command) wraps all of this in the +`storage` command, which prints the layout of a contract and reads any field +by its path. + +Modules that store data outside of the layout described in this article, for +example under named keys, declare their layout explicitly with the `layout` +argument of the module attribute: + +```rust +#[odra::module(layout = odra::schema::named_key::("decimals"))] +pub struct Decimals; +``` + +The named-key storage macros (`single_value_storage!`, `key_value_storage!`, +`base64_encoded_key_value_storage!`, `dictionary_value_storage!` and +`compound_key_value_storage!`) do this +for you. diff --git a/docusaurus/versioned_docs/version-2.10/tutorials/odra-cli.md b/docusaurus/versioned_docs/version-2.10/tutorials/odra-cli.md index adc9c5d95..8c35e5834 100644 --- a/docusaurus/versioned_docs/version-2.10/tutorials/odra-cli.md +++ b/docusaurus/versioned_docs/version-2.10/tutorials/odra-cli.md @@ -835,6 +835,102 @@ Types are shown as the CLI value hints you would type on the command line, not a Running `inspect` without a contract name describes every registered contract. Combined with `--json`, this is a convenient way to feed the contract interface to other tooling. +### Storage command + +The `storage` command reads the state of a contract straight from the storage, without calling any +entry point. It is handy when a value has no getter, or when you want to see exactly what is stored +and where. + +Run it with a contract name only to print the storage layout - every field with its index in the +module and the kind of storage behind it, followed by the paths you can read: + +```bash +cargo run --bin odra_cli -- storage DogContract +``` + +```bash +💁 INFO : Storage layout of DogContract (DogContract): +💁 INFO : barks [1]: true|false +💁 INFO : weight [2]: UINT +💁 INFO : name [3]: TEXT +💁 INFO : walks [4]: UINT (repeatable) +💁 INFO : Read with `storage DogContract [-k ]...`: +💁 INFO : barks: true|false +💁 INFO : weight: UINT +💁 INFO : name: TEXT +💁 INFO : walks: UINT (repeatable) +``` + +The layout comes from the contract's code, so this works before anything is deployed. Submodules +are printed as nested trees, and the path of a field is the dotted chain of names down the tree +(`ownable.owner`). Pass a path to read the value: + +```bash +cargo run --bin odra_cli -- storage DogContract name +``` + +```bash +💁 INFO : Contract: DogContract +💁 INFO : Path: name +💁 INFO : Type: TEXT +💁 INFO : Location: state[d236fca2b92ca42da90327820d7fe73c8ad22ea13cd8d761adc6e98822195c77] +💁 INFO : Value: +``` + +`Location` tells where the value lives: an item of the Odra `state` dictionary (with its key), a +named key, or an item of a named dictionary. That is exactly what you would query with the node +RPC or a block explorer, and it is printed even when the contract is not deployed, as above. For a +deployed contract `Value` is what is stored there, or `` if nothing is. + +Whenever the path goes through a `Mapping`, a `List` item or a dictionary, provide the key with +`--key` (or `-k`), using the same value syntax as for entry point arguments. Repeat it for nested +containers, in path order. The layout lists the key each path needs. For the `Cep18` module, which +keeps its data under Casper named keys and dictionaries: + +```bash +💁 INFO : Read with `storage Cep18 [-k ]...`: +💁 INFO : decimals: 0-255|0x00|0b00000000 +💁 INFO : symbol: TEXT +💁 INFO : name: TEXT +💁 INFO : total_supply: DECIMAL +💁 INFO : balances -k : DECIMAL +💁 INFO : allowances -k : DECIMAL +``` + +```bash +# A value under a named key. +cargo run --bin odra_cli -- storage Cep18 total_supply +# A dictionary entry. +cargo run --bin odra_cli -- storage Cep18 balances -k account-hash-... +# An entry with a tuple key: the parts are joined with `:`. +cargo run --bin odra_cli -- storage Cep18 allowances -k account-hash-...:account-hash-... +``` + +For a `List` field, `.len` is the number of items and `-k ` reads one of them. + +[`inspect`](#inspect-command) prints the same storage layout next to the entry points, and the +[Storage Layout](../advanced/04-storage-layout.md#reading-the-storage-from-the-outside) article shows +how to resolve a path in your own code. + +Add `--raw` to print the stored bytes hex-encoded instead of decoding them, and `--json` to get a +machine-readable report: + +```bash +cargo run --bin odra_cli -- storage DogContract name --json +``` + +```json +{ + "contract": "DogContract", + "path": "name", + "type": "TEXT", + "location": "state", + "key": "d236fca2b92ca42da90327820d7fe73c8ad22ea13cd8d761adc6e98822195c77", + "deployed": false, + "value": null +} +``` + ### Config command The `config` command prints the livenet configuration the CLI resolved, so you can confirm which network and account you are about to talk to before sending anything.