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

feat: New command to view contract storage state #239

Merged
merged 18 commits into from
Sep 9, 2023
50 changes: 50 additions & 0 deletions docs/GUIDE.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ fro_volod.testnet account has NFT tokens:
- [call-function](#call-function---Execute-function-contract-method)
- [deploy](#deploy---Add-a-new-contract-code)
- [download-wasm](#download-wasm---Download-wasm)
- [view-storage](#view-storage---View-contract-storage-state)

#### call-function - Execute function (contract method)

Expand Down Expand Up @@ -1233,6 +1234,55 @@ The file "/Users/frovolod/Downloads/contract_262_volodymyr_testnet.wasm" was dow
</a>
</details>

#### view-storage - View contract storage state

You can view the contract key values at the current moment in time (***now***) and at a certain point in the past by specifying a block (***at-block-height*** or ***at-block-hash***).
Examples of the use of these parameters are discussed in the ([View properties for an account](#view-account-summary---view-properties-for-an-account)).
The keys themselves can be viewed all (***all***) or filtered using ***keys-start-with-string*** or ***keys-start-with-bytes-as-base64***.

To view contract keys, enter at the terminal command line:

```txt
near contract \
view-storage turbo.volodymyr.testnet \
all \
as-json \
network-config testnet \
now
```

<details><summary><i>The result of this command will be as follows:</i></summary>
```txt
Contract state (values):
[
{
"key": "MjF2b2xvZHlteXIudGVzdG5ldA==",
"value": "JwAAAAAAAAAIAAAAAAAAAA=="
},
{
"key": "U1RBVEU=",
"value": ""
},
{
"key": "ZnJvX3ZvbG9kLnRlc3RuZXQ=",
"value": "HQAAAAAAAAAGAAAAAAAAAA=="
},
{
"key": "dm9sb2R5bXlyLnRlc3RuZXQ=",
"value": "QAEAAAAAAABAAAAAAAAAAA=="
}
]
Contract state (proof):
[]
```
</details>

<details><summary><i>Demonstration of the command in interactive mode</i></summary>
<a href="https://asciinema.org/a/ylVt2VzX2GZp6nP5OccBbdKul?autoplay=1&t=1&speed=2">
<img src="https://asciinema.org/a/ylVt2VzX2GZp6nP5OccBbdKul.png" width="836"/>
</a>
</details>

### transaction - Operate transactions

- [view-status](#view-status---View-a-transaction-status)
Expand Down
50 changes: 50 additions & 0 deletions docs/GUIDE.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,7 @@ fro_volod.testnet account has NFT tokens:
- [call-function](#call-function---Execute-function-contract-method)
- [deploy](#deploy---Add-a-new-contract-code)
- [download-wasm](#download-wasm---Download-wasm)
- [view-storage](#view-storage---View-contract-storage-state)

#### call-function - Execute function (contract method)

Expand Down Expand Up @@ -1234,6 +1235,55 @@ The file "/Users/frovolod/Downloads/contract_262_volodymyr_testnet.wasm" was dow
</a>
</details>

#### view-storage - View contract storage state

Просмотреть значения ключей контракта возможно на текущий момент времени (***now***) и на определеный момент в прошлом, указав блок (***at-block-height*** или ***at-block-hash***).
Примеры использования этих параметров рассмотрены в разделе [View properties for an account](#view-account-summary---view-properties-for-an-account).
Сами же ключи можно просмотреть все (***all***) или отфильтрованные с помощью ***keys-start-with-string*** или ***keys-start-with-bytes-as-base64***.

Для просмотра ключей контракта необходимо ввести в командной строке терминала:

```txt
near contract \
view-storage turbo.volodymyr.testnet \
all \
as-json \
network-config testnet \
now
```

<details><summary><i>Результат выполнения команды</i></summary>
```txt
Contract state (values):
[
{
"key": "MjF2b2xvZHlteXIudGVzdG5ldA==",
"value": "JwAAAAAAAAAIAAAAAAAAAA=="
},
{
"key": "U1RBVEU=",
"value": ""
},
{
"key": "ZnJvX3ZvbG9kLnRlc3RuZXQ=",
"value": "HQAAAAAAAAAGAAAAAAAAAA=="
},
{
"key": "dm9sb2R5bXlyLnRlc3RuZXQ=",
"value": "QAEAAAAAAABAAAAAAAAAAA=="
}
]
Contract state (proof):
[]
```
</details>

<details><summary><i>Демонстрация работы команды в интерактивном режиме</i></summary>
<a href="https://asciinema.org/a/ylVt2VzX2GZp6nP5OccBbdKul?autoplay=1&t=1&speed=2">
<img src="https://asciinema.org/a/ylVt2VzX2GZp6nP5OccBbdKul.png" width="836"/>
</a>
</details>

### transaction - Operate transactions

- [view-status](#view-status---View-a-transaction-status)
Expand Down
6 changes: 5 additions & 1 deletion src/commands/contract/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use strum::{EnumDiscriminants, EnumIter, EnumMessage};
pub mod call_function;
mod deploy;
mod download_wasm;
mod view_storage;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(context = crate::GlobalContext)]
Expand All @@ -15,7 +16,7 @@ pub struct ContractCommands {
#[interactive_clap(context = crate::GlobalContext)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
#[non_exhaustive]
/// Сhoose action for account:
/// Choose a contract action:
pub enum ContractActions {
#[strum_discriminants(strum(
message = "call-function - Execute function (contract method)"
Expand All @@ -28,4 +29,7 @@ pub enum ContractActions {
#[strum_discriminants(strum(message = "download-wasm - Download wasm"))]
/// Download wasm
DownloadWasm(self::download_wasm::ContractAccount),
#[strum_discriminants(strum(message = "view-storage - View contract storage state"))]
/// View contract storage state
ViewStorage(self::view_storage::ViewStorage),
}
29 changes: 29 additions & 0 deletions src/commands/contract/view_storage/keys_to_view/all_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = super::super::ViewStorageContext)]
#[interactive_clap(output_context = AllKeysContext)]
pub struct AllKeys {
#[interactive_clap(subcommand)]
output_format: super::super::output_format::OutputFormat,
}

#[derive(Debug, Clone)]
pub struct AllKeysContext(super::KeysContext);

impl AllKeysContext {
pub fn from_previous_context(
previous_context: super::super::ViewStorageContext,
_scope: &<AllKeys as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self(super::KeysContext {
global_context: previous_context.global_context,
contract_account_id: previous_context.contract_account_id,
prefix: near_primitives::types::StoreKey::from(Vec::new()),
}))
}
}

impl From<AllKeysContext> for super::KeysContext {
fn from(item: AllKeysContext) -> Self {
item.0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = super::super::ViewStorageContext)]
#[interactive_clap(output_context = KeysStartWithBytesAsBase64Context)]
pub struct KeysStartWithBytesAsBase64 {
/// Enter the string that the keys begin with Base64 bytes (for example, "Uw=="):
keys_begin_with: crate::types::base64_bytes::Base64Bytes,
#[interactive_clap(subcommand)]
output_format: super::super::output_format::OutputFormat,
}

#[derive(Debug, Clone)]
pub struct KeysStartWithBytesAsBase64Context(super::KeysContext);

impl KeysStartWithBytesAsBase64Context {
pub fn from_previous_context(
previous_context: super::super::ViewStorageContext,
scope: &<KeysStartWithBytesAsBase64 as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self(super::KeysContext {
global_context: previous_context.global_context,
contract_account_id: previous_context.contract_account_id,
prefix: near_primitives::types::StoreKey::from(scope.keys_begin_with.inner.clone()),
}))
}
}

impl From<KeysStartWithBytesAsBase64Context> for super::KeysContext {
fn from(item: KeysStartWithBytesAsBase64Context) -> Self {
item.0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = super::super::ViewStorageContext)]
#[interactive_clap(output_context = KeysStartWithStringContext)]
pub struct KeysStartWithString {
/// Enter the string that the keys begin with (for example, "S"):
keys_begin_with: String,
#[interactive_clap(subcommand)]
output_format: super::super::output_format::OutputFormat,
}

#[derive(Debug, Clone)]
pub struct KeysStartWithStringContext(super::KeysContext);

impl KeysStartWithStringContext {
pub fn from_previous_context(
previous_context: super::super::ViewStorageContext,
scope: &<KeysStartWithString as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self(super::KeysContext {
global_context: previous_context.global_context,
contract_account_id: previous_context.contract_account_id,
prefix: near_primitives::types::StoreKey::from(
scope.keys_begin_with.clone().into_bytes(),
),
}))
}
}

impl From<KeysStartWithStringContext> for super::KeysContext {
fn from(item: KeysStartWithStringContext) -> Self {
item.0
}
}
34 changes: 34 additions & 0 deletions src/commands/contract/view_storage/keys_to_view/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use strum::{EnumDiscriminants, EnumIter, EnumMessage};

mod all_keys;
mod keys_start_with_bytes_as_base64;
mod keys_start_with_string;

#[derive(Debug, EnumDiscriminants, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(context = super::ViewStorageContext)]
#[strum_discriminants(derive(EnumMessage, EnumIter))]
/// Select keys to view contract storage state:
pub enum KeysToView {
#[strum_discriminants(strum(
message = "all - View contract storage state for all keys"
))]
/// View contract storage state for all keys
All(self::all_keys::AllKeys),
#[strum_discriminants(strum(
message = "keys-start-with-string - View contract storage state for keys that start with a string (for example, \"S\")"
))]
/// View contract storage state for keys that start with a string (for example, "S")
KeysStartWithString(self::keys_start_with_string::KeysStartWithString),
#[strum_discriminants(strum(
message = "keys-start-with-bytes-as-base64 - View contract storage state for keys that start with Base64 bytes (for example, \"Uw==\")"
))]
/// View contract storage state for keys that start with Base64 bytes (for example, "Uw==")
KeysStartWithBytesAsBase64(self::keys_start_with_bytes_as_base64::KeysStartWithBytesAsBase64),
}

