diff --git a/Cargo.lock b/Cargo.lock index 4193c4bd693..149d83fc271 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3430,6 +3430,7 @@ dependencies = [ "iroha_smart_contract_derive", "iroha_smart_contract_utils", "parity-scale-codec", + "serde_json", "test_samples", "trybuild", "webassembly-test", diff --git a/client/tests/integration/multisig.rs b/client/tests/integration/multisig.rs index 2178f42fa2a..4eee3603c77 100644 --- a/client/tests/integration/multisig.rs +++ b/client/tests/integration/multisig.rs @@ -80,7 +80,8 @@ fn mutlisig() -> Result<()> { .map(Register::account), )?; - let call_trigger = ExecuteTrigger::new(multisig_register_trigger_id).with_args(&args); + let call_trigger = ExecuteTrigger::new(multisig_register_trigger_id) + .with_args(serde_json::to_value(&args).unwrap()); test_client.submit_blocking(call_trigger)?; // Check that multisig account exist @@ -107,7 +108,8 @@ fn mutlisig() -> Result<()> { if let Some((signatory, key_pair)) = signatories_iter.next() { let args = MultisigArgs::Instructions(isi); - let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args); + let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()) + .with_args(serde_json::to_value(&args).unwrap()); test_client.submit_transaction_blocking( &TransactionBuilder::new(test_client.chain.clone(), signatory) .with_instructions([call_trigger]) @@ -125,7 +127,8 @@ fn mutlisig() -> Result<()> { for (signatory, key_pair) in signatories_iter { let args = MultisigArgs::Vote(isi_hash); - let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args); + let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()) + .with_args(serde_json::to_value(&args).unwrap()); test_client.submit_transaction_blocking( &TransactionBuilder::new(test_client.chain.clone(), signatory) .with_instructions([call_trigger]) diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 3a5301c67f6..9d4411fcd0e 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -259,7 +259,7 @@ fn permissions_differ_not_only_by_names() { .submit_blocking(SetKeyValue::asset( mouse_hat_id, Name::from_str("color").expect("Valid"), - "red".parse::().expect("Valid"), + "red", )) .expect("Failed to modify Mouse's hats"); @@ -268,7 +268,7 @@ fn permissions_differ_not_only_by_names() { let set_shoes_color = SetKeyValue::asset( mouse_shoes_id.clone(), Name::from_str("color").expect("Valid"), - "yellow".parse::().expect("Valid"), + "yellow", ); let _err = client .submit_blocking(set_shoes_color.clone()) @@ -316,11 +316,12 @@ fn stored_vs_granted_permission_payload() -> Result<()> { .expect("Failed to register mouse"); // Allow alice to mint mouse asset and mint initial value - let value_json = JsonString::from_string_unchecked(format!( + let value_json = JsonValue::from_string(format!( // NOTE: Permissions is created explicitly as a json string to introduce additional whitespace // This way, if the executor compares permissions just as JSON strings, the test will fail r##"{{ "asset" : "xor#wonderland#{mouse_id}" }}"## - )); + )) + .expect("input is a valid JSON"); let mouse_asset = AssetId::new(asset_definition_id, mouse_id.clone()); let allow_alice_to_set_key_value_in_mouse_asset = Grant::account_permission( @@ -336,11 +337,7 @@ fn stored_vs_granted_permission_payload() -> Result<()> { .expect("Failed to grant permission to alice."); // Check that alice can indeed mint mouse asset - let set_key_value = SetKeyValue::asset( - mouse_asset, - Name::from_str("color")?, - "red".parse::().expect("Valid"), - ); + let set_key_value = SetKeyValue::asset(mouse_asset, Name::from_str("color")?, "red"); iroha .submit_blocking(set_key_value) .expect("Failed to mint asset for mouse."); diff --git a/client/tests/integration/queries/smart_contract.rs b/client/tests/integration/queries/smart_contract.rs index 9827599fc72..303aeef36d6 100644 --- a/client/tests/integration/queries/smart_contract.rs +++ b/client/tests/integration/queries/smart_contract.rs @@ -22,11 +22,12 @@ fn live_query_is_dropped_after_smart_contract_end() -> Result<()> { client.build_transaction(WasmSmartContract::from_compiled(wasm), Metadata::default()); client.submit_transaction_blocking(&transaction)?; - let metadata_value: JsonString = client.query_single(FindAccountMetadata::new( - client.account.clone(), - Name::from_str("cursor").unwrap(), - ))?; - let asset_cursor = metadata_value.try_into_any()?; + let asset_cursor = client + .query_single(FindAccountMetadata::new( + client.account.clone(), + Name::from_str("cursor").unwrap(), + ))? + .try_into_any()?; // here we are breaking the abstraction preventing us from using a cursor we pulled from the metadata let err = client diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index dc43608fcc6..a6563a0cb83 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -67,11 +67,7 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { test_client.submit_transaction_blocking(&grant_role_tx)?; // Alice modifies Mouse's metadata - let set_key_value = SetKeyValue::account( - mouse_id, - "key".parse::()?, - "value".parse::()?, - ); + let set_key_value = SetKeyValue::account(mouse_id, "key".parse::()?, "value"); test_client.submit_blocking(set_key_value)?; // Making request to find Alice's roles @@ -224,11 +220,7 @@ fn grant_revoke_role_permissions() -> Result<()> { .sign(mouse_keypair.private_key()); test_client.submit_transaction_blocking(&grant_role_tx)?; - let set_key_value = SetKeyValue::account( - mouse_id.clone(), - "key".parse()?, - "value".parse::()?, - ); + let set_key_value = SetKeyValue::account(mouse_id.clone(), "key".parse()?, "value"); let can_set_key_value_in_mouse = CanSetKeyValueInAccount { account: mouse_id.clone(), }; diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 315898caaae..4a7b2ec8a24 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -47,7 +47,7 @@ fn correct_pagination_assets_after_creating_new_one() { AssetDefinitionId::from_str(&format!("xor{i}#wonderland")).expect("Valid"); let asset_definition = AssetDefinition::store(asset_definition_id.clone()); let mut asset_metadata = Metadata::default(); - asset_metadata.insert(sort_by_metadata_key.clone(), i as u32); + asset_metadata.insert(sort_by_metadata_key.clone(), i); let asset = Asset::new( AssetId::new(asset_definition_id, account_id.clone()), AssetValue::Store(asset_metadata), diff --git a/client/tests/integration/transfer_domain.rs b/client/tests/integration/transfer_domain.rs index 0afb0ff0523..1403ef79ec1 100644 --- a/client/tests/integration/transfer_domain.rs +++ b/client/tests/integration/transfer_domain.rs @@ -13,7 +13,6 @@ use iroha_executor_data_model::permission::{ trigger::CanUnregisterUserTrigger, }; use iroha_genesis::GenesisBlock; -use iroha_primitives::json::JsonString; use test_network::{Peer as TestPeer, *}; use test_samples::{gen_account_in, ALICE_ID, BOB_ID, SAMPLE_GENESIS_ACCOUNT_ID}; use tokio::runtime::Runtime; @@ -74,8 +73,11 @@ fn domain_owner_domain_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can edit metadata in her domain let key: Name = "key".parse()?; - let value = JsonString::new("value"); - test_client.submit_blocking(SetKeyValue::domain(kingdom_id.clone(), key.clone(), value))?; + test_client.submit_blocking(SetKeyValue::domain( + kingdom_id.clone(), + key.clone(), + "value", + ))?; test_client.submit_blocking(RemoveKeyValue::domain(kingdom_id.clone(), key))?; // check that "alice@wonderland" as owner of domain can grant and revoke domain related permissions @@ -111,11 +113,10 @@ fn domain_owner_account_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can edit metadata of account in her domain let key: Name = "key".parse()?; - let value = JsonString::new("value"); test_client.submit_blocking(SetKeyValue::account( mad_hatter_id.clone(), key.clone(), - value, + "value", ))?; test_client.submit_blocking(RemoveKeyValue::account(mad_hatter_id.clone(), key))?; @@ -177,11 +178,10 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can edit metadata of asset definition in her domain let key: Name = "key".parse()?; - let value = JsonString::new("value"); test_client.submit_blocking(SetKeyValue::asset_definition( coin_id.clone(), key.clone(), - value, + "value", ))?; test_client.submit_blocking(RemoveKeyValue::asset_definition(coin_id.clone(), key))?; @@ -249,9 +249,12 @@ fn domain_owner_asset_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can edit metadata of store asset in her domain let key: Name = "key".parse()?; - let value = JsonString::new("value"); let bob_store_id = AssetId::new(store_id, bob_id.clone()); - test_client.submit_blocking(SetKeyValue::asset(bob_store_id.clone(), key.clone(), value))?; + test_client.submit_blocking(SetKeyValue::asset( + bob_store_id.clone(), + key.clone(), + "value", + ))?; test_client.submit_blocking(RemoveKeyValue::asset(bob_store_id.clone(), key))?; // check that "alice@wonderland" as owner of domain can grant and revoke asset related permissions in her domain diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index da5b457f25b..0dcaf00bfff 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -14,6 +14,7 @@ use iroha::{ use iroha_executor_data_model::permission::trigger::CanRegisterUserTrigger; use iroha_genesis::GenesisBlock; use iroha_logger::info; +use serde_json::json; use test_network::{Peer as TestPeer, *}; use test_samples::ALICE_ID; use tokio::runtime::Runtime; @@ -458,11 +459,7 @@ fn trigger_in_genesis_using_base64() -> Result<()> { // Executing trigger test_client - .submit_blocking(SetKeyValue::trigger( - trigger_id.clone(), - "VAL".parse()?, - 1_u32, - )) + .submit_blocking(SetKeyValue::trigger(trigger_id.clone(), "VAL".parse()?, 1)) .unwrap(); let call_trigger = ExecuteTrigger::new(trigger_id); test_client.submit_blocking(call_trigger)?; @@ -685,7 +682,7 @@ fn call_execute_trigger_with_args() -> Result<()> { test_client.submit_blocking(Register::trigger(trigger))?; let args: MintRoseArgs = MintRoseArgs { val: 42 }; - let call_trigger = ExecuteTrigger::new(trigger_id).with_args(&args); + let call_trigger = ExecuteTrigger::new(trigger_id).with_args(json!(args)); test_client.submit_blocking(call_trigger)?; let new_value = get_asset_value(&mut test_client, asset_id); diff --git a/client/tests/integration/triggers/time_trigger.rs b/client/tests/integration/triggers/time_trigger.rs index fead56dc6bf..ad789fd5e64 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -203,11 +203,7 @@ fn pre_commit_trigger_should_be_executed() -> Result<()> { prev_value = new_value; // ISI just to create a new block - let sample_isi = SetKeyValue::account( - account_id.clone(), - "key".parse::()?, - "value".parse::()?, - ); + let sample_isi = SetKeyValue::account(account_id.clone(), "key".parse::()?, "value"); test_client.submit(sample_isi)?; } @@ -343,11 +339,7 @@ fn submit_sample_isi_on_every_block_commit( for _ in block_committed_event_listener.take(times) { std::thread::sleep(timeout); // ISI just to create a new block - let sample_isi = SetKeyValue::account( - account_id.clone(), - "key".parse::()?, - JsonString::new("value"), - ); + let sample_isi = SetKeyValue::account(account_id.clone(), "key".parse::()?, "value"); test_client.submit(sample_isi)?; } diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index 18f2a491cea..417918a1766 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -11,7 +11,7 @@ use erased_serde::Serialize; use error_stack::{fmt::ColorMode, IntoReportCompat, ResultExt}; use eyre::{eyre, Error, Result, WrapErr}; use iroha::{client::Client, config::Config, data_model::prelude::*}; -use iroha_primitives::{addr::SocketAddr, json::JsonString}; +use iroha_primitives::addr::SocketAddr; use thiserror::Error; /// Re-usable clap `--metadata ` (`-m`) argument. @@ -49,25 +49,29 @@ impl MetadataArgs { /// Re-usable clap `--value ` (`-v`) argument. /// Should be combined with `#[command(flatten)]` attr. -#[derive(clap::Args, Debug, Clone, PartialEq, Eq)] +#[derive(clap::Args, Debug, Clone)] pub struct MetadataValueArg { - /// Wrapper around `MetadataValue` to accept possible values and fallback to json. + /// Value in metadata key-value map, JSON /// - /// The following types are supported: - /// Numbers: decimal with optional point - /// Booleans: false/true - /// Objects: e.g. {"Vec":[{"String":"a"},{"String":"b"}]} - #[arg(short, long)] - value: JsonString, + /// Examples: + /// + /// - `--value false` + /// - `--value 42` + /// - `--value '"i am a string"'` + /// - `--value '[1, 2, 3]'` + #[arg(short, long, value_name("JSON VALUE"))] + value: JsonArg, } -impl FromStr for MetadataValueArg { - type Err = Error; +#[derive(Clone, Debug)] +struct JsonArg(serde_json::Value); - fn from_str(s: &str) -> Result { - Ok(MetadataValueArg { - value: JsonString::from_str(s)?, - }) +impl FromStr for JsonArg { + type Err = serde_json::Error; + + fn from_str(s: &str) -> std::result::Result { + let value = serde_json::from_str(s)?; + Ok(Self(value)) } } @@ -514,7 +518,7 @@ mod domain { key, value: MetadataValueArg { value }, } = self; - let set_key_value = SetKeyValue::domain(id, key, value); + let set_key_value = SetKeyValue::domain(id, key, value.0); submit([set_key_value], Metadata::default(), context) .wrap_err("Failed to submit Set instruction") } @@ -967,7 +971,7 @@ mod asset { value: MetadataValueArg { value }, } = self; - let set = iroha::data_model::isi::SetKeyValue::asset(asset_id, key, value); + let set = iroha::data_model::isi::SetKeyValue::asset(asset_id, key, value.0); submit([set], Metadata::default(), context)?; Ok(()) } @@ -1199,30 +1203,34 @@ mod json { } #[cfg(test)] mod tests { - use std::str::FromStr; + use clap::Parser; + use serde_json::json; use super::*; #[test] - fn parse_value_arg_cases() { - macro_rules! case { - ($input:expr, $expected:expr) => { - let MetadataValueArg { value } = - MetadataValueArg::from_str($input).expect("should not fail with valid input"); - assert_eq!(value, $expected); - }; + fn parse_metadata_value_arg() { + #[derive(Parser)] + struct Args { + #[command(flatten)] + value: MetadataValueArg, } - // Boolean values - case!("true", JsonString::new(true)); - case!("false", JsonString::new(false)); - - // Numeric values - case!("\"123\"", JsonString::new(numeric!(123))); - case!("\"123.0\"", JsonString::new(numeric!(123.0))); - - // JSON Value - let json_str = r#"{"Vec":[{"String":"a"},{"String":"b"}]}"#; - case!(json_str, serde_json::from_str(json_str).unwrap()); + for (as_str, as_value) in [ + ("null", json!(null)), + ("false", json!(false)), + ("\"i am arg\"", json!("i am arg")), + ("[3,2,1]", json!([3, 2, 1])), + ] { + let Args { + value: + MetadataValueArg { + value: JsonArg(value), + }, + } = Args::try_parse_from(["whatever", "--value", as_str]) + .expect("should parse input fine"); + + assert_eq!(value, as_value); + } } } diff --git a/core/src/query/store.rs b/core/src/query/store.rs index 4e4113698b9..de82960db29 100644 --- a/core/src/query/store.rs +++ b/core/src/query/store.rs @@ -296,7 +296,6 @@ mod tests { permission::Permission, query::parameters::{FetchSize, Pagination, QueryParams, Sorting}, }; - use iroha_primitives::json::JsonString; use nonzero_ext::nonzero; use test_samples::ALICE_ID; @@ -322,8 +321,7 @@ mod tests { }; // it's not important which type we use here, just to test the flow - let query_output = - (0..100).map(|_| Permission::new(String::default(), JsonString::from(false))); + let query_output = (0..100).map(|_| Permission::new(String::default(), false)); let query_output = crate::smartcontracts::query::apply_query_postprocessing( query_output, &query_params, diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 70f5e56a760..11b75a88cff 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -491,7 +491,7 @@ pub mod query { }, }, }; - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use super::*; use crate::{smartcontracts::ValidQuery, state::StateReadOnly}; @@ -546,7 +546,7 @@ pub mod query { impl ValidSingularQuery for FindAccountMetadata { #[metrics(+"find_account_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 1150f6b48b6..d7c03402072 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -432,7 +432,7 @@ pub mod query { }, }, }; - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use super::*; use crate::{smartcontracts::ValidQuery, state::StateReadOnly}; @@ -504,7 +504,7 @@ pub mod query { impl ValidSingularQuery for FindAssetMetadata { #[metrics(+"find_asset_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; let asset = state_ro.world().asset(id).map_err(|asset_err| { diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index fc1a223f0a5..f90b1d1eb41 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -398,7 +398,7 @@ pub mod query { predicate::{predicate_atoms::domain::DomainPredicateBox, CompoundPredicate}, }, }; - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use super::*; use crate::{smartcontracts::ValidQuery, state::StateReadOnly}; @@ -420,7 +420,7 @@ pub mod query { impl ValidSingularQuery for FindDomainMetadata { #[metrics(+"find_domain_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); @@ -434,7 +434,7 @@ pub mod query { impl ValidSingularQuery for FindAssetDefinitionMetadata { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index e1d92e2a97d..439dfd7db43 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -229,6 +229,7 @@ mod tests { use core::str::FromStr as _; use std::sync::Arc; + use serde_json::json; use test_samples::{ gen_account_in, ALICE_ID, SAMPLE_GENESIS_ACCOUNT_ID, SAMPLE_GENESIS_ACCOUNT_KEYPAIR, }; @@ -270,14 +271,14 @@ mod tests { let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland")?; let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let key = Name::from_str("Bytes")?; - SetKeyValue::asset(asset_id.clone(), key.clone(), vec![1_u32, 2_u32, 3_u32]) + SetKeyValue::asset(asset_id.clone(), key.clone(), vec![1, 2, 3]) .execute(&account_id, &mut state_transaction)?; let asset = state_transaction.world.asset(&asset_id)?; let AssetValue::Store(store) = &asset.value else { panic!("expected store asset"); }; let value = store.get(&key).cloned(); - assert_eq!(value, Some(vec![1_u32, 2_u32, 3_u32,].into())); + assert_eq!(value, Some(json!([1, 2, 3]).into())); Ok(()) } @@ -289,12 +290,12 @@ mod tests { let mut state_transaction = state_block.transaction(); let account_id = ALICE_ID.clone(); let key = Name::from_str("Bytes")?; - SetKeyValue::account(account_id.clone(), key.clone(), vec![1_u32, 2_u32, 3_u32]) + SetKeyValue::account(account_id.clone(), key.clone(), vec![1, 2, 3]) .execute(&account_id, &mut state_transaction)?; let bytes = state_transaction .world .map_account(&account_id, |account| account.metadata().get(&key).cloned())?; - assert_eq!(bytes, Some(vec![1_u32, 2_u32, 3_u32,].into())); + assert_eq!(bytes, Some(json!([1, 2, 3]).into())); Ok(()) } @@ -307,19 +308,15 @@ mod tests { let definition_id = AssetDefinitionId::from_str("rose#wonderland")?; let account_id = ALICE_ID.clone(); let key = Name::from_str("Bytes")?; - SetKeyValue::asset_definition( - definition_id.clone(), - key.clone(), - vec![1_u32, 2_u32, 3_u32], - ) - .execute(&account_id, &mut state_transaction)?; + SetKeyValue::asset_definition(definition_id.clone(), key.clone(), vec![1, 2, 3]) + .execute(&account_id, &mut state_transaction)?; let value = state_transaction .world .asset_definition(&definition_id)? .metadata() .get(&key) .cloned(); - assert_eq!(value, Some(vec![1_u32, 2_u32, 3_u32,].into())); + assert_eq!(value, Some(json!([1, 2, 3]).into())); Ok(()) } @@ -332,7 +329,7 @@ mod tests { let domain_id = DomainId::from_str("wonderland")?; let account_id = ALICE_ID.clone(); let key = Name::from_str("Bytes")?; - SetKeyValue::domain(domain_id.clone(), key.clone(), vec![1_u32, 2_u32, 3_u32]) + SetKeyValue::domain(domain_id.clone(), key.clone(), vec![1, 2, 3]) .execute(&account_id, &mut state_transaction)?; let bytes = state_transaction .world @@ -340,7 +337,7 @@ mod tests { .metadata() .get(&key) .cloned(); - assert_eq!(bytes, Some(vec![1_u32, 2_u32, 3_u32,].into())); + assert_eq!(bytes, Some(json!([1, 2, 3]).into())); Ok(()) } diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 172e831206a..cbbe0362f56 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -26,29 +26,29 @@ pub trait SortableQueryOutput { /// Get the sorting key for the output, from metadata /// /// If the type doesn't have metadata or metadata key doesn't exist - return None - fn get_metadata_sorting_key(&self, key: &Name) -> Option; + fn get_metadata_sorting_key(&self, key: &Name) -> Option; } impl SortableQueryOutput for Account { - fn get_metadata_sorting_key(&self, key: &Name) -> Option { + fn get_metadata_sorting_key(&self, key: &Name) -> Option { self.metadata.get(key).cloned() } } impl SortableQueryOutput for Domain { - fn get_metadata_sorting_key(&self, key: &Name) -> Option { + fn get_metadata_sorting_key(&self, key: &Name) -> Option { self.metadata.get(key).cloned() } } impl SortableQueryOutput for AssetDefinition { - fn get_metadata_sorting_key(&self, key: &Name) -> Option { + fn get_metadata_sorting_key(&self, key: &Name) -> Option { self.metadata.get(key).cloned() } } impl SortableQueryOutput for Asset { - fn get_metadata_sorting_key(&self, key: &Name) -> Option { + fn get_metadata_sorting_key(&self, key: &Name) -> Option { match &self.value { AssetValue::Numeric(_) => None, AssetValue::Store(metadata) => metadata.get(key).cloned(), @@ -57,55 +57,55 @@ impl SortableQueryOutput for Asset { } impl SortableQueryOutput for Role { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for RoleId { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for TransactionQueryOutput { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for Peer { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for Permission { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for Trigger { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for TriggerId { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for iroha_data_model::block::SignedBlock { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } impl SortableQueryOutput for iroha_data_model::block::BlockHeader { - fn get_metadata_sorting_key(&self, _key: &Name) -> Option { + fn get_metadata_sorting_key(&self, _key: &Name) -> Option { None } } @@ -140,7 +140,7 @@ where let output = match &sorting.sort_by_metadata_key { Some(key) => { // if sorting was requested, we need to retrieve all the results first - let mut pairs: Vec<(Option, I::Item)> = iter + let mut pairs: Vec<(Option, I::Item)> = iter .map(|value| { let key = value.get_metadata_sorting_key(key); (key, value) @@ -362,7 +362,7 @@ mod tests { parameter::TransactionParameters, query::{error::FindError, predicate::CompoundPredicate}, }; - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use nonzero_ext::nonzero; use test_samples::{gen_account_in, ALICE_ID, ALICE_KEYPAIR}; use tokio::test; @@ -395,10 +395,7 @@ mod tests { AssetDefinition::numeric(asset_definition_id.clone()).build(&ALICE_ID); let mut store = Metadata::default(); - store.insert( - Name::from_str("Bytes").expect("Valid"), - vec![1_u32, 2_u32, 3_u32], - ); + store.insert(Name::from_str("Bytes").expect("Valid"), vec![1, 2, 3]); let asset_id = AssetId::new(asset_definition_id, account.id().clone()); let asset = Asset::new(asset_id, AssetValue::Store(store)); @@ -407,7 +404,7 @@ mod tests { fn world_with_test_account_with_metadata() -> Result { let mut metadata = Metadata::default(); - metadata.insert(Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32]); + metadata.insert(Name::from_str("Bytes")?, vec![1, 2, 3]); let domain = Domain::new(DomainId::from_str("wonderland")?).build(&ALICE_ID); let account = Account::new(ALICE_ID.clone()) @@ -500,7 +497,7 @@ mod tests { let asset_id = AssetId::new(asset_definition_id, ALICE_ID.clone()); let bytes = FindAssetMetadata::new(asset_id, Name::from_str("Bytes")?).execute(&state.view())?; - assert_eq!(JsonString::from(vec![1_u32, 2_u32, 3_u32,]), bytes,); + assert_eq!(JsonValue::from(vec![1, 2, 3]), bytes); Ok(()) } @@ -512,7 +509,7 @@ mod tests { let bytes = FindAccountMetadata::new(ALICE_ID.clone(), Name::from_str("Bytes")?) .execute(&state.view())?; - assert_eq!(JsonString::from(vec![1_u32, 2_u32, 3_u32,]), bytes,); + assert_eq!(JsonValue::from(vec![1, 2, 3]), bytes,); Ok(()) } @@ -654,7 +651,7 @@ mod tests { let kura = Kura::blank_kura_for_testing(); let state = { let mut metadata = Metadata::default(); - metadata.insert(Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32]); + metadata.insert(Name::from_str("Bytes")?, vec![1, 2, 3]); let domain = Domain::new(DomainId::from_str("wonderland")?) .with_metadata(metadata) .build(&ALICE_ID); @@ -672,7 +669,7 @@ mod tests { let domain_id = DomainId::from_str("wonderland")?; let key = Name::from_str("Bytes")?; let bytes = FindDomainMetadata::new(domain_id, key).execute(&state.view())?; - assert_eq!(JsonString::from(vec![1_u32, 2_u32, 3_u32,]), bytes,); + assert_eq!(JsonValue::from(vec![1, 2, 3]), bytes,); Ok(()) } } diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index 7aeef5148e7..1bb012d11db 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -328,7 +328,7 @@ pub mod query { }, trigger::{Trigger, TriggerId}, }; - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use super::*; use crate::{ @@ -379,7 +379,7 @@ pub mod query { impl ValidSingularQuery for FindTriggerMetadata { #[metrics(+"find_trigger_key_value_by_id_and_key")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { let id = &self.id; let key = &self.key; iroha_logger::trace!(%id, %key); diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index fffe50369a7..021adfbf12d 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -25,7 +25,7 @@ pub mod isi { query::error::FindError, Level, }; - use iroha_primitives::{json::JsonString, unique_vec::PushResult}; + use iroha_primitives::{json::JsonValue, unique_vec::PushResult}; use super::*; @@ -360,7 +360,7 @@ pub mod isi { CustomParameter { id: next.id.clone(), - payload: JsonString::default(), + payload: JsonValue::default(), } }); diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index 92a841a0e61..823eae88933 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -3,7 +3,7 @@ use getset::Getters; use iroha_data_model_derive::{model, EventSet, HasOrigin}; -use iroha_primitives::{json::JsonString, numeric::Numeric}; +use iroha_primitives::{json::JsonValue, numeric::Numeric}; pub use self::model::*; use super::*; @@ -60,7 +60,7 @@ mod model { pub struct MetadataChanged { pub target: Id, pub key: Name, - pub value: JsonString, + pub value: JsonValue, } /// Event @@ -653,7 +653,7 @@ impl MetadataChanged { } /// Getter for `value` - pub fn value(&self) -> &JsonString { + pub fn value(&self) -> &JsonValue { &self.value } } diff --git a/data_model/src/events/execute_trigger.rs b/data_model/src/events/execute_trigger.rs index 59324f91329..b185cfa5749 100644 --- a/data_model/src/events/execute_trigger.rs +++ b/data_model/src/events/execute_trigger.rs @@ -35,7 +35,7 @@ mod model { pub authority: AccountId, /// Args to pass for trigger execution #[getset(skip)] - pub args: Option, + pub args: Option, } /// Filter for trigger execution [`Event`] @@ -64,8 +64,8 @@ mod model { impl ExecuteTriggerEvent { /// Args to pass for trigger execution - pub fn args(&self) -> Option<&JsonString> { - self.args.as_ref() + pub fn args(&self) -> Option<&JsonValue> { + self.args.as_ref().map(|wrapped| &wrapped.value) } } diff --git a/data_model/src/executor.rs b/data_model/src/executor.rs index f03775cbe50..071f3531bdb 100644 --- a/data_model/src/executor.rs +++ b/data_model/src/executor.rs @@ -6,7 +6,7 @@ use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; use std::collections::BTreeSet; use iroha_data_model_derive::model; -use iroha_primitives::json::JsonString; +use iroha_primitives::json::JsonValue; use iroha_schema::{Ident, IntoSchema}; pub use self::model::*; @@ -89,7 +89,7 @@ mod model { /// Ids of permission tokens supported by the executor. pub permissions: BTreeSet, /// Schema of executor defined data types (instructions, parameters, permissions) - pub schema: JsonString, + pub schema: JsonValue, } // TODO: Client doesn't need structures defined inside this macro. When dynamic linking is @@ -105,7 +105,7 @@ impl ExecutorDataModel { } /// Getter - pub fn schema(&self) -> &JsonString { + pub fn schema(&self) -> &JsonValue { &self.schema } } diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index 74a2ac1fbec..be4ca9d4cd6 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -189,7 +189,7 @@ impl BuiltInInstruction for InstructionBox { } mod transparent { - use iroha_primitives::json::JsonString; + use iroha_primitives::json::JsonValue; use super::*; use crate::{account::NewAccount, domain::NewDomain, metadata::Metadata}; @@ -271,13 +271,13 @@ mod transparent { /// Key. pub key: Name, /// Value. - pub value: JsonString, + pub value: JsonValue, } } impl SetKeyValue { /// Constructs a new [`SetKeyValue`] for a [`Domain`] with the given `key` and `value`. - pub fn domain(domain_id: DomainId, key: Name, value: impl Into) -> Self { + pub fn domain(domain_id: DomainId, key: Name, value: impl Into) -> Self { Self { object: domain_id, key, @@ -288,7 +288,7 @@ mod transparent { impl SetKeyValue { /// Constructs a new [`SetKeyValue`] for an [`Account`] with the given `key` and `value`. - pub fn account(account_id: AccountId, key: Name, value: impl Into) -> Self { + pub fn account(account_id: AccountId, key: Name, value: impl Into) -> Self { Self { object: account_id, key, @@ -302,7 +302,7 @@ mod transparent { pub fn asset_definition( asset_definition_id: AssetDefinitionId, key: Name, - value: impl Into, + value: impl Into, ) -> Self { Self { object: asset_definition_id, @@ -314,7 +314,7 @@ mod transparent { impl SetKeyValue { /// Constructs a new [`SetKeyValue`] for an [`Asset`] with the given `key` and `value`. - pub fn asset(asset_id: AssetId, key: Name, value: impl Into) -> Self { + pub fn asset(asset_id: AssetId, key: Name, value: impl Into) -> Self { Self { object: asset_id, key, @@ -325,7 +325,7 @@ mod transparent { impl SetKeyValue { /// Constructs a new [`SetKeyValue`] for a [`Trigger`] with the given `key` and `value`. - pub fn trigger(trigger_id: TriggerId, key: Name, value: impl Into) -> Self { + pub fn trigger(trigger_id: TriggerId, key: Name, value: impl Into) -> Self { Self { object: trigger_id, key, @@ -930,7 +930,7 @@ mod transparent { /// Id of a trigger to execute pub trigger: TriggerId, /// Arguments to trigger execution - pub args: Option, + pub args: Option, } } @@ -945,8 +945,8 @@ mod transparent { /// Add trigger execution args #[must_use] - pub fn with_args(mut self, args: &T) -> Self { - self.args = Some(JsonString::new(args)); + pub fn with_args(mut self, args: impl Into) -> Self { + self.args = Some(JsonValueWrap { value: args.into() }); self } } @@ -992,13 +992,13 @@ mod transparent { #[display(fmt = "CUSTOM({payload})")] pub struct CustomInstruction { /// Custom payload - pub payload: JsonString, + pub payload: JsonValue, } } impl CustomInstruction { /// Constructor - pub fn new(payload: impl Into) -> Self { + pub fn new(payload: impl Into) -> Self { Self { payload: payload.into(), } diff --git a/data_model/src/metadata.rs b/data_model/src/metadata.rs index 998212f6fbe..186173f1189 100644 --- a/data_model/src/metadata.rs +++ b/data_model/src/metadata.rs @@ -7,7 +7,7 @@ use core::borrow::Borrow; use std::collections::BTreeMap; use iroha_data_model_derive::model; -use iroha_primitives::json::JsonString; +use iroha_primitives::json::JsonValue; pub use self::model::*; use crate::prelude::Name; @@ -46,7 +46,7 @@ mod model { #[serde(transparent)] #[display(fmt = "Metadata")] #[allow(clippy::multiple_inherent_impl)] - pub struct Metadata(pub(super) BTreeMap); + pub struct Metadata(pub(super) BTreeMap); } impl Metadata { @@ -56,13 +56,13 @@ impl Metadata { } /// Iterate over key/value pairs stored in the internal map. - pub fn iter(&self) -> impl ExactSizeIterator { + pub fn iter(&self) -> impl ExactSizeIterator { self.0.iter() } /// Get the `Some(&Value)` associated to `key`. Return `None` if not found. #[inline] - pub fn get(&self, key: &K) -> Option<&JsonString> + pub fn get(&self, key: &K) -> Option<&JsonValue> where Name: Borrow, { @@ -71,7 +71,7 @@ impl Metadata { /// Insert [`Value`] under the given key. Returns `Some(value)` /// if the value was already present, `None` otherwise. - pub fn insert(&mut self, key: Name, value: impl Into) -> Option { + pub fn insert(&mut self, key: Name, value: impl Into) -> Option { self.0.insert(key, value.into()) } } @@ -82,7 +82,7 @@ impl Metadata { /// `Some(value)` at the key if the key was previously in the /// map, else `None`. #[inline] - pub fn remove(&mut self, key: &K) -> Option + pub fn remove(&mut self, key: &K) -> Option where Name: Borrow, { diff --git a/data_model/src/parameter.rs b/data_model/src/parameter.rs index 546bb4a2455..fb78d6be013 100644 --- a/data_model/src/parameter.rs +++ b/data_model/src/parameter.rs @@ -6,7 +6,7 @@ use core::{num::NonZeroU64, time::Duration}; use std::collections::btree_map; use iroha_data_model_derive::model; -use iroha_primitives::json::JsonString; +use iroha_primitives::json::JsonValue; use nonzero_ext::nonzero; pub use self::model::*; @@ -218,7 +218,7 @@ mod model { /// /// It is JSON-encoded, and its structure must correspond to the structure of /// the type defined in [`crate::executor::ExecutorDataModel`]. - pub payload: JsonString, + pub payload: JsonValue, } /// Set of all current blockchain parameter values @@ -463,7 +463,7 @@ impl CustomParameterId { impl CustomParameter { /// Constructor - pub fn new(id: CustomParameterId, payload: impl Into) -> Self { + pub fn new(id: CustomParameterId, payload: impl Into) -> Self { Self { id, payload: payload.into(), @@ -472,7 +472,7 @@ impl CustomParameter { /// Getter // TODO: derive with getset once FFI impl is fixed - pub fn payload(&self) -> &JsonString { + pub fn payload(&self) -> &JsonValue { &self.payload } } diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index 04bab19588e..3e811e9f69d 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -5,7 +5,7 @@ use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; use std::collections::BTreeSet; use iroha_data_model_derive::model; -use iroha_primitives::json::JsonString; +use iroha_primitives::json::JsonValue; use iroha_schema::{Ident, IntoSchema}; pub use self::model::*; @@ -45,13 +45,13 @@ mod model { /// /// It is JSON-encoded, and its structure must correspond to the structure of /// the type defined in [`crate::executor::ExecutorDataModel`]. - pub payload: JsonString, + pub payload: JsonValue, } } impl Permission { /// Constructor - pub fn new(name: Ident, payload: impl Into) -> Self { + pub fn new(name: Ident, payload: impl Into) -> Self { Self { name, payload: payload.into(), @@ -65,7 +65,7 @@ impl Permission { /// Getter // TODO: derive with getset once FFI impl is fixed - pub fn payload(&self) -> &JsonString { + pub fn payload(&self) -> &JsonValue { &self.payload } } diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index 9f9ac391426..b2beeafb8e1 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -9,7 +9,7 @@ use derive_more::Constructor; use iroha_crypto::{PublicKey, SignatureOf}; use iroha_data_model_derive::model; use iroha_macro::FromVariant; -use iroha_primitives::{json::JsonString, numeric::Numeric}; +use iroha_primitives::{json::JsonValue, numeric::Numeric}; use iroha_schema::IntoSchema; use iroha_version::prelude::*; use parameters::{ForwardCursor, QueryParams, MAX_FETCH_SIZE}; @@ -155,7 +155,7 @@ mod model { pub enum SingularQueryOutputBox { Numeric(Numeric), ExecutorDataModel(crate::executor::ExecutorDataModel), - JsonString(JsonString), + JsonValue(JsonValue), Trigger(crate::trigger::Trigger), Parameters(Parameters), Transaction(TransactionQueryOutput), @@ -565,15 +565,15 @@ impl_iter_queries! { } impl_singular_queries! { - FindAccountMetadata => JsonString, + FindAccountMetadata => JsonValue, FindAssetQuantityById => Numeric, FindTotalAssetQuantityByAssetDefinitionId => Numeric, - FindAssetMetadata => JsonString, - FindAssetDefinitionMetadata => JsonString, - FindDomainMetadata => JsonString, + FindAssetMetadata => JsonValue, + FindAssetDefinitionMetadata => JsonValue, + FindDomainMetadata => JsonValue, FindParameters => crate::parameter::Parameters, FindTriggerById => crate::trigger::Trigger, - FindTriggerMetadata => JsonString, + FindTriggerMetadata => JsonValue, FindTransactionByHash => TransactionQueryOutput, FindBlockHeaderByHash => crate::block::BlockHeader, FindExecutorDataModel => crate::executor::ExecutorDataModel, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 2335b2e4930..ab514f9a475 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -1358,7 +1358,7 @@ "Struct": [ { "name": "payload", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -1370,7 +1370,7 @@ }, { "name": "payload", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -1698,7 +1698,7 @@ }, { "name": "args", - "type": "Option" + "type": "Option" } ] }, @@ -1714,7 +1714,7 @@ }, { "name": "args", - "type": "Option" + "type": "Option" } ] }, @@ -1767,7 +1767,7 @@ }, { "name": "schema", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2374,7 +2374,15 @@ "IpfsPath": "String", "Ipv4Addr": "Array", "Ipv6Addr": "Array", - "JsonString": "String", + "JsonValue": "JsonValue", + "JsonValueWrap": { + "Struct": [ + { + "name": "value", + "type": "JsonValue" + } + ] + }, "Level": { "Enum": [ { @@ -2447,7 +2455,7 @@ "MerkleTree": { "Vec": "HashOf" }, - "Metadata": "SortedMap", + "Metadata": "SortedMap", "MetadataChanged": { "Struct": [ { @@ -2460,7 +2468,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2476,7 +2484,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2492,7 +2500,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2508,7 +2516,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2524,7 +2532,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -2711,8 +2719,8 @@ "Option": { "Option": "IpfsPath" }, - "Option": { - "Option": "JsonString" + "Option": { + "Option": "JsonValueWrap" }, "Option": { "Option": "Name" @@ -2911,7 +2919,7 @@ }, { "name": "payload", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -3837,7 +3845,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -3853,7 +3861,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -3869,7 +3877,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -3885,7 +3893,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -3901,7 +3909,7 @@ }, { "name": "value", - "type": "JsonString" + "type": "JsonValue" } ] }, @@ -4089,9 +4097,9 @@ "type": "ExecutorDataModel" }, { - "tag": "JsonString", + "tag": "JsonValue", "discriminant": 2, - "type": "JsonString" + "type": "JsonValue" }, { "tag": "Trigger", @@ -4202,10 +4210,10 @@ "value": "CustomParameter" } }, - "SortedMap": { + "SortedMap": { "Map": { "key": "Name", - "value": "JsonString" + "value": "JsonValue" } }, "SortedVec": { diff --git a/primitives/src/json.rs b/primitives/src/json.rs index f36be4d5f92..e431c4f35d6 100644 --- a/primitives/src/json.rs +++ b/primitives/src/json.rs @@ -7,7 +7,7 @@ use alloc::{ string::{String, ToString}, vec::Vec, }; -use core::str::FromStr; +// use core::str::FromStr; #[cfg(feature = "std")] use std::{ string::{String, ToString}, @@ -15,162 +15,273 @@ use std::{ }; use derive_more::Display; -use iroha_schema::IntoSchema; +use iroha_schema::{IntoSchema, Metadata, TypeId}; use parity_scale_codec::{Decode, Encode}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; -/// A valid `JsonString` that consists of valid String of Json type -#[derive(Debug, Display, Clone, PartialOrd, PartialEq, Ord, Eq, IntoSchema, Encode, Decode)] -#[display(fmt = "{_0}")] -pub struct JsonString(String); +/// A newtype of [`serde_json::Value`] with the following features: +/// +/// - Delegates [`Serialize`]/[`Deserialize`] to the [`Value`] itself +/// - Delegates [`Encode`]/[`Decode`] to the JSON-serialized string of [`Value`] +/// - Delegates [`PartialOrd`]/[`Ord`]/[`PartialEq`]/[`Eq`] to the JSON-serialized string of [`Value`] +/// +/// It is a core type in the schema, i.e. SDKs should handle it as a special case. +/// +/// It **should not be** wrapped into [`Option`], as its [`None`] value overlaps with [`Value::Null`] in JSON (both are `null`). +/// Use [`JsonValueWrap`] with [`Option`] instead. +#[derive(Debug, Display, Clone, Serialize, TypeId)] +#[display(fmt = "{value}")] +#[serde(transparent)] +pub struct JsonValue { + value: Value, + #[serde(skip)] + stringified: String, +} -impl JsonString { - /// Constructs [`JsonString`] - /// # Errors - /// - /// - Serialization can fail if T's implementation of Serialize decides to fail, - /// - or if T contains a map with non-string keys. - // Todo: Doesn't remove extra spaces in if `&str` is an object - pub fn new(payload: T) -> Self { - candidate::JsonCandidate::new(payload).try_into().unwrap() +impl JsonValue { + /// Construct from [`serde_json::Value`] + pub fn from_value(value: Value) -> Self { + let string = value.to_string(); + Self { + value, + stringified: string, + } } - /// Tries cast [`JsonString`] to any value. + /// Construct from a JSON string, ensuring it is a valid JSON. /// - /// # Errors - /// - if invalid representation of `T` - pub fn try_into_any(&self) -> Result { - serde_json::from_str(&self.0) + /// While this method parses the string into [`Value`], it **guarantees** that + /// the original string is used for encoding, ord/eq impls, and is returned from [`Self::as_str`]. + pub fn from_string(raw: impl Into) -> serde_json::Result { + let stringified = raw.into(); + let value = serde_json::from_str(&stringified)?; + Ok(Self { value, stringified }) } - /// Create without checking whether the input is a valid JSON string. - /// - /// The caller must guarantee that the value is valid. - pub fn from_string_unchecked(value: String) -> Self { - Self(value) + /// Borrow the value itself + pub fn get(&self) -> &Value { + &self.value + } + + /// Consume the container and own the actual [`Value`]. + pub fn into_value(self) -> Value { + self.value + } + + /// Essentially a chaining shortcut for [`serde_json::from_value`]. + pub fn try_into_any(self) -> serde_json::Result { + serde_json::from_value(self.value) } - /// Getter for [`JsonString`] - pub fn get(&self) -> &String { - &self.0 + /// Borrow the string representation of the value + pub fn as_str(&self) -> &str { + &self.stringified } } -impl<'de> serde::de::Deserialize<'de> for JsonString { +// Note: need a custom impl so that `stringified` is filled on deserialization +impl<'de> Deserialize<'de> for JsonValue { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let json = Value::deserialize(deserializer)?; - Ok(Self(json.to_string())) + let value = Value::deserialize(deserializer)?; + Ok(Self::from_value(value)) } } -impl serde::ser::Serialize for JsonString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let json: Value = serde_json::from_str(&self.0).map_err(serde::ser::Error::custom)?; - json.serialize(serializer) +impl IntoSchema for JsonValue { + fn type_name() -> iroha_schema::Ident { + Self::id() } -} -impl From<&Value> for JsonString { - fn from(value: &Value) -> Self { - JsonString(value.to_string()) + fn update_schema_map(map: &mut iroha_schema::MetaMap) { + if !map.contains_key::() { + map.insert::(Metadata::JsonValue); + } } } -impl From for JsonString { - fn from(value: Value) -> Self { - JsonString(value.to_string()) +/// [`Value::Null`] +impl Default for JsonValue { + fn default() -> Self { + Self::from_value(Value::Null) } } -impl From for JsonString { - fn from(value: u32) -> Self { - JsonString::new(value) +impl From for JsonValue +where + T: Into, +{ + fn from(value: T) -> Self { + Self::from_value(value.into()) } } -impl From for JsonString { - fn from(value: u64) -> Self { - JsonString::new(value) +impl Encode for JsonValue { + fn encode_to(&self, dest: &mut T) { + self.stringified.encode_to(dest) } -} -impl From for JsonString { - fn from(value: f64) -> Self { - JsonString::new(value) + fn size_hint(&self) -> usize { + self.stringified.size_hint() } } -impl From for JsonString { - fn from(value: bool) -> Self { - JsonString::new(value) +impl Decode for JsonValue { + fn decode( + input: &mut I, + ) -> Result { + let string = String::decode(input)?; + let value = serde_json::from_str(&string).map_err(|_err| { + parity_scale_codec::Error::from( + "Could not decode `JsonValue`: string is not a valid JSON", + ) + })?; + Ok(Self { + value, + stringified: string, + }) } } -impl From<&str> for JsonString { - fn from(value: &str) -> Self { - value.parse::().expect("Impossible error") +impl PartialEq for JsonValue { + fn eq(&self, other: &Self) -> bool { + self.stringified.eq(&other.stringified) } } -impl + Serialize> From> for JsonString { - fn from(value: Vec) -> Self { - JsonString::new(value) - } -} +impl Eq for JsonValue {} -/// Removes extra spaces from object if `&str` is an object -impl FromStr for JsonString { - type Err = serde_json::Error; - - fn from_str(s: &str) -> Result { - if let Ok(value) = serde_json::from_str::(s) { - Ok(JsonString(value.to_string())) - } else { - let json_formatted_string = serde_json::to_string(s)?; - let value: Value = serde_json::from_str(&json_formatted_string)?; - Ok(JsonString(value.to_string())) - } +impl PartialOrd for JsonValue { + fn partial_cmp(&self, other: &Self) -> Option { + self.stringified.partial_cmp(&other.stringified) } } -impl Default for JsonString { - fn default() -> Self { - // NOTE: empty string isn't valid JSON - Self("null".to_string()) +impl Ord for JsonValue { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + self.stringified.cmp(&other.stringified) } } -impl AsRef for JsonString { - fn as_ref(&self) -> &str { - &self.0 - } +/// A wrapper around [`JsonValue`] to workaround issues with ambiguous serialisation of `Option`. +/// +/// The ambiguity comes from the fact that both `None` and `Some(null)` are serialised as `null`. To solve it, use +/// `Option`, so that the difference is clear: `null` vs `{ "value": null }` +#[derive( + Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, IntoSchema, +)] +pub struct JsonValueWrap { + /// The wrapped value + pub value: JsonValue, } -mod candidate { +#[cfg(test)] +mod tests { + use core::cmp::Ordering; + + use parity_scale_codec::DecodeAll; + use serde_json::json; + use super::*; - /// A candidate for a valid `JsonString`. - /// Is used for generalizing ser/de any types to `JsonString` and vise versa - #[derive(Serialize, Deserialize, Clone)] - pub(super) struct JsonCandidate(T); + #[test] + fn as_string_in_scale() { + for (value, as_str) in [ + (json!([1, 5, 2]), "[1,5,2]"), + (json!(null), "null"), + (json!(55.23), "55.23"), + (json!("i am data"), "\"i am data\""), + ] { + let expected_encoded_str = as_str.encode(); + + let value_encoded = JsonValue::from_value(value.clone()).encode(); + assert_eq!( + value_encoded, expected_encoded_str, + "value {value:?} is not encoded as `{as_str}`" + ); - impl JsonCandidate { - pub(super) fn new(value: T) -> Self { - JsonCandidate(value) + let value_decoded = JsonValue::decode(&mut expected_encoded_str.as_slice()) + .expect("should decode fine"); + assert_eq!(value_decoded, JsonValue::from_value(value)); } } - impl TryFrom> for JsonString { - type Error = serde_json::Error; - fn try_from(value: JsonCandidate) -> Result { - Ok(JsonString(serde_json::to_string(&value.0)?)) + #[test] + fn as_value_in_json() { + for input in [ + json!([1, 5, 2]), + json!(null), + json!(55.23), + json!("i am data"), + ] { + let value = JsonValue::from_value(input.clone()); + let value_str = serde_json::to_string(&value).unwrap(); + assert_eq!(value_str, input.to_string()); } } + + #[test] + fn sorts_values_by_their_str_impls() { + let input = vec![ + json!("Jack"), + json!("Brown"), + json!(412), + json!(null), + json!(["foo"]), + ]; + + let mut values: Vec<_> = input.into_iter().map(JsonValue::from_value).collect(); + values.sort(); + + let values: Vec<_> = values.into_iter().map(|x| x.get().clone()).collect(); + assert_eq!( + values, + vec![ + json!("Brown"), + json!("Jack"), + json!(412), + json!(["foo"]), + json!(null), + ] + ); + } + + #[test] + fn when_decoded_from_invalid_json_string() { + let invalid_input = "i am not JSON".encode(); + + let _err = JsonValue::decode(&mut invalid_input.as_slice()) + .expect_err("string is not a valid JSON"); + } + + #[test] + fn when_constructed_from_str_original_string_is_preserved_for_encoding() { + let source = "[1, 2, 3]"; + + let value = JsonValue::from_string(source).expect("input is a valid json"); + let whitespace_differ = JsonValue::from_string("[1, 2, 3]").expect("also a valid json"); + + assert_eq!(value.as_str(), source); + assert_eq!(value.get(), &json!([1, 2, 3])); + assert_ne!(value, whitespace_differ); + assert_ne!(value.cmp(&whitespace_differ), Ordering::Equal); + } + + #[test] + fn serialize_deserialize_encode_decode() { + let value = JsonValue::from_value(json!({ "foo": ["bar", false, 1234, null]})); + + let serialized = serde_json::to_string(&value).unwrap(); + assert_eq!(serialized, "{\"foo\":[\"bar\",false,1234,null]}"); + + let deserialized: JsonValue = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, value); + + let encoded = deserialized.encode(); + let decoded = JsonValue::decode_all(&mut encoded.as_slice()).unwrap(); + assert_eq!(decoded, value); + } } diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index 2f0c111ec2e..e50dbf01d1a 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -155,7 +155,7 @@ types!( Box>, Box, BTreeMap, - BTreeMap, + BTreeMap, BTreeSet, BTreeSet, BurnBox, @@ -277,7 +277,8 @@ types!( QueryWithFilter, QueryWithFilter, QueryWithParams, - JsonString, + JsonValue, + JsonValueWrap, Level, Log, MathError, @@ -313,7 +314,7 @@ types!( Option>, Option>, Option, - Option, + Option, Option, Option, Option, @@ -545,7 +546,7 @@ pub mod complete_data_model { addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, const_vec::ConstVec, conststr::ConstString, - json::JsonString, + json::{JsonValue, JsonValueWrap}, }; pub use iroha_schema::Compact; } @@ -735,4 +736,16 @@ mod tests { >::update_schema_map(&mut schemas); >::update_schema_map(&mut schemas); } + + #[test] + fn no_option_json_value() { + let map = super::build_schemas(); + + if map + .into_iter() + .any(|(_, (id, _))| id == "Option") + { + panic!("Using `Option` is not allowed. Use `Option` instead"); + } + } } diff --git a/schema/src/lib.rs b/schema/src/lib.rs index f3047a14aae..9d845815001 100644 --- a/schema/src/lib.rs +++ b/schema/src/lib.rs @@ -132,6 +132,8 @@ pub enum Metadata { String, /// Bool Bool, + /// JsonValue + JsonValue, /// Number with fixed decimal precision FixedPoint(FixedMeta), /// Array diff --git a/schema/src/serialize.rs b/schema/src/serialize.rs index 13f53a0a1ba..ec280d4070f 100644 --- a/schema/src/serialize.rs +++ b/schema/src/serialize.rs @@ -239,6 +239,7 @@ impl Serialize for WithContext<'_, '_, Metadata> { match self.data { Metadata::String => serializer.serialize_str(self.type_name(TypeId::of::())), Metadata::Bool => serializer.serialize_str(self.type_name(TypeId::of::())), + Metadata::JsonValue => serializer.serialize_str("JsonValue"), Metadata::Option(type_id) => { let mut map = serializer.serialize_map(Some(1))?; map.serialize_entry("Option", self.type_name(*type_id))?; @@ -281,6 +282,7 @@ impl PartialEq for WithContext<'_, '_, Metadata> { match self.data { Metadata::String => matches!(other.data, Metadata::String), Metadata::Bool => matches!(other.data, Metadata::Bool), + Metadata::JsonValue => matches!(other.data, Metadata::JsonValue), Metadata::Int(self_meta) => { if let Metadata::Int(other_meta) = other.data { return self_meta == other_meta diff --git a/smart_contract/Cargo.toml b/smart_contract/Cargo.toml index b523479c2c5..ffa84b4dff2 100644 --- a/smart_contract/Cargo.toml +++ b/smart_contract/Cargo.toml @@ -22,6 +22,7 @@ iroha_data_model.workspace = true iroha_smart_contract_utils.workspace = true parity-scale-codec.workspace = true +serde_json.workspace = true derive_more.workspace = true displaydoc.workspace = true diff --git a/smart_contract/executor/data_model/derive/src/parameter.rs b/smart_contract/executor/data_model/derive/src/parameter.rs index e98e4abbf25..570a4bd1889 100644 --- a/smart_contract/executor/data_model/derive/src/parameter.rs +++ b/smart_contract/executor/data_model/derive/src/parameter.rs @@ -22,7 +22,7 @@ pub fn impl_derive_parameter(input: &syn::DeriveInput) -> TokenStream { return Err(Self::Error::UnknownIdent(alloc::string::ToString::to_string(value_id.name().as_ref()))); } - serde_json::from_str::(value.payload().as_ref()).map_err(Self::Error::Deserialize) + serde_json::from_value::(value.payload().get().clone()).map_err(Self::Error::Deserialize) } } diff --git a/smart_contract/executor/data_model/derive/src/permission.rs b/smart_contract/executor/data_model/derive/src/permission.rs index c6ef479f599..675d0a8f7f3 100644 --- a/smart_contract/executor/data_model/derive/src/permission.rs +++ b/smart_contract/executor/data_model/derive/src/permission.rs @@ -22,7 +22,7 @@ pub fn impl_derive_permission(input: &syn::DeriveInput) -> TokenStream { return Err(Self::Error::UnknownIdent(value.name().to_owned())); } - serde_json::from_str::(value.payload().as_ref()).map_err(Self::Error::Deserialize) + serde_json::from_value::(value.payload().get().clone()).map_err(Self::Error::Deserialize) } } diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 52c3556bb6f..c49b1bb86d5 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -252,6 +252,7 @@ mod host { pub mod prelude { pub use iroha_smart_contract_derive::main; pub use iroha_smart_contract_utils::debug::DebugUnwrapExt; + pub use serde_json; pub use crate::{data_model::prelude::*, ExecuteOnHost}; } diff --git a/tools/kagami/src/genesis/generate.rs b/tools/kagami/src/genesis/generate.rs index d4fd4129222..eacf03f0e59 100644 --- a/tools/kagami/src/genesis/generate.rs +++ b/tools/kagami/src/genesis/generate.rs @@ -91,7 +91,7 @@ pub fn generate_default( ) -> color_eyre::Result { let genesis_account_id = AccountId::new(GENESIS_DOMAIN_ID.clone(), genesis_public_key); let mut meta = Metadata::default(); - meta.insert("key".parse()?, JsonString::new("value")); + meta.insert("key".parse()?, "value"); let mut builder = builder .domain_with_metadata("wonderland".parse()?, meta.clone()) diff --git a/tools/parity_scale_cli/Cargo.toml b/tools/parity_scale_cli/Cargo.toml index b076c9976eb..67197313751 100644 --- a/tools/parity_scale_cli/Cargo.toml +++ b/tools/parity_scale_cli/Cargo.toml @@ -24,7 +24,7 @@ clap = { workspace = true, features = ["derive", "cargo", "env", "string"] } eyre = { workspace = true } parity-scale-codec = { workspace = true } colored = "2.1.0" -serde_json = { workspace = true, features = ["std"]} +serde_json = { workspace = true, features = ["std"] } serde = { workspace = true } supports-color = { workspace = true } @@ -32,7 +32,7 @@ supports-color = { workspace = true } iroha_data_model = { workspace = true } parity-scale-codec = { workspace = true } -serde_json = { workspace = true, features = ["std"]} +serde_json = { workspace = true, features = ["std"] } serde = { workspace = true } eyre = { workspace = true } diff --git a/tools/parity_scale_cli/src/main.rs b/tools/parity_scale_cli/src/main.rs index c68cb4c8589..f0808f7f021 100644 --- a/tools/parity_scale_cli/src/main.rs +++ b/tools/parity_scale_cli/src/main.rs @@ -313,10 +313,7 @@ mod tests { #[test] fn decode_account_sample() { let mut metadata = Metadata::default(); - metadata.insert( - "hat".parse().expect("Valid"), - "white".parse::().expect("Valid"), - ); + metadata.insert("hat".parse().expect("Valid"), "white"); let account = Account::new(ALICE_ID.clone()).with_metadata(metadata); decode_sample("account.bin", String::from("NewAccount"), &account); diff --git a/wasm_samples/executor_custom_data_model/src/complex_isi.rs b/wasm_samples/executor_custom_data_model/src/complex_isi.rs index e28255dfbf5..6d4f7aa4ca5 100644 --- a/wasm_samples/executor_custom_data_model/src/complex_isi.rs +++ b/wasm_samples/executor_custom_data_model/src/complex_isi.rs @@ -12,7 +12,7 @@ mod isi { use iroha_data_model::{ isi::{CustomInstruction, Instruction, InstructionBox}, - prelude::JsonString, + prelude::JsonValue, }; use iroha_schema::IntoSchema; use serde::{Deserialize, Serialize}; @@ -69,11 +69,11 @@ mod isi { } } - impl TryFrom<&JsonString> for CustomInstructionExpr { + impl TryFrom<&JsonValue> for CustomInstructionExpr { type Error = serde_json::Error; - fn try_from(payload: &JsonString) -> serde_json::Result { - serde_json::from_str::(payload.as_ref()) + fn try_from(payload: &JsonValue) -> serde_json::Result { + serde_json::from_value(payload.get().clone()) } } diff --git a/wasm_samples/executor_custom_data_model/src/simple_isi.rs b/wasm_samples/executor_custom_data_model/src/simple_isi.rs index e89200fee18..f03b161e4d1 100644 --- a/wasm_samples/executor_custom_data_model/src/simple_isi.rs +++ b/wasm_samples/executor_custom_data_model/src/simple_isi.rs @@ -6,7 +6,7 @@ use alloc::{format, string::String, vec::Vec}; use iroha_data_model::{ asset::AssetDefinitionId, isi::{CustomInstruction, Instruction, InstructionBox}, - prelude::{JsonString, Numeric}, + prelude::{JsonValue, Numeric}, }; use iroha_schema::IntoSchema; use serde::{Deserialize, Serialize}; @@ -53,10 +53,10 @@ impl From for InstructionBox { } } -impl TryFrom<&JsonString> for CustomInstructionBox { +impl TryFrom<&JsonValue> for CustomInstructionBox { type Error = serde_json::Error; - fn try_from(payload: &JsonString) -> serde_json::Result { - serde_json::from_str::(payload.as_ref()) + fn try_from(payload: &JsonValue) -> serde_json::Result { + serde_json::from_value(payload.get().clone()) } } diff --git a/wasm_samples/mint_rose_trigger_args/src/lib.rs b/wasm_samples/mint_rose_trigger_args/src/lib.rs index 38f6374fa88..1c5e9fd132f 100644 --- a/wasm_samples/mint_rose_trigger_args/src/lib.rs +++ b/wasm_samples/mint_rose_trigger_args/src/lib.rs @@ -27,6 +27,7 @@ fn main(_id: TriggerId, owner: AccountId, event: EventBox) { EventBox::ExecuteTrigger(event) => event .args() .dbg_expect("Trigger expect parameters") + .clone() .try_into_any() .dbg_expect("Failed to parse args"), _ => dbg_panic("Only work as by call trigger"), diff --git a/wasm_samples/multisig/src/lib.rs b/wasm_samples/multisig/src/lib.rs index b3c7cf95ad8..2865e03d284 100644 --- a/wasm_samples/multisig/src/lib.rs +++ b/wasm_samples/multisig/src/lib.rs @@ -11,6 +11,7 @@ use alloc::{collections::btree_set::BTreeSet, format, vec::Vec}; use dlmalloc::GlobalDlmalloc; use executor_custom_data_model::multisig::MultisigArgs; use iroha_trigger::{debug::dbg_panic, prelude::*, smart_contract::query_single}; +use serde_json::json; #[global_allocator] static ALLOC: GlobalDlmalloc = GlobalDlmalloc; @@ -24,6 +25,7 @@ fn main(id: TriggerId, _owner: AccountId, event: EventBox) { event .args() .dbg_expect("trigger expect args") + .clone() .try_into_any() .dbg_expect("failed to parse arguments"), event.authority().clone(), @@ -52,18 +54,14 @@ fn main(id: TriggerId, _owner: AccountId, event: EventBox) { SetKeyValue::trigger( id.clone(), instructions_metadata_key.clone(), - JsonString::new(&instructions), + json!(&instructions), ) .execute() .dbg_unwrap(); - SetKeyValue::trigger( - id.clone(), - votes_metadata_key.clone(), - JsonString::new(&votes), - ) - .execute() - .dbg_unwrap(); + SetKeyValue::trigger(id.clone(), votes_metadata_key.clone(), json!(&votes)) + .execute() + .dbg_unwrap(); (votes, instructions) } @@ -78,13 +76,9 @@ fn main(id: TriggerId, _owner: AccountId, event: EventBox) { votes.insert(signatory.clone()); - SetKeyValue::trigger( - id.clone(), - votes_metadata_key.clone(), - JsonString::new(&votes), - ) - .execute() - .dbg_unwrap(); + SetKeyValue::trigger(id.clone(), votes_metadata_key.clone(), json!(&votes)) + .execute() + .dbg_unwrap(); let instructions: Vec = query_single(FindTriggerMetadata::new( id.clone(), diff --git a/wasm_samples/multisig_register/src/lib.rs b/wasm_samples/multisig_register/src/lib.rs index e1f1a7c0488..e8498700de2 100644 --- a/wasm_samples/multisig_register/src/lib.rs +++ b/wasm_samples/multisig_register/src/lib.rs @@ -12,6 +12,7 @@ use dlmalloc::GlobalDlmalloc; use executor_custom_data_model::multisig::MultisigRegisterArgs; use iroha_executor_data_model::permission::trigger::CanExecuteUserTrigger; use iroha_trigger::{debug::dbg_panic, prelude::*}; +use serde_json::json; #[global_allocator] static ALLOC: GlobalDlmalloc = GlobalDlmalloc; @@ -27,6 +28,7 @@ fn main(_id: TriggerId, _owner: AccountId, event: EventBox) { EventBox::ExecuteTrigger(event) => event .args() .dbg_expect("trigger expect args") + .clone() .try_into_any() .dbg_expect("failed to parse args"), _ => dbg_panic("Only work as by call trigger"), @@ -81,7 +83,7 @@ fn main(_id: TriggerId, _owner: AccountId, event: EventBox) { SetKeyValue::trigger( trigger_id, "signatories".parse().unwrap(), - JsonString::new(&args.signatories), + json!(&args.signatories), ) .execute() .dbg_unwrap(); diff --git a/wasm_samples/query_assets_and_save_cursor/src/lib.rs b/wasm_samples/query_assets_and_save_cursor/src/lib.rs index 8c719530978..ab60da859b0 100644 --- a/wasm_samples/query_assets_and_save_cursor/src/lib.rs +++ b/wasm_samples/query_assets_and_save_cursor/src/lib.rs @@ -20,6 +20,7 @@ use iroha_smart_contract::{ }; use nonzero_ext::nonzero; use parity_scale_codec::{Decode, DecodeAll, Encode}; +use serde_json::json; #[global_allocator] static ALLOC: GlobalDlmalloc = GlobalDlmalloc; @@ -50,11 +51,7 @@ fn main(owner: AccountId) { let asset_cursor = SmartContractQueryCursor::decode_all(&mut &cursor.dbg_unwrap().encode()[..]).dbg_unwrap(); - SetKeyValue::account( - owner, - "cursor".parse().unwrap(), - JsonString::new(asset_cursor.cursor), - ) - .execute() - .dbg_expect("Failed to save cursor to the owner's metadata"); + SetKeyValue::account(owner, "cursor".parse().unwrap(), json!(asset_cursor.cursor)) + .execute() + .dbg_expect("Failed to save cursor to the owner's metadata"); }