From 248760a3c4c99bf463ae157a673093b9966eb544 Mon Sep 17 00:00:00 2001 From: Daniil Polyakov Date: Thu, 14 Dec 2023 11:38:44 +0300 Subject: [PATCH] [feature] #3941, #3612: Remove expressions (#4089) Signed-off-by: Daniil Polyakov --- Cargo.lock | 23 - Cargo.toml | 1 - cli/src/torii/mod.rs | 4 +- client/benches/torii.rs | 29 +- client/benches/tps/utils.rs | 76 +- client/examples/million_accounts_genesis.rs | 5 +- client/examples/tutorial.rs | 57 +- client/src/client.rs | 49 +- client/tests/integration/add_account.rs | 4 +- client/tests/integration/add_domain.rs | 4 +- client/tests/integration/asset.rs | 139 +- client/tests/integration/asset_propagation.rs | 20 +- client/tests/integration/burn_public_keys.rs | 9 +- client/tests/integration/connected_peers.rs | 6 +- client/tests/integration/domain_owner.rs | 121 +- client/tests/integration/events/data.rs | 40 +- .../tests/integration/events/notification.rs | 16 +- client/tests/integration/events/pipeline.rs | 8 +- .../integration/multiple_blocks_created.rs | 19 +- .../integration/multisignature_account.rs | 19 +- .../integration/multisignature_transaction.rs | 13 +- client/tests/integration/non_mintable.rs | 39 +- client/tests/integration/offline_peers.rs | 2 +- client/tests/integration/pagination.rs | 4 +- client/tests/integration/permissions.rs | 63 +- client/tests/integration/queries/account.rs | 6 +- client/tests/integration/queries/asset.rs | 259 +-- client/tests/integration/queries/role.rs | 10 +- client/tests/integration/restart_peer.rs | 12 +- client/tests/integration/roles.rs | 22 +- client/tests/integration/set_parameter.rs | 6 +- .../src/lib.rs | 6 +- .../executor_with_admin/src/lib.rs | 9 +- .../executor_with_custom_token/src/lib.rs | 22 +- .../executor_with_migration_fail/src/lib.rs | 7 +- .../mint_rose_trigger/src/lib.rs | 2 +- .../query_assets_and_save_cursor/src/lib.rs | 10 +- client/tests/integration/sorting.rs | 21 +- client/tests/integration/transfer_asset.rs | 58 +- .../integration/triggers/by_call_trigger.rs | 86 +- .../integration/triggers/data_trigger.rs | 27 +- .../integration/triggers/event_trigger.rs | 7 +- .../integration/triggers/time_trigger.rs | 24 +- .../integration/triggers/trigger_rollback.rs | 12 +- client/tests/integration/tx_history.rs | 15 +- client/tests/integration/tx_rollback.rs | 13 +- client/tests/integration/unregister_peer.rs | 32 +- client/tests/integration/unstable_network.rs | 12 +- client/tests/integration/upgrade.rs | 8 +- client_cli/src/main.rs | 45 +- configs/peer/executor.wasm | Bin 495847 -> 389245 bytes configs/peer/genesis.json | 130 +- core/benches/blocks/common.rs | 42 +- core/benches/blocks/validate_blocks.rs | 4 +- core/benches/kura.rs | 6 +- core/benches/validation.rs | 14 +- core/src/block.rs | 27 +- core/src/executor.rs | 4 +- core/src/queue.rs | 2 +- core/src/smartcontracts/isi/account.rs | 57 +- core/src/smartcontracts/isi/asset.rs | 80 +- core/src/smartcontracts/isi/block.rs | 8 +- core/src/smartcontracts/isi/domain.rs | 39 +- core/src/smartcontracts/isi/mod.rs | 506 ++--- core/src/smartcontracts/isi/query.rs | 10 +- core/src/smartcontracts/isi/triggers/mod.rs | 40 +- core/src/smartcontracts/isi/triggers/set.rs | 2 +- core/src/smartcontracts/isi/tx.rs | 18 +- core/src/smartcontracts/isi/world.rs | 12 +- core/src/smartcontracts/mod.rs | 54 +- core/src/smartcontracts/wasm.rs | 40 +- core/src/sumeragi/main_loop.rs | 6 +- core/src/tx.rs | 249 +-- core/src/wsv.rs | 2 +- core/test_network/src/lib.rs | 8 +- data_model/src/evaluate.rs | 869 -------- data_model/src/events/data/events.rs | 18 + data_model/src/expression.rs | 703 ------- data_model/src/isi.rs | 1645 +++++++++------ data_model/src/lib.rs | 132 +- data_model/src/query/mod.rs | 339 +-- data_model/src/transaction.rs | 15 +- data_model/src/trigger.rs | 2 +- data_model/src/visit.rs | 752 +++---- data_model/tests/data_model.rs | 57 +- data_model/tests/ui.rs | 7 - data_model/tests/ui_fail/evaluates_to.rs | 12 - data_model/tests/ui_fail/evaluates_to.stderr | 17 - default_executor/src/lib.rs | 5 +- docs/source/references/schema.json | 1825 +++++++++-------- dsl/Cargo.toml | 28 - dsl/src/lib.rs | 208 -- ffi/src/lib.rs | 1 + genesis/src/lib.rs | 30 +- p2p/src/peer.rs | 2 + schema/derive/src/lib.rs | 26 +- .../tests/ui_pass/derive_into_schema.rs | 10 + schema/gen/src/lib.rs | 114 +- smart_contract/executor/derive/src/default.rs | 122 +- smart_contract/executor/derive/src/lib.rs | 29 +- smart_contract/executor/src/default.rs | 868 ++++---- smart_contract/executor/src/lib.rs | 26 +- smart_contract/src/lib.rs | 71 +- smart_contract/utils/src/lib.rs | 2 +- tools/kagami/src/genesis.rs | 37 +- .../parity_scale_decoder/samples/trigger.bin | Bin 80 -> 76 bytes .../parity_scale_decoder/samples/trigger.json | 8 +- tools/parity_scale_decoder/src/main.rs | 2 +- 108 files changed, 4000 insertions(+), 6843 deletions(-) delete mode 100644 data_model/src/evaluate.rs delete mode 100644 data_model/src/expression.rs delete mode 100644 data_model/tests/ui.rs delete mode 100644 data_model/tests/ui_fail/evaluates_to.rs delete mode 100644 data_model/tests/ui_fail/evaluates_to.stderr delete mode 100755 dsl/Cargo.toml delete mode 100755 dsl/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index d71bbe9df90..ff9e1f49ce9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2934,20 +2934,6 @@ dependencies = [ "trybuild", ] -[[package]] -name = "iroha_dsl" -version = "2.0.0-pre-rc.20" -dependencies = [ - "iroha_client", - "iroha_config", - "iroha_crypto", - "iroha_data_model", - "litrs", - "proc-macro2", - "quote", - "serde_json", -] - [[package]] name = "iroha_executor" version = "2.0.0-pre-rc.20" @@ -3582,15 +3568,6 @@ version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" -[[package]] -name = "litrs" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f17c3668f3cc1132437cdadc93dab05e52d592f06948d3f64828430c36e4a70" -dependencies = [ - "proc-macro2", -] - [[package]] name = "lock_api" version = "0.4.10" diff --git a/Cargo.toml b/Cargo.toml index 73b53e5a8bc..3da58f35d73 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -218,7 +218,6 @@ members = [ "genesis", "primitives", "primitives/derive", - "dsl", "ffi", "ffi/derive", "futures", diff --git a/cli/src/torii/mod.rs b/cli/src/torii/mod.rs index a20f2c3f11a..7780d4e5b74 100644 --- a/cli/src/torii/mod.rs +++ b/cli/src/torii/mod.rs @@ -112,9 +112,7 @@ impl Error { QueryFailed(query_error) | InstructionFailed(InstructionExecutionError::Query(query_error)) => match query_error { - Evaluate(_) | Conversion(_) | UnknownCursor | FetchSizeTooBig => { - StatusCode::BAD_REQUEST - } + Conversion(_) | UnknownCursor | FetchSizeTooBig => StatusCode::BAD_REQUEST, Signature(_) => StatusCode::UNAUTHORIZED, Find(_) => StatusCode::NOT_FOUND, }, diff --git a/client/benches/torii.rs b/client/benches/torii.rs index b8906f52504..95b6be46304 100644 --- a/client/benches/torii.rs +++ b/client/benches/torii.rs @@ -9,6 +9,7 @@ use iroha_client::{ data_model::prelude::*, }; use iroha_crypto::KeyPair; +use iroha_data_model::isi::InstructionBox; use iroha_genesis::{GenesisNetwork, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; use iroha_version::Encode; @@ -51,18 +52,19 @@ fn query_requests(criterion: &mut Criterion) { }); let mut group = criterion.benchmark_group("query-requests"); let domain_id: DomainId = "domain".parse().expect("Valid"); - let create_domain = RegisterExpr::new(Domain::new(domain_id.clone())); + let create_domain = Register::domain(Domain::new(domain_id.clone())); let account_id = AccountId::new("account".parse().expect("Valid"), domain_id.clone()); let (public_key, _) = KeyPair::generate() .expect("Failed to generate KeyPair") .into(); - let create_account = RegisterExpr::new(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), [public_key])); let asset_definition_id = AssetDefinitionId::new("xor".parse().expect("Valid"), domain_id); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id, account_id.clone())), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id, account_id.clone()), ); let mut client_config = iroha_client::samples::get_client_config(&get_key_pair()); @@ -71,7 +73,7 @@ fn query_requests(criterion: &mut Criterion) { let iroha_client = Client::new(&client_config).expect("Invalid client configuration"); thread::sleep(std::time::Duration::from_millis(5000)); - let instructions: [InstructionExpr; 4] = [ + let instructions: [InstructionBox; 4] = [ create_domain.into(), create_account.into(), create_asset.into(), @@ -140,12 +142,12 @@ fn instruction_submits(criterion: &mut Criterion) { rt.block_on(builder.start_with_peer(&mut peer)); let mut group = criterion.benchmark_group("instruction-requests"); let domain_id: DomainId = "domain".parse().expect("Valid"); - let create_domain = RegisterExpr::new(Domain::new(domain_id.clone())); + let create_domain: InstructionBox = Register::domain(Domain::new(domain_id.clone())).into(); let account_id = AccountId::new("account".parse().expect("Valid"), domain_id.clone()); let (public_key, _) = KeyPair::generate() .expect("Failed to generate Key-pair.") .into(); - let create_account = RegisterExpr::new(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); let asset_definition_id = AssetDefinitionId::new("xor".parse().expect("Valid"), domain_id); let mut client_config = iroha_client::samples::get_client_config(&get_key_pair()); client_config.torii_api_url = format!("http://{}", peer.api_address).parse().unwrap(); @@ -160,12 +162,9 @@ fn instruction_submits(criterion: &mut Criterion) { let _dropable = group.bench_function("instructions", |b| { b.iter(|| { let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); match iroha_client.submit(mint_asset) { Ok(_) => success_count += 1, diff --git a/client/benches/tps/utils.rs b/client/benches/tps/utils.rs index d21611f7d53..5c37499ce51 100644 --- a/client/benches/tps/utils.rs +++ b/client/benches/tps/utils.rs @@ -19,7 +19,6 @@ use iroha_client::{ }, }; use serde::Deserialize; -use serde_json::json; use test_network::*; pub type Tps = f64; @@ -61,7 +60,7 @@ impl Config { let clients = network.clients(); wait_for_genesis_committed(&clients, 0); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, self.max_txs_per_block)? .into_set_parameters(), @@ -70,13 +69,12 @@ impl Config { let unit_names = (UnitName::MIN..).take(self.peers as usize); let units = clients .into_iter() - .zip(unit_names.clone().zip(unit_names.cycle().skip(1))) - .map(|(client, pair)| { + .zip(unit_names) + .map(|(client, name)| { let unit = MeasurerUnit { config: self, client, - name: pair.0, - next_name: pair.1, + name, }; unit.ready() }) @@ -155,7 +153,6 @@ struct MeasurerUnit { pub config: Config, pub client: Client, pub name: UnitName, - pub next_name: UnitName, } type UnitName = u32; @@ -169,34 +166,13 @@ impl MeasurerUnit { let keypair = iroha_crypto::KeyPair::generate().expect("Failed to generate KeyPair."); let account_id = account_id(self.name); - let alice_id = AccountId::from_str("alice@wonderland")?; let asset_id = asset_id(self.name); - let register_me = RegisterExpr::new(Account::new( - account_id.clone(), - [keypair.public_key().clone()], - )); + let register_me = + Register::account(Account::new(account_id, [keypair.public_key().clone()])); self.client.submit_blocking(register_me)?; - let can_burn_my_asset = PermissionToken::new( - "CanBurnUserAsset".parse().unwrap(), - &json!({ "asset_id": asset_id }), - ); - let allow_alice_to_burn_my_asset = GrantExpr::new(can_burn_my_asset, alice_id.clone()); - let can_transfer_my_asset = PermissionToken::new( - "CanTransferUserAsset".parse().unwrap(), - &json!({ "asset_id": asset_id }), - ); - let allow_alice_to_transfer_my_asset = GrantExpr::new(can_transfer_my_asset, alice_id); - let grant_tx = TransactionBuilder::new(account_id) - .with_instructions([ - allow_alice_to_burn_my_asset, - allow_alice_to_transfer_my_asset, - ]) - .sign(keypair)?; - self.client.submit_transaction_blocking(&grant_tx)?; - - let mint_a_rose = MintExpr::new(1_u32, asset_id); + let mint_a_rose = Mint::asset_quantity(1_u32, asset_id); self.client.submit_blocking(mint_a_rose)?; Ok(self) @@ -267,42 +243,12 @@ impl MeasurerUnit { }) } - fn instructions(&self) -> impl Iterator { - [self.mint_or_burn(), self.relay_a_rose()] - .into_iter() - .cycle() - } - - fn mint_or_burn(&self) -> InstructionExpr { - let is_running_out = Less::new( - EvaluatesTo::new_unchecked(Expression::Query( - FindAssetQuantityById::new(asset_id(self.name)).into(), - )), - 100_u32, - ); - let supply_roses = MintExpr::new(100_u32.to_value(), asset_id(self.name)); - let burn_a_rose = BurnExpr::new(1_u32.to_value(), asset_id(self.name)); - - ConditionalExpr::with_otherwise(is_running_out, supply_roses, burn_a_rose).into() + fn instructions(&self) -> impl Iterator { + std::iter::once(self.mint()).cycle() } - fn relay_a_rose(&self) -> InstructionExpr { - // Save at least one rose - // because if asset value hits 0 it's automatically deleted from account - // and query `FindAssetQuantityById` return error - let enough_to_transfer = Greater::new( - EvaluatesTo::new_unchecked(Expression::Query( - FindAssetQuantityById::new(asset_id(self.name)).into(), - )), - 1_u32, - ); - let transfer_rose = TransferExpr::new( - asset_id(self.name), - 1_u32.to_value(), - account_id(self.next_name), - ); - - ConditionalExpr::new(enough_to_transfer, transfer_rose).into() + fn mint(&self) -> InstructionBox { + Mint::asset_quantity(1_u32, asset_id(self.name)).into() } } diff --git a/client/examples/million_accounts_genesis.rs b/client/examples/million_accounts_genesis.rs index 079c0ecc7e5..57993c1a972 100644 --- a/client/examples/million_accounts_genesis.rs +++ b/client/examples/million_accounts_genesis.rs @@ -3,6 +3,7 @@ use std::{thread, time::Duration}; use iroha::samples::{construct_executor, get_config}; use iroha_client::data_model::prelude::*; +use iroha_data_model::isi::InstructionBox; use iroha_genesis::{GenesisNetwork, RawGenesisBlock, RawGenesisBlockBuilder}; use iroha_primitives::unique_vec; use test_network::{ @@ -64,8 +65,8 @@ fn create_million_accounts_directly() { format!("bob-{i}").parse().expect("Valid"), domain_id.clone(), ); - let create_domain = RegisterExpr::new(Domain::new(domain_id)); - let create_account = RegisterExpr::new(Account::new(normal_account_id.clone(), [])); + let create_domain: InstructionBox = Register::domain(Domain::new(domain_id)).into(); + let create_account = Register::account(Account::new(normal_account_id.clone(), [])).into(); if test_client .submit_all([create_domain, create_account]) .is_err() diff --git a/client/examples/tutorial.rs b/client/examples/tutorial.rs index a961a1932e7..5cc86cd8495 100644 --- a/client/examples/tutorial.rs +++ b/client/examples/tutorial.rs @@ -3,7 +3,6 @@ use std::fs::File; use eyre::{Error, WrapErr}; -use iroha_client::data_model::TryToValue; // #region rust_config_crates use iroha_config::client::Configuration; // #endregion rust_config_crates @@ -51,7 +50,7 @@ fn domain_registration_test(config: &Configuration) -> Result<(), Error> { client::Client, data_model::{ metadata::UnlimitedMetadata, - prelude::{Domain, DomainId, InstructionExpr, RegisterExpr}, + prelude::{Domain, DomainId, InstructionBox, Register}, }, }; // #endregion domain_register_example_crates @@ -63,7 +62,7 @@ fn domain_registration_test(config: &Configuration) -> Result<(), Error> { // #region domain_register_example_create_isi // Create an ISI - let create_looking_glass = RegisterExpr::new(Domain::new(looking_glass)); + let create_looking_glass = Register::domain(Domain::new(looking_glass)); // #endregion domain_register_example_create_isi // #region rust_client_create @@ -74,7 +73,7 @@ fn domain_registration_test(config: &Configuration) -> Result<(), Error> { // #region domain_register_example_prepare_tx // Prepare a transaction let metadata = UnlimitedMetadata::default(); - let instructions: Vec = vec![create_looking_glass.into()]; + let instructions: Vec = vec![create_looking_glass.into()]; let tx = iroha_client .build_transaction(instructions, metadata) .wrap_err("Error building a domain registration transaction")?; @@ -117,7 +116,7 @@ fn account_registration_test(config: &Configuration) -> Result<(), Error> { client::Client, data_model::{ metadata::UnlimitedMetadata, - prelude::{Account, AccountId, InstructionExpr, RegisterExpr}, + prelude::{Account, AccountId, InstructionBox, Register}, }, }; use iroha_crypto::KeyPair; @@ -141,14 +140,14 @@ fn account_registration_test(config: &Configuration) -> Result<(), Error> { // #region register_account_generate // Generate a new account - let create_account = RegisterExpr::new(Account::new(account_id, [public_key])); + let create_account = Register::account(Account::new(account_id, [public_key])); // #endregion register_account_generate // #region register_account_prepare_tx // Prepare a transaction using the - // Account's RegisterExpr + // Account's RegisterBox let metadata = UnlimitedMetadata::new(); - let instructions: Vec = vec![create_account.into()]; + let instructions: Vec = vec![create_account.into()]; let tx = iroha_client.build_transaction(instructions, metadata)?; // #endregion register_account_prepare_tx @@ -168,7 +167,7 @@ fn asset_registration_test(config: &Configuration) -> Result<(), Error> { use iroha_client::{ client::Client, data_model::prelude::{ - AccountId, AssetDefinition, AssetDefinitionId, AssetId, IdBox, MintExpr, RegisterExpr, + AccountId, AssetDefinition, AssetDefinitionId, AssetId, Mint, Register, }, }; // #endregion register_asset_crates @@ -185,7 +184,7 @@ fn asset_registration_test(config: &Configuration) -> Result<(), Error> { // #region register_asset_init_submit // Initialise the registration time let register_time = - RegisterExpr::new(AssetDefinition::fixed(asset_def_id.clone()).mintable_once()); + Register::asset_definition(AssetDefinition::fixed(asset_def_id.clone()).mintable_once()); // Submit a registration time iroha_client.submit(register_time)?; @@ -197,10 +196,10 @@ fn asset_registration_test(config: &Configuration) -> Result<(), Error> { .expect("Valid, because the string contains no whitespace, has a single '@' character and is not empty after"); // #region register_asset_mint_submit - // Create a MintExpr using a previous asset and account - let mint = MintExpr::new( - 12.34_f64.try_to_value()?, - IdBox::AssetId(AssetId::new(asset_def_id, account_id)), + // Create a MintBox using a previous asset and account + let mint = Mint::asset_fixed( + 12.34_f64.try_into()?, + AssetId::new(asset_def_id, account_id), ); // Submit a minting transaction @@ -217,10 +216,7 @@ fn asset_minting_test(config: &Configuration) -> Result<(), Error> { use iroha_client::{ client::Client, - data_model::{ - prelude::{AccountId, AssetDefinitionId, AssetId, MintExpr, ToValue}, - IdBox, - }, + data_model::prelude::{AccountId, AssetDefinitionId, AssetId, Mint}, }; // #endregion mint_asset_crates @@ -237,10 +233,7 @@ fn asset_minting_test(config: &Configuration) -> Result<(), Error> { // Mint the Asset instance // #region mint_asset_mint - let mint_roses = MintExpr::new( - 42_u32.to_value(), - IdBox::AssetId(AssetId::new(roses, alice)), - ); + let mint_roses = Mint::asset_quantity(42_u32, AssetId::new(roses, alice)); // #endregion mint_asset_mint // #region mint_asset_submit_tx @@ -255,10 +248,7 @@ fn asset_minting_test(config: &Configuration) -> Result<(), Error> { // or `roses.to_string() + "#" + alice.to_string()`. // The `##` is a short-hand for the rose `which belongs to the same domain as the account // to which it belongs to. - let mint_roses_alt = MintExpr::new( - 10_u32.to_value(), - IdBox::AssetId("rose##alice@wonderland".parse()?), - ); + let mint_roses_alt = Mint::asset_quantity(10_u32, "rose##alice@wonderland".parse()?); // #endregion mint_asset_mint_alt // #region mint_asset_submit_tx_alt @@ -277,10 +267,7 @@ fn asset_burning_test(config: &Configuration) -> Result<(), Error> { use iroha_client::{ client::Client, - data_model::{ - prelude::{AccountId, AssetDefinitionId, AssetId, BurnExpr, ToValue}, - IdBox, - }, + data_model::prelude::{AccountId, AssetDefinitionId, AssetId, Burn}, }; // #endregion burn_asset_crates @@ -297,10 +284,7 @@ fn asset_burning_test(config: &Configuration) -> Result<(), Error> { // #region burn_asset_burn // Burn the Asset instance - let burn_roses = BurnExpr::new( - 10_u32.to_value(), - IdBox::AssetId(AssetId::new(roses, alice)), - ); + let burn_roses = Burn::asset_quantity(10_u32, AssetId::new(roses, alice)); // #endregion burn_asset_burn // #region burn_asset_submit_tx @@ -315,10 +299,7 @@ fn asset_burning_test(config: &Configuration) -> Result<(), Error> { // or `roses.to_string() + "#" + alice.to_string()`. // The `##` is a short-hand for the rose `which belongs to the same domain as the account // to which it belongs to. - let burn_roses_alt = BurnExpr::new( - 10_u32.to_value(), - IdBox::AssetId("rose##alice@wonderland".parse()?), - ); + let burn_roses_alt = Burn::asset_quantity(10_u32, "rose##alice@wonderland".parse()?); // #endregion burn_asset_burn_alt // #region burn_asset_submit_tx_alt diff --git a/client/src/client.rs b/client/src/client.rs index d4f7f383e30..e057a8c7c58 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -1472,14 +1472,12 @@ pub mod account { } /// Construct a query to get account by id - pub fn by_id(account_id: impl Into>) -> FindAccountById { + pub fn by_id(account_id: AccountId) -> FindAccountById { FindAccountById::new(account_id) } /// Construct a query to get all accounts containing specified asset - pub fn all_with_asset( - asset_definition_id: impl Into>, - ) -> FindAccountsWithAsset { + pub fn all_with_asset(asset_definition_id: AssetDefinitionId) -> FindAccountsWithAsset { FindAccountsWithAsset::new(asset_definition_id) } } @@ -1499,19 +1497,17 @@ pub mod asset { } /// Construct a query to get asset definition by its id - pub fn definition_by_id( - asset_definition_id: impl Into>, - ) -> FindAssetDefinitionById { + pub fn definition_by_id(asset_definition_id: AssetDefinitionId) -> FindAssetDefinitionById { FindAssetDefinitionById::new(asset_definition_id) } /// Construct a query to get all assets by account id - pub fn by_account_id(account_id: impl Into>) -> FindAssetsByAccountId { + pub fn by_account_id(account_id: AccountId) -> FindAssetsByAccountId { FindAssetsByAccountId::new(account_id) } /// Construct a query to get an asset by its id - pub fn by_id(asset_id: impl Into>) -> FindAssetById { + pub fn by_id(asset_id: AssetId) -> FindAssetById { FindAssetById::new(asset_id) } } @@ -1532,9 +1528,7 @@ pub mod block { } /// Construct a query to find block header by hash - pub fn header_by_hash( - hash: impl Into>>, - ) -> FindBlockHeaderByHash { + pub fn header_by_hash(hash: HashOf) -> FindBlockHeaderByHash { FindBlockHeaderByHash::new(hash) } } @@ -1549,7 +1543,7 @@ pub mod domain { } /// Construct a query to get all domain by id - pub fn by_id(domain_id: impl Into>) -> FindDomainById { + pub fn by_id(domain_id: DomainId) -> FindDomainById { FindDomainById::new(domain_id) } } @@ -1565,16 +1559,12 @@ pub mod transaction { } /// Construct a query to retrieve transactions for account - pub fn by_account_id( - account_id: impl Into>, - ) -> FindTransactionsByAccountId { + pub fn by_account_id(account_id: AccountId) -> FindTransactionsByAccountId { FindTransactionsByAccountId::new(account_id) } /// Construct a query to retrieve transaction by hash - pub fn by_hash( - hash: impl Into>>, - ) -> FindTransactionByHash { + pub fn by_hash(hash: HashOf) -> FindTransactionByHash { FindTransactionByHash::new(hash) } } @@ -1584,7 +1574,7 @@ pub mod trigger { use super::*; /// Construct a query to get triggers by domain id - pub fn by_domain_id(domain_id: impl Into>) -> FindTriggersByDomainId { + pub fn by_domain_id(domain_id: DomainId) -> FindTriggersByDomainId { FindTriggersByDomainId::new(domain_id) } } @@ -1600,10 +1590,8 @@ pub mod permission { /// Construct a query to get all [`PermissionToken`] granted /// to account with given [`Id`][AccountId] - pub fn by_account_id( - account_id: impl Into>, - ) -> FindPermissionTokensByAccountId { - FindPermissionTokensByAccountId::new(account_id.into()) + pub fn by_account_id(account_id: AccountId) -> FindPermissionTokensByAccountId { + FindPermissionTokensByAccountId::new(account_id) } } @@ -1622,12 +1610,12 @@ pub mod role { } /// Construct a query to retrieve a role by its id - pub fn by_id(role_id: impl Into>) -> FindRoleByRoleId { + pub fn by_id(role_id: RoleId) -> FindRoleByRoleId { FindRoleByRoleId::new(role_id) } /// Construct a query to retrieve all roles for an account - pub fn by_account_id(account_id: impl Into>) -> FindRolesByAccountId { + pub fn by_account_id(account_id: AccountId) -> FindRolesByAccountId { FindRolesByAccountId::new(account_id) } } @@ -1681,7 +1669,7 @@ mod tests { let build_transaction = || { client - .build_transaction(Vec::::new(), UnlimitedMetadata::new()) + .build_transaction(Vec::::new(), UnlimitedMetadata::new()) .unwrap() }; let tx1 = build_transaction(); @@ -1754,13 +1742,6 @@ mod tests { )), ), (StatusCode::UNPROCESSABLE_ENTITY, ValidationFail::TooComplex), - ( - StatusCode::NOT_FOUND, - // Here should be `Find`, but actually handler doesn't care - ValidationFail::QueryFailed(QueryExecutionFail::Evaluate( - "whatever".to_owned(), - )), - ), ]; for (status_code, err) in responses { let resp = Response::builder().status(status_code).body(err.encode())?; diff --git a/client/tests/integration/add_account.rs b/client/tests/integration/add_account.rs index f463266399e..bdec9441783 100644 --- a/client/tests/integration/add_account.rs +++ b/client/tests/integration/add_account.rs @@ -13,14 +13,14 @@ fn client_add_account_with_name_length_more_than_limit_should_not_commit_transac let pipeline_time = super::Configuration::pipeline_time(); let normal_account_id: AccountId = "bob@wonderland".parse().expect("Valid"); - let create_account = RegisterExpr::new(Account::new(normal_account_id.clone(), [])); + let create_account = Register::account(Account::new(normal_account_id.clone(), [])); test_client.submit(create_account)?; let too_long_account_name = "0".repeat(2_usize.pow(14)); let incorrect_account_id: AccountId = (too_long_account_name + "@wonderland") .parse() .expect("Valid"); - let create_account = RegisterExpr::new(Account::new(incorrect_account_id.clone(), [])); + let create_account = Register::account(Account::new(incorrect_account_id.clone(), [])); test_client.submit(create_account)?; thread::sleep(pipeline_time * 2); diff --git a/client/tests/integration/add_domain.rs b/client/tests/integration/add_domain.rs index f963fe31d10..09bf95bb90d 100644 --- a/client/tests/integration/add_domain.rs +++ b/client/tests/integration/add_domain.rs @@ -16,11 +16,11 @@ fn client_add_domain_with_name_length_more_than_limit_should_not_commit_transact // Given let normal_domain_id: DomainId = "sora".parse()?; - let create_domain = RegisterExpr::new(Domain::new(normal_domain_id.clone())); + let create_domain = Register::domain(Domain::new(normal_domain_id.clone())); test_client.submit(create_domain)?; let too_long_domain_name: DomainId = "0".repeat(2_usize.pow(14)).parse()?; - let create_domain = RegisterExpr::new(Domain::new(too_long_domain_name.clone())); + let create_domain = Register::domain(Domain::new(too_long_domain_name.clone())); test_client.submit(create_domain)?; thread::sleep(pipeline_time * 2); diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 26e672fc857..2cd3cab768f 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -6,6 +6,7 @@ use iroha_client::{ data_model::prelude::*, }; use iroha_crypto::{KeyPair, PublicKey}; +use iroha_data_model::isi::InstructionBox; use iroha_primitives::fixed::Fixed; use serde_json::json; use test_network::*; @@ -21,11 +22,13 @@ fn client_register_asset_should_add_asset_once_but_not_twice() -> Result<()> { let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("test_asset#wonderland").expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); - let register_asset = RegisterExpr::new(Asset::new( + let create_asset: InstructionBox = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); + let register_asset: InstructionBox = Register::asset(Asset::new( AssetId::new(asset_definition_id.clone(), account_id.clone()), AssetValue::Quantity(0), - )); + )) + .into(); test_client.submit_all([create_asset, register_asset.clone()])?; @@ -56,9 +59,11 @@ fn unregister_asset_should_remove_asset_from_account() -> Result<()> { let asset_definition_id = AssetDefinitionId::from_str("test_asset#wonderland").expect("Valid"); let asset_id = AssetId::new(asset_definition_id.clone(), account_id.clone()); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); - let register_asset = RegisterExpr::new(Asset::new(asset_id.clone(), AssetValue::Quantity(0))); - let unregister_asset = UnregisterExpr::new(asset_id); + let create_asset: InstructionBox = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); + let register_asset = + Register::asset(Asset::new(asset_id.clone(), AssetValue::Quantity(0))).into(); + let unregister_asset = Unregister::asset(asset_id); test_client.submit_all([create_asset, register_asset])?; @@ -93,18 +98,16 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount() -> // Given let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let metadata = iroha_client::data_model::metadata::UnlimitedMetadata::default(); //When let quantity: u32 = 200; - let mint = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); - let instructions: [InstructionExpr; 2] = [create_asset.into(), mint.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { @@ -127,18 +130,15 @@ fn client_add_big_asset_quantity_to_existing_asset_should_increase_asset_amount( let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); let create_asset = - RegisterExpr::new(AssetDefinition::big_quantity(asset_definition_id.clone())); + Register::asset_definition(AssetDefinition::big_quantity(asset_definition_id.clone())); let metadata = iroha_client::data_model::metadata::UnlimitedMetadata::default(); //When let quantity: u128 = 2_u128.pow(65); - let mint = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint = Mint::asset_big_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); - let instructions: [InstructionExpr; 2] = [create_asset.into(), mint.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { @@ -160,20 +160,17 @@ fn client_add_asset_with_decimal_should_increase_asset_amount() -> Result<()> { // Given let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let identifiable_box = AssetDefinition::fixed(asset_definition_id.clone()); - let create_asset = RegisterExpr::new(identifiable_box); + let asset_definition = AssetDefinition::fixed(asset_definition_id.clone()); + let create_asset = Register::asset_definition(asset_definition); let metadata = iroha_client::data_model::metadata::UnlimitedMetadata::default(); //When let quantity: Fixed = Fixed::try_from(123.456_f64).unwrap(); - let mint = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint = Mint::asset_fixed( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); - let instructions: [InstructionExpr; 2] = [create_asset.into(), mint.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), mint.into()]; let tx = test_client.build_transaction(instructions, metadata)?; test_client.submit_transaction(&tx)?; test_client.poll_request(client::asset::by_account_id(account_id.clone()), |result| { @@ -187,12 +184,9 @@ fn client_add_asset_with_decimal_should_increase_asset_amount() -> Result<()> { // Add some fractional part let quantity2: Fixed = Fixed::try_from(0.55_f64).unwrap(); - let mint = MintExpr::new( - quantity2.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint = Mint::asset_fixed( + quantity2, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); // and check that it is added without errors let sum = quantity @@ -217,7 +211,7 @@ fn client_add_asset_with_name_length_more_than_limit_should_not_commit_transacti // Given let normal_asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity( + let create_asset = Register::asset_definition(AssetDefinition::quantity( normal_asset_definition_id.clone(), )); test_client.submit(create_asset)?; @@ -226,7 +220,7 @@ fn client_add_asset_with_name_length_more_than_limit_should_not_commit_transacti let too_long_asset_name = "0".repeat(2_usize.pow(14)); let incorrect_asset_definition_id = AssetDefinitionId::from_str(&(too_long_asset_name + "#wonderland")).expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity( + let create_asset = Register::asset_definition(AssetDefinition::quantity( incorrect_asset_definition_id.clone(), )); @@ -273,11 +267,11 @@ fn find_rate_and_make_exchange_isi_should_succeed() { let buyer_keypair = KeyPair::generate().expect("Failed to generate seller KeyPair."); let register_account = |account_id: AccountId, signature: PublicKey| { - RegisterExpr::new(Account::new(account_id, [signature])) + Register::account(Account::new(account_id, [signature])) }; let grant_alice_asset_transfer_permission = |asset_id: AssetId, owner_keypair: KeyPair| { - let allow_alice_to_transfer_asset = GrantExpr::new( + let allow_alice_to_transfer_asset = Grant::permission_token( PermissionToken::new( "CanTransferUserAsset".parse().unwrap(), &json!({ "asset_id": asset_id }), @@ -304,7 +298,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { "exchange", account_id_new("dex", "exchange"), ); - let instructions: [InstructionExpr; 12] = [ + let instructions: [InstructionBox; 12] = [ register::domain("exchange").into(), register::domain("company").into(), register::domain("crypto").into(), @@ -314,17 +308,17 @@ fn find_rate_and_make_exchange_isi_should_succeed() { register::asset_definition("btc", "crypto").into(), register::asset_definition("eth", "crypto").into(), register::asset_definition("btc2eth_rate", "exchange").into(), - MintExpr::new( - 200_u32.to_value(), - IdBox::AssetId(asset_id_new("eth", "crypto", buyer_account_id.clone())), + Mint::asset_quantity( + 200_u32, + asset_id_new("eth", "crypto", buyer_account_id.clone()), ) .into(), - MintExpr::new( - 20_u32.to_value(), - IdBox::AssetId(asset_id_new("btc", "crypto", seller_account_id.clone())), + Mint::asset_quantity( + 20_u32, + asset_id_new("btc", "crypto", seller_account_id.clone()), ) .into(), - MintExpr::new(20_u32.to_value(), IdBox::AssetId(asset_id.clone())).into(), + Mint::asset_quantity(20_u32, asset_id.clone()).into(), ]; test_client .submit_all_blocking(instructions) @@ -333,23 +327,26 @@ fn find_rate_and_make_exchange_isi_should_succeed() { grant_alice_asset_transfer_permission(seller_btc, seller_keypair); grant_alice_asset_transfer_permission(buyer_eth, buyer_keypair); + let to_transfer = test_client + .request(FindAssetQuantityById::new(asset_id)) + .expect("Failed to execute query to find asset quantity by id."); + let to_transfer = match to_transfer { + NumericValue::U32(value) => value, + _ => panic!("Wrong asset quantity type."), + }; test_client - .submit_all_blocking([PairExpr::new( - TransferExpr::new( - IdBox::AssetId(asset_id_new("btc", "crypto", seller_account_id.clone())), - EvaluatesTo::new_evaluates_to_value(Expression::Query( - FindAssetQuantityById::new(asset_id.clone()).into(), - )), - IdBox::AccountId(buyer_account_id.clone()), + .submit_all_blocking([ + Transfer::asset_quantity( + asset_id_new("btc", "crypto", seller_account_id.clone()), + to_transfer, + buyer_account_id.clone(), ), - TransferExpr::new( - IdBox::AssetId(asset_id_new("eth", "crypto", buyer_account_id)), - EvaluatesTo::new_evaluates_to_value(Expression::Query( - FindAssetQuantityById::new(asset_id).into(), - )), - IdBox::AccountId(seller_account_id), + Transfer::asset_quantity( + asset_id_new("eth", "crypto", buyer_account_id), + to_transfer, + seller_account_id, ), - )]) + ]) .expect("Failed to exchange eth for btc."); let expected_seller_eth = NumericValue::U32(20); @@ -403,7 +400,7 @@ fn transfer_asset_definition() { let asset_definition_id: AssetDefinitionId = "asset#wonderland".parse().expect("Valid"); test_client - .submit_blocking(RegisterExpr::new(AssetDefinition::quantity( + .submit_blocking(Register::asset_definition(AssetDefinition::quantity( asset_definition_id.clone(), ))) .expect("Failed to submit transaction"); @@ -414,7 +411,7 @@ fn transfer_asset_definition() { assert_eq!(asset_definition.owned_by(), &alice_id); test_client - .submit_blocking(TransferExpr::new( + .submit_blocking(Transfer::asset_definition( alice_id, asset_definition_id.clone(), bob_id.clone(), @@ -447,12 +444,12 @@ fn asset_id_new(definition_name: &str, definition_domain: &str, account_id: Acco mod register { use super::*; - pub fn domain(name: &str) -> RegisterExpr { - RegisterExpr::new(Domain::new(DomainId::from_str(name).expect("Valid"))) + pub fn domain(name: &str) -> Register { + Register::domain(Domain::new(DomainId::from_str(name).expect("Valid"))) } - pub fn account(account_name: &str, domain_name: &str) -> RegisterExpr { - RegisterExpr::new(Account::new( + pub fn account(account_name: &str, domain_name: &str) -> Register { + Register::account(Account::new( AccountId::new( account_name.parse().expect("Valid"), domain_name.parse().expect("Valid"), @@ -461,8 +458,8 @@ mod register { )) } - pub fn asset_definition(asset_name: &str, domain_name: &str) -> RegisterExpr { - RegisterExpr::new(AssetDefinition::quantity(AssetDefinitionId::new( + pub fn asset_definition(asset_name: &str, domain_name: &str) -> Register { + Register::asset_definition(AssetDefinition::quantity(AssetDefinitionId::new( asset_name.parse().expect("Valid"), domain_name.parse().expect("Valid"), ))) diff --git a/client/tests/integration/asset_propagation.rs b/client/tests/integration/asset_propagation.rs index fb50c5b3f2d..de7a5238418 100644 --- a/client/tests/integration/asset_propagation.rs +++ b/client/tests/integration/asset_propagation.rs @@ -9,6 +9,7 @@ use iroha_client::{ }, }; use iroha_crypto::KeyPair; +use iroha_data_model::isi::InstructionBox; use test_network::*; use super::Configuration; @@ -21,28 +22,27 @@ fn client_add_asset_quantity_to_existing_asset_should_increase_asset_amount_on_a wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Configuration::pipeline_time(); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), )?; - let create_domain = RegisterExpr::new(Domain::new(DomainId::from_str("domain")?)); + let create_domain: InstructionBox = + Register::domain(Domain::new(DomainId::from_str("domain")?)).into(); let account_id = AccountId::from_str("account@domain")?; let (public_key, _) = KeyPair::generate()?.into(); - let create_account = RegisterExpr::new(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); let asset_definition_id = AssetDefinitionId::from_str("xor#domain")?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); client.submit_all([create_domain, create_account, create_asset])?; thread::sleep(pipeline_time * 3); //When let quantity: u32 = 200; - client.submit(MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + client.submit(Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ))?; thread::sleep(pipeline_time); diff --git a/client/tests/integration/burn_public_keys.rs b/client/tests/integration/burn_public_keys.rs index 4c1431c0639..fb4c54b2251 100644 --- a/client/tests/integration/burn_public_keys.rs +++ b/client/tests/integration/burn_public_keys.rs @@ -29,7 +29,7 @@ fn submit( } fn get(client: &Client, hash: HashOf) -> TransactionValue { - client + *client .request(transaction::by_hash(hash)) .unwrap() .transaction @@ -51,7 +51,7 @@ fn public_keys_cannot_be_burned_to_nothing() { wait_for_genesis_committed(&vec![client.clone()], 0); let charlie_initial_keypair = KeyPair::generate().unwrap(); - let register_charlie = RegisterExpr::new(Account::new( + let register_charlie = Register::account(Account::new( charlie_id.clone(), [charlie_initial_keypair.public_key().clone()], )); @@ -64,7 +64,7 @@ fn public_keys_cannot_be_burned_to_nothing() { let mint_keys = (0..KEYS_COUNT - 1).map(|_| { let (public_key, _) = KeyPair::generate().unwrap().into(); - MintExpr::new(public_key, charlie_id.clone()) + Mint::account_public_key(public_key, charlie_id.clone()) }); let (tx_hash, res) = submit( @@ -79,7 +79,8 @@ fn public_keys_cannot_be_burned_to_nothing() { let charlie = client.request(account::by_id(charlie_id.clone())).unwrap(); let mut keys = charlie.signatories(); - let burn = |key: PublicKey| InstructionExpr::from(BurnExpr::new(key, charlie_id.clone())); + let burn = + |key: PublicKey| InstructionBox::from(Burn::account_public_key(key, charlie_id.clone())); let burn_keys_leaving_one = keys .by_ref() .filter(|pub_key| pub_key != &charlie_initial_keypair.public_key()) diff --git a/client/tests/integration/connected_peers.rs b/client/tests/integration/connected_peers.rs index 0ad808b20c2..91745b15ff8 100644 --- a/client/tests/integration/connected_peers.rs +++ b/client/tests/integration/connected_peers.rs @@ -37,7 +37,7 @@ fn connected_peers_with_f(faults: u64, start_port: Option) -> Result<()> { wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Configuration::pipeline_time(); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), @@ -52,7 +52,7 @@ fn connected_peers_with_f(faults: u64, start_port: Option) -> Result<()> { // then `status.peers` decrements let peer = network.peers.values().last().unwrap(); let peer_client = Client::test(&peer.api_address); - let unregister_peer = UnregisterExpr::new(IdBox::PeerId(peer.id.clone())); + let unregister_peer = Unregister::peer(peer.id.clone()); client.submit_blocking(unregister_peer)?; thread::sleep(pipeline_time * 2); // Wait for some time to allow peers to connect status = client.get_status()?; @@ -63,7 +63,7 @@ fn connected_peers_with_f(faults: u64, start_port: Option) -> Result<()> { // Re-register the peer: committed with f = `faults` - 1 then // `status.peers` increments - let register_peer = RegisterExpr::new(DataModelPeer::new(peer.id.clone())); + let register_peer = Register::peer(DataModelPeer::new(peer.id.clone())); client.submit_blocking(register_peer)?; thread::sleep(pipeline_time * 4); // Wait for some time to allow peers to connect status = client.get_status()?; diff --git a/client/tests/integration/domain_owner.rs b/client/tests/integration/domain_owner.rs index 608eb38bdba..b420d5e81c2 100644 --- a/client/tests/integration/domain_owner.rs +++ b/client/tests/integration/domain_owner.rs @@ -13,13 +13,13 @@ fn domain_owner_domain_permissions() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id.clone()); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; // check that "alice@wonderland" as owner of domain can edit metadata in her domain let key: Name = "key".parse()?; let value: Name = "value".parse()?; - test_client.submit_blocking(SetKeyValueExpr::new(kingdom_id.clone(), key.clone(), value))?; - test_client.submit_blocking(RemoveKeyValueExpr::new(kingdom_id.clone(), key))?; + 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 permission tokens let bob_id: AccountId = "bob@wonderland".parse()?; @@ -27,11 +27,11 @@ fn domain_owner_domain_permissions() -> Result<()> { "CanUnregisterDomain".parse().unwrap(), &json!({ "domain_id": kingdom_id }), ); - test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?; - test_client.submit_blocking(RevokeExpr::new(token, bob_id))?; + test_client.submit_blocking(Grant::permission_token(token.clone(), bob_id.clone()))?; + test_client.submit_blocking(Revoke::permission_token(token, bob_id))?; // check that "alice@wonderland" as owner of domain can unregister her domain - test_client.submit_blocking(UnregisterExpr::new(kingdom_id))?; + test_client.submit_blocking(Unregister::domain(kingdom_id))?; Ok(()) } @@ -46,28 +46,28 @@ fn domain_owner_account_permissions() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; let mad_hatter_keypair = KeyPair::generate()?; let mad_hatter = Account::new( mad_hatter_id.clone(), [mad_hatter_keypair.public_key().clone()], ); - test_client.submit_blocking(RegisterExpr::new(mad_hatter))?; + test_client.submit_blocking(Register::account(mad_hatter))?; // check that "alice@wonderland" as owner of domain can burn and mint public keys for accounts in her domain let mad_hatter_new_keypair = KeyPair::generate()?; - test_client.submit_blocking(MintExpr::new( + test_client.submit_blocking(Mint::account_public_key( mad_hatter_new_keypair.public_key().clone(), mad_hatter_id.clone(), ))?; - test_client.submit_blocking(BurnExpr::new( + test_client.submit_blocking(Burn::account_public_key( mad_hatter_new_keypair.public_key().clone(), mad_hatter_id.clone(), ))?; // check that "alice@wonderland" as owner of domain can change signature check condition for accounts in her domain - test_client.submit_blocking(MintExpr::new( + test_client.submit_blocking(Mint::account_signature_check_condition( SignatureCheckCondition::AnyAccountSignatureOr(Vec::new().into()), mad_hatter_id.clone(), ))?; @@ -75,12 +75,12 @@ 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: Name = "value".parse()?; - test_client.submit_blocking(SetKeyValueExpr::new( + test_client.submit_blocking(SetKeyValue::account( mad_hatter_id.clone(), key.clone(), value, ))?; - test_client.submit_blocking(RemoveKeyValueExpr::new(mad_hatter_id.clone(), key))?; + test_client.submit_blocking(RemoveKeyValue::account(mad_hatter_id.clone(), key))?; // check that "alice@wonderland" as owner of domain can grant and revoke account related permission tokens in her domain let bob_id: AccountId = "bob@wonderland".parse()?; @@ -88,11 +88,11 @@ fn domain_owner_account_permissions() -> Result<()> { "CanUnregisterAccount".parse().unwrap(), &json!({ "account_id": mad_hatter_id }), ); - test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?; - test_client.submit_blocking(RevokeExpr::new(token, bob_id))?; + test_client.submit_blocking(Grant::permission_token(token.clone(), bob_id.clone()))?; + test_client.submit_blocking(Revoke::permission_token(token, bob_id))?; // check that "alice@wonderland" as owner of domain can unregister accounts in her domain - test_client.submit_blocking(UnregisterExpr::new(mad_hatter_id))?; + test_client.submit_blocking(Unregister::account(mad_hatter_id))?; Ok(()) } @@ -109,30 +109,38 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate()?; let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); - test_client.submit_blocking(RegisterExpr::new(bob))?; + test_client.submit_blocking(Register::account(bob))?; let rabbit = Account::new(rabbit_id.clone(), []); - test_client.submit_blocking(RegisterExpr::new(rabbit))?; + test_client.submit_blocking(Register::account(rabbit))?; // register asset definitions by "bob@kingdom" so he is owner of it let coin = AssetDefinition::quantity(coin_id.clone()); let transaction = TransactionBuilder::new(bob_id.clone()) - .with_instructions([RegisterExpr::new(coin)]) + .with_instructions([Register::asset_definition(coin)]) .sign(bob_keypair)?; test_client.submit_transaction_blocking(&transaction)?; // check that "alice@wonderland" as owner of domain can transfer asset definitions in her domain - test_client.submit_blocking(TransferExpr::new(bob_id, coin_id.clone(), rabbit_id))?; + test_client.submit_blocking(Transfer::asset_definition( + bob_id, + coin_id.clone(), + rabbit_id, + ))?; // check that "alice@wonderland" as owner of domain can edit metadata of asset definition in her domain let key: Name = "key".parse()?; let value: Name = "value".parse()?; - test_client.submit_blocking(SetKeyValueExpr::new(coin_id.clone(), key.clone(), value))?; - test_client.submit_blocking(RemoveKeyValueExpr::new(coin_id.clone(), key))?; + test_client.submit_blocking(SetKeyValue::asset_definition( + coin_id.clone(), + key.clone(), + value, + ))?; + test_client.submit_blocking(RemoveKeyValue::asset_definition(coin_id.clone(), key))?; // check that "alice@wonderland" as owner of domain can grant and revoke asset definition related permission tokens in her domain let bob_id: AccountId = "bob@wonderland".parse()?; @@ -140,11 +148,11 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { "CanUnregisterAssetDefinition".parse().unwrap(), &json!({ "asset_definition_id": coin_id }), ); - test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?; - test_client.submit_blocking(RevokeExpr::new(token, bob_id))?; + test_client.submit_blocking(Grant::permission_token(token.clone(), bob_id.clone()))?; + test_client.submit_blocking(Revoke::permission_token(token, bob_id))?; // check that "alice@wonderland" as owner of domain can unregister asset definitions in her domain - test_client.submit_blocking(UnregisterExpr::new(coin_id))?; + test_client.submit_blocking(Unregister::asset_definition(coin_id))?; Ok(()) } @@ -162,41 +170,40 @@ fn domain_owner_asset_permissions() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate()?; let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); - test_client.submit_blocking(RegisterExpr::new(bob))?; + test_client.submit_blocking(Register::account(bob))?; // register asset definitions by "bob@kingdom" so he is owner of it let coin = AssetDefinition::quantity(coin_id.clone()); let store = AssetDefinition::store(store_id.clone()); let transaction = TransactionBuilder::new(bob_id.clone()) - .with_instructions([RegisterExpr::new(coin), RegisterExpr::new(store)]) + .with_instructions([ + Register::asset_definition(coin), + Register::asset_definition(store), + ]) .sign(bob_keypair)?; test_client.submit_transaction_blocking(&transaction)?; // check that "alice@wonderland" as owner of domain can register and unregister assets in her domain let bob_coin_id = AssetId::new(coin_id, bob_id.clone()); let bob_coin = Asset::new(bob_coin_id.clone(), 30u32); - test_client.submit_blocking(RegisterExpr::new(bob_coin))?; - test_client.submit_blocking(UnregisterExpr::new(bob_coin_id.clone()))?; + test_client.submit_blocking(Register::asset(bob_coin))?; + test_client.submit_blocking(Unregister::asset(bob_coin_id.clone()))?; // check that "alice@wonderland" as owner of domain can burn, mint and transfer assets in her domain - test_client.submit_blocking(MintExpr::new(10u32.to_value(), bob_coin_id.clone()))?; - test_client.submit_blocking(BurnExpr::new(5u32.to_value(), bob_coin_id.clone()))?; - test_client.submit_blocking(TransferExpr::new(bob_coin_id, 5u32.to_value(), alice_id))?; + test_client.submit_blocking(Mint::asset_quantity(10u32, bob_coin_id.clone()))?; + test_client.submit_blocking(Burn::asset_quantity(5u32, bob_coin_id.clone()))?; + test_client.submit_blocking(Transfer::asset_quantity(bob_coin_id, 5u32, alice_id))?; // check that "alice@wonderland" as owner of domain can edit metadata of store asset in her domain let key: Name = "key".parse()?; let value: Name = "value".parse()?; let bob_store_id = AssetId::new(store_id, bob_id); - test_client.submit_blocking(SetKeyValueExpr::new( - bob_store_id.clone(), - key.clone(), - value, - ))?; - test_client.submit_blocking(RemoveKeyValueExpr::new(bob_store_id.clone(), key))?; + 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 permission tokens in her domain let bob_id: AccountId = "bob@wonderland".parse()?; @@ -204,8 +211,8 @@ fn domain_owner_asset_permissions() -> Result<()> { "CanUnregisterUserAsset".parse().unwrap(), &json!({ "asset_id": bob_store_id }), ); - test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?; - test_client.submit_blocking(RevokeExpr::new(token, bob_id))?; + test_client.submit_blocking(Grant::permission_token(token.clone(), bob_id.clone()))?; + test_client.submit_blocking(Revoke::permission_token(token, bob_id))?; Ok(()) } @@ -221,18 +228,18 @@ fn domain_owner_trigger_permissions() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate()?; let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); - test_client.submit_blocking(RegisterExpr::new(bob))?; + test_client.submit_blocking(Register::account(bob))?; let asset_definition_id = "rose#wonderland".parse()?; let asset_id = AssetId::new(asset_definition_id, alice_id.clone()); let trigger_id: TriggerId = "trigger$kingdom".parse()?; - let trigger_instructions = vec![MintExpr::new(1_u32, asset_id)]; - let register_trigger = RegisterExpr::new(Trigger::new( + let trigger_instructions = vec![Mint::asset_quantity(1_u32, asset_id)]; + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( trigger_instructions, @@ -248,11 +255,11 @@ fn domain_owner_trigger_permissions() -> Result<()> { test_client.submit_blocking(register_trigger)?; // check that "alice@wonderland" as owner of domain can edit repetitions of triggers in her domain - test_client.submit_blocking(MintExpr::new(1_u32, trigger_id.clone()))?; - test_client.submit_blocking(BurnExpr::new(1_u32, trigger_id.clone()))?; + test_client.submit_blocking(Mint::trigger_repetitions(1_u32, trigger_id.clone()))?; + test_client.submit_blocking(Burn::trigger_repetitions(1_u32, trigger_id.clone()))?; // check that "alice@wonderland" as owner of domain can call triggers in her domain - let execute_trigger = ExecuteTriggerExpr::new(trigger_id.clone()); + let execute_trigger = ExecuteTrigger::new(trigger_id.clone()); let _result = test_client.submit_blocking(execute_trigger)?; // check that "alice@wonderland" as owner of domain can grant and revoke trigger related permission tokens in her domain @@ -261,19 +268,15 @@ fn domain_owner_trigger_permissions() -> Result<()> { "CanUnregisterUserTrigger".parse().unwrap(), &json!({ "trigger_id": trigger_id }), ); - test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?; - test_client.submit_blocking(RevokeExpr::new(token, bob_id))?; + test_client.submit_blocking(Grant::permission_token(token.clone(), bob_id.clone()))?; + test_client.submit_blocking(Revoke::permission_token(token, bob_id))?; // check that "alice@wonderland" as owner of domain can unregister triggers in her domain - test_client.submit_blocking(UnregisterExpr::new(trigger_id))?; + test_client.submit_blocking(Unregister::trigger(trigger_id))?; Ok(()) } -#[deprecated( - since = "2.0.0-pre-rc.20", - note = "This test suite is deprecated, use test_transfer_domains.py instead" -)] #[ignore = "migrated to client cli python tests"] #[test] fn domain_owner_transfer() -> Result<()> { @@ -286,17 +289,17 @@ fn domain_owner_transfer() -> Result<()> { // "alice@wonderland" is owner of "kingdom" domain let kingdom = Domain::new(kingdom_id.clone()); - test_client.submit_blocking(RegisterExpr::new(kingdom))?; + test_client.submit_blocking(Register::domain(kingdom))?; let bob_keypair = KeyPair::generate()?; let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]); - test_client.submit_blocking(RegisterExpr::new(bob))?; + test_client.submit_blocking(Register::account(bob))?; let domain = test_client.request(FindDomainById::new(kingdom_id.clone()))?; assert_eq!(domain.owned_by(), &alice_id); test_client - .submit_blocking(TransferExpr::new( + .submit_blocking(Transfer::domain( alice_id, kingdom_id.clone(), bob_id.clone(), diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 161ec489ac7..d3aa2a96834 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -8,37 +8,15 @@ use test_network::*; use crate::wasm::utils::wasm_template; -fn produce_instructions() -> Vec { +fn produce_instructions() -> Vec { let domains = (0..4) .map(|domain_index: usize| Domain::new(domain_index.to_string().parse().expect("Valid"))); - let registers: [InstructionExpr; 4] = domains + domains .into_iter() - .map(RegisterExpr::new) - .map(InstructionExpr::from) + .map(Register::domain) + .map(InstructionBox::from) .collect::>() - .try_into() - .unwrap(); - - // TODO: should we re-introduce the DSL? - vec![ - // domain "0" - // pair - // domain "1" - // if false fail else sequence - // domain "2" - // domain "3" - registers[0].clone(), - PairExpr::new( - registers[1].clone(), - ConditionalExpr::with_otherwise( - false, - Fail::new("unreachable"), - SequenceExpr::new([registers[2].clone(), registers[3].clone()]), - ), - ) - .into(), - ] } #[test] @@ -69,7 +47,7 @@ fn wasm_execution_should_produce_events() -> Result<()> { ptr_len = ptr_len / 2, )?; - ptr_offset = ptr_len; + ptr_offset += ptr_len; } let wat = format!( @@ -121,8 +99,10 @@ fn transaction_execution_should_produce_events( client.submit_transaction_blocking(&transaction)?; // assertion + iroha_logger::info!("Listening for events"); for i in 0..4_usize { let event: DataEvent = event_receiver.recv()??.try_into()?; + iroha_logger::info!("Event: {:?}", event); assert!(matches!(event, DataEvent::Domain(_))); if let DataEvent::Domain(domain_event) = event { assert!(matches!(domain_event, DomainEvent::Created(_))); @@ -174,16 +154,16 @@ fn produce_multiple_events() -> Result<()> { let role = iroha_client::data_model::role::Role::new(role_id.clone()) .add_permission(token_1.clone()) .add_permission(token_2.clone()); - let instructions = [RegisterExpr::new(role.clone())]; + let instructions = [Register::role(role.clone())]; client.submit_all_blocking(instructions)?; // Grants role to Bob let bob_id = AccountId::from_str("bob@wonderland")?; - let grant_role = GrantExpr::new(role_id.clone(), bob_id.clone()); + let grant_role = Grant::role(role_id.clone(), bob_id.clone()); client.submit_blocking(grant_role)?; // Unregister role - let unregister_role = UnregisterExpr::new(role_id.clone()); + let unregister_role = Unregister::role(role_id.clone()); client.submit_blocking(unregister_role)?; // Inspect produced events diff --git a/client/tests/integration/events/notification.rs b/client/tests/integration/events/notification.rs index 2c5cf522fd2..2cd033e2b7c 100644 --- a/client/tests/integration/events/notification.rs +++ b/client/tests/integration/events/notification.rs @@ -14,11 +14,11 @@ fn trigger_completion_success_should_produce_event() -> Result<()> { let asset_id = AssetId::new(asset_definition_id, account_id); let trigger_id = TriggerId::from_str("mint_rose")?; - let instruction = MintExpr::new(1_u32, asset_id.clone()); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( - vec![InstructionExpr::from(instruction)], + vec![InstructionBox::from(instruction)], Repeats::Indefinitely, asset_id.account_id.clone(), TriggeringFilterBox::ExecuteTrigger(ExecuteTriggerEventFilter::new( @@ -29,7 +29,7 @@ fn trigger_completion_success_should_produce_event() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - let call_trigger = ExecuteTriggerExpr::new(trigger_id.clone()); + let call_trigger = ExecuteTrigger::new(trigger_id.clone()); let thread_client = test_client.clone(); let (sender, receiver) = mpsc::channel(); @@ -63,11 +63,11 @@ fn trigger_completion_failure_should_produce_event() -> Result<()> { let account_id: AccountId = "alice@wonderland".parse()?; let trigger_id = TriggerId::from_str("fail_box")?; - let instruction = Fail::new("Fail box"); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Fail::new("Fail box".to_owned()); + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( - vec![InstructionExpr::from(instruction)], + vec![InstructionBox::from(instruction)], Repeats::Indefinitely, account_id.clone(), TriggeringFilterBox::ExecuteTrigger(ExecuteTriggerEventFilter::new( @@ -78,7 +78,7 @@ fn trigger_completion_failure_should_produce_event() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - let call_trigger = ExecuteTriggerExpr::new(trigger_id.clone()); + let call_trigger = ExecuteTrigger::new(trigger_id.clone()); let thread_client = test_client.clone(); let (sender, receiver) = mpsc::channel(); diff --git a/client/tests/integration/events/pipeline.rs b/client/tests/integration/events/pipeline.rs index 8a9ce281d06..966a922f6e0 100644 --- a/client/tests/integration/events/pipeline.rs +++ b/client/tests/integration/events/pipeline.rs @@ -24,7 +24,7 @@ fn transaction_with_no_instructions_should_be_committed() -> Result<()> { // #[ignore = "Experiment"] #[test] fn transaction_with_fail_instruction_should_be_rejected() -> Result<()> { - let fail = Fail::new("Should be rejected"); + let fail = Fail::new("Should be rejected".to_owned()); test_with_instruction_and_status_and_port( Some(fail.into()), PipelineStatusKind::Rejected, @@ -34,7 +34,7 @@ fn transaction_with_fail_instruction_should_be_rejected() -> Result<()> { #[allow(dead_code, clippy::needless_range_loop, clippy::needless_pass_by_value)] fn test_with_instruction_and_status_and_port( - instruction: Option, + instruction: Option, should_be: PipelineStatusKind, port: u16, ) -> Result<()> { @@ -44,7 +44,7 @@ fn test_with_instruction_and_status_and_port( wait_for_genesis_committed(&clients, 0); let pipeline_time = Configuration::pipeline_time(); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), @@ -110,7 +110,7 @@ fn committed_block_must_be_available_in_kura() { .expect("Failed to subscribe for events"); client - .submit(Fail::new("Dummy instruction")) + .submit(Fail::new("Dummy instruction".to_owned())) .expect("Failed to submit transaction"); let event = event_iter.next().expect("Block must be committed"); diff --git a/client/tests/integration/multiple_blocks_created.rs b/client/tests/integration/multiple_blocks_created.rs index f2a13a8089b..aa3f3a551ba 100644 --- a/client/tests/integration/multiple_blocks_created.rs +++ b/client/tests/integration/multiple_blocks_created.rs @@ -9,6 +9,7 @@ use iroha_client::{ }, }; use iroha_crypto::KeyPair; +use iroha_data_model::isi::InstructionBox; use test_network::*; use super::Configuration; @@ -23,18 +24,19 @@ fn long_multiple_blocks_created() -> Result<()> { wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Configuration::pipeline_time(); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), )?; - let create_domain = RegisterExpr::new(Domain::new("domain".parse()?)); + let create_domain: InstructionBox = Register::domain(Domain::new("domain".parse()?)).into(); let account_id: AccountId = "account@domain".parse()?; let (public_key, _) = KeyPair::generate()?.into(); - let create_account = RegisterExpr::new(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), [public_key])).into(); let asset_definition_id: AssetDefinitionId = "xor#domain".parse()?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); client.submit_all([create_domain, create_account, create_asset])?; @@ -44,12 +46,9 @@ fn long_multiple_blocks_created() -> Result<()> { //When for _ in 0..N_BLOCKS { let quantity: u32 = 1; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); client.submit(mint_asset)?; account_has_quantity += quantity; diff --git a/client/tests/integration/multisignature_account.rs b/client/tests/integration/multisignature_account.rs index 44f289dcfb1..9f35fb9bbfa 100644 --- a/client/tests/integration/multisignature_account.rs +++ b/client/tests/integration/multisignature_account.rs @@ -19,24 +19,19 @@ fn transaction_signed_by_new_signatory_of_account_should_pass() -> Result<()> { // Given let account_id: AccountId = "alice@wonderland".parse().expect("Valid"); let asset_definition_id: AssetDefinitionId = "xor#wonderland".parse().expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let key_pair = KeyPair::generate()?; - let add_signatory = MintExpr::new( - key_pair.public_key().clone(), - IdBox::AccountId(account_id.clone()), - ); + let add_signatory = Mint::account_public_key(key_pair.public_key().clone(), account_id.clone()); - let instructions: [InstructionExpr; 2] = [create_asset.into(), add_signatory.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), add_signatory.into()]; client.submit_all(instructions)?; thread::sleep(pipeline_time * 2); //When let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); Client::test_with_key(&peer.api_address, key_pair).submit_till( mint_asset, diff --git a/client/tests/integration/multisignature_transaction.rs b/client/tests/integration/multisignature_transaction.rs index 4d999d5c920..280a07751f3 100644 --- a/client/tests/integration/multisignature_transaction.rs +++ b/client/tests/integration/multisignature_transaction.rs @@ -21,7 +21,7 @@ fn multisignature_transactions_should_wait_for_all_signatures() -> Result<()> { wait_for_genesis_committed(&network.clients(), 0); let pipeline_time = Configuration::pipeline_time(); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), @@ -31,23 +31,24 @@ fn multisignature_transactions_should_wait_for_all_signatures() -> Result<()> { let alice_key_pair = get_key_pair(); let key_pair_2 = KeyPair::generate()?; let asset_definition_id = AssetDefinitionId::from_str("camomile#wonderland")?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); - let set_signature_condition = MintExpr::new( + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); + let set_signature_condition = Mint::account_signature_check_condition( SignatureCheckCondition::AllAccountSignaturesAnd( vec![key_pair_2.public_key().clone()].into(), ), - IdBox::AccountId(alice_id.clone()), + alice_id.clone(), ); let mut client_configuration = ClientConfiguration::test(&network.genesis.api_address); let client = Client::new(&client_configuration)?; - let instructions: [InstructionExpr; 2] = [create_asset.into(), set_signature_condition.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), set_signature_condition.into()]; client.submit_all_blocking(instructions)?; //When let quantity: u32 = 200; let asset_id = AssetId::new(asset_definition_id, alice_id.clone()); - let mint_asset = MintExpr::new(quantity.to_value(), IdBox::AssetId(asset_id.clone())); + let mint_asset = Mint::asset_quantity(quantity, asset_id.clone()); let (public_key1, private_key1) = alice_key_pair.into(); client_configuration.account_id = alice_id.clone(); diff --git a/client/tests/integration/non_mintable.rs b/client/tests/integration/non_mintable.rs index 1dca25910a2..c80be2ca4d9 100644 --- a/client/tests/integration/non_mintable.rs +++ b/client/tests/integration/non_mintable.rs @@ -5,6 +5,7 @@ use iroha_client::{ client::{self, QueryResult}, data_model::{metadata::UnlimitedMetadata, prelude::*}, }; +use iroha_data_model::isi::InstructionBox; use test_network::*; #[test] @@ -15,20 +16,18 @@ fn non_mintable_asset_can_be_minted_once_but_not_twice() -> Result<()> { // Given let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone()).mintable_once()); + let create_asset = Register::asset_definition( + AssetDefinition::quantity(asset_definition_id.clone()).mintable_once(), + ); let metadata = UnlimitedMetadata::default(); - let mint = MintExpr::new( - 200_u32.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint = Mint::asset_quantity( + 200_u32, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); - let instructions: [InstructionExpr; 2] = [create_asset.into(), mint.clone().into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), mint.clone().into()]; let tx = test_client.build_transaction(instructions, metadata)?; // We can register and mint the non-mintable token @@ -65,11 +64,14 @@ fn non_mintable_asset_cannot_be_minted_if_registered_with_non_zero_value() -> Re // Given let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone()).mintable_once()); + let create_asset: InstructionBox = Register::asset_definition( + AssetDefinition::quantity(asset_definition_id.clone()).mintable_once(), + ) + .into(); let asset_id = AssetId::new(asset_definition_id.clone(), account_id.clone()); - let register_asset = RegisterExpr::new(Asset::new(asset_id.clone(), 1_u32)); + let register_asset: InstructionBox = + Register::asset(Asset::new(asset_id.clone(), 1_u32)).into(); // We can register the non-mintable token test_client.submit_all([create_asset, register_asset.clone()])?; @@ -85,7 +87,7 @@ fn non_mintable_asset_cannot_be_minted_if_registered_with_non_zero_value() -> Re assert!(test_client.submit_blocking(register_asset).is_err()); // And can't be minted - let mint = MintExpr::new(1_u32.to_value(), IdBox::AssetId(asset_id)); + let mint = Mint::asset_quantity(1_u32, asset_id); assert!(test_client.submit_blocking(mint).is_err()); Ok(()) @@ -99,15 +101,16 @@ fn non_mintable_asset_can_be_minted_if_registered_with_zero_value() -> Result<() // Given let account_id = AccountId::from_str("alice@wonderland").expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone()).mintable_once()); + let create_asset = Register::asset_definition( + AssetDefinition::quantity(asset_definition_id.clone()).mintable_once(), + ); let asset_id = AssetId::new(asset_definition_id.clone(), account_id.clone()); - let register_asset = RegisterExpr::new(Asset::new(asset_id.clone(), 0_u32)); - let mint = MintExpr::new(1_u32.to_value(), IdBox::AssetId(asset_id)); + let register_asset = Register::asset(Asset::new(asset_id.clone(), 0_u32)); + let mint = Mint::asset_quantity(1_u32, asset_id); // We can register the non-mintable token wih zero value and then mint it - let instructions: [InstructionExpr; 3] = + let instructions: [InstructionBox; 3] = [create_asset.into(), register_asset.into(), mint.into()]; test_client.submit_all(instructions)?; test_client.poll_request(client::asset::by_account_id(account_id), |result| { diff --git a/client/tests/integration/offline_peers.rs b/client/tests/integration/offline_peers.rs index 86838146000..193ff34afaf 100644 --- a/client/tests/integration/offline_peers.rs +++ b/client/tests/integration/offline_peers.rs @@ -21,7 +21,7 @@ fn genesis_block_is_committed_with_some_offline_peers() -> Result<()> { )); wait_for_genesis_committed(&network.clients(), 1); - client.submit_blocking( + client.submit_all_blocking( ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(), diff --git a/client/tests/integration/pagination.rs b/client/tests/integration/pagination.rs index 1ab7ab4c14d..6e0ed462ec2 100644 --- a/client/tests/integration/pagination.rs +++ b/client/tests/integration/pagination.rs @@ -46,11 +46,11 @@ fn fetch_size_should_work() -> Result<()> { } fn register_assets(client: &Client) -> Result<()> { - let register: Vec = ('a'..='z') + let register: Vec = ('a'..='z') .map(|c| c.to_string()) .map(|name| (name + "#wonderland").parse().expect("Valid")) .map(|asset_definition_id| { - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id)).into() + Register::asset_definition(AssetDefinition::quantity(asset_definition_id)).into() }) .collect(); let _ = client.submit_all_blocking(register)?; diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 11234a6b16a..fb829ed2ce3 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -18,7 +18,7 @@ fn genesis_transactions_are_validated() { let mut genesis = GenesisNetwork::test(true).expect("Expected genesis"); - let grant_invalid_token = GrantExpr::new( + let grant_invalid_token = Grant::permission_token( PermissionToken::new("InvalidToken".parse().unwrap(), &json!(null)), AccountId::from_str("alice@wonderland").unwrap(), ); @@ -78,7 +78,8 @@ fn permissions_disallow_asset_transfer() { let bob_id: AccountId = "bob@wonderland".parse().expect("Valid"); let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); let asset_definition_id: AssetDefinitionId = "xor#wonderland".parse().expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let mouse_keypair = iroha_crypto::KeyPair::generate().expect("Failed to generate KeyPair."); let alice_start_assets = get_assets(&iroha_client, &alice_id); @@ -87,19 +88,19 @@ fn permissions_disallow_asset_transfer() { .expect("Failed to prepare state."); let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id.clone(), bob_id.clone())), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), bob_id.clone()), ); iroha_client .submit_blocking(mint_asset) .expect("Failed to create asset."); //When - let transfer_asset = TransferExpr::new( - IdBox::AssetId(AssetId::new(asset_definition_id, bob_id)), - quantity.to_value(), - IdBox::AccountId(alice_id.clone()), + let transfer_asset = Transfer::asset_quantity( + AssetId::new(asset_definition_id, bob_id), + quantity, + alice_id.clone(), ); let transfer_tx = TransactionBuilder::new(mouse_id) .with_instructions([transfer_asset]) @@ -131,7 +132,8 @@ fn permissions_disallow_asset_burn() { let bob_id: AccountId = "bob@wonderland".parse().expect("Valid"); let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let mouse_keypair = iroha_crypto::KeyPair::generate().expect("Failed to generate KeyPair."); let alice_start_assets = get_assets(&iroha_client, &alice_id); @@ -141,16 +143,14 @@ fn permissions_disallow_asset_burn() { .expect("Failed to prepare state."); let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id.clone(), bob_id)), - ); + let mint_asset = + Mint::asset_quantity(quantity, AssetId::new(asset_definition_id.clone(), bob_id)); iroha_client .submit_blocking(mint_asset) .expect("Failed to create asset."); - let burn_asset = BurnExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id, mouse_id.clone())), + let burn_asset = Burn::asset_quantity( + quantity, + AssetId::new(asset_definition_id, mouse_id.clone()), ); let burn_tx = TransactionBuilder::new(mouse_id) .with_instructions([burn_asset]) @@ -184,7 +184,7 @@ fn account_can_query_only_its_own_domain() -> Result<()> { // Given let domain_id: DomainId = "wonderland".parse()?; let new_domain_id: DomainId = "wonderland2".parse()?; - let register_domain = RegisterExpr::new(Domain::new(new_domain_id.clone())); + let register_domain = Register::domain(Domain::new(new_domain_id.clone())); client.submit_blocking(register_domain)?; @@ -213,20 +213,20 @@ fn permissions_differ_not_only_by_names() { let new_shoes_definition = AssetDefinition::store(shoes_definition_id.clone()); client .submit_all_blocking([ - RegisterExpr::new(new_hat_definition), - RegisterExpr::new(new_shoes_definition), + Register::asset_definition(new_hat_definition), + Register::asset_definition(new_shoes_definition), ]) .expect("Failed to register new asset definitions"); // Registering mouse let new_mouse_account = Account::new(mouse_id.clone(), [mouse_keypair.public_key().clone()]); client - .submit_blocking(RegisterExpr::new(new_mouse_account)) + .submit_blocking(Register::account(new_mouse_account)) .expect("Failed to register mouse"); // Granting permission to Alice to modify metadata in Mouse's hats let mouse_hat_id = AssetId::new(hat_definition_id, mouse_id.clone()); - let allow_alice_to_set_key_value_in_hats = GrantExpr::new( + let allow_alice_to_set_key_value_in_hats = Grant::permission_token( PermissionToken::new( "CanSetKeyValueInUserAsset".parse().unwrap(), &json!({ "asset_id": mouse_hat_id }), @@ -244,7 +244,7 @@ fn permissions_differ_not_only_by_names() { // Checking that Alice can modify Mouse's hats ... client - .submit_blocking(SetKeyValueExpr::new( + .submit_blocking(SetKeyValue::asset( mouse_hat_id, Name::from_str("color").expect("Valid"), "red".to_owned(), @@ -253,7 +253,7 @@ fn permissions_differ_not_only_by_names() { // ... but not shoes let mouse_shoes_id = AssetId::new(shoes_definition_id, mouse_id.clone()); - let set_shoes_color = SetKeyValueExpr::new( + let set_shoes_color = SetKeyValue::asset( mouse_shoes_id.clone(), Name::from_str("color").expect("Valid"), "yellow".to_owned(), @@ -263,7 +263,7 @@ fn permissions_differ_not_only_by_names() { .expect_err("Expected Alice to fail to modify Mouse's shoes"); // Granting permission to Alice to modify metadata in Mouse's shoes - let allow_alice_to_set_key_value_in_shoes = GrantExpr::new( + let allow_alice_to_set_key_value_in_shoes = Grant::permission_token( PermissionToken::new( "CanSetKeyValueInUserAsset".parse().unwrap(), &json!({ "asset_id": mouse_shoes_id }), @@ -287,6 +287,7 @@ fn permissions_differ_not_only_by_names() { } #[test] +#[allow(deprecated)] fn stored_vs_granted_token_payload() -> Result<()> { let (_rt, _peer, iroha_client) = ::new().with_port(10_730).start_with_runtime(); wait_for_genesis_committed(&[iroha_client.clone()], 0); @@ -296,12 +297,13 @@ fn stored_vs_granted_token_payload() -> Result<()> { // Registering mouse and asset definition let asset_definition_id: AssetDefinitionId = "xor#wonderland".parse().expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::store(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::store(asset_definition_id.clone())); let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); let mouse_keypair = iroha_crypto::KeyPair::generate().expect("Failed to generate KeyPair."); let new_mouse_account = Account::new(mouse_id.clone(), [mouse_keypair.public_key().clone()]); - let instructions: [InstructionExpr; 2] = [ - RegisterExpr::new(new_mouse_account).into(), + let instructions: [InstructionBox; 2] = [ + Register::account(new_mouse_account).into(), create_asset.into(), ]; iroha_client @@ -310,7 +312,7 @@ fn stored_vs_granted_token_payload() -> Result<()> { // Allow alice to mint mouse asset and mint initial value let mouse_asset = AssetId::new(asset_definition_id, mouse_id.clone()); - let allow_alice_to_set_key_value_in_mouse_asset = GrantExpr::new( + let allow_alice_to_set_key_value_in_mouse_asset = Grant::permission_token( PermissionToken::from_str_unchecked( "CanSetKeyValueInUserAsset".parse().unwrap(), // NOTE: Introduced additional whitespaces in the serialized form @@ -328,8 +330,7 @@ fn stored_vs_granted_token_payload() -> Result<()> { .expect("Failed to grant permission to alice."); // Check that alice can indeed mint mouse asset - let set_key_value = - SetKeyValueExpr::new(mouse_asset, Name::from_str("color")?, "red".to_owned()); + let set_key_value = SetKeyValue::asset(mouse_asset, Name::from_str("color")?, "red".to_owned()); iroha_client .submit_blocking(set_key_value) .expect("Failed to mint asset for mouse."); diff --git a/client/tests/integration/queries/account.rs b/client/tests/integration/queries/account.rs index d19a4498361..69d28c66e6f 100644 --- a/client/tests/integration/queries/account.rs +++ b/client/tests/integration/queries/account.rs @@ -15,7 +15,7 @@ fn find_accounts_with_asset() -> Result<()> { // Registering new asset definition let definition_id = AssetDefinitionId::from_str("test_coin#wonderland").expect("Valid"); let asset_definition = AssetDefinition::quantity(definition_id.clone()); - test_client.submit_blocking(RegisterExpr::new(asset_definition.clone()))?; + test_client.submit_blocking(Register::asset_definition(asset_definition.clone()))?; // Checking results before all let received_asset_definition = @@ -40,7 +40,7 @@ fn find_accounts_with_asset() -> Result<()> { .iter() .skip(1) // Alice has already been registered in genesis .cloned() - .map(|account_id| RegisterExpr::new(Account::new(account_id, []))) + .map(|account_id| Register::account(Account::new(account_id, []))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; @@ -48,7 +48,7 @@ fn find_accounts_with_asset() -> Result<()> { .iter() .cloned() .map(|account_id| AssetId::new(definition_id.clone(), account_id)) - .map(|asset_id| MintExpr::new(1_u32, asset_id)) + .map(|asset_id| Mint::asset_quantity(1_u32, asset_id)) .collect::>(); test_client.submit_all_blocking(mint_asset)?; diff --git a/client/tests/integration/queries/asset.rs b/client/tests/integration/queries/asset.rs index 4748cd6d7c2..bb34d302158 100644 --- a/client/tests/integration/queries/asset.rs +++ b/client/tests/integration/queries/asset.rs @@ -1,12 +1,14 @@ use eyre::Result; use iroha_client::{ - client::ClientQueryError, + client::{Client, ClientQueryError}, data_model::{ + asset::AssetValue, prelude::*, query::{asset::FindTotalAssetQuantityByAssetDefinitionId, error::QueryExecutionFail}, }, }; use iroha_crypto::KeyPair; +use iroha_data_model::isi::Instruction; use iroha_primitives::fixed::Fixed; use test_network::*; @@ -19,7 +21,7 @@ fn find_asset_total_quantity() -> Result<()> { // Register new domain let domain_id: DomainId = "looking_glass".parse()?; let domain = Domain::new(domain_id); - test_client.submit_blocking(RegisterExpr::new(domain))?; + test_client.submit_blocking(Register::domain(domain))?; let accounts: [AccountId; 5] = [ "alice@wonderland".parse()?, @@ -40,121 +42,52 @@ fn find_asset_total_quantity() -> Result<()> { .skip(1) // Alice has already been registered in genesis .cloned() .zip(keys.iter().map(KeyPair::public_key).cloned()) - .map(|(account_id, public_key)| RegisterExpr::new(Account::new(account_id, [public_key]))) + .map(|(account_id, public_key)| Register::account(Account::new(account_id, [public_key]))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; // Test for numeric assets value types - for ( - definition, - asset_value_type, - initial_value, - to_mint, - to_burn, - expected_total_asset_quantity, - ) in [ - ( - "quantity#wonderland", - AssetValueType::Quantity, - AssetValue::Quantity(1_u32), - 10_u32.to_value(), - 5_u32.to_value(), - NumericValue::U32(30_u32), - ), - ( - "big-quantity#wonderland", - AssetValueType::BigQuantity, - AssetValue::BigQuantity(1_u128), - 10_u128.to_value(), - 5_u128.to_value(), - NumericValue::U128(30_u128), - ), - ( - "fixed#wonderland", - AssetValueType::Fixed, - AssetValue::Fixed(Fixed::try_from(1.0)?), - 10.0_f64.try_to_value()?, - 5.0_f64.try_to_value()?, - NumericValue::Fixed(Fixed::try_from(30.0)?), - ), - ] { - // Registering new asset definition - let definition_id: AssetDefinitionId = - definition.parse().expect("Failed to parse `definition_id`"); - let asset_definition = AssetDefinition::new(definition_id.clone(), asset_value_type); - test_client.submit_blocking(RegisterExpr::new(asset_definition.clone()))?; - - let asset_ids = accounts - .iter() - .cloned() - .map(|account_id| AssetId::new(definition_id.clone(), account_id)) - .collect::>(); - - // Assert that initial total quantity before any burns and mints is zero - let initial_total_asset_quantity = test_client.request( - FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), - )?; - assert!(initial_total_asset_quantity.is_zero_value()); - - let register_asset = asset_ids - .iter() - .cloned() - .map(|asset_id| Asset::new(asset_id, initial_value.clone())) - .map(RegisterExpr::new) - .collect::>(); - test_client.submit_all_blocking(register_asset)?; - - let mint_asset = asset_ids - .iter() - .cloned() - .map(|asset_id| MintExpr::new(to_mint.clone(), asset_id)); - test_client.submit_all_blocking(mint_asset)?; - - let burn_asset = asset_ids - .iter() - .cloned() - .map(|asset_id| BurnExpr::new(to_burn.clone(), asset_id)) - .collect::>(); - test_client.submit_all_blocking(burn_asset)?; - - // Assert that total asset quantity is equal to: `n_accounts * (initial_value + to_mint - to_burn)` - let total_asset_quantity = test_client.request( - FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), - )?; - assert_eq!(expected_total_asset_quantity, total_asset_quantity); - - let unregister_asset = asset_ids - .iter() - .cloned() - .map(UnregisterExpr::new) - .collect::>(); - test_client.submit_all_blocking(unregister_asset)?; - - // Assert that total asset quantity is zero after unregistering asset from all accounts - let total_asset_quantity = test_client.request( - FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), - )?; - assert!(total_asset_quantity.is_zero_value()); - - // Unregister asset definition - test_client.submit_blocking(UnregisterExpr::new(definition_id.clone()))?; - - // Assert that total asset quantity cleared with unregistering of asset definition - let result = test_client.request(FindTotalAssetQuantityByAssetDefinitionId::new( - definition_id.clone(), - )); - assert!(matches!( - result, - Err(ClientQueryError::Validation(ValidationFail::QueryFailed( - QueryExecutionFail::Find(_) - ))) - )); - } + test_total_quantity( + &test_client, + &accounts, + "quantity#wonderland", + AssetValueType::Quantity, + 1_u32, + 10_u32, + 5_u32, + NumericValue::U32(30_u32), + Mint::asset_quantity, + Burn::asset_quantity, + )?; + test_total_quantity( + &test_client, + &accounts, + "big-quantity#wonderland", + AssetValueType::BigQuantity, + 1_u128, + 10_u128, + 5_u128, + NumericValue::U128(30_u128), + Mint::asset_big_quantity, + Burn::asset_big_quantity, + )?; + test_total_quantity( + &test_client, + &accounts, + "fixed#wonderland", + AssetValueType::Fixed, + Fixed::try_from(1.0)?, + Fixed::try_from(10.0)?, + Fixed::try_from(5.0)?, + NumericValue::Fixed(Fixed::try_from(30.0)?), + Mint::asset_fixed, + Burn::asset_fixed, + )?; // Test for `Store` asset value type let definition_id: AssetDefinitionId = "store#wonderland".parse().expect("Valid"); let asset_definition = AssetDefinition::store(definition_id.clone()); - test_client.submit_blocking(RegisterExpr::new(asset_definition))?; + test_client.submit_blocking(Register::asset_definition(asset_definition))?; let asset_ids = accounts .iter() @@ -168,13 +101,13 @@ fn find_asset_total_quantity() -> Result<()> { )?; assert!(initial_total_asset_quantity.is_zero_value()); - let register_asset = asset_ids + let register_assets = asset_ids .iter() .cloned() .map(|asset_id| Asset::new(asset_id, Metadata::default())) - .map(RegisterExpr::new) + .map(Register::asset) .collect::>(); - test_client.submit_all_blocking(register_asset)?; + test_client.submit_all_blocking(register_assets)?; // Assert that total quantity is equal to number of registrations let result = test_client.request(FindTotalAssetQuantityByAssetDefinitionId::new( @@ -182,12 +115,106 @@ fn find_asset_total_quantity() -> Result<()> { ))?; assert_eq!(NumericValue::U32(5), result); - let unregister_asset = asset_ids + let unregister_assets = asset_ids + .iter() + .cloned() + .map(Unregister::asset) + .collect::>(); + test_client.submit_all_blocking(unregister_assets)?; + + // Assert that total asset quantity is zero after unregistering asset from all accounts + let total_asset_quantity = test_client.request( + FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), + )?; + assert!(total_asset_quantity.is_zero_value()); + + // Unregister asset definition + test_client.submit_blocking(Unregister::asset_definition(definition_id.clone()))?; + + // Assert that total asset quantity cleared with unregistering of asset definition + let result = test_client.request(FindTotalAssetQuantityByAssetDefinitionId::new( + definition_id, + )); + assert!(matches!( + result, + Err(ClientQueryError::Validation(ValidationFail::QueryFailed( + QueryExecutionFail::Find(_) + ))) + )); + + Ok(()) +} + +#[allow(clippy::too_many_arguments)] +fn test_total_quantity( + test_client: &Client, + accounts: &[AccountId; 5], + definition: &str, + asset_value_type: AssetValueType, + initial_value: T, + to_mint: T, + to_burn: T, + expected_total_asset_quantity: NumericValue, + mint_ctr: impl Fn(T, AssetId) -> Mint, + burn_ctr: impl Fn(T, AssetId) -> Burn, +) -> Result<()> +where + T: Copy + Into, + Value: From, + Mint: Instruction, + Burn: Instruction, +{ + // Registering new asset definition + let definition_id: AssetDefinitionId = + definition.parse().expect("Failed to parse `definition_id`"); + let asset_definition = AssetDefinition::new(definition_id.clone(), asset_value_type); + test_client.submit_blocking(Register::asset_definition(asset_definition))?; + + let asset_ids = accounts + .iter() + .cloned() + .map(|account_id| AssetId::new(definition_id.clone(), account_id)) + .collect::>(); + + // Assert that initial total quantity before any burns and mints is zero + let initial_total_asset_quantity = test_client.request( + FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), + )?; + assert!(initial_total_asset_quantity.is_zero_value()); + + let register_assets = asset_ids + .iter() + .cloned() + .map(|asset_id| Asset::new(asset_id, initial_value)) + .map(Register::asset) + .collect::>(); + test_client.submit_all_blocking(register_assets)?; + + let mint_assets = asset_ids + .iter() + .cloned() + .map(|asset_id| mint_ctr(to_mint, asset_id)); + test_client.submit_all_blocking(mint_assets)?; + + let burn_assets = asset_ids + .iter() + .cloned() + .map(|asset_id| burn_ctr(to_burn, asset_id)) + .collect::>(); + test_client.submit_all_blocking(burn_assets)?; + + // Assert that total asset quantity is equal to: `n_accounts * (initial_value + to_mint - to_burn)` + let total_asset_quantity = test_client.request( + FindTotalAssetQuantityByAssetDefinitionId::new(definition_id.clone()), + )?; + assert_eq!(expected_total_asset_quantity, total_asset_quantity); + + let unregister_assets = asset_ids .iter() .cloned() - .map(UnregisterExpr::new) + .map(Unregister::asset) .collect::>(); - test_client.submit_all_blocking(unregister_asset)?; + test_client.submit_all_blocking(unregister_assets)?; // Assert that total asset quantity is zero after unregistering asset from all accounts let total_asset_quantity = test_client.request( @@ -196,7 +223,7 @@ fn find_asset_total_quantity() -> Result<()> { assert!(total_asset_quantity.is_zero_value()); // Unregister asset definition - test_client.submit_blocking(UnregisterExpr::new(definition_id.clone()))?; + test_client.submit_blocking(Unregister::asset_definition(definition_id.clone()))?; // Assert that total asset quantity cleared with unregistering of asset definition let result = test_client.request(FindTotalAssetQuantityByAssetDefinitionId::new( diff --git a/client/tests/integration/queries/role.rs b/client/tests/integration/queries/role.rs index 5432dc2e547..9d18b523910 100644 --- a/client/tests/integration/queries/role.rs +++ b/client/tests/integration/queries/role.rs @@ -29,7 +29,7 @@ fn find_roles() -> Result<()> { let register_roles = role_ids .iter() .cloned() - .map(|role_id| RegisterExpr::new(Role::new(role_id))) + .map(|role_id| Register::role(Role::new(role_id))) .collect::>(); test_client.submit_all_blocking(register_roles)?; @@ -61,7 +61,7 @@ fn find_role_ids() -> Result<()> { let register_roles = role_ids .iter() .cloned() - .map(|role_id| RegisterExpr::new(Role::new(role_id))) + .map(|role_id| Register::role(Role::new(role_id))) .collect::>(); test_client.submit_all_blocking(register_roles)?; @@ -87,7 +87,7 @@ fn find_role_by_id() -> Result<()> { let new_role = Role::new(role_id.clone()); // Registering role - let register_role = RegisterExpr::new(new_role.clone()); + let register_role = Register::role(new_role.clone()); test_client.submit_blocking(register_role)?; let found_role = test_client.request(client::role::by_id(role_id))?; @@ -130,7 +130,7 @@ fn find_roles_by_account_id() -> Result<()> { .iter() .cloned() .map(|role_id| { - RegisterExpr::new(Role::new(role_id).add_permission(PermissionToken::new( + Register::role(Role::new(role_id).add_permission(PermissionToken::new( "CanSetKeyValueInUserAccount".parse().unwrap(), &json!({ "account_id": alice_id }), ))) @@ -142,7 +142,7 @@ fn find_roles_by_account_id() -> Result<()> { let grant_roles = role_ids .iter() .cloned() - .map(|role_id| GrantExpr::new(role_id, alice_id.clone())) + .map(|role_id| Grant::role(role_id, alice_id.clone())) .collect::>(); test_client.submit_all_blocking(grant_roles)?; diff --git a/client/tests/integration/restart_peer.rs b/client/tests/integration/restart_peer.rs index cfe153e3c9d..1699ae9763e 100644 --- a/client/tests/integration/restart_peer.rs +++ b/client/tests/integration/restart_peer.rs @@ -22,7 +22,8 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> { let account_id = AccountId::from_str("alice@wonderland").unwrap(); let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").unwrap(); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); let quantity: u32 = 200; let iroha_client = client::Client::test(&peer.api_address); @@ -38,12 +39,9 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> { wait_for_genesis_committed(&vec![iroha_client.clone()], 0); iroha_client.submit_blocking(create_asset)?; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); iroha_client.submit_blocking(mint_asset)?; diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index a89a939c69d..8523cfb39b6 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -14,7 +14,7 @@ fn register_empty_role() -> Result<()> { wait_for_genesis_committed(&vec![test_client.clone()], 0); let role_id = "root".parse().expect("Valid"); - let register_role = RegisterExpr::new(Role::new(role_id)); + let register_role = Register::role(Role::new(role_id)); test_client.submit(register_role)?; Ok(()) @@ -29,7 +29,7 @@ fn register_role_with_empty_token_params() -> Result<()> { let token = PermissionToken::new("token".parse()?, &json!(null)); let role = Role::new(role_id).add_permission(token); - test_client.submit(RegisterExpr::new(role))?; + test_client.submit(Register::role(role))?; Ok(()) } @@ -53,7 +53,7 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { // Registering Mouse let mouse_key_pair = iroha_crypto::KeyPair::generate()?; - let register_mouse = RegisterExpr::new(Account::new( + let register_mouse = Register::account(Account::new( mouse_id.clone(), [mouse_key_pair.public_key().clone()], )); @@ -70,18 +70,18 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { "CanRemoveKeyValueInUserAccount".parse()?, &json!({ "account_id": mouse_id }), )); - let register_role = RegisterExpr::new(role); + let register_role = Register::role(role); test_client.submit_blocking(register_role)?; // Mouse grants role to Alice - let grant_role = GrantExpr::new(role_id.clone(), alice_id.clone()); + let grant_role = Grant::role(role_id.clone(), alice_id.clone()); let grant_role_tx = TransactionBuilder::new(mouse_id.clone()) .with_instructions([grant_role]) .sign(mouse_key_pair)?; test_client.submit_transaction_blocking(&grant_role_tx)?; // Alice modifies Mouse's metadata - let set_key_value = SetKeyValueExpr::new( + let set_key_value = SetKeyValue::account( mouse_id, Name::from_str("key").expect("Valid"), Value::String("value".to_owned()), @@ -107,11 +107,11 @@ fn unregistered_role_removed_from_account() -> Result<()> { let mouse_id: AccountId = "mouse@wonderland".parse().expect("Valid"); // Registering Mouse - let register_mouse = RegisterExpr::new(Account::new(mouse_id.clone(), [])); + let register_mouse = Register::account(Account::new(mouse_id.clone(), [])); test_client.submit_blocking(register_mouse)?; // Register root role - let register_role = RegisterExpr::new(Role::new(role_id.clone()).add_permission( + let register_role = Register::role(Role::new(role_id.clone()).add_permission( PermissionToken::new( "CanSetKeyValueInUserAccount".parse()?, &json!({ "account_id": alice_id }), @@ -120,7 +120,7 @@ fn unregistered_role_removed_from_account() -> Result<()> { test_client.submit_blocking(register_role)?; // Grant root role to Mouse - let grant_role = GrantExpr::new(role_id.clone(), mouse_id.clone()); + let grant_role = Grant::role(role_id.clone(), mouse_id.clone()); test_client.submit_blocking(grant_role)?; // Check that Mouse has root role @@ -130,7 +130,7 @@ fn unregistered_role_removed_from_account() -> Result<()> { assert!(found_mouse_roles.contains(&role_id)); // Unregister root role - let unregister_role = UnregisterExpr::new(role_id.clone()); + let unregister_role = Unregister::role(role_id.clone()); test_client.submit_blocking(unregister_role)?; // Check that Mouse doesn't have the root role @@ -155,7 +155,7 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { )); let err = test_client - .submit_blocking(RegisterExpr::new(role)) + .submit_blocking(Register::role(role)) .expect_err("Submitting role with invalid permission token should fail"); let rejection_reason = err diff --git a/client/tests/integration/set_parameter.rs b/client/tests/integration/set_parameter.rs index 1308c7ea141..08012429e01 100644 --- a/client/tests/integration/set_parameter.rs +++ b/client/tests/integration/set_parameter.rs @@ -14,7 +14,7 @@ fn can_change_parameter_value() -> Result<()> { let parameter = Parameter::from_str("?BlockTime=4000")?; let parameter_id = ParameterId::from_str("BlockTime")?; - let param_box = SetParameterExpr::new(parameter); + let param_box = SetParameter::new(parameter); let old_params = test_client .request(client::parameter::all())? @@ -46,13 +46,13 @@ fn parameter_propagated() -> Result<()> { wait_for_genesis_committed(&vec![test_client.clone()], 0); let too_long_domain_name: DomainId = "0".repeat(2_usize.pow(8)).parse()?; - let create_domain = RegisterExpr::new(Domain::new(too_long_domain_name)); + let create_domain = Register::domain(Domain::new(too_long_domain_name)); let _ = test_client .submit_blocking(create_domain.clone()) .expect_err("Should fail before ident length limits update"); let parameter = Parameter::from_str("?WSVIdentLengthLimits=1,256_LL")?; - let param_box = SetParameterExpr::new(parameter); + let param_box = SetParameter::new(parameter); test_client.submit_blocking(param_box)?; test_client diff --git a/client/tests/integration/smartcontracts/create_nft_for_every_user_trigger/src/lib.rs b/client/tests/integration/smartcontracts/create_nft_for_every_user_trigger/src/lib.rs index 5fa18856b48..bce2802adcb 100644 --- a/client/tests/integration/smartcontracts/create_nft_for_every_user_trigger/src/lib.rs +++ b/client/tests/integration/smartcontracts/create_nft_for_every_user_trigger/src/lib.rs @@ -43,8 +43,10 @@ fn main(_owner: AccountId, _event: Event) { let account_nft_id = AssetId::new(nft_id, account.id().clone()); let account_nft = Asset::new(account_nft_id, Metadata::new()); - RegisterExpr::new(nft_definition).execute().dbg_unwrap(); - RegisterExpr::new(account_nft).execute().dbg_unwrap(); + Register::asset_definition(nft_definition) + .execute() + .dbg_unwrap(); + Register::asset(account_nft).execute().dbg_unwrap(); } iroha_trigger::log::info!("Smart contract executed successfully"); diff --git a/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs index 1b3b6443ef8..8a950ee38cd 100644 --- a/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs @@ -6,23 +6,22 @@ #[cfg(not(test))] extern crate panic_halt; -use iroha_executor::{parse, prelude::*, smart_contract}; +use iroha_executor::{parse, prelude::*}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); -#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[derive(Constructor, ValidateEntrypoints, Validate, Visit)] #[visit(custom(visit_instruction))] struct Executor { verdict: Result, block_height: u64, - host: smart_contract::Host, } -fn visit_instruction(executor: &mut Executor, authority: &AccountId, isi: &InstructionExpr) { +fn visit_instruction(executor: &mut Executor, authority: &AccountId, isi: &InstructionBox) { if parse!("admin@admin" as AccountId) == *authority { - pass!(executor); + execute!(executor, isi); } iroha_executor::default::visit_instruction(executor, authority, isi); diff --git a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs index f75d0e43fed..bd9322a7f87 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs @@ -20,7 +20,7 @@ use alloc::{borrow::ToOwned, string::String}; use anyhow::anyhow; use iroha_executor::{ - default::default_permission_token_schema, permission::Token as _, prelude::*, smart_contract, + default::default_permission_token_schema, permission::Token as _, prelude::*, }; use iroha_schema::IntoSchema; use lol_alloc::{FreeListAllocator, LockedAllocator}; @@ -54,12 +54,11 @@ mod token { pub struct CanControlDomainLives; } -#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[derive(Constructor, ValidateEntrypoints, Validate, Visit)] #[visit(custom(visit_register_domain, visit_unregister_domain))] struct Executor { verdict: Result, block_height: u64, - host: smart_contract::Host, } impl Executor { @@ -119,7 +118,7 @@ impl Executor { accounts .iter() .try_for_each(|(account, domain_id)| { - RevokeExpr::new( + Revoke::permission_token( PermissionToken::new( can_unregister_domain_definition_id.clone(), &json!({ "domain_id": domain_id }), @@ -138,7 +137,7 @@ impl Executor { ) })?; - GrantExpr::new( + Grant::permission_token( PermissionToken::new( can_control_domain_lives_definition_id.clone(), &json!(null), @@ -170,13 +169,12 @@ impl Executor { } } -// TODO (#4049): Fix unused `visit_register_domain()` -fn visit_register_domain(executor: &mut Executor, authority: &AccountId, _isi: Register) { +fn visit_register_domain(executor: &mut Executor, authority: &AccountId, isi: &Register) { if executor.block_height() == 0 { - pass!(executor) + execute!(executor, isi); } if token::CanControlDomainLives.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -188,13 +186,13 @@ fn visit_register_domain(executor: &mut Executor, authority: &AccountId, _isi: R fn visit_unregister_domain( executor: &mut Executor, authority: &AccountId, - _isi: Unregister, + isi: &Unregister, ) { if executor.block_height() == 0 { - pass!(executor); + execute!(executor, isi); } if token::CanControlDomainLives.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "You don't have permission to unregister domain"); diff --git a/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs index 437e01b9bdd..e603758dd1d 100644 --- a/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs @@ -9,17 +9,16 @@ extern crate panic_halt; use alloc::{borrow::ToOwned as _, format}; use anyhow::anyhow; -use iroha_executor::{parse, prelude::*, smart_contract}; +use iroha_executor::{parse, prelude::*}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); -#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[derive(Constructor, ValidateEntrypoints, Validate, Visit)] struct Executor { verdict: Result, block_height: u64, - host: smart_contract::Host, } #[entrypoint] @@ -28,7 +27,7 @@ pub fn migrate(_block_height: u64) -> MigrationResult { // Registering a new domain (using ISI) let domain_id = parse!("failed_migration_test_domain" as DomainId); - RegisterExpr::new(Domain::new(domain_id)) + Register::domain(Domain::new(domain_id)) .execute() .map_err(|error| { format!( diff --git a/client/tests/integration/smartcontracts/mint_rose_trigger/src/lib.rs b/client/tests/integration/smartcontracts/mint_rose_trigger/src/lib.rs index 7dd2d5c7c0d..f794772bebd 100644 --- a/client/tests/integration/smartcontracts/mint_rose_trigger/src/lib.rs +++ b/client/tests/integration/smartcontracts/mint_rose_trigger/src/lib.rs @@ -20,7 +20,7 @@ fn main(owner: AccountId, _event: Event) { .dbg_expect("Failed to parse `rose#wonderland` asset definition id"); let rose_id = AssetId::new(rose_definition_id, owner); - MintExpr::new(1_u32, rose_id) + Mint::asset_quantity(1_u32, rose_id) .execute() .dbg_expect("Failed to mint rose"); } diff --git a/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs index c86e452e693..87137474596 100644 --- a/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs +++ b/client/tests/integration/smartcontracts/query_assets_and_save_cursor/src/lib.rs @@ -26,12 +26,14 @@ fn main(owner: AccountId) { let (_batch, cursor) = asset_cursor.into_raw_parts(); - SetKeyValueExpr::new( + SetKeyValue::account( owner, parse!("cursor" as Name), - serde_json::to_value(cursor) - .dbg_expect("Failed to convert cursor to JSON") - .to_string(), + Value::String( + serde_json::to_value(cursor) + .dbg_expect("Failed to convert cursor to JSON") + .to_string(), + ), ) .execute() .dbg_expect("Failed to save cursor to the owner's metadata"); diff --git a/client/tests/integration/sorting.rs b/client/tests/integration/sorting.rs index 31b974d2b05..19f69f3b86e 100644 --- a/client/tests/integration/sorting.rs +++ b/client/tests/integration/sorting.rs @@ -14,6 +14,7 @@ use iroha_client::{ query::{Pagination, Sorting}, }, }; +use iroha_data_model::isi::InstructionBox; use test_network::*; #[test] @@ -46,8 +47,9 @@ fn correct_pagination_assets_after_creating_new_one() { assets.push(asset.clone()); - let create_asset_definition = RegisterExpr::new(asset_definition); - let create_asset = RegisterExpr::new(asset); + let create_asset_definition: InstructionBox = + Register::asset_definition(asset_definition).into(); + let create_asset = Register::asset(asset).into(); instructions.push(create_asset_definition); instructions.push(create_asset); @@ -94,8 +96,9 @@ fn correct_pagination_assets_after_creating_new_one() { AssetValue::Store(new_asset_metadata), ); - let create_asset_definition = RegisterExpr::new(new_asset_definition); - let create_asset = RegisterExpr::new(new_asset.clone()); + let create_asset_definition: InstructionBox = + Register::asset_definition(new_asset_definition).into(); + let create_asset = Register::asset(new_asset.clone()).into(); test_client .submit_all_blocking([create_asset_definition, create_asset]) @@ -153,7 +156,7 @@ fn correct_sorting_of_entities() { metadata_of_assets.push(asset_metadata); asset_definitions.push(asset_definition_id); - let create_asset_definition = RegisterExpr::new(asset_definition); + let create_asset_definition = Register::asset_definition(asset_definition); instructions.push(create_asset_definition); } @@ -203,7 +206,7 @@ fn correct_sorting_of_entities() { accounts.push(account_id); metadata_of_accounts.push(account_metadata); - let create_account = RegisterExpr::new(account); + let create_account = Register::account(account); instructions.push(create_account); } @@ -249,7 +252,7 @@ fn correct_sorting_of_entities() { domains.push(domain_id); metadata_of_domains.push(domain_metadata); - let create_account = RegisterExpr::new(domain); + let create_account = Register::domain(domain); instructions.push(create_account); } @@ -294,7 +297,7 @@ fn correct_sorting_of_entities() { domains.push(domain_id); metadata_of_domains.push(domain_metadata); - let create_account = RegisterExpr::new(domain); + let create_account = Register::domain(domain); instructions.push(create_account); } test_client @@ -356,7 +359,7 @@ fn sort_only_elements_which_have_sorting_key() -> Result<()> { account }; - let create_account = RegisterExpr::new(account); + let create_account = Register::account(account); instructions.push(create_account); } diff --git a/client/tests/integration/transfer_asset.rs b/client/tests/integration/transfer_asset.rs index b62ba7dc216..cbc23b190fa 100644 --- a/client/tests/integration/transfer_asset.rs +++ b/client/tests/integration/transfer_asset.rs @@ -3,17 +3,32 @@ use iroha_client::{ data_model::{prelude::*, Registered}, }; use iroha_crypto::KeyPair; +use iroha_data_model::isi::Instruction; use iroha_primitives::fixed::Fixed; use test_network::*; #[test] fn simulate_transfer_quantity() { - simulate_transfer(200_u32, &20_u32, AssetDefinition::quantity, 10_710) + simulate_transfer( + 200_u32, + &20_u32, + AssetDefinition::quantity, + Mint::asset_quantity, + Transfer::asset_quantity, + 10_710, + ) } #[test] fn simulate_transfer_big_quantity() { - simulate_transfer(200_u128, &20_u128, AssetDefinition::big_quantity, 10_785) + simulate_transfer( + 200_u128, + &20_u128, + AssetDefinition::big_quantity, + Mint::asset_big_quantity, + Transfer::asset_big_quantity, + 10_785, + ) } #[test] @@ -22,6 +37,8 @@ fn simulate_transfer_fixed() { Fixed::try_from(200_f64).expect("Valid"), &Fixed::try_from(20_f64).expect("Valid"), AssetDefinition::fixed, + Mint::asset_fixed, + Transfer::asset_fixed, 10_790, ) } @@ -34,22 +51,24 @@ fn simulate_insufficient_funds() { Fixed::try_from(20_f64).expect("Valid"), &Fixed::try_from(200_f64).expect("Valid"), AssetDefinition::fixed, + Mint::asset_fixed, + Transfer::asset_fixed, 10_800, ) } -// TODO add tests when the transfer uses the wrong AssetId. - -fn simulate_transfer< - T: Into + Clone, - D: FnOnce(AssetDefinitionId) -> ::With, ->( +fn simulate_transfer( starting_amount: T, amount_to_transfer: &T, - value_type: D, + asset_definition_ctr: impl FnOnce(AssetDefinitionId) -> ::With, + mint_ctr: impl FnOnce(T, AssetId) -> Mint, + transfer_ctr: impl FnOnce(AssetId, T, AccountId) -> Transfer, port_number: u16, ) where + T: std::fmt::Debug + Clone + Into, Value: From, + Mint: Instruction, + Transfer: Instruction, { let (_rt, _peer, iroha_client) = ::new() .with_port(port_number) @@ -61,15 +80,16 @@ fn simulate_transfer< let (bob_public_key, _) = KeyPair::generate() .expect("Failed to generate KeyPair") .into(); - let create_mouse = RegisterExpr::new(Account::new(mouse_id.clone(), [bob_public_key])); + let create_mouse = Register::account(Account::new(mouse_id.clone(), [bob_public_key])); let asset_definition_id: AssetDefinitionId = "camomile#wonderland".parse().expect("Valid"); - let create_asset = RegisterExpr::new(value_type(asset_definition_id.clone())); - let mint_asset = MintExpr::new( - starting_amount.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id.clone(), alice_id.clone())), + let create_asset = + Register::asset_definition(asset_definition_ctr(asset_definition_id.clone())); + let mint_asset = mint_ctr( + starting_amount, + AssetId::new(asset_definition_id.clone(), alice_id.clone()), ); - let instructions: [InstructionExpr; 3] = [ + let instructions: [InstructionBox; 3] = [ // create_alice.into(), We don't need to register Alice, because she is created in genesis create_mouse.into(), create_asset.into(), @@ -80,10 +100,10 @@ fn simulate_transfer< .expect("Failed to prepare state."); //When - let transfer_asset = TransferExpr::new( - IdBox::AssetId(AssetId::new(asset_definition_id.clone(), alice_id)), - amount_to_transfer.clone().to_value(), - IdBox::AccountId(mouse_id.clone()), + let transfer_asset = transfer_ctr( + AssetId::new(asset_definition_id.clone(), alice_id), + amount_to_transfer.clone(), + mouse_id.clone(), ); iroha_client .submit_till( diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index a2b03df24d7..41644169a73 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -9,6 +9,7 @@ use iroha_client::{ transaction::Executable, }, }; +use iroha_data_model::events::TriggeringFilterBox; use iroha_genesis::GenesisNetwork; use iroha_logger::info; use test_network::*; @@ -25,12 +26,12 @@ fn call_execute_trigger() -> Result<()> { let asset_id = AssetId::new(asset_definition_id, account_id); let prev_value = get_asset_value(&mut test_client, asset_id.clone())?; - let instruction = MintExpr::new(1_u32, asset_id.clone()); + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); let register_trigger = build_register_trigger_isi(asset_id.clone(), vec![instruction.into()]); test_client.submit_blocking(register_trigger)?; let trigger_id = TriggerId::from_str(TRIGGER_NAME)?; - let call_trigger = ExecuteTriggerExpr::new(trigger_id); + let call_trigger = ExecuteTrigger::new(trigger_id); test_client.submit_blocking(call_trigger)?; let new_value = get_asset_value(&mut test_client, asset_id)?; @@ -48,12 +49,12 @@ fn execute_trigger_should_produce_event() -> Result<()> { let account_id: AccountId = "alice@wonderland".parse()?; let asset_id = AssetId::new(asset_definition_id, account_id.clone()); - let instruction = MintExpr::new(1_u32, asset_id.clone()); + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); let register_trigger = build_register_trigger_isi(asset_id, vec![instruction.into()]); test_client.submit_blocking(register_trigger)?; let trigger_id = TriggerId::from_str(TRIGGER_NAME)?; - let call_trigger = ExecuteTriggerExpr::new(trigger_id.clone()); + let call_trigger = ExecuteTrigger::new(trigger_id.clone()); let thread_client = test_client.clone(); let (sender, receiver) = mpsc::channel(); @@ -83,11 +84,11 @@ fn infinite_recursion_should_produce_one_call_per_block() -> Result<()> { let account_id = "alice@wonderland".parse()?; let asset_id = AssetId::new(asset_definition_id, account_id); let trigger_id = TriggerId::from_str(TRIGGER_NAME)?; - let call_trigger = ExecuteTriggerExpr::new(trigger_id); + let call_trigger = ExecuteTrigger::new(trigger_id); let prev_value = get_asset_value(&mut test_client, asset_id.clone())?; let instructions = vec![ - MintExpr::new(1_u32, asset_id.clone()).into(), + Mint::asset_quantity(1_u32, asset_id.clone()).into(), call_trigger.clone().into(), ]; let register_trigger = build_register_trigger_isi(asset_id.clone(), instructions); @@ -113,8 +114,8 @@ fn trigger_failure_should_not_cancel_other_triggers_execution() -> Result<()> { // Registering trigger that should fail on execution let bad_trigger_id = TriggerId::from_str("bad_trigger")?; // Invalid instruction - let bad_trigger_instructions = vec![MintExpr::new(1_u32, account_id.clone())]; - let register_bad_trigger = RegisterExpr::new(Trigger::new( + let bad_trigger_instructions = vec![Fail::new("Bad trigger".to_owned())]; + let register_bad_trigger = Register::trigger(Trigger::new( bad_trigger_id.clone(), Action::new( bad_trigger_instructions, @@ -130,8 +131,8 @@ fn trigger_failure_should_not_cancel_other_triggers_execution() -> Result<()> { // Registering normal trigger let trigger_id = TriggerId::from_str(TRIGGER_NAME)?; - let trigger_instructions = vec![MintExpr::new(1_u32, asset_id.clone())]; - let register_trigger = RegisterExpr::new(Trigger::new( + let trigger_instructions = vec![Mint::asset_quantity(1_u32, asset_id.clone())]; + let register_trigger = Register::trigger(Trigger::new( trigger_id, Action::new( trigger_instructions, @@ -147,7 +148,7 @@ fn trigger_failure_should_not_cancel_other_triggers_execution() -> Result<()> { let prev_asset_value = get_asset_value(&mut test_client, asset_id.clone())?; // Executing bad trigger - test_client.submit_blocking(ExecuteTriggerExpr::new(bad_trigger_id))?; + test_client.submit_blocking(ExecuteTrigger::new(bad_trigger_id))?; // Checking results let new_asset_value = get_asset_value(&mut test_client, asset_id)?; @@ -165,8 +166,8 @@ fn trigger_should_not_be_executed_with_zero_repeats_count() -> Result<()> { let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let trigger_id = TriggerId::from_str("self_modifying_trigger")?; - let trigger_instructions = vec![MintExpr::new(1_u32, asset_id.clone())]; - let register_trigger = RegisterExpr::new(Trigger::new( + let trigger_instructions = vec![Mint::asset_quantity(1_u32, asset_id.clone())]; + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( trigger_instructions, @@ -184,7 +185,7 @@ fn trigger_should_not_be_executed_with_zero_repeats_count() -> Result<()> { let prev_asset_value = get_asset_value(&mut test_client, asset_id.clone())?; // Executing trigger first time - let execute_trigger = ExecuteTriggerExpr::new(trigger_id.clone()); + let execute_trigger = ExecuteTrigger::new(trigger_id.clone()); test_client.submit_blocking(execute_trigger.clone())?; // Executing trigger second time @@ -224,10 +225,10 @@ fn trigger_should_be_able_to_modify_its_own_repeats_count() -> Result<()> { let trigger_id = TriggerId::from_str("self_modifying_trigger")?; let trigger_instructions = vec![ - MintExpr::new(1_u32, trigger_id.clone()), - MintExpr::new(1_u32, asset_id.clone()), + InstructionBox::from(Mint::trigger_repetitions(1_u32, trigger_id.clone())), + InstructionBox::from(Mint::asset_quantity(1_u32, asset_id.clone())), ]; - let register_trigger = RegisterExpr::new(Trigger::new( + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( trigger_instructions, @@ -245,7 +246,7 @@ fn trigger_should_be_able_to_modify_its_own_repeats_count() -> Result<()> { let prev_asset_value = get_asset_value(&mut test_client, asset_id.clone())?; // Executing trigger first time - let execute_trigger = ExecuteTriggerExpr::new(trigger_id); + let execute_trigger = ExecuteTrigger::new(trigger_id); test_client.submit_blocking(execute_trigger.clone())?; // Executing trigger second time @@ -270,7 +271,7 @@ fn unregister_trigger() -> Result<()> { let trigger = Trigger::new( trigger_id.clone(), Action::new( - Vec::::new(), + Vec::::new(), Repeats::Indefinitely, account_id.clone(), TriggeringFilterBox::ExecuteTrigger(ExecuteTriggerEventFilter::new( @@ -279,12 +280,12 @@ fn unregister_trigger() -> Result<()> { )), ), ); - let register_trigger = RegisterExpr::new(trigger.clone()); + let register_trigger = Register::trigger(trigger.clone()); test_client.submit_blocking(register_trigger)?; // Finding trigger let find_trigger = FindTriggerById { - id: trigger_id.clone().into(), + id: trigger_id.clone(), }; let found_trigger = test_client.request(find_trigger.clone())?; let found_action = found_trigger.action; @@ -303,7 +304,7 @@ fn unregister_trigger() -> Result<()> { assert_eq!(found_trigger, trigger); // Unregistering trigger - let unregister_trigger = UnregisterExpr::new(trigger_id); + let unregister_trigger = Unregister::trigger(trigger_id); test_client.submit_blocking(unregister_trigger)?; // Checking result @@ -362,7 +363,7 @@ fn trigger_in_genesis_using_base64() -> Result<()> { let tx_ref = &mut genesis.transactions[0].0; match &mut tx_ref.payload_mut().instructions { Executable::Instructions(instructions) => { - instructions.push(RegisterExpr::new(trigger).into()); + instructions.push(Register::trigger(trigger).into()); } Executable::Wasm(_) => panic!("Expected instructions"), } @@ -378,7 +379,7 @@ fn trigger_in_genesis_using_base64() -> Result<()> { let prev_value = get_asset_value(&mut test_client, asset_id.clone())?; // Executing trigger - let call_trigger = ExecuteTriggerExpr::new(trigger_id); + let call_trigger = ExecuteTrigger::new(trigger_id); test_client.submit_blocking(call_trigger)?; // Checking result @@ -397,12 +398,11 @@ fn trigger_should_be_able_to_modify_other_trigger() -> Result<()> { let account_id = AccountId::from_str("alice@wonderland")?; let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let trigger_id_unregister = TriggerId::from_str("unregister_other_trigger")?; - let trigger_id_should_be_unregistered = TriggerId::from_str("should_be_unregistered_trigger")?; + let trigger_id_to_be_unregistered = TriggerId::from_str("should_be_unregistered_trigger")?; - let trigger_unregister_instructions = vec![UnregisterExpr::new( - trigger_id_should_be_unregistered.clone(), - )]; - let register_trigger = RegisterExpr::new(Trigger::new( + let trigger_unregister_instructions = + vec![Unregister::trigger(trigger_id_to_be_unregistered.clone())]; + let register_trigger = Register::trigger(Trigger::new( trigger_id_unregister.clone(), Action::new( trigger_unregister_instructions, @@ -416,15 +416,16 @@ fn trigger_should_be_able_to_modify_other_trigger() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - let trigger_should_be_unregistered_instructions = vec![MintExpr::new(1_u32, asset_id.clone())]; - let register_trigger = RegisterExpr::new(Trigger::new( - trigger_id_should_be_unregistered.clone(), + let trigger_should_be_unregistered_instructions = + vec![Mint::asset_quantity(1_u32, asset_id.clone())]; + let register_trigger = Register::trigger(Trigger::new( + trigger_id_to_be_unregistered.clone(), Action::new( trigger_should_be_unregistered_instructions, Repeats::from(1_u32), account_id.clone(), TriggeringFilterBox::ExecuteTrigger(ExecuteTriggerEventFilter::new( - trigger_id_should_be_unregistered.clone(), + trigger_id_to_be_unregistered.clone(), account_id, )), ), @@ -435,9 +436,8 @@ fn trigger_should_be_able_to_modify_other_trigger() -> Result<()> { let prev_asset_value = get_asset_value(&mut test_client, asset_id.clone())?; // Executing triggers - let execute_trigger_unregister = ExecuteTriggerExpr::new(trigger_id_unregister); - let execute_trigger_should_be_unregistered = - ExecuteTriggerExpr::new(trigger_id_should_be_unregistered); + let execute_trigger_unregister = ExecuteTrigger::new(trigger_id_unregister); + let execute_trigger_should_be_unregistered = ExecuteTrigger::new(trigger_id_to_be_unregistered); test_client.submit_all_blocking([ execute_trigger_unregister, execute_trigger_should_be_unregistered, @@ -461,8 +461,8 @@ fn trigger_burn_repetitions() -> Result<()> { let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let trigger_id = TriggerId::from_str("trigger")?; - let trigger_instructions = vec![MintExpr::new(1_u32, asset_id)]; - let register_trigger = RegisterExpr::new(Trigger::new( + let trigger_instructions = vec![Mint::asset_quantity(1_u32, asset_id)]; + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( trigger_instructions, @@ -476,10 +476,10 @@ fn trigger_burn_repetitions() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - test_client.submit_blocking(BurnExpr::new(1_u32, trigger_id.clone()))?; + test_client.submit_blocking(Burn::trigger_repetitions(1_u32, trigger_id.clone()))?; // Executing trigger - let execute_trigger = ExecuteTriggerExpr::new(trigger_id); + let execute_trigger = ExecuteTrigger::new(trigger_id); let _err = test_client .submit_blocking(execute_trigger) .expect_err("Should fail without repetitions"); @@ -494,11 +494,11 @@ fn get_asset_value(client: &mut Client, asset_id: AssetId) -> Result { fn build_register_trigger_isi( asset_id: AssetId, - trigger_instructions: Vec, -) -> RegisterExpr { + trigger_instructions: Vec, +) -> Register> { let trigger_id: TriggerId = TRIGGER_NAME.parse().expect("Valid"); - RegisterExpr::new(Trigger::new( + Register::trigger(Trigger::new( trigger_id.clone(), Action::new( trigger_instructions, diff --git a/client/tests/integration/triggers/data_trigger.rs b/client/tests/integration/triggers/data_trigger.rs index 46744151559..e7096f7b024 100644 --- a/client/tests/integration/triggers/data_trigger.rs +++ b/client/tests/integration/triggers/data_trigger.rs @@ -13,8 +13,8 @@ fn must_execute_both_triggers() -> Result<()> { let prev_value = get_asset_value(&test_client, asset_id.clone())?; - let instruction = MintExpr::new(1_u32, asset_id.clone()); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); + let register_trigger = Register::trigger(Trigger::new( "mint_rose_1".parse()?, Action::new( [instruction.clone()], @@ -27,7 +27,7 @@ fn must_execute_both_triggers() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - let register_trigger = RegisterExpr::new(Trigger::new( + let register_trigger = Register::trigger(Trigger::new( "mint_rose_2".parse()?, Action::new( [instruction], @@ -40,11 +40,11 @@ fn must_execute_both_triggers() -> Result<()> { )); test_client.submit_blocking(register_trigger)?; - test_client.submit_blocking(RegisterExpr::new(Account::new( + test_client.submit_blocking(Register::account(Account::new( "bunny@wonderland".parse()?, [], )))?; - test_client.submit_blocking(RegisterExpr::new(Domain::new("neverland".parse()?)))?; + test_client.submit_blocking(Register::domain(Domain::new("neverland".parse()?)))?; let new_value = get_asset_value(&test_client, asset_id)?; assert_eq!(new_value, prev_value + 2); @@ -57,18 +57,19 @@ fn domain_scoped_trigger_must_be_executed_only_on_events_in_its_domain() -> Resu let (_rt, _peer, test_client) = ::new().with_port(10_655).start_with_runtime(); wait_for_genesis_committed(&[test_client.clone()], 0); - let create_neverland_domain = RegisterExpr::new(Domain::new("neverland".parse()?)); + let create_neverland_domain: InstructionBox = + Register::domain(Domain::new("neverland".parse()?)).into(); let account_id: AccountId = "sapporo@neverland".parse()?; - let create_sapporo_account = RegisterExpr::new(Account::new(account_id.clone(), [])); + let create_sapporo_account = Register::account(Account::new(account_id.clone(), [])).into(); let asset_definition_id: AssetDefinitionId = "sakura#neverland".parse()?; let create_sakura_asset_definition = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())).into(); let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let create_sakura_asset = - RegisterExpr::new(Asset::new(asset_id.clone(), AssetValue::Quantity(0))); + Register::asset(Asset::new(asset_id.clone(), AssetValue::Quantity(0))).into(); test_client.submit_all_blocking([ create_neverland_domain, @@ -79,10 +80,10 @@ fn domain_scoped_trigger_must_be_executed_only_on_events_in_its_domain() -> Resu let prev_value = get_asset_value(&test_client, asset_id.clone())?; - let register_trigger = RegisterExpr::new(Trigger::new( + let register_trigger = Register::trigger(Trigger::new( "mint_sakura$neverland".parse()?, Action::new( - [MintExpr::new(1_u32, asset_id.clone())], + [Mint::asset_quantity(1_u32, asset_id.clone())], Repeats::Indefinitely, account_id, TriggeringFilterBox::Data(BySome(DataEntityFilter::ByAccount(BySome( @@ -92,12 +93,12 @@ fn domain_scoped_trigger_must_be_executed_only_on_events_in_its_domain() -> Resu )); test_client.submit_blocking(register_trigger)?; - test_client.submit_blocking(RegisterExpr::new(Account::new( + test_client.submit_blocking(Register::account(Account::new( "asahi@wonderland".parse()?, [], )))?; - test_client.submit_blocking(RegisterExpr::new(Account::new( + test_client.submit_blocking(Register::account(Account::new( "asahi@neverland".parse()?, [], )))?; diff --git a/client/tests/integration/triggers/event_trigger.rs b/client/tests/integration/triggers/event_trigger.rs index 8b438d47fb9..8269a244ad4 100644 --- a/client/tests/integration/triggers/event_trigger.rs +++ b/client/tests/integration/triggers/event_trigger.rs @@ -17,8 +17,8 @@ fn test_mint_asset_when_new_asset_definition_created() -> Result<()> { let asset_id = AssetId::new(asset_definition_id, account_id.clone()); let prev_value = get_asset_value(&mut test_client, asset_id.clone())?; - let instruction = MintExpr::new(1_u32, asset_id.clone()); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); + let register_trigger = Register::trigger(Trigger::new( "mint_rose".parse()?, Action::new( vec![instruction], @@ -35,7 +35,8 @@ fn test_mint_asset_when_new_asset_definition_created() -> Result<()> { test_client.submit(register_trigger)?; let tea_definition_id = "tea#wonderland".parse()?; - let register_tea_definition = RegisterExpr::new(AssetDefinition::quantity(tea_definition_id)); + let register_tea_definition = + Register::asset_definition(AssetDefinition::quantity(tea_definition_id)); test_client.submit_blocking(register_tea_definition)?; 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 2a150f336c4..9b9c76d3fe6 100644 --- a/client/tests/integration/triggers/time_trigger.rs +++ b/client/tests/integration/triggers/time_trigger.rs @@ -44,8 +44,8 @@ fn time_trigger_execution_count_error_should_be_less_than_15_percent() -> Result let schedule = TimeSchedule::starting_at(start_time).with_period(Duration::from_millis(PERIOD_MS)); - let instruction = MintExpr::new(1_u32, asset_id.clone()); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); + let register_trigger = Register::trigger(Trigger::new( "mint_rose".parse()?, Action::new( vec![instruction], @@ -98,8 +98,8 @@ fn change_asset_metadata_after_1_sec() -> Result<()> { let schedule = TimeSchedule::starting_at(start_time + Duration::from_millis(PERIOD_MS)); let instruction = - SetKeyValueExpr::new(asset_definition_id.clone(), key.clone(), 3_u32.to_value()); - let register_trigger = RegisterExpr::new(Trigger::new( + SetKeyValue::asset_definition(asset_definition_id.clone(), key.clone(), 3_u32.to_value()); + let register_trigger = Register::trigger(Trigger::new( "change_rose_metadata".parse().expect("Valid"), Action::new( vec![instruction], @@ -119,8 +119,8 @@ fn change_asset_metadata_after_1_sec() -> Result<()> { let value = test_client .request(FindAssetDefinitionKeyValueByIdAndKey { - id: asset_definition_id.into(), - key: key.into(), + id: asset_definition_id, + key, })? .into(); assert!(matches!(value, Value::Numeric(NumericValue::U32(3_u32)))); @@ -144,8 +144,8 @@ fn pre_commit_trigger_should_be_executed() -> Result<()> { // Start listening BEFORE submitting any transaction not to miss any block committed event let event_listener = get_block_committed_event_listener(&test_client)?; - let instruction = MintExpr::new(1_u32, asset_id.clone()); - let register_trigger = RegisterExpr::new(Trigger::new( + let instruction = Mint::asset_quantity(1_u32, asset_id.clone()); + let register_trigger = Register::trigger(Trigger::new( "mint_rose".parse()?, Action::new( vec![instruction], @@ -162,7 +162,7 @@ fn pre_commit_trigger_should_be_executed() -> Result<()> { prev_value = new_value; // ISI just to create a new block - let sample_isi = SetKeyValueExpr::new( + let sample_isi = SetKeyValue::account( account_id.clone(), "key".parse::()?, String::from("value"), @@ -196,7 +196,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { .iter() .skip(1) // Alice has already been registered in genesis .cloned() - .map(|account_id| RegisterExpr::new(Account::new(account_id, []))) + .map(|account_id| Register::account(Account::new(account_id, []))) .collect::>(); test_client.submit_all_blocking(register_accounts)?; @@ -220,7 +220,7 @@ fn mint_nft_for_every_user_every_1_sec() -> Result<()> { let start_time = current_time(); let schedule = TimeSchedule::starting_at(start_time).with_period(Duration::from_millis(TRIGGER_PERIOD_MS)); - let register_trigger = RegisterExpr::new(Trigger::new( + let register_trigger = Register::trigger(Trigger::new( "mint_nft_for_all".parse()?, Action::new( WasmSmartContract::from_compiled(wasm), @@ -295,7 +295,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 = SetKeyValueExpr::new( + let sample_isi = SetKeyValue::account( account_id.clone(), "key".parse::()?, String::from("value"), diff --git a/client/tests/integration/triggers/trigger_rollback.rs b/client/tests/integration/triggers/trigger_rollback.rs index 67861a9f7b2..182045a2c7e 100644 --- a/client/tests/integration/triggers/trigger_rollback.rs +++ b/client/tests/integration/triggers/trigger_rollback.rs @@ -14,9 +14,13 @@ fn failed_trigger_revert() -> Result<()> { let trigger_id = TriggerId::from_str("trigger")?; let account_id = AccountId::from_str("alice@wonderland")?; let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland")?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); - let instructions: [InstructionExpr; 2] = [create_asset.into(), Fail::new("Always fail").into()]; - let register_trigger = RegisterExpr::new(Trigger::new( + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); + let instructions: [InstructionBox; 2] = [ + create_asset.into(), + Fail::new("Always fail".to_owned()).into(), + ]; + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( instructions, @@ -30,7 +34,7 @@ fn failed_trigger_revert() -> Result<()> { )); let _ = client.submit_blocking(register_trigger); - let call_trigger = ExecuteTriggerExpr::new(trigger_id); + let call_trigger = ExecuteTrigger::new(trigger_id); client.submit_blocking(call_trigger)?; //Then diff --git a/client/tests/integration/tx_history.rs b/client/tests/integration/tx_history.rs index 15c2ef3c3e1..8cbf9a3f5cc 100644 --- a/client/tests/integration/tx_history.rs +++ b/client/tests/integration/tx_history.rs @@ -24,19 +24,20 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() -> Result<()> // Given let account_id = AccountId::from_str("alice@wonderland")?; let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland")?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); client.submit_blocking(create_asset)?; //When let quantity: u32 = 200; let asset_id = AssetId::new(asset_definition_id, account_id.clone()); - let mint_existed_asset = MintExpr::new(quantity.to_value(), IdBox::AssetId(asset_id)); - let mint_not_existed_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( + let mint_existed_asset = Mint::asset_quantity(quantity, asset_id); + let mint_not_existed_asset = Mint::asset_quantity( + quantity, + AssetId::new( AssetDefinitionId::from_str("foo#wonderland")?, account_id.clone(), - )), + ), ); let transactions_count = 100; @@ -47,7 +48,7 @@ fn client_has_rejected_and_acepted_txs_should_return_tx_history() -> Result<()> } else { &mint_not_existed_asset }; - let instructions: Vec = vec![mint_asset.clone().into()]; + let instructions: Vec = vec![mint_asset.clone().into()]; let transaction = client.build_transaction(instructions, UnlimitedMetadata::new())?; client.submit_transaction(&transaction)?; } diff --git a/client/tests/integration/tx_rollback.rs b/client/tests/integration/tx_rollback.rs index a8b1b918dc9..0c04bbec3a8 100644 --- a/client/tests/integration/tx_rollback.rs +++ b/client/tests/integration/tx_rollback.rs @@ -16,16 +16,13 @@ fn client_sends_transaction_with_invalid_instruction_should_not_see_any_changes( let account_id = AccountId::from_str("alice@wonderland")?; let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland")?; let wrong_asset_definition_id = AssetDefinitionId::from_str("ksor#wonderland")?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id)); + let create_asset = Register::asset_definition(AssetDefinition::quantity(asset_definition_id)); let quantity: u32 = 200; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - wrong_asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(wrong_asset_definition_id.clone(), account_id.clone()), ); - let instructions: [InstructionExpr; 2] = [create_asset.into(), mint_asset.into()]; + let instructions: [InstructionBox; 2] = [create_asset.into(), mint_asset.into()]; let _ = client.submit_all_blocking(instructions); //Then diff --git a/client/tests/integration/unregister_peer.rs b/client/tests/integration/unregister_peer.rs index 845f938de16..84a2e4fa5b3 100644 --- a/client/tests/integration/unregister_peer.rs +++ b/client/tests/integration/unregister_peer.rs @@ -34,7 +34,7 @@ fn unstable_network_stable_after_add_and_after_remove_peer() -> Result<()> { // Then the new peer should already have the mint result. check_assets(&peer_client, &account_id, &asset_definition_id, 100); // Also, when a peer is unregistered - let remove_peer = UnregisterExpr::new(IdBox::PeerId(peer.id.clone())); + let remove_peer = Unregister::peer(peer.id.clone()); genesis_client.submit(remove_peer)?; thread::sleep(pipeline_time * 2); // We can mint without error. @@ -82,12 +82,9 @@ fn mint( pipeline_time: std::time::Duration, quantity: u32, ) -> Result { - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); client.submit(mint_asset)?; thread::sleep(pipeline_time * 5); @@ -109,18 +106,21 @@ fn init() -> Result<( let parameters = ParametersBuilder::new() .add_parameter(MAX_TRANSACTIONS_IN_BLOCK, 1u32)? .into_set_parameters(); - let create_domain = RegisterExpr::new(Domain::new("domain".parse()?)); + let create_domain = Register::domain(Domain::new("domain".parse()?)); let account_id: AccountId = "account@domain".parse()?; let (public_key, _) = KeyPair::generate()?.into(); - let create_account = RegisterExpr::new(Account::new(account_id.clone(), [public_key])); + let create_account = Register::account(Account::new(account_id.clone(), [public_key])); let asset_definition_id: AssetDefinitionId = "xor#domain".parse()?; - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); - let instructions: [InstructionExpr; 4] = [ - parameters.into(), - create_domain.into(), - create_account.into(), - create_asset.into(), - ]; + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); + let instructions = parameters.into_iter().chain( + [ + create_domain.into(), + create_account.into(), + create_asset.into(), + ] + .into_iter(), + ); client.submit_all_blocking(instructions)?; iroha_logger::info!("Init"); Ok(( diff --git a/client/tests/integration/unstable_network.rs b/client/tests/integration/unstable_network.rs index da962f4728d..d0cd9ce186b 100644 --- a/client/tests/integration/unstable_network.rs +++ b/client/tests/integration/unstable_network.rs @@ -77,7 +77,8 @@ fn unstable_network( let account_id: AccountId = "alice@wonderland".parse().expect("Valid"); let asset_definition_id: AssetDefinitionId = "camomile#wonderland".parse().expect("Valid"); - let register_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + let register_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); iroha_client .submit_blocking(register_asset) .expect("Failed to register asset"); @@ -99,12 +100,9 @@ fn unstable_network( } let quantity = 1; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new( - asset_definition_id.clone(), - account_id.clone(), - )), + let mint_asset = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id.clone(), account_id.clone()), ); iroha_client .submit(mint_asset) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 5c1de0736c4..cfa822046c2 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -17,19 +17,19 @@ fn executor_upgrade_should_work() -> Result<()> { // Register `admin` domain and account let admin_domain = Domain::new("admin".parse()?); - let register_admin_domain = RegisterExpr::new(admin_domain); + let register_admin_domain = Register::domain(admin_domain); client.submit_blocking(register_admin_domain)?; let admin_id: AccountId = "admin@admin".parse()?; let admin_keypair = KeyPair::generate()?; let admin_account = Account::new(admin_id.clone(), [admin_keypair.public_key().clone()]); - let register_admin_account = RegisterExpr::new(admin_account); + let register_admin_account = Register::account(admin_account); client.submit_blocking(register_admin_account)?; // Check that admin isn't allowed to transfer alice's rose by default let alice_rose: AssetId = "rose##alice@wonderland".parse()?; let admin_rose: AccountId = "admin@admin".parse()?; - let transfer_alice_rose = TransferExpr::new(alice_rose, NumericValue::U32(1), admin_rose); + let transfer_alice_rose = Transfer::asset_quantity(alice_rose, 1_u32, admin_rose); let transfer_rose_tx = TransactionBuilder::new(admin_id.clone()) .with_instructions([transfer_alice_rose.clone()]) .sign(admin_keypair.clone())?; @@ -152,7 +152,7 @@ fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { info!("WASM size is {} bytes", wasm.len()); - let upgrade_executor = UpgradeExpr::new(Executor::new(WasmSmartContract::from_compiled(wasm))); + let upgrade_executor = Upgrade::new(Executor::new(WasmSmartContract::from_compiled(wasm))); client.submit_blocking(upgrade_executor)?; Ok(()) diff --git a/client_cli/src/main.rs b/client_cli/src/main.rs index c16c633fd56..8aca7cef98c 100644 --- a/client_cli/src/main.rs +++ b/client_cli/src/main.rs @@ -391,7 +391,7 @@ mod domain { id, metadata: Metadata(metadata), } = self; - let create_domain = RegisterExpr::new(Domain::new(id)); + let create_domain = iroha_client::data_model::isi::Register::domain(Domain::new(id)); submit([create_domain], metadata, context).wrap_err("Failed to create domain") } } @@ -449,7 +449,7 @@ mod domain { to, metadata: Metadata(metadata), } = self; - let transfer_domain = TransferExpr::new(from, id, to); + let transfer_domain = iroha_client::data_model::isi::Transfer::domain(from, id, to); submit([transfer_domain], metadata, context).wrap_err("Failed to transfer domain") } } @@ -512,7 +512,8 @@ mod account { key, metadata: Metadata(metadata), } = self; - let create_account = RegisterExpr::new(Account::new(id, [key])); + let create_account = + iroha_client::data_model::isi::Register::account(Account::new(id, [key])); submit([create_account], metadata, context).wrap_err("Failed to register account") } } @@ -558,12 +559,12 @@ mod account { impl RunArgs for SignatureCondition { fn run(self, context: &mut dyn RunContext) -> Result<()> { - let account = Account::new(context.configuration().account_id.clone(), []); + let account_id = context.configuration().account_id.clone(); let Self { condition: Signature(condition), metadata: Metadata(metadata), } = self; - let mint_box = MintExpr::new(account, EvaluatesTo::new_unchecked(condition)); + let mint_box = Mint::account_signature_check_condition(condition, account_id); submit([mint_box], metadata, context).wrap_err("Failed to set signature condition") } } @@ -634,7 +635,7 @@ mod account { permission, metadata: Metadata(metadata), } = self; - let grant = GrantExpr::new(permission.0, id); + let grant = iroha_client::data_model::isi::Grant::permission_token(permission.0, id); submit([grant], metadata, context) .wrap_err("Failed to grant the permission to the account") } @@ -727,7 +728,8 @@ mod asset { if unmintable { asset_definition = asset_definition.mintable_once(); } - let create_asset_definition = RegisterExpr::new(asset_definition); + let create_asset_definition = + iroha_client::data_model::isi::Register::asset_definition(asset_definition); submit([create_asset_definition], metadata, context) .wrap_err("Failed to register asset") } @@ -758,9 +760,9 @@ mod asset { quantity, metadata: Metadata(metadata), } = self; - let mint_asset = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset, account)), + let mint_asset = iroha_client::data_model::isi::Mint::asset_quantity( + quantity, + AssetId::new(asset, account), ); submit([mint_asset], metadata, context) .wrap_err("Failed to mint asset of type `NumericValue::U32`") @@ -792,9 +794,9 @@ mod asset { quantity, metadata: Metadata(metadata), } = self; - let burn_asset = BurnExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset, account)), + let burn_asset = iroha_client::data_model::isi::Burn::asset_quantity( + quantity, + AssetId::new(asset, account), ); submit([burn_asset], metadata, context) .wrap_err("Failed to burn asset of type `NumericValue::U32`") @@ -830,10 +832,10 @@ mod asset { quantity, metadata: Metadata(metadata), } = self; - let transfer_asset = TransferExpr::new( - IdBox::AssetId(AssetId::new(asset_id, from)), - quantity.to_value(), - IdBox::AccountId(to), + let transfer_asset = iroha_client::data_model::isi::Transfer::asset_quantity( + AssetId::new(asset_id, from), + quantity, + to, ); submit([transfer_asset], metadata, context).wrap_err("Failed to transfer asset") } @@ -934,7 +936,9 @@ mod peer { key, metadata: Metadata(metadata), } = self; - let register_peer = RegisterExpr::new(Peer::new(PeerId::new(&address, &key))); + let register_peer = iroha_client::data_model::isi::Register::peer(Peer::new( + PeerId::new(&address, &key), + )); submit([register_peer], metadata, context).wrap_err("Failed to register peer") } } @@ -960,7 +964,8 @@ mod peer { key, metadata: Metadata(metadata), } = self; - let unregister_peer = UnregisterExpr::new(IdBox::PeerId(PeerId::new(&address, &key))); + let unregister_peer = + iroha_client::data_model::isi::Unregister::peer(PeerId::new(&address, &key)); submit([unregister_peer], metadata, context).wrap_err("Failed to unregister peer") } } @@ -1017,7 +1022,7 @@ mod json { reader.read_to_end(&mut raw_content)?; let string_content = String::from_utf8(raw_content)?; - let instructions: Vec = json5::from_str(&string_content)?; + let instructions: Vec = json5::from_str(&string_content)?; submit(instructions, UnlimitedMetadata::new(), context) .wrap_err("Failed to submit parsed instructions") } diff --git a/configs/peer/executor.wasm b/configs/peer/executor.wasm index 2ef54f969b642947ac965f14c877ddb8a7b17bb0..48c0f25b41cc2e2c2b2ef6a26a9d75b19fd5ad4d 100644 GIT binary patch literal 389245 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

wma#Zkdo&zd)&zZ0a-aA&o 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

4J9bVM8&+eTF#)55 zE%rjUS4u+*b?A(r>@CM|Je3cfuMd4zZ>o~FFfMK}{BRE^k(s`EL!5Aj=B zc6(F|2p4k z#ma=kPe#9getieDXS>a|nE8Nh`yU)A5hPf-2qvl{H@4nFd<=CJT z_)iah&e&!vBcnJKH`pIMg=TXeuecD8Qpgk6b~R7(s%*N`)4xnAl9US}YG&u=amOb_ zp(2a&zY}^S^iL%B>3Gyh3UgmukKHXZQFSYamy4GGP1Mz$O^8tPin;r z2;EOKnZoy2JNH-KubLVC%4TD1N4AL&^+bH88&&Z4G!cI3wsr>B@!&l^vqq24cOIWP zU*gE-tp3z+dqgd)9|{AZ%iitQU(@n9nGIlLuUtQ%b};LZDmtEM+0`X$hbP9JbjRIE zb-d}$oq>f(eECa1N;Cf1;pjgDZ`|w_WRp6t0j4##IWprA$7ZR`9qQU%(=C631M|>A zAvDLd0QbX&;o@U$ZgJo?b#`-)6gUzgf4%6kdeOeuGJLNnfQ|1JeS(07fqYxoW*orGRiZvwNLv!p)b;u2=!xL_ZZoYL zLP^mlkjPd#u!m)pMP{d>=Fp1O{)Zc9vX-e>`AN7WKgvSZo5;k1jY68SmM{56^W_^& zmv1y%z7fy$ji$&qni$_Cx1%}T-N6$q?fxrBEYUwp4JY1iyg;#SbrY?>8}Ytu=R|?B z#J4wMV_7W7D~r*yuKNz83IcZzb>m*!wC?9Jd)IEJ=`j0$HYC*`$gst0nAQ6i^F@UZ zgi%%FukIm{C{4+bE9Rm*R75OO@ek~B>Z7dsVV`}nOPEHk1~pPBKz2M9u93AwF{22> z*ozY%tK|9QnwTgQE3b)i2T4;jdtQHBfmZY(Cs-~DDBeBix8mGclJ^2Hl~BRK@=LLMton4W z9_&P8+8D0a-cmUc+8SDYi=kDcAB=H;vH~GV5Ur^!oi|zAF zbf;=PWRWW~+YGyt#J336+JY^6O!|E!bKg@nQpu!=_=_%n=PHUoV%=QW~@-kY< zJeaP(ga;E$i!X)1+M9)4v-MYBP840QU5}3KqNEXR`YNW#UYo^UZx_7DqU$Y9##xjY zyvHs$zL}<|JApg5d%>W@C(BL_a0xgESlATHy}KDOc^>Y|EYDC&wKu^bgAzaJLor)E ze#9imJhhW>jR_!uMms4Ld1j-M+KnlopD`071-37PIOEYZ^qJj9>t+KHiX=~6O&nej zwgM^v&+^4^pLLw##+j0H&gDvoA9*()?L8Gy1bTypt?xTn(P|U)HEYL2(rM(u#Q49 z>`Z`-!3^VA3m*BfGq{iO7=mM)xu`1kf6vzET~!n3c1!&_;&N}gOdHp7#s`V9X+>f2 zB6kAlnUOdl*Pc8v`O!}ok(#0M=!Na&YQx*qDe#R>wL8BGw^~j`wQKqHY*C)M5VW#kitt7ghJ<7YQxIgP4$n_Vv0{zBQNy@;iQrWDsNxJAdAKH(JClt)~1 z1-lekTODDtXu|zg=ySnvx-mw4C)Vu?y)Z$z-@|Jr$(Ql3RzJc=4|VzJ_0SY z(rR=NwLMk`)hz{wF&OYQwmzye`jA)i(eVXhw?K1iQfF${bHJgHe5@{umW%qFGxyc? zx;H;cZ`8#&6h`TH$SMR_m>jBc_=&-!z85@)hr!{!hebeGQ2E|XA&D0YE}#b&Za-(dXF~93Ixm{8)SK= zjj&uQ`EX-yehZU7Z zNCsve>EvuGuD!5)e4RM`-r{=2$p|p><5?J!xIaLp*Zq>9uFL9VjS|IlR=Z|hhhY0? z*lInV>opz>ffC*Z^LXJPfe$Eb!X`8aPpmft%CyyfY$#n%Y=Lu;u${J4{(?Y|3j!G+ zKXalD-)N^zS-o58iFP-xn_D%4(VLj_uRxAage8d=o#J^vsx@1XoZ6zH(NFz@nSwe> zZAPqT$rSokm?>!O^sJbIq4u7&oC99~r%?`U8^>sZ*dI?L_|U% z=rh68d7(BTj(dYR5`36}!RT=%?{w?9{lSsC$)#(lA}nnZl!pE88AJZ`1v8ad#Pt6 zX6jmvL}*On0Lt^0B!I$R94T5!4XRUS{_L})GA9#IVe_*nX`GW#QE9hdzX?xJeb=wU z*3CL^h%gDhe;M^huyx1UZG%%vLs+xBcw^$@w+V;#7O$W!=_Vu%Boocv^0tvANQhkg zZ9)>Z(|{s+xjNA?kU@tj?;i*=!^yc{Z`L*Q^~OBRT5+PN0_`NzK_U{3-miW?_?zmt zu#y%p20!cP^Ys4T(9@Qn`xs0z(hvA82}PBYkGS=gXHTIc!SOKbcP2KR0`#nXKQrBI znlBZr!iRm?Gt$laRs8d0!#{pTHq5jZ7~q2qgAPo%qIBEe&zemi*h1d^_(qnZQ)qiz z(1fwt>)x;d>n@P(7EsXwC2$}c&qY9Z*|DO{w|G#dP)WUz5ZuO=!67{ntTCcJ;(F5MJ zk!XLS2p?j@or7xo>#$gJM1~5lKb7fGtEEl_hzu3YUSiY?FY#ObYoSvBR*u$p#!~Dl z^e-`wY5glLOTA7Lx3Er-mM^=K{>AEyCFTbGD=n*~|49FWXY4I5=;ssLs7jgdy?)&a z!oZQz)>#%>=vY(Il3#gF#J?KLLSOWBiwJF_-_o*hrYfF>^g>Z=EDKd=?Wn6v3UWc6 zNtT5j(w9(HPVT1I6>-YhSU zbMJD&Yf+;m6|4G(I=P50q=V^fb{RW4g0<3dd3ExZC@JAPZh!F-`W6gC4j_yI$7n{2 z*RIX>0PYlRG=m9iLWYZ1M}~_D_Iluf{z+ppkdY7Z>ZPJs#U+WMi7FieEdyXgme5d2 zhbRoBz>*5gkX8(PIIzZpz%s*S)H^TZ+&kJ=?=(k@T$o+`x;L=_IK9NC}ip8?*K#FRO6K3B3bke~8?9?GQu^STm^ z`ir`ve*UtqD5}4rD_tAbkNnj;?b`;zr&aBJ@oySMpd;V64t z85;HcEcJ{CG2oYDtO$A2>=1J>IwVsArdhkcu;bOa%z+s4vidE*lTgHl2W)PB?C<{f z86R?4|5{t*?wpDM25wunyVr|y+p1co-RYaFmj=Yfx!gvDxD-m9cumfoBOI%rCrt`J zY=^5($#0#?v=4tF=wSVazu5oqmxw(+pmuj2UCuz-Tr47`w^NUZ{xGhqDfW!Yq%?Kt z`rsCw!08$;cWu{Z>Y%y!vK-S)9FV zku2Z!vK&iK@1H;o2_M^bj$;S5H>Z}J?QQHk*c6_^8<+sx75Yt<$?Aa_ey~@vdC4Yw zlC<-Ty^`tK#GBFir5*Wxw|!L4QAB=ZYO8)lbu3M}xTpJE&uj9LX!rsc;O2Y3)mrtd zGx8w&_AN7t>hu;BzYR73|uoKt$iA@c8OCXQt~XL%;C5r~xr80L%?t zU>Q&U86cKS4det>@N=ABxm!lZG(N85^m;jOM(HtziG_1c)Bt-5HZ)+eZ{!V+7UaYP=)&g#B?CO{ z!!$NEu+aO!MXt?UO_UC80@oZ5z__wuhXzUYHfFkgRg+0UQK>7}?(RRu3$Kq3^dC~c z=M?oyP~%v;H0!A{oAKrM(?AjJ>KS3d^kqK|Tcmm3r_@?oD2Qs+Wvi&xOb1I;zax@2 z%&`&)KxQgt5u<&^k{I(2P*DeMj6^$eZX$BKH@%XFckL1cJ zZQNAv!1m6s(=k{`OY@C-Akg~F7vl~21U9^iYytILe3S;%5_Vvqse4143G zxCp>zq_@)6cR9ka5dFmk}Jx$yN{O!aVn;YeVGyRm@_TXcX;q83ZEK30sr zrB|C~-=`rn1Y@s}Xfx&+``Nat^S@7ZCd3Tb*oEMV*RjYm_5IB4JQs~aZ3qbP+_X2G zo+IL}+&%hl)Q#DO-n$LDXbYqIY$Jre4$w|Q+{Mb&Qz*vn?OMQYt!z1(_(0G>syE}E)J@=av3RG_{}oO;fpnhfLWL|Q*`WnoHC z)JkGUu&Or2s+U8h9RY>t#_x#%i#+k|R1flFnv*9)jDC1of5FlC#x&0lePb3!hpOM# z560AVSG{zX>Ov#es;p#QH;mldsn{fkl$>f2PdNbhU>TRjt~GX+#B+>2kSKxBd!#g> ztqS9=$DrIdzCL8(xAyu^KBC2Ey{$gq!>5I`RZ7Cj)vEvWmriAAr;?wbTdj!NfVS7D=$6_tFOwAp3g^~1|ot|2LU+q&twIQ zmY^e?wOJdC{tbM#0X}HrmEcmhIceB!PEv{?2TAqy1py*7Izu6pCutIC__yJgfDTb7 zf(wa^rf6vWREbb*d7;=&c;*tz|GHQX2B$=~g2qnKIMOe0KJO`e`dn>~zD?kx;TYS~ zXtYnj1fvs*4#m)zR#0uN-+1O)kw;O~G?+O19wN^-sY?42+PHDFK_7g@$eIIvh-{UQ zoF)Gvmq)R?XrXUcCN5Yae8T9#)yjq5z&sUXsJlobU4S>$o1k=u6^{Ne&xbv~lO|#r z+*r<-l~f%Ne~@UJn%yK(@5HAZ40e>+l8f!jE~p9TlVzv=k)Na_dt~&I;6IipGu1;n zp;Ss&S-(!N2@}+7=4i}`L^yqw4kV*TPK2kfZnp&kzfJx48%}0apu+VJ`Qz%%rSwW- z`*@X_T80nEYAoZURR@Nl`6ccRJJefB@1XJhaf@p>86~QpjUQS=RT!PD@|^qwYF()Q z4>X7|)vII7H{Fca_r0IjXK(p>?JxUZ_l(!+>-X?_e#@^qt#;(%!RwgUaU6cH$$@ zx@6K~`F%?IO74?GLAEcrbIUf%z)~xkl5|Cu>oq`M_QO^~sTU|#zkn5=; z07M`Bm>>lq*(tDI5ee17;(bMctN-x=r9dV=Hq`pB#0elDULlPPgtkeJWm23duMD$( zlSIBuHwi^ee|#Iiq0Up{=91U()`z0<0OxZ?;>HFFb#5d9(~>TyG;#o=$fXr&DnTsi zAQlGV6gq?n+*HU1_hT=;bJRqU<(G1Sf+)Xe6va&Ae;l-cvCuF-h$2eII}&$Ds9qt} z$8jChtG?J6YLK2PNS_*xa*u)P{zx@HqU=G^qEtB_xrOFKH2{b0RlB|&bup@TfS&zOqG$Ay2R16UAt=>BYB7yS!o@#$TMu>@2__Mi;~qm)nppRTF(CG%cK7(P z?twxLPm7s)Y@h1Az=QNY0Yz>cXvD0Utu6u~FxSy$x>MLpj}+DBlXM`Rp$YU|IY`Ax z87552_okNh-)g$L4_1Qio(<4yj_#!^Hr>U*Px^uFRu&^cM}XBNRa|tie83Bdgl6Um zrUf7=EZ00G0TeS7ksnx2g+DmkT$R>%9+&Vyoe8|Lt|6J&;=zNP zDhte7ykOd%t7%(or>$76W%)iYva{+oNyQSq%L?m52J3MwN}4HJ%BAd7f1C5GnGRHk z_ad%P4Md-N%ZCbcX38NAOYF@Wq+UE;DoDOQPqoC;`aF2MwE|bIb_A|y6}GZPL$m-v zaok!oDASe|uBUEqSICky6tySh`KN2|Ike~L4R!NA8Wbyt24Ry^12h=-WfFJ^jsOFA z<}uonc?|ai`yTcDf!yE(a!BKw2~rJ_1=kFwZ=u8N$BS`BfPZ;1=Q@8dJE7~$!HnYs zamydfj_W#iFuPAqpoXeN(`;FisBaO%^Tdn`9fHNc-GmOBes>FH4);>?mZA$Xd-Q%U z%G}ZWEr3A2rl*wz5g67WNgeZ~$3q*~TRvK7&|*^2+6M|7Po%ohS=6?sV$31W~}^1G7MNk3&Txv`2=be@u=JGZG_vPYVSiyQz z{65yl^`2zTk-s`58q@l@X=o0?k>Rm3ZU>KF}vr8>eoN1|Oz z1Zgcz_3O)il^bgNf(C)M@(7lbSjr{5Nr7u&c67i9m3IxUsKIWD1Qs?Vw~iP_Ag$Mm zLbP#=5e(|Kc_l0Z`BFG}AZtQHE8?O4M?lu;QgOQfqYZQ-9@$r_`G!FhtdbZn#@dcW zzXO}MkIwdG0=lG z*k)2{NV1~EnVtFY16r=k6B$P?Cr7o=bu|>w_MDWx^0@ zcILrf@d>Q5j_++~j*6+&D@x^y-{8a0F$YEc=2Y!uh!=x!u7TVOC9h_ba8C?N4G*>a z-k-1KO{hN4UxQwqr?nG)cb@!6@5vwd3Eo_l$$=O+mcNpdJPt`4L!Al3N_IIt#4Drx z1HuQ4R2B@@F#KKpzxV+yh~uiJ`VD?U3vJzlOY%E&w^1HCn15fc5y-hx7(8)Y9_HH) zisF-d%Qa+Ios-oOXaY)UZ#Y_pLYkQXDmr*KN4mo%1Y~ri@JtKREbKsX4a-zxOAH=j zWffqGWM46H1R&2A+os`Ubm3vx8;~tK>Uk}XtdO~@GP<;i)I%OMTZoz`T1DNhQM0*1 zqt|f&{H0v7^w?_DHo!{#Y`QiG6G;E@4!UZBXc{%5RcFy{ski<$AvuDeBBX$@uoTDc zPAFk5DG?gqpt?;1K)WpJyP5p7xCYpCk;$hndxY@+B`_#HPbaDJ4sH_v0s!*%lxm!6{9H9*UR|?rA5sH8}dT5ZX&K|QfRn6?v{bzfxkYK~XGds>Yd*?al+Fiy5=ZuE6RhdPg@NRi4y1gb=r+GVn$@j^bo*B0xJsEjy@*a2l0ClFuK92#5lSID(Mos;VOh zg}sYc`RQ!gMgW)JyD_|UX65^=X$=_5B9s+y-@9y2%8p>+%F326-=`4B7_@9Y z_r2UN4kKX88$)uyMncvw1M@Dp3PbsMfPwRVd49!gAeMe09P9p2r-fBKjTw_#V59gx zUe*FQR*lzSY#8fimiazFvOVHx8UJ4FvtcYlz66@jQ3NqvyebuE>{8W6rJ#?K=)-r_ zg!;6*Dn^O00n?R=tje+QLfkQr8M<_r7TSK?1&glap&1wpEUi@7{f(YOU(yb;-i8jZ zQ2>}&D1;(63US-)N);l)RHTIDq6ig?T=uGp?>eOoVJ4X_XS@RcHAZz|1cTvl7H>Zz z(8B38tK2LZf#oOzLlN|aIpH9Gf!um*>S}-ZXYr<8dgp{wah%`a5hmXEcI3Ctkr4gwFwy3W9t}| z3gN|5l!fFkxyCExuVLl{O(yQvY9D}BL5r8;RXgSCeO@;xI6$VJ2cBEkO(Xc|@UIn%#yC1kt z`x1QHMp`9?BjO85K`Hbc@R?FT4!%WNBHJ6CSA!RNqDpft8-mGr0=G85#~Lb5{DRJ& z0#t!pNi#9*7)&FDR1TB7*jCPdaHc)Q)}jE^SeLaDT<;T?S%*1D{^>I3p(!Ts?CynGYH8>(s9f4nfj8J+5<`#@oS1X|u;g&hK z+*H!;1Y&e{yu+d?d&-Cp_(@~&Zd^7+BX}>JSVR78UEzQN4syn1y^SnG_~z6OVTU&+ zZ+0SX$0dNoZMfMF7$!gd{T8UhFawB~JUlE>A-uID3Kj*rPKT5Jt`DtA=DM==9|cHkO`!bw#*XcptV<+jsvXdu+V5xi$f37 zR??b);#R=(h<%|jw=yJYU5br9xDr>#)slE24ow3USs^5(gYcN4Xblfyi=NTB{R%aQ zAxIZL2Xz5*)oOOZjaz`J6IfP5A=d5MeSUzTWm^6bAiP%x6vO(*fkxpjC1n(&4vngG zL@-EF9;Vm?Qy&oq0)G`sN%8Q&=W~lMAK`84XA5Zfv>;J_IrC3T%7Eo(U4I46BeTMW z1zfEhbkXjiASCFy2tcx6P!E)P3-Kn1nQ#Q(iV;7la{Yuiz6ytNH(G~RICsVRw5YW-L!d%P=7P~^sQd)>? zMM{O`*rU(|wT{$F_!F@}SJ$WN3^pmOtS2*GFu&ePY6(}w0goh(@y3`8fST4|(*Uwj zsxxdIq}8%^ZPXMXgaeCE>^@Xh?KZNZ5Np8XK~y8935-aD7XC%(UNoH>!3ctfpmSj{ zVZi9;S%y^W#-J*3ETxI3n3zqe5t<#D(P9{W2z@6hRtB%YL=z71VPElWdNdi&1dNDp z^I2mcttdC(r{O>rAy{J|1Hc4q2jMleZkS(!xQ#3##AFqiba^7sy2W5OKc)jhs9lUC z)q=s#R(!G};XM=uBSFi8 z0Ol|jDhE7jIHUuiZep~E^Qy!IX=a6>sxm9V&y3}p_(gq8Eet>~sRlGK6#vXah!q#M zFO!C_Ey`d`Fo`gF1z|l9V`C=!R)*qij<(<@%`AqMJwVu_8L$#ZX&o^(51KO>mgNC!JiguD z2NisB5GwvjUS64nzx9O_qjD@!kmfa&`TeM|=gcqd-)HR`zTTpu`fsRqVbb4=$%vFF z^W@>cu5w3N#)vr$2&$;%04@4x#EZ3{*?1`4Qg91R**vruWSTP~#qty*l2$R~QNV+! zj?qCHBd4*7f*xWdz#OJJNgs(n$OsnLduouPal{(Z1mhS%N1PQvevMxf6Z#D;U3i^Y zC<$i^>(eZ0Oa}>xGoO`-eCZgz+7)v6cYlLY6@I42OgUm9Yhe&l{!{fjp%X-e4`j zS8qa}qcsFIONTw9pievEbE;{DVvHrI!L*1_c!eR3CN{Q%f-{cE^dZOX(7qEZB|6mr zmLt;o1(f2bt{r|WB{WSdC8t;^h4}Trw@R{77g#w&_&2QIM(uhOof*k%BRiI4_`z$V zZI&q9n&yfM#mWe^5)QEB(RP9cVp#-N9l>Lzo&Xm(LV@0sXMm(xz;ehK_av@@)+;ph zX}!Wnw=FYPkfqhEAor5xf>)3y1YQ8H?AeE_)p!M|5ID4gyy%A)T;EpR3Uar-Vg+eI z+=!BHTh*)}{|H<`5|%_Yw1m9m!K$e++|+$`1 z=`bDHa~@Yi(s09lk2Ktfv=(g@vnO>hQeZJuV9^n1v-!hjj5tCPrYsuwD7~NRfiiEH zY(#6(rpX3tZXuSMf@8J2tylq6m~05$Tnml22S~FA!u(7&DRi&A4Fm(MRg5jEiSdTk0>+zP2Qc2OMi(&LY}5%c+*bNQfp&>{fa3Dz8Zq4di6Fat1UczPki7;$ zHZW%!1bOPp2=cxkK`e;%V^rESsFMB!5v^52kcTfrkf;4n0S+cqbS6sB>q?)E9XOd8U!NP~bv%!V8MN{kp{LgkwKO4lY-_%CPdw1|GoOx`2W22_mg0%|CRRF*pE{jnyCE$9_4Sj23><>$|YYL6?d8`F8Rm={PZ zkW&$SPyk&YXD#817!R~`493&Q;KJZ@+dXnI@7S07(@7ne$MM7jTMYff<|!>L-6r+M zNg=eeRVRHInma{{fC-U@!bhO#0vLpjfbZ}XzYsk^D-)by4!NeCgGPc3R21#ihf+dM zOiDQ3o0J!a*wHdGq5>e?5VHMlemL zemH;c8?PKbvv5BP0W#@fW|vTg*24vf60Tb#$7L$7*kxMzcP3G^g?5@E_zAxeAwWU< zWY$Fdj*X9)nun$lQPD!GL4G2cV`$iEE}UDaCs`_$p_o-k1l^@QiJhR@T72i8keI_! z0e-+%bVE=KI7)s0z~N^eIKObdoVxepR}YkzKJx7hd=z`ju$s4NYk0L63e*834$$|+ zscllUBvF4I;0n)=NVk)uP7E_ly8uz9)&C516i6<{1H>x;Wn@o{DW2=tV^5u^qG|2~ z@B+_YFVKQdC_}TrS+vsGcRZP59#1AyxRdBCmLS5Fj|0j`#e~Sg2b@qwT9}!y;{7@{ z5SZ`5g1+)Nr_8TB4k}~bM_mx6gxDz5R~_O`+1ySD3YeE|LPf9of1Cw}SPp2XIB^Kd zIVP`g7i>vk#x7DYE6-3)MBk8l#@2SX6})Wqz$eh8FXBF|I7tc!l6vuP3>clp3wG(A zwz+Y!3>Fof_l9XiHxD!$QrLK1ZJ||Z_pne*I2N5wD*!C$7K@oLo9|BSuqUy=q3$g z0de235PpvpK6*#Lec}L~Kri~`QxeDvYG?>F22^+f zK;>j<1qOzJOGp-=7DXWu4T~8>Vh$QsuzD4@&LXxSVK`zzg*Xiu0#v+llGlRISR+W{ z!H;4s1k}^Y2j)0;7<1q95r{h)!FJDF5{^+;B?TGmG_ny6OM=%*JVFBtHi{4k1dJ(K3BvM$ zl_2bnV0b!aluF`Y2h7fk^bizrgH0DLOqB-Ft0B;`L;1fO8}O!k5u1yNJj%hsozC5r zY4phGgvF!aNUt&*4NjaJ$-~Fts)yde>vVqETM~w1edLq1TCi8T_=rqo1;~;%*yZ|D z^*QUY+%RHCi83&!Q>Zi9EU0D`zgRAlicPW&a~&Q1jRA~7&gZ^if`_@64n2U~m;zK` z+oco~2;{Znb0nT-JPn-B3s5*(>BV!ewh)>QyUn!E`I;!iz>pPz1`tZ&I5#>zf>kYE zY>N0aJ8;ML*3^X%npjsi6)o#10u86aWrUw#NHWV(39vc4#h|jqAO--q6cHWA5&kdOm$(zmFK(3~BmI9>MZYrFe+BdLf>5gZ# zcQ{6xPP%|3X9%IRz*#~zH*${<#7Ec;(}i7IG{dLRIa@u6Az)P1%0M$>Fo`44`f;Qz zvjE-*H~7;q;gM4Tf5ZN_H*?HUStn z$v0M^X}+-vNC~J2gNwE$6KD!R6IWAJ0~+#H0*=cE0PKest`--P(Xs-ke)Hms&g@4k zywV8llc*{fA5q|H77cXD@^2I&Bt|NTE(^6^!a;Q$?^r!uJSG4#UH=z^@L*KFq=3v7 zDxU{j1Ev5wA=-w zsN+0kY${4?7t9YHTW=iGOxiF-e5<`-$E5+F@rVF$;*e)rCZaNz5m1iaBNq=BEx(GP zlkfW0%Hu8&9sVibfmM8huG8scB{{_948?C}nBhOJ+z{XpgB9ymS=$WE`saCF99)b{ zTYSH$tNtzf|MoaVzbnqlWsTiD_Qjxqc2OLC479u{s>QFZzF*sXzt;GEZSwuvt1^NR zGD_4#@anrHbmFFHJLrjucG44jwGD{z=3`L=1TPoWAmHYb4+NtTv2DVn!UmBM9pA~n z1j$#?IvXsO23~Ck^`oNe9-8QI_$YBEoqIzE*Wej$74bF1dvx;XdE<;op^o4sCMe)r z%ja=VDab?;M~F4o(^NhzfobslAlx45nFG`^d*UXT`UV=^NHm z`1XnK8+1adWuJ*)(Fue1am{i?t8o~Zsp8Qv3Mfyo1IZN~2LNW( zuEnxcL9mKn+z4MjjxmFC-keT4m@v5N3WkU`YS)r0{pjbzy}_^GpIK%ubC_xUT;qZO zVfh#C5W!eBs}a9ZMFYOJFJFIs5+^jWg<$mwH896clD+r}vS_n_tPhArnA){ufe}JF zMV>;y3So?$mEkH1F-{OTjzXlfB=bB$XF&$_1r^91Z24p54P||2H__061qk#CSMA2x zjXGcy5g<4p5#B3kk2t^Jk~+GB;Uw-Jg_LsU;6UZw-Tt_22p^^6%A zN0+&7_XW}iM8;AH9MeG$b1j5mIjFvvy-0m#n@L?-B`KK4F^1?1Tb1}!7vAn6UI4vN z47Xvk9O-4M_6v8PgKlIL-dka@K&l)AT39-uuSYA};TOdv1Y~rhhVT{t`waiAFGF+Q z#^@wHoDd6`Y4w7Taugv|S92ruew(Z#4uWioFr0|3H;Cb(P0p--a%fd5b1ynfc*Hf? zIp|WJh=Bm)iZH0i+Q$?i0W?4tX2&rL3CM8mb-4o7$ARi5L8vzAu-LS|N0?(tPXwK=)}54)l~c1hxk|>v>T4#V}oi+ zKK402(&EW~(3t>jIB}V3A3cnZ)gs{}K30eLeeYkU+84jX$GX)3bnF{^jH%}1E4%Qq zIt*>ykB`+s`~GfxtPVrZet?gp*;j?3jeGF1I_^7q03TaaQ}S`*zP8nTy!Z(|Hmm02 zW8dOqo1l*xgea*Av?kTo*oD!;v+fOKrQ2b!YYV4HW2-XjxUiePWPX9b!B!<*MP!h+ zmI8_Bf)G?jE8lR4BhN-Nrz@b1wP@oxSzKpqT*@SQ-ln{v+Il;Sf8Gg>qaQIl(TQ|` z&aHaBk#MDp2gVLMPE;n-EXhF?&K|yca0NaI*j8+F%5yl;3K0rCdUk6qK4_6ssOpL8-&b!Fv7vh9wnhY z927AY_X3DkF<~L2N;X7*g^I{X2kk6MCdjB`Y=;3fVCfvUgqww^Vp3tqsV8awL3AHy zqZK`VIgph-dcwp9@KF(El`Cb1ZB7`D$T0|uW*_i1@hnxxSNXM*(*c?Ss|n5Fw6_D; zv;xTGbUp`=f!kO8rcZHxIhVUjrgI! zfDue1AQ@*UVY>+t2|$2e0YrjVhKXbsM8fw#B7w#z&qOBI0g%a5!q5XCh=kE)AEjW) zeMQ!-L@IXa%L+gu06{TU7l8_**Cx_CfM&*=Dw2dY=)?xSh-fs5M7S8OUJ1!o>L)4J zNZ^x#yL$v-(JgI*L0E8xL0AYYVr_+qW)c<^__E@DsQ}``PRNAqTbZyMNGeuA*%P?x z2hyN=%DN!|5bLSEVt11jjMa0g6X8h`=10j@L-N^^VZV{ITCP$VqeS?;5#LHtYp=kP zs;guej)Zau7OF)lvZE0IFopx2Y`-PDuC|Kh6veObk5Cv$fp z0fY+wF51Ua;U|Vf4pHwfI_%YaX$%V+hNxDF}Ma2zXvgu7+mtT^ZwFfu^YI~SQ_32Fl0LY#>K$UuN+_(?aS>DT~n!ehQ?tL~txN+W_1wZN&2 zr&&5QSM}!{!`-MC)rN4bV^1&; zCAyEjEZK<(i0q(4HB@K99g%`Xiyz@W!gfp%@r%fOD@hU>2stziUzN!0&oKkCxQe5_v@A(VQPI0e&TvA zNE#X?j6#O*j%voNz-~3)1%Z^2eSH*-Mr&G@D`JZ-XO&z%prQ(8hISe#R5KsTjumed z(!|{~cQ+weh|BSx1ze7tB-+_Vc1iBx>gN;jt8C`vy&9e1HiKsGy*9W3FDK(al~Egj zS}sDJz=SY4!oDLRq7*3AGJszE^e3r3@ihB^u*e0sI<)yf9+16@%=i?Wwv_NK+69q?uqK z6v)aVM+O?4hzIPNjMoM|>1qfeGKA`iiWRNg&ade=>Oh!^IjOlyY0JHQi`e?BE2%f! z=C&=;ZFAg8>hj&vC`$_X1M>x>ut&+kcf6ec490RL<(&kyB?laIOv(#X>mgL}g@}}T z>8%6phKX8AqeyE2w>*QYzmfXT)PRhzu&WTSRo9QIaf%U0f^)hC1QiHaW3Yxy)q%8J zUCb0-L%_$D~NLP}Da`f5QZXLM;8c-MyD@cbrhW;m#(FjT$ChpCz@WJBGBi zZz0X86e(q7McQJ2hZr%-fjf`k}d~3pTFARDwlC@0p1B`zMXo-hy9BV zN!%fN!5v~D4M7QHF*u|sgusoDhm-ULT;<7AGldqD-T<*)p)Z&(4J0@iH3-(jUnleh zQn!V^@FXgi!E}(K75V~7bFY9+?v8PZLQpbP0Nz)g<}p67=3UL&$X^0nYmX^h8AQJM=__+v!Q?y=N6`;6oD?LB+0cl)Wb$HOPGBjxut;y-&c2mV}c5)o+oN6ry7F z{xg~gF5^GjWo%x(jQ?yOS@EkQp84KG;|SMOn9Uj2R8+pB0)435R>eW+qCO9aK$;UF zvLuqsR4qJ!6}}J5Lhs4qgfd(fNL~%T(v*I`Y208+zqcAUm(uTTawu~Uraw?aTvtl( z*YItnDP~XT=>yWQK?X=`CfcF-5pD9ZXY{+DxFD5&CW}_YPrehCe#X<8S)z)5zB8sh zL{WT2C*dA4yev3m+BlO!!bQMS&?1*4{gjDKFHwM($>cgArG*`a_@QlJKZ7h>ws0s$ z!LA?!B%Eq(NCLQh6q%GIc2Wud3~D45hTpgVs(Hxq6~f0Sc?s_&>M)#|SThRvlc~C^ zXgtK18}_`)=)u|Pq630nf!)#KQ35)Qa7GdK=*4+-k6ltkd3phz#n*9mdLswR2y{Xq zf{G?1CtjEtTtK6UiJ+D}v{o>V2AA=ZsHP&mf)RTfu0U=_5!I4J`PdFTpvJTKM&by^ z4)T+j1K$uwD!x&xivz!Lr~#F$;+w;VEq|`{LBgy@HJd1)7t}mZAy7ass9w0GMc4ga zxWL!E6CNK8=EK-^lb(!OSJ4Jj3EAw1$!qLvy{Zxv+`-=bW`b~NMEk^WBO1Tik62l7 zFxOUkP1XJX%E@2JJq>pqv?Uqt>6b9t{geP2(jedeC80qEm^11!0#Jm*XJQn z&ai?r4r=g2u>Uj?_}iy#)qtEZ19-%B+_ZM3&v5x-ISMztd1DwXZ4L2a!!O&Qep1wC zI9c+Hb4TzLA>lMTYFJX}sD)OzWacAZGwunaDGF5^hb2@|^8%afvja2y_Z5UnhJP5EU6<)8s}sWyR(HL^n9VxR`)yFoJL zDofOG1ag{+7XpI5wNx+Y@%eMRt1fEQzJobI%ml*Cdcxvo;#;hkCcfor)h51j+}r}Q zQuS_}vk^)yVM#p=OAz@{Q!4&a|KOb>J5oFxPLVJ=SUpQ|%mZwh?c{q@w*$Tc>;alf zu2p<=lCn?xUqA)yK6FHd@+L%VY#?(G3^YzpXbxiAU0BRUk(G)xG%^p7bu$FVc06FA zHK4hD0Uu1~GO2fwX;gh=syrtul59t0LP76nVPR~OCYz`@84a}xSgaKHMfJmv!+u}* zf(bkuZB{`&Frbn+!f|pWsjz(#(-#2a?jh0`4O&PCb%+(52fgdv!`yJ2__-O+uG zJCxeT){ST-E6SyPX@`ACAO{B^zy&kK9nlG>LUyuQY65|66lweDO*zZUsB%BtXp-13oX++IYi)h52qet{a5|LdJ zy6qs6!S-Zl#}2aQawr%bQ(>o8P&$%cfj(&00Co-PsikY^8^h)~f^=>D8e=nr#q%A~ zb@UoONN~@~j6}6eMmO*?BT+@PDK^I?D3d)BC~omcdgL?iy1J`Df|7+V)E52Dz$YmE zdN`igB&TX$5!KlcKq34V+H!SeR2o4;{gEV;haq00HaQV7MGx~eh`5Oi?0T!) zGYlsy@OTKJ;6B>dcM!pFs=+J=<*1s(9!F$s2E+4^3_dw*cZV830x z5wD0xoCc3z_px5n0CXGbffd(4UoAh+yXIk_cybp`~5}L2!Fl^^C zhbC%HsbI!v;zhd6UB#9xvNE$|MGOj?$SqM;VhFSg+bxL-aw{<^xO*hM>^g9V3CIoi zEhZX1Cj}XWOt5$`7R;7eENS=#d1E3(urmY&+53s`kOqu=T;I``5-gzI@XC>-qEP_R zNH6J78`ZM{@`FdBL28w`g|9)NOeCo=$xs+5m(EC($g|+A7&yEs%M=#m2QY~Wsf+za zbN&&i9eQgEUVu66hy&-oc&696@`zmje%Mo);pOcfnIeA*#WZ)K3HFB-xSn zi-$Eg38NjVPEN%RG}?kjd#yZlCjpEiS2!cVQYDW#{F4a+uch_jQ{POJ@ejNw_;n%rGy&}I3xCo1b+jgKvtu288Qhptnlh!hUbyN&UetG zau~??s+=z*uq7}HJx%8erg)zZhxip^)#Mf8tO^bhd{x1@u9*^Pgft)#3r|0Ur&mQJ zBSS0VtAMO2ttH8m(u;1*won8TCPI{psG;q2YZwZka>h+P?2<`zJS4VMQLnZ^Lf-|?Gb3IH0e1= zJ;ak$R;ZcA&OIJs+DWk=fWC*|>Jl0bG&}(^unHl}?op0=r_~0>6?G&!KvTz-JbGAq zpn7E1&T3);x=d9Mj`%$bJ|&pnIEN~AbO77i8JZ( z1ELNA1jvF38S;rNoC@HD0tx4IF?RL~|0qc)+8Ii5P!!ta3JO|a08AX$LJ z1R@<41VRn`O!E(Y0C~9I+(!*_Sz%-I6fPiHcX<0ZM zOq?|bpplE6J*FZXuQ{3)D1=Nn1&uA*sJ`+O^ z_8+uqW}yHRTx-ck*#x|ETksaGTT*XWi~_#V=paC9!K!>#_B+C}iq)+HiXnK&Lf(1i zB`XgFtEVlkKCGU1r5^bIxYVN%J6%#SHPen+PzC4dV|?= zr;>j>Fyk*~M!9lE)s>19NK(N)bKLe+X|N?Fm5#W8=ukJm!P)|845f1y@oD~~r$H|y z@=~-C9<-~5=64zG?7z8#5Pno)hPz;{B2{@y-ST0ll`5+IglA@;gm;q;iKvK9v zP=W(rApuBAxX6|qN-H#X7zMF|)qpA7&_isGAJ}Idg}(7&zJQ6w6YcY|0>Q|N=x1Ji zdsvOCCOcN_A}b69dLdz-W(%qv$i{JYzY3R&FZ~PiOqj>oS65RXz-cD*uil_Sq*j@! zVbh3Dp?V=0LkKU3HmKvNA-CwE>r*eu3uYVfA4b!RE)bYRF)20nz70qT2a%&30!I+G zc+IO+12<0KhS#o6n)fga3ERX9tnxdn>r6oBe!qM0tE5oSF~$Z_i93&@2E2WdSMEhLa7t$sfm<*RdtvNKeTo{0 zm4g*ZPe2ZQ?8VuN8n}q5SVLjVax|*dLyRQ#)SVuC-$GRN0VVq|BcY?YI2%aVorIXT zVwBM?1Qi3=(IAZ&fah<~Ma~F6#4G9|j8A@DG&^WWSV=N}l){b&Hv6R&u#euOYTTG7LM9jH_y~kPy5CTCk$>1tj8ZGt>}V1=)ge0Kl0QQkbbN zK$F{1q$5Vs;n|1{nW)o*LdwJf$^y(0;GPXI1_Jlp%F0hJ`fbv6YP@9DYYeQlh+G=3V<*j~{kyV)h4{ZZ8VpotCcBrzH^ z3YcWcTFO%r1}>P@+ye|#>d$u9g-Asc*3Kgol#kwveTy<3H36mz_=`Odoc<@L8IEOX zBbD=p8T@!p{Gbt(23qj3BXsrzI*hOH%Wl=S@yhHagYw1;iVIPd(q_NxJLL zc+#YD22To9J%=ZzUsJSz{pkQ}EFt}?hwN&I2BMW+*h3+s-{u{6Bz_wZyAU%%Xt3z_ zoLKx8_6KSEt!^CMVGqiV*!7_7C;hX-HXcxWy-L}CrtCN`m20mVlsz&iJ0l2si=-WZ z)`+WqQq@6VE#vqV*yns}Tl$FeSMUjjLbAbYKiTQhKoBun8NnKkSVq@H6P{XAM3i?x zSB%E+8BtB2v6h;sN^VctE<7 zg9}G`NAmTAlzoUFEV%U$5VVmZp;x&n7buCjvwK*0Ad(e=7N$#ujs;xIe1i#!D2;u! zWWx-_PhbOvXYK@>HF4GyCUlyQ)D(qqwu#h2@G`O5CYb_q+eoh&3<(|3pJNaRq@WNL zYa?wYwrtH?fN_Ed@t*@@9%t|bmAMAP8nw#)++^U?Xf-ViCh9iQjopb$+em|G*Aw@c z>Sem(WRd_l5D#TmaRw?m#fYgMTaPeu4gU?pTR0{y%w}X=Qs`7b*tq?SD&e$aPM!fu zgfXL(0c<8E&3OhMoU{S|rs~Me81)vfpKS>Wo#i9*&gwX&Fl%rQ<{AQakX{AJB zxb;E!WQ=lj2Cy-6EX9N>y0jRivI}(@vj9*kCJ|`vP!LFgfD%>g#i?QvAo-p=7K{E|`5J`k;>@lK3xcC+R z0RuZ23yeiHVPcV1fzaP8kY!Gv%f%0AaU{5IG7$c&2EFpe7TARgz5v8;s}l6Dp5knLtTE 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%0Dmo&h)O1CxgExU60R+*eS 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 Px`AOjvVk0dGe!Rg!(0e9 diff --git a/configs/peer/genesis.json b/configs/peer/genesis.json index 2ca5d0365ed..a915d22f4f8 100644 --- a/configs/peer/genesis.json +++ b/configs/peer/genesis.json @@ -3,7 +3,7 @@ [ { "Register": { - "NewDomain": { + "Domain": { "id": "wonderland", "logo": null, "metadata": { @@ -16,7 +16,7 @@ }, { "Register": { - "NewAccount": { + "Account": { "id": "alice@wonderland", "signatories": [ "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0" @@ -31,7 +31,7 @@ }, { "Register": { - "NewAccount": { + "Account": { "id": "bob@wonderland", "signatories": [ "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0" @@ -46,7 +46,7 @@ }, { "Register": { - "NewAssetDefinition": { + "AssetDefinition": { "id": "rose#wonderland", "value_type": "Quantity", "mintable": "Infinitely", @@ -57,7 +57,7 @@ }, { "Register": { - "NewDomain": { + "Domain": { "id": "garden_of_live_flowers", "logo": null, "metadata": {} @@ -66,7 +66,7 @@ }, { "Register": { - "NewAccount": { + "Account": { "id": "carpenter@garden_of_live_flowers", "signatories": [ "ed01207233BFC89DCBD68C19FDE6CE6158225298EC1131B6A130D1AEB454C1AB5183C0" @@ -77,7 +77,7 @@ }, { "Register": { - "NewAssetDefinition": { + "AssetDefinition": { "id": "cabbage#garden_of_live_flowers", "value_type": "Quantity", "mintable": "Infinitely", @@ -88,95 +88,71 @@ }, { "Mint": { - "object": "13_u32", - "destination_id": { - "AssetId": "rose##alice@wonderland" + "Asset": { + "Quantity": { + "object": 13, + "destination_id": "rose##alice@wonderland" + } } } }, { "Mint": { - "object": "44_u32", - "destination_id": { - "AssetId": "cabbage#garden_of_live_flowers#alice@wonderland" + "Asset": { + "Quantity": { + "object": 44, + "destination_id": "cabbage#garden_of_live_flowers#alice@wonderland" + } } } }, { "Grant": { - "object": { - "PermissionToken": { + "PermissionToken": { + "object": { "definition_id": "CanSetParameters", "payload": null - } - }, - "destination_id": { - "AccountId": "alice@wonderland" + }, + "destination_id": "alice@wonderland" } } }, { - "Sequence": [ - { - "NewParameter": { - "Parameter": "?MaxTransactionsInBlock=512" - } - }, - { - "NewParameter": { - "Parameter": "?BlockTime=2000" - } - }, - { - "NewParameter": { - "Parameter": "?CommitTimeLimit=4000" - } - }, - { - "NewParameter": { - "Parameter": "?TransactionLimits=4096,4194304_TL" - } - }, - { - "NewParameter": { - "Parameter": "?WSVAssetMetadataLimits=1048576,4096_ML" - } - }, - { - "NewParameter": { - "Parameter": "?WSVAssetDefinitionMetadataLimits=1048576,4096_ML" - } - }, - { - "NewParameter": { - "Parameter": "?WSVAccountMetadataLimits=1048576,4096_ML" - } - }, - { - "NewParameter": { - "Parameter": "?WSVDomainMetadataLimits=1048576,4096_ML" - } - }, - { - "NewParameter": { - "Parameter": "?WSVIdentLengthLimits=1,128_LL" - } - }, - { - "NewParameter": { - "Parameter": "?WASMFuelLimit=23000000" - } - }, - { - "NewParameter": { - "Parameter": "?WASMMaxMemory=524288000" - } - } - ] + "NewParameter": "?MaxTransactionsInBlock=512" + }, + { + "NewParameter": "?BlockTime=2000" + }, + { + "NewParameter": "?CommitTimeLimit=4000" + }, + { + "NewParameter": "?TransactionLimits=4096,4194304_TL" + }, + { + "NewParameter": "?WSVAssetMetadataLimits=1048576,4096_ML" + }, + { + "NewParameter": "?WSVAssetDefinitionMetadataLimits=1048576,4096_ML" + }, + { + "NewParameter": "?WSVAccountMetadataLimits=1048576,4096_ML" + }, + { + "NewParameter": "?WSVDomainMetadataLimits=1048576,4096_ML" + }, + { + "NewParameter": "?WSVIdentLengthLimits=1,128_LL" + }, + { + "NewParameter": "?WASMFuelLimit=23000000" + }, + { + "NewParameter": "?WASMMaxMemory=524288000" }, { "Register": { - "NewRole": { + "Role": { "id": "ALICE_METADATA_ACCESS", "permissions": [ { diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index f4f412eb633..7aef12edd2d 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -12,7 +12,7 @@ use iroha_data_model::{ account::Account, asset::{AssetDefinition, AssetDefinitionId}, domain::Domain, - isi::InstructionExpr, + isi::InstructionBox, prelude::*, transaction::TransactionLimits, }; @@ -22,7 +22,7 @@ use serde_json::json; /// Create block pub fn create_block( wsv: &mut WorldStateView, - instructions: Vec, + instructions: Vec, account_id: AccountId, key_pair: KeyPair, ) -> CommittedBlock { @@ -57,13 +57,13 @@ pub fn populate_wsv( accounts_per_domain: usize, assets_per_domain: usize, owner_id: &AccountId, -) -> Vec { - let mut instructions: Vec = Vec::new(); +) -> Vec { + let mut instructions: Vec = Vec::new(); for i in 0..domains { let domain_id = construct_domain_id(i); let domain = Domain::new(domain_id.clone()); - instructions.push(RegisterExpr::new(domain).into()); - let can_unregister_domain = GrantExpr::new( + instructions.push(Register::domain(domain).into()); + let can_unregister_domain = Grant::permission_token( PermissionToken::new( "CanUnregisterDomain".parse().unwrap(), &json!({ "domain_id": domain_id.clone() }), @@ -74,8 +74,8 @@ pub fn populate_wsv( for j in 0..accounts_per_domain { let account_id = construct_account_id(j, domain_id.clone()); let account = Account::new(account_id.clone(), []); - instructions.push(RegisterExpr::new(account).into()); - let can_unregister_account = GrantExpr::new( + instructions.push(Register::account(account).into()); + let can_unregister_account = Grant::permission_token( PermissionToken::new( "CanUnregisterAccount".parse().unwrap(), &json!({ "account_id": account_id.clone() }), @@ -90,8 +90,8 @@ pub fn populate_wsv( asset_definition_id.clone(), iroha_data_model::asset::AssetValueType::Quantity, ); - instructions.push(RegisterExpr::new(asset_definition).into()); - let can_unregister_asset_definition = GrantExpr::new( + instructions.push(Register::asset_definition(asset_definition).into()); + let can_unregister_asset_definition = Grant::permission_token( PermissionToken::new( "CanUnregisterAssetDefinition".parse().unwrap(), &json!({ "asset_definition_id": asset_definition_id }), @@ -109,23 +109,23 @@ pub fn delete_every_nth( accounts_per_domain: usize, assets_per_domain: usize, nth: usize, -) -> Vec { - let mut instructions: Vec = Vec::new(); +) -> Vec { + let mut instructions: Vec = Vec::new(); for i in 0..domains { let domain_id = construct_domain_id(i); if i % nth == 0 { - instructions.push(UnregisterExpr::new(domain_id.clone()).into()); + instructions.push(Unregister::domain(domain_id.clone()).into()); } else { for j in 0..accounts_per_domain { if j % nth == 0 { let account_id = construct_account_id(j, domain_id.clone()); - instructions.push(UnregisterExpr::new(account_id.clone()).into()); + instructions.push(Unregister::account(account_id.clone()).into()); } } for k in 0..assets_per_domain { if k % nth == 0 { let asset_definition_id = construct_asset_definition_id(k, domain_id.clone()); - instructions.push(UnregisterExpr::new(asset_definition_id).into()); + instructions.push(Unregister::asset_definition(asset_definition_id).into()); } } } @@ -138,19 +138,19 @@ pub fn restore_every_nth( accounts_per_domain: usize, assets_per_domain: usize, nth: usize, -) -> Vec { - let mut instructions: Vec = Vec::new(); +) -> Vec { + let mut instructions: Vec = Vec::new(); for i in 0..domains { let domain_id = construct_domain_id(i); if i % nth == 0 { let domain = Domain::new(domain_id.clone()); - instructions.push(RegisterExpr::new(domain).into()); + instructions.push(Register::domain(domain).into()); } for j in 0..accounts_per_domain { if j % nth == 0 || i % nth == 0 { let account_id = construct_account_id(j, domain_id.clone()); let account = Account::new(account_id.clone(), []); - instructions.push(RegisterExpr::new(account).into()); + instructions.push(Register::account(account).into()); } } for k in 0..assets_per_domain { @@ -160,7 +160,7 @@ pub fn restore_every_nth( asset_definition_id, iroha_data_model::asset::AssetValueType::Quantity, ); - instructions.push(RegisterExpr::new(asset_definition).into()); + instructions.push(Register::asset_definition(asset_definition).into()); } } } @@ -186,7 +186,7 @@ pub fn build_wsv(account_id: &AccountId, key_pair: &KeyPair) -> WorldStateView { let wasm = std::fs::read(&path_to_executor) .unwrap_or_else(|_| panic!("Failed to read file: {}", path_to_executor.display())); let executor = Executor::new(WasmSmartContract::from_compiled(wasm)); - UpgradeExpr::new(executor) + Upgrade::new(executor) .execute(account_id, &mut wsv) .expect("Failed to load executor"); } diff --git a/core/benches/blocks/validate_blocks.rs b/core/benches/blocks/validate_blocks.rs index 6a6d0bc585d..f39e7eb288e 100644 --- a/core/benches/blocks/validate_blocks.rs +++ b/core/benches/blocks/validate_blocks.rs @@ -1,6 +1,6 @@ use eyre::Result; use iroha_core::prelude::*; -use iroha_data_model::{isi::InstructionExpr, prelude::*}; +use iroha_data_model::{isi::InstructionBox, prelude::*}; #[path = "./common.rs"] mod common; @@ -10,7 +10,7 @@ use common::*; #[derive(Clone)] pub struct WsvValidateBlocks { wsv: WorldStateView, - instructions: Vec>, + instructions: Vec>, key_pair: KeyPair, account_id: AccountId, } diff --git a/core/benches/kura.rs b/core/benches/kura.rs index 279f8d97528..a47f731e31d 100644 --- a/core/benches/kura.rs +++ b/core/benches/kura.rs @@ -23,11 +23,7 @@ async fn measure_block_size_for_n_executors(n_executors: u32) { let bob_id = AccountId::from_str("bob@test").expect("tested"); let xor_id = AssetDefinitionId::from_str("xor#test").expect("tested"); let alice_xor_id = AssetId::new(xor_id, alice_id); - let transfer = TransferExpr::new( - IdBox::AssetId(alice_xor_id), - 10_u32.to_value(), - IdBox::AccountId(bob_id), - ); + let transfer = Transfer::asset_quantity(alice_xor_id, 10_u32, bob_id); let keypair = KeyPair::generate().expect("Failed to generate KeyPair."); let tx = TransactionBuilder::new(AccountId::from_str("alice@wonderland").expect("checked")) .with_instructions([transfer]) diff --git a/core/benches/validation.rs b/core/benches/validation.rs index 0a474ab3ea0..3a5bcaefe23 100644 --- a/core/benches/validation.rs +++ b/core/benches/validation.rs @@ -12,7 +12,7 @@ use iroha_core::{ tx::TransactionExecutor, wsv::World, }; -use iroha_data_model::{prelude::*, transaction::TransactionLimits}; +use iroha_data_model::{isi::InstructionBox, prelude::*, transaction::TransactionLimits}; use iroha_primitives::unique_vec::UniqueVec; const START_DOMAIN: &str = "start"; @@ -26,23 +26,25 @@ const TRANSACTION_LIMITS: TransactionLimits = TransactionLimits { fn build_test_transaction(keys: KeyPair) -> SignedTransaction { let domain_name = "domain"; let domain_id = DomainId::from_str(domain_name).expect("does not panic"); - let create_domain = RegisterExpr::new(Domain::new(domain_id)); + let create_domain: InstructionBox = Register::domain(Domain::new(domain_id)).into(); let account_name = "account"; let (public_key, _) = KeyPair::generate() .expect("Failed to generate KeyPair.") .into(); - let create_account = RegisterExpr::new(Account::new( + let create_account = Register::account(Account::new( AccountId::new( account_name.parse().expect("Valid"), domain_name.parse().expect("Valid"), ), [public_key], - )); + )) + .into(); let asset_definition_id = AssetDefinitionId::new( "xor".parse().expect("Valid"), domain_name.parse().expect("Valid"), ); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id)); + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id)).into(); let instructions = [create_domain, create_account, create_asset]; TransactionBuilder::new(AccountId::new( @@ -82,7 +84,7 @@ fn build_test_and_transient_wsv(keys: KeyPair) -> WorldStateView { .unwrap_or_else(|_| panic!("Failed to read file: {}", path_to_executor.display())); let executor = Executor::new(WasmSmartContract::from_compiled(wasm)); let authority = "genesis@genesis".parse().expect("Valid"); - UpgradeExpr::new(executor) + Upgrade::new(executor) .execute(&authority, &mut wsv) .expect("Failed to load executor"); } diff --git a/core/src/block.rs b/core/src/block.rs index 9322d16400d..21a7405ef42 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -735,7 +735,7 @@ mod tests { // Creating an instruction let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); let create_asset_definition = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id)); + Register::asset_definition(AssetDefinition::quantity(asset_definition_id)); // Making two transactions that have the same instruction let transaction_limits = &wsv.transaction_executor().transaction_limits; @@ -778,7 +778,7 @@ mod tests { // Creating an instruction let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").expect("Valid"); let create_asset_definition = - RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone())); + Register::asset_definition(AssetDefinition::quantity(asset_definition_id.clone())); // Making two transactions that have the same instruction let transaction_limits = &wsv.transaction_executor().transaction_limits; @@ -791,14 +791,14 @@ mod tests { let quantity: u32 = 200; let fail_quantity: u32 = 20; - let fail_mint = MintExpr::new( - fail_quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id.clone(), alice_id.clone())), + let fail_mint = Mint::asset_quantity( + fail_quantity, + AssetId::new(asset_definition_id.clone(), alice_id.clone()), ); - let succeed_mint = MintExpr::new( - quantity.to_value(), - IdBox::AssetId(AssetId::new(asset_definition_id, alice_id.clone())), + let succeed_mint = Mint::asset_quantity( + quantity, + AssetId::new(asset_definition_id, alice_id.clone()), ); let tx0 = TransactionBuilder::new(alice_id.clone()) @@ -848,14 +848,15 @@ mod tests { let transaction_limits = &wsv.transaction_executor().transaction_limits; let domain_id = DomainId::from_str("domain").expect("Valid"); - let create_domain = RegisterExpr::new(Domain::new(domain_id)); + let create_domain = Register::domain(Domain::new(domain_id)); let asset_definition_id = AssetDefinitionId::from_str("coin#domain").expect("Valid"); - let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id)); - let instructions_fail: [InstructionExpr; 2] = [ + let create_asset = + Register::asset_definition(AssetDefinition::quantity(asset_definition_id)); + let instructions_fail: [InstructionBox; 2] = [ create_domain.clone().into(), - Fail::new("Always fail").into(), + Fail::new("Always fail".to_owned()).into(), ]; - let instructions_accept: [InstructionExpr; 2] = [create_domain.into(), create_asset.into()]; + let instructions_accept: [InstructionBox; 2] = [create_domain.into(), create_asset.into()]; let tx_fail = TransactionBuilder::new(alice_id.clone()) .with_instructions(instructions_fail) .sign(alice_keys.clone()) diff --git a/core/src/executor.rs b/core/src/executor.rs index 971f7dd7867..62af571fe49 100644 --- a/core/src/executor.rs +++ b/core/src/executor.rs @@ -4,7 +4,7 @@ use derive_more::DebugCustom; use iroha_data_model::{ account::AccountId, executor as data_model_executor, - isi::InstructionExpr, + isi::InstructionBox, query::QueryBox, transaction::{Executable, SignedTransaction}, ValidationFail, @@ -181,7 +181,7 @@ impl Executor { &self, wsv: &mut WorldStateView, authority: &AccountId, - instruction: InstructionExpr, + instruction: InstructionBox, ) -> Result<(), ValidationFail> { trace!("Running instruction validation"); diff --git a/core/src/queue.rs b/core/src/queue.rs index e19e46f2ba5..2872ebc9365 100644 --- a/core/src/queue.rs +++ b/core/src/queue.rs @@ -512,7 +512,7 @@ mod tests { .build() .expect("Default queue config should always build") }); - let instructions: [InstructionExpr; 0] = []; + let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new("alice@wonderland".parse().expect("Valid")) .with_instructions(instructions); let tx_limits = TransactionLimits { diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index 2c1e67ca25c..eff4dbaebbc 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -478,10 +478,9 @@ pub mod isi { /// Account-related [`Query`] instructions. pub mod query { - use eyre::{Result, WrapErr}; + use eyre::Result; use iroha_data_model::{ account::Account, - evaluate::ExpressionEvaluator, permission::PermissionToken, query::{error::QueryExecutionFail as Error, MetadataValue}, }; @@ -494,13 +493,10 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let account_id = wsv - .evaluate(&self.id) - .wrap_err("Failed to evaluate account id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let account_id = &self.id; iroha_logger::trace!(%account_id, roles=?wsv.world.roles); - wsv.account(&account_id)?; - Ok(Box::new(wsv.account_roles(&account_id).cloned())) + wsv.account(account_id)?; + Ok(Box::new(wsv.account_roles(account_id).cloned())) } } @@ -510,13 +506,10 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let account_id = wsv - .evaluate(&self.id) - .wrap_err("Failed to evaluate account id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let account_id = &self.id; iroha_logger::trace!(%account_id, accounts=?wsv.world.domains); Ok(Box::new( - wsv.account_permission_tokens(&account_id)?.cloned(), + wsv.account_permission_tokens(account_id)?.cloned(), )) } } @@ -539,12 +532,9 @@ pub mod query { impl ValidQuery for FindAccountById { #[metrics(+"find_account_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to evaluate id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; iroha_logger::trace!(%id); - wsv.map_account(&id, Clone::clone).map_err(Into::into) + wsv.map_account(id, Clone::clone).map_err(Into::into) } } @@ -554,10 +544,7 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let name = wsv - .evaluate(&self.name) - .wrap_err("Failed to evaluate account name") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let name = self.name.clone(); iroha_logger::trace!(%name); Ok(Box::new( wsv.domains() @@ -581,30 +568,21 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let id = wsv - .evaluate(&self.domain_id) - .wrap_err("Failed to evaluate domain id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.domain_id; iroha_logger::trace!(%id); - Ok(Box::new(wsv.domain(&id)?.accounts.values().cloned())) + Ok(Box::new(wsv.domain(id)?.accounts.values().cloned())) } } impl ValidQuery for FindAccountKeyValueByIdAndKey { #[metrics(+"find_account_key_value_by_id_and_key")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to evaluate account id") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let key = wsv - .evaluate(&self.key) - .wrap_err("Failed to evaluate key") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; + let key = &self.key; iroha_logger::trace!(%id, %key); - wsv.map_account(&id, |account| account.metadata.get(&key).map(Clone::clone))? - .ok_or_else(|| FindError::MetadataKey(key).into()) + wsv.map_account(id, |account| account.metadata.get(key).map(Clone::clone))? + .ok_or_else(|| FindError::MetadataKey(key.clone()).into()) .map(Into::into) } } @@ -615,10 +593,7 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let asset_definition_id = wsv - .evaluate(&self.asset_definition_id) - .wrap_err("Failed to evaluate asset id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let asset_definition_id = self.asset_definition_id.clone(); iroha_logger::trace!(%asset_definition_id); Ok(Box::new( diff --git a/core/src/smartcontracts/isi/asset.rs b/core/src/smartcontracts/isi/asset.rs index 6d5fd0ccda3..4aaf3e19168 100644 --- a/core/src/smartcontracts/isi/asset.rs +++ b/core/src/smartcontracts/isi/asset.rs @@ -416,7 +416,7 @@ pub mod isi { /// Asset-related query implementations. pub mod query { - use eyre::{Result, WrapErr as _}; + use eyre::Result; use iroha_data_model::{ asset::{Asset, AssetDefinition}, query::{ @@ -464,12 +464,9 @@ pub mod query { impl ValidQuery for FindAssetById { #[metrics(+"find_asset_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; iroha_logger::trace!(%id); - wsv.asset(&id).map_err(|asset_err| { + wsv.asset(id).map_err(|asset_err| { if let Err(definition_err) = wsv.asset_definition(&id.definition_id) { definition_err.into() } else { @@ -482,12 +479,9 @@ pub mod query { impl ValidQuery for FindAssetDefinitionById { #[metrics(+"find_asset_defintion_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset definition id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; - let entry = wsv.asset_definition(&id).map_err(Error::from)?; + let entry = wsv.asset_definition(id).map_err(Error::from)?; Ok(entry) } @@ -499,10 +493,7 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let name = wsv - .evaluate(&self.name) - .wrap_err("Failed to get asset name") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let name = self.name.clone(); iroha_logger::trace!(%name); Ok(Box::new( wsv.domains() @@ -530,12 +521,9 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let id = wsv - .evaluate(&self.account_id) - .wrap_err("Failed to get account id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.account_id; iroha_logger::trace!(%id); - Ok(Box::new(wsv.account_assets(&id)?.cloned())) + Ok(Box::new(wsv.account_assets(id)?.cloned())) } } @@ -545,10 +533,7 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let id = wsv - .evaluate(&self.asset_definition_id) - .wrap_err("Failed to get asset definition id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = self.asset_definition_id.clone(); iroha_logger::trace!(%id); Ok(Box::new( wsv.domains() @@ -576,13 +561,10 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let id = wsv - .evaluate(&self.domain_id) - .wrap_err("Failed to get domain id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.domain_id; iroha_logger::trace!(%id); Ok(Box::new( - wsv.domain(&id)? + wsv.domain(id)? .accounts .values() .flat_map(|account| account.assets.values()) @@ -597,14 +579,8 @@ pub mod query { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, Error> { - let domain_id = wsv - .evaluate(&self.domain_id) - .wrap_err("Failed to get domain id") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let asset_definition_id = wsv - .evaluate(&self.asset_definition_id) - .wrap_err("Failed to get asset definition id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let domain_id = self.domain_id.clone(); + let asset_definition_id = self.asset_definition_id.clone(); let domain = wsv.domain(&domain_id)?; let _definition = domain .asset_definitions @@ -632,13 +608,10 @@ pub mod query { impl ValidQuery for FindAssetQuantityById { #[metrics(+"find_asset_quantity_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; iroha_logger::trace!(%id); let value = wsv - .asset(&id) + .asset(id) .map_err(|asset_err| { if let Err(definition_err) = wsv.asset_definition(&id.definition_id) { Error::Find(definition_err) @@ -656,12 +629,9 @@ pub mod query { impl ValidQuery for FindTotalAssetQuantityByAssetDefinitionId { #[metrics(+"find_total_asset_quantity_by_asset_definition_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset definition id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; iroha_logger::trace!(%id); - let asset_value = wsv.asset_total_amount(&id)?; + let asset_value = wsv.asset_total_amount(id)?; Ok(asset_value) } } @@ -669,15 +639,9 @@ pub mod query { impl ValidQuery for FindAssetKeyValueByIdAndKey { #[metrics(+"find_asset_key_value_by_id_and_key")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset id") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let key = wsv - .evaluate(&self.key) - .wrap_err("Failed to get key") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let asset = wsv.asset(&id).map_err(|asset_err| { + let id = &self.id; + let key = &self.key; + let asset = wsv.asset(id).map_err(|asset_err| { if let Err(definition_err) = wsv.asset_definition(&id.definition_id) { Error::Find(definition_err) } else { @@ -691,8 +655,8 @@ pub mod query { .map_err(eyre::Error::from) .map_err(|e| Error::Conversion(e.to_string()))?; store - .get(&key) - .ok_or_else(|| Error::Find(FindError::MetadataKey(key))) + .get(key) + .ok_or_else(|| Error::Find(FindError::MetadataKey(key.clone()))) .cloned() .map(Into::into) } diff --git a/core/src/smartcontracts/isi/block.rs b/core/src/smartcontracts/isi/block.rs index 4f241372ef1..08f4af0fb6e 100644 --- a/core/src/smartcontracts/isi/block.rs +++ b/core/src/smartcontracts/isi/block.rs @@ -1,8 +1,7 @@ //! This module contains trait implementations related to block queries -use eyre::{Result, WrapErr}; +use eyre::Result; use iroha_data_model::{ block::{BlockHeader, SignedBlock}, - evaluate::ExpressionEvaluator, query::{ block::FindBlockHeaderByHash, error::{FindError, QueryExecutionFail}, @@ -43,10 +42,7 @@ impl ValidQuery for FindAllBlockHeaders { impl ValidQuery for FindBlockHeaderByHash { #[metrics(+"find_block_header")] fn execute(&self, wsv: &WorldStateView) -> Result { - let hash = wsv - .evaluate(&self.hash) - .wrap_err("Failed to evaluate hash") - .map_err(|e| QueryExecutionFail::Evaluate(e.to_string()))?; + let hash = self.hash; let block = wsv .all_blocks() diff --git a/core/src/smartcontracts/isi/domain.rs b/core/src/smartcontracts/isi/domain.rs index b7930106a04..81855321e7c 100644 --- a/core/src/smartcontracts/isi/domain.rs +++ b/core/src/smartcontracts/isi/domain.rs @@ -301,7 +301,7 @@ pub mod isi { /// Query module provides [`Query`] Domain related implementations. pub mod query { - use eyre::{Result, WrapErr}; + use eyre::Result; use iroha_data_model::{ domain::Domain, query::{error::QueryExecutionFail as Error, MetadataValue}, @@ -322,29 +322,20 @@ pub mod query { impl ValidQuery for FindDomainById { #[metrics(+"find_domain_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get domain id") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; iroha_logger::trace!(%id); - Ok(wsv.domain(&id)?.clone()) + Ok(wsv.domain(id)?.clone()) } } impl ValidQuery for FindDomainKeyValueByIdAndKey { #[metrics(+"find_domain_key_value_by_id_and_key")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get domain id") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let key = wsv - .evaluate(&self.key) - .wrap_err("Failed to get key") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; + let key = &self.key; iroha_logger::trace!(%id, %key); - wsv.map_domain(&id, |domain| domain.metadata.get(&key).map(Clone::clone))? - .ok_or_else(|| FindError::MetadataKey(key).into()) + wsv.map_domain(id, |domain| domain.metadata.get(key).map(Clone::clone))? + .ok_or_else(|| FindError::MetadataKey(key.clone()).into()) .map(Into::into) } } @@ -352,20 +343,14 @@ pub mod query { impl ValidQuery for FindAssetDefinitionKeyValueByIdAndKey { #[metrics(+"find_asset_definition_key_value_by_id_and_key")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .wrap_err("Failed to get asset definition id") - .map_err(|e| Error::Evaluate(e.to_string()))?; - let key = wsv - .evaluate(&self.key) - .wrap_err("Failed to get key") - .map_err(|e| Error::Evaluate(e.to_string()))?; + let id = &self.id; + let key = &self.key; iroha_logger::trace!(%id, %key); Ok(wsv - .asset_definition(&id)? + .asset_definition(id)? .metadata - .get(&key) - .ok_or(FindError::MetadataKey(key)) + .get(key) + .ok_or(FindError::MetadataKey(key.clone())) .cloned() .map(Into::into)?) } diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index 7f80bbfade1..a0bf424f1ef 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -12,12 +12,10 @@ pub mod world; use eyre::Result; use iroha_data_model::{ - evaluate::ExpressionEvaluator, isi::{error::InstructionExecutionError as Error, *}, prelude::*, }; -use iroha_logger::prelude::{Span, *}; -use iroha_primitives::fixed::Fixed; +use iroha_logger::prelude::*; use super::Execute; use crate::{prelude::*, wsv::WorldStateView}; @@ -31,343 +29,167 @@ pub trait Registrable { fn build(self, authority: &AccountId) -> Self::Target; } -impl Execute for InstructionExpr { +impl Execute for InstructionBox { fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { iroha_logger::debug!(isi=%self, "Executing"); - macro_rules! match_all { - ($($isi:ident),+ $(,)?) => { - - match self { $( - InstructionExpr::$isi(isi) => isi.execute(authority, wsv), )+ - } - }; - } - - match_all! { - Register, - Unregister, - Mint, - Burn, - Transfer, - If, - Pair, - Sequence, - Fail, - SetKeyValue, - RemoveKeyValue, - Grant, - Revoke, - ExecuteTrigger, - SetParameter, - NewParameter, - Upgrade, - Log, + match self { + Self::Register(isi) => isi.execute(authority, wsv), + Self::Unregister(isi) => isi.execute(authority, wsv), + Self::Mint(isi) => isi.execute(authority, wsv), + Self::Burn(isi) => isi.execute(authority, wsv), + Self::Transfer(isi) => isi.execute(authority, wsv), + Self::Fail(isi) => isi.execute(authority, wsv), + Self::SetKeyValue(isi) => isi.execute(authority, wsv), + Self::RemoveKeyValue(isi) => isi.execute(authority, wsv), + Self::Grant(isi) => isi.execute(authority, wsv), + Self::Revoke(isi) => isi.execute(authority, wsv), + Self::ExecuteTrigger(isi) => isi.execute(authority, wsv), + Self::SetParameter(isi) => isi.execute(authority, wsv), + Self::NewParameter(isi) => isi.execute(authority, wsv), + Self::Upgrade(isi) => isi.execute(authority, wsv), + Self::Log(isi) => isi.execute(authority, wsv), } } } -impl Execute for RegisterExpr { +impl Execute for RegisterBox { #[iroha_logger::log(name = "register", skip_all, fields(id))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let object_id = wsv.evaluate(&self.object)?; - Span::current().record("id", &object_id.to_string()); - match object_id { - RegistrableBox::Peer(object) => Register:: { object }.execute(authority, wsv), - RegistrableBox::Domain(object) => Register:: { object }.execute(authority, wsv), - RegistrableBox::Account(object) => { - Register:: { object }.execute(authority, wsv) - } - RegistrableBox::AssetDefinition(object) => { - Register:: { object }.execute(authority, wsv) - } - RegistrableBox::Asset(object) => Register:: { object }.execute(authority, wsv), - RegistrableBox::Trigger(object) => { - Register::> { object }.execute(authority, wsv) - } - RegistrableBox::Role(object) => Register:: { object }.execute(authority, wsv), + match self { + Self::Peer(isi) => isi.execute(authority, wsv), + Self::Domain(isi) => isi.execute(authority, wsv), + Self::Account(isi) => isi.execute(authority, wsv), + Self::AssetDefinition(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), + Self::Role(isi) => isi.execute(authority, wsv), + Self::Trigger(isi) => isi.execute(authority, wsv), } } } -impl Execute for UnregisterExpr { +impl Execute for UnregisterBox { #[iroha_logger::log(name = "unregister", skip_all, fields(id))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let object_id = wsv.evaluate(&self.object_id)?; - Span::current().record("id", &object_id.to_string()); - match object_id { - IdBox::AccountId(object_id) => { - Unregister:: { object_id }.execute(authority, wsv) - } - IdBox::AssetId(object_id) => Unregister:: { object_id }.execute(authority, wsv), - IdBox::AssetDefinitionId(object_id) => { - Unregister:: { object_id }.execute(authority, wsv) - } - IdBox::DomainId(object_id) => { - Unregister:: { object_id }.execute(authority, wsv) - } - IdBox::PeerId(object_id) => Unregister:: { object_id }.execute(authority, wsv), - IdBox::RoleId(object_id) => Unregister:: { object_id }.execute(authority, wsv), - IdBox::TriggerId(object_id) => { - Unregister::> { object_id }.execute(authority, wsv) - } - IdBox::PermissionTokenId(_) | IdBox::ParameterId(_) => { - Err(Error::Evaluate(InstructionType::Unregister.into())) - } + match self { + Self::Peer(isi) => isi.execute(authority, wsv), + Self::Domain(isi) => isi.execute(authority, wsv), + Self::Account(isi) => isi.execute(authority, wsv), + Self::AssetDefinition(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), + Self::Role(isi) => isi.execute(authority, wsv), + Self::Trigger(isi) => isi.execute(authority, wsv), } } } -impl Execute for MintExpr { +impl Execute for MintBox { #[iroha_logger::log(name = "Mint", skip_all, fields(destination))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let destination_id = wsv.evaluate(&self.destination_id)?; - let object = wsv.evaluate(&self.object)?; - Span::current().record("destination", &destination_id.to_string()); - iroha_logger::trace!(?object, %authority); - match (destination_id, object) { - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::U32(object))) => { - Mint:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::U128(object))) => { - Mint:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::Fixed(object))) => { - Mint:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::AccountId(destination_id), Value::PublicKey(object)) => { - Mint:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::AccountId(destination_id), Value::SignatureCheckCondition(object)) => { - Mint:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::TriggerId(destination_id), Value::Numeric(NumericValue::U32(object))) => { - Mint::> { - object, - destination_id, - } - .execute(authority, wsv) - } - _ => Err(Error::Evaluate(InstructionType::Mint.into())), + match self { + Self::Account(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), + Self::TriggerRepetitions(isi) => isi.execute(authority, wsv), } } } -impl Execute for BurnExpr { - #[iroha_logger::log(name = "burn", skip_all, fields(destination))] - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let destination_id = wsv.evaluate(&self.destination_id)?; - let object = wsv.evaluate(&self.object)?; - Span::current().record("destination", &destination_id.to_string()); - iroha_logger::trace!(?object, %authority); - match (destination_id, object) { - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::U32(object))) => { - Burn:: { - object, - destination_id, - } - .execute(authority, wsv) - } - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::U128(object))) => Burn { - object, - destination_id, - } - .execute(authority, wsv), - (IdBox::AssetId(destination_id), Value::Numeric(NumericValue::Fixed(object))) => Burn { - object, - destination_id, - } - .execute(authority, wsv), - (IdBox::AccountId(destination_id), Value::PublicKey(object)) => Burn { - object, - destination_id, - } - .execute(authority, wsv), - (IdBox::TriggerId(destination_id), Value::Numeric(NumericValue::U32(object))) => { - Burn::> { - object, - destination_id, - } - .execute(authority, wsv) - } - // TODO: Not implemented yet. - // (IdBox::AccountId(account_id), Value::SignatureCheckCondition(condition)) => { - // Burn::{condition, account_id}.execute(authority, wsv) - // } - _ => Err(Error::Evaluate(InstructionType::Burn.into())), +impl Execute for AccountMintBox { + fn execute( + self, + authority: &AccountId, + wsv: &mut WorldStateView, + ) -> std::prelude::v1::Result<(), Error> { + match self { + Self::PublicKey(isi) => isi.execute(authority, wsv), + Self::SignatureCheckCondition(isi) => isi.execute(authority, wsv), } } } -impl Execute for TransferExpr { - #[iroha_logger::log(name = "transfer", skip_all, fields(from, to))] - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let source_id = wsv.evaluate(&self.source_id)?; - let destination_id = wsv.evaluate(&self.destination_id)?; - let object = wsv.evaluate(&self.object)?; - iroha_logger::trace!(%object, %authority); - Span::current().record("from", source_id.to_string()); - Span::current().record("to", destination_id.to_string()); - - match (source_id, object, destination_id) { - ( - IdBox::AssetId(source_id), - Value::Numeric(value), - IdBox::AccountId(destination_id), - ) => match value { - NumericValue::U32(object) => Transfer { - source_id, - object, - destination_id, - } - .execute(authority, wsv), - NumericValue::U128(object) => Transfer { - source_id, - object, - destination_id, - } - .execute(authority, wsv), - NumericValue::Fixed(object) => Transfer { - source_id, - object, - destination_id, - } - .execute(authority, wsv), - _ => Err(Error::Evaluate(InstructionType::Transfer.into())), - }, - ( - IdBox::AccountId(source_id), - Value::Id(IdBox::AssetDefinitionId(object)), - IdBox::AccountId(destination_id), - ) => Transfer { - source_id, - object, - destination_id, - } - .execute(authority, wsv), - ( - IdBox::AccountId(source_id), - Value::Id(IdBox::DomainId(object)), - IdBox::AccountId(destination_id), - ) => Transfer { - source_id, - object, - destination_id, - } - .execute(authority, wsv), - _ => Err(Error::Evaluate(InstructionType::Transfer.into())), +impl Execute for AssetMintBox { + fn execute( + self, + authority: &AccountId, + wsv: &mut WorldStateView, + ) -> std::prelude::v1::Result<(), Error> { + match self { + Self::Quantity(isi) => isi.execute(authority, wsv), + Self::BigQuantity(isi) => isi.execute(authority, wsv), + Self::Fixed(isi) => isi.execute(authority, wsv), } } } -impl Execute for SetKeyValueExpr { +impl Execute for BurnBox { + #[iroha_logger::log(name = "burn", skip_all, fields(destination))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let key = wsv.evaluate(&self.key)?; - let value = wsv.evaluate(&self.value)?; - iroha_logger::trace!(?key, ?value, %authority); - match wsv.evaluate(&self.object_id)? { - IdBox::AssetId(object_id) => SetKeyValue:: { - object_id, - key, - value, - } - .execute(authority, wsv), - IdBox::AssetDefinitionId(object_id) => SetKeyValue:: { - object_id, - key, - value, - } - .execute(authority, wsv), - IdBox::AccountId(object_id) => SetKeyValue:: { - object_id, - key, - value, - } - .execute(authority, wsv), - IdBox::DomainId(object_id) => SetKeyValue:: { - object_id, - key, - value, - } - .execute(authority, wsv), - _ => Err(Error::Evaluate(InstructionType::SetKeyValue.into())), + match self { + Self::AccountPublicKey(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), + Self::TriggerRepetitions(isi) => isi.execute(authority, wsv), } } } -impl Execute for RemoveKeyValueExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let key = wsv.evaluate(&self.key)?; - iroha_logger::trace!(?key, %authority); - match wsv.evaluate(&self.object_id)? { - IdBox::AssetId(object_id) => { - RemoveKeyValue:: { object_id, key }.execute(authority, wsv) - } - IdBox::AssetDefinitionId(object_id) => { - RemoveKeyValue:: { object_id, key }.execute(authority, wsv) - } - IdBox::AccountId(object_id) => { - RemoveKeyValue:: { object_id, key }.execute(authority, wsv) - } - IdBox::DomainId(object_id) => { - RemoveKeyValue:: { object_id, key }.execute(authority, wsv) - } - _ => Err(Error::Evaluate(InstructionType::RemoveKeyValue.into())), +impl Execute for AssetBurnBox { + fn execute( + self, + authority: &AccountId, + wsv: &mut WorldStateView, + ) -> std::prelude::v1::Result<(), Error> { + match self { + Self::Quantity(isi) => isi.execute(authority, wsv), + Self::BigQuantity(isi) => isi.execute(authority, wsv), + Self::Fixed(isi) => isi.execute(authority, wsv), } } } -impl Execute for ConditionalExpr { +impl Execute for TransferBox { + #[iroha_logger::log(name = "transfer", skip_all, fields(from, to))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - iroha_logger::trace!(?self); - if wsv.evaluate(&self.condition)? { - self.then.execute(authority, wsv)?; - } else if let Some(otherwise) = self.otherwise { - otherwise.execute(authority, wsv)?; + match self { + Self::Domain(isi) => isi.execute(authority, wsv), + Self::AssetDefinition(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), } - Ok(()) } } -impl Execute for PairExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - iroha_logger::trace!(?self); +impl Execute for AssetTransferBox { + fn execute( + self, + authority: &AccountId, + wsv: &mut WorldStateView, + ) -> std::prelude::v1::Result<(), Error> { + match self { + Self::Quantity(isi) => isi.execute(authority, wsv), + Self::BigQuantity(isi) => isi.execute(authority, wsv), + Self::Fixed(isi) => isi.execute(authority, wsv), + } + } +} - self.left_instruction.execute(authority, wsv)?; - self.right_instruction.execute(authority, wsv)?; - Ok(()) +impl Execute for SetKeyValueBox { + fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { + match self { + Self::Domain(isi) => isi.execute(authority, wsv), + Self::Account(isi) => isi.execute(authority, wsv), + Self::AssetDefinition(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), + } } } -impl Execute for SequenceExpr { - #[iroha_logger::log(skip_all, name = "Sequence", fields(count))] +impl Execute for RemoveKeyValueBox { fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - Span::current().record("count", self.instructions.len()); - for instruction in self.instructions { - iroha_logger::trace!(%instruction); - instruction.execute(authority, wsv)?; + match self { + Self::Domain(isi) => isi.execute(authority, wsv), + Self::Account(isi) => isi.execute(authority, wsv), + Self::AssetDefinition(isi) => isi.execute(authority, wsv), + Self::Asset(isi) => isi.execute(authority, wsv), } - Ok(()) } } @@ -379,86 +201,26 @@ impl Execute for Fail { } } -impl Execute for GrantExpr { +impl Execute for GrantBox { #[iroha_logger::log(name = "grant", skip_all, fields(object))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let destination_id = wsv.evaluate(&self.destination_id)?; - let object = wsv.evaluate(&self.object)?; - Span::current().record("object", &object.to_string()); - iroha_logger::trace!(%destination_id, %authority); - match object { - Value::PermissionToken(object) => Grant:: { - object, - destination_id, - } - .execute(authority, wsv), - Value::Id(IdBox::RoleId(object)) => Grant:: { - object, - destination_id, - } - .execute(authority, wsv), - _ => Err(Error::Evaluate(InstructionType::Grant.into())), + match self { + Self::PermissionToken(sub_isi) => sub_isi.execute(authority, wsv), + Self::Role(sub_isi) => sub_isi.execute(authority, wsv), } } } -impl Execute for RevokeExpr { +impl Execute for RevokeBox { #[iroha_logger::log(name = "revoke", skip_all, fields(object))] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let destination_id = wsv.evaluate(&self.destination_id)?; - let object = wsv.evaluate(&self.object)?; - Span::current().record("object", &object.to_string()); - iroha_logger::trace!(?destination_id, ?object, %authority); - match object { - Value::PermissionToken(object) => Revoke:: { - object, - destination_id, - } - .execute(authority, wsv), - Value::Id(IdBox::RoleId(object)) => Revoke:: { - object, - destination_id, - } - .execute(authority, wsv), - _ => Err(Error::Evaluate(InstructionType::Revoke.into())), + match self { + Self::PermissionToken(sub_isi) => sub_isi.execute(authority, wsv), + Self::Role(sub_isi) => sub_isi.execute(authority, wsv), } } } -impl Execute for SetParameterExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let parameter = wsv.evaluate(&self.parameter)?; - SetParameter { parameter }.execute(authority, wsv) - } -} - -impl Execute for NewParameterExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let parameter = wsv.evaluate(&self.parameter)?; - NewParameter { parameter }.execute(authority, wsv) - } -} - -impl Execute for UpgradeExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let object = wsv.evaluate(&self.object)?; - match object { - UpgradableBox::Executor(object) => { - Upgrade:: { object }.execute(authority, wsv) - } - } - } -} - -impl Execute for LogExpr { - fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let level = wsv.evaluate(&self.level)?; - let msg = wsv.evaluate(&self.msg)?; - - Log { level, msg }.execute(authority, wsv) - } -} - pub mod prelude { //! Re-export important traits and types for glob import `(::*)` pub use super::*; @@ -483,11 +245,11 @@ mod tests { let account_id = AccountId::from_str("alice@wonderland")?; let (public_key, _) = KeyPair::generate()?.into(); let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland")?; - RegisterExpr::new(Domain::new(DomainId::from_str("wonderland")?)) + Register::domain(Domain::new(DomainId::from_str("wonderland")?)) .execute(&genesis_account_id, &mut wsv)?; - RegisterExpr::new(Account::new(account_id, [public_key])) + Register::account(Account::new(account_id, [public_key])) .execute(&genesis_account_id, &mut wsv)?; - RegisterExpr::new(AssetDefinition::store(asset_definition_id)) + Register::asset_definition(AssetDefinition::store(asset_definition_id)) .execute(&genesis_account_id, &mut wsv)?; Ok(wsv) } @@ -499,8 +261,8 @@ mod tests { let account_id = AccountId::from_str("alice@wonderland")?; let asset_definition_id = AssetDefinitionId::from_str("rose#wonderland")?; let asset_id = AssetId::new(asset_definition_id, account_id.clone()); - SetKeyValueExpr::new( - IdBox::from(asset_id.clone()), + SetKeyValue::asset( + asset_id.clone(), Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32], ) @@ -526,8 +288,8 @@ mod tests { let kura = Kura::blank_kura_for_testing(); let mut wsv = wsv_with_test_domains(&kura)?; let account_id = AccountId::from_str("alice@wonderland")?; - SetKeyValueExpr::new( - IdBox::from(account_id.clone()), + SetKeyValue::account( + account_id.clone(), Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32], ) @@ -555,8 +317,8 @@ mod tests { let mut wsv = wsv_with_test_domains(&kura)?; let definition_id = AssetDefinitionId::from_str("rose#wonderland")?; let account_id = AccountId::from_str("alice@wonderland")?; - SetKeyValueExpr::new( - IdBox::from(definition_id.clone()), + SetKeyValue::asset_definition( + definition_id.clone(), Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32], ) @@ -583,8 +345,8 @@ mod tests { let mut wsv = wsv_with_test_domains(&kura)?; let domain_id = DomainId::from_str("wonderland")?; let account_id = AccountId::from_str("alice@wonderland")?; - SetKeyValueExpr::new( - IdBox::from(domain_id.clone()), + SetKeyValue::domain( + domain_id.clone(), Name::from_str("Bytes")?, vec![1_u32, 2_u32, 3_u32], ) @@ -613,7 +375,7 @@ mod tests { let trigger_id = TriggerId::from_str("test_trigger_id")?; assert!(matches!( - ExecuteTriggerExpr::new(trigger_id) + ExecuteTrigger::new(trigger_id) .execute(&account_id, &mut wsv) .expect_err("Error expected"), Error::Find(_) @@ -635,14 +397,14 @@ mod tests { .expect("Failed to generate KeyPair") .into(); let register_account = - RegisterExpr::new(Account::new(fake_account_id.clone(), [public_key])); + Register::account(Account::new(fake_account_id.clone(), [public_key])); register_account.execute(&account_id, &mut wsv)?; // register the trigger - let register_trigger = RegisterExpr::new(Trigger::new( + let register_trigger = Register::trigger(Trigger::new( trigger_id.clone(), Action::new( - Vec::::new(), + Vec::::new(), Repeats::Indefinitely, account_id.clone(), TriggeringFilterBox::ExecuteTrigger(ExecuteTriggerEventFilter::new( @@ -655,11 +417,11 @@ mod tests { register_trigger.execute(&account_id, &mut wsv)?; // execute with the valid account - ExecuteTriggerExpr::new(trigger_id.clone()).execute(&account_id, &mut wsv)?; + ExecuteTrigger::new(trigger_id.clone()).execute(&account_id, &mut wsv)?; // execute with the fake account assert!(matches!( - ExecuteTriggerExpr::new(trigger_id) + ExecuteTrigger::new(trigger_id) .execute(&fake_account_id, &mut wsv) .expect_err("Error expected"), Error::InvariantViolation(_) diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 19671e06587..d14ed740d0b 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -265,14 +265,14 @@ mod tests { wsv.config.transaction_limits = limits; let valid_tx = { - let instructions: [InstructionExpr; 0] = []; + let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(ALICE_ID.clone()) .with_instructions(instructions) .sign(ALICE_KEYS.clone())?; AcceptedTransaction::accept(tx, &limits)? }; let invalid_tx = { - let isi = Fail::new("fail"); + let isi = Fail::new("fail".to_owned()); let tx = TransactionBuilder::new(ALICE_ID.clone()) .with_instructions([isi.clone(), isi]) .sign(ALICE_KEYS.clone())?; @@ -413,7 +413,7 @@ mod tests { let query_handle = LiveQueryStore::test().start(); let mut wsv = WorldStateView::new(world_with_test_domains(), kura.clone(), query_handle); - let instructions: [InstructionExpr; 0] = []; + let instructions: [InstructionBox; 0] = []; let tx = TransactionBuilder::new(ALICE_ID.clone()) .with_instructions(instructions) .sign(ALICE_KEYS.clone())?; @@ -432,9 +432,7 @@ mod tests { kura.store_block(vcb); let unapplied_tx = TransactionBuilder::new(ALICE_ID.clone()) - .with_instructions([UnregisterExpr::new( - "account@domain".parse::().unwrap(), - )]) + .with_instructions([Unregister::account("account@domain".parse().unwrap())]) .sign(ALICE_KEYS.clone())?; let wrong_hash = unapplied_tx.hash(); let not_found = FindTransactionByHash::new(wrong_hash).execute(&wsv); diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index 7c814b6fe47..2b8da8ed19f 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -1,9 +1,7 @@ //! This module contains implementations of smart-contract traits and //! instructions for triggers in Iroha. -use iroha_data_model::{ - evaluate::ExpressionEvaluator, isi::error::MathError, prelude::*, query::error::FindError, -}; +use iroha_data_model::{isi::error::MathError, prelude::*, query::error::FindError}; use iroha_telemetry::metrics; pub mod set; @@ -154,13 +152,13 @@ pub mod isi { } } - impl Execute for ExecuteTriggerExpr { + impl Execute for ExecuteTrigger { #[metrics(+"execute_trigger")] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let id = wsv.evaluate(&self.trigger_id)?; + let id = &self.trigger_id; wsv.triggers() - .inspect_by_id(&id, |action| -> Result<(), Error> { + .inspect_by_id(id, |action| -> Result<(), Error> { let allow_execute = if let TriggeringFilterBox::ExecuteTrigger(filter) = action.clone_and_box().filter { @@ -186,7 +184,7 @@ pub mod isi { .ok_or_else(|| Error::Find(Box::new(FindError::Trigger(id.clone())))) .and_then(core::convert::identity)?; - wsv.execute_trigger(id, authority); + wsv.execute_trigger(id.clone(), authority); Ok(()) } @@ -217,43 +215,37 @@ pub mod query { impl ValidQuery for FindTriggerById { #[metrics(+"find_trigger_by_id")] fn execute(&self, wsv: &WorldStateView) -> Result, Error> { - let id = wsv - .evaluate(&self.id) - .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; + let id = &self.id; iroha_logger::trace!(%id); // Can't use just `LoadedActionTrait::clone_and_box` cause this will trigger lifetime mismatch #[allow(clippy::redundant_closure_for_method_calls)] let loaded_action = wsv .triggers() - .inspect_by_id(&id, |action| action.clone_and_box()) + .inspect_by_id(id, |action| action.clone_and_box()) .ok_or_else(|| Error::Find(FindError::Trigger(id.clone())))?; let action = wsv.triggers().get_original_action(loaded_action); // TODO: Should we redact the metadata if the account is not the authority/owner? - Ok(Trigger::new(id, action)) + Ok(Trigger::new(id.clone(), action)) } } impl ValidQuery for FindTriggerKeyValueByIdAndKey { #[metrics(+"find_trigger_key_value_by_id_and_key")] fn execute(&self, wsv: &WorldStateView) -> Result { - let id = wsv - .evaluate(&self.id) - .map_err(|e| Error::Evaluate(format!("Failed to evaluate trigger id. {e}")))?; - let key = wsv - .evaluate(&self.key) - .map_err(|e| Error::Evaluate(format!("Failed to evaluate key. {e}")))?; + let id = &self.id; + let key = &self.key; iroha_logger::trace!(%id, %key); wsv.triggers() - .inspect_by_id(&id, |action| { + .inspect_by_id(id, |action| { action .metadata() - .get(&key) + .get(key) .cloned() .ok_or_else(|| FindError::MetadataKey(key.clone()).into()) }) - .ok_or_else(|| Error::Find(FindError::Trigger(id)))? + .ok_or_else(|| Error::Find(FindError::Trigger(id.clone())))? .map(Into::into) } } @@ -265,13 +257,11 @@ pub mod query { wsv: &'wsv WorldStateView, ) -> eyre::Result> + 'wsv>, Error> { - let domain_id = wsv - .evaluate(&self.domain_id) - .map_err(|e| Error::Evaluate(format!("Failed to evaluate domain id. {e}")))?; + let domain_id = &self.domain_id; Ok(Box::new( wsv.triggers() - .inspect_by_domain_id(&domain_id, |trigger_id, action| { + .inspect_by_domain_id(domain_id, |trigger_id, action| { (trigger_id.clone(), action.clone_and_box()) }) .map(|(trigger_id, action)| { diff --git a/core/src/smartcontracts/isi/triggers/set.rs b/core/src/smartcontracts/isi/triggers/set.rs index b32e49190e1..57e32ae1406 100644 --- a/core/src/smartcontracts/isi/triggers/set.rs +++ b/core/src/smartcontracts/isi/triggers/set.rs @@ -862,7 +862,7 @@ pub enum LoadedExecutable { /// Loaded WASM Wasm(LoadedWasm), /// Vector of ISI - Instructions(Vec), + Instructions(Vec), } impl core::fmt::Debug for LoadedExecutable { diff --git a/core/src/smartcontracts/isi/tx.rs b/core/src/smartcontracts/isi/tx.rs index b33fa69f7f5..f103853a9d5 100644 --- a/core/src/smartcontracts/isi/tx.rs +++ b/core/src/smartcontracts/isi/tx.rs @@ -2,11 +2,10 @@ use std::sync::Arc; -use eyre::{Result, WrapErr}; +use eyre::Result; use iroha_crypto::HashOf; use iroha_data_model::{ block::SignedBlock, - evaluate::ExpressionEvaluator, prelude::*, query::{ error::{FindError, QueryExecutionFail}, @@ -66,7 +65,7 @@ impl ValidQuery for FindAllTransactions { .flat_map(BlockTransactionIter::new) .map(|tx| TransactionQueryOutput { block_hash: tx.block_hash(), - transaction: tx.value(), + transaction: Box::new(tx.value()), }), )) } @@ -78,10 +77,7 @@ impl ValidQuery for FindTransactionsByAccountId { &self, wsv: &'wsv WorldStateView, ) -> Result + 'wsv>, QueryExecutionFail> { - let account_id = wsv - .evaluate(&self.account_id) - .wrap_err("Failed to get account id") - .map_err(|e| QueryExecutionFail::Evaluate(e.to_string()))?; + let account_id = self.account_id.clone(); Ok(Box::new( wsv.all_blocks() @@ -89,7 +85,7 @@ impl ValidQuery for FindTransactionsByAccountId { .filter(move |tx| *tx.authority() == account_id) .map(|tx| TransactionQueryOutput { block_hash: tx.block_hash(), - transaction: tx.value(), + transaction: Box::new(tx.value()), }), )) } @@ -98,10 +94,7 @@ impl ValidQuery for FindTransactionsByAccountId { impl ValidQuery for FindTransactionByHash { #[metrics(+"find_transaction_by_hash")] fn execute(&self, wsv: &WorldStateView) -> Result { - let tx_hash = wsv - .evaluate(&self.hash) - .wrap_err("Failed to get hash") - .map_err(|e| QueryExecutionFail::Evaluate(e.to_string()))?; + let tx_hash = self.hash; iroha_logger::trace!(%tx_hash); if !wsv.has_transaction(tx_hash) { return Err(FindError::Transaction(tx_hash).into()); @@ -118,6 +111,7 @@ impl ValidQuery for FindTransactionByHash { .iter() .find(|transaction| transaction.value.hash() == tx_hash) .cloned() + .map(Box::new) .map(|transaction| TransactionQueryOutput { block_hash, transaction, diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index 64199fd9eb8..44ae2f2eb2e 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -214,10 +214,10 @@ pub mod isi { } } - impl Execute for Upgrade { + impl Execute for Upgrade { #[metrics(+"upgrade_executor")] fn execute(self, authority: &AccountId, wsv: &mut WorldStateView) -> Result<(), Error> { - let raw_executor = self.object; + let raw_executor = self.executor; // Cloning executor to avoid multiple mutable borrows of `wsv`. // Also it's a cheap operation. @@ -303,13 +303,11 @@ pub mod query { impl ValidQuery for FindRoleByRoleId { #[metrics(+"find_role_by_role_id")] fn execute(&self, wsv: &WorldStateView) -> Result { - let role_id = wsv - .evaluate(&self.id) - .map_err(|e| Error::Evaluate(e.to_string()))?; + let role_id = &self.id; iroha_logger::trace!(%role_id); - wsv.world.roles.get(&role_id).map_or_else( - || Err(Error::Find(FindError::Role(role_id))), + wsv.world.roles.get(role_id).map_or_else( + || Err(Error::Find(FindError::Role(role_id.clone()))), |role_ref| Ok(role_ref.clone()), ) } diff --git a/core/src/smartcontracts/mod.rs b/core/src/smartcontracts/mod.rs index 05d0195defd..35587c6580b 100644 --- a/core/src/smartcontracts/mod.rs +++ b/core/src/smartcontracts/mod.rs @@ -7,15 +7,12 @@ pub mod isi; pub mod wasm; -use std::collections::BTreeMap; - use iroha_data_model::{ - evaluate::ExpressionEvaluator, isi::error::InstructionExecutionError as Error, prelude::*, - query::error::QueryExecutionFail, + isi::error::InstructionExecutionError as Error, prelude::*, query::error::QueryExecutionFail, }; pub use isi::*; -use self::query::{Lazy, LazyValue}; +use self::query::Lazy; use crate::wsv::WorldStateView; /// Trait implementations should provide actions to apply changes on [`WorldStateView`]. @@ -44,50 +41,3 @@ where wsv: &'wsv WorldStateView, ) -> Result<::Lazy<'wsv>, QueryExecutionFail>; } - -impl ExpressionEvaluator for WorldStateView { - fn evaluate( - &self, - expression: &E, - ) -> Result { - expression.evaluate(&Context::new(self)) - } -} - -#[derive(Clone)] -pub(crate) struct Context<'wsv> { - values: BTreeMap, - wsv: &'wsv WorldStateView, -} - -impl<'a> Context<'a> { - /// Create new [`Self`] - pub fn new(wsv: &'a WorldStateView) -> Self { - Self { - values: BTreeMap::new(), - wsv, - } - } -} - -impl iroha_data_model::evaluate::Context for Context<'_> { - fn query(&self, query: &QueryBox) -> Result { - query - .execute(self.wsv) - .map(|value| match value { - LazyValue::Value(value) => value, - // NOTE: This will only be executed when evaluating an expression for an - // instruction, i.e. it will only be executed from the executor. - LazyValue::Iter(iter) => Value::Vec(iter.collect()), - }) - .map_err(Into::into) - } - - fn get(&self, name: &Name) -> Option<&Value> { - self.values.get(name) - } - - fn update(&mut self, other: impl IntoIterator) { - self.values.extend(other) - } -} diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index a7c0b8b757e..cde7fe6a624 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -13,7 +13,7 @@ use iroha_config::{ use iroha_data_model::{ account::AccountId, executor::{self, MigrationResult}, - isi::InstructionExpr, + isi::InstructionBox, permission::PermissionTokenSchema, prelude::*, query::{QueryBox, QueryId, QueryRequest, QueryWithParameters}, @@ -87,7 +87,7 @@ mod import { /// Execute `instruction` on host #[codec::wrap_trait_fn] fn execute_instruction( - instruction: InstructionExpr, + instruction: InstructionBox, state: &mut S, ) -> Result<(), ValidationFail>; } @@ -100,7 +100,7 @@ mod import { fn get_validate_transaction_payload(state: &S) -> Validate; #[codec::wrap_trait_fn] - fn get_validate_instruction_payload(state: &S) -> Validate; + fn get_validate_instruction_payload(state: &S) -> Validate; #[codec::wrap_trait_fn] fn get_validate_query_payload(state: &S) -> Validate; @@ -480,7 +480,7 @@ pub mod state { pub type ValidateQuery = Validate; /// State kind for executing `validate_instruction()` entrypoint of executor - pub type ValidateInstruction = Validate; + pub type ValidateInstruction = Validate; /// State kind for executing `migrate()` entrypoint of executor #[derive(Copy, Clone)] @@ -802,7 +802,7 @@ where impl<'wrld, S> Runtime, S>> { fn default_execute_instruction( - instruction: InstructionExpr, + instruction: InstructionBox, state: &mut state::CommonState, S>, ) -> Result<(), ValidationFail> { debug!(%instruction, "Executing"); @@ -912,7 +912,7 @@ impl<'wrld> import::traits::ExecuteOperations> #[codec::wrap] fn execute_instruction( - instruction: InstructionExpr, + instruction: InstructionBox, state: &mut state::SmartContract<'wrld>, ) -> Result<(), ValidationFail> { if let Some(limits_executor) = state.specific_state.limits_executor.as_mut() { @@ -984,7 +984,7 @@ impl<'wrld> import::traits::ExecuteOperations> #[codec::wrap] fn execute_instruction( - instruction: InstructionExpr, + instruction: InstructionBox, state: &mut state::Trigger<'wrld>, ) -> Result<(), ValidationFail> { Self::default_execute_instruction(instruction, state) @@ -1015,7 +1015,7 @@ where #[codec::wrap] fn execute_instruction( - instruction: InstructionExpr, + instruction: InstructionBox, state: &mut state::CommonState, S>, ) -> Result<(), ValidationFail> { debug!(%instruction, "Executing as executor"); @@ -1109,7 +1109,7 @@ impl<'wrld> import::traits::GetExecutorPayloads, - ) -> Validate { + ) -> Validate { panic!("Executor `validate_transaction()` entrypoint should not query payload for `validate_instruction()` entrypoint") } @@ -1141,7 +1141,7 @@ impl<'wrld> Runtime> { wsv: &'wrld mut WorldStateView, authority: &AccountId, module: &wasmtime::Module, - instruction: InstructionExpr, + instruction: InstructionBox, ) -> Result { let span = wasm_log_span!("Running `validate_instruction()`"); @@ -1184,7 +1184,7 @@ impl<'wrld> import::traits::GetExecutorPayloads, - ) -> Validate { + ) -> Validate { Validate { authority: state.authority.clone(), block_height: state.wsv.0.height(), @@ -1253,7 +1253,7 @@ impl<'wrld> import::traits::ExecuteOperations, ) -> Result<(), ValidationFail> { panic!("Executor `validate_query()` entrypoint should not execute instructions") @@ -1278,7 +1278,7 @@ impl<'wrld> import::traits::GetExecutorPayloads, - ) -> Validate { + ) -> Validate { panic!("Executor `validate_query()` entrypoint should not query payload for `validate_instruction()` entrypoint") } @@ -1377,7 +1377,7 @@ impl<'wrld> import::traits::GetExecutorPayloads> #[codec::wrap] fn get_validate_instruction_payload( _state: &state::executor::Migrate<'wrld>, - ) -> Validate { + ) -> Validate { panic!("Executor `migrate()` entrypoint should not query payload for `validate_instruction()` entrypoint") } @@ -1715,8 +1715,8 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = RegisterExpr::new(Account::new(new_authority, [])); - encode_hex(InstructionExpr::from(register_isi)) + let register_isi = Register::account(Account::new(new_authority, [])); + encode_hex(InstructionBox::from(register_isi)) }; let wat = format!( @@ -1801,8 +1801,8 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = RegisterExpr::new(Account::new(new_authority, [])); - encode_hex(InstructionExpr::from(register_isi)) + let register_isi = Register::account(Account::new(new_authority, [])); + encode_hex(InstructionBox::from(register_isi)) }; let wat = format!( @@ -1850,8 +1850,8 @@ mod tests { let isi_hex = { let new_authority = AccountId::from_str("mad_hatter@wonderland").expect("Valid"); - let register_isi = RegisterExpr::new(Account::new(new_authority, [])); - encode_hex(InstructionExpr::from(register_isi)) + let register_isi = Register::account(Account::new(new_authority, [])); + encode_hex(InstructionBox::from(register_isi)) }; let wat = format!( diff --git a/core/src/sumeragi/main_loop.rs b/core/src/sumeragi/main_loop.rs index 441c0946b2d..b24b263188a 100644 --- a/core/src/sumeragi/main_loop.rs +++ b/core/src/sumeragi/main_loop.rs @@ -1210,7 +1210,7 @@ mod tests { // Create "genesis" block // Creating an instruction - let fail_box: InstructionExpr = Fail::new("Dummy isi").into(); + let fail_box: InstructionBox = Fail::new("Dummy isi".to_owned()).into(); // Making two transactions that have the same instruction let tx = TransactionBuilder::new(alice_id.clone()) @@ -1231,10 +1231,10 @@ mod tests { kura.store_block(genesis); // Making two transactions that have the same instruction - let create_asset_definition1 = RegisterExpr::new(AssetDefinition::quantity( + let create_asset_definition1 = Register::asset_definition(AssetDefinition::quantity( "xor1#wonderland".parse().expect("Valid"), )); - let create_asset_definition2 = RegisterExpr::new(AssetDefinition::quantity( + let create_asset_definition2 = Register::asset_definition(AssetDefinition::quantity( "xor2#wonderland".parse().expect("Valid"), )); diff --git a/core/src/tx.rs b/core/src/tx.rs index 790d8942326..01ee688edcf 100644 --- a/core/src/tx.rs +++ b/core/src/tx.rs @@ -36,179 +36,6 @@ pub enum AcceptTransactionFail { UnexpectedGenesisAccountSignature, } -mod len { - use iroha_data_model::{expression::*, query::QueryBox, Value}; - - pub trait ExprLen { - fn len(&self) -> usize; - } - - impl> ExprLen for EvaluatesTo { - fn len(&self) -> usize { - self.expression.len() - } - } - - impl ExprLen for Expression { - fn len(&self) -> usize { - use Expression::*; - - match self { - Add(add) => add.len(), - Subtract(subtract) => subtract.len(), - Greater(greater) => greater.len(), - Less(less) => less.len(), - Equal(equal) => equal.len(), - Not(not) => not.len(), - And(and) => and.len(), - Or(or) => or.len(), - If(if_expression) => if_expression.len(), - Raw(raw) => raw.len(), - Query(query) => query.len(), - Contains(contains) => contains.len(), - ContainsAll(contains_all) => contains_all.len(), - ContainsAny(contains_any) => contains_any.len(), - Where(where_expression) => where_expression.len(), - ContextValue(context_value) => context_value.len(), - Multiply(multiply) => multiply.len(), - Divide(divide) => divide.len(), - Mod(modulus) => modulus.len(), - RaiseTo(raise_to) => raise_to.len(), - } - } - } - impl ExprLen for ContextValue { - fn len(&self) -> usize { - 1 - } - } - impl ExprLen for QueryBox { - fn len(&self) -> usize { - 1 - } - } - - impl ExprLen for Add { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Subtract { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Multiply { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for RaiseTo { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Divide { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Mod { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Greater { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Less { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Equal { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for And { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - impl ExprLen for Or { - fn len(&self) -> usize { - self.left.len() + self.right.len() + 1 - } - } - - impl ExprLen for Not { - fn len(&self) -> usize { - self.expression.len() + 1 - } - } - - impl ExprLen for Contains { - fn len(&self) -> usize { - self.collection.len() + self.element.len() + 1 - } - } - impl ExprLen for ContainsAll { - fn len(&self) -> usize { - self.collection.len() + self.elements.len() + 1 - } - } - impl ExprLen for ContainsAny { - fn len(&self) -> usize { - self.collection.len() + self.elements.len() + 1 - } - } - - impl ExprLen for If { - fn len(&self) -> usize { - // TODO: This is wrong because we don't evaluate both branches - self.condition.len() + self.then.len() + self.otherwise.len() + 1 - } - } - impl ExprLen for Where { - fn len(&self) -> usize { - self.expression.len() + self.values.values().map(EvaluatesTo::len).sum::() + 1 - } - } -} - -fn instruction_size(isi: &InstructionExpr) -> usize { - use len::ExprLen as _; - use InstructionExpr::*; - - match isi { - Register(isi) => isi.object.len() + 1, - Unregister(isi) => isi.object_id.len() + 1, - Mint(isi) => isi.destination_id.len() + isi.object.len() + 1, - Burn(isi) => isi.destination_id.len() + isi.object.len() + 1, - Transfer(isi) => isi.destination_id.len() + isi.object.len() + isi.source_id.len() + 1, - If(isi) => { - let otherwise = isi.otherwise.as_ref().map_or(0, instruction_size); - isi.condition.len() + instruction_size(&isi.then) + otherwise + 1 - } - Pair(isi) => { - instruction_size(&isi.left_instruction) + instruction_size(&isi.right_instruction) + 1 - } - Sequence(isi) => isi.instructions.iter().map(instruction_size).sum::() + 1, - SetKeyValue(isi) => isi.object_id.len() + isi.key.len() + isi.value.len() + 1, - RemoveKeyValue(isi) => isi.object_id.len() + isi.key.len() + 1, - Grant(isi) => isi.object.len() + isi.destination_id.len() + 1, - Revoke(isi) => isi.object.len() + isi.destination_id.len() + 1, - SetParameter(isi) => isi.parameter.len() + 1, - NewParameter(isi) => isi.parameter.len() + 1, - Upgrade(isi) => isi.object.len() + 1, - Log(isi) => isi.msg.len() + isi.msg.len() + 1, - Fail(_) | ExecuteTrigger(_) => 1, - } -} - impl AcceptedTransaction { /// Accept genesis transaction. Transition from [`GenesisTransaction`] to [`AcceptedTransaction`]. pub fn accept_genesis(tx: GenesisTransaction) -> Self { @@ -230,14 +57,10 @@ impl AcceptedTransaction { match &transaction.payload().instructions { Executable::Instructions(instructions) => { - let instruction_count: u64 = instructions - .iter() - .map(instruction_size) - .sum::() - .try_into() - .expect("`usize` should always fit in `u64`"); - - if instruction_count > limits.max_instruction_number { + let instruction_count = instructions.len(); + if u64::try_from(instruction_count).expect("`usize` should always fit into `u64`") + > limits.max_instruction_number + { return Err(AcceptTransactionFail::TransactionLimit( TransactionLimitError { reason: format!( @@ -426,67 +249,3 @@ impl TransactionExecutor { }) } } - -#[cfg(test)] -mod tests { - use core::str::FromStr as _; - - use super::*; - - fn if_instruction( - c: impl Into, - then: InstructionExpr, - otherwise: Option, - ) -> InstructionExpr { - let condition: Expression = c.into(); - let condition = EvaluatesTo::new_unchecked(condition); - ConditionalExpr { - condition, - then, - otherwise, - } - .into() - } - - fn fail() -> InstructionExpr { - Fail { - message: String::default(), - } - .into() - } - - #[test] - fn len_empty_sequence() { - assert_eq!(instruction_size(&SequenceExpr::new(vec![]).into()), 1); - } - - #[test] - fn len_if_one_branch() { - let instructions = vec![if_instruction( - ContextValue { - value_name: Name::from_str("a").expect("Cannot fail."), - }, - fail(), - None, - )]; - - assert_eq!(instruction_size(&SequenceExpr::new(instructions).into()), 4); - } - - #[test] - fn len_sequence_if() { - let instructions = vec![ - fail(), - if_instruction( - ContextValue { - value_name: Name::from_str("b").expect("Cannot fail."), - }, - fail(), - Some(fail()), - ), - fail(), - ]; - - assert_eq!(instruction_size(&SequenceExpr::new(instructions).into()), 7); - } -} diff --git a/core/src/wsv.rs b/core/src/wsv.rs index 29274a3323e..01c4e8e8d30 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -602,7 +602,7 @@ impl WorldStateView { fn process_instructions( &mut self, - instructions: impl IntoIterator, + instructions: impl IntoIterator, authority: &AccountId, ) -> Result<()> { instructions.into_iter().try_for_each(|instruction| { diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 96c4210fd08..b51e1726e47 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -118,7 +118,7 @@ impl TestGenesis for GenesisNetwork { upgrade_executor_permission, ] { first_transaction - .append_instruction(GrantExpr::new(permission, alice_id.clone()).into()); + .append_instruction(Grant::permission_token(permission, alice_id.clone()).into()); } if submit_genesis { @@ -211,7 +211,7 @@ impl Network { time::sleep(Configuration::pipeline_time() + Configuration::block_sync_gossip_time()).await; - let add_peer = RegisterExpr::new(DataModelPeer::new(peer.id.clone())); + let add_peer = Register::peer(DataModelPeer::new(peer.id.clone())); genesis_client .submit(add_peer) .expect("Failed to add new peer."); @@ -704,7 +704,7 @@ pub trait TestClient: Sized { /// If predicate is not satisfied, after maximum retries. fn submit_all_till( &self, - instructions: Vec, + instructions: Vec, request: R, f: impl Fn(::Target) -> bool, ) -> eyre::Result<()> @@ -836,7 +836,7 @@ impl TestClient for Client { fn submit_all_till( &self, - instructions: Vec, + instructions: Vec, request: R, f: impl Fn(::Target) -> bool, ) -> eyre::Result<()> diff --git a/data_model/src/evaluate.rs b/data_model/src/evaluate.rs deleted file mode 100644 index b566bf4e0b9..00000000000 --- a/data_model/src/evaluate.rs +++ /dev/null @@ -1,869 +0,0 @@ -//! Implementations for Expression evaluation for different expressions. - -#[cfg(not(feature = "std"))] -use alloc::{ - boxed::Box, - collections::BTreeMap, - format, - string::{String, ToString}, - vec::Vec, -}; -#[cfg(feature = "std")] -use std::collections::BTreeMap; - -use iroha_data_model_derive::model; -use iroha_macro::FromVariant; -use iroha_schema::IntoSchema; - -pub use self::model::*; -use crate::{ - expression::{prelude::*, Expression}, - isi::error::{BinaryOpIncompatibleNumericValueTypesError, MathError}, - prelude::*, -}; - -/// Expression evaluator -pub trait ExpressionEvaluator { - /// Evaluates expression against current state of the underlying system - /// - /// # Errors - /// - /// - if expression is malformed - fn evaluate(&self, expression: &E) -> Result; -} - -/// Context of expression evaluation, holding (name, value) pairs for resolving identifiers. -/// Context comes into play because of [`Where`] and [`Query`] expressions. -/// -/// # Example -/// -/// Say you have an expression such as: `SELECT name FROM table WHERE name = "alice"`. This -/// compound expression is made up of two basic expressions, namely `SELECT FROM` and `WHERE`. -/// To evaluate any expression you have to substitute concrete values for variable names. -/// In this case, `WHERE` should be evaluated first which would place `name = "alice"` -/// inside the context. This context will then be used to evaluate `SELECT FROM`. -/// Starting expression would then be evaluated to `SELECT "alice" FROM table` -pub trait Context: Clone { - /// Execute query against the current state of `Iroha` - /// - /// # Errors - /// - /// If query execution fails - fn query(&self, query: &QueryBox) -> Result; - - /// Return a reference to the [`Value`] corresponding to the [`Name`]. - fn get(&self, name: &Name) -> Option<&Value>; - - /// Update this context with given values. - fn update(&mut self, other: impl IntoIterator); -} - -/// Calculate the result of the expression without mutating the state. -#[allow(clippy::len_without_is_empty)] // NOTE: Evaluate cannot be empty -pub trait Evaluate { - /// The resulting type of the expression. - type Value; - - /// Calculate result. - /// - /// # Errors - /// Concrete to each implementer. - fn evaluate(&self, context: &C) -> Result; -} - -impl> Evaluate for EvaluatesTo -where - V::Error: ToString, -{ - type Value = V; - - fn evaluate(&self, context: &C) -> Result { - let expr = self.expression.evaluate(context)?; - - V::try_from(expr).map_err(|error| EvaluationError::Conversion(error.to_string())) - } -} - -impl Evaluate for Expression { - type Value = Value; - - fn evaluate(&self, context: &C) -> Result { - macro_rules! match_evals { - ($($non_value: ident),+ $(,)?) => { - match self { $( - $non_value(expr) => expr.evaluate(context).map(Into::into)?, )+ - Raw(value) => value.clone(), - } - }; - } - - use Expression::*; - let result = match_evals!( - // numeric - Add, - Subtract, - Greater, - Less, - Multiply, - Divide, - Mod, - RaiseTo, - // logical - Equal, - Not, - And, - Or, - Contains, - ContainsAll, - ContainsAny, - // value - If, - Where, - Query, - ContextValue, - ); - - Ok(result) - } -} - -impl Evaluate for ContextValue { - type Value = Value; - - fn evaluate(&self, context: &C) -> Result { - context - .get(&self.value_name) - .cloned() - .ok_or_else(|| EvaluationError::Find(self.value_name.to_string())) - } -} - -mod numeric { - use super::*; - - impl Evaluate for Add { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left - .checked_add(right) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - (U128(left), U128(right)) => left - .checked_add(right) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - (Fixed(left), Fixed(right)) => left - .checked_add(right) - .map(NumericValue::from) - .map_err(MathError::from)?, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Subtract { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left - .checked_sub(right) - .ok_or(MathError::NotEnoughQuantity) - .map(NumericValue::from)?, - (U128(left), U128(right)) => left - .checked_sub(right) - .ok_or(MathError::NotEnoughQuantity) - .map(NumericValue::from)?, - (Fixed(left), Fixed(right)) => left - .checked_sub(right) - .map(NumericValue::from) - .map_err(MathError::from)?, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Multiply { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left - .checked_mul(right) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - (U128(left), U128(right)) => left - .checked_mul(right) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - (Fixed(left), Fixed(right)) => left - .checked_mul(right) - .map(NumericValue::from) - .map_err(MathError::from)?, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for RaiseTo { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let value = self.left.evaluate(context)?; - let exp = self.right.evaluate(context)?; - - let result = match (value, exp) { - (U32(value), U32(exp)) => value - .checked_pow(exp) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - (U128(value), U32(exp)) => value - .checked_pow(exp) - .ok_or(MathError::Overflow) - .map(NumericValue::from)?, - // TODO (#2945): Extend `RaiseTo` to support `Fixed` - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Divide { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left - .checked_div(right) - .ok_or(MathError::DivideByZero) - .map(NumericValue::from)?, - (U128(left), U128(right)) => left - .checked_div(right) - .ok_or(MathError::DivideByZero) - .map(NumericValue::from)?, - (Fixed(left), Fixed(right)) => left - .checked_div(right) - .map(NumericValue::from) - .map_err(MathError::from)?, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Mod { - type Value = NumericValue; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left - .checked_rem(right) - .ok_or(MathError::DivideByZero) - .map(NumericValue::from)?, - (U128(left), U128(right)) => left - .checked_rem(right) - .ok_or(MathError::DivideByZero) - .map(NumericValue::from)?, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } -} - -mod logical { - use super::*; - - impl Evaluate for Greater { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left > right, - (U128(left), U128(right)) => left > right, - (Fixed(left), Fixed(right)) => left > right, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Less { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - use NumericValue::*; - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - - let result = match (left, right) { - (U32(left), U32(right)) => left < right, - (U128(left), U128(right)) => left < right, - (Fixed(left), Fixed(right)) => left < right, - (left, right) => Err(MathError::from( - BinaryOpIncompatibleNumericValueTypesError { left, right }, - ))?, - }; - - Ok(result) - } - } - - impl Evaluate for Equal { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - Ok(left == right) - } - } - - impl Evaluate for And { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - Ok(left && right) - } - } - - impl Evaluate for Or { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let left = self.left.evaluate(context)?; - let right = self.right.evaluate(context)?; - Ok(left || right) - } - } - - impl Evaluate for Not { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let expression = self.expression.evaluate(context)?; - Ok(!expression) - } - } - - impl Evaluate for Contains { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let collection = self.collection.evaluate(context)?; - let element = self.element.evaluate(context)?; - Ok(collection.contains(&element)) - } - } - - impl Evaluate for ContainsAll { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let collection = self.collection.evaluate(context)?; - let elements = self.elements.evaluate(context)?; - Ok(elements.iter().all(|element| collection.contains(element))) - } - } - - impl Evaluate for ContainsAny { - type Value = bool; - - fn evaluate(&self, context: &C) -> Result { - let collection = self.collection.evaluate(context)?; - let elements = self.elements.evaluate(context)?; - Ok(elements.iter().any(|element| collection.contains(element))) - } - } -} - -impl Evaluate for If { - type Value = Value; - - fn evaluate(&self, context: &C) -> Result { - let condition = self.condition.evaluate(context)?; - if condition { - self.then.evaluate(context) - } else { - self.otherwise.evaluate(context) - } - } -} - -impl Evaluate for Where { - type Value = Value; - - fn evaluate(&self, context: &C) -> Result { - let additional_context: Result, EvaluationError> = self - .values - .clone() - .into_iter() - .map(|(value_name, expression)| { - expression - .evaluate(context) - .map(|expression_result| (value_name, expression_result)) - }) - .collect(); - - let mut combined_context = context.clone(); - combined_context.update(additional_context?); - self.expression.evaluate(&combined_context) - } -} - -impl Evaluate for QueryBox { - type Value = Value; - - fn evaluate(&self, context: &C) -> Result { - context - .query(self) - .map_err(|err| EvaluationError::Validation(Box::new(err))) - } -} - -#[model] -pub mod model { - #[cfg(not(feature = "std"))] - use alloc::boxed::Box; - - use parity_scale_codec::{Decode, Encode}; - use serde::{Deserialize, Serialize}; - - use super::*; - - /// Expression evaluation error - #[derive( - Debug, - displaydoc::Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - FromVariant, - Deserialize, - Serialize, - Decode, - Encode, - IntoSchema, - )] - #[cfg_attr(feature = "std", derive(thiserror::Error))] - // TODO: Only temporarily opaque because of problems with FFI - #[ffi_type(opaque)] - pub enum EvaluationError { - /// Failed due to math exception - Math(#[cfg_attr(feature = "std", source)] MathError), - /// Validation failed - Validation(#[cfg_attr(feature = "std", source)] Box), - /// `{0}`: Value not found in the context - Find( - #[skip_from] - #[skip_try_from] - String, - ), - /// Conversion evaluation error: {0} - Conversion( - #[skip_from] - #[skip_try_from] - String, - ), - } -} - -pub mod prelude { - //! Prelude: re-export of most commonly used traits, structs and macros in this crate. - - pub use super::Evaluate; -} - -#[cfg(test)] -mod tests { - use core::{fmt::Debug, str::FromStr as _}; - - use iroha_crypto::KeyPair; - use iroha_primitives::fixed::Fixed; - use parity_scale_codec::{DecodeAll, Encode}; - - use super::*; - use crate::val_vec; - - /// Context of expression evaluation - #[derive(Clone)] - #[repr(transparent)] - struct TestContext { - values: BTreeMap, - } - - impl TestContext { - fn new() -> Self { - Self { - values: BTreeMap::new(), - } - } - } - - impl super::Context for TestContext { - fn query(&self, _: &QueryBox) -> Result { - unimplemented!("This has to be tested on iroha_core") - } - - fn get(&self, name: &Name) -> Option<&Value> { - self.values.get(name) - } - - fn update(&mut self, other: impl IntoIterator) { - self.values.extend(other) - } - } - - /// Example taken from [whitepaper](https://github.com/hyperledger/iroha/blob/iroha2-dev/docs/source/iroha_2_whitepaper.md#261-multisignature-transactions) - #[test] - #[allow(clippy::too_many_lines)] - fn conditional_multisignature_quorum() -> Result<(), EvaluationError> { - let asset_quantity_high = 750_u32.to_value(); - let asset_quantity_low = 300_u32.to_value(); - let (public_key_teller_1, _) = KeyPair::generate().expect("Valid").into(); - let (public_key_teller_2, _) = KeyPair::generate().expect("Valid").into(); - let (manager_public_key, _) = KeyPair::generate().expect("Valid").into(); - let teller_signatory_set = vec![ - Value::PublicKey(public_key_teller_1.clone()), - Value::PublicKey(public_key_teller_2), - ]; - let one_teller_set = Value::Vec(vec![Value::PublicKey(public_key_teller_1)]); - let manager_signatory = Value::PublicKey(manager_public_key); - let manager_signatory_set = Value::Vec(vec![manager_signatory.clone()]); - let condition = If::new( - And::new( - Greater::new( - EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("usd_quantity").expect("Can't fail."), - )), - 500_u32, - ), - Less::new( - EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("usd_quantity").expect("Can't fail."), - )), - 1000_u32, - ), - ), - EvaluatesTo::new_evaluates_to_value(Or::new( - ContainsAll::new( - EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("signatories").expect("Can't fail."), - )), - teller_signatory_set.clone(), - ), - Contains::new( - EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("signatories").expect("Can't fail."), - )), - manager_signatory, - ), - )), - true, - ); - // Signed by all tellers - let expression = Where::new(EvaluatesTo::new_evaluates_to_value(condition.clone())) - .with_value( - //TODO: use query to get the actual quantity of an asset from WSV - // in that case this test should be moved to iroha_core - Name::from_str("usd_quantity").expect("Can't fail."), - asset_quantity_high.clone(), - ) - .with_value( - Name::from_str("signatories").expect("Can't fail."), - teller_signatory_set, - ); - assert_eq!(expression.evaluate(&TestContext::new())?, Value::Bool(true)); - // Signed by manager - let expression = Where::new(EvaluatesTo::new_evaluates_to_value(condition.clone())) - .with_value( - Name::from_str("usd_quantity").expect("Can't fail."), - asset_quantity_high.clone(), - ) - .with_value( - Name::from_str("signatories").expect("Can't fail."), - manager_signatory_set, - ); - assert_eq!(expression.evaluate(&TestContext::new())?, Value::Bool(true)); - // Signed by one teller - let expression = Where::new(EvaluatesTo::new_evaluates_to_value(condition.clone())) - .with_value( - Name::from_str("usd_quantity").expect("Can't fail."), - asset_quantity_high, - ) - .with_value( - Name::from_str("signatories").expect("Can't fail."), - one_teller_set.clone(), - ); - assert_eq!( - expression.evaluate(&TestContext::new())?, - Value::Bool(false) - ); - // Signed by one teller with less value - let expression = Where::new(EvaluatesTo::new_evaluates_to_value(condition)) - .with_value( - Name::from_str("usd_quantity").expect("Can't fail."), - asset_quantity_low, - ) - .with_value( - Name::from_str("signatories").expect("Can't fail."), - one_teller_set, - ); - assert_eq!(expression.evaluate(&TestContext::new())?, Value::Bool(true)); - Ok(()) - } - - #[test] - fn where_expression() -> Result<(), EvaluationError> { - assert_eq!( - Where::new(EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("test_value").expect("Can't fail.") - ))) - .with_value( - Name::from_str("test_value").expect("Can't fail."), - EvaluatesTo::new_evaluates_to_value(Add::new(2_u32, 3_u32)) - ) - .evaluate(&TestContext::new())?, - 5_u32.to_value() - ); - Ok(()) - } - - #[test] - fn nested_where_expression() -> Result<(), EvaluationError> { - let expression = Where::new(EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("a").expect("Can't fail."), - ))) - .with_value(Name::from_str("a").expect("Can't fail."), 2_u32); - let outer_expression = Where::new(EvaluatesTo::new_evaluates_to_value(Add::new( - EvaluatesTo::new_unchecked(expression), - EvaluatesTo::new_unchecked(ContextValue::new( - Name::from_str("b").expect("Can't fail."), - )), - ))) - .with_value(Name::from_str("b").expect("Can't fail."), 4_u32); - assert_eq!( - outer_expression.evaluate(&TestContext::new())?, - 6_u32.to_value() - ); - Ok(()) - } - - #[test] - fn if_condition_branches_correctly() -> Result<(), EvaluationError> { - assert_eq!( - If::new(true, 1_u32, 2_u32).evaluate(&TestContext::new())?, - 1_u32.to_value() - ); - assert_eq!( - If::new(false, 1_u32, 2_u32).evaluate(&TestContext::new())?, - 2_u32.to_value() - ); - Ok(()) - } - - #[test] - #[allow(clippy::unnecessary_wraps)] - fn incorrect_types_are_caught() -> Result<(), EvaluationError> { - fn assert_eval(inst: &I, err_msg: &str) - where - I: Evaluate + Debug, - I::Value: Debug, - { - let result: Result<_, _> = inst.evaluate(&TestContext::new()); - let _err = result.expect_err(err_msg); - } - - assert_eval( - &And::new( - EvaluatesTo::new_unchecked(1_u32), - EvaluatesTo::new_unchecked(Vec::::new()), - ), - "Should not be possible to apply logical and to int and vec.", - ); - assert_eval( - &Or::new( - EvaluatesTo::new_unchecked(1_u32), - EvaluatesTo::new_unchecked(Vec::::new()), - ), - "Should not be possible to apply logical or to int and vec.", - ); - assert_eval( - &Greater::new( - EvaluatesTo::new_unchecked(1_u32), - EvaluatesTo::new_unchecked(Vec::::new()), - ), - "Should not be possible to apply greater sign to int and vec.", - ); - assert_eval( - &Less::new( - EvaluatesTo::new_unchecked(1_u32), - EvaluatesTo::new_unchecked(Vec::::new()), - ), - "Should not be possible to apply greater sign to int and vec.", - ); - assert_eval( - &If::new(EvaluatesTo::new_unchecked(1_u32), 2_u32, 3_u32), - "If condition should be bool", - ); - assert_eval( - &Add::new(10_u32, 1_u128), - "Should not be possible to add `u32` and `u128`", - ); - assert_eval( - &Subtract::new(Fixed::try_from(10.2_f64).map_err(MathError::from)?, 1_u128), - "Should not be possible to subtract `Fixed` and `u128`", - ); - assert_eval( - &Multiply::new(0_u32, Fixed::try_from(1.0_f64).map_err(MathError::from)?), - "Should not be possible to multiply `u32` and `Fixed`", - ); - Ok(()) - } - - #[test] - fn operations_are_correctly_calculated() -> Result<(), EvaluationError> { - assert_eq!( - Add::new(1_u32, 2_u32).evaluate(&TestContext::new())?, - 3_u32.into() - ); - assert_eq!( - Add::new(1_u128, 2_u128).evaluate(&TestContext::new())?, - 3_u128.into(), - ); - assert_eq!( - Add::new( - Fixed::try_from(1.17_f64).map_err(MathError::from)?, - Fixed::try_from(2.13_f64).map_err(MathError::from)? - ) - .evaluate(&TestContext::new())?, - 3.30_f64.try_into().map_err(MathError::from)? - ); - assert_eq!( - Subtract::new(7_u32, 2_u32).evaluate(&TestContext::new())?, - 5_u32.into() - ); - assert_eq!( - Subtract::new(7_u128, 2_u128).evaluate(&TestContext::new())?, - 5_u128.into() - ); - assert_eq!( - Subtract::new( - Fixed::try_from(7.250_f64).map_err(MathError::from)?, - Fixed::try_from(2.125_f64).map_err(MathError::from)? - ) - .evaluate(&TestContext::new())?, - 5.125_f64.try_into().map_err(MathError::from)? - ); - assert!(!Greater::new(1_u32, 2_u32).evaluate(&TestContext::new())?); - assert!(Greater::new(2_u32, 1_u32).evaluate(&TestContext::new())?); - assert!(Less::new(1_u32, 2_u32).evaluate(&TestContext::new())?); - assert!(!Less::new(2_u32, 1_u32).evaluate(&TestContext::new())?); - assert!(!Equal::new(1_u32, 2_u32).evaluate(&TestContext::new())?); - assert!( - Equal::new(vec![1_u32, 3_u32, 5_u32], vec![1_u32, 3_u32, 5_u32]) - .evaluate(&TestContext::new())? - ); - assert!(Contains::new(val_vec![1_u32, 3_u32, 5_u32], 3_u32).evaluate(&TestContext::new())?); - assert!(!Contains::new(val_vec![1_u32, 3_u32, 5_u32], 7_u32).evaluate(&TestContext::new())?); - assert!( - ContainsAll::new(val_vec![1_u32, 3_u32, 5_u32], val_vec![1_u32, 5_u32]) - .evaluate(&TestContext::new())? - ); - assert!( - !ContainsAll::new(val_vec![1_u32, 3_u32, 5_u32], val_vec![1_u32, 5_u32, 7_u32]) - .evaluate(&TestContext::new())? - ); - Ok(()) - } - - #[test] - #[ignore = "Stack overflow"] - fn serde_serialization_works() { - let expression: Expression = Add::new(1_u32, Subtract::new(7_u32, 4_u32)).into(); - let serialized_expression = - serde_json::to_string(&expression).expect("Failed to serialize."); - let deserialized_expression: Expression = - serde_json::from_str(&serialized_expression).expect("Failed to de-serialize."); - assert_eq!( - expression - .evaluate(&TestContext::new()) - .expect("Failed to calculate."), - deserialized_expression - .evaluate(&TestContext::new()) - .expect("Failed to calculate.") - ) - } - - #[test] - fn scale_codec_serialization_works() { - let expression: Expression = Add::new(1_u32, Subtract::new(7_u32, 4_u32)).into(); - let serialized_expression: Vec = expression.encode(); - let deserialized_expression: Expression = - DecodeAll::decode_all(&mut serialized_expression.as_slice()) - .expect("Failed to decode."); - assert_eq!( - expression - .evaluate(&TestContext::new()) - .expect("Failed to calculate."), - deserialized_expression - .evaluate(&TestContext::new()) - .expect("Failed to calculate.") - ) - } -} diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index 8bf07a8e49b..90f1caf2cf8 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -566,6 +566,7 @@ mod executor { #[derive( Debug, + Copy, Clone, PartialEq, Eq, @@ -586,6 +587,23 @@ mod executor { } /// Filter for [`ExecutorEvent`]. + #[derive( + Debug, + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + parity_scale_codec::Decode, + parity_scale_codec::Encode, + serde::Deserialize, + serde::Serialize, + iroha_schema::IntoSchema, + )] + #[non_exhaustive] + #[serde(untagged)] // Unaffected by #3330, as single unit variant + #[repr(transparent)] pub enum ExecutorFilter { Upgraded, } diff --git a/data_model/src/expression.rs b/data_model/src/expression.rs deleted file mode 100644 index b5e75ce26bc..00000000000 --- a/data_model/src/expression.rs +++ /dev/null @@ -1,703 +0,0 @@ -//! Expressions to use inside of ISIs. - -#![allow( - // Because of `codec(skip)` - clippy::default_trait_access, - // Because of length on instructions and expressions (can't be 0) - clippy::len_without_is_empty, - // Because of length on instructions and expressions (XXX: Should it be trait?) - clippy::unused_self -)] - -#[cfg(not(feature = "std"))] -use alloc::{boxed::Box, collections::btree_map, format, string::String, vec, vec::Vec}; -use core::marker::PhantomData; -#[cfg(feature = "std")] -use std::collections::btree_map; - -use derive_more::{Constructor, DebugCustom, Display}; -use getset::Getters; -use iroha_data_model_derive::{model, PartiallyTaggedDeserialize, PartiallyTaggedSerialize}; -use iroha_macro::FromVariant; -use iroha_schema::{IntoSchema, TypeId}; -use operation::*; -use parity_scale_codec::{Decode, Encode}; -use serde::{Deserialize, Serialize}; - -pub use self::model::*; -use super::{query::QueryBox, Name, Value}; -use crate::NumericValue; - -/// Generate expression structure and basic impls for it. -/// -/// # Syntax -/// -/// Basic syntax: -/// -/// ```ignore -/// gen_expr_and_impls! { -/// /// Comment -/// #[derive(Derives)] -/// pub Expr(param1: Type1, param2: Type2, param3: Type3, ...) -> OutputType -/// } -/// ``` -/// -/// The macro has three syntax forms to specify parameters: -/// - One unnamed parameter. In that case, the parameter name will be `expression`. -/// - Two unnamed parameters. -/// In that case, the parameter names will be `left` and `right` respectively. -/// - Any number of named parameters. -/// -/// The macro has two syntax forms to specify result: -/// - With the actual result type after the arrow (`->`). -/// In that case, `impl From<$i> for EvaluatesTo<$result_type>` will be generated. -/// - With `?` sign as a result type. -/// In that case `impl From<$i> for EvaluatesTo<$result_type>` **won't** be generated. -/// -/// See the example and further usage for more details. -/// -/// # Example -/// -/// ```ignore -/// gen_expr_and_impls! { -/// /// Evaluates to the sum of left and right expressions. -/// #[derive(Debug)] -/// pub Add(u32, u32) -> u32 -/// } -/// -/// // Will generate the following code: -/// -/// /// Evaluates to the sum of left and right expressions. -/// iroha_data_model_derive::model_single! { -/// #[derive(Debug)] -/// pub struct Add { -/// #[allow(missing_docs)] -/// pub left: EvaluatesTo, -/// #[allow(missing_docs)] -/// pub right: EvaluatesTo, -/// } -/// } -/// -/// impl Add { -/// /// Construct new [`Add`] expression -/// pub fn new(left: impl Into>, right: impl Into>) -> Self { -/// Self { -/// left: left.into(), -/// right: right.into(), -/// } -/// } -/// } -/// -/// impl From for EvaluatesTo { -/// fn from(expression: Add) -> Self { -/// EvaluatesTo::new_unchecked(expression) -/// } -/// } -/// ``` -macro_rules! gen_expr_and_impls { - // Case: one unnamed parameter - ($(#[$me:meta])* $v:vis $i:ident($first_type:ty $(,)?) -> $($result:tt)*) => { - gen_expr_and_impls!($(#[$me])* $v $i(expression: $first_type) -> $($result)*); - }; - // Case: two unnamed parameters - ($(#[$me:meta])* $v:vis $i:ident($first_type:ty, $second_type:ty $(,)?) -> $($result:tt)*) => { - gen_expr_and_impls!($(#[$me])* $v $i(left: $first_type, right: $second_type) -> $($result)*); - }; - // Case: any number of named parameters - ($(#[$me:meta])* $v:vis $i:ident($($param_name:ident: $param_type:ty),* $(,)?) -> $($result:tt)*) => { - gen_expr_and_impls!(impl_basic $(#[$me])* $v $i($($param_name: $param_type),*)); - gen_expr_and_impls!(impl_extra_convert $i $($result)*); - }; - // Internal usage: generate basic code for the expression - (impl_basic $(#[$me:meta])* $v:vis $i:ident($($param_name:ident: $param_type:ty),* $(,)?)) => { - iroha_data_model_derive::model_single! { - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Getters, Decode, Encode, Deserialize, Serialize, IntoSchema)] - #[getset(get = "pub")] - $(#[$me])* - $v struct $i { $( - /// - #[allow(missing_docs)] - pub $param_name: EvaluatesTo<$param_type>, )* - } - } - - impl $i { - #[doc = concat!(" Construct new [`", stringify!($i), "`] expression")] - pub fn new( - $($param_name: impl Into>),* - ) -> Self { - Self { - $($param_name: $param_name.into()),* - } - } - } - }; - // Internal usage: do nothing for expressions with unknown result type - (impl_extra_convert $i:ident ?) => { - }; - // Internal usage: generate extra `From` impl for expressions with known result type - (impl_extra_convert $i:ident $result_type:ty) => { - impl From<$i> for EvaluatesTo<$result_type> { - fn from(expression: $i) -> Self { - EvaluatesTo::new_unchecked(expression) - } - } - }; -} - -#[model] -pub mod model { - use super::*; - - /// Struct for type checking and converting expression results. - #[derive( - DebugCustom, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Decode, - Encode, - Deserialize, - Serialize, - TypeId, - )] - // As this structure exists only for type checking - // it makes sense to display `expression` directly - #[display(fmt = "{expression}")] - #[debug(fmt = "{expression:?}")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `EvaluatesTo` has no trap representation in `Box` - #[ffi_type(unsafe {robust})] - pub struct EvaluatesTo { - /// Expression. - #[serde(flatten)] - pub expression: Box, - #[codec(skip)] - pub(super) _value_type: PhantomData, - } - - /// Represents all possible expressions. - #[derive( - DebugCustom, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - FromVariant, - Decode, - Encode, - PartiallyTaggedDeserialize, - PartiallyTaggedSerialize, - IntoSchema, - )] - #[ffi_type(opaque)] - pub enum Expression { - /// Add expression. - Add(Add), - /// Subtract expression. - Subtract(Subtract), - /// Multiply expression. - Multiply(Multiply), - /// Divide expression. - Divide(Divide), - /// Module expression. - Mod(Mod), - /// Raise to power expression. - RaiseTo(RaiseTo), - /// Greater expression. - Greater(Greater), - /// Less expression. - Less(Less), - /// Equal expression. - Equal(Equal), - /// Not expression. - Not(Not), - /// And expression. - And(And), - /// Or expression. - Or(Or), - /// If expression. - If(If), - /// Raw value. - #[serde_partially_tagged(untagged)] - #[debug(fmt = "{_0:?}")] - Raw(#[skip_from] Value), - /// Query to Iroha state. - Query(QueryBox), - /// Contains expression for vectors. - Contains(Contains), - /// Contains all expression for vectors. - ContainsAll(ContainsAll), - /// Contains any expression for vectors. - ContainsAny(ContainsAny), - /// Where expression to supply temporary values to local context. - Where(Where), - /// Get a temporary value by name - ContextValue(ContextValue), - } - - /// Get a temporary value by name. The values are brought into [`Context`] by [`Where`] expression. - // NOTE: Can't use `gen_expr_and_impls!` here because we need special type for `value_name` - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Getters, - Constructor, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[display(fmt = "CONTEXT `{value_name}`")] - #[getset(get = "pub")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `ContextValue` has no trap representation in `Name` - #[ffi_type(unsafe {robust})] - pub struct ContextValue { - /// Name bound to the value. - pub value_name: Name, - } - - gen_expr_and_impls! { - /// Evaluates to the multiplication of left and right expressions. - #[derive(Display)] - #[display(fmt = "{}*{}", // Keep without spaces - "self.left.parenthesise(Operation::Multiply)", - "self.right.parenthesise(Operation::Multiply)" - )] - #[ffi_type] - pub Multiply(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Evaluates to the left expression divided by the right expression. - #[derive(Display)] - #[display(fmt = "{}/{}", // Keep without spaces - "self.left.parenthesise(Operation::Divide)", - "self.right.parenthesise(Operation::Divide)" - )] - #[ffi_type] - pub Divide(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Evaluates to the left expression modulo the right expression. - #[derive(Display)] - #[display(fmt = "{} % {}", - "self.left.parenthesise(Operation::Mod)", - "self.right.parenthesise(Operation::Mod)" - )] - #[ffi_type] - pub Mod(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Evaluates to the left expression in the power of right expression. - /// Currently does not support [`NumericValue::Fixed`]. - #[derive(Display)] - #[display(fmt = "{}**{}", - "self.left.parenthesise(Operation::RaiseTo)", - "self.right.parenthesise(Operation::RaiseTo)" - )] - #[ffi_type] - pub RaiseTo(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Evaluates to the sum of left and right expressions. - #[derive(Display)] - #[display(fmt = "{}+{}", - "self.left.parenthesise(Operation::Add)", - "self.right.parenthesise(Operation::Add)" - )] - #[ffi_type] - pub Add(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Evaluates to the left expression minus the right expression. - #[derive(Display)] - #[display(fmt = "{}-{}", - "self.left.parenthesise(Operation::Subtract)", - "self.right.parenthesise(Operation::Subtract)" - )] - #[ffi_type] - pub Subtract(NumericValue, NumericValue) -> NumericValue - } - - gen_expr_and_impls! { - /// Returns whether the `left` expression is greater than the `right`. - #[derive(Display)] - #[display(fmt = "{} > {}", - "self.left.parenthesise(Operation::Greater)", - "self.right.parenthesise(Operation::Greater)" - )] - #[ffi_type] - pub Greater(NumericValue, NumericValue) -> bool - } - - gen_expr_and_impls! { - /// Returns whether the `left` expression is less than the `right`. - #[derive(Display)] - #[display(fmt = "{} < {}", - "self.left.parenthesise(Operation::Less)", - "self.right.parenthesise(Operation::Less)" - )] - #[ffi_type] - pub Less(NumericValue, NumericValue) -> bool - } - - gen_expr_and_impls! { - /// Negates the result of the `expression`. - /// Works only for `Value::Bool`. - #[derive(Display)] - #[display(fmt = "!{}", "self.expression.parenthesise(Operation::Not)")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `Not` has no trap representation in `bool` - #[ffi_type(unsafe {robust})] - pub Not(bool) -> bool - } - - gen_expr_and_impls! { - /// Applies the logical `and` to two `Value::Bool` operands. - #[derive(Display)] - #[display(fmt = "{} && {}", - "self.left.parenthesise(Operation::And)", - "self.right.parenthesise(Operation::And)" - )] - #[ffi_type] - pub And(bool, bool) -> bool - } - - gen_expr_and_impls! { - /// Applies the logical `or` to two `Value::Bool` operands. - #[derive(Display)] - #[display(fmt = "{} || {}", - "self.left.parenthesise(Operation::Or)", - "self.right.parenthesise(Operation::Or)" - )] - #[ffi_type] - pub Or(bool, bool) -> bool - } - - gen_expr_and_impls! { - /// If expression. Based on the `condition`, returns the result of either `then` or `otherwise`. - #[derive(Display)] - #[display(fmt = "if {condition} {{ {then} }} else {{ {otherwise} }}")] - #[ffi_type] - pub If(condition: bool, then: Value, otherwise: Value) -> ? - } - - gen_expr_and_impls! { - /// `Contains` expression. - /// Returns `true` if `collection` contains an `element`, `false` otherwise. - #[derive(Display)] - #[display(fmt = "{}.contains({})", "collection.parenthesise(Operation::MethodCall)", "element")] - #[ffi_type] - pub Contains(collection: Vec, element: Value) -> bool - } - - gen_expr_and_impls! { - /// `ContainsAll` expression. - /// Returns `true` if `collection` contains all `elements`, `false` otherwise. - #[derive(Display)] - #[display(fmt = "{}.contains_all({})", "collection.parenthesise(Operation::MethodCall)", "elements")] - #[ffi_type] - pub ContainsAll(collection: Vec, elements: Vec) -> bool - } - - gen_expr_and_impls! { - /// `ContainsAny` expression. - /// Returns `true` if `collection` contains any element out of the `elements`, `false` otherwise. - #[derive(Display)] - #[display(fmt = "{}.contains_any({})", "collection.parenthesise(Operation::MethodCall)", "elements")] - #[ffi_type] - pub ContainsAny(collection: Vec, elements: Vec) -> bool - } - - gen_expr_and_impls! { - /// Returns `true` if `left` operand is equal to the `right` operand. - #[derive(Display)] - #[display(fmt = "{} == {}", - "self.left.parenthesise(Operation::Equal)", - "self.right.parenthesise(Operation::Equal)" - )] - #[ffi_type] - pub Equal(Value, Value) -> bool - } - - /// Adds a local context of `values` for the `expression`. - /// It is similar to **where** syntax in *Haskell* although evaluated eagerly. - // NOTE: Can't use `gen_expr_and_impls!` here because we need special type for `values` - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[ffi_type] - pub struct Where { - /// Expression to be evaluated. - #[getset(get = "pub")] - pub expression: EvaluatesTo, - /// Context values for the context bonded to their `String` names. - pub values: btree_map::BTreeMap>, - } -} - -impl> From for Expression { - fn from(value: V) -> Self { - Self::Raw(value.into()) - } -} - -impl, E: Into + Into> From for EvaluatesTo { - fn from(expression: E) -> Self { - Self::new_unchecked(expression) - } -} - -impl EvaluatesTo { - /// Expression - #[inline] - // NOTE: getset would return &Box - pub fn expression(&self) -> &Expression { - &self.expression - } - - /// Construct new [`EvaluatesTo`] from [`Expression`] without type checking. - /// - /// # Warning - /// Prefer using [`Into`] conversions rather than this method, - /// because it does not check the value type at compile-time. - #[inline] - pub fn new_unchecked(expression: impl Into) -> Self { - Self { - expression: Box::new(expression.into()), - _value_type: PhantomData, - } - } - - fn operation(&self) -> Operation { - use Expression::*; - - match self.expression.as_ref() { - Add(_) => Operation::Add, - Subtract(_) => Operation::Subtract, - Multiply(_) => Operation::Multiply, - Divide(_) => Operation::Divide, - Mod(_) => Operation::Mod, - RaiseTo(_) => Operation::RaiseTo, - Greater(_) => Operation::Greater, - Less(_) => Operation::Less, - Equal(_) => Operation::Equal, - Not(_) => Operation::Not, - And(_) => Operation::And, - Or(_) => Operation::Or, - Contains(_) | ContainsAll(_) | ContainsAny(_) => Operation::MethodCall, - If(_) | Raw(_) | Query(_) | Where(_) | ContextValue(_) => Operation::Other, - } - } - - /// Wrap expression into parentheses depending on `operation` and get the resulting string. - fn parenthesise(&self, operation: Operation) -> String { - if self.operation().priority() < operation.priority() - && !matches!(self.expression.as_ref(), Expression::Raw(_)) - { - format!("({})", self.expression) - } else { - format!("{}", self.expression) - } - } -} - -impl EvaluatesTo { - /// Construct `EvaluatesTo` from any `expression` - /// because all of them evaluate to [`Value`]. - #[inline] - pub fn new_evaluates_to_value(expression: impl Into) -> Self { - Self::new_unchecked(expression) - } -} - -impl + IntoSchema> IntoSchema for EvaluatesTo { - fn type_name() -> String { - format!("EvaluatesTo<{}>", V::type_name()) - } - fn update_schema_map(map: &mut iroha_schema::MetaMap) { - const EXPRESSION: &str = "expression"; - - if !map.contains_key::() { - map.insert::(iroha_schema::Metadata::Struct( - iroha_schema::NamedFieldsMeta { - declarations: vec![iroha_schema::Declaration { - name: String::from(EXPRESSION), - ty: core::any::TypeId::of::(), - }], - }, - )); - - Expression::update_schema_map(map); - } - } -} - -impl core::fmt::Display for Where { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "`{} where: [", self.expression)?; - - let mut first = true; - for (key, value) in &self.values { - if !first { - write!(f, ", ")?; - } - first = false; - write!(f, "`{key}` : `{value}`")?; - } - - write!(f, "]") - } -} - -impl Where { - /// Construct [`Where`] expression - #[must_use] - pub fn new(expression: impl Into>) -> Self { - Self { - expression: expression.into(), - values: Default::default(), - } - } - - /// Get an iterator over the values of [`Where`] clause - #[inline] - pub fn values(&self) -> impl ExactSizeIterator)> { - self.values.iter() - } - - /// Binds `expression` result to a `value_name`, by which it will be reachable from the main expression. - #[must_use] - pub fn with_value>>( - mut self, - value_name: Name, - expression: E, - ) -> Self { - self.values.insert(value_name, expression.into()); - self - } -} - -mod operation { - //! Module containing operations and their priorities. - - /// Type of expression operation. - #[derive(Clone, Copy, PartialEq, Eq)] - pub enum Operation { - MethodCall, - RaiseTo, - Multiply, - Divide, - Mod, - Add, - Subtract, - Greater, - Less, - Equal, - Not, - And, - Or, - Other, - } - - /// Priority of operation. - /// - /// [`First`](Operation::First) is the highest priority - /// and [`Ninth`](Operation::Ninth) is the lowest. - #[derive(Debug, Clone, Copy, PartialEq, Eq)] - pub enum Priority { - First = 1, - Second = 2, - Third = 3, - Fourth = 4, - Fifth = 5, - Sixth = 6, - Seventh = 7, - Eighth = 8, - Ninth = 9, - } - - impl Operation { - /// Get the priority of the operation. - /// - /// Ordering is the same as in Python code. - /// See [`here`](https://docs.python.org/3/reference/expressions.html#operator-precedence) - /// for more details. - pub fn priority(self) -> Priority { - use Operation::*; - - match self { - MethodCall => Priority::First, - RaiseTo => Priority::Second, - Multiply | Divide | Mod => Priority::Third, - Add | Subtract => Priority::Fourth, - Greater | Less | Equal => Priority::Fifth, - Not => Priority::Sixth, - And => Priority::Seventh, - Or => Priority::Eighth, - Other => Priority::Ninth, - } - } - } - - impl PartialOrd for Priority { - #[inline] - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - impl Ord for Priority { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - use core::cmp::Ordering::*; - - let lhs = *self as u8; - let rhs = *other as u8; - - match lhs.cmp(&rhs) { - Less => Greater, - Equal => Equal, - Greater => Less, - } - } - } -} - -/// The prelude re-exports most commonly used traits, structs and macros from this crate. -pub mod prelude { - pub use super::{ - Add, And, Contains, ContainsAll, ContainsAny, ContextValue, Divide, Equal, EvaluatesTo, - Expression, Greater, If, Less, Mod, Multiply, Not, Or, RaiseTo, Subtract, Where, - }; -} diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index c0b2fd41da1..0dd3b178d9e 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -4,36 +4,25 @@ #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, string::String, vec::Vec}; -use core::fmt::Debug; +use core::fmt::{Debug, Display}; -use derive_more::{DebugCustom, Display}; +use derive_more::{Constructor, DebugCustom, Display}; use iroha_data_model_derive::model; -use iroha_macro::FromVariant; use iroha_schema::IntoSchema; use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; use strum::EnumDiscriminants; -pub use self::model::*; -use super::{expression::EvaluatesTo, prelude::*, IdBox, RegistrableBox, Value}; +pub use self::{model::*, transparent::*}; +use super::{prelude::*, Value}; use crate::{seal, Level, Registered}; -/// Marker trait designating instruction -pub trait Instruction: Into + seal::Sealed {} - -macro_rules! isi { - ($($meta:meta)* $item:item) => { - iroha_data_model_derive::model_single! { - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, getset::Getters)] - #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode)] - #[derive(serde::Deserialize, serde::Serialize)] - #[derive(iroha_schema::IntoSchema)] - #[getset(get = "pub")] - $($meta)* - $item - } - }; -} +/// Marker trait designating instruction. +/// +/// Instructions allows to change the state of `Iroha`. +/// All possible instructions are implementors of this trait, excluding +/// [`InstructionBox`] which is just a wrapper. +pub trait Instruction: Into + seal::Sealed {} #[model] pub mod model { @@ -42,6 +31,10 @@ pub mod model { use super::*; /// Sized structure for all possible Instructions. + /// + /// Note that [`InstructionBox`] is not a self-sufficient instruction, + /// but just a wrapper to pass instructions back and forth. + /// If you are a client SDK user then you likely don't need to use this type directly. #[derive( DebugCustom, Display, @@ -50,7 +43,6 @@ pub mod model { Eq, PartialOrd, Ord, - FromVariant, EnumDiscriminants, Decode, Encode, @@ -79,790 +71,1151 @@ pub mod model { )] #[ffi_type(opaque)] #[allow(missing_docs)] - pub enum InstructionExpr { - #[debug(fmt = "{_0:?}")] - Register(RegisterExpr), + pub enum InstructionBox { #[debug(fmt = "{_0:?}")] - Unregister(UnregisterExpr), + Register(RegisterBox), #[debug(fmt = "{_0:?}")] - Mint(MintExpr), + Unregister(UnregisterBox), #[debug(fmt = "{_0:?}")] - Burn(BurnExpr), + Mint(MintBox), #[debug(fmt = "{_0:?}")] - Transfer(TransferExpr), + Burn(BurnBox), #[debug(fmt = "{_0:?}")] - If(Box), + Transfer(TransferBox), #[debug(fmt = "{_0:?}")] - Pair(Box), + SetKeyValue(SetKeyValueBox), #[debug(fmt = "{_0:?}")] - Sequence(SequenceExpr), + RemoveKeyValue(RemoveKeyValueBox), #[debug(fmt = "{_0:?}")] - SetKeyValue(SetKeyValueExpr), + Grant(GrantBox), #[debug(fmt = "{_0:?}")] - RemoveKeyValue(RemoveKeyValueExpr), + Revoke(RevokeBox), #[debug(fmt = "{_0:?}")] - Grant(GrantExpr), + ExecuteTrigger(ExecuteTrigger), #[debug(fmt = "{_0:?}")] - Revoke(RevokeExpr), + SetParameter(SetParameter), #[debug(fmt = "{_0:?}")] - ExecuteTrigger(ExecuteTriggerExpr), + NewParameter(NewParameter), #[debug(fmt = "{_0:?}")] - SetParameter(SetParameterExpr), + Upgrade(Upgrade), #[debug(fmt = "{_0:?}")] - NewParameter(NewParameterExpr), - Upgrade(UpgradeExpr), - /// `Log` variant. - Log(LogExpr), + Log(Log), #[debug(fmt = "{_0:?}")] Fail(Fail), } - impl Instruction for InstructionExpr {} - - impl Instruction for SetKeyValueExpr {} - impl Instruction for RemoveKeyValueExpr {} - impl Instruction for RegisterExpr {} - impl Instruction for UnregisterExpr {} - impl Instruction for MintExpr {} - impl Instruction for BurnExpr {} - impl Instruction for TransferExpr {} - impl Instruction for GrantExpr {} - impl Instruction for RevokeExpr {} - impl Instruction for SetParameterExpr {} - impl Instruction for NewParameterExpr {} - impl Instruction for UpgradeExpr {} - impl Instruction for ExecuteTriggerExpr {} - impl Instruction for LogExpr {} + impl Instruction for InstructionBox {} + + impl Instruction for SetKeyValue {} + impl Instruction for SetKeyValue {} + impl Instruction for SetKeyValue {} + impl Instruction for SetKeyValue {} + + impl Instruction for RemoveKeyValue {} + impl Instruction for RemoveKeyValue {} + impl Instruction for RemoveKeyValue {} + impl Instruction for RemoveKeyValue {} + + impl Instruction for Register {} + impl Instruction for Register {} + impl Instruction for Register {} + impl Instruction for Register {} + impl Instruction for Register {} + impl Instruction for Register {} + impl Instruction for Register> {} + + impl Instruction for Unregister {} + impl Instruction for Unregister {} + impl Instruction for Unregister {} + impl Instruction for Unregister {} + impl Instruction for Unregister {} + impl Instruction for Unregister {} + impl Instruction for Unregister> {} + + impl Instruction for Mint {} + impl Instruction for Mint {} + impl Instruction for Mint {} + impl Instruction for Mint {} + impl Instruction for Mint {} + impl Instruction for Mint> {} + + impl Instruction for Burn {} + impl Instruction for Burn {} + impl Instruction for Burn {} + impl Instruction for Burn {} + impl Instruction for Burn> {} + + impl Instruction for Transfer {} + impl Instruction for Transfer {} + impl Instruction for Transfer {} + impl Instruction for Transfer {} + impl Instruction for Transfer {} + + impl Instruction for Grant {} + impl Instruction for Grant {} + + impl Instruction for Revoke {} + impl Instruction for Revoke {} + + impl Instruction for SetParameter {} + impl Instruction for NewParameter {} + impl Instruction for Upgrade {} + impl Instruction for ExecuteTrigger {} + impl Instruction for Log {} impl Instruction for Fail {} - - // Composite instructions - impl Instruction for ConditionalExpr {} - impl Instruction for SequenceExpr {} - impl Instruction for PairExpr {} } mod transparent { - // NOTE: instructions in this module don't have to be made opaque with `model!` - // because they are never shared between client and server(http)/host(wasm) - use super::*; - use crate::executor::Executor; - - /// Generic instruction to set key value at the object. - #[derive(Debug, Clone)] - pub struct SetKeyValue { - /// Where to set key value. - pub object_id: O::Id, - /// Key. - pub key: Name, - /// Value. - pub value: Value, - } - - /// Generic instruction to remove key value at the object. - #[derive(Debug, Clone)] - pub struct RemoveKeyValue { - /// From where to remove key value. - pub object_id: O::Id, - /// Key of the pair to remove. - pub key: Name, - } - - /// Generic instruction for a registration of an object to the identifiable destination. - #[derive(Debug, Clone)] - pub struct Register { - /// The object that should be registered, should be uniquely identifiable by its id. - pub object: O::With, + use crate::{account::NewAccount, domain::NewDomain}; + + macro_rules! isi { + ($($meta:meta)* $item:item) => { + iroha_data_model_derive::model_single! { + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + #[derive(getset::Getters)] + #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode)] + #[derive(serde::Deserialize, serde::Serialize)] + #[derive(iroha_schema::IntoSchema)] + #[getset(get = "pub")] + $($meta)* + $item + } + }; + } + + macro_rules! impl_display { + ( + $ty:ident $(< $($generic:tt),+ >)? + $(where + $( $lt:path $( : $clt:tt $(< $inner_generic:tt >)? $(+ $dlt:tt )* )? ),+ $(,)?)? + => $fmt:literal, $($args:ident),* $(,)? + ) => { + impl $(< $($generic),+ >)? ::core::fmt::Display for $ty $(< $($generic),+ >)? + $(where + $( $lt $( : $clt $(< $inner_generic >)? $(+ $dlt )* )? ),+)? + { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + write!( + f, + $fmt, + $(self.$args),* + ) + } + } + } } - /// Generic instruction for an unregistration of an object from the identifiable destination. - #[derive(Debug, Clone)] - pub struct Unregister { - /// [`Identifiable::Id`] of the object which should be unregistered. - pub object_id: O::Id, + macro_rules! impl_into_box { + ( + $($isi:ident $(< $($generic:ident $(< $nested_generic:ident >)?),+ >)?)|* + ==> $boxed:ident :: $variant:ident + ) => {$( + impl From<$isi $(< $($generic $(< $nested_generic >)?),+ >)? > for $boxed { + fn from(instruction: $isi $(< $($generic $(< $nested_generic >)?),+ >)?) -> Self { + Self::$variant(instruction) + } + } + )*}; + ( + $($isi:ident $(< $($generic:ident $(< $nested_generic:ident >)?),+ >)?)|* + => $middle:ident ==> $boxed:ident :: $variant:ident + ) => {$( + impl From<$isi $(< $($generic $(< $nested_generic >)?),+ >)? > for $boxed { + fn from(instruction: $isi $(< $($generic $(< $nested_generic >)?),+ >)?) -> Self { + Self::$variant($middle::from(instruction)) + } + } + )*}; + } + + isi! { + /// Generic instruction for setting a chain-wide config parameter. + #[derive(Constructor, Display)] + #[display(fmt = "SET `{parameter}`")] + #[serde(transparent)] + #[repr(transparent)] + pub struct SetParameter { + /// The configuration parameter being changed. + #[serde(flatten)] + pub parameter: Parameter, + } } - /// Generic instruction for a mint of an object to the identifiable destination. - #[derive(Debug, Clone)] - pub struct Mint, D: Identifiable> { - /// Object which should be minted. - pub object: O, - /// Destination object [`Identifiable::Id`]. - pub destination_id: D::Id, + impl_into_box!(SetParameter ==> InstructionBox::SetParameter); + + isi! { + /// Sized structure for all possible on-chain configuration parameters when they are first created. + /// Generic instruction for setting a chain-wide config parameter. + #[derive(Constructor, Display)] + #[display(fmt = "SET `{parameter}`")] + #[serde(transparent)] + #[repr(transparent)] + pub struct NewParameter { + /// Parameter to be changed. + #[serde(flatten)] + pub parameter: Parameter, + } } - /// Generic instruction for a burn of an object to the identifiable destination. - #[derive(Debug, Clone)] - pub struct Burn, D: Identifiable> { - /// Object which should be burned. - pub object: O, - /// Destination object [`Identifiable::Id`]. - pub destination_id: D::Id, + impl_into_box!(NewParameter ==> InstructionBox::NewParameter); + + isi! { + /// Generic instruction to set key value at the object. + #[schema(bounds = "O: Identifiable, O::Id: IntoSchema")] + pub struct SetKeyValue { + /// Where to set key value. + #[serde(flatten)] + pub object_id: O::Id, + /// Key. + pub key: Name, + /// Value. + pub value: Value, + } } - /// Generic instruction for a transfer of an object from the identifiable source to the identifiable destination. - #[derive(Debug, Clone)] - pub struct Transfer, D: Identifiable> { - /// Source object `Id`. - pub source_id: S::Id, - /// Object which should be transferred. - pub object: O, - /// Destination object `Id`. - pub destination_id: D::Id, + 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 { + Self { + object_id: domain_id, + key, + value: value.into(), + } + } } - /// Generic instruction for granting permission to an entity. - #[derive(Debug, Clone)] - pub struct Grant> { - /// Object to grant. - pub object: O, - /// Entity to which to grant this token. - pub destination_id: AccountId, + 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 { + Self { + object_id: account_id, + key, + value: value.into(), + } + } } - /// Generic instruction for revoking permission from an entity. - #[derive(Debug, Clone)] - pub struct Revoke> { - /// Object to revoke. - pub object: O, - /// Entity which is being revoked this token from. - pub destination_id: AccountId, + impl SetKeyValue { + /// Constructs a new [`SetKeyValue`] for an [`AssetDefinition`] with the given `key` and `value`. + pub fn asset_definition( + asset_definition_id: AssetDefinitionId, + key: Name, + value: impl Into, + ) -> Self { + Self { + object_id: asset_definition_id, + key, + value: value.into(), + } + } } - /// Generic instruction for setting a chain-wide config parameter. - #[derive(Debug, Clone)] - pub struct SetParameter { - /// Parameter to be changed. - pub parameter: Parameter, + 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 { + Self { + object_id: asset_id, + key, + value: value.into(), + } + } } - /// Generic instruction for setting a chain-wide config parameter. - #[derive(Debug, Clone)] - pub struct NewParameter { - /// Parameter to be changed. - pub parameter: Parameter, + impl_display! { + SetKeyValue + where + O: Identifiable, + O::Id: Display, + => + "SET `{}` = `{}` IN `{}`", + key, value, object_id, + } + + impl_into_box! { + SetKeyValue | + SetKeyValue | + SetKeyValue | + SetKeyValue => SetKeyValueBox ==> InstructionBox::SetKeyValue + } + + isi! { + /// Generic instruction to remove key value at the object. + #[schema(bounds = "O: Identifiable, O::Id: IntoSchema")] + pub struct RemoveKeyValue { + /// From where to remove key value. + #[serde(flatten)] + pub object_id: O::Id, + /// Key of the pair to remove. + pub key: Name, + } } - /// Generic instruction for upgrading runtime objects. - #[derive(Debug, Clone)] - pub struct Upgrade> { - /// Object to upgrade. - pub object: O, + impl RemoveKeyValue { + /// Constructs a new [`RemoveKeyValue`] for a [`Domain`] with the given `key`. + pub fn domain(domain_id: DomainId, key: Name) -> Self { + Self { + object_id: domain_id, + key, + } + } + } + + impl RemoveKeyValue { + /// Constructs a new [`RemoveKeyValue`] for an [`Account`] with the given `key`. + pub fn account(account_id: AccountId, key: Name) -> Self { + Self { + object_id: account_id, + key, + } + } + } + + impl RemoveKeyValue { + /// Constructs a new [`RemoveKeyValue`] for an [`AssetDefinition`] with the given `key`. + pub fn asset_definition(asset_definition_id: AssetDefinitionId, key: Name) -> Self { + Self { + object_id: asset_definition_id, + key, + } + } + } + + impl RemoveKeyValue { + /// Constructs a new [`RemoveKeyValue`] for an [`Asset`] with the given `key`. + pub fn asset(asset_id: AssetId, key: Name) -> Self { + Self { + object_id: asset_id, + key, + } + } + } + + impl_display! { + RemoveKeyValue + where + O: Identifiable, + O::Id: Display, + => + "REMOVE `{}` from `{}`", + key, object_id, + } + + impl_into_box! { + RemoveKeyValue | + RemoveKeyValue | + RemoveKeyValue | + RemoveKeyValue => RemoveKeyValueBox ==> InstructionBox::RemoveKeyValue + } + + isi! { + /// Generic instruction for a registration of an object to the identifiable destination. + #[schema(bounds = "O: Registered, O::With: IntoSchema")] + #[serde(transparent)] + pub struct Register { + /// The object that should be registered, should be uniquely identifiable by its id. + pub object: O::With, + } } - /// Generic instruction for executing specified trigger - #[derive(Debug, Clone)] - pub struct ExecuteTrigger { - /// Id of a trigger to execute - pub trigger_id: TriggerId, + impl Register { + /// Constructs a new [`Register`] for a [`Peer`]. + pub fn peer(new_peer: Peer) -> Self { + Self { object: new_peer } + } } - /// Generic instruction for logging messages - #[derive(Debug, Clone)] - pub struct Log { - /// Log level of the message - pub level: Level, - /// Message to be logged - pub msg: String, + impl Register { + /// Constructs a new [`Register`] for a [`Domain`]. + pub fn domain(new_domain: NewDomain) -> Self { + Self { object: new_domain } + } } - impl From> for SetKeyValueExpr { - fn from(source: SetKeyValue) -> Self { - Self::new(source.object_id.into(), source.key, source.value) + impl Register { + /// Constructs a new [`Register`] for an [`Account`]. + pub fn account(new_account: NewAccount) -> Self { + Self { + object: new_account, + } } } - impl From> for RemoveKeyValueExpr { - fn from(source: RemoveKeyValue) -> Self { - Self::new(source.object_id.into(), source.key) + impl Register { + /// Constructs a new [`Register`] for an [`AssetDefinition`]. + pub fn asset_definition(new_asset_definition: NewAssetDefinition) -> Self { + Self { + object: new_asset_definition, + } } } - impl From> for RegisterExpr { - fn from(source: Register) -> Self { - Self::new(source.object.into()) + impl Register { + /// Constructs a new [`Register`] for an [`Asset`]. + pub fn asset(new_asset: Asset) -> Self { + Self { object: new_asset } } } - impl From> for UnregisterExpr { - fn from(source: Unregister) -> Self { - Self::new(source.object_id.into()) + impl Register { + /// Constructs a new [`Register`] for a [`Role`]. + pub fn role(new_role: NewRole) -> Self { + Self { object: new_role } } } - impl, D: Identifiable> From> for MintExpr { - fn from(source: Mint) -> Self { - Self::new(source.object, source.destination_id.into()) + impl Register> { + /// Constructs a new [`Register`] for a [`Trigger`]. + pub fn trigger(new_trigger: Trigger) -> Self { + Self { + object: new_trigger, + } } } - impl, D: Identifiable> From> for BurnExpr { - fn from(source: Burn) -> Self { - Self::new(source.object, source.destination_id.into()) + impl_display! { + Register + where + O: Registered, + O::With: Display, + => + "REGISTER `{}`", + object, + } + + impl_into_box! { + Register | + Register | + Register | + Register | + Register | + Register | + Register > => RegisterBox ==> InstructionBox::Register + } + + isi! { + /// Generic instruction for an unregistration of an object from the identifiable destination. + #[schema(bounds = "O: Identifiable, O::Id: IntoSchema")] + pub struct Unregister { + /// [`Identifiable::Id`] of the object which should be unregistered. + pub object_id: O::Id, } } - impl, D: Identifiable> From> for TransferExpr { - fn from(source: Transfer) -> Self { - Self::new( - source.source_id.into(), - source.object, - source.destination_id.into(), - ) + impl_display! { + Unregister + where + O: Identifiable, + O::Id: Display, + => + "UNREGISTER `{}`", + object_id, + } + + impl_into_box! { + Unregister | + Unregister | + Unregister | + Unregister | + Unregister | + Unregister | + Unregister > => UnregisterBox ==> InstructionBox::Unregister + } + + impl Unregister { + /// Constructs a new [`Unregister`] for a [`Peer`]. + pub fn peer(peer_id: PeerId) -> Self { + Self { object_id: peer_id } } } - impl> From> for GrantExpr { - fn from(source: Grant) -> Self { - Self::new(source.object, source.destination_id) + impl Unregister { + /// Constructs a new [`Unregister`] for a [`Domain`]. + pub fn domain(domain_id: DomainId) -> Self { + Self { + object_id: domain_id, + } } } - impl> From> for RevokeExpr { - fn from(source: Revoke) -> Self { - Self::new(source.object, source.destination_id) + impl Unregister { + /// Constructs a new [`Unregister`] for an [`Account`]. + pub fn account(account_id: AccountId) -> Self { + Self { + object_id: account_id, + } } } - impl From for SetParameterExpr { - fn from(source: SetParameter) -> Self { - Self::new(source.parameter) + impl Unregister { + /// Constructs a new [`Unregister`] for an [`AssetDefinition`]. + pub fn asset_definition(asset_definition_id: AssetDefinitionId) -> Self { + Self { + object_id: asset_definition_id, + } } } - impl From for NewParameterExpr { - fn from(source: NewParameter) -> Self { - Self::new(source.parameter) + impl Unregister { + /// Constructs a new [`Unregister`] for an [`Asset`]. + pub fn asset(asset_id: AssetId) -> Self { + Self { + object_id: asset_id, + } } } - impl From> for UpgradeExpr { - fn from(source: Upgrade) -> Self { - Self::new(source.object) + impl Unregister { + /// Constructs a new [`Unregister`] for a [`Role`]. + pub fn role(role_id: RoleId) -> Self { + Self { object_id: role_id } } } - impl From for ExecuteTriggerExpr { - fn from(source: ExecuteTrigger) -> Self { - Self::new(source.trigger_id) + impl Unregister> { + /// Constructs a new [`Unregister`] for a [`Trigger`]. + pub fn trigger(trigger_id: TriggerId) -> Self { + Self { + object_id: trigger_id, + } } } - impl From for LogExpr { - fn from(source: Log) -> Self { - Self::new(source.level, source.msg) + isi! { + /// Generic instruction for a mint of an object to the identifiable destination. + #[schema(bounds = "O: Into + IntoSchema, D: Identifiable, D::Id: IntoSchema")] + pub struct Mint, D: Identifiable> { + /// Object which should be minted. + pub object: O, + /// Destination object [`Identifiable::Id`]. + pub destination_id: D::Id, } } -} -isi! { - /// Sized structure for all possible on-chain configuration parameters. - #[derive(Display)] - #[display(fmt = "SET `{parameter}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `SetParameterExpr` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct SetParameterExpr { - /// The configuration parameter being changed. - #[serde(flatten)] - pub parameter: EvaluatesTo, + impl Mint { + /// Constructs a new [`Mint`] for a [`PublicKey`] for [`Account`]. + pub fn account_public_key(public_key: PublicKey, account_id: AccountId) -> Self { + Self { + object: public_key, + destination_id: account_id, + } + } } -} -isi! { - /// Sized structure for all possible on-chain configuration parameters when they are first created. - #[derive(Display)] - #[display(fmt = "SET `{parameter}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `NewParameterExpr` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct NewParameterExpr { - /// The configuration parameter being created. - #[serde(flatten)] - pub parameter: EvaluatesTo, + impl Mint { + /// Constructs a new [`Mint`] for a [`SignatureCheckCondition`] for [`Account`]. + pub fn account_signature_check_condition( + signature_check_condition: SignatureCheckCondition, + account_id: AccountId, + ) -> Self { + Self { + object: signature_check_condition, + destination_id: account_id, + } + } } -} -isi! { - /// Sized structure for all possible key value set instructions. - #[derive(Display)] - #[display(fmt = "SET `{key}` = `{value}` IN `{object_id}`")] - #[ffi_type] - pub struct SetKeyValueExpr { - /// Where to set this key value. - #[serde(flatten)] - pub object_id: EvaluatesTo, - /// Key string. - pub key: EvaluatesTo, - /// Object to set as a value. - pub value: EvaluatesTo, + impl Mint { + /// Constructs a new [`Mint`] for an [`Asset`] of [`Quantity`] type. + pub fn asset_quantity(quantity: u32, asset_id: AssetId) -> Self { + Self { + object: quantity, + destination_id: asset_id, + } + } } -} -isi! { - /// Sized structure for all possible key value pair remove instructions. - #[derive(Display)] - #[display(fmt = "REMOVE `{key}` from `{object_id}`")] - #[ffi_type] - pub struct RemoveKeyValueExpr { - /// From where to remove this key value. - #[serde(flatten)] - pub object_id: EvaluatesTo, - /// Key string. - pub key: EvaluatesTo, + impl Mint { + /// Constructs a new [`Mint`] for an [`Asset`] of [`BigQuantity`] type. + pub fn asset_big_quantity(big_quantity: u128, asset_id: AssetId) -> Self { + Self { + object: big_quantity, + destination_id: asset_id, + } + } } -} -isi! { - /// Sized structure for all possible Registers. - #[derive(Display)] - #[display(fmt = "REGISTER `{object}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `RegisterExpr` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct RegisterExpr { - /// The object that should be registered, should be uniquely identifiable by its id. - pub object: EvaluatesTo, + impl Mint { + /// Constructs a new [`Mint`] for an [`Asset`] of [`Fixed`] type. + pub fn asset_fixed(fixed: Fixed, asset_id: AssetId) -> Self { + Self { + object: fixed, + destination_id: asset_id, + } + } } -} -isi! { - /// Sized structure for all possible Unregisters. - #[derive(Display)] - #[display(fmt = "UNREGISTER `{object_id}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `UnregisterExpr` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct UnregisterExpr { - /// The id of the object that should be unregistered. - pub object_id: EvaluatesTo, + impl Mint> { + /// Constructs a new [`Mint`] for repetition count of [`Trigger`]. + pub fn trigger_repetitions(repetitions: u32, trigger_id: TriggerId) -> Self { + Self { + object: repetitions, + destination_id: trigger_id, + } + } } -} -isi! { - /// Sized structure for all possible Mints. - #[derive(Display)] - #[display(fmt = "MINT `{object}` TO `{destination_id}`")] - #[ffi_type] - pub struct MintExpr { - /// Object to mint. - pub object: EvaluatesTo, - /// Entity to mint to. - pub destination_id: EvaluatesTo, + impl_display! { + Mint + where + O: Into + Display, + D: Identifiable, + D::Id: Display, + => + "MINT `{}` TO `{}`", + object, + destination_id, } -} -isi! { - /// Sized structure for all possible Burns. - #[derive(Display)] - #[display(fmt = "BURN `{object}` FROM `{destination_id}`")] - #[ffi_type] - pub struct BurnExpr { - /// Object to burn. - pub object: EvaluatesTo, - /// Entity to burn from. - pub destination_id: EvaluatesTo, + impl_into_box! { + Mint | + Mint => AccountMintBox ==> MintBox::Account } -} -isi! { - /// Sized structure for all possible Transfers. - #[derive(Display)] - #[display(fmt = "TRANSFER `{object}` FROM `{source_id}` TO `{destination_id}`")] - #[ffi_type] - pub struct TransferExpr { - /// Entity to transfer from. - pub source_id: EvaluatesTo, - /// Object to transfer. - pub object: EvaluatesTo, - /// Entity to transfer to. - pub destination_id: EvaluatesTo, + impl_into_box! { + Mint | + Mint | + Mint => AssetMintBox ==> MintBox::Asset } -} -isi! { - /// Composite instruction for a pair of instructions. - #[derive(Display)] - #[display(fmt = "(`{left_instruction}`, `{right_instruction}`)")] - #[ffi_type] - pub struct PairExpr { - /// Left instruction - pub left_instruction: InstructionExpr, - /// Right instruction - pub right_instruction: InstructionExpr, + impl_into_box! { + Mint | + Mint | + Mint | + Mint | + Mint | + Mint > => MintBox ==> InstructionBox::Mint } -} -isi! { - /// Composite instruction for a sequence of instructions. - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `SequenceExpr` has no trap representation in `Vec` - #[ffi_type(unsafe {robust})] - pub struct SequenceExpr { - /// Sequence of Iroha Special Instructions to execute. - pub instructions: Vec, + isi! { + /// Generic instruction for a burn of an object to the identifiable destination. + #[schema(bounds = "O: Into + IntoSchema, D: Identifiable, D::Id: IntoSchema")] + pub struct Burn, D: Identifiable> { + /// Object which should be burned. + pub object: O, + /// Destination object [`Identifiable::Id`]. + pub destination_id: D::Id, + } } -} -impl core::fmt::Display for SequenceExpr { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "SEQUENCE [")?; - let mut first = true; - for instruction in &self.instructions { - if !first { - write!(f, ", ")?; + impl Burn { + /// Constructs a new [`Burn`] for a [`PublicKey`] for [`Account`]. + pub fn account_public_key(public_key: PublicKey, account_id: AccountId) -> Self { + Self { + object: public_key, + destination_id: account_id, } - first = false; + } + } - write!(f, "`{instruction}`")?; + impl Burn { + /// Constructs a new [`Burn`] for an [`Asset`] of [`Quantity`] type. + pub fn asset_quantity(quantity: u32, asset_id: AssetId) -> Self { + Self { + object: quantity, + destination_id: asset_id, + } } - write!(f, "]") } -} -isi! { - /// Composite instruction for a conditional execution of other instructions. - #[ffi_type] - pub struct ConditionalExpr { - /// Condition to be checked. - pub condition: EvaluatesTo, - /// Instruction to be executed if condition pass. - pub then: InstructionExpr, - /// Optional instruction to be executed if condition fail. - pub otherwise: Option, + impl Burn { + /// Constructs a new [`Burn`] for an [`Asset`] of [`BigQuantity`] type. + pub fn asset_big_quantity(big_quantity: u128, asset_id: AssetId) -> Self { + Self { + object: big_quantity, + destination_id: asset_id, + } + } } -} -impl core::fmt::Display for ConditionalExpr { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "IF `{}` THEN `{}`", self.condition, self.then)?; - if let Some(otherwise) = &self.otherwise { - write!(f, " ELSE `{otherwise}`")?; + impl Burn { + /// Constructs a new [`Burn`] for an [`Asset`] of [`Fixed`] type. + pub fn asset_fixed(fixed: Fixed, asset_id: AssetId) -> Self { + Self { + object: fixed, + destination_id: asset_id, + } } + } - Ok(()) + impl Burn> { + /// Constructs a new [`Burn`] for repetition count of [`Trigger`]. + pub fn trigger_repetitions(repetitions: u32, trigger_id: TriggerId) -> Self { + Self { + object: repetitions, + destination_id: trigger_id, + } + } } -} -isi! { - /// Utilitary instruction to fail execution and submit an error `message`. - #[derive(Display)] - #[display(fmt = "FAIL `{message}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `Fail` has no trap representation in `String` - #[ffi_type(unsafe {robust})] - pub struct Fail { - /// Message to submit. - pub message: String, + impl_display! { + Burn + where + O: Into + Display, + D: Identifiable, + D::Id: Display, + => + "BURN `{}` FROM `{}`", + object, + destination_id, + } + + impl_into_box! { + Burn | + Burn | + Burn => AssetBurnBox ==> BurnBox::Asset + } + + impl_into_box! { + Burn | + Burn | + Burn | + Burn | + Burn > => BurnBox ==> InstructionBox::Burn + } + + isi! { + /// Generic instruction for a transfer of an object from the identifiable source to the identifiable destination. + #[schema(bounds = "S: Identifiable, S::Id: IntoSchema, \ + O: Into + IntoSchema, \ + D: Identifiable, D::Id: IntoSchema")] + pub struct Transfer, D: Identifiable> { + /// Source object `Id`. + pub source_id: S::Id, + /// Object which should be transferred. + pub object: O, + /// Destination object `Id`. + pub destination_id: D::Id, + } } -} -isi! { - /// Sized structure for all possible Grants. - #[derive(Display)] - #[display(fmt = "GRANT `{object}` TO `{destination_id}`")] - #[ffi_type] - pub struct GrantExpr { - /// Object to grant. - pub object: EvaluatesTo, - /// Account to which to grant this object. - pub destination_id: EvaluatesTo, + impl Transfer { + /// Constructs a new [`Transfer`] for a [`Domain`]. + pub fn domain(from: AccountId, domain_id: DomainId, to: AccountId) -> Self { + Self { + source_id: from, + object: domain_id, + destination_id: to, + } + } } -} -isi! { - /// Sized structure for all possible Grants. - #[derive(Display)] - #[display(fmt = "REVOKE `{object}` FROM `{destination_id}`")] - #[ffi_type] - pub struct RevokeExpr { - /// Object to revoke. - pub object: EvaluatesTo, - /// Account to which to revoke this object from. - pub destination_id: EvaluatesTo, + impl Transfer { + /// Constructs a new [`Transfer`] for an [`AssetDefinition`]. + pub fn asset_definition( + from: AccountId, + asset_definition_id: AssetDefinitionId, + to: AccountId, + ) -> Self { + Self { + source_id: from, + object: asset_definition_id, + destination_id: to, + } + } } -} -isi! { - /// Instruction to execute specified trigger - #[derive(Display)] - #[display(fmt = "EXECUTE `{trigger_id}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `ExecuteTriggerExpr` has no trap representation in `TriggerId` - #[ffi_type(unsafe {robust})] - pub struct ExecuteTriggerExpr { - /// Id of a trigger to execute - pub trigger_id: EvaluatesTo, + impl Transfer { + /// Constructs a new [`Transfer`] for an [`Asset`] of [`Quantity`] type. + pub fn asset_quantity(asset_id: AssetId, quantity: u32, to: AccountId) -> Self { + Self { + source_id: asset_id, + object: quantity, + destination_id: to, + } + } } -} -isi! { - /// Sized structure for all possible Upgrades. - #[derive(Display)] - #[display(fmt = "UPGRADE `{object}`")] - #[serde(transparent)] - #[repr(transparent)] - // SAFETY: `UpgradeExpr` has no trap representation in `EvaluatesTo` - #[ffi_type(unsafe {robust})] - pub struct UpgradeExpr { - /// The object to upgrade. - pub object: EvaluatesTo, + impl Transfer { + /// Constructs a new [`Transfer`] for an [`Asset`] of [`BigQuantity`] type. + pub fn asset_big_quantity(asset_id: AssetId, big_quantity: u128, to: AccountId) -> Self { + Self { + source_id: asset_id, + object: big_quantity, + destination_id: to, + } + } } -} -isi! { - /// Instruction to print logs - #[derive(Display)] - #[display(fmt = "LOG({level}): {msg}")] - #[ffi_type] - pub struct LogExpr { - /// Message log level - #[serde(flatten)] - pub level: EvaluatesTo, - /// Msg to be logged - pub msg: EvaluatesTo, + impl Transfer { + /// Constructs a new [`Transfer`] for an [`Asset`] of [`Fixed`] type. + pub fn asset_fixed(asset_id: AssetId, fixed: Fixed, to: AccountId) -> Self { + Self { + source_id: asset_id, + object: fixed, + destination_id: to, + } + } } -} -impl ExecuteTriggerExpr { - /// Construct [`ExecuteTriggerExpr`] - pub fn new(trigger_id: I) -> Self - where - I: Into>, - { - Self { - trigger_id: trigger_id.into(), + impl_display! { + Transfer + where + S: Identifiable, + S::Id: Display, + O: Into + Display, + D: Identifiable, + D::Id: Display, + => + "TRANSFER `{}` FROM `{}` TO `{}`", + object, + source_id, + destination_id, + } + + impl_into_box! { + Transfer | + Transfer | + Transfer => AssetTransferBox ==> TransferBox::Asset + } + + impl_into_box! { + Transfer | + Transfer | + Transfer | + Transfer | + Transfer => TransferBox ==> InstructionBox::Transfer + } + + isi! { + /// Utilitary instruction to fail execution and submit an error `message`. + #[derive(Constructor, Display)] + #[display(fmt = "FAIL `{message}`")] + #[serde(transparent)] + #[repr(transparent)] + pub struct Fail { + /// Message to submit. + pub message: String, } } -} -impl RevokeExpr { - /// Generic constructor. - pub fn new>, I: Into>>( - object: P, - destination_id: I, - ) -> Self { - Self { - destination_id: destination_id.into(), - object: object.into(), + impl_into_box!(Fail ==> InstructionBox::Fail); + + isi! { + /// Generic instruction for granting permission to an entity. + pub struct Grant> { + /// Object to grant. + pub object: O, + /// Entity to which to grant this token. + pub destination_id: AccountId, } } -} -impl GrantExpr { - /// Constructor. - pub fn new>, I: Into>>( - object: P, - destination_id: I, - ) -> Self { - Self { - destination_id: destination_id.into(), - object: object.into(), + impl Grant { + /// Constructs a new [`Grant`] for a [`PermissionToken`]. + pub fn permission_token(permission_token: PermissionToken, to: AccountId) -> Self { + Self { + object: permission_token, + destination_id: to, + } } } -} -impl SetKeyValueExpr { - /// Construct [`SetKeyValueExpr`]. - pub fn new< - I: Into>, - K: Into>, - V: Into>, - >( - object_id: I, - key: K, - value: V, - ) -> Self { - Self { - object_id: object_id.into(), - key: key.into(), - value: value.into(), + impl Grant { + /// Constructs a new [`Grant`] for a [`Role`]. + pub fn role(role_id: RoleId, to: AccountId) -> Self { + Self { + object: role_id, + destination_id: to, + } } } -} -impl RemoveKeyValueExpr { - /// Construct [`RemoveKeyValueExpr`]. - pub fn new>, K: Into>>( - object_id: I, - key: K, - ) -> Self { - Self { - object_id: object_id.into(), - key: key.into(), + impl_display! { + Grant + where + O: Into + Display, + => + "GRANT `{}` TO `{}`", + object, + destination_id, + } + + impl_into_box! { + Grant | + Grant => GrantBox ==> InstructionBox::Grant + } + + isi! { + /// Generic instruction for revoking permission from an entity. + pub struct Revoke> { + /// Object to revoke. + pub object: O, + /// Entity which is being revoked this token from. + pub destination_id: AccountId, } } -} -impl RegisterExpr { - /// Construct [`Register`]. - pub fn new>>(object: O) -> Self { - Self { - object: object.into(), + impl Revoke { + /// Constructs a new [`Revoke`] for a [`PermissionToken`]. + pub fn permission_token(permission_token: PermissionToken, from: AccountId) -> Self { + Self { + object: permission_token, + destination_id: from, + } } } -} -impl UnregisterExpr { - /// Construct [`Unregister`]. - pub fn new>>(object_id: O) -> Self { - Self { - object_id: object_id.into(), + impl Revoke { + /// Constructs a new [`Revoke`] for a [`Role`]. + pub fn role(role_id: RoleId, from: AccountId) -> Self { + Self { + object: role_id, + destination_id: from, + } } } -} -impl MintExpr { - /// Construct [`Mint`]. - pub fn new>, D: Into>>( - object: O, - destination_id: D, - ) -> Self { - Self { - object: object.into(), - destination_id: destination_id.into(), + impl_display! { + Revoke + where + O: Into + Display, + => + "REVOKE `{}` FROM `{}`", + object, + destination_id, + } + + impl_into_box! { + Revoke | + Revoke => RevokeBox ==> InstructionBox::Revoke + } + + isi! { + /// Instruction to execute specified trigger + #[derive(Constructor, Display)] + #[display(fmt = "EXECUTE `{trigger_id}`")] + #[serde(transparent)] + #[repr(transparent)] + pub struct ExecuteTrigger { + /// Id of a trigger to execute + pub trigger_id: TriggerId, } } -} -impl BurnExpr { - /// Construct [`Burn`]. - pub fn new>, D: Into>>( - object: O, - destination_id: D, - ) -> Self { - Self { - object: object.into(), - destination_id: destination_id.into(), + impl_into_box!(ExecuteTrigger ==> InstructionBox::ExecuteTrigger); + + isi! { + /// Generic instruction for upgrading runtime objects. + #[derive(Constructor, Display)] + #[display(fmt = "UPGRADE")] + #[serde(transparent)] + #[repr(transparent)] + pub struct Upgrade { + /// Object to upgrade. + pub executor: Executor, } } -} -impl TransferExpr { - /// Construct [`Transfer`]. - pub fn new< - S: Into>, - O: Into>, - D: Into>, - >( - source_id: S, - object: O, - destination_id: D, - ) -> Self { - Self { - source_id: source_id.into(), - object: object.into(), - destination_id: destination_id.into(), + impl_into_box!(Upgrade ==> InstructionBox::Upgrade); + + isi! { + /// Instruction to print logs + #[derive(Constructor, Display)] + #[display(fmt = "LOG({level}): {msg}")] + pub struct Log { + /// Message log level + #[serde(flatten)] + pub level: Level, + #[getset(skip)] // TODO: Fix this by addressing ffi issues + /// Msg to be logged + pub msg: String, } } + + impl_into_box!(Log ==> InstructionBox::Log); } -impl PairExpr { - /// Construct [`Pair`]. - pub fn new, RI: Into>( - left_instruction: LI, - right_instruction: RI, - ) -> Self { - PairExpr { - left_instruction: left_instruction.into(), - right_instruction: right_instruction.into(), - } +macro_rules! isi_box { + ($($meta:meta)* $item:item) => { + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Display, + parity_scale_codec::Decode, + parity_scale_codec::Encode, + serde::Deserialize, + serde::Serialize, + iroha_schema::IntoSchema, + derive_more::From, + )] + $($meta)* + $item + }; +} + +isi_box! { + /// Enum with all supported [`SetKeyValue`] instructions. + pub enum SetKeyValueBox { + /// Set key value for [`Domain`]. + Domain(SetKeyValue), + /// Set key value for [`Account`]. + Account(SetKeyValue), + /// Set key value for [`AssetDefinition`]. + AssetDefinition(SetKeyValue), + /// Set key value for [`Asset`]. + Asset(SetKeyValue), } } -impl SequenceExpr { - /// Construct [`SequenceExpr`]. - pub fn new(instructions: impl IntoIterator) -> Self { - Self { - instructions: instructions.into_iter().collect(), - } +isi_box! { + /// Enum with all supported [`RemoveKeyValue`] instructions. + pub enum RemoveKeyValueBox { + /// Remove key value from [`Domain`]. + Domain(RemoveKeyValue), + /// Remove key value from [`Account`]. + Account(RemoveKeyValue), + /// Remove key value from [`AssetDefinition`]. + AssetDefinition(RemoveKeyValue), + /// Remove key value from [`Asset`]. + Asset(RemoveKeyValue), } } -impl ConditionalExpr { - /// Construct [`If`]. - pub fn new>, T: Into>( - condition: C, - then: T, - ) -> Self { - Self { - condition: condition.into(), - then: then.into(), - otherwise: None, - } - } - /// [`If`] constructor with `Otherwise` instruction. - pub fn with_otherwise< - C: Into>, - T: Into, - O: Into, - >( - condition: C, - then: T, - otherwise: O, - ) -> Self { - Self { - condition: condition.into(), - then: then.into(), - otherwise: Some(otherwise.into()), - } +isi_box! { + /// Enum with all supported [`Register`] instructions. + pub enum RegisterBox { + /// Register [`Peer`]. + Peer(Register), + /// Register [`Domain`]. + Domain(Register), + /// Register [`Account`]. + Account(Register), + /// Register [`AssetDefinition`]. + AssetDefinition(Register), + /// Register [`Asset`]. + Asset(Register), + /// Register [`Role`]. + Role(Register), + /// Register [`Trigger`]. + Trigger(Register>) } } -impl Fail { - /// Construct [`Fail`]. - pub fn new(message: &str) -> Self { - Self { - message: String::from(message), - } +isi_box! { + /// Enum with all supported [`Unregister`] instructions. + pub enum UnregisterBox { + /// Unregister [`Peer`]. + Peer(Unregister), + /// Unregister [`Domain`]. + Domain(Unregister), + /// Unregister [`Account`]. + Account(Unregister), + /// Unregister [`AssetDefinition`]. + AssetDefinition(Unregister), + /// Unregister [`Asset`]. + Asset(Unregister), + /// Unregister [`Role`]. + Role(Unregister), + /// Unregister [`Trigger`]. + Trigger(Unregister>) } } -impl SetParameterExpr { - /// Construct [`SetParameterExpr`]. - pub fn new>>(parameter: P) -> Self { - Self { - parameter: parameter.into(), - } +isi_box! { + /// Enum with all supported [`Mint`] instructions. + pub enum MintBox { + /// Mint for [`Account`]. + Account(AccountMintBox), + /// Mint for [`Asset`]. + Asset(AssetMintBox), + /// Mint [`Trigger`] repetitions. + TriggerRepetitions(Mint>), } } -impl NewParameterExpr { - /// Construct [`NewParameterExpr`]. - pub fn new>>(parameter: P) -> Self { - Self { - parameter: parameter.into(), - } +isi_box! { + /// Enum with all supported [`Mint`] instructions related to [`Account`]. + pub enum AccountMintBox { + /// Mint [`PublicKey`]. + PublicKey(Mint), + /// Mint [`SignatureCheckCondition`]. + SignatureCheckCondition(Mint), } } -impl UpgradeExpr { - /// Construct [`UpgradeExpr`]. - pub fn new>>(object: O) -> Self { - Self { - object: object.into(), - } +isi_box! { + /// Enum with all supported [`Mint`] instructions related to [`Asset`]. + pub enum AssetMintBox { + /// Mint [`Asset`] of [`Quantity`] type. + Quantity(Mint), + /// Mint [`Asset`] of [`BigQuantity`] type. + BigQuantity(Mint), + /// Mint [`Asset`] of [`Fixed`] type. + Fixed(Mint), } } -impl LogExpr { - /// Construct [`LogExpr`] - pub fn new>, M: Into>>( - level: L, - msg: M, - ) -> Self { - Self { - level: level.into(), - msg: msg.into(), - } +isi_box! { + /// Enum with all supported [`Burn`] instructions. + pub enum BurnBox { + /// Burn [`PublicKey`] for [`Account`]. + AccountPublicKey(Burn), + /// Burn [`Asset`]. + Asset(AssetBurnBox), + /// Burn [`Trigger`] repetitions. + TriggerRepetitions(Burn>), + } +} + +isi_box! { + /// Enum with all supported [`Burn`] instructions related to [`Asset`]. + pub enum AssetBurnBox { + /// Burn [`Asset`] of [`Quantity`] type. + Quantity(Burn), + /// Burn [`Asset`] of [`BigQuantity`] type. + BigQuantity(Burn), + /// Burn [`Asset`] of [`Fixed`] type. + Fixed(Burn), + } +} + +isi_box! { + /// Enum with all supported [`Transfer`] instructions. + pub enum TransferBox { + /// Transfer [`Domain`] to another [`Account`]. + Domain(Transfer), + /// Transfer [`AssetDefinition`] to another [`Account`]. + AssetDefinition(Transfer), + /// Transfer [`Asset`] to another [`Account`]. + Asset(AssetTransferBox), + } +} + +isi_box! { + /// Enum with all supported [`Transfer`] instructions related to [`Asset`]. + pub enum AssetTransferBox { + /// Transfer [`Asset`] of [`Quantity`] type. + Quantity(Transfer), + /// Transfer [`Asset`] of [`BigQuantity`] type. + BigQuantity(Transfer), + /// Transfer [`Asset`] of [`Fixed`] type. + Fixed(Transfer), + } +} + +isi_box! { + /// Enum with all supported [`Grant`] instructions. + pub enum GrantBox { + /// Grant [`PermissionToken`] to [`Account`]. + PermissionToken(Grant), + /// Grant [`Role`] to [`Account`]. + Role(Grant), + } +} + +isi_box! { + /// Enum with all supported [`Revoke`] instructions. + pub enum RevokeBox { + /// Revoke [`PermissionToken`] from [`Account`]. + PermissionToken(Revoke), + /// Revoke [`Role`] from [`Account`]. + Role(Revoke), } } @@ -884,9 +1237,9 @@ pub mod error { use super::InstructionType; use crate::{ asset::AssetValueType, - evaluate, metadata, + metadata, query::error::{FindError, QueryExecutionFail}, - IdBox, NumericValue, Value, + IdBox, Value, }; #[model] @@ -975,8 +1328,6 @@ pub mod error { // TODO: Only temporarily opaque because of problems with FFI #[ffi_type(opaque)] pub enum InstructionEvaluationError { - /// Failed to evaluate expression - Expression(#[cfg_attr(feature = "std", source)] evaluate::EvaluationError), /// Unsupported parameter type for instruction of type `{0}` Unsupported(InstructionType), /// Failed to find parameter in a permission: {0} @@ -1074,38 +1425,10 @@ pub mod error { /// /// No actual function should ever return this if possible Unknown, - /// Encountered incompatible type of arguments - BinaryOpIncompatibleNumericValueTypes( - #[cfg_attr(feature = "std", source)] BinaryOpIncompatibleNumericValueTypesError, - ), /// Conversion failed: {0} FixedPointConversion(String), } - #[derive( - Debug, - Display, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Deserialize, - Serialize, - Decode, - Encode, - IntoSchema, - )] - #[display( - fmt = "Binary operation does not support provided combination of arguments ({left}, {right})" - )] - #[cfg_attr(feature = "std", derive(thiserror::Error))] - #[ffi_type] - pub struct BinaryOpIncompatibleNumericValueTypesError { - pub left: NumericValue, - pub right: NumericValue, - } - /// Mintability logic error #[derive( Debug, @@ -1160,9 +1483,10 @@ pub mod error { NameLength, } + /// Repetition of of `{instruction_type}` for id `{id}` #[derive( Debug, - Display, + displaydoc::Display, Clone, PartialEq, Eq, @@ -1174,11 +1498,12 @@ pub mod error { Encode, IntoSchema, )] - #[display(fmt = "Repetition of of `{instruction_type}` for id `{id}`")] #[cfg_attr(feature = "std", derive(thiserror::Error))] #[ffi_type] pub struct RepetitionError { + /// Instruction type pub instruction_type: InstructionType, + /// Id of the object being repeated pub id: IdBox, } } @@ -1188,11 +1513,6 @@ pub mod error { Self::Evaluate(InstructionEvaluationError::Type(err)) } } - impl From for InstructionExecutionError { - fn from(err: evaluate::EvaluationError) -> Self { - Self::Evaluate(InstructionEvaluationError::Expression(err)) - } - } impl From for MathError { fn from(err: FixedPointOperationError) -> Self { match err { @@ -1215,10 +1535,9 @@ pub mod error { /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ - Burn, BurnExpr, ConditionalExpr, ExecuteTrigger, ExecuteTriggerExpr, Fail, Grant, - GrantExpr, InstructionExpr, Log, LogExpr, Mint, MintExpr, NewParameter, NewParameterExpr, - PairExpr, Register, RegisterExpr, RemoveKeyValue, RemoveKeyValueExpr, Revoke, RevokeExpr, - SequenceExpr, SetKeyValue, SetKeyValueExpr, SetParameter, SetParameterExpr, Transfer, - TransferExpr, Unregister, UnregisterExpr, Upgrade, UpgradeExpr, + AccountMintBox, AssetBurnBox, AssetMintBox, AssetTransferBox, Burn, BurnBox, + ExecuteTrigger, Fail, Grant, GrantBox, InstructionBox, Log, Mint, MintBox, NewParameter, + Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, Revoke, RevokeBox, SetKeyValue, + SetKeyValueBox, SetParameter, Transfer, TransferBox, Unregister, UnregisterBox, Upgrade, }; } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index ff1920331da..0852f58d05f 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -56,10 +56,8 @@ pub mod account; pub mod asset; pub mod block; pub mod domain; -pub mod evaluate; pub mod events; pub mod executor; -pub mod expression; pub mod ipfs; pub mod isi; pub mod metadata; @@ -77,39 +75,76 @@ pub mod trigger; pub mod visit; mod seal { - use crate::{isi::prelude::*, query::prelude::*}; + use crate::prelude::*; pub trait Sealed {} macro_rules! impl_sealed { - ($($ident:ident),+ $(,)?) => { $( - impl Sealed for $ident {} )+ + ($($ident:ident $(< $($generic:ident $(< $inner_generic:ident >)?),+ >)?),+ $(,)?) => { $( + impl Sealed for $ident $(< $($generic $(< $inner_generic >)?),+ >)? {} )+ }; } impl_sealed! { // Boxed instructions - InstructionExpr, - SetKeyValueExpr, - RemoveKeyValueExpr, - RegisterExpr, - UnregisterExpr, - MintExpr, - BurnExpr, - TransferExpr, - GrantExpr, - RevokeExpr, - SetParameterExpr, - NewParameterExpr, - UpgradeExpr, - ExecuteTriggerExpr, - LogExpr, - - // Composite instructions - SequenceExpr, - ConditionalExpr, - PairExpr, - + InstructionBox, + + SetKeyValue, + SetKeyValue, + SetKeyValue, + SetKeyValue, + + RemoveKeyValue, + RemoveKeyValue, + RemoveKeyValue, + RemoveKeyValue, + + Register, + Register, + Register, + Register, + Register, + Register, + Register >, + + Unregister, + Unregister, + Unregister, + Unregister, + Unregister, + Unregister, + Unregister >, + + Mint, + Mint, + Mint, + Mint, + Mint, + Mint >, + + Burn, + Burn, + Burn, + Burn, + Burn >, + + Transfer, + Transfer, + Transfer, + Transfer, + Transfer, + + Grant, + Grant, + + Revoke, + Revoke, + + SetParameter, + NewParameter, + Upgrade, + ExecuteTrigger, + Log, Fail, // Boxed queries @@ -227,6 +262,7 @@ pub mod parameter { pub use self::model::*; use super::*; + use crate::isi::InstructionBox; /// Set of parameter names currently used by iroha #[allow(missing_docs)] @@ -450,27 +486,21 @@ pub mod parameter { } /// Create sequence isi for setting parameters - pub fn into_set_parameters(self) -> isi::SequenceExpr { - isi::SequenceExpr { - instructions: self - .parameters - .into_iter() - .map(isi::SetParameterExpr::new) - .map(Into::into) - .collect(), - } + pub fn into_set_parameters(self) -> Vec { + self.parameters + .into_iter() + .map(isi::SetParameter::new) + .map(Into::into) + .collect() } /// Create sequence isi for creating parameters - pub fn into_create_parameters(self) -> isi::SequenceExpr { - isi::SequenceExpr { - instructions: self - .parameters - .into_iter() - .map(isi::NewParameterExpr::new) - .map(Into::into) - .collect(), - } + pub fn into_create_parameters(self) -> Vec { + self.parameters + .into_iter() + .map(isi::NewParameter::new) + .map(Into::into) + .collect() } } @@ -1828,12 +1858,12 @@ pub mod prelude { #[cfg(feature = "std")] pub use super::current_time; pub use super::{ - account::prelude::*, asset::prelude::*, domain::prelude::*, evaluate::prelude::*, - events::prelude::*, executor::prelude::*, expression::prelude::*, isi::prelude::*, - metadata::prelude::*, name::prelude::*, parameter::prelude::*, peer::prelude::*, - permission::prelude::*, query::prelude::*, role::prelude::*, transaction::prelude::*, - trigger::prelude::*, EnumTryAsError, HasMetadata, IdBox, Identifiable, IdentifiableBox, - LengthLimits, NumericValue, PredicateTrait, RegistrableBox, ToValue, TryAsMut, TryAsRef, - TryToValue, UpgradableBox, ValidationFail, Value, + account::prelude::*, asset::prelude::*, domain::prelude::*, events::prelude::*, + executor::prelude::*, isi::prelude::*, metadata::prelude::*, name::prelude::*, + parameter::prelude::*, peer::prelude::*, permission::prelude::*, query::prelude::*, + role::prelude::*, transaction::prelude::*, trigger::prelude::*, EnumTryAsError, + HasMetadata, IdBox, Identifiable, IdentifiableBox, LengthLimits, NumericValue, + PredicateTrait, RegistrableBox, ToValue, TryAsMut, TryAsRef, TryToValue, UpgradableBox, + ValidationFail, Value, }; } diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index d5df0b85016..6d96d52634f 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -88,6 +88,7 @@ macro_rules! queries { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] #[derive(parity_scale_codec::Decode, parity_scale_codec::Encode)] #[derive(serde::Deserialize, serde::Serialize)] + #[derive(derive_more::Constructor)] #[derive(iroha_schema::IntoSchema)] $($meta)* $item )+ @@ -184,7 +185,7 @@ pub mod model { /// The hash of the block to which `tx` belongs to pub block_hash: HashOf, /// Transaction - pub transaction: TransactionValue, + pub transaction: Box, } /// Type returned from [`Metadata`] queries @@ -220,9 +221,13 @@ pub mod model { )] #[getset(get = "pub")] pub struct QueryWithParameters { + /// The actual query. pub query: Q, + /// Sorting of the query results. pub sorting: Sorting, + /// Pagination of the query results. pub pagination: Pagination, + /// Amount of results to fetch. pub fetch_size: FetchSize, } } @@ -323,7 +328,7 @@ pub mod role { #[ffi_type(unsafe {robust})] pub struct FindRoleByRoleId { /// `Id` of the [`Role`] to find - pub id: EvaluatesTo, + pub id: RoleId, } /// [`FindRolesByAccountId`] Iroha Query finds all [`Role`]s for a specified account. @@ -334,7 +339,7 @@ pub mod role { #[ffi_type(unsafe {robust})] pub struct FindRolesByAccountId { /// `Id` of an account to find. - pub id: EvaluatesTo, + pub id: AccountId, } } @@ -354,22 +359,6 @@ pub mod role { type Output = Role; } - impl FindRoleByRoleId { - /// Construct [`FindRoleByRoleId`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindRolesByAccountId { - /// Construct [`FindRolesByAccountId`]. - pub fn new(account_id: impl Into>) -> Self { - Self { - id: account_id.into(), - } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this module. pub mod prelude { pub use super::{FindAllRoleIds, FindAllRoles, FindRoleByRoleId, FindRolesByAccountId}; @@ -405,7 +394,7 @@ pub mod permission { #[ffi_type(unsafe {robust})] pub struct FindPermissionTokensByAccountId { /// `Id` of an account to find. - pub id: EvaluatesTo, + pub id: AccountId, } } @@ -417,15 +406,6 @@ pub mod permission { type Output = Vec; } - impl FindPermissionTokensByAccountId { - /// Construct [`FindPermissionTokensByAccountId`]. - pub fn new(account_id: impl Into>) -> Self { - Self { - id: account_id.into(), - } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this module. pub mod prelude { pub use super::{FindPermissionTokenSchema, FindPermissionTokensByAccountId}; @@ -459,7 +439,7 @@ pub mod account { #[ffi_type(unsafe {robust})] pub struct FindAccountById { /// `Id` of an account to find. - pub id: EvaluatesTo, + pub id: AccountId, } /// [`FindAccountKeyValueByIdAndKey`] Iroha Query finds a [`Value`] @@ -469,9 +449,9 @@ pub mod account { #[ffi_type] pub struct FindAccountKeyValueByIdAndKey { /// `Id` of an account to find. - pub id: EvaluatesTo, + pub id: AccountId, /// Key of the specific key-value in the Account's metadata. - pub key: EvaluatesTo, + pub key: Name, } /// [`FindAccountsByName`] Iroha Query gets [`Account`]s name as input and @@ -483,7 +463,7 @@ pub mod account { #[ffi_type(unsafe {robust})] pub struct FindAccountsByName { /// `name` of accounts to find. - pub name: EvaluatesTo, + pub name: Name, } @@ -496,7 +476,7 @@ pub mod account { #[ffi_type(unsafe {robust})] pub struct FindAccountsByDomainId { /// `Id` of the domain under which accounts should be found. - pub domain_id: EvaluatesTo, + pub domain_id: DomainId, } /// [`FindAccountsWithAsset`] Iroha Query gets [`AssetDefinition`]s id as input and @@ -508,7 +488,7 @@ pub mod account { #[ffi_type(unsafe {robust})] pub struct FindAccountsWithAsset { /// `Id` of the definition of the asset which should be stored in founded accounts. - pub asset_definition_id: EvaluatesTo, + pub asset_definition_id: AssetDefinitionId, } } @@ -536,51 +516,6 @@ pub mod account { type Output = Vec; } - impl FindAccountById { - /// Construct [`FindAccountById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindAccountKeyValueByIdAndKey { - /// Construct [`FindAccountById`]. - pub fn new( - id: impl Into>, - key: impl Into>, - ) -> Self { - Self { - id: id.into(), - key: key.into(), - } - } - } - - impl FindAccountsByName { - /// Construct [`FindAccountsByName`]. - pub fn new(name: impl Into>) -> Self { - Self { name: name.into() } - } - } - - impl FindAccountsByDomainId { - /// Construct [`FindAccountsByDomainId`]. - pub fn new(domain_id: impl Into>) -> Self { - Self { - domain_id: domain_id.into(), - } - } - } - - impl FindAccountsWithAsset { - /// Construct [`FindAccountsWithAsset`]. - pub fn new(asset_definition_id: impl Into>) -> Self { - Self { - asset_definition_id: asset_definition_id.into(), - } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ @@ -627,7 +562,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetById { /// `Id` of an [`Asset`] to find. - pub id: EvaluatesTo, + pub id: AssetId, } /// [`FindAssetDefinitionById`] Iroha Query finds an [`AssetDefinition`] by it's identification in Iroha [`Peer`]. @@ -638,7 +573,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetDefinitionById { /// `Id` of an [`AssetDefinition`] to find. - pub id: EvaluatesTo, + pub id: AssetDefinitionId, } /// [`FindAssetsByName`] Iroha Query gets [`Asset`]s name as input and @@ -650,7 +585,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetsByName { /// [`Name`] of [`Asset`]s to find. - pub name: EvaluatesTo, + pub name: Name, } /// [`FindAssetsByAccountId`] Iroha Query gets [`AccountId`] as input and find all [`Asset`]s @@ -662,7 +597,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetsByAccountId { /// [`AccountId`] under which assets should be found. - pub account_id: EvaluatesTo, + pub account_id: AccountId, } /// [`FindAssetsByAssetDefinitionId`] Iroha Query gets [`AssetDefinitionId`] as input and @@ -674,7 +609,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetsByAssetDefinitionId { /// [`AssetDefinitionId`] with type of [`Asset`]s should be found. - pub asset_definition_id: EvaluatesTo, + pub asset_definition_id: AssetDefinitionId, } /// [`FindAssetsByDomainId`] Iroha Query gets [`Domain`]s id as input and @@ -686,7 +621,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetsByDomainId { /// `Id` of the domain under which assets should be found. - pub domain_id: EvaluatesTo, + pub domain_id: DomainId, } /// [`FindAssetsByDomainIdAndAssetDefinitionId`] Iroha Query gets [`DomainId`] and @@ -697,9 +632,9 @@ pub mod asset { #[ffi_type] pub struct FindAssetsByDomainIdAndAssetDefinitionId { /// `Id` of the domain under which assets should be found. - pub domain_id: EvaluatesTo, + pub domain_id: DomainId, /// [`AssetDefinitionId`] assets of which type should be found. - pub asset_definition_id: EvaluatesTo, + pub asset_definition_id: AssetDefinitionId, } /// [`FindAssetQuantityById`] Iroha Query gets [`AssetId`] as input and finds [`Asset::quantity`] @@ -711,7 +646,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindAssetQuantityById { /// `Id` of an [`Asset`] to find quantity of. - pub id: EvaluatesTo, + pub id: AssetId, } /// [`FindTotalAssetQuantityByAssetDefinitionId`] Iroha Query gets [`AssetDefinitionId`] as input and finds total [`Asset::quantity`] @@ -724,7 +659,7 @@ pub mod asset { #[ffi_type(unsafe {robust})] pub struct FindTotalAssetQuantityByAssetDefinitionId { /// `Id` of an [`Asset`] to find quantity of. - pub id: EvaluatesTo, + pub id: AssetDefinitionId, } /// [`FindAssetKeyValueByIdAndKey`] Iroha Query gets [`AssetId`] and key as input and finds [`Value`] @@ -734,9 +669,9 @@ pub mod asset { #[ffi_type] pub struct FindAssetKeyValueByIdAndKey { /// `Id` of an [`Asset`] acting as [`Store`](crate::asset::AssetValue::Store). - pub id: EvaluatesTo, + pub id: AssetId, /// The key of the key-value pair stored in the asset. - pub key: EvaluatesTo, + pub key: Name, } /// [`FindAssetDefinitionKeyValueByIdAndKey`] Iroha Query gets [`AssetDefinitionId`] and key as input and finds [`Value`] @@ -746,9 +681,9 @@ pub mod asset { #[ffi_type] pub struct FindAssetDefinitionKeyValueByIdAndKey { /// `Id` of an [`Asset`] acting as [`Store`](crate::asset::AssetValue::Store).. - pub id: EvaluatesTo, + pub id: AssetDefinitionId, /// The key of the key-value pair stored in the asset. - pub key: EvaluatesTo, + pub key: Name, } } @@ -804,104 +739,6 @@ pub mod asset { type Output = MetadataValue; } - impl FindAssetById { - /// Construct [`FindAssetById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindAssetDefinitionById { - /// Construct [`FindAssetDefinitionById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindAssetsByName { - /// Construct [`FindAssetsByName`]. - pub fn new(name: impl Into>) -> Self { - Self { name: name.into() } - } - } - - impl FindAssetsByAccountId { - /// Construct [`FindAssetsByAccountId`]. - pub fn new(account_id: impl Into>) -> Self { - Self { - account_id: account_id.into(), - } - } - } - - impl FindAssetsByAssetDefinitionId { - /// Construct [`FindAssetsByAssetDefinitionId`]. - pub fn new(asset_definition_id: impl Into>) -> Self { - Self { - asset_definition_id: asset_definition_id.into(), - } - } - } - - impl FindAssetsByDomainId { - /// Construct [`FindAssetsByDomainId`]. - pub fn new(domain_id: impl Into>) -> Self { - Self { - domain_id: domain_id.into(), - } - } - } - - impl FindAssetsByDomainIdAndAssetDefinitionId { - /// Construct [`FindAssetsByDomainIdAndAssetDefinitionId`]. - pub fn new( - domain_id: impl Into>, - asset_definition_id: impl Into>, - ) -> Self { - Self { - domain_id: domain_id.into(), - asset_definition_id: asset_definition_id.into(), - } - } - } - - impl FindAssetQuantityById { - /// Construct [`FindAssetQuantityById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindTotalAssetQuantityByAssetDefinitionId { - /// Construct [`FindTotalAssetQuantityByAssetDefinitionId`] - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindAssetKeyValueByIdAndKey { - /// Construct [`FindAssetKeyValueByIdAndKey`]. - pub fn new(id: impl Into>, key: impl Into>) -> Self { - Self { - id: id.into(), - key: key.into(), - } - } - } - - impl FindAssetDefinitionKeyValueByIdAndKey { - /// Construct [`FindAssetDefinitionKeyValueByIdAndKey`]. - pub fn new( - id: impl Into>, - key: impl Into>, - ) -> Self { - Self { - id: id.into(), - key: key.into(), - } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{ @@ -942,10 +779,9 @@ pub mod domain { #[ffi_type(unsafe {robust})] pub struct FindDomainById { /// `Id` of the domain to find. - pub id: EvaluatesTo, + pub id: DomainId, } - /// [`FindDomainKeyValueByIdAndKey`] Iroha Query finds a [`Value`] of the key-value metadata pair /// in the specified domain. #[derive(Display)] @@ -953,9 +789,9 @@ pub mod domain { #[ffi_type] pub struct FindDomainKeyValueByIdAndKey { /// `Id` of an domain to find. - pub id: EvaluatesTo, + pub id: DomainId, /// Key of the specific key-value in the domain's metadata. - pub key: EvaluatesTo, + pub key: Name, } } @@ -971,26 +807,6 @@ pub mod domain { type Output = MetadataValue; } - impl FindDomainById { - /// Construct [`FindDomainById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindDomainKeyValueByIdAndKey { - /// Construct [`FindDomainKeyValueByIdAndKey`]. - pub fn new( - id: impl Into>, - key: impl Into>, - ) -> Self { - Self { - id: id.into(), - key: key.into(), - } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{FindAllDomains, FindDomainById, FindDomainKeyValueByIdAndKey}; @@ -1015,7 +831,6 @@ pub mod peer { #[ffi_type] pub struct FindAllPeers; - /// [`FindAllParameters`] Iroha Query finds all [`Peer`]s parameters. #[derive(Copy, Display)] #[display(fmt = "Find all peers parameters")] @@ -1049,8 +864,7 @@ pub mod trigger { use crate::{ domain::prelude::*, events::TriggeringFilterBox, - expression::EvaluatesTo, - prelude::InstructionExpr, + prelude::InstructionBox, trigger::{Trigger, TriggerId}, Executable, Identifiable, Name, Value, }; @@ -1063,7 +877,6 @@ pub mod trigger { #[ffi_type] pub struct FindAllActiveTriggerIds; - /// Find Trigger given its ID. #[derive(Display)] #[display(fmt = "Find `{id}` trigger")] @@ -1072,7 +885,7 @@ pub mod trigger { #[ffi_type(unsafe {robust})] pub struct FindTriggerById { /// The Identification of the trigger to be found. - pub id: EvaluatesTo, + pub id: TriggerId, } @@ -1082,9 +895,9 @@ pub mod trigger { #[ffi_type] pub struct FindTriggerKeyValueByIdAndKey { /// The Identification of the trigger to be found. - pub id: EvaluatesTo, + pub id: TriggerId, /// The key inside the metadata dictionary to be returned. - pub key: EvaluatesTo, + pub key: Name, } @@ -1096,7 +909,7 @@ pub mod trigger { #[ffi_type(unsafe {robust})] pub struct FindTriggersByDomainId { /// [`DomainId`] specifies the domain in which to search for triggers. - pub domain_id: EvaluatesTo, + pub domain_id: DomainId, } } @@ -1116,35 +929,6 @@ pub mod trigger { type Output = Vec>; } - impl FindTriggerById { - /// Construct [`FindTriggerById`]. - pub fn new(id: impl Into>) -> Self { - Self { id: id.into() } - } - } - - impl FindTriggerKeyValueByIdAndKey { - /// Construct [`FindTriggerKeyValueByIdAndKey`]. - pub fn new( - id: impl Into>, - key: impl Into>, - ) -> Self { - Self { - id: id.into(), - key: key.into(), - } - } - } - - impl FindTriggersByDomainId { - /// Construct [`FindTriggersByDomainId`]. - pub fn new(domain_id: impl Into>) -> Self { - Self { - domain_id: domain_id.into(), - } - } - } - pub mod prelude { //! Prelude Re-exports most commonly used traits, structs and macros from this crate. pub use super::{ @@ -1166,10 +950,7 @@ pub mod transaction { use iroha_crypto::HashOf; use super::{Query, TransactionQueryOutput}; - use crate::{ - account::AccountId, expression::EvaluatesTo, prelude::Account, - transaction::SignedTransaction, - }; + use crate::{account::AccountId, prelude::Account, transaction::SignedTransaction}; queries! { /// [`FindAllTransactions`] Iroha Query lists all transactions included in a blockchain @@ -1187,19 +968,19 @@ pub mod transaction { #[ffi_type(unsafe {robust})] pub struct FindTransactionsByAccountId { /// Signer's [`AccountId`] under which transactions should be found. - pub account_id: EvaluatesTo, + pub account_id: AccountId, } /// [`FindTransactionByHash`] Iroha Query finds a transaction (if any) /// with corresponding hash value - #[derive(Display)] + #[derive(Copy, Display)] #[display(fmt = "Find transaction with `{hash}` hash")] #[repr(transparent)] // SAFETY: `FindTransactionByHash` has no trap representation in `EvaluatesTo>` #[ffi_type(unsafe {robust})] pub struct FindTransactionByHash { /// Transaction hash. - pub hash: EvaluatesTo>, + pub hash: HashOf, } } @@ -1215,22 +996,6 @@ pub mod transaction { type Output = TransactionQueryOutput; } - impl FindTransactionsByAccountId { - /// Construct [`FindTransactionsByAccountId`]. - pub fn new(account_id: impl Into>) -> Self { - Self { - account_id: account_id.into(), - } - } - } - - impl FindTransactionByHash { - /// Construct [`FindTransactionByHash`]. - pub fn new(hash: impl Into>>) -> Self { - Self { hash: hash.into() } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{FindAllTransactions, FindTransactionByHash, FindTransactionsByAccountId}; @@ -1249,10 +1014,7 @@ pub mod block { use iroha_crypto::HashOf; use super::Query; - use crate::{ - block::{BlockHeader, SignedBlock}, - prelude::EvaluatesTo, - }; + use crate::block::{BlockHeader, SignedBlock}; queries! { /// [`FindAllBlocks`] Iroha Query lists all blocks sorted by @@ -1270,14 +1032,14 @@ pub mod block { pub struct FindAllBlockHeaders; /// [`FindBlockHeaderByHash`] Iroha Query finds block header by block hash - #[derive(Display)] + #[derive(Copy, Display)] #[display(fmt = "Find block header with `{hash}` hash")] #[repr(transparent)] // SAFETY: `FindBlockHeaderByHash` has no trap representation in `EvaluatesTo>` #[ffi_type(unsafe {robust})] pub struct FindBlockHeaderByHash { /// Block hash. - pub hash: EvaluatesTo>, + pub hash: HashOf, } } @@ -1293,13 +1055,6 @@ pub mod block { type Output = BlockHeader; } - impl FindBlockHeaderByHash { - /// Construct [`FindBlockHeaderByHash`]. - pub fn new(hash: impl Into>>) -> Self { - Self { hash: hash.into() } - } - } - /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { pub use super::{FindAllBlockHeaders, FindAllBlocks, FindBlockHeaderByHash}; @@ -1538,12 +1293,6 @@ pub mod error { #[skip_try_from] String, ), - /// Query has a malformed expression: {0} - Evaluate( - #[skip_from] - #[skip_try_from] - String, - ), /// {0} #[cfg_attr(feature = "std", error(transparent))] Find(FindError), diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index a7742873c7a..9d288239636 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -22,7 +22,7 @@ use serde::{Deserialize, Serialize}; pub use self::model::*; use crate::{ account::AccountId, - isi::{Instruction, InstructionExpr}, + isi::{Instruction, InstructionBox}, metadata::UnlimitedMetadata, name::Name, Value, @@ -51,7 +51,7 @@ pub mod model { pub enum Executable { /// Ordered set of instructions. #[debug(fmt = "{_0:?}")] - Instructions(Vec), + Instructions(Vec), /// WebAssembly smartcontract Wasm(WasmSmartContract), } @@ -533,7 +533,7 @@ pub mod error { pub struct InstructionExecutionFail { /// Instruction for which execution failed #[getset(get = "pub")] - pub instruction: InstructionExpr, + pub instruction: InstructionBox, /// Error which happened during execution pub reason: String, } @@ -611,15 +611,12 @@ pub mod error { impl Display for InstructionExecutionFail { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - use InstructionExpr::*; + use InstructionBox::*; let kind = match self.instruction { Burn(_) => "burn", Fail(_) => "fail", - If(_) => "if", Mint(_) => "mint", - Pair(_) => "pair", Register(_) => "register", - Sequence(_) => "sequence", Transfer(_) => "transfer", Unregister(_) => "un-register", SetKeyValue(_) => "set key-value pair", @@ -691,7 +688,7 @@ mod http { creation_time_ms, nonce: None, time_to_live_ms: None, - instructions: Vec::::new().into(), + instructions: Vec::::new().into(), metadata: UnlimitedMetadata::new(), }, } @@ -707,7 +704,7 @@ mod http { self.payload.instructions = instructions .into_iter() .map(Into::into) - .collect::>() + .collect::>() .into(); self } diff --git a/data_model/src/trigger.rs b/data_model/src/trigger.rs index 0127ce3bf77..4270d2d34ac 100644 --- a/data_model/src/trigger.rs +++ b/data_model/src/trigger.rs @@ -203,7 +203,7 @@ pub mod action { /// Enumeration of possible repetitions schemes. #[derive( - Debug, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, + Debug, Copy, Clone, PartialEq, Eq, Decode, Encode, Deserialize, Serialize, IntoSchema, )] #[ffi_type] pub enum Repeats { diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index 62a33f328c8..9f82b19baae 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -6,7 +6,7 @@ use alloc::format; use iroha_crypto::PublicKey; -use crate::{evaluate::ExpressionEvaluator, isi::Log, prelude::*, NumericValue}; +use crate::{isi::Log, prelude::*}; macro_rules! delegate { ( $($visitor:ident $(<$param:ident $(: $bound:path)?>)?($operation:ty)),+ $(,)? ) => { $( @@ -16,54 +16,36 @@ macro_rules! delegate { }; } -macro_rules! evaluate_expr { - ($visitor:ident, $authority:ident, <$isi:ident as $isi_type:ty>::$field:ident()) => {{ - $visitor.visit_expression($authority, $isi.$field()); - - $visitor.evaluate($isi.$field()).expect(&format!( - "Failed to evaluate field '{}::{}'", - stringify!($isi_type), - stringify!($field), - )) - }}; -} - /// Trait to validate Iroha entities. /// Default implementation of non-leaf visitors runs `visit_` functions for leafs. /// Default implementation for leaf visitors is blank. /// /// This trait is based on the visitor pattern. -pub trait Visit: ExpressionEvaluator { +pub trait Visit { delegate! { - visit_unsupported(T), - // Visit SignedTransaction visit_transaction(&SignedTransaction), - visit_instruction(&InstructionExpr), - visit_expression(&EvaluatesTo), + visit_instruction(&InstructionBox), visit_wasm(&WasmSmartContract), visit_query(&QueryBox), - // Visit InstructionExpr - visit_burn(&BurnExpr), + // Visit InstructionBox + visit_burn(&BurnBox), visit_fail(&Fail), - visit_grant(&GrantExpr), - visit_if(&ConditionalExpr), - visit_mint(&MintExpr), - visit_pair(&PairExpr), - visit_register(&RegisterExpr), - visit_remove_key_value(&RemoveKeyValueExpr), - visit_revoke(&RevokeExpr), - visit_sequence(&SequenceExpr), - visit_set_key_value(&SetKeyValueExpr), - visit_transfer(&TransferExpr), - visit_unregister(&UnregisterExpr), - visit_upgrade(&UpgradeExpr), - - visit_execute_trigger(ExecuteTrigger), - visit_new_parameter(NewParameter), - visit_set_parameter(SetParameter), - visit_log(Log), + visit_grant(&GrantBox), + visit_mint(&MintBox), + visit_register(&RegisterBox), + visit_remove_key_value(&RemoveKeyValueBox), + visit_revoke(&RevokeBox), + visit_set_key_value(&SetKeyValueBox), + visit_transfer(&TransferBox), + visit_unregister(&UnregisterBox), + visit_upgrade(&Upgrade), + + visit_execute_trigger(&ExecuteTrigger), + visit_new_parameter(&NewParameter), + visit_set_parameter(&SetParameter), + visit_log(&Log), // Visit QueryBox visit_find_account_by_id(&FindAccountById), @@ -78,7 +60,7 @@ pub trait Visit: ExpressionEvaluator { visit_find_all_block_headers(&FindAllBlockHeaders), visit_find_all_blocks(&FindAllBlocks), visit_find_all_domains(&FindAllDomains), - visit_find_all_parammeters(&FindAllParameters), + visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), visit_find_permission_token_schema(&FindPermissionTokenSchema), visit_find_all_role_ids(&FindAllRoleIds), @@ -107,74 +89,69 @@ pub trait Visit: ExpressionEvaluator { visit_find_trigger_key_value_by_id_and_key(&FindTriggerKeyValueByIdAndKey), visit_find_triggers_by_domain_id(&FindTriggersByDomainId), - // Visit RegisterExpr - visit_register_peer(Register), - visit_register_domain(Register), - visit_register_account(Register), - visit_register_asset_definition(Register), - visit_register_asset(Register), - visit_register_role(Register), - visit_register_trigger(Register>), - - // Visit UnregisterExpr - visit_unregister_peer(Unregister), - visit_unregister_domain(Unregister), - visit_unregister_account(Unregister), - visit_unregister_asset_definition(Unregister), - visit_unregister_asset(Unregister), + // Visit RegisterBox + visit_register_peer(&Register), + visit_register_domain(&Register), + visit_register_account(&Register), + visit_register_asset_definition(&Register), + visit_register_asset(&Register), + visit_register_role(&Register), + visit_register_trigger(&Register>), + + // Visit UnregisterBox + visit_unregister_peer(&Unregister), + visit_unregister_domain(&Unregister), + visit_unregister_account(&Unregister), + visit_unregister_asset_definition(&Unregister), + visit_unregister_asset(&Unregister), // TODO: Need to allow role creator to unregister it somehow - visit_unregister_role(Unregister), - visit_unregister_trigger(Unregister>), - - // Visit MintExpr - visit_mint_asset(Mint), - visit_mint_account_public_key(Mint), - visit_mint_account_signature_check_condition(Mint), - visit_mint_trigger_repetitions(Mint>), - - // Visit BurnExpr - visit_burn_account_public_key(Burn), - visit_burn_asset(Burn), - visit_burn_trigger_repetitions(Burn>), - - // Visit TransferExpr - visit_transfer_asset_definition(Transfer), - visit_transfer_asset(Transfer), - visit_transfer_domain(Transfer), - - // Visit SetKeyValueExpr - visit_set_domain_key_value(SetKeyValue), - visit_set_account_key_value(SetKeyValue), - visit_set_asset_definition_key_value(SetKeyValue), - visit_set_asset_key_value(SetKeyValue), - - // Visit RemoveKeyValueExpr - visit_remove_domain_key_value(RemoveKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - - // Visit GrantExpr - visit_grant_account_permission(Grant), - visit_grant_account_role(Grant), - - // Visit RevokeExpr - visit_revoke_account_permission(Revoke), - visit_revoke_account_role(Revoke), - - // Visit UpgradeExpr - visit_upgrade_executor(Upgrade), + visit_unregister_role(&Unregister), + visit_unregister_trigger(&Unregister>), + + // Visit MintBox + visit_mint_asset_quantity(&Mint), + visit_mint_asset_big_quantity(&Mint), + visit_mint_asset_fixed(&Mint), + visit_mint_account_public_key(&Mint), + visit_mint_account_signature_check_condition(&Mint), + visit_mint_trigger_repetitions(&Mint>), + + // Visit BurnBox + visit_burn_account_public_key(&Burn), + visit_burn_asset_quantity(&Burn), + visit_burn_asset_big_quantity(&Burn), + visit_burn_asset_fixed(&Burn), + visit_burn_trigger_repetitions(&Burn>), + + // Visit TransferBox + visit_transfer_asset_definition(&Transfer), + visit_transfer_asset_quantity(&Transfer), + visit_transfer_asset_big_quantity(&Transfer), + visit_transfer_asset_fixed(&Transfer), + visit_transfer_domain(&Transfer), + + // Visit SetKeyValueBox + visit_set_domain_key_value(&SetKeyValue), + visit_set_account_key_value(&SetKeyValue), + visit_set_asset_definition_key_value(&SetKeyValue), + visit_set_asset_key_value(&SetKeyValue), + + // Visit RemoveKeyValueBox + visit_remove_domain_key_value(&RemoveKeyValue), + visit_remove_account_key_value(&RemoveKeyValue), + visit_remove_asset_definition_key_value(&RemoveKeyValue), + visit_remove_asset_key_value(&RemoveKeyValue), + + // Visit GrantBox + visit_grant_account_permission(&Grant), + visit_grant_account_role(&Grant), + + // Visit RevokeBox + visit_revoke_account_permission(&Revoke), + visit_revoke_account_role(&Revoke), } } -/// Called when visiting any unsupported syntax tree node -fn visit_unsupported( - _visitor: &mut V, - _authority: &AccountId, - _isi: T, -) { -} - pub fn visit_transaction( visitor: &mut V, authority: &AccountId, @@ -213,7 +190,7 @@ pub fn visit_query(visitor: &mut V, authority: &AccountId, qu visit_find_all_block_headers(FindAllBlockHeaders), visit_find_all_blocks(FindAllBlocks), visit_find_all_domains(FindAllDomains), - visit_find_all_parammeters(FindAllParameters), + visit_find_all_parameters(FindAllParameters), visit_find_all_peers(FindAllPeers), visit_find_permission_token_schema(FindPermissionTokenSchema), visit_find_all_role_ids(FindAllRoleIds), @@ -251,7 +228,7 @@ pub fn visit_wasm( ) { } -/// Default validation for [`InstructionExpr`]. +/// Default validation for [`InstructionBox`]. /// /// # Warning /// @@ -259,430 +236,167 @@ pub fn visit_wasm( pub fn visit_instruction( visitor: &mut V, authority: &AccountId, - isi: &InstructionExpr, -) { - macro_rules! isi_visitors { - ( $($visitor:ident($isi:ident)),+ $(,)? ) => { - match isi { - InstructionExpr::NewParameter(isi) => { - let parameter = evaluate_expr!(visitor, authority, ::parameter()); - visitor.visit_new_parameter(authority, NewParameter{parameter}); - } - InstructionExpr::SetParameter(isi) => { - let parameter = evaluate_expr!(visitor, authority, ::parameter()); - visitor.visit_set_parameter(authority, SetParameter{parameter}); - } - InstructionExpr::ExecuteTrigger(isi) => { - let trigger_id = evaluate_expr!(visitor, authority, ::trigger_id()); - visitor.visit_execute_trigger(authority, ExecuteTrigger{trigger_id}); - } - InstructionExpr::Log(isi) => { - let msg = evaluate_expr!(visitor, authority, ::msg()); - let level = evaluate_expr!(visitor, authority, ::level()); - visitor.visit_log(authority, Log { msg, level }); - } $( - InstructionExpr::$isi(isi) => { - visitor.$visitor(authority, isi); - } )+ - } - }; - } - - isi_visitors! { - visit_burn(Burn), - visit_fail(Fail), - visit_grant(Grant), - visit_mint(Mint), - visit_register(Register), - visit_remove_key_value(RemoveKeyValue), - visit_revoke(Revoke), - visit_set_key_value(SetKeyValue), - visit_transfer(Transfer), - visit_unregister(Unregister), - visit_upgrade(Upgrade), - visit_sequence(Sequence), - visit_pair(Pair), - visit_if(If), - } -} - -pub fn visit_expression( - visitor: &mut V, - authority: &AccountId, - expression: &EvaluatesTo, + isi: &InstructionBox, ) { - macro_rules! visit_binary_math_expression { - ($e:ident) => {{ - visitor.visit_expression(authority, $e.left()); - visitor.visit_expression(authority, $e.right()) - }}; - } - - macro_rules! visit_binary_bool_expression { - ($e:ident) => {{ - visitor.visit_expression(authority, $e.left()); - visitor.visit_expression(authority, $e.right()) - }}; - } - - match expression.expression() { - Expression::Add(expr) => visit_binary_math_expression!(expr), - Expression::Subtract(expr) => visit_binary_math_expression!(expr), - Expression::Multiply(expr) => visit_binary_math_expression!(expr), - Expression::Divide(expr) => visit_binary_math_expression!(expr), - Expression::Mod(expr) => visit_binary_math_expression!(expr), - Expression::RaiseTo(expr) => visit_binary_math_expression!(expr), - Expression::Greater(expr) => visit_binary_math_expression!(expr), - Expression::Less(expr) => visit_binary_math_expression!(expr), - Expression::Equal(expr) => visit_binary_bool_expression!(expr), - Expression::Not(expr) => visitor.visit_expression(authority, expr.expression()), - Expression::And(expr) => visit_binary_bool_expression!(expr), - Expression::Or(expr) => visit_binary_bool_expression!(expr), - Expression::If(expr) => { - visitor.visit_expression(authority, expr.condition()); - visitor.visit_expression(authority, expr.then()); - visitor.visit_expression(authority, expr.otherwise()); + match isi { + InstructionBox::NewParameter(variant_value) => { + visitor.visit_new_parameter(authority, variant_value) } - Expression::Contains(expr) => { - visitor.visit_expression(authority, expr.collection()); - visitor.visit_expression(authority, expr.element()); + InstructionBox::SetParameter(variant_value) => { + visitor.visit_set_parameter(authority, variant_value) } - Expression::ContainsAll(expr) => { - visitor.visit_expression(authority, expr.collection()); - visitor.visit_expression(authority, expr.elements()); + InstructionBox::ExecuteTrigger(variant_value) => { + visitor.visit_execute_trigger(authority, variant_value) } - Expression::ContainsAny(expr) => { - visitor.visit_expression(authority, expr.collection()); - visitor.visit_expression(authority, expr.elements()); + InstructionBox::Log(variant_value) => visitor.visit_log(authority, variant_value), + InstructionBox::Burn(variant_value) => visitor.visit_burn(authority, variant_value), + InstructionBox::Fail(variant_value) => visitor.visit_fail(authority, variant_value), + InstructionBox::Grant(variant_value) => visitor.visit_grant(authority, variant_value), + InstructionBox::Mint(variant_value) => visitor.visit_mint(authority, variant_value), + InstructionBox::Register(variant_value) => visitor.visit_register(authority, variant_value), + InstructionBox::RemoveKeyValue(variant_value) => { + visitor.visit_remove_key_value(authority, variant_value) } - Expression::Where(expr) => visitor.visit_expression(authority, expr.expression()), - Expression::Query(query) => visitor.visit_query(authority, query), - Expression::ContextValue(_) | Expression::Raw(_) => {} + InstructionBox::Revoke(variant_value) => visitor.visit_revoke(authority, variant_value), + InstructionBox::SetKeyValue(variant_value) => { + visitor.visit_set_key_value(authority, variant_value) + } + InstructionBox::Transfer(variant_value) => visitor.visit_transfer(authority, variant_value), + InstructionBox::Unregister(variant_value) => { + visitor.visit_unregister(authority, variant_value) + } + InstructionBox::Upgrade(variant_value) => visitor.visit_upgrade(authority, variant_value), } } pub fn visit_register( visitor: &mut V, authority: &AccountId, - isi: &RegisterExpr, + isi: &RegisterBox, ) { - macro_rules! match_all { - ( $( $visitor:ident($object:ident) ),+ $(,)? ) => { - let object = evaluate_expr!(visitor, authority, ::object()); - - match object { $( - RegistrableBox::$object(object) => visitor.$visitor(authority, Register{object}), )+ - } - }; - } - - match_all! { - visit_register_peer(Peer), - visit_register_domain(Domain), - visit_register_account(Account), - visit_register_asset_definition(AssetDefinition), - visit_register_asset(Asset), - visit_register_role(Role), - visit_register_trigger(Trigger), + match isi { + RegisterBox::Peer(obj) => visitor.visit_register_peer(authority, obj), + RegisterBox::Domain(obj) => visitor.visit_register_domain(authority, obj), + RegisterBox::Account(obj) => visitor.visit_register_account(authority, obj), + RegisterBox::AssetDefinition(obj) => { + visitor.visit_register_asset_definition(authority, obj) + } + RegisterBox::Asset(obj) => visitor.visit_register_asset(authority, obj), + RegisterBox::Role(obj) => visitor.visit_register_role(authority, obj), + RegisterBox::Trigger(obj) => visitor.visit_register_trigger(authority, obj), } } pub fn visit_unregister( visitor: &mut V, authority: &AccountId, - isi: &UnregisterExpr, + isi: &UnregisterBox, ) { - macro_rules! match_all { - ( $( $visitor:ident($id:ident) ),+ $(,)? ) => { - let object_id = evaluate_expr!(visitor, authority, ::object_id()); - match object_id { $( - IdBox::$id(object_id) => visitor.$visitor(authority, Unregister{object_id}), )+ - _ => visitor.visit_unsupported(authority, isi), - } - }; - } - - match_all! { - visit_unregister_peer(PeerId), - visit_unregister_domain(DomainId), - visit_unregister_account(AccountId), - visit_unregister_asset_definition(AssetDefinitionId), - visit_unregister_asset(AssetId), - visit_unregister_role(RoleId), - visit_unregister_trigger(TriggerId), + match isi { + UnregisterBox::Peer(obj) => visitor.visit_unregister_peer(authority, obj), + UnregisterBox::Domain(obj) => visitor.visit_unregister_domain(authority, obj), + UnregisterBox::Account(obj) => visitor.visit_unregister_account(authority, obj), + UnregisterBox::AssetDefinition(obj) => { + visitor.visit_unregister_asset_definition(authority, obj) + } + UnregisterBox::Asset(obj) => visitor.visit_unregister_asset(authority, obj), + UnregisterBox::Role(obj) => visitor.visit_unregister_role(authority, obj), + UnregisterBox::Trigger(obj) => visitor.visit_unregister_trigger(authority, obj), } } -pub fn visit_mint(visitor: &mut V, authority: &AccountId, isi: &MintExpr) { - let destination_id = evaluate_expr!(visitor, authority, ::destination_id()); - let object = evaluate_expr!(visitor, authority, ::object()); - - match (destination_id, object) { - (IdBox::AssetId(destination_id), Value::Numeric(object)) => visitor.visit_mint_asset( - authority, - Mint { - object, - destination_id, - }, - ), - (IdBox::AccountId(destination_id), Value::PublicKey(object)) => visitor - .visit_mint_account_public_key( - authority, - Mint { - object, - destination_id, - }, - ), - (IdBox::AccountId(destination_id), Value::SignatureCheckCondition(object)) => visitor - .visit_mint_account_signature_check_condition( - authority, - Mint { - object, - destination_id, - }, - ), - (IdBox::TriggerId(destination_id), Value::Numeric(NumericValue::U32(object))) => visitor - .visit_mint_trigger_repetitions( - authority, - Mint { - object, - destination_id, - }, - ), - _ => visitor.visit_unsupported(authority, isi), +pub fn visit_mint(visitor: &mut V, authority: &AccountId, isi: &MintBox) { + match isi { + MintBox::Account(mint_account) => match mint_account { + AccountMintBox::PublicKey(obj) => visitor.visit_mint_account_public_key(authority, obj), + AccountMintBox::SignatureCheckCondition(obj) => { + visitor.visit_mint_account_signature_check_condition(authority, obj) + } + }, + MintBox::Asset(mint_asset) => match mint_asset { + AssetMintBox::Quantity(obj) => visitor.visit_mint_asset_quantity(authority, obj), + AssetMintBox::BigQuantity(obj) => visitor.visit_mint_asset_big_quantity(authority, obj), + AssetMintBox::Fixed(obj) => visitor.visit_mint_asset_fixed(authority, obj), + }, + MintBox::TriggerRepetitions(obj) => visitor.visit_mint_trigger_repetitions(authority, obj), } } -pub fn visit_burn(visitor: &mut V, authority: &AccountId, isi: &BurnExpr) { - let destination_id = evaluate_expr!(visitor, authority, ::destination_id()); - let object = evaluate_expr!(visitor, authority, ::object()); - - match (destination_id, object) { - (IdBox::AssetId(destination_id), Value::Numeric(object)) => visitor.visit_burn_asset( - authority, - Burn { - object, - destination_id, - }, - ), - (IdBox::AccountId(destination_id), Value::PublicKey(object)) => visitor - .visit_burn_account_public_key( - authority, - Burn { - object, - destination_id, - }, - ), - (IdBox::TriggerId(destination_id), Value::Numeric(NumericValue::U32(object))) => visitor - .visit_burn_trigger_repetitions( - authority, - Burn { - object, - destination_id, - }, - ), - _ => visitor.visit_unsupported(authority, isi), +pub fn visit_burn(visitor: &mut V, authority: &AccountId, isi: &BurnBox) { + match isi { + BurnBox::AccountPublicKey(obj) => visitor.visit_burn_account_public_key(authority, obj), + BurnBox::Asset(burn_asset) => match burn_asset { + AssetBurnBox::Quantity(obj) => visitor.visit_burn_asset_quantity(authority, obj), + AssetBurnBox::BigQuantity(obj) => visitor.visit_burn_asset_big_quantity(authority, obj), + AssetBurnBox::Fixed(obj) => visitor.visit_burn_asset_fixed(authority, obj), + }, + BurnBox::TriggerRepetitions(obj) => visitor.visit_burn_trigger_repetitions(authority, obj), } } pub fn visit_transfer( visitor: &mut V, authority: &AccountId, - isi: &TransferExpr, + isi: &TransferBox, ) { - let object = evaluate_expr!(visitor, authority, ::object()); - let source_id = evaluate_expr!(visitor, authority, ::source_id()); - let destination_id = evaluate_expr!(visitor, authority, ::destination_id()); - - match (source_id, object, destination_id) { - (IdBox::AssetId(source_id), Value::Numeric(object), IdBox::AccountId(destination_id)) => { - visitor.visit_transfer_asset( - authority, - Transfer { - source_id, - object, - destination_id, - }, - ); + match isi { + TransferBox::Domain(obj) => visitor.visit_transfer_domain(authority, obj), + TransferBox::AssetDefinition(obj) => { + visitor.visit_transfer_asset_definition(authority, obj) } - ( - IdBox::AccountId(source_id), - Value::Id(IdBox::AssetDefinitionId(object)), - IdBox::AccountId(destination_id), - ) => visitor.visit_transfer_asset_definition( - authority, - Transfer { - source_id, - object, - destination_id, - }, - ), - ( - IdBox::AccountId(source_id), - Value::Id(IdBox::DomainId(object)), - IdBox::AccountId(destination_id), - ) => visitor.visit_transfer_domain( - authority, - Transfer { - source_id, - object, - destination_id, - }, - ), - _ => visitor.visit_unsupported(authority, isi), + TransferBox::Asset(transfer_asset) => match transfer_asset { + AssetTransferBox::Quantity(obj) => { + visitor.visit_transfer_asset_quantity(authority, obj) + } + AssetTransferBox::BigQuantity(obj) => { + visitor.visit_transfer_asset_big_quantity(authority, obj) + } + AssetTransferBox::Fixed(obj) => visitor.visit_transfer_asset_fixed(authority, obj), + }, } } pub fn visit_set_key_value( visitor: &mut V, authority: &AccountId, - isi: &SetKeyValueExpr, + isi: &SetKeyValueBox, ) { - let object_id = evaluate_expr!(visitor, authority, ::object_id()); - let key = evaluate_expr!(visitor, authority, ::key()); - let value = evaluate_expr!(visitor, authority, ::value()); - - match object_id { - IdBox::AssetId(object_id) => visitor.visit_set_asset_key_value( - authority, - SetKeyValue { - object_id, - key, - value, - }, - ), - IdBox::AssetDefinitionId(object_id) => visitor.visit_set_asset_definition_key_value( - authority, - SetKeyValue { - object_id, - key, - value, - }, - ), - IdBox::AccountId(object_id) => visitor.visit_set_account_key_value( - authority, - SetKeyValue { - object_id, - key, - value, - }, - ), - IdBox::DomainId(object_id) => visitor.visit_set_domain_key_value( - authority, - SetKeyValue { - object_id, - key, - value, - }, - ), - _ => visitor.visit_unsupported(authority, isi), + match isi { + SetKeyValueBox::Domain(obj) => visitor.visit_set_domain_key_value(authority, obj), + SetKeyValueBox::Account(obj) => visitor.visit_set_account_key_value(authority, obj), + SetKeyValueBox::AssetDefinition(obj) => { + visitor.visit_set_asset_definition_key_value(authority, obj) + } + SetKeyValueBox::Asset(obj) => visitor.visit_set_asset_key_value(authority, obj), } } pub fn visit_remove_key_value( visitor: &mut V, authority: &AccountId, - isi: &RemoveKeyValueExpr, + isi: &RemoveKeyValueBox, ) { - let object_id = evaluate_expr!(visitor, authority, ::object_id()); - let key = evaluate_expr!(visitor, authority, ::key()); - - match object_id { - IdBox::AssetId(object_id) => { - visitor.visit_remove_asset_key_value(authority, RemoveKeyValue { object_id, key }); - } - IdBox::AssetDefinitionId(object_id) => visitor - .visit_remove_asset_definition_key_value(authority, RemoveKeyValue { object_id, key }), - IdBox::AccountId(object_id) => { - visitor.visit_remove_account_key_value(authority, RemoveKeyValue { object_id, key }); - } - IdBox::DomainId(object_id) => { - visitor.visit_remove_domain_key_value(authority, RemoveKeyValue { object_id, key }); - } - _ => visitor.visit_unsupported(authority, isi), - } -} - -pub fn visit_grant(visitor: &mut V, authority: &AccountId, isi: &GrantExpr) { - let destination_id = evaluate_expr!(visitor, authority, ::destination_id()); - let object = evaluate_expr!(visitor, authority, ::object()); - - match object { - Value::PermissionToken(object) => visitor.visit_grant_account_permission( - authority, - Grant { - object, - destination_id, - }, - ), - Value::Id(IdBox::RoleId(object)) => visitor.visit_grant_account_role( - authority, - Grant { - object, - destination_id, - }, - ), - _ => visitor.visit_unsupported(authority, isi), - } -} - -pub fn visit_revoke(visitor: &mut V, authority: &AccountId, isi: &RevokeExpr) { - let destination_id = evaluate_expr!(visitor, authority, ::destination_id()); - let object = evaluate_expr!(visitor, authority, ::object()); - - match object { - Value::PermissionToken(object) => visitor.visit_revoke_account_permission( - authority, - Revoke { - object, - destination_id, - }, - ), - Value::Id(IdBox::RoleId(object)) => visitor.visit_revoke_account_role( - authority, - Revoke { - object, - destination_id, - }, - ), - _ => visitor.visit_unsupported(authority, isi), - } -} - -pub fn visit_upgrade(visitor: &mut V, authority: &AccountId, isi: &UpgradeExpr) { - let object = evaluate_expr!(visitor, authority, ::object()); - - match object { - UpgradableBox::Executor(object) => { - visitor.visit_upgrade_executor(authority, Upgrade { object }); + match isi { + RemoveKeyValueBox::Domain(obj) => visitor.visit_remove_domain_key_value(authority, obj), + RemoveKeyValueBox::Account(obj) => visitor.visit_remove_account_key_value(authority, obj), + RemoveKeyValueBox::AssetDefinition(obj) => { + visitor.visit_remove_asset_definition_key_value(authority, obj) } + RemoveKeyValueBox::Asset(obj) => visitor.visit_remove_asset_key_value(authority, obj), } } -pub fn visit_if(visitor: &mut V, authority: &AccountId, isi: &ConditionalExpr) { - let condition = evaluate_expr!(visitor, authority, ::condition()); - - // TODO: Should visit both by default or not? It will affect Executor behavior - // because only one branch needs to be executed. IMO both should be validated - if condition { - visitor.visit_instruction(authority, isi.then()); - } else if let Some(otherwise) = isi.otherwise() { - visitor.visit_instruction(authority, otherwise); +pub fn visit_grant(visitor: &mut V, authority: &AccountId, isi: &GrantBox) { + match isi { + GrantBox::PermissionToken(obj) => visitor.visit_grant_account_permission(authority, obj), + GrantBox::Role(obj) => visitor.visit_grant_account_role(authority, obj), } } -pub fn visit_pair(visitor: &mut V, authority: &AccountId, isi: &PairExpr) { - visitor.visit_instruction(authority, isi.left_instruction()); - visitor.visit_instruction(authority, isi.right_instruction()); -} - -pub fn visit_sequence( - visitor: &mut V, - authority: &AccountId, - isi: &SequenceExpr, -) { - for instruction in isi.instructions() { - visitor.visit_instruction(authority, instruction); +pub fn visit_revoke(visitor: &mut V, authority: &AccountId, isi: &RevokeBox) { + match isi { + RevokeBox::PermissionToken(obj) => visitor.visit_revoke_account_permission(authority, obj), + RevokeBox::Role(obj) => visitor.visit_revoke_account_role(authority, obj), } } @@ -696,48 +410,54 @@ macro_rules! leaf_visitors { leaf_visitors! { // Instruction visitors - visit_register_account(Register), - visit_unregister_account(Unregister), - visit_mint_account_public_key(Mint), - visit_burn_account_public_key(Burn), - visit_mint_account_signature_check_condition(Mint), - visit_set_account_key_value(SetKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - visit_register_asset(Register), - visit_unregister_asset(Unregister), - visit_mint_asset(Mint), - visit_burn_asset(Burn), - visit_transfer_asset(Transfer), - visit_set_asset_key_value(SetKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - visit_register_asset_definition(Register), - visit_unregister_asset_definition(Unregister), - visit_transfer_asset_definition(Transfer), - visit_set_asset_definition_key_value(SetKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - visit_register_domain(Register), - visit_unregister_domain(Unregister), - visit_transfer_domain(Transfer), - visit_set_domain_key_value(SetKeyValue), - visit_remove_domain_key_value(RemoveKeyValue), - visit_register_peer(Register), - visit_unregister_peer(Unregister), - visit_grant_account_permission(Grant), - visit_revoke_account_permission(Revoke), - visit_register_role(Register), - visit_unregister_role(Unregister), - visit_grant_account_role(Grant), - visit_revoke_account_role(Revoke), - visit_register_trigger(Register>), - visit_unregister_trigger(Unregister>), - visit_mint_trigger_repetitions(Mint>), - visit_burn_trigger_repetitions(Burn>), - visit_upgrade_executor(Upgrade), - visit_new_parameter(NewParameter), - visit_set_parameter(SetParameter), - visit_execute_trigger(ExecuteTrigger), + visit_register_account(&Register), + visit_unregister_account(&Unregister), + visit_mint_account_public_key(&Mint), + visit_burn_account_public_key(&Burn), + visit_mint_account_signature_check_condition(&Mint), + visit_set_account_key_value(&SetKeyValue), + visit_remove_account_key_value(&RemoveKeyValue), + visit_register_asset(&Register), + visit_unregister_asset(&Unregister), + visit_mint_asset_quantity(&Mint), + visit_burn_asset_quantity(&Burn), + visit_mint_asset_big_quantity(&Mint), + visit_burn_asset_big_quantity(&Burn), + visit_mint_asset_fixed(&Mint), + visit_burn_asset_fixed(&Burn), + visit_transfer_asset_quantity(&Transfer), + visit_transfer_asset_big_quantity(&Transfer), + visit_transfer_asset_fixed(&Transfer), + visit_set_asset_key_value(&SetKeyValue), + visit_remove_asset_key_value(&RemoveKeyValue), + visit_register_asset_definition(&Register), + visit_unregister_asset_definition(&Unregister), + visit_transfer_asset_definition(&Transfer), + visit_set_asset_definition_key_value(&SetKeyValue), + visit_remove_asset_definition_key_value(&RemoveKeyValue), + visit_register_domain(&Register), + visit_unregister_domain(&Unregister), + visit_transfer_domain(&Transfer), + visit_set_domain_key_value(&SetKeyValue), + visit_remove_domain_key_value(&RemoveKeyValue), + visit_register_peer(&Register), + visit_unregister_peer(&Unregister), + visit_grant_account_permission(&Grant), + visit_revoke_account_permission(&Revoke), + visit_register_role(&Register), + visit_unregister_role(&Unregister), + visit_grant_account_role(&Grant), + visit_revoke_account_role(&Revoke), + visit_register_trigger(&Register>), + visit_unregister_trigger(&Unregister>), + visit_mint_trigger_repetitions(&Mint>), + visit_burn_trigger_repetitions(&Burn>), + visit_upgrade(&Upgrade), + visit_new_parameter(&NewParameter), + visit_set_parameter(&SetParameter), + visit_execute_trigger(&ExecuteTrigger), visit_fail(&Fail), - visit_log(Log), + visit_log(&Log), // Query visitors visit_find_account_by_id(&FindAccountById), @@ -752,7 +472,7 @@ leaf_visitors! { visit_find_all_block_headers(&FindAllBlockHeaders), visit_find_all_blocks(&FindAllBlocks), visit_find_all_domains(&FindAllDomains), - visit_find_all_parammeters(&FindAllParameters), + visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), visit_find_permission_token_schema(&FindPermissionTokenSchema), visit_find_all_role_ids(&FindAllRoleIds), diff --git a/data_model/tests/data_model.rs b/data_model/tests/data_model.rs index 09cf1d602ff..c795f7590a4 100644 --- a/data_model/tests/data_model.rs +++ b/data_model/tests/data_model.rs @@ -1,65 +1,14 @@ -use std::str::FromStr as _; - use iroha_data_model::{prelude::*, ParseError}; #[test] fn transfer_isi_should_be_valid() { - let _instruction = TransferExpr::new( - IdBox::AssetId("btc##seller@crypto".parse().expect("Valid")), + let _instruction = Transfer::asset_quantity( + "btc##seller@crypto".parse().expect("Valid"), 12_u32, - IdBox::AccountId("buyer@crypto".parse().expect("Valid")), + "buyer@crypto".parse().expect("Valid"), ); } -#[test] -fn find_quantity_and_check_it_greater_than_value_isi_should_be_valid() { - let asset_id: AssetId = "rose##alice@wonderland".parse().expect("Valid"); - let find_asset = QueryBox::from(FindAssetQuantityById::new(asset_id)); - - let _instruction = ConditionalExpr::new( - Not::new(Greater::new(EvaluatesTo::new_unchecked(find_asset), 10_u32)), - Fail::new("rate is less or equal to value"), - ); -} - -struct FindRateAndCheckItGreaterThanValue { - from_currency: String, - to_currency: String, - value: u32, -} - -impl FindRateAndCheckItGreaterThanValue { - pub fn new(from_currency: &str, to_currency: &str, value: u32) -> Self { - Self { - from_currency: from_currency.to_string(), - to_currency: to_currency.to_string(), - value, - } - } - - pub fn into_isi(self) -> ConditionalExpr { - ConditionalExpr::new( - Not::new(Greater::new( - EvaluatesTo::new_unchecked(QueryBox::from(FindAssetQuantityById::new( - AssetId::new( - format!("{}2{}_rate#exchange", self.from_currency, self.to_currency) - .parse() - .expect("Valid"), - AccountId::from_str("dex@exchange").expect("Valid"), - ), - ))), - self.value, - )), - Fail::new("rate is less or equal to value"), - ) - } -} - -#[test] -fn find_rate_and_check_it_greater_than_value_predefined_isi_should_be_valid() { - let _instruction = FindRateAndCheckItGreaterThanValue::new("btc", "eth", 10).into_isi(); -} - #[test] fn account_id_parsing() -> Result<(), ParseError> { // `AccountId` should have format `name@domain_name` diff --git a/data_model/tests/ui.rs b/data_model/tests/ui.rs deleted file mode 100644 index 48e1f1bbea3..00000000000 --- a/data_model/tests/ui.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![cfg(not(coverage))] -use trybuild::TestCases; - -#[test] -fn ui() { - TestCases::new().compile_fail("tests/ui_fail/*.rs"); -} diff --git a/data_model/tests/ui_fail/evaluates_to.rs b/data_model/tests/ui_fail/evaluates_to.rs deleted file mode 100644 index 6694b013653..00000000000 --- a/data_model/tests/ui_fail/evaluates_to.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! This test ensures that [`EvaluatesTo`] provides compile-time strong typing - -use iroha_data_model::prelude::*; - -fn get_assets_by_account_id(_account_id: impl Into>) -> Vec { - Vec::new() -} - -fn main() { - let asset_definition_id: AssetDefinitionId = "rose#wonderland".parse().unwrap(); - get_assets_by_account_id(asset_definition_id); -} diff --git a/data_model/tests/ui_fail/evaluates_to.stderr b/data_model/tests/ui_fail/evaluates_to.stderr deleted file mode 100644 index ff7da36be16..00000000000 --- a/data_model/tests/ui_fail/evaluates_to.stderr +++ /dev/null @@ -1,17 +0,0 @@ -error[E0277]: the trait bound `iroha_data_model::account::AccountId: From` is not satisfied - --> tests/ui_fail/evaluates_to.rs:11:30 - | -11 | get_assets_by_account_id(asset_definition_id); - | ------------------------ ^^^^^^^^^^^^^^^^^^^ the trait `From` is not implemented for `iroha_data_model::account::AccountId` - | | - | required by a bound introduced by this call - | - = note: required for `iroha_data_model::asset::AssetDefinitionId` to implement `Into` - = note: required for `iroha_data_model::expression::EvaluatesTo` to implement `From` - = note: 1 redundant requirement hidden - = note: required for `iroha_data_model::asset::AssetDefinitionId` to implement `Into>` -note: required by a bound in `get_assets_by_account_id` - --> tests/ui_fail/evaluates_to.rs:5:47 - | -5 | fn get_assets_by_account_id(_account_id: impl Into>) -> Vec { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `get_assets_by_account_id` diff --git a/default_executor/src/lib.rs b/default_executor/src/lib.rs index 4834bcc9228..6214fdaf03b 100644 --- a/default_executor/src/lib.rs +++ b/default_executor/src/lib.rs @@ -8,7 +8,7 @@ extern crate panic_halt; use alloc::borrow::ToOwned as _; -use iroha_executor::{default::default_permission_token_schema, prelude::*, smart_contract}; +use iroha_executor::{default::default_permission_token_schema, prelude::*}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -19,11 +19,10 @@ static ALLOC: LockedAllocator = LockedAllocator::new(FreeList /// # Warning /// /// The defaults are not guaranteed to be stable. -#[derive(Clone, Constructor, Debug, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[derive(Debug, Clone, Constructor, Visit, Validate, ValidateEntrypoints)] pub struct Executor { verdict: Result, block_height: u64, - host: smart_contract::Host, } impl Executor { diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 756d22082be..a4640d1122e 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -155,6 +155,20 @@ } ] }, + "AccountMintBox": { + "Enum": [ + { + "tag": "PublicKey", + "discriminant": 0, + "type": "Mint" + }, + { + "tag": "SignatureCheckCondition", + "discriminant": 1, + "type": "Mint" + } + ] + }, "AccountPermissionChanged": { "Struct": [ { @@ -203,18 +217,6 @@ } ] }, - "Add": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "Algorithm": { "Enum": [ { @@ -235,18 +237,6 @@ } ] }, - "And": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "Array, 8>": { "Array": { "type": "Interval", @@ -289,6 +279,25 @@ } ] }, + "AssetBurnBox": { + "Enum": [ + { + "tag": "Quantity", + "discriminant": 0, + "type": "Burn" + }, + { + "tag": "BigQuantity", + "discriminant": 1, + "type": "Burn" + }, + { + "tag": "Fixed", + "discriminant": 2, + "type": "Burn" + } + ] + }, "AssetChanged": { "Struct": [ { @@ -534,6 +543,44 @@ } ] }, + "AssetMintBox": { + "Enum": [ + { + "tag": "Quantity", + "discriminant": 0, + "type": "Mint" + }, + { + "tag": "BigQuantity", + "discriminant": 1, + "type": "Mint" + }, + { + "tag": "Fixed", + "discriminant": 2, + "type": "Mint" + } + ] + }, + "AssetTransferBox": { + "Enum": [ + { + "tag": "Quantity", + "discriminant": 0, + "type": "Transfer" + }, + { + "tag": "BigQuantity", + "discriminant": 1, + "type": "Transfer" + }, + { + "tag": "Fixed", + "discriminant": 2, + "type": "Transfer" + } + ] + }, "AssetValue": { "Enum": [ { @@ -632,18 +679,6 @@ } ] }, - "BinaryOpIncompatibleNumericValueTypesError": { - "Struct": [ - { - "name": "left", - "type": "NumericValue" - }, - { - "name": "right", - "type": "NumericValue" - } - ] - }, "BlockHeader": { "Struct": [ { @@ -702,31 +737,82 @@ ] }, "BlockSubscriptionRequest": "NonZero", - "BurnExpr": { + "Burn": { + "Struct": [ + { + "name": "object", + "type": "Fixed" + }, + { + "name": "destination_id", + "type": "AssetId" + } + ] + }, + "Burn": { + "Struct": [ + { + "name": "object", + "type": "PublicKey" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Burn": { + "Struct": [ + { + "name": "object", + "type": "u128" + }, + { + "name": "destination_id", + "type": "AssetId" + } + ] + }, + "Burn": { "Struct": [ { "name": "object", - "type": "EvaluatesTo" + "type": "u32" }, { "name": "destination_id", - "type": "EvaluatesTo" + "type": "AssetId" } ] }, - "ConditionalExpr": { + "Burn>": { "Struct": [ { - "name": "condition", - "type": "EvaluatesTo" + "name": "object", + "type": "u32" + }, + { + "name": "destination_id", + "type": "TriggerId" + } + ] + }, + "BurnBox": { + "Enum": [ + { + "tag": "AccountPublicKey", + "discriminant": 0, + "type": "Burn" }, { - "name": "then", - "type": "InstructionExpr" + "tag": "Asset", + "discriminant": 1, + "type": "AssetBurnBox" }, { - "name": "otherwise", - "type": "Option" + "tag": "TriggerRepetitions", + "discriminant": 2, + "type": "Burn>" } ] }, @@ -778,50 +864,6 @@ } ] }, - "Contains": { - "Struct": [ - { - "name": "collection", - "type": "EvaluatesTo>" - }, - { - "name": "element", - "type": "EvaluatesTo" - } - ] - }, - "ContainsAll": { - "Struct": [ - { - "name": "collection", - "type": "EvaluatesTo>" - }, - { - "name": "elements", - "type": "EvaluatesTo>" - } - ] - }, - "ContainsAny": { - "Struct": [ - { - "name": "collection", - "type": "EvaluatesTo>" - }, - { - "name": "elements", - "type": "EvaluatesTo>" - } - ] - }, - "ContextValue": { - "Struct": [ - { - "name": "value_name", - "type": "Name" - } - ] - }, "DataEntityFilter": { "Enum": [ { @@ -915,18 +957,6 @@ } ] }, - "Divide": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "Domain": { "Struct": [ { @@ -1070,423 +1100,131 @@ "u32" ] }, - "Equal": { - "Struct": [ + "Event": { + "Enum": [ { - "name": "left", - "type": "EvaluatesTo" + "tag": "Pipeline", + "discriminant": 0, + "type": "PipelineEvent" }, { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, - "EvaluatesTo": { - "Struct": [ + "tag": "Data", + "discriminant": 1, + "type": "DataEvent" + }, { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ + "tag": "Time", + "discriminant": 2, + "type": "TimeEvent" + }, { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ + "tag": "ExecuteTrigger", + "discriminant": 3, + "type": "ExecuteTriggerEvent" + }, { - "name": "expression", - "type": "Expression" + "tag": "Notification", + "discriminant": 4, + "type": "NotificationEvent" } ] }, - "EvaluatesTo": { - "Struct": [ + "EventMessage": "Event", + "EventSubscriptionRequest": "FilterBox", + "Executable": { + "Enum": [ + { + "tag": "Instructions", + "discriminant": 0, + "type": "Vec" + }, { - "name": "expression", - "type": "Expression" + "tag": "Wasm", + "discriminant": 1, + "type": "WasmSmartContract" } ] }, - "EvaluatesTo>": { + "ExecuteTrigger": { "Struct": [ { - "name": "expression", - "type": "Expression" + "name": "trigger_id", + "type": "TriggerId" } ] }, - "EvaluatesTo>": { + "ExecuteTriggerEvent": { "Struct": [ { - "name": "expression", - "type": "Expression" + "name": "trigger_id", + "type": "TriggerId" + }, + { + "name": "authority", + "type": "AccountId" } ] }, - "EvaluatesTo": { + "ExecuteTriggerEventFilter": { "Struct": [ { - "name": "expression", - "type": "Expression" + "name": "trigger_id", + "type": "TriggerId" + }, + { + "name": "authority", + "type": "AccountId" } ] }, - "EvaluatesTo": { - "Struct": [ + "ExecutionTime": { + "Enum": [ + { + "tag": "PreCommit", + "discriminant": 0 + }, { - "name": "expression", - "type": "Expression" + "tag": "Schedule", + "discriminant": 1, + "type": "Schedule" } ] }, - "EvaluatesTo": { + "Executor": { "Struct": [ { - "name": "expression", - "type": "Expression" + "name": "wasm", + "type": "WasmSmartContract" } ] }, - "EvaluatesTo": { - "Struct": [ + "ExecutorEvent": { + "Enum": [ { - "name": "expression", - "type": "Expression" + "tag": "Upgraded", + "discriminant": 0 } ] }, - "EvaluatesTo": { - "Struct": [ + "ExecutorMode": { + "Enum": [ + { + "tag": "Path", + "discriminant": 0, + "type": "String" + }, { - "name": "expression", - "type": "Expression" + "tag": "Inline", + "discriminant": 1, + "type": "Executor" } ] }, - "EvaluatesTo": { + "Fail": { "Struct": [ { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo>": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluatesTo": { - "Struct": [ - { - "name": "expression", - "type": "Expression" - } - ] - }, - "EvaluationError": { - "Enum": [ - { - "tag": "Math", - "discriminant": 0, - "type": "MathError" - }, - { - "tag": "Validation", - "discriminant": 1, - "type": "ValidationFail" - }, - { - "tag": "Find", - "discriminant": 2, - "type": "String" - }, - { - "tag": "Conversion", - "discriminant": 3, - "type": "String" - } - ] - }, - "Event": { - "Enum": [ - { - "tag": "Pipeline", - "discriminant": 0, - "type": "PipelineEvent" - }, - { - "tag": "Data", - "discriminant": 1, - "type": "DataEvent" - }, - { - "tag": "Time", - "discriminant": 2, - "type": "TimeEvent" - }, - { - "tag": "ExecuteTrigger", - "discriminant": 3, - "type": "ExecuteTriggerEvent" - }, - { - "tag": "Notification", - "discriminant": 4, - "type": "NotificationEvent" - } - ] - }, - "EventMessage": "Event", - "EventSubscriptionRequest": "FilterBox", - "Executable": { - "Enum": [ - { - "tag": "Instructions", - "discriminant": 0, - "type": "Vec" - }, - { - "tag": "Wasm", - "discriminant": 1, - "type": "WasmSmartContract" - } - ] - }, - "ExecuteTriggerEvent": { - "Struct": [ - { - "name": "trigger_id", - "type": "TriggerId" - }, - { - "name": "authority", - "type": "AccountId" - } - ] - }, - "ExecuteTriggerEventFilter": { - "Struct": [ - { - "name": "trigger_id", - "type": "TriggerId" - }, - { - "name": "authority", - "type": "AccountId" - } - ] - }, - "ExecuteTriggerExpr": { - "Struct": [ - { - "name": "trigger_id", - "type": "EvaluatesTo" - } - ] - }, - "ExecutionTime": { - "Enum": [ - { - "tag": "PreCommit", - "discriminant": 0 - }, - { - "tag": "Schedule", - "discriminant": 1, - "type": "Schedule" - } - ] - }, - "Executor": { - "Struct": [ - { - "name": "wasm", - "type": "WasmSmartContract" - } - ] - }, - "ExecutorEvent": { - "Enum": [ - { - "tag": "Upgraded", - "discriminant": 0 - } - ] - }, - "ExecutorMode": { - "Enum": [ - { - "tag": "Path", - "discriminant": 0, - "type": "String" - }, - { - "tag": "Inline", - "discriminant": 1, - "type": "Executor" - } - ] - }, - "Expression": { - "Enum": [ - { - "tag": "Add", - "discriminant": 0, - "type": "Add" - }, - { - "tag": "Subtract", - "discriminant": 1, - "type": "Subtract" - }, - { - "tag": "Multiply", - "discriminant": 2, - "type": "Multiply" - }, - { - "tag": "Divide", - "discriminant": 3, - "type": "Divide" - }, - { - "tag": "Mod", - "discriminant": 4, - "type": "Mod" - }, - { - "tag": "RaiseTo", - "discriminant": 5, - "type": "RaiseTo" - }, - { - "tag": "Greater", - "discriminant": 6, - "type": "Greater" - }, - { - "tag": "Less", - "discriminant": 7, - "type": "Less" - }, - { - "tag": "Equal", - "discriminant": 8, - "type": "Equal" - }, - { - "tag": "Not", - "discriminant": 9, - "type": "Not" - }, - { - "tag": "And", - "discriminant": 10, - "type": "And" - }, - { - "tag": "Or", - "discriminant": 11, - "type": "Or" - }, - { - "tag": "If", - "discriminant": 12, - "type": "If" - }, - { - "tag": "Raw", - "discriminant": 13, - "type": "Value" - }, - { - "tag": "Query", - "discriminant": 14, - "type": "QueryBox" - }, - { - "tag": "Contains", - "discriminant": 15, - "type": "Contains" - }, - { - "tag": "ContainsAll", - "discriminant": 16, - "type": "ContainsAll" - }, - { - "tag": "ContainsAny", - "discriminant": 17, - "type": "ContainsAny" - }, - { - "tag": "Where", - "discriminant": 18, - "type": "Where" - }, - { - "tag": "ContextValue", - "discriminant": 19, - "type": "ContextValue" - } - ] - }, - "Fail": { - "Struct": [ - { - "name": "message", - "type": "String" + "name": "message", + "type": "String" } ] }, @@ -1809,7 +1547,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, @@ -1817,11 +1555,11 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AccountId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -1829,7 +1567,7 @@ "Struct": [ { "name": "domain_id", - "type": "EvaluatesTo" + "type": "DomainId" } ] }, @@ -1837,7 +1575,7 @@ "Struct": [ { "name": "name", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -1845,7 +1583,7 @@ "Struct": [ { "name": "asset_definition_id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" } ] }, @@ -1865,7 +1603,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetId" } ] }, @@ -1873,7 +1611,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" } ] }, @@ -1881,11 +1619,11 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -1893,11 +1631,11 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -1905,7 +1643,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetId" } ] }, @@ -1913,7 +1651,7 @@ "Struct": [ { "name": "account_id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, @@ -1921,7 +1659,7 @@ "Struct": [ { "name": "asset_definition_id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" } ] }, @@ -1929,7 +1667,7 @@ "Struct": [ { "name": "domain_id", - "type": "EvaluatesTo" + "type": "DomainId" } ] }, @@ -1937,11 +1675,11 @@ "Struct": [ { "name": "domain_id", - "type": "EvaluatesTo" + "type": "DomainId" }, { "name": "asset_definition_id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" } ] }, @@ -1949,7 +1687,7 @@ "Struct": [ { "name": "name", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -1957,7 +1695,7 @@ "Struct": [ { "name": "hash", - "type": "EvaluatesTo>" + "type": "HashOf" } ] }, @@ -1965,7 +1703,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "DomainId" } ] }, @@ -1973,11 +1711,11 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "DomainId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -2055,7 +1793,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, @@ -2063,7 +1801,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "RoleId" } ] }, @@ -2071,7 +1809,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, @@ -2079,7 +1817,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "AssetDefinitionId" } ] }, @@ -2087,7 +1825,7 @@ "Struct": [ { "name": "hash", - "type": "EvaluatesTo>" + "type": "HashOf" } ] }, @@ -2095,7 +1833,7 @@ "Struct": [ { "name": "account_id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, @@ -2103,7 +1841,7 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "TriggerId" } ] }, @@ -2111,11 +1849,11 @@ "Struct": [ { "name": "id", - "type": "EvaluatesTo" + "type": "TriggerId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" } ] }, @@ -2123,7 +1861,7 @@ "Struct": [ { "name": "domain_id", - "type": "EvaluatesTo" + "type": "DomainId" } ] }, @@ -2170,27 +1908,41 @@ } ] }, - "GrantExpr": { + "Grant": { "Struct": [ { "name": "object", - "type": "EvaluatesTo" + "type": "PermissionToken" }, { "name": "destination_id", - "type": "EvaluatesTo" + "type": "AccountId" } ] }, - "Greater": { + "Grant": { "Struct": [ { - "name": "left", - "type": "EvaluatesTo" + "name": "object", + "type": "RoleId" }, { - "name": "right", - "type": "EvaluatesTo" + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "GrantBox": { + "Enum": [ + { + "tag": "PermissionToken", + "discriminant": 0, + "type": "Grant" + }, + { + "tag": "Role", + "discriminant": 1, + "type": "Grant" } ] }, @@ -2291,76 +2043,134 @@ { "tag": "Domain", "discriminant": 5, - "type": "Domain" + "type": "Domain" + }, + { + "tag": "Account", + "discriminant": 6, + "type": "Account" + }, + { + "tag": "AssetDefinition", + "discriminant": 7, + "type": "AssetDefinition" + }, + { + "tag": "Asset", + "discriminant": 8, + "type": "Asset" + }, + { + "tag": "Trigger", + "discriminant": 9, + "type": "Trigger" + }, + { + "tag": "Role", + "discriminant": 10, + "type": "Role" + }, + { + "tag": "Parameter", + "discriminant": 11, + "type": "Parameter" + } + ] + }, + "InstructionBox": { + "Enum": [ + { + "tag": "Register", + "discriminant": 0, + "type": "RegisterBox" + }, + { + "tag": "Unregister", + "discriminant": 1, + "type": "UnregisterBox" + }, + { + "tag": "Mint", + "discriminant": 2, + "type": "MintBox" + }, + { + "tag": "Burn", + "discriminant": 3, + "type": "BurnBox" + }, + { + "tag": "Transfer", + "discriminant": 4, + "type": "TransferBox" + }, + { + "tag": "SetKeyValue", + "discriminant": 5, + "type": "SetKeyValueBox" }, { - "tag": "Account", + "tag": "RemoveKeyValue", "discriminant": 6, - "type": "Account" + "type": "RemoveKeyValueBox" }, { - "tag": "AssetDefinition", + "tag": "Grant", "discriminant": 7, - "type": "AssetDefinition" + "type": "GrantBox" }, { - "tag": "Asset", + "tag": "Revoke", "discriminant": 8, - "type": "Asset" + "type": "RevokeBox" }, { - "tag": "Trigger", + "tag": "ExecuteTrigger", "discriminant": 9, - "type": "Trigger" + "type": "ExecuteTrigger" }, { - "tag": "Role", + "tag": "SetParameter", "discriminant": 10, - "type": "Role" + "type": "SetParameter" }, { - "tag": "Parameter", + "tag": "NewParameter", "discriminant": 11, - "type": "Parameter" - } - ] - }, - "If": { - "Struct": [ + "type": "NewParameter" + }, { - "name": "condition", - "type": "EvaluatesTo" + "tag": "Upgrade", + "discriminant": 12, + "type": "Upgrade" }, { - "name": "then", - "type": "EvaluatesTo" + "tag": "Log", + "discriminant": 13, + "type": "Log" }, { - "name": "otherwise", - "type": "EvaluatesTo" + "tag": "Fail", + "discriminant": 14, + "type": "Fail" } ] }, "InstructionEvaluationError": { "Enum": [ - { - "tag": "Expression", - "discriminant": 0, - "type": "EvaluationError" - }, { "tag": "Unsupported", - "discriminant": 1, + "discriminant": 0, "type": "InstructionType" }, { "tag": "PermissionParameter", - "discriminant": 2, + "discriminant": 1, "type": "String" }, { "tag": "Type", - "discriminant": 3, + "discriminant": 2, "type": "TypeError" } ] @@ -2428,7 +2238,7 @@ "Struct": [ { "name": "instruction", - "type": "InstructionExpr" + "type": "InstructionBox" }, { "name": "reason", @@ -2436,100 +2246,6 @@ } ] }, - "InstructionExpr": { - "Enum": [ - { - "tag": "Register", - "discriminant": 0, - "type": "RegisterExpr" - }, - { - "tag": "Unregister", - "discriminant": 1, - "type": "UnregisterExpr" - }, - { - "tag": "Mint", - "discriminant": 2, - "type": "MintExpr" - }, - { - "tag": "Burn", - "discriminant": 3, - "type": "BurnExpr" - }, - { - "tag": "Transfer", - "discriminant": 4, - "type": "TransferExpr" - }, - { - "tag": "If", - "discriminant": 5, - "type": "ConditionalExpr" - }, - { - "tag": "Pair", - "discriminant": 6, - "type": "PairExpr" - }, - { - "tag": "Sequence", - "discriminant": 7, - "type": "SequenceExpr" - }, - { - "tag": "SetKeyValue", - "discriminant": 8, - "type": "SetKeyValueExpr" - }, - { - "tag": "RemoveKeyValue", - "discriminant": 9, - "type": "RemoveKeyValueExpr" - }, - { - "tag": "Grant", - "discriminant": 10, - "type": "GrantExpr" - }, - { - "tag": "Revoke", - "discriminant": 11, - "type": "RevokeExpr" - }, - { - "tag": "ExecuteTrigger", - "discriminant": 12, - "type": "ExecuteTriggerExpr" - }, - { - "tag": "SetParameter", - "discriminant": 13, - "type": "SetParameterExpr" - }, - { - "tag": "NewParameter", - "discriminant": 14, - "type": "NewParameterExpr" - }, - { - "tag": "Upgrade", - "discriminant": 15, - "type": "UpgradeExpr" - }, - { - "tag": "Log", - "discriminant": 16, - "type": "LogExpr" - }, - { - "tag": "Fail", - "discriminant": 17, - "type": "Fail" - } - ] - }, "InstructionType": { "Enum": [ { @@ -2552,57 +2268,45 @@ "tag": "Transfer", "discriminant": 4 }, - { - "tag": "If", - "discriminant": 5 - }, - { - "tag": "Pair", - "discriminant": 6 - }, - { - "tag": "Sequence", - "discriminant": 7 - }, { "tag": "SetKeyValue", - "discriminant": 8 + "discriminant": 5 }, { "tag": "RemoveKeyValue", - "discriminant": 9 + "discriminant": 6 }, { "tag": "Grant", - "discriminant": 10 + "discriminant": 7 }, { "tag": "Revoke", - "discriminant": 11 + "discriminant": 8 }, { "tag": "ExecuteTrigger", - "discriminant": 12 + "discriminant": 9 }, { "tag": "SetParameter", - "discriminant": 13 + "discriminant": 10 }, { "tag": "NewParameter", - "discriminant": 14 + "discriminant": 11 }, { "tag": "Upgrade", - "discriminant": 15 + "discriminant": 12 }, { "tag": "Log", - "discriminant": 16 + "discriminant": 13 }, { "tag": "Fail", - "discriminant": 17 + "discriminant": 14 } ] }, @@ -2660,18 +2364,6 @@ } ] }, - "Less": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "Level": { "Enum": [ { @@ -2708,15 +2400,15 @@ } ] }, - "LogExpr": { + "Log": { "Struct": [ { "name": "level", - "type": "EvaluatesTo" + "type": "Level" }, { "name": "msg", - "type": "EvaluatesTo" + "type": "String" } ] }, @@ -2746,14 +2438,9 @@ "tag": "Unknown", "discriminant": 5 }, - { - "tag": "BinaryOpIncompatibleNumericValueTypes", - "discriminant": 6, - "type": "BinaryOpIncompatibleNumericValueTypesError" - }, { "tag": "FixedPointConversion", - "discriminant": 7, + "discriminant": 6, "type": "String" } ] @@ -2801,75 +2488,154 @@ } ] }, - "MetadataChanged": { + "MetadataChanged": { + "Struct": [ + { + "name": "target_id", + "type": "AssetId" + }, + { + "name": "key", + "type": "Name" + }, + { + "name": "value", + "type": "Value" + } + ] + }, + "MetadataChanged": { + "Struct": [ + { + "name": "target_id", + "type": "DomainId" + }, + { + "name": "key", + "type": "Name" + }, + { + "name": "value", + "type": "Value" + } + ] + }, + "MetadataError": { + "Enum": [ + { + "tag": "EntryTooBig", + "discriminant": 0, + "type": "SizeError" + }, + { + "tag": "OverallSize", + "discriminant": 1, + "type": "SizeError" + }, + { + "tag": "EmptyPath", + "discriminant": 2 + }, + { + "tag": "MissingSegment", + "discriminant": 3, + "type": "Name" + }, + { + "tag": "InvalidSegment", + "discriminant": 4, + "type": "Name" + } + ] + }, + "Mint": { + "Struct": [ + { + "name": "object", + "type": "Fixed" + }, + { + "name": "destination_id", + "type": "AssetId" + } + ] + }, + "Mint": { + "Struct": [ + { + "name": "object", + "type": "PublicKey" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Mint": { + "Struct": [ + { + "name": "object", + "type": "SignatureCheckCondition" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Mint": { + "Struct": [ + { + "name": "object", + "type": "u128" + }, + { + "name": "destination_id", + "type": "AssetId" + } + ] + }, + "Mint": { "Struct": [ { - "name": "target_id", - "type": "AssetId" - }, - { - "name": "key", - "type": "Name" + "name": "object", + "type": "u32" }, { - "name": "value", - "type": "Value" + "name": "destination_id", + "type": "AssetId" } ] }, - "MetadataChanged": { + "Mint>": { "Struct": [ { - "name": "target_id", - "type": "DomainId" - }, - { - "name": "key", - "type": "Name" + "name": "object", + "type": "u32" }, { - "name": "value", - "type": "Value" + "name": "destination_id", + "type": "TriggerId" } ] }, - "MetadataError": { + "MintBox": { "Enum": [ { - "tag": "EntryTooBig", + "tag": "Account", "discriminant": 0, - "type": "SizeError" + "type": "AccountMintBox" }, { - "tag": "OverallSize", + "tag": "Asset", "discriminant": 1, - "type": "SizeError" - }, - { - "tag": "EmptyPath", - "discriminant": 2 - }, - { - "tag": "MissingSegment", - "discriminant": 3, - "type": "Name" - }, - { - "tag": "InvalidSegment", - "discriminant": 4, - "type": "Name" - } - ] - }, - "MintExpr": { - "Struct": [ - { - "name": "object", - "type": "EvaluatesTo" + "type": "AssetMintBox" }, { - "name": "destination_id", - "type": "EvaluatesTo" + "tag": "TriggerRepetitions", + "discriminant": 2, + "type": "Mint>" } ] }, @@ -2937,30 +2703,6 @@ } ] }, - "Mod": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, - "Multiply": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "Name": "String", "NewAccount": { "Struct": [ @@ -3018,11 +2760,11 @@ } ] }, - "NewParameterExpr": { + "NewParameter": { "Struct": [ { "name": "parameter", - "type": "EvaluatesTo" + "type": "Parameter" } ] }, @@ -3037,14 +2779,6 @@ "NonTrivial>": "Vec>", "NonZero": "u32", "NonZero": "u64", - "Not": { - "Struct": [ - { - "name": "expression", - "type": "EvaluatesTo" - } - ] - }, "NotificationEvent": { "Enum": [ { @@ -3106,9 +2840,6 @@ "Option>": { "Option": "HashOf" }, - "Option": { - "Option": "InstructionExpr" - }, "Option": { "Option": "IpfsPath" }, @@ -3139,18 +2870,6 @@ "Option": { "Option": "TriggerId" }, - "Or": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "OriginFilter": "AccountId", "OriginFilter": "AssetDefinitionId", "OriginFilter": "AssetId", @@ -3158,18 +2877,6 @@ "OriginFilter": "PeerId", "OriginFilter": "RoleId", "OriginFilter": "TriggerId", - "PairExpr": { - "Struct": [ - { - "name": "left_instruction", - "type": "InstructionExpr" - }, - { - "name": "right_instruction", - "type": "InstructionExpr" - } - ] - }, "Parameter": { "Struct": [ { @@ -3610,28 +3317,23 @@ "discriminant": 0, "type": "String" }, - { - "tag": "Evaluate", - "discriminant": 1, - "type": "String" - }, { "tag": "Find", - "discriminant": 2, + "discriminant": 1, "type": "FindError" }, { "tag": "Conversion", - "discriminant": 3, + "discriminant": 2, "type": "String" }, { "tag": "UnknownCursor", - "discriminant": 4 + "discriminant": 3 }, { "tag": "FetchSizeTooBig", - "discriminant": 5 + "discriminant": 4 } ] }, @@ -3651,35 +3353,110 @@ } ] }, - "RaiseTo": { + "RawGenesisBlock": { "Struct": [ { - "name": "left", - "type": "EvaluatesTo" + "name": "transactions", + "type": "Vec>" }, { - "name": "right", - "type": "EvaluatesTo" + "name": "executor", + "type": "ExecutorMode" } ] }, - "RawGenesisBlock": { + "Register": { "Struct": [ { - "name": "transactions", - "type": "Vec>" - }, + "name": "object", + "type": "NewAccount" + } + ] + }, + "Register": { + "Struct": [ { - "name": "executor", - "type": "ExecutorMode" + "name": "object", + "type": "Asset" + } + ] + }, + "Register": { + "Struct": [ + { + "name": "object", + "type": "NewAssetDefinition" } ] }, - "RegisterExpr": { + "Register": { "Struct": [ { "name": "object", - "type": "EvaluatesTo" + "type": "NewDomain" + } + ] + }, + "Register": { + "Struct": [ + { + "name": "object", + "type": "Peer" + } + ] + }, + "Register": { + "Struct": [ + { + "name": "object", + "type": "NewRole" + } + ] + }, + "Register>": { + "Struct": [ + { + "name": "object", + "type": "Trigger" + } + ] + }, + "RegisterBox": { + "Enum": [ + { + "tag": "Peer", + "discriminant": 0, + "type": "Register" + }, + { + "tag": "Domain", + "discriminant": 1, + "type": "Register" + }, + { + "tag": "Account", + "discriminant": 2, + "type": "Register" + }, + { + "tag": "AssetDefinition", + "discriminant": 3, + "type": "Register" + }, + { + "tag": "Asset", + "discriminant": 4, + "type": "Register" + }, + { + "tag": "Role", + "discriminant": 5, + "type": "Register" + }, + { + "tag": "Trigger", + "discriminant": 6, + "type": "Register>" } ] }, @@ -3722,15 +3499,75 @@ } ] }, - "RemoveKeyValueExpr": { + "RemoveKeyValue": { + "Struct": [ + { + "name": "object_id", + "type": "AccountId" + }, + { + "name": "key", + "type": "Name" + } + ] + }, + "RemoveKeyValue": { "Struct": [ { "name": "object_id", - "type": "EvaluatesTo" + "type": "AssetId" + }, + { + "name": "key", + "type": "Name" + } + ] + }, + "RemoveKeyValue": { + "Struct": [ + { + "name": "object_id", + "type": "AssetDefinitionId" + }, + { + "name": "key", + "type": "Name" + } + ] + }, + "RemoveKeyValue": { + "Struct": [ + { + "name": "object_id", + "type": "DomainId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" + } + ] + }, + "RemoveKeyValueBox": { + "Enum": [ + { + "tag": "Domain", + "discriminant": 0, + "type": "RemoveKeyValue" + }, + { + "tag": "Account", + "discriminant": 1, + "type": "RemoveKeyValue" + }, + { + "tag": "AssetDefinition", + "discriminant": 2, + "type": "RemoveKeyValue" + }, + { + "tag": "Asset", + "discriminant": 3, + "type": "RemoveKeyValue" } ] }, @@ -3747,27 +3584,53 @@ } ] }, - "RepetitionError": { + "RepetitionError": { + "Struct": [ + { + "name": "instruction_type", + "type": "InstructionType" + }, + { + "name": "id", + "type": "IdBox" + } + ] + }, + "Revoke": { + "Struct": [ + { + "name": "object", + "type": "PermissionToken" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Revoke": { "Struct": [ { - "name": "instruction_type", - "type": "InstructionType" + "name": "object", + "type": "RoleId" }, { - "name": "id", - "type": "IdBox" + "name": "destination_id", + "type": "AccountId" } ] }, - "RevokeExpr": { - "Struct": [ + "RevokeBox": { + "Enum": [ { - "name": "object", - "type": "EvaluatesTo" + "tag": "PermissionToken", + "discriminant": 0, + "type": "Revoke" }, { - "name": "destination_id", - "type": "EvaluatesTo" + "tag": "Role", + "discriminant": 1, + "type": "Revoke" } ] }, @@ -3905,35 +3768,99 @@ } ] }, - "SequenceExpr": { + "SetKeyValue": { "Struct": [ { - "name": "instructions", - "type": "Vec" + "name": "object_id", + "type": "AccountId" + }, + { + "name": "key", + "type": "Name" + }, + { + "name": "value", + "type": "Value" + } + ] + }, + "SetKeyValue": { + "Struct": [ + { + "name": "object_id", + "type": "AssetId" + }, + { + "name": "key", + "type": "Name" + }, + { + "name": "value", + "type": "Value" + } + ] + }, + "SetKeyValue": { + "Struct": [ + { + "name": "object_id", + "type": "AssetDefinitionId" + }, + { + "name": "key", + "type": "Name" + }, + { + "name": "value", + "type": "Value" } ] }, - "SetKeyValueExpr": { + "SetKeyValue": { "Struct": [ { "name": "object_id", - "type": "EvaluatesTo" + "type": "DomainId" }, { "name": "key", - "type": "EvaluatesTo" + "type": "Name" }, { "name": "value", - "type": "EvaluatesTo" + "type": "Value" + } + ] + }, + "SetKeyValueBox": { + "Enum": [ + { + "tag": "Domain", + "discriminant": 0, + "type": "SetKeyValue" + }, + { + "tag": "Account", + "discriminant": 1, + "type": "SetKeyValue" + }, + { + "tag": "AssetDefinition", + "discriminant": 2, + "type": "SetKeyValue" + }, + { + "tag": "Asset", + "discriminant": 3, + "type": "SetKeyValue" } ] }, - "SetParameterExpr": { + "SetParameter": { "Struct": [ { "name": "parameter", - "type": "EvaluatesTo" + "type": "Parameter" } ] }, @@ -4136,12 +4063,6 @@ "value": "Asset" } }, - "SortedMap>": { - "Map": { - "key": "Name", - "value": "EvaluatesTo" - } - }, "SortedMap": { "Map": { "key": "Name", @@ -4186,18 +4107,6 @@ ] }, "StringWithJson": "String", - "Subtract": { - "Struct": [ - { - "name": "left", - "type": "EvaluatesTo" - }, - { - "name": "right", - "type": "EvaluatesTo" - } - ] - }, "TimeEvent": { "Struct": [ { @@ -4328,19 +4237,102 @@ } ] }, - "TransferExpr": { + "Transfer": { + "Struct": [ + { + "name": "source_id", + "type": "AccountId" + }, + { + "name": "object", + "type": "AssetDefinitionId" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Transfer": { + "Struct": [ + { + "name": "source_id", + "type": "AccountId" + }, + { + "name": "object", + "type": "DomainId" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Transfer": { + "Struct": [ + { + "name": "source_id", + "type": "AssetId" + }, + { + "name": "object", + "type": "Fixed" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Transfer": { + "Struct": [ + { + "name": "source_id", + "type": "AssetId" + }, + { + "name": "object", + "type": "u128" + }, + { + "name": "destination_id", + "type": "AccountId" + } + ] + }, + "Transfer": { "Struct": [ { "name": "source_id", - "type": "EvaluatesTo" + "type": "AssetId" }, { "name": "object", - "type": "EvaluatesTo" + "type": "u32" }, { "name": "destination_id", - "type": "EvaluatesTo" + "type": "AccountId" + } + ] + }, + "TransferBox": { + "Enum": [ + { + "tag": "Domain", + "discriminant": 0, + "type": "Transfer" + }, + { + "tag": "AssetDefinition", + "discriminant": 1, + "type": "Transfer" + }, + { + "tag": "Asset", + "discriminant": 2, + "type": "AssetTransferBox" } ] }, @@ -4529,11 +4521,98 @@ ] }, "UniqueVec": "Vec", - "UnregisterExpr": { + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "AccountId" + } + ] + }, + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "AssetId" + } + ] + }, + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "AssetDefinitionId" + } + ] + }, + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "DomainId" + } + ] + }, + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "PeerId" + } + ] + }, + "Unregister": { + "Struct": [ + { + "name": "object_id", + "type": "RoleId" + } + ] + }, + "Unregister>": { "Struct": [ { "name": "object_id", - "type": "EvaluatesTo" + "type": "TriggerId" + } + ] + }, + "UnregisterBox": { + "Enum": [ + { + "tag": "Peer", + "discriminant": 0, + "type": "Unregister" + }, + { + "tag": "Domain", + "discriminant": 1, + "type": "Unregister" + }, + { + "tag": "Account", + "discriminant": 2, + "type": "Unregister" + }, + { + "tag": "AssetDefinition", + "discriminant": 3, + "type": "Unregister" + }, + { + "tag": "Asset", + "discriminant": 4, + "type": "Unregister" + }, + { + "tag": "Role", + "discriminant": 5, + "type": "Unregister" + }, + { + "tag": "Trigger", + "discriminant": 6, + "type": "Unregister>" } ] }, @@ -4546,11 +4625,11 @@ } ] }, - "UpgradeExpr": { + "Upgrade": { "Struct": [ { - "name": "object", - "type": "EvaluatesTo" + "name": "executor", + "type": "Executor" } ] }, @@ -4761,8 +4840,8 @@ "Vec>": { "Vec": "GenericPredicateBox" }, - "Vec": { - "Vec": "InstructionExpr" + "Vec": { + "Vec": "InstructionBox" }, "Vec": { "Vec": "Name" @@ -4782,8 +4861,8 @@ "Vec": { "Vec": "Value" }, - "Vec>": { - "Vec": "Vec" + "Vec>": { + "Vec": "Vec" }, "Vec": { "Vec": "u8" @@ -4797,18 +4876,6 @@ ] }, "WasmSmartContract": "Vec", - "Where": { - "Struct": [ - { - "name": "expression", - "type": "EvaluatesTo" - }, - { - "name": "values", - "type": "SortedMap>" - } - ] - }, "bool": "bool", "i64": { "Int": "FixedWidth" diff --git a/dsl/Cargo.toml b/dsl/Cargo.toml deleted file mode 100755 index 7cdbd40ee01..00000000000 --- a/dsl/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "iroha_dsl" - -edition.workspace = true -version.workspace = true -authors.workspace = true - -license.workspace = true - -[lints] -workspace = true - -[lib] -proc-macro = true - -[dependencies] -quote = "1.0" -proc-macro2 = "1.0" -litrs = "0.4.0" - -[dev-dependencies] -iroha_data_model = { workspace = true } -iroha_crypto = { workspace = true } -iroha_config = { workspace = true } -iroha_client = { workspace = true } -serde_json = "1.0" - - diff --git a/dsl/src/lib.rs b/dsl/src/lib.rs deleted file mode 100755 index c1378c24b1d..00000000000 --- a/dsl/src/lib.rs +++ /dev/null @@ -1,208 +0,0 @@ -// TODO: add docs -#![allow(missing_docs)] - -use std::{convert::TryFrom, iter::Peekable, str::FromStr}; - -use litrs::Literal; -use proc_macro2::{ - token_stream::IntoIter, - TokenStream, TokenTree, - TokenTree::{Ident, Punct}, -}; -use quote::quote; - -#[derive(PartialEq)] -enum ExprType { - Nil, - Int, - Bool, -} - -// within the compiler, these generate tokens for -// Value -struct ParsedExpr { - t: ExprType, - tokens: TokenStream, -} - -struct Operator(i32, ExprType, &'static str); - -fn parse_literal(it: &mut Peekable) -> ParsedExpr { - let token = it - .next() - .expect("failed to parse literal, hit end of string"); - - match token { - Punct(ref x) => { - if x.as_char() == '(' { - let v = parse_expr(it); - - it.next() - .expect("expected closing paren, hit end of string"); - return v; - } - } - - // boolean literals - Ident(ref x) => match x.to_string().as_str() { - "true" => { - return ParsedExpr { - t: ExprType::Bool, - tokens: quote! { true }, - } - } - "false" => { - return ParsedExpr { - t: ExprType::Bool, - tokens: quote! { false }, - } - } - - // unary not - "not" => { - let v = parse_literal(it).tokens; - return ParsedExpr { - t: ExprType::Bool, - tokens: quote! { Not::new(#v) }, - }; - } - _ => panic!("error: unknown identifier"), - }, - - // kinda ugly but we fallthrough basically - _ => {} - } - - // integer literals - if let Ok(Literal::Integer(i)) = Literal::try_from(token) { - let v = i.value::().expect("i don't think this integer fits?"); - return ParsedExpr { - t: ExprType::Int, - tokens: quote! { #v }, - }; - } - - ParsedExpr { - t: ExprType::Nil, - tokens: TokenStream::new(), - } -} - -fn precedence(it: &mut Peekable) -> Option { - match it.peek() { - Some(Punct(x)) => match x.as_char() { - // arithmatic - '+' => Some(Operator(4, ExprType::Int, "Add")), - '-' => Some(Operator(4, ExprType::Int, "Subtract")), - '*' => Some(Operator(3, ExprType::Int, "Multiply")), - - // compares - '=' => Some(Operator(2, ExprType::Int, "Equal")), - '>' => Some(Operator(2, ExprType::Int, "Greater")), - '<' => Some(Operator(2, ExprType::Int, "Less")), - _ => None, - }, - Some(Ident(ref x)) => match x.to_string().as_str() { - "and" => Some(Operator(1, ExprType::Bool, "And")), - "or" => Some(Operator(1, ExprType::Bool, "Or")), - _ => None, - }, - _ => None, - } -} - -/// precedence walking -fn parse_binop(it: &mut Peekable, min_prec: i32) -> ParsedExpr { - let mut lhs = parse_literal(it); - while let Some(prec) = precedence(it) { - it.next(); - if prec.0 < min_prec { - break; - } - - let op = proc_macro2::TokenStream::from_str(prec.2).unwrap(); - let rhs = parse_literal(it); - - assert!( - lhs.t == prec.1 && rhs.t == prec.1, - "cannot perform binary operator on these!" - ); - - let lhs_tokens = lhs.tokens; - let rhs_tokens = rhs.tokens; - lhs = ParsedExpr { - t: prec.1, - tokens: quote! { #op::new(#lhs_tokens, #rhs_tokens) }, - }; - } - - lhs -} - -fn is_ident(a: &TokenTree, b: &'static str) -> bool { - if let Ident(ref x) = a { - return x.to_string().as_str() == b; - } - - false -} - -fn parse_expr(it: &mut Peekable) -> ParsedExpr { - if is_ident(it.peek().expect("hit end of string"), "if") { - it.next(); - - let cond_tokens = parse_binop(it, 0).tokens; - assert!( - is_ident(&it.next().expect("hit end of string"), "then"), - "expected 'then'" - ); - - let true_case = parse_binop(it, 0); - assert!( - is_ident(&it.next().expect("hit end of string"), "else"), - "expected 'else'" - ); - - let false_case = parse_binop(it, 0); - assert!( - true_case.t == false_case.t, - "both types in a conditional must match" - ); - - let true_tokens = true_case.tokens; - let false_tokens = false_case.tokens; - return ParsedExpr { - t: ExprType::Int, - tokens: quote! { If::new(#cond_tokens, #true_tokens, #false_tokens) }, - }; - } - - // normal expression - parse_binop(it, 0) -} - -/// Convert arithmetic expression into bare expression in the iroha data model. -/// -/// Basic arithmetic and boolean expressions are supported, namely: `> < = + - * and or if not` -/// -/// # Examples -/// -/// ``` -/// extern crate iroha_dsl; -/// extern crate iroha_data_model; -/// use iroha_dsl::expr; -/// use iroha_data_model::{prelude::*, ParseError}; -/// -/// fn main() { -/// assert_eq!(expr!(54654*5 + 1), Add::new(Multiply::new(54654_u64, 5_u64), 1_u64)); -/// println!("{}", expr!(not true and false)); -/// println!("{}", expr!(if 4 = 4 then 64 else 32)); -/// } -/// ``` -#[proc_macro] -pub fn expr(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = proc_macro2::TokenStream::from(input); - let mut it = input.into_iter().peekable(); - - proc_macro::TokenStream::from(parse_expr(&mut it).tokens) -} diff --git a/ffi/src/lib.rs b/ffi/src/lib.rs index dcdd52f0d9a..4590f55b1c7 100644 --- a/ffi/src/lib.rs +++ b/ffi/src/lib.rs @@ -415,6 +415,7 @@ pub struct Extern { /// ``` pub trait WrapperTypeOf { /// Correct return type of `T` in a function generated via [`ffi_import`] + // TODO: Is associated type necessary if we already have a generic? type Type; } diff --git a/genesis/src/lib.rs b/genesis/src/lib.rs index 0428a2240ac..84cd9964e49 100644 --- a/genesis/src/lib.rs +++ b/genesis/src/lib.rs @@ -61,7 +61,7 @@ impl GenesisNetwork { // First instruction should be Executor upgrade. // This makes possible to grant permissions to users in genesis. let transactions_iter = std::iter::once(GenesisTransactionBuilder { - isi: vec![UpgradeExpr::new(Executor::try_from(raw_block.executor)?).into()], + isi: vec![Upgrade::new(Executor::try_from(raw_block.executor)?).into()], }) .chain(raw_block.transactions); @@ -190,7 +190,7 @@ impl ExecutorPath { #[repr(transparent)] pub struct GenesisTransactionBuilder { /// Instructions - isi: Vec, + isi: Vec, } impl GenesisTransactionBuilder { @@ -208,7 +208,7 @@ impl GenesisTransactionBuilder { } /// Add new instruction to the transaction. - pub fn append_instruction(&mut self, instruction: InstructionExpr) { + pub fn append_instruction(&mut self, instruction: InstructionBox) { self.isi.push(instruction); } } @@ -287,7 +287,7 @@ impl RawGenesisBlockBuilder { let new_domain = Domain::new(domain_id.clone()).with_metadata(metadata); self.transaction .isi - .push(RegisterExpr::new(new_domain).into()); + .push(Register::domain(new_domain).into()); RawGenesisDomainBuilder { transaction: self.transaction, domain_id, @@ -322,7 +322,7 @@ impl RawGenesisDomainBuilder { let account_id = AccountId::new(account_name, self.domain_id.clone()); self.transaction .isi - .push(RegisterExpr::new(Account::new(account_id, [])).into()); + .push(Register::account(Account::new(account_id, [])).into()); self } @@ -340,7 +340,7 @@ impl RawGenesisDomainBuilder { ) -> Self { let account_id = AccountId::new(account_name, self.domain_id.clone()); let register = - RegisterExpr::new(Account::new(account_id, [public_key]).with_metadata(metadata)); + Register::account(Account::new(account_id, [public_key]).with_metadata(metadata)); self.transaction.isi.push(register.into()); self } @@ -356,7 +356,7 @@ impl RawGenesisDomainBuilder { }; self.transaction .isi - .push(RegisterExpr::new(asset_definition).into()); + .push(Register::asset_definition(asset_definition).into()); self } } @@ -418,11 +418,11 @@ mod tests { let domain_id: DomainId = "wonderland".parse().unwrap(); assert_eq!( finished_genesis_block.transactions[0].isi[0], - RegisterExpr::new(Domain::new(domain_id.clone())).into() + Register::domain(Domain::new(domain_id.clone())).into() ); assert_eq!( finished_genesis_block.transactions[0].isi[1], - RegisterExpr::new(Account::new( + Register::account(Account::new( AccountId::new("alice".parse().unwrap(), domain_id.clone()), [] )) @@ -430,7 +430,7 @@ mod tests { ); assert_eq!( finished_genesis_block.transactions[0].isi[2], - RegisterExpr::new(Account::new( + Register::account(Account::new( AccountId::new("bob".parse().unwrap(), domain_id), [] )) @@ -441,11 +441,11 @@ mod tests { let domain_id: DomainId = "tulgey_wood".parse().unwrap(); assert_eq!( finished_genesis_block.transactions[0].isi[3], - RegisterExpr::new(Domain::new(domain_id.clone())).into() + Register::domain(Domain::new(domain_id.clone())).into() ); assert_eq!( finished_genesis_block.transactions[0].isi[4], - RegisterExpr::new(Account::new( + Register::account(Account::new( AccountId::new("Cheshire_Cat".parse().unwrap(), domain_id), [] )) @@ -456,11 +456,11 @@ mod tests { let domain_id: DomainId = "meadow".parse().unwrap(); assert_eq!( finished_genesis_block.transactions[0].isi[5], - RegisterExpr::new(Domain::new(domain_id.clone())).into() + Register::domain(Domain::new(domain_id.clone())).into() ); assert_eq!( finished_genesis_block.transactions[0].isi[6], - RegisterExpr::new(Account::new( + Register::account(Account::new( AccountId::new("Mad_Hatter".parse().unwrap(), domain_id), [public_key.parse().unwrap()], )) @@ -468,7 +468,7 @@ mod tests { ); assert_eq!( finished_genesis_block.transactions[0].isi[7], - RegisterExpr::new(AssetDefinition::big_quantity( + Register::asset_definition(AssetDefinition::big_quantity( "hats#meadow".parse().unwrap() )) .into() diff --git a/p2p/src/peer.rs b/p2p/src/peer.rs index e08a93f6aca..182b72e9e7d 100644 --- a/p2p/src/peer.rs +++ b/p2p/src/peer.rs @@ -409,6 +409,7 @@ mod state { } impl ConnectedTo { + #[allow(clippy::similar_names)] pub(super) async fn send_client_hello( Self { peer_addr, @@ -452,6 +453,7 @@ mod state { } impl ConnectedFrom { + #[allow(clippy::similar_names)] pub(super) async fn read_client_hello( Self { peer_addr, diff --git a/schema/derive/src/lib.rs b/schema/derive/src/lib.rs index 233a901e5cb..3f801b67459 100644 --- a/schema/derive/src/lib.rs +++ b/schema/derive/src/lib.rs @@ -65,6 +65,7 @@ impl FromMeta for Transparent { #[darling(attributes(schema))] struct SchemaAttributes { transparent: Transparent, + bounds: Option, } // NOTE: this will fail on unknown attributes.. This is not ideal @@ -184,12 +185,16 @@ pub fn schema_derive(input: TokenStream) -> Result { let impl_type_id = impl_type_id(&mut syn2::parse2(original_input).unwrap()); let impl_schema = match &input.schema_attrs.transparent { - Transparent::NotTransparent => impl_into_schema(&input), + Transparent::NotTransparent => impl_into_schema(&input, input.schema_attrs.bounds.as_ref()), Transparent::Transparent(transparent_type) => { let transparent_type = transparent_type .clone() .unwrap_or_else(|| infer_transparent_type(&input.data, &mut emitter)); - Ok(impl_transparent_into_schema(&input, &transparent_type)) + impl_transparent_into_schema( + &input, + &transparent_type, + input.schema_attrs.bounds.as_ref(), + ) } }; let impl_schema = match impl_schema { @@ -211,11 +216,16 @@ pub fn schema_derive(input: TokenStream) -> Result { fn impl_transparent_into_schema( input: &IntoSchemaInput, transparent_type: &syn2::Type, -) -> TokenStream { + bounds: Option<&String>, +) -> Result { let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let name = &input.ident; + let where_clause: Option = match bounds { + Some(bounds) => Some(syn2::parse_str(&format!("where {bounds}"))?), + None => where_clause.cloned(), + }; - quote! { + Ok(quote! { impl #impl_generics iroha_schema::IntoSchema for #name #ty_generics #where_clause { fn update_schema_map(map: &mut iroha_schema::MetaMap) { if !map.contains_key::() { @@ -233,14 +243,18 @@ fn impl_transparent_into_schema( <#transparent_type as iroha_schema::IntoSchema>::type_name() } } - } + }) } -fn impl_into_schema(input: &IntoSchemaInput) -> Result { +fn impl_into_schema(input: &IntoSchemaInput, bounds: Option<&String>) -> Result { let name = &input.ident; let type_name_body = trait_body(name, &input.generics, false); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let metadata = metadata(&input.data)?; + let where_clause: Option = match bounds { + Some(bounds) => Some(syn2::parse_str(&format!("where {bounds}"))?), + None => where_clause.cloned(), + }; Ok(quote! { impl #impl_generics iroha_schema::IntoSchema for #name #ty_generics #where_clause { diff --git a/schema/derive/tests/ui_pass/derive_into_schema.rs b/schema/derive/tests/ui_pass/derive_into_schema.rs index 576fb77f5ce..f75295a3f82 100644 --- a/schema/derive/tests/ui_pass/derive_into_schema.rs +++ b/schema/derive/tests/ui_pass/derive_into_schema.rs @@ -42,4 +42,14 @@ pub enum Enum { FortyTwo, } +pub trait Trait { + type Assoc; +} + +#[derive(IntoSchema)] +#[schema(bounds = "T: Trait, T::Assoc: IntoSchema")] +pub struct WithComplexGeneric { + _value: T::Assoc, +} + pub fn main() {} diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index ddac0fad272..ef3c1cab1f8 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -66,13 +66,13 @@ types!( AccountEventFilter, AccountFilter, AccountId, + AccountMintBox, AccountPermissionChanged, AccountRoleChanged, Action, - Add, Algorithm, - And, Asset, + AssetBurnBox, AssetChanged, AssetDefinition, AssetDefinitionEvent, @@ -85,6 +85,8 @@ types!( AssetEventFilter, AssetFilter, AssetId, + AssetMintBox, + AssetTransferBox, AssetValue, AssetValueType, AtIndex, @@ -92,7 +94,6 @@ types!( BTreeMap, BTreeMap, BTreeMap, - BTreeMap>, BTreeMap, BTreeSet, BTreeSet, @@ -106,53 +107,31 @@ types!( BlockSubscriptionRequest, Box, Box>, - Box, Box, Box, - BurnExpr, - ConditionalExpr, + BurnBox, ConfigurationEvent, ConstString, Container, - Contains, - ContainsAll, - ContainsAny, - ContextValue, DataEntityFilter, DataEvent, DataEventFilter, - Divide, Domain, DomainEvent, DomainEventFilter, DomainFilter, DomainId, Duration, - Equal, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo, - EvaluatesTo>, - EvaluatesTo, Event, EventMessage, EventSubscriptionRequest, Executable, - ExecuteTriggerExpr, + ExecuteTrigger, ExecuteTriggerEvent, ExecuteTriggerEventFilter, ExecutionTime, - Expression, + Executor, + ExecutorEvent, Fail, FilterBox, FilterOpt, @@ -220,16 +199,14 @@ types!( FixNum, Fixed, ForwardCursor, - GrantExpr, - Greater, + GrantBox, Hash, HashOf>, HashOf, HashOf, IdBox, IdentifiableBox, - If, - InstructionExpr, + InstructionBox, InstructionExecutionFail, Interval, Interval, @@ -239,7 +216,6 @@ types!( Ipv6Addr, Ipv6Predicate, LengthLimits, - Less, MerkleTree, Metadata, MetadataChanged, @@ -247,19 +223,16 @@ types!( MetadataChanged, MetadataChanged, MetadataLimits, - MintExpr, + MintBox, Mintable, - Mod, - Multiply, Name, NewAccount, NewAssetDefinition, NewDomain, - NewParameterExpr, + NewParameter, NewRole, NonTrivial, NonZeroU64, - Not, NotificationEventFilter, NumericValue, Option, @@ -267,7 +240,6 @@ types!( Option, Option>>, Option>, - Option, Option, Option, Option, @@ -275,7 +247,6 @@ types!( Option, Option, Option, - Or, OriginFilter, OriginFilter, OriginFilter, @@ -283,7 +254,6 @@ types!( OriginFilter, OriginFilter, OriginFilter, - PairExpr, Parameter, ParameterId, Peer, @@ -306,12 +276,11 @@ types!( QueryBox, QueryExecutionFail, QueryPayload, - RaiseTo, - RegisterExpr, + RegisterBox, RegistrableBox, - RemoveKeyValueExpr, + RemoveKeyValueBox, Repeats, - RevokeExpr, + RevokeBox, Role, RoleEvent, RoleEventFilter, @@ -321,9 +290,8 @@ types!( SemiInterval, SemiInterval, SemiRange, - SequenceExpr, - SetKeyValueExpr, - SetParameterExpr, + SetKeyValueBox, + SetParameter, Signature, SignatureCheckCondition, SignatureOf, @@ -339,7 +307,6 @@ types!( SignedTransactionV1, String, StringPredicate, - Subtract, TimeEvent, TimeEventFilter, TimeInterval, @@ -350,7 +317,7 @@ types!( TransactionQueryOutput, TransactionRejectionReason, TransactionValue, - TransferExpr, + TransferBox, Trigger, TriggerCompletedEventFilter, TriggerCompletedOutcomeType, @@ -360,16 +327,14 @@ types!( TriggerId, TriggerNumberOfExecutionsChanged, TriggeringFilterBox, - UnregisterExpr, + UnregisterBox, UpgradableBox, ValidationFail, - Executor, - ExecutorEvent, Value, ValueOfKey, ValuePredicate, Vec, - Vec, + Vec, Vec, Vec, Vec, @@ -377,7 +342,6 @@ types!( Vec, WasmExecutionFail, WasmSmartContract, - Where, [Interval; 8], [Interval; 4], [u16; 8], @@ -465,40 +429,18 @@ mod tests { let mut missing_schemas = HashMap::<&str, _>::new(); for type_name in type_names { - if let (Some(mut start), Some(end)) = (type_name.find('<'), type_name.rfind('>')) { - start += 1; - - let mut angle_bracket_diff = 0_u8; - for (i, c) in type_name[start..end].chars().enumerate() { - if c == '<' { - angle_bracket_diff += 1_u8; - } - if c == '>' { - angle_bracket_diff -= 1_u8; - } - - if c == ',' && angle_bracket_diff == 0_u8 { - let generic = type_name[start..(start + i)].trim(); + let (Some(start), Some(end)) = (type_name.find('<'), type_name.rfind('>')) else { + continue; + }; - start += i + 1; - if !is_const_generic(generic) { - continue; - } + assert!(start < end, "Invalid type name: {type_name}"); - if !type_names.contains(generic) { - missing_schemas - .entry(type_name) - .or_insert_with(Vec::new) - .push(generic); - } - } + for generic in type_name.split(", ") { + if !is_const_generic(generic) { + continue; } - let generic = type_name[start..end].trim(); - if !generic.is_empty() - && !is_const_generic(generic) - && !type_names.contains(generic) - { + if !type_names.contains(generic) { missing_schemas .entry(type_name) .or_insert_with(Vec::new) diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs index 6045bd40b82..fa12601e750 100644 --- a/smart_contract/executor/derive/src/default.rs +++ b/smart_contract/executor/derive/src/default.rs @@ -54,7 +54,7 @@ pub fn impl_derive_entrypoints(emitter: &mut Emitter, input: &syn2::DeriveInput) #[::iroha_executor::prelude::entrypoint] pub fn validate_instruction( authority: ::iroha_executor::prelude::AccountId, - instruction: ::iroha_executor::prelude::InstructionExpr, + instruction: ::iroha_executor::prelude::InstructionBox, block_height: u64, #(#custom_args),* ) -> ::iroha_executor::prelude::Result { @@ -107,54 +107,63 @@ pub fn impl_derive_entrypoints(emitter: &mut Emitter, input: &syn2::DeriveInput) } } +#[allow(clippy::too_many_lines)] pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { return quote!(); }; let ExecutorDeriveInput { ident, custom, .. } = &input; let default_visit_sigs: Vec = [ - "fn visit_unsupported(operation: T)", "fn visit_transaction(operation: &SignedTransaction)", - "fn visit_instruction(operation: &InstructionExpr)", - "fn visit_expression(operation: &EvaluatesTo)", - "fn visit_sequence(operation: &SequenceExpr)", - "fn visit_if(operation: &ConditionalExpr)", - "fn visit_pair(operation: &PairExpr)", - "fn visit_unregister_peer(operation: Unregister)", - "fn visit_unregister_domain(operation: Unregister)", - "fn visit_transfer_domain(operation: Transfer)", - "fn visit_set_domain_key_value(operation: SetKeyValue)", - "fn visit_remove_domain_key_value(operation: RemoveKeyValue)", - "fn visit_unregister_account(operation: Unregister)", - "fn visit_mint_account_public_key(operation: Mint)", - "fn visit_burn_account_public_key(operation: Burn)", - "fn visit_mint_account_signature_check_condition(operation: Mint)", - "fn visit_set_account_key_value(operation: SetKeyValue)", - "fn visit_remove_account_key_value(operation: RemoveKeyValue)", - "fn visit_register_asset(operation: Register)", - "fn visit_unregister_asset(operation: Unregister)", - "fn visit_mint_asset(operation: Mint)", - "fn visit_burn_asset(operation: Burn)", - "fn visit_transfer_asset(operation: Transfer)", - "fn visit_set_asset_key_value(operation: SetKeyValue)", - "fn visit_remove_asset_key_value(operation: RemoveKeyValue)", - "fn visit_unregister_asset_definition(operation: Unregister)", - "fn visit_transfer_asset_definition(operation: Transfer)", - "fn visit_set_asset_definition_key_value(operation: SetKeyValue)", - "fn visit_remove_asset_definition_key_value(operation: RemoveKeyValue)", - "fn visit_grant_account_permission(operation: Grant)", - "fn visit_revoke_account_permission(operation: Revoke)", - "fn visit_register_role(operation: Register)", - "fn visit_unregister_role(operation: Unregister)", - "fn visit_grant_account_role(operation: Grant)", - "fn visit_revoke_account_role(operation: Revoke)", - "fn visit_unregister_trigger(operation: Unregister>)", - "fn visit_mint_trigger_repetitions(operation: Mint>)", - "fn visit_burn_trigger_repetitions(operation: Burn>)", - "fn visit_execute_trigger(operation: ExecuteTrigger)", - "fn visit_set_parameter(operation: SetParameter)", - "fn visit_new_parameter(operation: NewParameter)", - "fn visit_upgrade_executor(operation: Upgrade)", + "fn visit_instruction(operation: &InstructionBox)", + "fn visit_register_peer(operation: &Register)", + "fn visit_unregister_peer(operation: &Unregister)", + "fn visit_register_domain(operation: &Register)", + "fn visit_unregister_domain(operation: &Unregister)", + "fn visit_transfer_domain(operation: &Transfer)", + "fn visit_set_domain_key_value(operation: &SetKeyValue)", + "fn visit_remove_domain_key_value(operation: &RemoveKeyValue)", + "fn visit_register_account(operation: &Register)", + "fn visit_unregister_account(operation: &Unregister)", + "fn visit_mint_account_public_key(operation: &Mint)", + "fn visit_burn_account_public_key(operation: &Burn)", + "fn visit_mint_account_signature_check_condition(operation: &Mint)", + "fn visit_set_account_key_value(operation: &SetKeyValue)", + "fn visit_remove_account_key_value(operation: &RemoveKeyValue)", + "fn visit_register_asset(operation: &Register)", + "fn visit_unregister_asset(operation: &Unregister)", + "fn visit_mint_asset_quantity(operation: &Mint)", + "fn visit_burn_asset_quantity(operation: &Burn)", + "fn visit_mint_asset_big_quantity(operation: &Mint)", + "fn visit_burn_asset_big_quantity(operation: &Burn)", + "fn visit_mint_asset_fixed(operation: &Mint)", + "fn visit_burn_asset_fixed(operation: &Burn)", + "fn visit_transfer_asset_quantity(operation: &Transfer)", + "fn visit_transfer_asset_big_quantity(operation: &Transfer)", + "fn visit_transfer_asset_fixed(operation: &Transfer)", + "fn visit_set_asset_key_value(operation: &SetKeyValue)", + "fn visit_remove_asset_key_value(operation: &RemoveKeyValue)", + "fn visit_register_asset_definition(operation: &Register)", + "fn visit_unregister_asset_definition(operation: &Unregister)", + "fn visit_transfer_asset_definition(operation: &Transfer)", + "fn visit_set_asset_definition_key_value(operation: &SetKeyValue)", + "fn visit_remove_asset_definition_key_value(operation: &RemoveKeyValue)", + "fn visit_grant_account_permission(operation: &Grant)", + "fn visit_revoke_account_permission(operation: &Revoke)", + "fn visit_register_role(operation: &Register)", + "fn visit_unregister_role(operation: &Unregister)", + "fn visit_grant_account_role(operation: &Grant)", + "fn visit_revoke_account_role(operation: &Revoke)", + "fn visit_register_trigger(operation: &Register>)", + "fn visit_unregister_trigger(operation: &Unregister>)", + "fn visit_mint_trigger_repetitions(operation: &Mint>)", + "fn visit_burn_trigger_repetitions(operation: &Burn>)", + "fn visit_execute_trigger(operation: &ExecuteTrigger)", + "fn visit_set_parameter(operation: &SetParameter)", + "fn visit_new_parameter(operation: &NewParameter)", + "fn visit_upgrade(operation: &Upgrade)", + "fn visit_log(operation: &Log)", + "fn visit_fail(operation: &Fail)", ] .into_iter() .map(|item| { @@ -228,29 +237,6 @@ pub fn impl_derive_validate(emitter: &mut Emitter, input: &syn2::DeriveInput) -> } } -pub fn impl_derive_expression_evaluator( - emitter: &mut Emitter, - input: &syn2::DeriveInput, -) -> TokenStream2 { - let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { - return quote!(); - }; - let ExecutorDeriveInput { ident, data, .. } = &input; - check_required_fields(data, emitter); - quote! { - impl ::iroha_executor::data_model::evaluate::ExpressionEvaluator for #ident { - fn evaluate( - &self, - expression: &E, - ) -> ::core::result::Result - { - self.host.evaluate(expression) - } - } - - } -} - pub fn impl_derive_constructor(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { return quote!(); @@ -268,7 +254,6 @@ pub fn impl_derive_constructor(emitter: &mut Emitter, input: &syn2::DeriveInput) Self { verdict: Ok(()), block_height, - host: ::iroha_executor::smart_contract::Host, #(#custom_idents),* } } @@ -278,7 +263,8 @@ pub fn impl_derive_constructor(emitter: &mut Emitter, input: &syn2::DeriveInput) } fn check_required_fields(ast: &ExecutorData, emitter: &mut Emitter) { - let required_fields: syn2::FieldsNamed = parse_quote!({ verdict: ::iroha_executor::prelude::Result, block_height: u64, host: ::iroha_executor::smart_contract::Host }); + let required_fields: syn2::FieldsNamed = + parse_quote!({ verdict: ::iroha_executor::prelude::Result, block_height: u64 }); let struct_fields = ast .as_ref() .take_struct() @@ -328,7 +314,7 @@ fn check_type_equivalence(full_ty: &syn2::Type, given_ty: &syn2::Type) -> bool { /// Processes an `Executor` by draining it of default fields and returning the idents of the /// custom fields and the corresponding function arguments for use in the constructor fn custom_field_idents_and_fn_args(ast: &ExecutorData) -> (Vec<&Ident>, Vec) { - let required_idents: Vec = ["verdict", "block_height", "host"] + let required_idents: Vec = ["verdict", "block_height"] .iter() .map(|s| Ident::new(s, Span::call_site())) .collect(); diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index 228357ca7ef..71d682c974c 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -233,9 +233,7 @@ pub fn derive_ref_into_domain_owner(input: TokenStream) -> Result { } /// Implements the `iroha_executor::Validate` trait for the given `Executor` struct. As -/// this trait has a `iroha_executor::prelude::Visit`, and the latter has an -/// `iroha_executor::data_model::evaluate::ExpressionEvaluator` -/// bound, at least these two should be implemented as well. +/// this trait has a `iroha_executor::prelude::Visit` at least this one should be implemented as well. /// /// Emits a compile error if the struct didn't have all the expected fields with corresponding /// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and @@ -271,7 +269,7 @@ pub fn derive_validate(input: TokenStream) -> TokenStream { /// ```ignore /// use iroha_executor::{smart_contract, prelude::*}; /// -/// #[derive(Constructor, Entrypoints, ExpressionEvaluator, Validate, Visit)] +/// #[derive(Constructor, Entrypoints, Validate, Visit)] /// #[visit(custom(visit_query)] /// pub struct Executor { /// verdict: Result, @@ -318,7 +316,7 @@ pub fn derive_visit(input: TokenStream) -> TokenStream { /// ```ignore /// use iroha_executor::{smart_contract, prelude::*}; /// -/// #[derive(Constructor, Entrypoints, ExpressionEvaluator, Validate, Visit)] +/// #[derive(Constructor, Entrypoints, Validate, Visit)] /// #[entrypoints(custom(validate_query))] /// pub struct Executor { /// verdict: Result, @@ -341,27 +339,6 @@ pub fn derive_entrypoints(input: TokenStream) -> TokenStream { emitter.finish_token_stream_with(result) } -/// Implements `iroha_executor::data_model::evaluate::ExpressionEvaluator` trait -/// for the given `Executor` struct. -/// -/// Emits a compile error if the struct didn't have all the expected fields with corresponding -/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and -/// `host`: `iroha_executor::smart_contract::Host`, though technically only `host` is needed. -/// The types can be unqualified, but not aliased. -#[manyhow] -#[proc_macro_derive(ExpressionEvaluator)] -pub fn derive_expression_evaluator(input: TokenStream) -> TokenStream { - let mut emitter = Emitter::new(); - - let Some(input) = emitter.handle(syn2::parse2(input)) else { - return emitter.finish_token_stream(); - }; - - let result = default::impl_derive_expression_evaluator(&mut emitter, &input); - - emitter.finish_token_stream_with(result) -} - /// Implements a constructor for the given `Executor` struct. If the `Executor` has any custom fields /// (i.e. different from the expected fields listed below), they will be included into the constructor /// automatically and will need to be passed into `new()` function explicitly. In the default case, diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index 771439e9742..5b70d96a884 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -7,48 +7,42 @@ use alloc::format; pub use account::{ visit_burn_account_public_key, visit_mint_account_public_key, - visit_mint_account_signature_check_condition, visit_remove_account_key_value, - visit_set_account_key_value, visit_unregister_account, + visit_mint_account_signature_check_condition, visit_register_account, + visit_remove_account_key_value, visit_set_account_key_value, visit_unregister_account, }; pub use asset::{ - visit_burn_asset, visit_mint_asset, visit_register_asset, visit_remove_asset_key_value, - visit_set_asset_key_value, visit_transfer_asset, visit_unregister_asset, + visit_burn_asset_big_quantity, visit_burn_asset_fixed, visit_burn_asset_quantity, + visit_mint_asset_big_quantity, visit_mint_asset_fixed, visit_mint_asset_quantity, + visit_register_asset, visit_remove_asset_key_value, visit_set_asset_key_value, + visit_transfer_asset_big_quantity, visit_transfer_asset_fixed, visit_transfer_asset_quantity, + visit_unregister_asset, }; pub use asset_definition::{ - visit_remove_asset_definition_key_value, visit_set_asset_definition_key_value, - visit_transfer_asset_definition, visit_unregister_asset_definition, + visit_register_asset_definition, visit_remove_asset_definition_key_value, + visit_set_asset_definition_key_value, visit_transfer_asset_definition, + visit_unregister_asset_definition, }; pub use domain::{ - visit_remove_domain_key_value, visit_set_domain_key_value, visit_transfer_domain, - visit_unregister_domain, + visit_register_domain, visit_remove_domain_key_value, visit_set_domain_key_value, + visit_transfer_domain, visit_unregister_domain, }; -pub use executor::visit_upgrade_executor; -use iroha_smart_contract::debug::DebugExpectExt as _; +pub use executor::visit_upgrade; +pub use fail::visit_fail; +use iroha_smart_contract::data_model::isi::InstructionBox; +pub use log::visit_log; pub use parameter::{visit_new_parameter, visit_set_parameter}; -pub use peer::visit_unregister_peer; +pub use peer::{visit_register_peer, visit_unregister_peer}; pub use permission_token::{visit_grant_account_permission, visit_revoke_account_permission}; pub use role::{ visit_grant_account_role, visit_register_role, visit_revoke_account_role, visit_unregister_role, }; pub use trigger::{ visit_burn_trigger_repetitions, visit_execute_trigger, visit_mint_trigger_repetitions, - visit_unregister_trigger, + visit_register_trigger, visit_unregister_trigger, }; use crate::{permission, permission::Token as _, prelude::*}; -macro_rules! evaluate_expr { - ($visitor:ident, $authority:ident, <$isi:ident as $isi_type:ty>::$field:ident()) => {{ - $visitor.visit_expression($authority, $isi.$field()); - - $visitor.evaluate($isi.$field()).dbg_expect(&alloc::format!( - "Failed to evaluate field '{}::{}'", - stringify!($isi_type), - stringify!($field), - )) - }}; -} - pub fn default_permission_token_schema() -> PermissionTokenSchema { let mut schema = iroha_executor::PermissionTokenSchema::default(); @@ -86,7 +80,7 @@ pub fn visit_transaction( } } -/// Default validation for [`InstructionExpr`]. +/// Default validation for [`InstructionBox`]. /// /// # Warning /// @@ -94,198 +88,53 @@ pub fn visit_transaction( pub fn visit_instruction( executor: &mut V, authority: &AccountId, - isi: &InstructionExpr, + isi: &InstructionBox, ) { - macro_rules! isi_executors { - ( - single {$( - $executor:ident($isi:ident) - ),+ $(,)?} - composite {$( - $composite_executor:ident($composite_isi:ident) - ),+ $(,)?} - ) => { - match isi { - InstructionExpr::NewParameter(isi) => { - let parameter = evaluate_expr!(executor, authority, ::parameter()); - executor.visit_new_parameter(authority, NewParameter{parameter}); - - if executor.verdict().is_ok() { - isi_executors!(@execute isi); - } - } - InstructionExpr::SetParameter(isi) => { - let parameter = evaluate_expr!(executor, authority, ::parameter()); - executor.visit_set_parameter(authority, SetParameter{parameter}); - - if executor.verdict().is_ok() { - isi_executors!(@execute isi); - } - } - InstructionExpr::ExecuteTrigger(isi) => { - let trigger_id = evaluate_expr!(executor, authority, ::trigger_id()); - executor.visit_execute_trigger(authority, ExecuteTrigger{trigger_id}); - - if executor.verdict().is_ok() { - isi_executors!(@execute isi); - } - } - InstructionExpr::Log(isi) => { - let msg = evaluate_expr!(executor, authority, ::msg()); - let level = evaluate_expr!(executor, authority, ::level()); - executor.visit_log(authority, Log{level, msg}); - - if executor.verdict().is_ok() { - isi_executors!(@execute isi); - } - } $( - InstructionExpr::$isi(isi) => { - executor.$executor(authority, isi); - - if executor.verdict().is_ok() { - isi_executors!(@execute isi); - } - } )+ $( - // NOTE: `visit_and_execute_instructions` is reentrant, so don't execute composite instructions - InstructionExpr::$composite_isi(isi) => executor.$composite_executor(authority, isi), )+ - } - }; - (@execute $isi:ident) => { - // TODO: Execution should be infallible after successful validation - if let Err(err) = isi.execute() { - executor.deny(err); - } + match isi { + InstructionBox::NewParameter(isi) => { + executor.visit_new_parameter(authority, isi); } - } - - isi_executors! { - single { - visit_burn(Burn), - visit_fail(Fail), - visit_grant(Grant), - visit_mint(Mint), - visit_register(Register), - visit_remove_key_value(RemoveKeyValue), - visit_revoke(Revoke), - visit_set_key_value(SetKeyValue), - visit_transfer(Transfer), - visit_unregister(Unregister), - visit_upgrade(Upgrade), - } - - composite { - visit_sequence(Sequence), - visit_pair(Pair), - visit_if(If), + InstructionBox::SetParameter(isi) => { + executor.visit_set_parameter(authority, isi); } - } -} - -pub fn visit_unsupported( - executor: &mut V, - _authority: &AccountId, - isi: T, -) { - deny!(executor, "{isi:?}: Unsupported operation"); -} - -pub fn visit_expression( - executor: &mut V, - authority: &AccountId, - expression: &EvaluatesTo, -) { - macro_rules! visit_binary_expression { - ($e:ident) => {{ - executor.visit_expression(authority, $e.left()); - - if executor.verdict().is_ok() { - executor.visit_expression(authority, $e.right()); - } - }}; - } - - match expression.expression() { - Expression::Add(expr) => visit_binary_expression!(expr), - Expression::Subtract(expr) => visit_binary_expression!(expr), - Expression::Multiply(expr) => visit_binary_expression!(expr), - Expression::Divide(expr) => visit_binary_expression!(expr), - Expression::Mod(expr) => visit_binary_expression!(expr), - Expression::RaiseTo(expr) => visit_binary_expression!(expr), - Expression::Greater(expr) => visit_binary_expression!(expr), - Expression::Less(expr) => visit_binary_expression!(expr), - Expression::Equal(expr) => visit_binary_expression!(expr), - Expression::Not(expr) => executor.visit_expression(authority, expr.expression()), - Expression::And(expr) => visit_binary_expression!(expr), - Expression::Or(expr) => visit_binary_expression!(expr), - Expression::If(expr) => { - executor.visit_expression(authority, expr.condition()); - - if executor.verdict().is_ok() { - executor.visit_expression(authority, expr.then()); - } - - if executor.verdict().is_ok() { - executor.visit_expression(authority, expr.otherwise()); - } + InstructionBox::Log(isi) => { + executor.visit_log(authority, isi); } - Expression::Contains(expr) => { - executor.visit_expression(authority, expr.collection()); - - if executor.verdict().is_ok() { - executor.visit_expression(authority, expr.element()); - } + InstructionBox::ExecuteTrigger(isi) => { + executor.visit_execute_trigger(authority, isi); } - Expression::ContainsAll(expr) => { - executor.visit_expression(authority, expr.collection()); - - if executor.verdict().is_ok() { - executor.visit_expression(authority, expr.elements()); - } + InstructionBox::Burn(isi) => { + executor.visit_burn(authority, isi); } - Expression::ContainsAny(expr) => { - executor.visit_expression(authority, expr.collection()); - - if executor.verdict().is_ok() { - executor.visit_expression(authority, expr.elements()); - } + InstructionBox::Fail(isi) => { + executor.visit_fail(authority, isi); } - Expression::Where(expr) => executor.visit_expression(authority, expr.expression()), - Expression::Query(query) => executor.visit_query(authority, query), - Expression::ContextValue(_) | Expression::Raw(_) => (), - } -} - -pub fn visit_if( - executor: &mut V, - authority: &AccountId, - isi: &ConditionalExpr, -) { - let condition = evaluate_expr!(executor, authority, ::condition()); - - // TODO: Do we have to make sure both branches are syntactically valid? - if condition { - executor.visit_instruction(authority, isi.then()); - } else if let Some(otherwise) = isi.otherwise() { - executor.visit_instruction(authority, otherwise); - } -} - -pub fn visit_pair(executor: &mut V, authority: &AccountId, isi: &PairExpr) { - executor.visit_instruction(authority, isi.left_instruction()); - - if executor.verdict().is_ok() { - executor.visit_instruction(authority, isi.right_instruction()) - } -} - -pub fn visit_sequence( - executor: &mut V, - authority: &AccountId, - sequence: &SequenceExpr, -) { - for isi in sequence.instructions() { - if executor.verdict().is_ok() { - executor.visit_instruction(authority, isi); + InstructionBox::Grant(isi) => { + executor.visit_grant(authority, isi); + } + InstructionBox::Mint(isi) => { + executor.visit_mint(authority, isi); + } + InstructionBox::Register(isi) => { + executor.visit_register(authority, isi); + } + InstructionBox::RemoveKeyValue(isi) => { + executor.visit_remove_key_value(authority, isi); + } + InstructionBox::Revoke(isi) => { + executor.visit_revoke(authority, isi); + } + InstructionBox::SetKeyValue(isi) => { + executor.visit_set_key_value(authority, isi); + } + InstructionBox::Transfer(isi) => { + executor.visit_transfer(authority, isi); + } + InstructionBox::Unregister(isi) => { + executor.visit_unregister(authority, isi); + } + InstructionBox::Upgrade(isi) => { + executor.visit_upgrade(authority, isi); } } } @@ -293,17 +142,25 @@ pub fn visit_sequence( pub mod peer { use super::*; + pub fn visit_register_peer( + executor: &mut V, + _authority: &AccountId, + isi: &Register, + ) { + execute!(executor, isi) + } + #[allow(clippy::needless_pass_by_value)] pub fn visit_unregister_peer( executor: &mut V, authority: &AccountId, - _isi: Unregister, + isi: &Unregister, ) { if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } if tokens::peer::CanUnregisterAnyPeer.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't unregister peer"); @@ -315,24 +172,34 @@ pub mod domain { use super::*; + pub fn visit_register_domain( + executor: &mut V, + _authority: &AccountId, + isi: &Register, + ) { + execute!(executor, isi) + } + pub fn visit_unregister_domain( executor: &mut V, authority: &AccountId, - isi: Unregister, + isi: &Unregister, ) { - let domain_id = isi.object_id; + let domain_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_domain_owner(&domain_id, authority) { + match is_domain_owner(domain_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_unregister_domain_token = tokens::domain::CanUnregisterDomain { domain_id }; + let can_unregister_domain_token = tokens::domain::CanUnregisterDomain { + domain_id: domain_id.clone(), + }; if can_unregister_domain_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't unregister domain"); @@ -341,16 +208,16 @@ pub mod domain { pub fn visit_transfer_domain( executor: &mut V, authority: &AccountId, - isi: Transfer, + isi: &Transfer, ) { - let destination_id = isi.object; + let destination_id = isi.object(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_domain_owner(&destination_id, authority) { + match is_domain_owner(destination_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } @@ -360,22 +227,23 @@ pub mod domain { pub fn visit_set_domain_key_value( executor: &mut V, authority: &AccountId, - isi: SetKeyValue, + isi: &SetKeyValue, ) { - let domain_id = isi.object_id; + let domain_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_domain_owner(&domain_id, authority) { + match is_domain_owner(domain_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_domain_token = - tokens::domain::CanSetKeyValueInDomain { domain_id }; + let can_set_key_value_in_domain_token = tokens::domain::CanSetKeyValueInDomain { + domain_id: domain_id.clone(), + }; if can_set_key_value_in_domain_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't set key value in domain metadata"); @@ -384,22 +252,23 @@ pub mod domain { pub fn visit_remove_domain_key_value( executor: &mut V, authority: &AccountId, - isi: RemoveKeyValue, + isi: &RemoveKeyValue, ) { - let domain_id = isi.object_id; + let domain_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_domain_owner(&domain_id, authority) { + match is_domain_owner(domain_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_remove_key_value_in_domain_token = - tokens::domain::CanRemoveKeyValueInDomain { domain_id }; + let can_remove_key_value_in_domain_token = tokens::domain::CanRemoveKeyValueInDomain { + domain_id: domain_id.clone(), + }; if can_remove_key_value_in_domain_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't remove key value in domain metadata"); @@ -411,24 +280,34 @@ pub mod account { use super::*; + pub fn visit_register_account( + executor: &mut V, + _authority: &AccountId, + isi: &Register, + ) { + execute!(executor, isi) + } + pub fn visit_unregister_account( executor: &mut V, authority: &AccountId, - isi: Unregister, + isi: &Unregister, ) { - let account_id = isi.object_id; + let account_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_unregister_user_account = tokens::account::CanUnregisterAccount { account_id }; + let can_unregister_user_account = tokens::account::CanUnregisterAccount { + account_id: account_id.clone(), + }; if can_unregister_user_account.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't unregister another account"); @@ -437,21 +316,23 @@ pub mod account { pub fn visit_mint_account_public_key( executor: &mut V, authority: &AccountId, - isi: Mint, + isi: &Mint, ) { - let account_id = isi.destination_id; + let account_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_public_keys = tokens::account::CanMintUserPublicKeys { account_id }; + let can_mint_user_public_keys = tokens::account::CanMintUserPublicKeys { + account_id: account_id.clone(), + }; if can_mint_user_public_keys.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't mint public keys of another account"); @@ -460,21 +341,23 @@ pub mod account { pub fn visit_burn_account_public_key( executor: &mut V, authority: &AccountId, - isi: Burn, + isi: &Burn, ) { - let account_id = isi.destination_id; + let account_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_burn_user_public_keys = tokens::account::CanBurnUserPublicKeys { account_id }; + let can_burn_user_public_keys = tokens::account::CanBurnUserPublicKeys { + account_id: account_id.clone(), + }; if can_burn_user_public_keys.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't burn public keys of another account"); @@ -483,22 +366,24 @@ pub mod account { pub fn visit_mint_account_signature_check_condition( executor: &mut V, authority: &AccountId, - isi: Mint, + isi: &Mint, ) { - let account_id = isi.destination_id; + let account_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_mint_user_signature_check_conditions_token = - tokens::account::CanMintUserSignatureCheckConditions { account_id }; + tokens::account::CanMintUserSignatureCheckConditions { + account_id: account_id.clone(), + }; if can_mint_user_signature_check_conditions_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -510,22 +395,24 @@ pub mod account { pub fn visit_set_account_key_value( executor: &mut V, authority: &AccountId, - isi: SetKeyValue, + isi: &SetKeyValue, ) { - let account_id = isi.object_id; + let account_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_set_key_value_in_user_account_token = - tokens::account::CanSetKeyValueInUserAccount { account_id }; + tokens::account::CanSetKeyValueInUserAccount { + account_id: account_id.clone(), + }; if can_set_key_value_in_user_account_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -537,22 +424,24 @@ pub mod account { pub fn visit_remove_account_key_value( executor: &mut V, authority: &AccountId, - isi: RemoveKeyValue, + isi: &RemoveKeyValue, ) { - let account_id = isi.object_id; + let account_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&account_id, authority) { + match is_account_owner(account_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_remove_key_value_in_user_account_token = - tokens::account::CanRemoveKeyValueInUserAccount { account_id }; + tokens::account::CanRemoveKeyValueInUserAccount { + account_id: account_id.clone(), + }; if can_remove_key_value_in_user_account_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -567,27 +456,35 @@ pub mod asset_definition { use super::*; + pub fn visit_register_asset_definition( + executor: &mut V, + _authority: &AccountId, + isi: &Register, + ) { + execute!(executor, isi); + } + pub fn visit_unregister_asset_definition( executor: &mut V, authority: &AccountId, - isi: Unregister, + isi: &Unregister, ) { - let asset_definition_id = isi.object_id; + let asset_definition_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_definition_owner(&asset_definition_id, authority) { + match is_asset_definition_owner(asset_definition_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_unregister_asset_definition_token = tokens::asset_definition::CanUnregisterAssetDefinition { - asset_definition_id, + asset_definition_id: asset_definition_id.clone(), }; if can_unregister_asset_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -599,22 +496,22 @@ pub mod asset_definition { pub fn visit_transfer_asset_definition( executor: &mut V, authority: &AccountId, - isi: Transfer, + isi: &Transfer, ) { - let source_id = isi.source_id; - let destination_id = isi.object; + let source_id = isi.source_id(); + let destination_id = isi.object(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_account_owner(&source_id, authority) { + match is_account_owner(source_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - match is_asset_definition_owner(&destination_id, authority) { + match is_asset_definition_owner(destination_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } @@ -627,24 +524,24 @@ pub mod asset_definition { pub fn visit_set_asset_definition_key_value( executor: &mut V, authority: &AccountId, - isi: SetKeyValue, + isi: &SetKeyValue, ) { - let asset_definition_id = isi.object_id; + let asset_definition_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_definition_owner(&asset_definition_id, authority) { + match is_asset_definition_owner(asset_definition_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_set_key_value_in_asset_definition_token = tokens::asset_definition::CanSetKeyValueInAssetDefinition { - asset_definition_id, + asset_definition_id: asset_definition_id.clone(), }; if can_set_key_value_in_asset_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -656,24 +553,24 @@ pub mod asset_definition { pub fn visit_remove_asset_definition_key_value( executor: &mut V, authority: &AccountId, - isi: RemoveKeyValue, + isi: &RemoveKeyValue, ) { - let asset_definition_id = isi.object_id; + let asset_definition_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_definition_owner(&asset_definition_id, authority) { + match is_asset_definition_owner(asset_definition_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_remove_key_value_in_asset_definition_token = tokens::asset_definition::CanRemoveKeyValueInAssetDefinition { - asset_definition_id, + asset_definition_id: asset_definition_id.clone(), }; if can_remove_key_value_in_asset_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -684,6 +581,8 @@ pub mod asset_definition { } pub mod asset { + use iroha_smart_contract::data_model::isi::Instruction; + use iroha_smart_contract_utils::Encode; use permission::{asset::is_asset_owner, asset_definition::is_asset_definition_owner}; use super::*; @@ -691,16 +590,16 @@ pub mod asset { pub fn visit_register_asset( executor: &mut V, authority: &AccountId, - isi: Register, + isi: &Register, ) { - let asset = isi.object; + let asset = isi.object(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } match is_asset_definition_owner(asset.id().definition_id(), authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_register_assets_with_definition_token = @@ -708,7 +607,7 @@ pub mod asset { asset_definition_id: asset.id().definition_id().clone(), }; if can_register_assets_with_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -720,21 +619,21 @@ pub mod asset { pub fn visit_unregister_asset( executor: &mut V, authority: &AccountId, - isi: Unregister, + isi: &Unregister, ) { - let asset_id = isi.object_id; + let asset_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_owner(&asset_id, authority) { + match is_asset_owner(asset_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } match is_asset_definition_owner(asset_id.definition_id(), authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_unregister_assets_with_definition_token = @@ -742,36 +641,38 @@ pub mod asset { asset_definition_id: asset_id.definition_id().clone(), }; if can_unregister_assets_with_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } - let can_unregister_user_asset_token = tokens::asset::CanUnregisterUserAsset { asset_id }; + let can_unregister_user_asset_token = tokens::asset::CanUnregisterUserAsset { + asset_id: asset_id.clone(), + }; if can_unregister_user_asset_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't unregister asset from another account"); } - pub fn visit_mint_asset( - executor: &mut V, - authority: &AccountId, - isi: Mint, - ) { - let asset_id = isi.destination_id; - + fn validate_mint_asset(executor: &mut V, authority: &AccountId, isi: &Mint) + where + V: Validate + ?Sized, + Q: Into, + Mint: Instruction + Encode + Clone, + { + let asset_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } match is_asset_definition_owner(asset_id.definition_id(), authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_mint_assets_with_definition_token = tokens::asset::CanMintAssetsWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_mint_assets_with_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -780,58 +681,111 @@ pub mod asset { ); } - pub fn visit_burn_asset( + pub fn visit_mint_asset_quantity( executor: &mut V, authority: &AccountId, - isi: Burn, + isi: &Mint, ) { - let asset_id = isi.destination_id; + validate_mint_asset(executor, authority, isi); + } + pub fn visit_mint_asset_big_quantity( + executor: &mut V, + authority: &AccountId, + isi: &Mint, + ) { + validate_mint_asset(executor, authority, isi); + } + + pub fn visit_mint_asset_fixed( + executor: &mut V, + authority: &AccountId, + isi: &Mint, + ) { + validate_mint_asset(executor, authority, isi); + } + + fn validate_burn_asset(executor: &mut V, authority: &AccountId, isi: &Burn) + where + V: Validate + ?Sized, + Q: Into, + Burn: Instruction + Encode + Clone, + { + let asset_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_owner(&asset_id, authority) { + match is_asset_owner(asset_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } match is_asset_definition_owner(asset_id.definition_id(), authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_burn_assets_with_definition_token = tokens::asset::CanBurnAssetsWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_burn_assets_with_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } - let can_burn_user_asset_token = tokens::asset::CanBurnUserAsset { asset_id }; + let can_burn_user_asset_token = tokens::asset::CanBurnUserAsset { + asset_id: asset_id.clone(), + }; if can_burn_user_asset_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't burn assets from another account"); } - pub fn visit_transfer_asset( + pub fn visit_burn_asset_quantity( + executor: &mut V, + authority: &AccountId, + isi: &Burn, + ) { + validate_burn_asset(executor, authority, isi); + } + + pub fn visit_burn_asset_big_quantity( + executor: &mut V, + authority: &AccountId, + isi: &Burn, + ) { + validate_burn_asset(executor, authority, isi); + } + + pub fn visit_burn_asset_fixed( executor: &mut V, authority: &AccountId, - isi: Transfer, + isi: &Burn, ) { - let asset_id = isi.source_id; + validate_burn_asset(executor, authority, isi); + } + fn validate_transfer_asset( + executor: &mut V, + authority: &AccountId, + isi: &Transfer, + ) where + V: Validate + ?Sized, + Q: Into, + Transfer: Instruction + Encode + Clone, + { + let asset_id = isi.source_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_owner(&asset_id, authority) { + match is_asset_owner(asset_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } match is_asset_definition_owner(asset_id.definition_id(), authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_transfer_assets_with_definition_token = @@ -839,36 +793,63 @@ pub mod asset { asset_definition_id: asset_id.definition_id().clone(), }; if can_transfer_assets_with_definition_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } - let can_transfer_user_asset_token = tokens::asset::CanTransferUserAsset { asset_id }; + let can_transfer_user_asset_token = tokens::asset::CanTransferUserAsset { + asset_id: asset_id.clone(), + }; if can_transfer_user_asset_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't transfer assets of another account"); } + pub fn visit_transfer_asset_quantity( + executor: &mut V, + authority: &AccountId, + isi: &Transfer, + ) { + validate_transfer_asset(executor, authority, isi); + } + + pub fn visit_transfer_asset_big_quantity( + executor: &mut V, + authority: &AccountId, + isi: &Transfer, + ) { + validate_transfer_asset(executor, authority, isi); + } + + pub fn visit_transfer_asset_fixed( + executor: &mut V, + authority: &AccountId, + isi: &Transfer, + ) { + validate_transfer_asset(executor, authority, isi); + } + pub fn visit_set_asset_key_value( executor: &mut V, authority: &AccountId, - isi: SetKeyValue, + isi: &SetKeyValue, ) { - let asset_id = isi.object_id; + let asset_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_owner(&asset_id, authority) { + match is_asset_owner(asset_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_user_asset_token = - tokens::asset::CanSetKeyValueInUserAsset { asset_id }; + let can_set_key_value_in_user_asset_token = tokens::asset::CanSetKeyValueInUserAsset { + asset_id: asset_id.clone(), + }; if can_set_key_value_in_user_asset_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -880,22 +861,24 @@ pub mod asset { pub fn visit_remove_asset_key_value( executor: &mut V, authority: &AccountId, - isi: RemoveKeyValue, + isi: &RemoveKeyValue, ) { - let asset_id = isi.object_id; + let asset_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_asset_owner(&asset_id, authority) { + match is_asset_owner(asset_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } let can_remove_key_value_in_user_asset_token = - tokens::asset::CanRemoveKeyValueInUserAsset { asset_id }; + tokens::asset::CanRemoveKeyValueInUserAsset { + asset_id: asset_id.clone(), + }; if can_remove_key_value_in_user_asset_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -912,13 +895,13 @@ pub mod parameter { pub fn visit_new_parameter( executor: &mut V, authority: &AccountId, - _isi: NewParameter, + isi: &NewParameter, ) { if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } if tokens::parameter::CanCreateParameters.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -931,13 +914,13 @@ pub mod parameter { pub fn visit_set_parameter( executor: &mut V, authority: &AccountId, - _isi: SetParameter, + isi: &SetParameter, ) { if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } if tokens::parameter::CanSetParameters.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -952,9 +935,9 @@ pub mod role { macro_rules! impl_validate { ($executor:ident, $isi:ident, $authority:ident, $method:ident) => { - let role_id = $isi.object; + let role_id = $isi.object(); - let find_role_query_res = match FindRoleByRoleId::new(role_id).execute() { + let find_role_query_res = match FindRoleByRoleId::new(role_id.clone()).execute() { Ok(res) => res.into_raw_parts().0, Err(error) => { deny!($executor, error); @@ -986,6 +969,7 @@ pub mod role { } assert!(unknown_tokens.is_empty(), "Role contains unknown permission tokens: {unknown_tokens:?}"); + execute!($executor, $isi) }; } @@ -993,9 +977,9 @@ pub mod role { pub fn visit_register_role( executor: &mut V, _authority: &AccountId, - isi: Register, + isi: &Register, ) { - let role = isi.object.inner(); + let role = isi.object().inner(); let mut unknown_tokens = Vec::new(); for token in role.permissions() { @@ -1021,20 +1005,20 @@ pub mod role { ); } - pass!(executor); + execute!(executor, isi); } #[allow(clippy::needless_pass_by_value)] pub fn visit_unregister_role( executor: &mut V, authority: &AccountId, - _isi: Unregister, + isi: &Unregister, ) { if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } if tokens::role::CanUnregisterAnyRole.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't unregister role"); @@ -1043,7 +1027,7 @@ pub mod role { pub fn visit_grant_account_role( executor: &mut V, authority: &AccountId, - isi: Grant, + isi: &Grant, ) { impl_validate!(executor, isi, authority, validate_grant); } @@ -1051,7 +1035,7 @@ pub mod role { pub fn visit_revoke_account_role( executor: &mut V, authority: &AccountId, - isi: Revoke, + isi: &Revoke, ) { impl_validate!(executor, isi, authority, validate_revoke); } @@ -1062,25 +1046,34 @@ pub mod trigger { use super::*; + pub fn visit_register_trigger( + executor: &mut V, + _authority: &AccountId, + isi: &Register>, + ) { + execute!(executor, isi) + } + pub fn visit_unregister_trigger( executor: &mut V, authority: &AccountId, - isi: Unregister>, + isi: &Unregister>, ) { - let trigger_id = isi.object_id; + let trigger_id = isi.object_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_trigger_owner(&trigger_id, authority) { + match is_trigger_owner(trigger_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_unregister_user_trigger_token = - tokens::trigger::CanUnregisterUserTrigger { trigger_id }; + let can_unregister_user_trigger_token = tokens::trigger::CanUnregisterUserTrigger { + trigger_id: trigger_id.clone(), + }; if can_unregister_user_trigger_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -1092,21 +1085,23 @@ pub mod trigger { pub fn visit_mint_trigger_repetitions( executor: &mut V, authority: &AccountId, - isi: Mint>, + isi: &Mint>, ) { - let trigger_id = isi.destination_id; + let trigger_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_trigger_owner(&trigger_id, authority) { + match is_trigger_owner(trigger_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanMintUserTrigger { trigger_id }; + let can_mint_user_trigger_token = tokens::trigger::CanMintUserTrigger { + trigger_id: trigger_id.clone(), + }; if can_mint_user_trigger_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -1118,21 +1113,23 @@ pub mod trigger { pub fn visit_burn_trigger_repetitions( executor: &mut V, authority: &AccountId, - isi: Burn>, + isi: &Burn>, ) { - let trigger_id = isi.destination_id; + let trigger_id = isi.destination_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_trigger_owner(&trigger_id, authority) { + match is_trigger_owner(trigger_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanBurnUserTrigger { trigger_id }; + let can_mint_user_trigger_token = tokens::trigger::CanBurnUserTrigger { + trigger_id: trigger_id.clone(), + }; if can_mint_user_trigger_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!( @@ -1144,21 +1141,23 @@ pub mod trigger { pub fn visit_execute_trigger( executor: &mut V, authority: &AccountId, - isi: ExecuteTrigger, + isi: &ExecuteTrigger, ) { - let trigger_id = isi.trigger_id; + let trigger_id = isi.trigger_id(); if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } - match is_trigger_owner(&trigger_id, authority) { + match is_trigger_owner(trigger_id, authority) { Err(err) => deny!(executor, err), - Ok(true) => pass!(executor), + Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_execute_trigger_token = tokens::trigger::CanExecuteUserTrigger { trigger_id }; + let can_execute_trigger_token = tokens::trigger::CanExecuteUserTrigger { + trigger_id: trigger_id.clone(), + }; if can_execute_trigger_token.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't execute trigger owned by another account"); @@ -1169,13 +1168,14 @@ pub mod permission_token { use super::*; macro_rules! impl_validate { - ($executor:ident, $authority:ident, $self:ident, $method:ident) => { - let token = $self.object; + ($executor:ident, $authority:ident, $isi:ident, $method:ident) => { + // TODO: https://github.com/hyperledger/iroha/issues/4082 + let token = $isi.object().clone(); macro_rules! visit_internal { ($token:ident) => { if is_genesis($executor) { - pass!($executor); + execute!($executor, $isi); } if let Err(error) = permission::ValidateGrantRevoke::$method( &$token, @@ -1185,7 +1185,7 @@ pub mod permission_token { deny!($executor, error); } - pass!($executor); + execute!($executor, $isi); }; } @@ -1201,7 +1201,7 @@ pub mod permission_token { pub fn visit_grant_account_permission( executor: &mut V, authority: &AccountId, - isi: Grant, + isi: &Grant, ) { impl_validate!(executor, authority, isi, validate_grant); } @@ -1209,7 +1209,7 @@ pub mod permission_token { pub fn visit_revoke_account_permission( executor: &mut V, authority: &AccountId, - isi: Revoke, + isi: &Revoke, ) { impl_validate!(executor, authority, isi, validate_revoke); } @@ -1219,22 +1219,38 @@ pub mod executor { use super::*; #[allow(clippy::needless_pass_by_value)] - pub fn visit_upgrade_executor( + pub fn visit_upgrade( executor: &mut V, authority: &AccountId, - _isi: Upgrade, + isi: &Upgrade, ) { if is_genesis(executor) { - pass!(executor); + execute!(executor, isi); } if tokens::executor::CanUpgradeExecutor.is_owned_by(authority) { - pass!(executor); + execute!(executor, isi); } deny!(executor, "Can't upgrade executor"); } } +pub mod log { + use super::*; + + pub fn visit_log(executor: &mut V, _authority: &AccountId, isi: &Log) { + execute!(executor, isi) + } +} + +pub mod fail { + use super::*; + + pub fn visit_fail(executor: &mut V, _authority: &AccountId, isi: &Fail) { + execute!(executor, isi) + } +} + fn is_genesis(executor: &V) -> bool { executor.block_height() == 0 } diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 8b9a7403a52..ef953f78f14 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -49,7 +49,7 @@ pub fn get_validate_transaction_payload() -> payloads::Validate payloads::Validate { +pub fn get_validate_instruction_payload() -> payloads::Validate { // Safety: ownership of the returned result is transferred into `_decode_from_raw` unsafe { decode_with_length_prefix_from_raw(host::get_validate_instruction_payload()) } } @@ -131,20 +131,23 @@ mod host { } } -/// Shortcut for `return Ok(())`. +/// Execute instruction if verdict is [`Ok`], deny if execution failed and return. +/// +/// Convention is that you have no checks left if you decided to execute instruction. #[macro_export] -macro_rules! pass { - ($executor:ident) => {{ - #[cfg(debug_assertions)] - if let Err(_error) = $executor.verdict() { - unreachable!("Executor already denied"); +macro_rules! execute { + ($executor:ident, $isi:ident) => {{ + if $executor.verdict().is_ok() { + if let Err(err) = $isi.execute() { + $executor.deny(err); + } } return; }}; } -/// Shortcut for `return Err(ValidationFail)`. +/// Shortcut for setting verdict to [`Err`] and return. /// /// Supports [`format!`](alloc::fmt::format) syntax as well as any expression returning [`String`](alloc::string::String). #[macro_export] @@ -219,10 +222,9 @@ pub mod prelude { pub use alloc::vec::Vec; pub use iroha_executor_derive::{ - entrypoint, Constructor, ExpressionEvaluator, Token, Validate, ValidateEntrypoints, - ValidateGrantRevoke, Visit, + entrypoint, Constructor, Token, Validate, ValidateEntrypoints, ValidateGrantRevoke, Visit, }; - pub use iroha_smart_contract::{prelude::*, Context}; + pub use iroha_smart_contract::prelude::*; pub use super::{ data_model::{ @@ -230,6 +232,6 @@ pub mod prelude { visit::Visit, ValidationFail, }, - deny, pass, PermissionTokenSchema, Validate, + deny, execute, PermissionTokenSchema, Validate, }; } diff --git a/smart_contract/src/lib.rs b/smart_contract/src/lib.rs index 45232f9cdfb..2bdd93706db 100644 --- a/smart_contract/src/lib.rs +++ b/smart_contract/src/lib.rs @@ -4,14 +4,14 @@ extern crate alloc; -use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; +use alloc::{boxed::Box, vec::Vec}; #[cfg(not(test))] use data_model::smart_contract::payloads; use data_model::{ isi::Instruction, prelude::*, - query::{cursor::ForwardCursor, sorting::Sorting, Pagination, Query, QueryBox}, + query::{cursor::ForwardCursor, sorting::Sorting, Pagination, Query}, smart_contract::SmartContractQueryRequest, BatchedResponse, }; @@ -88,7 +88,7 @@ impl ExecuteOnHost for I { use tests::_iroha_smart_contract_execute_instruction_mock as host_execute_instruction; // TODO: Redundant conversion into `InstructionExpr` - let isi_box: InstructionExpr = self.clone().into(); + let isi_box: InstructionBox = self.clone().into(); // Safety: `host_execute_instruction` doesn't take ownership of it's pointer parameter unsafe { decode_with_length_prefix_from_raw(encode_and_execute( @@ -374,52 +374,6 @@ pub enum QueryOutputCursorError { #[derive(Debug, Clone, Copy)] pub struct Host; -impl iroha_data_model::evaluate::ExpressionEvaluator for Host { - fn evaluate( - &self, - expression: &E, - ) -> Result { - expression.evaluate(&Context::new()) - } -} - -/// Context of expression evaluation -#[derive(Clone, Default)] -#[repr(transparent)] -pub struct Context { - values: BTreeMap, -} - -impl Context { - /// Create new [`Self`] - pub fn new() -> Self { - Self { - values: BTreeMap::new(), - } - } -} - -impl iroha_data_model::evaluate::Context for Context { - fn query(&self, query: &QueryBox) -> Result { - let value_cursor = query.clone().execute()?; - match value_cursor.collect() { - Ok(value) => Ok(value), - Err(QueryOutputCursorError::Validation(err)) => Err(err), - Err(QueryOutputCursorError::Conversion(err)) => { - panic!("Conversion error during collecting query result: {err:?}") - } - } - } - - fn get(&self, name: &Name) -> Option<&Value> { - self.values.get(name) - } - - fn update(&mut self, other: impl IntoIterator) { - self.values.extend(other) - } -} - /// Get payload for smart contract `main()` entrypoint. #[cfg(not(test))] pub fn get_smart_contract_payload() -> payloads::SmartContract { @@ -481,11 +435,10 @@ mod tests { cursor: ForwardCursor::new(None, None), }); const ISI_RESULT: Result<(), ValidationFail> = Ok(()); - const EXPRESSION_RESULT: NumericValue = NumericValue::U32(5_u32); - fn get_test_instruction() -> InstructionExpr { + fn get_test_instruction() -> InstructionBox { let new_account_id = "mad_hatter@wonderland".parse().expect("Valid"); - let register_isi = RegisterExpr::new(Account::new(new_account_id, [])); + let register_isi = Register::account(Account::new(new_account_id, [])); register_isi.into() } @@ -495,17 +448,13 @@ mod tests { FindAssetQuantityById::new(asset_id).into() } - fn get_test_expression() -> EvaluatesTo { - Add::new(2_u32, 3_u32).into() - } - #[no_mangle] pub unsafe extern "C" fn _iroha_smart_contract_execute_instruction_mock( ptr: *const u8, len: usize, ) -> *const u8 { let bytes = slice::from_raw_parts(ptr, len); - let instruction = InstructionExpr::decode_all(&mut &*bytes); + let instruction = InstructionBox::decode_all(&mut &*bytes); assert_eq!(get_test_instruction(), instruction.unwrap()); ManuallyDrop::new(encode_with_length_prefix(&ISI_RESULT)).as_ptr() @@ -538,12 +487,4 @@ mod tests { fn execute_query() { assert_eq!(get_test_query().execute(), QUERY_RESULT); } - - #[webassembly_test] - fn evaluate_expression() { - assert_eq!( - get_test_expression().evaluate(&Context::new()), - Ok(EXPRESSION_RESULT) - ); - } } diff --git a/smart_contract/utils/src/lib.rs b/smart_contract/utils/src/lib.rs index ec9f70a242e..5e0919b095a 100644 --- a/smart_contract/utils/src/lib.rs +++ b/smart_contract/utils/src/lib.rs @@ -7,7 +7,7 @@ extern crate alloc; use alloc::{boxed::Box, format, vec::Vec}; use core::ops::RangeFrom; -use parity_scale_codec::{DecodeAll, Encode}; +pub use parity_scale_codec::{DecodeAll, Encode}; pub mod debug; pub mod log; diff --git a/tools/kagami/src/genesis.rs b/tools/kagami/src/genesis.rs index 48caeb74337..6036e4723ab 100644 --- a/tools/kagami/src/genesis.rs +++ b/tools/kagami/src/genesis.rs @@ -4,11 +4,9 @@ use clap::{ArgGroup, Parser, Subcommand}; use iroha_config::{sumeragi::default::*, wasm::default::*, wsv::default::*}; use iroha_data_model::{ asset::AssetValueType, - isi::{MintExpr, RegisterExpr}, metadata::Limits, parameter::{default::*, ParametersBuilder}, prelude::AssetId, - IdBox, }; use iroha_genesis::{ExecutorMode, ExecutorPath, RawGenesisBlock, RawGenesisBlockBuilder}; use serde_json::json; @@ -146,22 +144,19 @@ pub fn generate_default(executor: ExecutorMode) -> color_eyre::Result color_eyre::Result::new( - vec![MintExpr::new(1_u32, rose_id)], + vec![Mint::asset_quantity(1_u32, rose_id)], Repeats::Indefinitely, account_id, FilterBox::Data(DataEventFilter::BySome(DataEntityFilter::ByAccount(