#[derive(Debug, Clone)]
pub struct KeysContext {
pub global_context: crate::GlobalContext,
pub contract_account_id: near_primitives::types::AccountId,
pub prefix: near_primitives::types::StoreKey,
}
42 changes: 42 additions & 0 deletions src/commands/contract/view_storage/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
mod keys_to_view;
mod output_format;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = crate::GlobalContext)]
#[interactive_clap(output_context = ViewStorageContext)]
pub struct ViewStorage {
#[interactive_clap(skip_default_input_arg)]
/// What is the contract account ID?
contract_account_id: crate::types::account_id::AccountId,
#[interactive_clap(subcommand)]
keys_to_view: self::keys_to_view::KeysToView,
}

#[derive(Debug, Clone)]
pub struct ViewStorageContext {
global_context: crate::GlobalContext,
contract_account_id: near_primitives::types::AccountId,
}

impl ViewStorageContext {
pub fn from_previous_context(
previous_context: crate::GlobalContext,
scope: &<ViewStorage as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
Ok(Self {
global_context: previous_context,
contract_account_id: scope.contract_account_id.clone().into(),
})
}
}

impl ViewStorage {
pub fn input_contract_account_id(
context: &crate::GlobalContext,
) -> color_eyre::eyre::Result<Option<crate::types::account_id::AccountId>> {
crate::common::input_non_signer_account_id_from_used_account_list(
&context.config.credentials_home_dir,
"What is the contract account ID?",
)
}
}
68 changes: 68 additions & 0 deletions src/commands/contract/view_storage/output_format/as_json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use crate::common::JsonRpcClientExt;
use color_eyre::eyre::Context;

#[derive(Debug, Clone, interactive_clap::InteractiveClap)]
#[interactive_clap(input_context = super::super::keys_to_view::KeysContext)]
#[interactive_clap(output_context = AsJsonContext)]
pub struct AsJson {
#[interactive_clap(named_arg)]
/// Select network
network_config: crate::network_view_at_block::NetworkViewAtBlockArgs,
}

#[derive(Clone)]
pub struct AsJsonContext(crate::network_view_at_block::ArgsForViewContext);

impl AsJsonContext {
pub fn from_previous_context(
previous_context: super::super::keys_to_view::KeysContext,
_scope: &<AsJson as interactive_clap::ToInteractiveClapContextScope>::InteractiveClapContextScope,
) -> color_eyre::eyre::Result<Self> {
let on_after_getting_block_reference_callback: crate::network_view_at_block::OnAfterGettingBlockReferenceCallback = std::sync::Arc::new({
let contract_account_id = previous_context.contract_account_id.clone();
let prefix = previous_context.prefix;

move |network_config, block_reference| {
let query_view_method_response = network_config
.json_rpc_client()
.blocking_call(near_jsonrpc_client::methods::query::RpcQueryRequest {
block_reference: block_reference.clone(),
request: near_primitives::views::QueryRequest::ViewState {
account_id: contract_account_id.clone(),
prefix: prefix.clone(),
include_proof: false,
},
})
.wrap_err_with(|| format!("Failed to fetch query ViewState for <{contract_account_id}>"))?;
if let near_jsonrpc_primitives::types::query::QueryResponseKind::ViewState(result) =
query_view_method_response.kind
{
eprintln!("Contract state (values):");
println!(
"{}",
serde_json::to_string_pretty(&result.values)?
);
eprintln!(
"\nContract state (proof):\n{:#?}\n",
&result.proof
);
} else {
return Err(color_eyre::Report::msg("Error call result".to_string()));
};
Ok(())
}
});

Ok(Self(crate::network_view_at_block::ArgsForViewContext {
config: previous_context.global_context.config,
interacting_with_account_ids: vec![previous_context.contract_account_id],
on_after_getting_block_reference_callback,
}))
}
}

impl From<AsJsonContext> for crate::network_view_at_block::ArgsForViewContext {
fn from(item: AsJsonContext) -> Self {
item.0
}
}
Loading
Loading