From c620fbe1631425f2826bee7eb8c27c6b970a5655 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Tue, 28 May 2024 10:32:10 +0900 Subject: [PATCH 01/10] feat!: generalize permissions schema to executor data model Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/src/client.rs | 43 ++-- client/tests/integration/events/data.rs | 8 +- client/tests/integration/permissions.rs | 24 ++- client/tests/integration/roles.rs | 13 +- .../executor_remove_permission/src/lib.rs | 15 +- .../src/lib.rs | 35 ++- client/tests/integration/upgrade.rs | 18 +- configs/swarm/executor.wasm | Bin 522309 -> 524647 bytes configs/swarm/genesis.json | 6 +- core/src/smartcontracts/isi/account.rs | 16 +- core/src/smartcontracts/isi/query.rs | 4 +- core/src/smartcontracts/isi/world.rs | 59 ++--- core/src/smartcontracts/wasm.rs | 48 ++--- core/src/state.rs | 70 +++--- data_model/src/events/data/events.rs | 81 +++---- data_model/src/events/data/filters.rs | 6 - data_model/src/executor.rs | 60 +++++- data_model/src/lib.rs | 111 +++++++++- data_model/src/permission.rs | 188 ++++------------ data_model/src/query/mod.rs | 58 +++-- data_model/src/visit.rs | 6 +- default_executor/src/lib.rs | 8 +- docs/source/references/schema.json | 104 +++++---- schema/gen/src/lib.rs | 32 +-- .../executor/derive/src/entrypoint.rs | 2 +- smart_contract/executor/derive/src/lib.rs | 12 +- .../executor/derive/src/permission.rs | 34 +++ smart_contract/executor/derive/src/token.rs | 80 ------- smart_contract/executor/src/default.rs | 187 ++++++++-------- .../src/default/{tokens.rs => permissions.rs} | 204 +++++++++--------- smart_contract/executor/src/lib.rs | 105 ++++++--- smart_contract/executor/src/permission.rs | 97 ++++++--- tools/parity_scale_cli/src/main.rs | 46 +--- 33 files changed, 905 insertions(+), 875 deletions(-) create mode 100644 smart_contract/executor/derive/src/permission.rs delete mode 100644 smart_contract/executor/derive/src/token.rs rename smart_contract/executor/src/default/{tokens.rs => permissions.rs} (77%) diff --git a/client/src/client.rs b/client/src/client.rs index f1079c1138a..5c53801742f 100644 --- a/client/src/client.rs +++ b/client/src/client.rs @@ -102,7 +102,7 @@ where You are likely using a version of the client library \ that is incompatible with the version of the peer software", ) - .map_err(Into::into) + .map_err(Into::into) } StatusCode::BAD_REQUEST | StatusCode::UNAUTHORIZED @@ -111,22 +111,22 @@ where | StatusCode::UNPROCESSABLE_ENTITY => Err(ValidationFail::decode_all( &mut resp.body().as_ref(), ) - .map_or_else( - |_| { - ClientQueryError::Other( - ResponseReport::with_msg("Query failed", resp) - .map_or_else( - |_| eyre!( + .map_or_else( + |_| { + ClientQueryError::Other( + ResponseReport::with_msg("Query failed", resp) + .map_or_else( + |_| eyre!( "Failed to decode response from Iroha. \ Response is neither a `ValidationFail` encoded value nor a valid utf-8 string error response. \ You are likely using a version of the client library that is incompatible with the version of the peer software", ), - Into::into - ), - ) - }, - ClientQueryError::Validation, - )), + Into::into + ), + ) + }, + ClientQueryError::Validation, + )), _ => Err(ResponseReport::with_msg("Unexpected query response", resp).unwrap_or_else(core::convert::identity).into()), } } @@ -328,7 +328,7 @@ impl_query_output! { crate::data_model::block::BlockHeader, crate::data_model::metadata::MetadataValueBox, crate::data_model::query::TransactionQueryOutput, - crate::data_model::permission::PermissionSchema, + crate::data_model::executor::ExecutorDataModel, crate::data_model::trigger::Trigger, crate::data_model::prelude::Numeric, } @@ -1517,11 +1517,6 @@ pub mod permission { //! Module with queries for permission tokens use super::*; - /// Construct a query to get all registered [`PermissionDefinition`]s - pub const fn permission_schema() -> FindPermissionSchema { - FindPermissionSchema {} - } - /// Construct a query to get all [`Permission`] granted /// to account with given [`Id`][AccountId] pub fn by_account_id(account_id: AccountId) -> FindPermissionsByAccountId { @@ -1564,6 +1559,16 @@ pub mod parameter { } } +pub mod executor { + //! Queries for executor entities + use super::*; + + /// Retrieve executor data model + pub const fn data_model() -> FindExecutorDataModel { + FindExecutorDataModel + } +} + #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 9d29901409f..7dcbe278d2c 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -240,13 +240,13 @@ fn produce_multiple_events() -> Result<()> { DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_1.definition_id.clone(), + permission_id: token_1.id.clone(), }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionAdded( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_2.definition_id.clone(), + permission_id: token_2.id.clone(), }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleGranted( @@ -258,13 +258,13 @@ fn produce_multiple_events() -> Result<()> { DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_1.definition_id, + permission_id: token_1.id, }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id: bob_id.clone(), - permission_id: token_2.definition_id, + permission_id: token_2.id, }, ))), DataEvent::Domain(DomainEvent::Account(AccountEvent::RoleRevoked( diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 678d7b71b1a..d2bc825b8c3 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -8,6 +8,7 @@ use iroha_client::{ }; use iroha_data_model::{ permission::Permission, role::RoleId, transaction::error::TransactionRejectionReason, + JsonString, }; use iroha_genesis::GenesisNetwork; use serde_json::json; @@ -315,10 +316,14 @@ 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 = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "xor#wonderland#{mouse_id}" }}"###), + JsonString::from_json_string_unchecked(format!( + // Introducing some whitespaces + // This way, if the executor compares just JSON strings, this test would fail + r##"{{ "asset_id" : "xor#wonderland#{}" }}"##, + mouse_id + )), ), alice_id, ); @@ -349,19 +354,18 @@ fn permissions_are_unified() { let alice_id = ALICE_ID.clone(); let allow_alice_to_transfer_rose_1 = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "rose#wonderland#{alice_id}" }}"###), + json!({ "asset_id": format!("rose#wonderland#{alice_id}") }), ), alice_id.clone(), ); let allow_alice_to_transfer_rose_2 = Grant::permission( - Permission::from_str_unchecked( + Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - &*format!(r###"{{ "asset_id" : "rose##{alice_id}" }}"###), + // different content, but same meaning + json!({ "asset_id": format!("rose##{alice_id}") }), ), alice_id, ); @@ -372,7 +376,7 @@ fn permissions_are_unified() { let _ = iroha_client .submit_blocking(allow_alice_to_transfer_rose_2) - .expect_err("permission tokens are not unified"); + .expect_err("should reject due to duplication"); } #[test] diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 6efab725cd5..a27875fff2a 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -172,20 +172,19 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { #[test] #[allow(deprecated)] -fn role_permissions_unified() { +fn role_permissions_are_deduplicated() { let (_rt, _peer, test_client) = ::new().with_port(11_235).start_with_runtime(); wait_for_genesis_committed(&vec![test_client.clone()], 0); - let allow_alice_to_transfer_rose_1 = Permission::from_str_unchecked( + let allow_alice_to_transfer_rose_1 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - "{ \"asset_id\" : \"rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland\" }", + json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); - let allow_alice_to_transfer_rose_2 = Permission::from_str_unchecked( + // Different content, but same meaning + let allow_alice_to_transfer_rose_2 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - // NOTE: Introduced additional whitespaces in the serialized form - "{ \"asset_id\" : \"rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland\" }", + json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); let role_id: RoleId = "role_id".parse().expect("Valid"); diff --git a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs index 9ed9fba3b5d..a81d80355f4 100644 --- a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs @@ -3,11 +3,12 @@ #![no_std] -extern crate alloc; #[cfg(not(test))] extern crate panic_halt; -use iroha_executor::{default::default_permission_schema, prelude::*}; +use iroha_executor::{ + default::permissions::domain::CanUnregisterDomain, prelude::*, DataModelBuilder, +}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -25,13 +26,9 @@ struct Executor { pub fn migrate(_block_height: u64) -> MigrationResult { // Note that actually migration will reset token schema to default (minus `CanUnregisterDomain`) // So any added custom permission tokens will be also removed - let mut schema = default_permission_schema(); - schema.remove::(); - - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions() + .remove_permission::() + .set(); Ok(()) } diff --git a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs index 3c4033465c1..1fb8612595f 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs @@ -3,9 +3,9 @@ //! //! This executor should be applied on top of the blockchain with default validation. //! -//! It also doesn't have [`iroha_executor::default::tokens::domain::CanUnregisterDomain`]. +//! It also doesn't have [`iroha_executor::default::permissions::domain::CanUnregisterDomain`]. //! -//! In migration it replaces [`iroha_executor::default::tokens::domain::CanUnregisterDomain`] +//! In migration it replaces [`iroha_executor::default::permissions::domain::CanUnregisterDomain`] //! with [`token::CanControlDomainLives`] for all accounts. //! So it doesn't matter which domain user was able to unregister before migration, they will //! get access to control all domains. Remember that this is just a test example. @@ -16,10 +16,10 @@ extern crate alloc; #[cfg(not(test))] extern crate panic_halt; -use alloc::{borrow::ToOwned, string::String}; +use alloc::string::String; use anyhow::anyhow; -use iroha_executor::{default::default_permission_schema, permission::Token as _, prelude::*}; +use iroha_executor::{prelude::*, DataModelBuilder}; use iroha_schema::IntoSchema; use lol_alloc::{FreeListAllocator, LockedAllocator}; use parity_scale_codec::{Decode, Encode}; @@ -42,7 +42,7 @@ mod token { #[derive( PartialEq, Eq, - Token, + Permission, ValidateGrantRevoke, Decode, Encode, @@ -95,7 +95,9 @@ impl Executor { })?; if let Ok(can_unregister_domain_token) = - iroha_executor::default::tokens::domain::CanUnregisterDomain::try_from(&token) + iroha_executor::default::permissions::domain::CanUnregisterDomain::try_from_object( + &token, + ) { found_accounts.push((account, can_unregister_domain_token.domain_id)); break; @@ -107,13 +109,10 @@ impl Executor { } fn replace_token(accounts: &[(Account, DomainId)]) -> MigrationResult { - let can_unregister_domain_definition_id = PermissionId::try_from( - iroha_executor::default::tokens::domain::CanUnregisterDomain::type_name(), - ) - .unwrap(); + let can_unregister_domain_definition_id = + iroha_executor::default::permissions::domain::CanUnregisterDomain::id(); - let can_control_domain_lives_definition_id = - PermissionId::try_from(token::CanControlDomainLives::type_name()).unwrap(); + let can_control_domain_lives_definition_id = token::CanControlDomainLives::id(); accounts .iter() @@ -199,14 +198,10 @@ fn visit_unregister_domain( pub fn migrate(_block_height: u64) -> MigrationResult { let accounts = Executor::get_all_accounts_with_can_unregister_domain_permission()?; - let mut schema = default_permission_schema(); - schema.remove::(); - schema.insert::(); - - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions() + .remove_permission::() + .add_permission::() + .set(); Executor::replace_token(&accounts) } diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 8910e7f4abe..be51bbca183 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -76,8 +76,8 @@ fn executor_upgrade_should_run_migration() -> Result<()> { let can_unregister_domain_token_id = "CanUnregisterDomain".parse().unwrap(); // Check that `CanUnregisterDomain` exists - let definitions = client.request(FindPermissionSchema)?; - assert!(definitions + let data_model = client.request(FindExecutorDataModel)?; + assert!(data_model .permissions() .iter() .any(|id| id == &can_unregister_domain_token_id)); @@ -99,15 +99,15 @@ fn executor_upgrade_should_run_migration() -> Result<()> { )?; // Check that `CanUnregisterDomain` doesn't exist - let definitions = client.request(FindPermissionSchema)?; - assert!(!definitions + let data_model = client.request(FindExecutorDataModel)?; + assert!(!data_model .permissions() .iter() .any(|id| id == &can_unregister_domain_token_id)); let can_control_domain_lives_token_id = "CanControlDomainLives".parse().unwrap(); - assert!(definitions + assert!(data_model .permissions() .iter() .any(|id| id == &can_control_domain_lives_token_id)); @@ -144,9 +144,9 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Check that permission exists assert!(client - .request(FindPermissionSchema)? + .request(FindExecutorDataModel)? .permissions() - .contains(&can_unregister_domain_token.definition_id)); + .contains(&can_unregister_domain_token.id)); // Check that `TEST_ROLE` has permission assert!(client @@ -172,9 +172,9 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Check that permission doesn't exist assert!(!client - .request(FindPermissionSchema)? + .request(FindExecutorDataModel)? .permissions() - .contains(&can_unregister_domain_token.definition_id)); + .contains(&can_unregister_domain_token.id)); // Check that `TEST_ROLE` doesn't have permission assert!(!client diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 8c552ccf2056fc4ab83600ba6fae647b7db903ad..fc5dd07b00c8f9567b6972f6cb828beece79aee9 100644 GIT binary patch delta 194930 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 q?&WtszwP{<;P*#()rgb{^oEvM+Nq$ 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

8Iz-0=nnGn=x#a`#H?<>-IqI{kC%TJBz> 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 Result<(), Error> { let account_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); + let permission_id = permission.id.clone(); // Check if account exists state_transaction.world.account_mut(&account_id)?; if !state_transaction .world - .permission_schema - .token_ids + .executor_data_model + .permissions .contains(&permission_id) { return Err(FindError::Permission(permission_id).into()); @@ -265,7 +265,7 @@ pub mod isi { { return Err(RepetitionError { instruction_type: InstructionType::Grant, - id: permission.definition_id.into(), + id: permission.id.into(), } .into()); } @@ -304,7 +304,7 @@ pub mod isi { .world .remove_account_permission(&account_id, &permission) { - return Err(FindError::Permission(permission.definition_id).into()); + return Err(FindError::Permission(permission.id).into()); } state_transaction @@ -312,7 +312,7 @@ pub mod isi { .emit_events(Some(AccountEvent::PermissionRemoved( AccountPermissionChanged { account_id, - permission_id: permission.definition_id, + permission_id: permission.id, }, ))); @@ -338,7 +338,7 @@ pub mod isi { .clone() .permissions .into_iter() - .map(|token| token.definition_id); + .map(|token| token.id); state_transaction.world.account(&account_id)?; @@ -397,7 +397,7 @@ pub mod isi { .clone() .permissions .into_iter() - .map(|token| token.definition_id); + .map(|token| token.id); if state_transaction .world diff --git a/core/src/smartcontracts/isi/query.rs b/core/src/smartcontracts/isi/query.rs index 4bb37a6e8d0..26b6d603808 100644 --- a/core/src/smartcontracts/isi/query.rs +++ b/core/src/smartcontracts/isi/query.rs @@ -162,7 +162,7 @@ impl_lazy! { iroha_data_model::block::BlockHeader, iroha_data_model::metadata::MetadataValueBox, iroha_data_model::query::TransactionQueryOutput, - iroha_data_model::permission::PermissionSchema, + iroha_data_model::executor::ExecutorDataModel, iroha_data_model::trigger::Trigger, } @@ -259,7 +259,7 @@ impl ValidQuery for QueryBox { FindAccountKeyValueByIdAndKey, FindAssetDefinitionKeyValueByIdAndKey, FindTriggerKeyValueByIdAndKey, - FindPermissionSchema, + FindExecutorDataModel, } FindAllAccounts, diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index ec89111cf2c..66faa222671 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -17,8 +17,9 @@ impl Registrable for NewRole { /// Iroha Special Instructions that have `World` as their target. pub mod isi { + use std::collections::BTreeSet; + use eyre::Result; - use indexmap::IndexSet; use iroha_data_model::{ isi::error::{InstructionExecutionError, InvalidParameterError, RepetitionError}, prelude::*, @@ -162,11 +163,11 @@ pub mod isi { for permission in &role.permissions { if !state_transaction .world - .permission_schema - .token_ids - .contains(&permission.definition_id) + .executor_data_model + .permissions + .contains(&permission.id) { - return Err(FindError::Permission(permission.definition_id.clone()).into()); + return Err(FindError::Permission(permission.id.clone()).into()); } } @@ -227,12 +228,12 @@ pub mod isi { ) -> Result<(), Error> { let role_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); + let permission_id = permission.id.clone(); if !state_transaction .world - .permission_schema - .token_ids + .executor_data_model + .permissions .contains(&permission_id) { return Err(FindError::Permission(permission_id).into()); @@ -245,7 +246,7 @@ pub mod isi { if !role.permissions.insert(permission.clone()) { return Err(RepetitionError { instruction_type: InstructionType::Grant, - id: permission.definition_id.into(), + id: permission.id.into(), } .into()); } @@ -270,7 +271,7 @@ pub mod isi { ) -> Result<(), Error> { let role_id = self.destination_id; let permission = self.object; - let permission_id = permission.definition_id.clone(); + let permission_id = permission.id.clone(); let Some(role) = state_transaction.world.roles.get_mut(&role_id) else { return Err(FindError::Role(role_id).into()); @@ -348,7 +349,11 @@ pub mod isi { ) -> Result<(), Error> { let raw_executor = self.executor; - let permissions_before = state_transaction.world.permission_schema.token_ids.clone(); + let permissions_before = state_transaction + .world + .executor_data_model + .permissions + .clone(); // Cloning executor to avoid multiple mutable borrows of `state_transaction`. // Also it's a cheap operation. @@ -368,7 +373,9 @@ pub mod isi { state_transaction .world - .emit_events(std::iter::once(ExecutorEvent::Upgraded)); + .emit_events(std::iter::once(ExecutorEvent::Upgraded(ExecutorUpgrade { + new_data_model: state_transaction.world.executor_data_model.clone(), + }))); Ok(()) } @@ -377,18 +384,15 @@ pub mod isi { fn revoke_removed_permissions( authority: &AccountId, state_transaction: &mut StateTransaction, - permissions_before: Vec, + permissions_before: BTreeSet, ) -> Result<(), Error> { let world = state_transaction.world(); - let permissions_after = world - .permission_schema() - .token_ids - .iter() - .collect::>(); + // permissions_before.reta + let permissions_after = world.executor_data_model().permissions(); let permissions_removed = permissions_before .into_iter() .filter(|permission| !permissions_after.contains(permission)) - .collect::>(); + .collect::>(); if permissions_removed.is_empty() { return Ok(()); } @@ -409,7 +413,7 @@ pub mod isi { fn find_related_accounts( world: &impl WorldReadOnly, - permissions: &IndexSet, + permissions: &BTreeSet, ) -> Vec<(AccountId, Permission)> { world .account_permissions() @@ -417,7 +421,7 @@ pub mod isi { .flat_map(|(account_id, account_permissions)| { account_permissions .iter() - .filter(|permission| permissions.contains(&permission.definition_id)) + .filter(|permission| permissions.contains(&permission.id)) .map(|permission| (account_id.clone(), permission.clone())) }) .collect() @@ -425,7 +429,7 @@ pub mod isi { fn find_related_roles( world: &impl WorldReadOnly, - permissions: &IndexSet, + permissions: &BTreeSet, ) -> Vec<(RoleId, Permission)> { world .roles() @@ -433,7 +437,7 @@ pub mod isi { .flat_map(|(role_id, role)| { role.permissions .iter() - .filter(|permission| permissions.contains(&permission.definition_id)) + .filter(|permission| permissions.contains(&permission.id)) .map(|permission| (role_id.clone(), permission.clone())) }) .collect() @@ -466,7 +470,6 @@ pub mod query { use iroha_data_model::{ parameter::Parameter, peer::Peer, - permission::PermissionSchema, prelude::*, query::error::{FindError, QueryExecutionFail as Error}, role::{Role, RoleId}, @@ -534,10 +537,10 @@ pub mod query { } } - impl ValidQuery for FindPermissionSchema { - #[metrics("find_permission_schema")] - fn execute(&self, state_ro: &impl StateReadOnly) -> Result { - Ok(state_ro.world().permission_schema().clone()) + impl ValidQuery for FindExecutorDataModel { + #[metrics("find_executor_data_model")] + fn execute(&self, state_ro: &impl StateReadOnly) -> Result { + Ok(state_ro.world().executor_data_model().clone()) } } diff --git a/core/src/smartcontracts/wasm.rs b/core/src/smartcontracts/wasm.rs index 955134976da..1f6d2474193 100644 --- a/core/src/smartcontracts/wasm.rs +++ b/core/src/smartcontracts/wasm.rs @@ -5,13 +5,12 @@ use std::borrow::Borrow; use error::*; -use import::traits::{ExecuteOperations as _, GetExecutorPayloads as _, SetPermissionSchema as _}; +use import::traits::{ExecuteOperations as _, GetExecutorPayloads as _, SetDataModel as _}; use iroha_config::parameters::actual::WasmRuntime as Config; use iroha_data_model::{ account::AccountId, - executor::{self, MigrationResult}, + executor::{self, ExecutorDataModel, MigrationResult}, isi::InstructionBox, - permission::PermissionSchema, prelude::*, query::{QueryBox, QueryId, QueryOutputBox, QueryRequest, SmartContractQuery}, smart_contract::payloads::{self, Validate}, @@ -47,7 +46,7 @@ mod export { pub const GET_VALIDATE_TRANSACTION_PAYLOAD: &str = "get_validate_transaction_payload"; pub const GET_VALIDATE_INSTRUCTION_PAYLOAD: &str = "get_validate_instruction_payload"; pub const GET_VALIDATE_QUERY_PAYLOAD: &str = "get_validate_query_payload"; - pub const SET_PERMISSION_SCHEMA: &str = "set_permission_schema"; + pub const SET_DATA_MODEL: &str = "set_data_model"; pub const DBG: &str = "dbg"; pub const LOG: &str = "log"; @@ -102,9 +101,9 @@ mod import { fn get_validate_query_payload(state: &S) -> Validate; } - pub trait SetPermissionSchema { + pub trait SetDataModel { #[codec::wrap_trait_fn] - fn set_permission_schema(schema: PermissionSchema, state: &mut S); + fn set_data_model(data_model: ExecutorDataModel, state: &mut S); } } } @@ -1076,23 +1075,23 @@ where } } -/// Marker trait to auto-implement [`import_traits::SetPermissionSchema`] for a concrete [`Runtime`]. +/// Marker trait to auto-implement [`import_traits::SetExecutorDataModel`] for a concrete [`Runtime`]. /// /// Useful because *Executor* exposes more entrypoints than just `migrate()` which is the /// only entrypoint allowed to execute operations on permission tokens. -trait FakeSetPermissionSchema { +trait FakeSetExecutorDataModel { /// Entrypoint function name for panic message const ENTRYPOINT_FN_NAME: &'static str; } -impl import::traits::SetPermissionSchema for R +impl import::traits::SetDataModel for R where - R: FakeSetPermissionSchema, + R: FakeSetExecutorDataModel, { #[codec::wrap] - fn set_permission_schema(_schema: PermissionSchema, _state: &mut S) { + fn set_data_model(_model: ExecutorDataModel, _state: &mut S) { panic!( - "Executor `{}()` entrypoint should not set permission token schema", + "Executor `{}()` entrypoint should not set data model", Self::ENTRYPOINT_FN_NAME ) } @@ -1172,7 +1171,7 @@ impl<'wrld, 'block, 'state> } } -impl<'wrld> FakeSetPermissionSchema> +impl<'wrld> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_transaction"; @@ -1252,7 +1251,7 @@ impl<'wrld, 'block, 'state> } } -impl<'wrld> FakeSetPermissionSchema> +impl<'wrld> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_instruction"; @@ -1348,7 +1347,7 @@ impl<'wrld, S: StateReadOnly> } } -impl<'wrld, S: StateReadOnly> FakeSetPermissionSchema> +impl<'wrld, S: StateReadOnly> FakeSetExecutorDataModel> for Runtime> { const ENTRYPOINT_FN_NAME: &'static str = "validate_query"; @@ -1447,17 +1446,17 @@ impl<'wrld, 'block, 'state> } impl<'wrld, 'block, 'state> - import::traits::SetPermissionSchema> + import::traits::SetDataModel> for Runtime> { #[codec::wrap] - fn set_permission_schema( - schema: PermissionSchema, + fn set_data_model( + data_model: ExecutorDataModel, state: &mut state::executor::Migrate<'wrld, 'block, 'state>, ) { - debug!(%schema, "Setting permission token schema"); + debug!(%data_model, "Setting executor data model"); - state.state.0.world.set_permission_schema(schema) + state.state.0.world.set_executor_data_model(data_model) } } @@ -1602,7 +1601,7 @@ impl<'wrld, 'block, 'state> export::GET_VALIDATE_TRANSACTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1630,7 +1629,7 @@ impl<'wrld, 'block, 'state> export::GET_VALIDATE_TRANSACTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1655,7 +1654,7 @@ impl<'wrld, S: StateReadOnly> RuntimeBuilder |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) @@ -1663,6 +1662,7 @@ impl<'wrld, S: StateReadOnly> RuntimeBuilder RuntimeBuilder> { + // FIXME: outdated doc. I guess it executes `migrate` entrypoint? /// Builds the [`Runtime`] to execute `permissions()` entrypoint of *Executor* /// /// # Errors @@ -1679,7 +1679,7 @@ impl<'wrld, 'block, 'state> RuntimeBuilder |caller: ::wasmtime::Caller>| Runtime::get_validate_transaction_payload(caller), export::GET_VALIDATE_INSTRUCTION_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_instruction_payload(caller), export::GET_VALIDATE_QUERY_PAYLOAD => |caller: ::wasmtime::Caller>| Runtime::get_validate_query_payload(caller), - export::SET_PERMISSION_SCHEMA => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_permission_schema(caller, offset, len), + export::SET_DATA_MODEL => |caller: ::wasmtime::Caller>, offset, len| Runtime::set_data_model(caller, offset, len), )?; Ok(linker) }) diff --git a/core/src/state.rs b/core/src/state.rs index 49c2a12fa4d..38a74462a71 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -13,9 +13,10 @@ use iroha_data_model::{ trigger_completed::{TriggerCompletedEvent, TriggerCompletedOutcome}, EventBox, }, + executor::ExecutorDataModel, isi::error::{InstructionExecutionError as Error, MathError}, parameter::{Parameter, ParameterValueBox}, - permission::{PermissionSchema, Permissions}, + permission::Permissions, prelude::*, query::error::{FindError, QueryExecutionFail}, role::RoleId, @@ -73,12 +74,12 @@ pub struct World { pub(crate) account_permissions: Storage, /// Roles of an account. pub(crate) account_roles: Storage, - /// Registered permission token ids. - pub(crate) permission_schema: Cell, /// Triggers pub(crate) triggers: TriggerSet, /// Runtime Executor pub(crate) executor: Cell, + /// Executor-defined data model + pub(crate) executor_data_model: Cell, } /// Struct for block's aggregated changes @@ -95,12 +96,12 @@ pub struct WorldBlock<'world> { pub(crate) account_permissions: StorageBlock<'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageBlock<'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellBlock<'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetBlock<'world>, /// Runtime Executor pub(crate) executor: CellBlock<'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellBlock<'world, ExecutorDataModel>, /// Events produced during execution of block events_buffer: Vec, } @@ -119,12 +120,12 @@ pub struct WorldTransaction<'block, 'world> { pub(crate) account_permissions: StorageTransaction<'block, 'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageTransaction<'block, 'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellTransaction<'block, 'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetTransaction<'block, 'world>, /// Runtime Executor pub(crate) executor: CellTransaction<'block, 'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellTransaction<'block, 'world, ExecutorDataModel>, /// Events produced during execution of a transaction events_buffer: TransactionEventBuffer<'block>, } @@ -151,12 +152,12 @@ pub struct WorldView<'world> { pub(crate) account_permissions: StorageView<'world, AccountId, Permissions>, /// Roles of an account. pub(crate) account_roles: StorageView<'world, RoleIdWithOwner, ()>, - /// Registered permission token ids. - pub(crate) permission_schema: CellView<'world, PermissionSchema>, /// Triggers pub(crate) triggers: TriggerSetView<'world>, /// Runtime Executor pub(crate) executor: CellView<'world, Executor>, + /// Executor-defined data model + pub(crate) executor_data_model: CellView<'world, ExecutorDataModel>, } /// Current state of the blockchain @@ -284,9 +285,9 @@ impl World { roles: self.roles.block(), account_permissions: self.account_permissions.block(), account_roles: self.account_roles.block(), - permission_schema: self.permission_schema.block(), triggers: self.triggers.block(), executor: self.executor.block(), + executor_data_model: self.executor_data_model.block(), events_buffer: Vec::new(), } } @@ -300,9 +301,9 @@ impl World { roles: self.roles.block_and_revert(), account_permissions: self.account_permissions.block_and_revert(), account_roles: self.account_roles.block_and_revert(), - permission_schema: self.permission_schema.block_and_revert(), triggers: self.triggers.block_and_revert(), executor: self.executor.block_and_revert(), + executor_data_model: self.executor_data_model.block_and_revert(), events_buffer: Vec::new(), } } @@ -316,9 +317,9 @@ impl World { roles: self.roles.view(), account_permissions: self.account_permissions.view(), account_roles: self.account_roles.view(), - permission_schema: self.permission_schema.view(), triggers: self.triggers.view(), executor: self.executor.view(), + executor_data_model: self.executor_data_model.view(), } } } @@ -332,9 +333,9 @@ pub trait WorldReadOnly { fn roles(&self) -> &impl StorageReadOnly; fn account_permissions(&self) -> &impl StorageReadOnly; fn account_roles(&self) -> &impl StorageReadOnly; - fn permission_schema(&self) -> &PermissionSchema; fn triggers(&self) -> &impl TriggerSetReadOnly; fn executor(&self) -> &Executor; + fn executor_data_model(&self) -> &ExecutorDataModel; // Domain-related methods @@ -578,15 +579,15 @@ macro_rules! impl_world_ro { fn account_roles(&self) -> &impl StorageReadOnly { &self.account_roles } - fn permission_schema(&self) -> &PermissionSchema { - &self.permission_schema - } fn triggers(&self) -> &impl TriggerSetReadOnly { &self.triggers } fn executor(&self) -> &Executor { &self.executor } + fn executor_data_model(&self) -> &ExecutorDataModel { + &self.executor_data_model + } } )*}; } @@ -605,9 +606,9 @@ impl<'world> WorldBlock<'world> { roles: self.roles.transaction(), account_permissions: self.account_permissions.transaction(), account_roles: self.account_roles.transaction(), - permission_schema: self.permission_schema.transaction(), triggers: self.triggers.transaction(), executor: self.executor.transaction(), + executor_data_model: self.executor_data_model.transaction(), events_buffer: TransactionEventBuffer { events_buffer: &mut self.events_buffer, events_created_in_transaction: 0, @@ -618,9 +619,9 @@ impl<'world> WorldBlock<'world> { /// Commit block's changes pub fn commit(self) { // IMPORTANT!!! Commit fields in reverse order, this way consistent results are insured + self.executor_data_model.commit(); self.executor.commit(); self.triggers.commit(); - self.permission_schema.commit(); self.account_roles.commit(); self.account_permissions.commit(); self.roles.commit(); @@ -633,9 +634,9 @@ impl<'world> WorldBlock<'world> { impl WorldTransaction<'_, '_> { /// Apply transaction's changes pub fn apply(mut self) { + self.executor_data_model.apply(); self.executor.apply(); self.triggers.apply(); - self.permission_schema.apply(); self.account_roles.apply(); self.account_permissions.apply(); self.roles.apply(); @@ -835,18 +836,9 @@ impl WorldTransaction<'_, '_> { Ok(()) } - /// Set new permission token schema. - /// - /// Produces [`PermissionSchemaUpdateEvent`]. - pub fn set_permission_schema(&mut self, schema: PermissionSchema) { - let old_schema: PermissionSchema = - std::mem::replace(&mut self.permission_schema, schema.clone()); - self.emit_events(std::iter::once(DataEvent::Permission( - PermissionSchemaUpdateEvent { - old_schema, - new_schema: schema, - }, - ))) + /// Set executor data model. + pub fn set_executor_data_model(&mut self, executor_data_model: ExecutorDataModel) { + *self.executor_data_model.get_mut() = executor_data_model; } /// Execute trigger with `trigger_id` as id and `authority` as owner @@ -1589,9 +1581,9 @@ pub(crate) mod deserialize { let mut roles = None; let mut account_permissions = None; let mut account_roles = None; - let mut permission_schema = None; let mut triggers = None; let mut executor = None; + let mut executor_data_model = None; while let Some(key) = map.next_key::()? { match key.as_str() { @@ -1613,9 +1605,6 @@ pub(crate) mod deserialize { "account_roles" => { account_roles = Some(map.next_value()?); } - "permission_schema" => { - permission_schema = Some(map.next_value()?); - } "triggers" => { triggers = Some(map.next_value_seed(self.loader.cast::())?); @@ -1625,6 +1614,10 @@ pub(crate) mod deserialize { seed: self.loader.cast::(), })?); } + "executor_data_model" => { + executor_data_model = Some(map.next_value()?); + } + _ => { /* Skip unknown fields */ } } } @@ -1642,12 +1635,13 @@ pub(crate) mod deserialize { })?, account_roles: account_roles .ok_or_else(|| serde::de::Error::missing_field("account_roles"))?, - permission_schema: permission_schema - .ok_or_else(|| serde::de::Error::missing_field("permission_schema"))?, triggers: triggers .ok_or_else(|| serde::de::Error::missing_field("triggers"))?, executor: executor .ok_or_else(|| serde::de::Error::missing_field("executor"))?, + executor_data_model: executor_data_model.ok_or_else(|| { + serde::de::Error::missing_field("executor_data_model") + })?, }) } } @@ -1661,9 +1655,9 @@ pub(crate) mod deserialize { "roles", "account_permissions", "account_roles", - "permission_schema", "triggers", "executor", + "executor_data_model", ], WorldVisitor { loader: &self }, ) diff --git a/data_model/src/events/data/events.rs b/data_model/src/events/data/events.rs index 6584e22b9d5..d43e34050aa 100644 --- a/data_model/src/events/data/events.rs +++ b/data_model/src/events/data/events.rs @@ -89,8 +89,6 @@ mod model { Trigger(trigger::TriggerEvent), /// Role event Role(role::RoleEvent), - /// Permission token event - Permission(permission::PermissionSchemaUpdateEvent), /// Configuration event Configuration(config::ConfigurationEvent), /// Executor event @@ -287,44 +285,6 @@ mod role { } } -mod permission { - //! This module contains [`PermissionSchemaUpdateEvent`] - - pub use self::model::*; - use super::*; - use crate::permission::PermissionSchema; - - #[model] - mod model { - use super::*; - - /// Information about permission tokens update. - /// Only happens when registering new executor - #[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Getters, - Decode, - Encode, - Deserialize, - Serialize, - IntoSchema, - )] - #[getset(get = "pub")] - #[ffi_type] - pub struct PermissionSchemaUpdateEvent { - /// Previous set of permission tokens - pub old_schema: PermissionSchema, - /// New set of permission tokens - pub new_schema: PermissionSchema, - } - } -} - mod account { //! This module contains `AccountEvent` and its impls @@ -332,7 +292,6 @@ mod account { pub use self::model::*; use super::*; - use crate::name::Name; type AccountMetadataChanged = MetadataChanged; @@ -414,7 +373,7 @@ mod account { impl AccountPermissionChanged { /// Get permission id - pub fn permission_id(&self) -> &Name { + pub fn permission_id(&self) -> &PermissionId { &self.permission_id } } @@ -558,10 +517,10 @@ mod executor { // this is used in no_std #[allow(unused)] use super::*; + use crate::executor::ExecutorDataModel; #[derive( Debug, - Copy, Clone, PartialEq, Eq, @@ -575,11 +534,34 @@ mod executor { EventSet, )] #[non_exhaustive] - #[ffi_type] + #[ffi_type(opaque)] #[serde(untagged)] // Unaffected by #3330, as single unit variant #[repr(transparent)] pub enum ExecutorEvent { - Upgraded, + Upgraded(ExecutorUpgrade), + } + + /// Information about the updated executor data model. + #[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + Getters, + )] + #[ffi_type] + #[repr(transparent)] + #[getset(get = "pub")] + pub struct ExecutorUpgrade { + /// Updated data model + pub new_data_model: ExecutorDataModel, } } } @@ -616,11 +598,7 @@ impl DataEvent { match self { Self::Domain(event) => Some(event.origin_id()), Self::Trigger(event) => event.origin_id().domain_id.as_ref(), - Self::Peer(_) - | Self::Configuration(_) - | Self::Role(_) - | Self::Permission(_) - | Self::Executor(_) => None, + Self::Peer(_) | Self::Configuration(_) | Self::Role(_) | Self::Executor(_) => None, } } } @@ -635,9 +613,8 @@ pub mod prelude { }, config::{ConfigurationEvent, ConfigurationEventSet}, domain::{DomainEvent, DomainEventSet, DomainOwnerChanged}, - executor::{ExecutorEvent, ExecutorEventSet}, + executor::{ExecutorEvent, ExecutorEventSet, ExecutorUpgrade}, peer::{PeerEvent, PeerEventSet}, - permission::PermissionSchemaUpdateEvent, role::{RoleEvent, RoleEventSet, RolePermissionChanged}, trigger::{TriggerEvent, TriggerEventSet, TriggerNumberOfExecutionsChanged}, DataEvent, HasOrigin, MetadataChanged, diff --git a/data_model/src/events/data/filters.rs b/data_model/src/events/data/filters.rs index 03a2048250b..58840eb68bc 100644 --- a/data_model/src/events/data/filters.rs +++ b/data_model/src/events/data/filters.rs @@ -47,9 +47,6 @@ mod model { Trigger(TriggerEventFilter), /// Matches [`RoleEvent`]s Role(RoleEventFilter), - /// Matches [`PermissionSchemaUpdateEvent`]s - // nothing to filter for, really - PermissionSchemaUpdate, /// Matches [`ConfigurationEvent`]s Configuration(ConfigurationEventFilter), /// Matches [`ExecutorEvent`]s @@ -706,7 +703,6 @@ impl EventFilter for DataEventFilter { (DataEvent::Trigger(event), Trigger(filter)) => filter.matches(event), (DataEvent::Role(event), Role(filter)) => filter.matches(event), (DataEvent::Configuration(event), Configuration(filter)) => filter.matches(event), - (DataEvent::Permission(_), PermissionSchemaUpdate) => true, (DataEvent::Executor(event), Executor(filter)) => filter.matches(event), ( @@ -714,7 +710,6 @@ impl EventFilter for DataEventFilter { | DataEvent::Domain(_) | DataEvent::Trigger(_) | DataEvent::Role(_) - | DataEvent::Permission(_) | DataEvent::Configuration(_) | DataEvent::Executor(_), Any, @@ -724,7 +719,6 @@ impl EventFilter for DataEventFilter { | DataEvent::Domain(_) | DataEvent::Trigger(_) | DataEvent::Role(_) - | DataEvent::Permission(_) | DataEvent::Configuration(_) | DataEvent::Executor(_), _, diff --git a/data_model/src/executor.rs b/data_model/src/executor.rs index 8c147cc477b..d0c0b225efd 100644 --- a/data_model/src/executor.rs +++ b/data_model/src/executor.rs @@ -1,9 +1,11 @@ //! Structures, traits and impls related to *runtime* `Executor`s. #[cfg(not(feature = "std"))] -use alloc::{format, string::String, vec::Vec}; +use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; +#[cfg(feature = "std")] +use std::collections::BTreeSet; -use derive_more::Constructor; +use derive_more::{Constructor, Display}; use getset::Getters; use iroha_data_model_derive::model; use iroha_schema::IntoSchema; @@ -11,7 +13,7 @@ use parity_scale_codec::{Decode, Encode}; use serde::{Deserialize, Serialize}; pub use self::model::*; -use crate::transaction::WasmSmartContract; +use crate::{permission::PermissionId, transaction::WasmSmartContract, JsonString}; #[model] mod model { @@ -47,10 +49,60 @@ mod model { pub wasm: WasmSmartContract, } + /// Executor data model. + /// + /// Defined from within the executor, it describes certain structures the executor + /// works with. + /// + /// Executor can define: + /// + /// - Permission tokens (see [`crate::permission::Permission`]) + /// - Configuration parameters (see [`crate::parameter::Parameter`]) + #[derive( + Default, + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Constructor, + Decode, + Encode, + Deserialize, + Serialize, + IntoSchema, + Display, + )] + #[ffi_type] + #[display(fmt = "{self:?}")] + pub struct ExecutorDataModel { + /// Permission tokens supported by the executor. + /// + /// These IDs refer to the types in the schema. + pub permissions: BTreeSet, + /// Data model JSON schema, typically produced by [`IntoSchema`]. + pub schema: JsonString, + } + // TODO: Client doesn't need structures defined inside this macro. When dynamic linking is // implemented use: #[cfg(any(feature = "transparent_api", feature = "ffi_import"))] } +// TODO: derive `Getters` once FFI impl is fixed +// currently it fails for all fields +impl ExecutorDataModel { + /// Getter + pub fn permissions(&self) -> &BTreeSet { + &self.permissions + } + + /// Getter + pub fn schema(&self) -> &JsonString { + &self.schema + } +} + /// Result type that every executor should return. pub type Result = core::result::Result; @@ -62,5 +114,5 @@ pub type MigrationResult = Result<(), MigrationError>; pub mod prelude { //! The prelude re-exports most commonly used traits, structs and macros from this crate. - pub use super::Executor; + pub use super::{Executor, ExecutorDataModel}; } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 301b01ba84e..979a27147c4 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -154,7 +154,7 @@ mod seal { FindTransactionsByAccountId, FindTransactionByHash, FindPermissionsByAccountId, - FindPermissionSchema, + FindExecutorDataModel, FindAllActiveTriggerIds, FindTriggerById, FindTriggerKeyValueByIdAndKey, @@ -617,6 +617,8 @@ pub mod parameter { #[model] #[allow(clippy::redundant_pub_crate)] mod model { + use iroha_schema::TypeId; + use super::*; /// Unique id of blockchain @@ -864,6 +866,113 @@ mod model { /// in the next request to continue fetching results of the original query pub cursor: crate::query::cursor::ForwardCursor, } + + /// String containing serialized valid JSON. + /// + /// This string is guaranteed to be parsed as JSON. + #[derive(Display, Default, Debug, Clone, Eq, Encode, Decode, TypeId, Ord, PartialOrd)] + #[ffi_type(unsafe {robust})] + #[repr(transparent)] + #[display(fmt = "{}", "0")] + pub struct JsonString(pub(super) String); +} + +/// A helper trait for polymorphism, implemented for various types +pub trait IntoJsonString { + /// Converts self into [`JsonString`] + fn into_json_string(self) -> JsonString; +} + +impl IntoJsonString for JsonString { + fn into_json_string(self) -> JsonString { + self + } +} + +impl IntoJsonString for &serde_json::Value { + fn into_json_string(self) -> JsonString { + JsonString::from(self) + } +} + +impl IntoJsonString for serde_json::Value { + fn into_json_string(self) -> JsonString { + (&self).into_json_string() + } +} + +impl JsonString { + /// Deserialize JSON into something + /// # Errors + /// See [`serde_json::from_str`]. + pub fn deserialize<'a, T>(&'a self) -> serde_json::Result + where + T: Deserialize<'a>, + { + serde_json::from_str(&self.0) + } + + /// Serializes a value into [`JsonString`] + /// # Errors + /// See [`serde_json::to_string`]. + pub fn serialize(value: &T) -> serde_json::Result { + let serialized = serde_json::to_string(value)?; + // the string was obtained from serde_json serialization, + // so it should be a valid JSON string + Ok(Self(serialized)) + } + + /// Create without checking whether the input is a valid JSON string. + /// + /// The caller must guarantee that the value is valid. + pub fn from_json_string_unchecked(value: String) -> Self { + Self(value) + } +} + +impl From<&serde_json::Value> for JsonString { + fn from(value: &serde_json::Value) -> Self { + Self(value.to_string()) + } +} + +impl PartialEq for JsonString { + fn eq(&self, other: &Self) -> bool { + serde_json::from_str::(&self.0).unwrap() + == serde_json::from_str::(&other.0).unwrap() + } +} + +impl<'de> serde::de::Deserialize<'de> for JsonString { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let json = serde_json::Value::deserialize(deserializer)?; + Ok(Self::from(&json)) + } +} + +impl serde::ser::Serialize for JsonString { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let json = serde_json::Value::from_str(&self.0).map_err(serde::ser::Error::custom)?; + json.serialize(serializer) + } +} + +impl IntoSchema for JsonString { + fn type_name() -> iroha_schema::Ident { + ::id() + } + + fn update_schema_map(map: &mut iroha_schema::MetaMap) { + if !map.contains_key::() { + map.insert::(iroha_schema::Metadata::String); + } + } } macro_rules! impl_encode_as_id_box { diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index 4f94ed139d0..4651554585c 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -1,6 +1,7 @@ //! Permission Token and related impls #[cfg(not(feature = "std"))] -use alloc::{borrow::ToOwned as _, collections::BTreeSet, format, string::String, vec::Vec}; +use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; +use core::borrow::Borrow; #[cfg(feature = "std")] use std::collections::BTreeSet; @@ -17,200 +18,101 @@ pub type Permissions = BTreeSet; use super::*; -/// Unique id of [`Permission`] -pub type PermissionId = Name; - #[model] mod model { use super::*; - /// Stored proof of the account having a permission for a certain action. - /// - /// Since permission token is represented opaque to core - /// either executor or client should make sure that tokens are represented uniformly. - /// - /// So that: - /// - payload A is equal to payload B then token A must be equal to token B - /// - and if payload A is't equal to B then token A mustn't be equal to token B + /// Identifies a [`Permission`]. + /// The executor defines available permission names. #[derive( Debug, + Display, Clone, PartialEq, Eq, PartialOrd, Ord, + Hash, + Constructor, + FromStr, + Getters, Decode, Encode, Deserialize, Serialize, IntoSchema, )] - #[ffi_type] - pub struct Permission { - /// Token identifier - pub definition_id: PermissionId, - /// JSON encoded token payload - pub payload: JsonString, + #[getset(get = "pub")] + #[serde(transparent)] + #[repr(transparent)] + #[ffi_type(opaque)] + pub struct PermissionId { + /// Should be unique. + pub name: Name, } - /// Description of tokens defined in the executor + /// Stored proof of the account having a permission for a certain action. #[derive( Debug, - Display, Clone, PartialEq, Eq, PartialOrd, Ord, - Default, Decode, Encode, Deserialize, Serialize, IntoSchema, + Display, + Getters, )] - #[display(fmt = "{token_ids:#?}")] #[ffi_type] - pub struct PermissionSchema { - /// Ids of all permission tokens, sorted. - pub token_ids: Vec, - /// Type schema of permission tokens + #[display(fmt = "PERMISSION `{id}` = `{payload}`")] + #[getset(get = "pub")] + pub struct Permission { + /// Refers to a type defined in [`crate::executor::ExecutorDataModel`]. + pub id: PermissionId, + /// Payload containing actual value. /// - /// At the time of writing this doc a complete schema is returned but it's - /// possible that in the future this field will contain references to types - /// defined in the Iroha schema without defining them itself - pub schema: String, - } - - /// String containing serialized valid JSON. - /// This string is guaranteed to parse as JSON - #[derive(Debug, Clone, Eq, Decode, Encode)] - pub struct JsonString(pub(super) String); -} - -// TODO: Use getset to derive this -impl PermissionSchema { - /// Construct new [`PermissionSchema`] - pub fn new(token_ids: Vec, schema: String) -> Self { - Self { token_ids, schema } - } - - /// Return id of this token - pub fn permissions(&self) -> &[PermissionId] { - &self.token_ids + /// It is JSON-encoded, and its structure must correspond to the structure of + /// the type defined in [`crate::executor::ExecutorDataModel`]. + #[getset(skip)] + pub payload: JsonString, } } impl Permission { - /// Construct [`Self`] from a raw string slice. The caller of the function - /// must make sure that the given string slice can be parsed as valid JSON. - /// - /// Only used in tests - #[cfg(debug_assertions)] - // TODO: Remove after integration tests have been moved to python tests - #[deprecated(note = "Will be removed after integration tests are removed from iroha_client")] - pub fn from_str_unchecked(definition_id: PermissionId, payload: &str) -> Self { - Self { - definition_id, - payload: JsonString(payload.to_owned()), - } - } - - /// Construct [`Self`] - pub fn new(definition_id: PermissionId, payload: &serde_json::Value) -> Self { + /// Constructor + pub fn new(id: PermissionId, payload: impl IntoJsonString) -> Self { Self { - definition_id, - payload: JsonString::new(payload), + id, + payload: payload.into_json_string(), } } - - /// Return id of this token - // TODO: Use getset to derive this after fixes in FFI - pub fn definition_id(&self) -> &Name { - &self.definition_id - } - - /// Payload of this token - // TODO: Use getset to derive this after fixes in FFI - pub fn payload(&self) -> &String { - &self.payload.0 - } -} - -impl core::fmt::Display for Permission { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - write!(f, "{}", self.definition_id) - } -} - -impl JsonString { - /// Construct [`JsonString`] - pub fn new(payload: &serde_json::Value) -> Self { - Self(payload.to_string()) - } -} - -impl PartialEq for JsonString { - fn eq(&self, other: &Self) -> bool { - serde_json::from_str::(&self.0).unwrap() - == serde_json::from_str::(&other.0).unwrap() - } -} - -impl<'de> serde::de::Deserialize<'de> for JsonString { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let json = serde_json::Value::deserialize(deserializer)?; - Ok(Self::new(&json)) - } -} - -impl serde::ser::Serialize for JsonString { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let json = serde_json::Value::from_str(&self.0).map_err(serde::ser::Error::custom)?; - json.serialize(serializer) - } -} - -impl iroha_schema::TypeId for JsonString { - fn id() -> iroha_schema::Ident { - "JsonString".to_owned() - } } -impl IntoSchema for JsonString { - fn type_name() -> iroha_schema::Ident { - ::id() - } - - fn update_schema_map(map: &mut iroha_schema::MetaMap) { - if !map.contains_key::() { - map.insert::(iroha_schema::Metadata::String); - } +impl Borrow for PermissionId { + fn borrow(&self) -> &str { + self.name.borrow() } } -impl PartialOrd for JsonString { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) +impl Borrow for Permission { + fn borrow(&self) -> &str { + self.id.borrow() } } -impl Ord for JsonString { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - let first = serde_json::from_str::(&self.0).unwrap(); - let second = serde_json::from_str::(&other.0).unwrap(); - - first.to_string().cmp(&second.to_string()) +impl Permission { + /// Getter + // TODO: derive with getset once FFI impl is fixed + pub fn payload(&self) -> &JsonString { + &self.payload } } pub mod prelude { //! The prelude re-exports most commonly used traits, structs and macros from this crate. - pub use super::{Permission, PermissionId, PermissionSchema}; + pub use super::{Permission, PermissionId}; } diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index daed565038f..41472ed8b55 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -26,8 +26,8 @@ pub use sorting::Sorting; pub use self::model::*; use self::{ - account::*, asset::*, block::*, domain::*, peer::*, permission::*, predicate::*, role::*, - transaction::*, trigger::*, + account::*, asset::*, block::*, domain::*, executor::*, peer::*, permission::*, predicate::*, + role::*, transaction::*, trigger::*, }; use crate::{ account::{Account, AccountId}, @@ -197,7 +197,7 @@ mod model { FindTransactionsByAccountId(FindTransactionsByAccountId), FindTransactionByHash(FindTransactionByHash), FindPermissionsByAccountId(FindPermissionsByAccountId), - FindPermissionSchema(FindPermissionSchema), + FindExecutorDataModel(FindExecutorDataModel), FindAllActiveTriggerIds(FindAllActiveTriggerIds), FindTriggerById(FindTriggerById), FindTriggerKeyValueByIdAndKey(FindTriggerKeyValueByIdAndKey), @@ -230,11 +230,11 @@ mod model { Identifiable(IdentifiableBox), Transaction(TransactionQueryOutput), Permission(crate::permission::Permission), - PermissionSchema(crate::permission::PermissionSchema), LimitedMetadata(MetadataValueBox), Numeric(Numeric), BlockHeader(BlockHeader), Block(crate::block::SignedBlock), + ExecutorDataModel(crate::executor::ExecutorDataModel), Vec( #[skip_from] @@ -399,7 +399,6 @@ impl_queries! { FindAllRoleIds => Vec, FindRolesByAccountId => Vec, FindRoleByRoleId => crate::role::Role, - FindPermissionSchema => crate::permission::PermissionSchema, FindPermissionsByAccountId => Vec, FindAllAccounts => Vec, FindAccountById => crate::account::Account, @@ -434,6 +433,7 @@ impl_queries! { FindAllBlocks => Vec, FindAllBlockHeaders => Vec, FindBlockHeaderByHash => crate::block::BlockHeader, + FindExecutorDataModel => crate::executor::ExecutorDataModel } impl Query for QueryBox { @@ -452,11 +452,11 @@ impl core::fmt::Display for QueryOutputBox { QueryOutputBox::Identifiable(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Transaction(_) => write!(f, "TransactionQueryOutput"), QueryOutputBox::Permission(v) => core::fmt::Display::fmt(&v, f), - QueryOutputBox::PermissionSchema(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Block(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::BlockHeader(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Numeric(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::LimitedMetadata(v) => core::fmt::Display::fmt(&v, f), + QueryOutputBox::ExecutorDataModel(v) => core::fmt::Display::fmt(&v, f), QueryOutputBox::Vec(v) => { // TODO: Remove so we can derive. @@ -661,17 +661,9 @@ pub mod permission { use parity_scale_codec::Encode; use super::{Query, QueryType}; - use crate::{ - permission::{self, PermissionSchema}, - prelude::*, - }; + use crate::prelude::*; queries! { - /// Finds all registered permission tokens - #[derive(Copy, Display)] - #[ffi_type] - pub struct FindPermissionSchema; - /// [`FindPermissionsByAccountId`] Iroha Query finds all [`Permission`]s /// for a specified account. #[derive(Display)] @@ -687,7 +679,7 @@ pub mod permission { /// The prelude re-exports most commonly used traits, structs and macros from this module. pub mod prelude { - pub use super::{FindPermissionSchema, FindPermissionsByAccountId}; + pub use super::FindPermissionsByAccountId; } } @@ -994,7 +986,7 @@ pub mod domain { } pub mod peer { - //! Queries related to [`Domain`](crate::domain::Domain). + //! Queries related to [`crate::peer`]. #[cfg(not(feature = "std"))] use alloc::{format, string::String, vec::Vec}; @@ -1010,18 +1002,39 @@ pub mod peer { #[display(fmt = "Find all peers")] #[ffi_type] pub struct FindAllPeers; + } + + /// The prelude re-exports most commonly used traits, structs and macros from this crate. + pub mod prelude { + pub use super::FindAllPeers; + } +} + +pub mod executor { + //! Queries related to [`crate::executor`]. + + #[cfg(not(feature = "std"))] + use alloc::{format, string::String, vec::Vec}; + + use derive_more::Display; + + queries! { + /// [`FindExecutorDataModel`] Iroha Query finds the data model of the current executor. + #[derive(Copy, Display)] + #[display(fmt = "Find executor data model")] + #[ffi_type] + pub struct FindExecutorDataModel; - /// [`FindAllParameters`] Iroha Query finds all [`Peer`]s parameters. + /// [`FindAllParameters`] Iroha Query finds all defined executor configuration parameters. #[derive(Copy, Display)] #[display(fmt = "Find all peers parameters")] - // TODO: Unused query. Remove? #[ffi_type] pub struct FindAllParameters; } /// The prelude re-exports most commonly used traits, structs and macros from this crate. pub mod prelude { - pub use super::{FindAllParameters, FindAllPeers}; + pub use super::{FindAllParameters, FindExecutorDataModel}; } } @@ -1535,7 +1548,8 @@ pub mod prelude { pub use super::http::*; pub use super::{ account::prelude::*, asset::prelude::*, block::prelude::*, domain::prelude::*, - peer::prelude::*, permission::prelude::*, predicate::PredicateTrait, role::prelude::*, - transaction::*, trigger::prelude::*, FetchSize, QueryBox, QueryId, TransactionQueryOutput, + executor::prelude::*, peer::prelude::*, permission::prelude::*, predicate::PredicateTrait, + role::prelude::*, transaction::prelude::*, trigger::prelude::*, FetchSize, QueryBox, + QueryId, TransactionQueryOutput, }; } diff --git a/data_model/src/visit.rs b/data_model/src/visit.rs index 754ee8368c6..f2b16f0b81c 100644 --- a/data_model/src/visit.rs +++ b/data_model/src/visit.rs @@ -58,7 +58,7 @@ pub trait Visit { visit_find_all_domains(&FindAllDomains), visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), - visit_find_permission_schema(&FindPermissionSchema), + visit_find_executor_data_model(&FindExecutorDataModel), visit_find_all_role_ids(&FindAllRoleIds), visit_find_all_roles(&FindAllRoles), visit_find_all_transactions(&FindAllTransactions), @@ -183,7 +183,7 @@ pub fn visit_query(visitor: &mut V, authority: &AccountId, qu visit_find_all_domains(FindAllDomains), visit_find_all_parameters(FindAllParameters), visit_find_all_peers(FindAllPeers), - visit_find_permission_schema(FindPermissionSchema), + visit_find_executor_data_model(FindExecutorDataModel), visit_find_all_role_ids(FindAllRoleIds), visit_find_all_roles(FindAllRoles), visit_find_all_transactions(FindAllTransactions), @@ -444,7 +444,7 @@ leaf_visitors! { visit_find_all_domains(&FindAllDomains), visit_find_all_parameters(&FindAllParameters), visit_find_all_peers(&FindAllPeers), - visit_find_permission_schema(&FindPermissionSchema), + visit_find_executor_data_model(&FindExecutorDataModel), visit_find_all_role_ids(&FindAllRoleIds), visit_find_all_roles(&FindAllRoles), visit_find_all_transactions(&FindAllTransactions), diff --git a/default_executor/src/lib.rs b/default_executor/src/lib.rs index 26b3593f753..546a6cab359 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_schema, prelude::*}; +use iroha_executor::{prelude::*, DataModelBuilder}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -52,11 +52,7 @@ impl Executor { pub fn migrate(block_height: u64) -> MigrationResult { Executor::ensure_genesis(block_height)?; - let schema = default_permission_schema(); - let (token_ids, schema_str) = schema.serialize(); - iroha_executor::set_permission_schema( - &iroha_executor::data_model::permission::PermissionSchema::new(token_ids, schema_str), - ); + DataModelBuilder::with_default_permissions().set(); Ok(()) } diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 3428213f02e..1f11b12abf7 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -157,7 +157,7 @@ }, { "name": "permission_id", - "type": "Name" + "type": "PermissionId" } ] }, @@ -855,19 +855,14 @@ "discriminant": 3, "type": "RoleEvent" }, - { - "tag": "Permission", - "discriminant": 4, - "type": "PermissionSchemaUpdateEvent" - }, { "tag": "Configuration", - "discriminant": 5, + "discriminant": 4, "type": "ConfigurationEvent" }, { "tag": "Executor", - "discriminant": 6, + "discriminant": 5, "type": "ExecutorEvent" } ] @@ -913,18 +908,14 @@ "discriminant": 7, "type": "RoleEventFilter" }, - { - "tag": "PermissionSchemaUpdate", - "discriminant": 8 - }, { "tag": "Configuration", - "discriminant": 9, + "discriminant": 8, "type": "ConfigurationEventFilter" }, { "tag": "Executor", - "discriminant": 10, + "discriminant": 9, "type": "ExecutorEventFilter" } ] @@ -1200,11 +1191,24 @@ } ] }, + "ExecutorDataModel": { + "Struct": [ + { + "name": "permissions", + "type": "SortedVec" + }, + { + "name": "schema", + "type": "JsonString" + } + ] + }, "ExecutorEvent": { "Enum": [ { "tag": "Upgraded", - "discriminant": 0 + "discriminant": 0, + "type": "ExecutorUpgrade" } ] }, @@ -1227,6 +1231,14 @@ ] } }, + "ExecutorUpgrade": { + "Struct": [ + { + "name": "new_data_model", + "type": "ExecutorDataModel" + } + ] + }, "Fail": { "Struct": [ { @@ -1466,7 +1478,7 @@ { "tag": "Permission", "discriminant": 10, - "type": "Name" + "type": "PermissionId" }, { "tag": "Parameter", @@ -1480,7 +1492,7 @@ } ] }, - "FindPermissionSchema": null, + "FindExecutorDataModel": null, "FindPermissionsByAccountId": { "Struct": [ { @@ -1692,7 +1704,7 @@ { "tag": "PermissionId", "discriminant": 7, - "type": "Name" + "type": "PermissionId" }, { "tag": "ParameterId", @@ -2628,8 +2640,8 @@ "Permission": { "Struct": [ { - "name": "definition_id", - "type": "Name" + "name": "id", + "type": "PermissionId" }, { "name": "payload", @@ -2637,27 +2649,11 @@ } ] }, - "PermissionSchema": { + "PermissionId": { "Struct": [ { - "name": "token_ids", - "type": "Vec" - }, - { - "name": "schema", - "type": "String" - } - ] - }, - "PermissionSchemaUpdateEvent": { - "Struct": [ - { - "name": "old_schema", - "type": "PermissionSchema" - }, - { - "name": "new_schema", - "type": "PermissionSchema" + "name": "name", + "type": "Name" } ] }, @@ -2849,9 +2845,9 @@ "type": "FindPermissionsByAccountId" }, { - "tag": "FindPermissionSchema", + "tag": "FindExecutorDataModel", "discriminant": 29, - "type": "FindPermissionSchema" + "type": "FindExecutorDataModel" }, { "tag": "FindAllActiveTriggerIds", @@ -2953,31 +2949,31 @@ "discriminant": 3, "type": "Permission" }, - { - "tag": "PermissionSchema", - "discriminant": 4, - "type": "PermissionSchema" - }, { "tag": "LimitedMetadata", - "discriminant": 5, + "discriminant": 4, "type": "MetadataValueBox" }, { "tag": "Numeric", - "discriminant": 6, + "discriminant": 5, "type": "Numeric" }, { "tag": "BlockHeader", - "discriminant": 7, + "discriminant": 6, "type": "BlockHeader" }, { "tag": "Block", - "discriminant": 8, + "discriminant": 7, "type": "SignedBlock" }, + { + "tag": "ExecutorDataModel", + "discriminant": 8, + "type": "ExecutorDataModel" + }, { "tag": "Vec", "discriminant": 9, @@ -3369,7 +3365,7 @@ }, { "name": "permission_id", - "type": "Name" + "type": "PermissionId" } ] }, @@ -3721,6 +3717,9 @@ "SortedVec": { "Vec": "Permission" }, + "SortedVec": { + "Vec": "PermissionId" + }, "SortedVec>": { "Vec": "SignatureOf" }, @@ -4364,9 +4363,6 @@ "Vec": { "Vec": "MetadataValueBox" }, - "Vec": { - "Vec": "Name" - }, "Vec": { "Vec": "PeerId" }, diff --git a/schema/gen/src/lib.rs b/schema/gen/src/lib.rs index ff6b9888da7..e0cceca6941 100644 --- a/schema/gen/src/lib.rs +++ b/schema/gen/src/lib.rs @@ -137,6 +137,8 @@ types!( ExecutorEvent, ExecutorEventFilter, ExecutorEventSet, + ExecutorUpgrade, + ExecutorDataModel, Fail, EventFilterBox, FetchSize, @@ -170,7 +172,6 @@ types!( FindDomainById, FindDomainKeyValueByIdAndKey, FindError, - FindPermissionSchema, FindPermissionsByAccountId, FindRoleByRoleId, FindRolesByAccountId, @@ -267,8 +268,6 @@ types!( PeerId, RolePermissionChanged, Permission, - PermissionSchema, - PermissionSchemaUpdateEvent, PipelineEventBox, PipelineEventFilterBox, PredicateBox, @@ -396,16 +395,17 @@ types!( u8, ); -#[cfg(test)] -mod tests { - use core::num::{NonZeroU32, NonZeroU64}; - use std::{ +pub mod complete_data_model { + //! Complete set of types participating in the schema + + pub use core::num::{NonZeroU32, NonZeroU64}; + pub use std::{ collections::{BTreeMap, BTreeSet, HashMap, HashSet}, time::Duration, }; - use iroha_crypto::*; - use iroha_data_model::{ + pub use iroha_crypto::*; + pub use iroha_data_model::{ account::NewAccount, asset::NewAssetDefinition, block::{ @@ -415,7 +415,7 @@ mod tests { }, domain::NewDomain, events::pipeline::{BlockEventFilter, TransactionEventFilter}, - executor::Executor, + executor::{Executor, ExecutorDataModel}, ipfs::IpfsPath, isi::{ error::{ @@ -426,7 +426,6 @@ mod tests { }, metadata::{MetadataError, MetadataValueBox, SizeError}, parameter::ParameterValueBox, - permission::JsonString, prelude::*, query::{ error::{FindError, QueryExecutionFail}, @@ -442,17 +441,20 @@ mod tests { error::TransactionLimitError, SignedTransactionV1, TransactionLimits, TransactionPayload, }, - BatchedResponse, BatchedResponseV1, Level, + BatchedResponse, BatchedResponseV1, JsonString, Level, }; - use iroha_primitives::{ + pub use iroha_primitives::{ addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, const_vec::ConstVec, conststr::ConstString, unique_vec::UniqueVec, }; - use iroha_schema::Compact; + pub use iroha_schema::Compact; +} - use super::IntoSchema; +#[cfg(test)] +mod tests { + use super::{complete_data_model::*, IntoSchema}; fn is_const_generic(generic: &str) -> bool { generic.parse::().is_ok() diff --git a/smart_contract/executor/derive/src/entrypoint.rs b/smart_contract/executor/derive/src/entrypoint.rs index 84b685bd64e..c17109def46 100644 --- a/smart_contract/executor/derive/src/entrypoint.rs +++ b/smart_contract/executor/derive/src/entrypoint.rs @@ -145,7 +145,7 @@ fn impl_migrate_entrypoint(fn_item: syn::ItemFn) -> TokenStream { let migrate_fn_name = syn::Ident::new(export::EXECUTOR_MIGRATE, proc_macro2::Span::call_site()); quote! { - /// Executor `permission_schema` entrypoint + /// Executor `migrate` entrypoint /// /// # Memory safety /// diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index fa4a4318c8b..962cbd794a1 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -7,7 +7,7 @@ use proc_macro2::TokenStream; mod conversion; mod default; mod entrypoint; -mod token; +mod permission; mod validate; /// Annotate the user-defined function that starts the execution of a executor. @@ -64,14 +64,14 @@ pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { emitter.finish_token_stream_with(result) } -/// Derive macro for `Token` trait. +/// Derive macro for `Permission` trait. /// /// # Example /// /// ```ignore /// use iroha_executor::{permission, prelude::*}; /// -/// #[derive(Token, ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] +/// #[derive(Permission, ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] /// #[validate(permission::asset::Owner)] /// struct CanDoSomethingWithAsset { /// some_data: String, @@ -93,11 +93,11 @@ pub fn entrypoint(attr: TokenStream, item: TokenStream) -> TokenStream { /// } /// ``` #[manyhow] -#[proc_macro_derive(Token)] -pub fn derive_token(input: TokenStream) -> Result { +#[proc_macro_derive(Permission)] +pub fn derive_permission(input: TokenStream) -> Result { let input = syn::parse2(input)?; - Ok(token::impl_derive_token(&input)) + Ok(permission::impl_derive_permission(&input)) } /// Derive macro for `ValidateGrantRevoke` trait. diff --git a/smart_contract/executor/derive/src/permission.rs b/smart_contract/executor/derive/src/permission.rs new file mode 100644 index 00000000000..82c6a51fb14 --- /dev/null +++ b/smart_contract/executor/derive/src/permission.rs @@ -0,0 +1,34 @@ +//! Module with [`derive_permission`](crate::derive_permission) macro implementation + +use proc_macro2::TokenStream; +use quote::quote; + +/// [`derive_permission`](crate::derive_permission()) macro implementation +pub fn impl_derive_permission(input: &syn::DeriveInput) -> TokenStream { + let generics = &input.generics; + let ident = &input.ident; + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); + + quote! { + impl #impl_generics ::iroha_executor::permission::Permission for #ident #ty_generics #where_clause { + fn is_owned_by(&self, account_id: &::iroha_executor::data_model::account::AccountId) -> bool { + let account_tokens_cursor = + ::iroha_executor::smart_contract::ExecuteQueryOnHost::execute( + &::iroha_executor::data_model::query::permission::FindPermissionsByAccountId::new( + account_id.clone(), + ) + ) + .expect("`FindPermissionsByAccountId` query should never fail, it's a bug"); + + account_tokens_cursor + .into_iter() + .map(|res| ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( + res, + "Failed to get permission token from cursor" + )) + .filter_map(|token| Self::try_from_object(&token).ok()) + .any(|token| self == &token) + } + } + } +} diff --git a/smart_contract/executor/derive/src/token.rs b/smart_contract/executor/derive/src/token.rs deleted file mode 100644 index c2ca5adea29..00000000000 --- a/smart_contract/executor/derive/src/token.rs +++ /dev/null @@ -1,80 +0,0 @@ -//! Module with [`derive_token`](crate::derive_token) macro implementation - -use proc_macro2::TokenStream; -use quote::quote; - -/// [`derive_token`](crate::derive_token()) macro implementation -pub fn impl_derive_token(input: &syn::DeriveInput) -> TokenStream { - let generics = &input.generics; - let ident = &input.ident; - - let impl_token = impl_token(ident, generics); - let impl_try_from_permission = impl_try_from_permission(ident, generics); - - quote! { - #impl_token - #impl_try_from_permission - } -} - -fn impl_token(ident: &syn::Ident, generics: &syn::Generics) -> proc_macro2::TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - quote! { - impl #impl_generics ::iroha_executor::permission::Token for #ident #ty_generics #where_clause { - fn is_owned_by(&self, account_id: &::iroha_executor::data_model::account::AccountId) -> bool { - let account_tokens_cursor = ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - ::iroha_executor::smart_contract::ExecuteQueryOnHost::execute( - &::iroha_executor::data_model::query::permission::FindPermissionsByAccountId::new( - account_id.clone(), - ) - ), - "Failed to execute `FindPermissionsByAccountId` query" - ); - - account_tokens_cursor - .into_iter() - .map(|res| ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - res, - "Failed to get permission token from cursor" - )) - .filter_map(|token| Self::try_from(&token).ok()) - .any(|token| self == &token) - } - } - } -} - -fn impl_try_from_permission(ident: &syn::Ident, generics: &syn::Generics) -> TokenStream { - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let token_id = quote! { <#ident #ty_generics as ::iroha_executor::permission::Token>::name() }; - - quote! { - impl #impl_generics ::core::convert::TryFrom<&::iroha_executor::data_model::permission::Permission> for #ident #ty_generics #where_clause { - type Error = ::iroha_executor::permission::PermissionConversionError; - - fn try_from(token: &::iroha_executor::data_model::permission::Permission) -> ::core::result::Result { - if #token_id != *token.definition_id() { - return Err(::iroha_executor::permission::PermissionConversionError::Id( - ToOwned::to_owned(token.definition_id()) - )); - } - ::serde_json::from_str::(token.payload()) - .map_err(::iroha_executor::permission::PermissionConversionError::Deserialize) - } - } - - impl #impl_generics ::core::convert::From<#ident #ty_generics> for ::iroha_executor::data_model::permission::Permission #where_clause { - fn from(token: #ident #ty_generics) -> Self { - let definition_id = #token_id; - - let payload = ::iroha_executor::smart_contract::debug::DebugExpectExt::dbg_expect( - ::serde_json::to_value::<#ident #ty_generics>(token), - "failed to serialize concrete permission token type. This is a bug." - ); - - ::iroha_executor::data_model::permission::Permission::new(definition_id, &payload) - } - } - } -} diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index e2a4e827b9e..57f0e1956e3 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -1,7 +1,7 @@ //! Definition of Iroha default executor and accompanying validation functions #![allow(missing_docs, clippy::missing_errors_doc)] -pub mod tokens; +pub mod permissions; use alloc::format; @@ -30,6 +30,7 @@ pub use log::visit_log; pub use parameter::{visit_new_parameter, visit_set_parameter}; pub use peer::{visit_register_peer, visit_unregister_peer}; pub use permission::{visit_grant_account_permission, visit_revoke_account_permission}; +use permissions::AnyPermission; pub use role::{ visit_grant_account_role, visit_grant_role_permission, visit_register_role, visit_revoke_account_role, visit_revoke_role_permission, visit_unregister_role, @@ -40,21 +41,10 @@ pub use trigger::{ visit_unregister_trigger, }; -use crate::{permission::Token as _, prelude::*}; - -pub fn default_permission_schema() -> PermissionSchema { - let mut schema = iroha_executor::PermissionSchema::default(); - - macro_rules! add_to_schema { - ($token_ty:ty) => { - schema.insert::<$token_ty>(); - }; - } - - tokens::map_token_type!(add_to_schema); - - schema -} +use crate::{ + permission::Permission as _, + prelude::{Permission as PermissionObject, *}, +}; // NOTE: If any new `visit_..` functions are introduced in this module, one should // not forget to update the default executor boilerplate too, specifically the @@ -163,7 +153,7 @@ pub mod peer { if is_genesis(executor) { execute!(executor, isi); } - if tokens::peer::CanUnregisterAnyPeer.is_owned_by(authority) { + if permissions::peer::CanUnregisterAnyPeer.is_owned_by(authority) { execute!(executor, isi); } @@ -172,8 +162,7 @@ pub mod peer { } pub mod domain { - use iroha_smart_contract::data_model::{domain::DomainId, permission::Permission}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::domain::DomainId; use super::*; use crate::permission::{ @@ -201,7 +190,7 @@ pub mod domain { Ok(is_domain_owner) => is_domain_owner, } || { - let can_unregister_domain_token = tokens::domain::CanUnregisterDomain { + let can_unregister_domain_token = permissions::domain::CanUnregisterDomain { domain_id: domain_id.clone(), }; can_unregister_domain_token.is_owned_by(authority) @@ -268,7 +257,7 @@ pub mod domain { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_domain_token = tokens::domain::CanSetKeyValueInDomain { + let can_set_key_value_in_domain_token = permissions::domain::CanSetKeyValueInDomain { domain_id: domain_id.clone(), }; if can_set_key_value_in_domain_token.is_owned_by(authority) { @@ -293,7 +282,7 @@ pub mod domain { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_remove_key_value_in_domain_token = tokens::domain::CanRemoveKeyValueInDomain { + let can_remove_key_value_in_domain_token = permissions::domain::CanRemoveKeyValueInDomain { domain_id: domain_id.clone(), }; if can_remove_key_value_in_domain_token.is_owned_by(authority) { @@ -304,7 +293,7 @@ pub mod domain { } #[allow(clippy::too_many_lines)] - fn is_token_domain_associated(permission: &Permission, domain_id: &DomainId) -> bool { + fn is_token_domain_associated(permission: &PermissionObject, domain_id: &DomainId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -421,9 +410,6 @@ pub mod domain { } pub mod account { - use iroha_smart_contract::data_model::permission::Permission; - use tokens::AnyPermission; - use super::*; use crate::permission::{account::is_account_owner, accounts_permissions, roles_permissions}; @@ -440,7 +426,7 @@ pub mod account { Ok(false) => {} } - let can_register_account_in_domain = tokens::domain::CanRegisterAccountInDomain { + let can_register_account_in_domain = permissions::domain::CanRegisterAccountInDomain { domain_id: domain_id.clone(), }; if can_register_account_in_domain.is_owned_by(authority) { @@ -466,7 +452,7 @@ pub mod account { Ok(is_account_owner) => is_account_owner, } || { - let can_unregister_user_account = tokens::account::CanUnregisterAccount { + let can_unregister_user_account = permissions::account::CanUnregisterAccount { account_id: account_id.clone(), }; can_unregister_user_account.is_owned_by(authority) @@ -508,9 +494,10 @@ pub mod account { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_user_account_token = tokens::account::CanSetKeyValueInAccount { - account_id: account_id.clone(), - }; + let can_set_key_value_in_user_account_token = + permissions::account::CanSetKeyValueInAccount { + account_id: account_id.clone(), + }; if can_set_key_value_in_user_account_token.is_owned_by(authority) { execute!(executor, isi); } @@ -537,7 +524,7 @@ pub mod account { Ok(false) => {} } let can_remove_key_value_in_user_account_token = - tokens::account::CanRemoveKeyValueInAccount { + permissions::account::CanRemoveKeyValueInAccount { account_id: account_id.clone(), }; if can_remove_key_value_in_user_account_token.is_owned_by(authority) { @@ -550,7 +537,7 @@ pub mod account { ); } - fn is_token_account_associated(permission: &Permission, account_id: &AccountId) -> bool { + fn is_token_account_associated(permission: &PermissionObject, account_id: &AccountId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -627,8 +614,7 @@ pub mod account { } pub mod asset_definition { - use iroha_smart_contract::data_model::{asset::AssetDefinitionId, permission::Permission}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::asset::AssetDefinitionId; use super::*; use crate::permission::{ @@ -650,7 +636,7 @@ pub mod asset_definition { } let can_register_asset_definition_in_domain_token = - tokens::domain::CanRegisterAssetDefinitionInDomain { + permissions::domain::CanRegisterAssetDefinitionInDomain { domain_id: domain_id.clone(), }; if can_register_asset_definition_in_domain_token.is_owned_by(authority) { @@ -677,7 +663,7 @@ pub mod asset_definition { } || { let can_unregister_asset_definition_token = - tokens::asset_definition::CanUnregisterAssetDefinition { + permissions::asset_definition::CanUnregisterAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; can_unregister_asset_definition_token.is_owned_by(authority) @@ -751,7 +737,7 @@ pub mod asset_definition { Ok(false) => {} } let can_set_key_value_in_asset_definition_token = - tokens::asset_definition::CanSetKeyValueInAssetDefinition { + permissions::asset_definition::CanSetKeyValueInAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; if can_set_key_value_in_asset_definition_token.is_owned_by(authority) { @@ -780,7 +766,7 @@ pub mod asset_definition { Ok(false) => {} } let can_remove_key_value_in_asset_definition_token = - tokens::asset_definition::CanRemoveKeyValueInAssetDefinition { + permissions::asset_definition::CanRemoveKeyValueInAssetDefinition { asset_definition_id: asset_definition_id.clone(), }; if can_remove_key_value_in_asset_definition_token.is_owned_by(authority) { @@ -794,7 +780,7 @@ pub mod asset_definition { } fn is_token_asset_definition_associated( - permission: &Permission, + permission: &PermissionObject, asset_definition_id: &AssetDefinitionId, ) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { @@ -899,7 +885,7 @@ pub mod asset { Ok(false) => {} } let can_register_assets_with_definition_token = - tokens::asset::CanRegisterAssetWithDefinition { + permissions::asset::CanRegisterAssetWithDefinition { asset_definition_id: asset.id().definition_id().clone(), }; if can_register_assets_with_definition_token.is_owned_by(authority) { @@ -933,13 +919,13 @@ pub mod asset { Ok(false) => {} } let can_unregister_assets_with_definition_token = - tokens::asset::CanUnregisterAssetWithDefinition { + permissions::asset::CanUnregisterAssetWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_unregister_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_unregister_user_asset_token = tokens::asset::CanUnregisterUserAsset { + let can_unregister_user_asset_token = permissions::asset::CanUnregisterUserAsset { asset_id: asset_id.clone(), }; if can_unregister_user_asset_token.is_owned_by(authority) { @@ -964,13 +950,14 @@ pub mod asset { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_assets_with_definition_token = tokens::asset::CanMintAssetWithDefinition { - asset_definition_id: asset_id.definition_id().clone(), - }; + let can_mint_assets_with_definition_token = + permissions::asset::CanMintAssetWithDefinition { + asset_definition_id: asset_id.definition_id().clone(), + }; if can_mint_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_mint_user_asset_token = tokens::asset::CanMintUserAsset { + let can_mint_user_asset_token = permissions::asset::CanMintUserAsset { asset_id: asset_id.clone(), }; if can_mint_user_asset_token.is_owned_by(authority) { @@ -1011,13 +998,14 @@ pub mod asset { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_burn_assets_with_definition_token = tokens::asset::CanBurnAssetWithDefinition { - asset_definition_id: asset_id.definition_id().clone(), - }; + let can_burn_assets_with_definition_token = + permissions::asset::CanBurnAssetWithDefinition { + asset_definition_id: asset_id.definition_id().clone(), + }; if can_burn_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_burn_user_asset_token = tokens::asset::CanBurnUserAsset { + let can_burn_user_asset_token = permissions::asset::CanBurnUserAsset { asset_id: asset_id.clone(), }; if can_burn_user_asset_token.is_owned_by(authority) { @@ -1059,13 +1047,13 @@ pub mod asset { Ok(false) => {} } let can_transfer_assets_with_definition_token = - tokens::asset::CanTransferAssetWithDefinition { + permissions::asset::CanTransferAssetWithDefinition { asset_definition_id: asset_id.definition_id().clone(), }; if can_transfer_assets_with_definition_token.is_owned_by(authority) { execute!(executor, isi); } - let can_transfer_user_asset_token = tokens::asset::CanTransferUserAsset { + let can_transfer_user_asset_token = permissions::asset::CanTransferUserAsset { asset_id: asset_id.clone(), }; if can_transfer_user_asset_token.is_owned_by(authority) { @@ -1107,7 +1095,7 @@ pub mod asset { Ok(false) => {} } - let can_set_key_value_in_user_asset_token = tokens::asset::CanSetKeyValueInUserAsset { + let can_set_key_value_in_user_asset_token = permissions::asset::CanSetKeyValueInUserAsset { asset_id: asset_id.clone(), }; if can_set_key_value_in_user_asset_token.is_owned_by(authority) { @@ -1136,7 +1124,7 @@ pub mod asset { Ok(false) => {} } let can_remove_key_value_in_user_asset_token = - tokens::asset::CanRemoveKeyValueInUserAsset { + permissions::asset::CanRemoveKeyValueInUserAsset { asset_id: asset_id.clone(), }; if can_remove_key_value_in_user_asset_token.is_owned_by(authority) { @@ -1162,7 +1150,7 @@ pub mod parameter { if is_genesis(executor) { execute!(executor, isi); } - if tokens::parameter::CanCreateParameters.is_owned_by(authority) { + if permissions::parameter::CanCreateParameters.is_owned_by(authority) { execute!(executor, isi); } @@ -1181,20 +1169,20 @@ pub mod parameter { if is_genesis(executor) { execute!(executor, isi); } - if tokens::parameter::CanSetParameters.is_owned_by(authority) { + if permissions::parameter::CanSetParameters.is_owned_by(authority) { execute!(executor, isi); } deny!( executor, - "Can't set configuration parameters without permission" + "Can't set executor configuration parameters without permission" ); } } pub mod role { use iroha_smart_contract::data_model::role::Role; - use role::tokens::AnyPermission; + use role::permissions::AnyPermission; use super::*; @@ -1242,7 +1230,7 @@ pub mod role { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token.clone()); + let token = PermissionObject::from(any_token.clone()); let isi = <$isi_type>::role_permission(token, role_id); if is_genesis($executor) { execute!($executor, isi); @@ -1280,7 +1268,7 @@ pub mod role { iroha_smart_contract::debug!(&format!("Checking `{token:?}`")); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token); + let token = PermissionObject::from(any_token); new_role = new_role.add_permission(token); continue; } @@ -1310,7 +1298,7 @@ pub mod role { if is_genesis(executor) { execute!(executor, isi); } - if tokens::role::CanUnregisterAnyRole.is_owned_by(authority) { + if permissions::role::CanUnregisterAnyRole.is_owned_by(authority) { execute!(executor, isi); } @@ -1336,28 +1324,29 @@ pub mod role { pub fn visit_grant_role_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); } pub fn visit_revoke_role_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); } } pub mod trigger { - use iroha_executor::permission::trigger::find_trigger; - use iroha_smart_contract::data_model::{permission::Permission, trigger::Trigger}; - use tokens::AnyPermission; + use iroha_smart_contract::data_model::trigger::Trigger; use super::*; use crate::permission::{ - accounts_permissions, domain::is_domain_owner, roles_permissions, trigger::is_trigger_owner, + accounts_permissions, + domain::is_domain_owner, + roles_permissions, + trigger::{find_trigger, is_trigger_owner}, }; pub fn visit_register_trigger( @@ -1375,9 +1364,10 @@ pub mod trigger { } } || { - let can_register_user_trigger_token = tokens::trigger::CanRegisterUserTrigger { - account_id: isi.object().action().authority().clone(), - }; + let can_register_user_trigger_token = + permissions::trigger::CanRegisterUserTrigger { + account_id: isi.object().action().authority().clone(), + }; can_register_user_trigger_token.is_owned_by(authority) } { @@ -1399,13 +1389,14 @@ pub mod trigger { Ok(is_trigger_owner) => is_trigger_owner, } || { - let can_unregister_user_trigger_token = tokens::trigger::CanUnregisterUserTrigger { - account_id: find_trigger(trigger_id) - .unwrap() - .action() - .authority() - .clone(), - }; + let can_unregister_user_trigger_token = + permissions::trigger::CanUnregisterUserTrigger { + account_id: find_trigger(trigger_id) + .unwrap() + .action() + .authority() + .clone(), + }; can_unregister_user_trigger_token.is_owned_by(authority) } { @@ -1448,7 +1439,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanMintUserTrigger { + let can_mint_user_trigger_token = permissions::trigger::CanMintUserTrigger { trigger_id: trigger_id.clone(), }; if can_mint_user_trigger_token.is_owned_by(authority) { @@ -1476,7 +1467,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_mint_user_trigger_token = tokens::trigger::CanBurnUserTrigger { + let can_mint_user_trigger_token = permissions::trigger::CanBurnUserTrigger { trigger_id: trigger_id.clone(), }; if can_mint_user_trigger_token.is_owned_by(authority) { @@ -1504,7 +1495,7 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_execute_trigger_token = tokens::trigger::CanExecuteUserTrigger { + let can_execute_trigger_token = permissions::trigger::CanExecuteUserTrigger { trigger_id: trigger_id.clone(), }; if can_execute_trigger_token.is_owned_by(authority) { @@ -1529,9 +1520,10 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_set_key_value_in_user_trigger_token = tokens::trigger::CanSetKeyValueInTrigger { - trigger_id: trigger_id.clone(), - }; + let can_set_key_value_in_user_trigger_token = + permissions::trigger::CanSetKeyValueInTrigger { + trigger_id: trigger_id.clone(), + }; if can_set_key_value_in_user_trigger_token.is_owned_by(authority) { execute!(executor, isi); } @@ -1557,9 +1549,10 @@ pub mod trigger { Ok(true) => execute!(executor, isi), Ok(false) => {} } - let can_remove_key_value_in_trigger_token = tokens::trigger::CanRemoveKeyValueInTrigger { - trigger_id: trigger_id.clone(), - }; + let can_remove_key_value_in_trigger_token = + permissions::trigger::CanRemoveKeyValueInTrigger { + trigger_id: trigger_id.clone(), + }; if can_remove_key_value_in_trigger_token.is_owned_by(authority) { execute!(executor, isi); } @@ -1570,7 +1563,7 @@ pub mod trigger { ); } - fn is_token_trigger_associated(permission: &Permission, trigger_id: &TriggerId) -> bool { + fn is_token_trigger_associated(permission: &PermissionObject, trigger_id: &TriggerId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -1627,8 +1620,6 @@ pub mod trigger { } pub mod permission { - use tokens::AnyPermission; - use super::*; macro_rules! impl_validate { @@ -1637,7 +1628,7 @@ pub mod permission { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = Permission::from(any_token.clone()); + let token = PermissionObject::from(any_token.clone()); let isi = <$isi_type>::permission(token, account_id); if is_genesis($executor) { execute!($executor, isi); @@ -1663,28 +1654,28 @@ pub mod permission { pub fn visit_grant_account_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { impl_validate!( executor, authority, isi, validate_grant, - Grant + Grant ); } pub fn visit_revoke_account_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { impl_validate!( executor, authority, isi, validate_revoke, - Revoke + Revoke ); } } @@ -1701,7 +1692,7 @@ pub mod executor { if is_genesis(executor) { execute!(executor, isi); } - if tokens::executor::CanUpgradeExecutor.is_owned_by(authority) { + if permissions::executor::CanUpgradeExecutor.is_owned_by(authority) { execute!(executor, isi); } diff --git a/smart_contract/executor/src/default/tokens.rs b/smart_contract/executor/src/default/permissions.rs similarity index 77% rename from smart_contract/executor/src/default/tokens.rs rename to smart_contract/executor/src/default/permissions.rs index 283b48858ae..8406a9afe2c 100644 --- a/smart_contract/executor/src/default/tokens.rs +++ b/smart_contract/executor/src/default/permissions.rs @@ -6,7 +6,7 @@ use alloc::{borrow::ToOwned, format, string::String, vec::Vec}; use iroha_executor_derive::ValidateGrantRevoke; use iroha_smart_contract::data_model::{executor::Result, prelude::*}; -use crate::permission::{self, Token as _}; +use crate::permission::{self, Permission as _}; /// Declare token types of current module. Use it with a full path to the token. /// Used to iterate over tokens to validate `Grant` and `Revoke` instructions. @@ -27,9 +27,9 @@ use crate::permission::{self, Token as _}; /// pub struct MyToken; /// } /// ``` -macro_rules! declare_tokens { +macro_rules! declare_permissions { ($($($token_path:ident ::)+ { $token_ty:ident }),+ $(,)?) => { - macro_rules! map_token_type { + macro_rules! map_default_permissions { ($callback:ident) => { $( $callback!($($token_path::)+$token_ty); )+ }; @@ -43,15 +43,15 @@ macro_rules! declare_tokens { } impl TryFrom<&$crate::data_model::permission::Permission> for AnyPermission { - type Error = $crate::permission::PermissionConversionError; + type Error = $crate::TryFromDataModelObjectError; fn try_from(token: &$crate::data_model::permission::Permission) -> Result { - match token.definition_id().as_ref() { $( + match token.id().name().as_ref() { $( stringify!($token_ty) => { - let token = <$($token_path::)+$token_ty>::try_from(token)?; + let token = <$($token_path::)+$token_ty>::try_from_object(token)?; Ok(Self::$token_ty(token)) } )+ - _ => Err(Self::Error::Id(token.definition_id().clone())) + _ => Err(Self::Error::Id(token.id().name().clone())) } } } @@ -59,7 +59,7 @@ macro_rules! declare_tokens { impl From for $crate::data_model::permission::Permission { fn from(token: AnyPermission) -> Self { match token { $( - AnyPermission::$token_ty(token) => Self::from(token), )* + AnyPermission::$token_ty(token) => token.to_object(), )* } } } @@ -78,76 +78,76 @@ macro_rules! declare_tokens { } } - pub(crate) use map_token_type; + pub(crate) use map_default_permissions; }; } -macro_rules! token { +macro_rules! permission { ($($meta:meta)* $item:item) => { #[derive(PartialEq, Eq, serde::Serialize, serde::Deserialize)] - #[derive(Clone, iroha_executor_derive::Token)] + #[derive(Clone, iroha_executor_derive::Permission)] #[derive(iroha_schema::IntoSchema)] $($meta)* $item }; } -declare_tokens! { - crate::default::tokens::peer::{CanUnregisterAnyPeer}, - - crate::default::tokens::domain::{CanUnregisterDomain}, - crate::default::tokens::domain::{CanSetKeyValueInDomain}, - crate::default::tokens::domain::{CanRemoveKeyValueInDomain}, - crate::default::tokens::domain::{CanRegisterAccountInDomain}, - crate::default::tokens::domain::{CanRegisterAssetDefinitionInDomain}, - - crate::default::tokens::account::{CanUnregisterAccount}, - crate::default::tokens::account::{CanMintUserPublicKeys}, - crate::default::tokens::account::{CanBurnUserPublicKeys}, - crate::default::tokens::account::{CanMintUserSignatureCheckConditions}, - crate::default::tokens::account::{CanSetKeyValueInAccount}, - crate::default::tokens::account::{CanRemoveKeyValueInAccount}, - - crate::default::tokens::asset_definition::{CanUnregisterAssetDefinition}, - crate::default::tokens::asset_definition::{CanSetKeyValueInAssetDefinition}, - crate::default::tokens::asset_definition::{CanRemoveKeyValueInAssetDefinition}, - - crate::default::tokens::asset::{CanRegisterAssetWithDefinition}, - crate::default::tokens::asset::{CanUnregisterAssetWithDefinition}, - crate::default::tokens::asset::{CanUnregisterUserAsset}, - crate::default::tokens::asset::{CanBurnAssetWithDefinition}, - crate::default::tokens::asset::{CanMintAssetWithDefinition}, - crate::default::tokens::asset::{CanMintUserAsset}, - crate::default::tokens::asset::{CanBurnUserAsset}, - crate::default::tokens::asset::{CanTransferAssetWithDefinition}, - crate::default::tokens::asset::{CanTransferUserAsset}, - crate::default::tokens::asset::{CanSetKeyValueInUserAsset}, - crate::default::tokens::asset::{CanRemoveKeyValueInUserAsset}, - - crate::default::tokens::parameter::{CanGrantPermissionToCreateParameters}, - crate::default::tokens::parameter::{CanRevokePermissionToCreateParameters}, - crate::default::tokens::parameter::{CanCreateParameters}, - crate::default::tokens::parameter::{CanGrantPermissionToSetParameters}, - crate::default::tokens::parameter::{CanRevokePermissionToSetParameters}, - crate::default::tokens::parameter::{CanSetParameters}, - - crate::default::tokens::role::{CanUnregisterAnyRole}, - - crate::default::tokens::trigger::{CanRegisterUserTrigger}, - crate::default::tokens::trigger::{CanExecuteUserTrigger}, - crate::default::tokens::trigger::{CanUnregisterUserTrigger}, - crate::default::tokens::trigger::{CanMintUserTrigger}, - crate::default::tokens::trigger::{CanBurnUserTrigger}, - crate::default::tokens::trigger::{CanSetKeyValueInTrigger}, - crate::default::tokens::trigger::{CanRemoveKeyValueInTrigger}, - - crate::default::tokens::executor::{CanUpgradeExecutor}, +declare_permissions! { + crate::default::permissions::peer::{CanUnregisterAnyPeer}, + + crate::default::permissions::domain::{CanUnregisterDomain}, + crate::default::permissions::domain::{CanSetKeyValueInDomain}, + crate::default::permissions::domain::{CanRemoveKeyValueInDomain}, + crate::default::permissions::domain::{CanRegisterAccountInDomain}, + crate::default::permissions::domain::{CanRegisterAssetDefinitionInDomain}, + + crate::default::permissions::account::{CanUnregisterAccount}, + crate::default::permissions::account::{CanMintUserPublicKeys}, + crate::default::permissions::account::{CanBurnUserPublicKeys}, + crate::default::permissions::account::{CanMintUserSignatureCheckConditions}, + crate::default::permissions::account::{CanSetKeyValueInAccount}, + crate::default::permissions::account::{CanRemoveKeyValueInAccount}, + + crate::default::permissions::asset_definition::{CanUnregisterAssetDefinition}, + crate::default::permissions::asset_definition::{CanSetKeyValueInAssetDefinition}, + crate::default::permissions::asset_definition::{CanRemoveKeyValueInAssetDefinition}, + + crate::default::permissions::asset::{CanRegisterAssetWithDefinition}, + crate::default::permissions::asset::{CanUnregisterAssetWithDefinition}, + crate::default::permissions::asset::{CanUnregisterUserAsset}, + crate::default::permissions::asset::{CanBurnAssetWithDefinition}, + crate::default::permissions::asset::{CanMintAssetWithDefinition}, + crate::default::permissions::asset::{CanMintUserAsset}, + crate::default::permissions::asset::{CanBurnUserAsset}, + crate::default::permissions::asset::{CanTransferAssetWithDefinition}, + crate::default::permissions::asset::{CanTransferUserAsset}, + crate::default::permissions::asset::{CanSetKeyValueInUserAsset}, + crate::default::permissions::asset::{CanRemoveKeyValueInUserAsset}, + + crate::default::permissions::parameter::{CanGrantPermissionToCreateParameters}, + crate::default::permissions::parameter::{CanRevokePermissionToCreateParameters}, + crate::default::permissions::parameter::{CanCreateParameters}, + crate::default::permissions::parameter::{CanGrantPermissionToSetParameters}, + crate::default::permissions::parameter::{CanRevokePermissionToSetParameters}, + crate::default::permissions::parameter::{CanSetParameters}, + + crate::default::permissions::role::{CanUnregisterAnyRole}, + + crate::default::permissions::trigger::{CanRegisterUserTrigger}, + crate::default::permissions::trigger::{CanExecuteUserTrigger}, + crate::default::permissions::trigger::{CanUnregisterUserTrigger}, + crate::default::permissions::trigger::{CanMintUserTrigger}, + crate::default::permissions::trigger::{CanBurnUserTrigger}, + crate::default::permissions::trigger::{CanSetKeyValueInTrigger}, + crate::default::permissions::trigger::{CanRemoveKeyValueInTrigger}, + + crate::default::permissions::executor::{CanUpgradeExecutor}, } pub mod peer { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUnregisterAnyPeer; @@ -157,7 +157,7 @@ pub mod peer { pub mod domain { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanUnregisterDomain { @@ -165,7 +165,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanSetKeyValueInDomain { @@ -173,7 +173,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRemoveKeyValueInDomain { @@ -181,7 +181,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRegisterAccountInDomain { @@ -189,7 +189,7 @@ pub mod domain { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)] #[validate(permission::domain::Owner)] pub struct CanRegisterAssetDefinitionInDomain { @@ -201,42 +201,42 @@ pub mod domain { pub mod account { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanUnregisterAccount { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanMintUserPublicKeys { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanBurnUserPublicKeys { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanMintUserSignatureCheckConditions { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanSetKeyValueInAccount { pub account_id: AccountId, } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanRemoveKeyValueInAccount { @@ -248,7 +248,7 @@ pub mod account { pub mod asset_definition { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanUnregisterAssetDefinition { @@ -256,7 +256,7 @@ pub mod asset_definition { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanSetKeyValueInAssetDefinition { @@ -264,7 +264,7 @@ pub mod asset_definition { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanRemoveKeyValueInAssetDefinition { @@ -276,7 +276,7 @@ pub mod asset_definition { pub mod asset { use super::*; - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanRegisterAssetWithDefinition { @@ -284,7 +284,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanUnregisterAssetWithDefinition { @@ -292,7 +292,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanUnregisterUserAsset { @@ -300,7 +300,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanBurnAssetWithDefinition { @@ -308,7 +308,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanBurnUserAsset { @@ -316,7 +316,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanMintAssetWithDefinition { @@ -324,7 +324,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanMintUserAsset { @@ -332,7 +332,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset_definition::Owner)] #[validate(permission::asset_definition::Owner)] pub struct CanTransferAssetWithDefinition { @@ -340,7 +340,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanTransferUserAsset { @@ -348,7 +348,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanSetKeyValueInUserAsset { @@ -356,7 +356,7 @@ pub mod asset { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::asset::Owner)] #[validate(permission::asset::Owner)] pub struct CanRemoveKeyValueInUserAsset { @@ -370,36 +370,36 @@ pub mod parameter { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanGrantPermissionToCreateParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanRevokePermissionToCreateParameters; } - token! { + permission! { #[derive(Copy)] pub struct CanCreateParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanGrantPermissionToSetParameters; } - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanRevokePermissionToSetParameters; } - token! { + permission! { #[derive(Copy)] pub struct CanSetParameters; } @@ -456,7 +456,7 @@ pub mod parameter { pub mod role { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUnregisterAnyRole; @@ -478,7 +478,7 @@ pub mod trigger { )+}; } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanRegisterUserTrigger { @@ -486,7 +486,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanExecuteUserTrigger { @@ -494,7 +494,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke, permission::derive_conversions::account::Owner)] #[validate(permission::account::Owner)] pub struct CanUnregisterUserTrigger { @@ -502,7 +502,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanMintUserTrigger { @@ -510,7 +510,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanBurnUserTrigger { @@ -518,7 +518,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanSetKeyValueInTrigger { @@ -526,7 +526,7 @@ pub mod trigger { } } - token! { + permission! { #[derive(ValidateGrantRevoke)] #[validate(permission::trigger::Owner)] pub struct CanRemoveKeyValueInTrigger { @@ -546,7 +546,7 @@ pub mod trigger { pub mod executor { use super::*; - token! { + permission! { #[derive(Copy, ValidateGrantRevoke)] #[validate(permission::OnlyGenesis)] pub struct CanUpgradeExecutor; diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 1e26b7b5c9e..61fd61bc2cf 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -5,9 +5,9 @@ extern crate alloc; extern crate self as iroha_executor; -use alloc::vec::Vec; +use alloc::collections::BTreeSet; -use data_model::{executor::Result, permission::PermissionId, ValidationFail}; +use data_model::{executor::Result, ValidationFail}; #[cfg(not(test))] use data_model::{prelude::*, smart_contract::payloads}; pub use iroha_schema::MetaMap; @@ -78,7 +78,7 @@ pub fn get_migrate_payload() -> payloads::Migrate { unsafe { decode_with_length_prefix_from_raw(host::get_migrate_payload()) } } -/// Set new [`PermissionSchema`]. +/// Set new [`ExecutorDataModel`]. /// /// # Errors /// @@ -89,9 +89,9 @@ pub fn get_migrate_payload() -> payloads::Migrate { /// Host side will generate a trap if this function was not called from a /// executor's `migrate()` entrypoint. #[cfg(not(test))] -pub fn set_permission_schema(schema: &data_model::permission::PermissionSchema) { +pub fn set_data_model(data_model: &ExecutorDataModel) { // Safety: - ownership of the returned result is transferred into `_decode_from_raw` - unsafe { encode_and_execute(&schema, host::set_permission_schema) } + unsafe { encode_and_execute(&data_model, host::set_data_model) } } #[cfg(not(test))] @@ -126,8 +126,8 @@ mod host { /// This function does transfer ownership of the result to the caller pub(super) fn get_migrate_payload() -> *const u8; - /// Set new [`PermissionSchema`]. - pub(super) fn set_permission_schema(ptr: *const u8, len: usize); + /// Set new [`ExecutorDataModel`]. + pub(super) fn set_data_model(ptr: *const u8, len: usize); } } @@ -172,35 +172,77 @@ macro_rules! deny { }}; } -/// Collection of all permission tokens defined by the executor -#[derive(Debug, Clone, Default)] -pub struct PermissionSchema(Vec, MetaMap); +/// An error that might occur while converting a data model object (with id and payload) +/// into a native executor type. +/// +/// Such objects are [`data_model::prelude::Permission`] and [`data_model::prelude::Parameter`]. +#[derive(Debug)] +pub enum TryFromDataModelObjectError { + /// Unexpected object id + Id(data_model::prelude::Name), + /// Failed to deserialize object payload + Deserialize(serde_json::Error), +} + +/// A convenience to build [`ExecutorDataModel`] from within the executor +#[derive(Debug, Clone)] +pub struct DataModelBuilder { + schema: MetaMap, + permissions: BTreeSet, +} + +impl DataModelBuilder { + /// Constructor + // we don't need to confuse with `with_default_permissions` + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + schema: <_>::default(), + permissions: <_>::default(), + } + } -impl PermissionSchema { - /// Remove permission token from this collection - pub fn remove(&mut self) { - let to_remove = ::name(); + /// Creates a data model with default permissions preset (defined in [`default::permissions`]) + #[must_use] + pub fn with_default_permissions() -> Self { + let mut builder = Self::new(); - if let Some(pos) = self.0.iter().position(|token_id| *token_id == to_remove) { - self.0.remove(pos); - ::remove_from_schema(&mut self.1); + macro_rules! add_to_schema { + ($token_ty:ty) => { + builder = builder.add_permission::<$token_ty>(); + }; } + + default::permissions::map_default_permissions!(add_to_schema); + + builder } - /// Insert new permission token into this collection - pub fn insert(&mut self) { - ::update_schema_map(&mut self.1); - self.0.push(::name()); + /// Define a permission in the data model + #[must_use] + pub fn add_permission(mut self) -> Self { + ::update_schema_map(&mut self.schema); + self.permissions.insert(::id()); + self } - /// Serializes schema into a JSON string representation - pub fn serialize(mut self) -> (Vec, alloc::string::String) { - self.0.sort(); + /// Remove a permission from the data model + #[must_use] + pub fn remove_permission(mut self) -> Self { + ::remove_from_schema(&mut self.schema); + self.permissions + .remove(&::id()); + self + } - ( - self.0, - serde_json::to_string(&self.1).expect("schema serialization must not fail"), - ) + /// Set the data model of the executor via [`set_data_model`] + #[cfg(not(test))] + pub fn set(self) { + set_data_model(&ExecutorDataModel::new( + self.permissions, + data_model::JsonString::serialize(&self.schema) + .expect("schema serialization must not fail"), + )) } } @@ -222,7 +264,8 @@ pub mod prelude { pub use alloc::vec::Vec; pub use iroha_executor_derive::{ - entrypoint, Constructor, Token, Validate, ValidateEntrypoints, ValidateGrantRevoke, Visit, + entrypoint, Constructor, Permission, Validate, ValidateEntrypoints, ValidateGrantRevoke, + Visit, }; pub use iroha_smart_contract::prelude::*; @@ -232,6 +275,8 @@ pub mod prelude { visit::Visit, ValidationFail, }, - deny, execute, PermissionSchema, Validate, + deny, execute, + permission::Permission as PermissionTrait, + DataModelBuilder, Validate, }; } diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 67f2f6a689a..5672b4a7114 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -3,27 +3,52 @@ use alloc::borrow::ToOwned as _; use iroha_schema::IntoSchema; -use iroha_smart_contract::{data_model::permission::Permission, QueryOutputCursor}; +use iroha_smart_contract::{data_model::JsonString, QueryOutputCursor}; use iroha_smart_contract_utils::debug::DebugExpectExt as _; use serde::{de::DeserializeOwned, Serialize}; -use crate::{data_model::prelude::*, prelude::*}; +use crate::{ + prelude::{Permission as PermissionObject, *}, + TryFromDataModelObjectError, +}; -/// [`Token`] trait is used to check if the token is owned by the account. -pub trait Token: +/// Is used to check if the permission token is owned by the account. +pub trait Permission: Serialize + DeserializeOwned + IntoSchema + PartialEq + ValidateGrantRevoke -where - for<'a> Self: TryFrom<&'a Permission, Error = PermissionConversionError>, { - /// Return name of this permission token - fn name() -> Name { - ::type_name() - .parse() - .dbg_expect("Failed to parse permission token as `Name`") + /// Check if the account owns this token + fn is_owned_by(&self, account_id: &AccountId) -> bool; + + /// Permission id, according to [`IntoSchema`]. + fn id() -> PermissionId { + PermissionId::new( + ::type_name() + .parse() + .dbg_expect("Failed to parse permission id as `Name`"), + ) } - /// Check if token is owned by the account - fn is_owned_by(&self, account_id: &AccountId) -> bool; + /// Try to convert from [`PermissionObject`] + /// # Errors + /// See [`TryFromDataModelObjectError`] + fn try_from_object(object: &PermissionObject) -> Result { + if *object.id() != ::id() { + return Err(TryFromDataModelObjectError::Id(object.id().name().clone())); + } + object + .payload() + .deserialize() + .map_err(TryFromDataModelObjectError::Deserialize) + } + + /// Convert into [`PermissionObject`] + fn to_object(&self) -> PermissionObject { + PermissionObject::new( + ::id(), + JsonString::serialize(&self) + .expect("failed to serialize concrete data model entity; this is a bug"), + ) + } } /// Trait that should be implemented for all permission tokens. @@ -43,15 +68,6 @@ pub trait PassCondition { fn validate(&self, authority: &AccountId, block_height: u64) -> Result; } -/// Error type for `TryFrom` implementations. -#[derive(Debug)] -pub enum PermissionConversionError { - /// Unexpected token id. - Id(PermissionId), - /// Failed to deserialize JSON - Deserialize(serde_json::Error), -} - pub mod derive_conversions { //! Module with derive macros to generate conversion from custom strongly-typed token //! to some pass condition to successfully derive [`ValidateGrantRevoke`](iroha_executor_derive::ValidateGrantRevoke) @@ -307,7 +323,7 @@ impl PassCondition for AlwaysPass { } } -impl From<&T> for AlwaysPass { +impl From<&T> for AlwaysPass { fn from(_: &T) -> Self { Self } @@ -331,14 +347,14 @@ impl PassCondition for OnlyGenesis { } } -impl From<&T> for OnlyGenesis { +impl From<&T> for OnlyGenesis { fn from(_: &T) -> Self { Self } } /// Iterator over all accounts and theirs permission tokens -pub(crate) fn accounts_permissions() -> impl Iterator { +pub(crate) fn accounts_permissions() -> impl Iterator { FindAllAccounts .execute() .dbg_expect("failed to query all accounts") @@ -355,7 +371,7 @@ pub(crate) fn accounts_permissions() -> impl Iterator impl Iterator { +pub(crate) fn roles_permissions() -> impl Iterator { FindAllRoles .execute() .dbg_expect("failed to query all accounts") @@ -369,3 +385,32 @@ pub(crate) fn roles_permissions() -> impl Iterator .map(move |token| (role.id().clone(), token)) }) } + +#[cfg(test)] +mod tests { + use alloc::{format, string::String}; + + use serde::Deserialize; + use serde_json::json; + + use super::*; + + #[test] + fn convert_token() { + #[derive( + Serialize, Deserialize, IntoSchema, PartialEq, ValidateGrantRevoke, Permission, + )] + #[validate(AlwaysPass)] + struct SampleToken { + can_do_whatever: bool, + } + + let object = PermissionObject::new( + "SampleToken".parse().unwrap(), + json!({ "can_do_whatever": false }), + ); + let parsed = SampleToken::try_from_object(&object).expect("valid"); + + assert!(!parsed.can_do_whatever); + } +} diff --git a/tools/parity_scale_cli/src/main.rs b/tools/parity_scale_cli/src/main.rs index 9d7f8457a4d..b47530cb08c 100644 --- a/tools/parity_scale_cli/src/main.rs +++ b/tools/parity_scale_cli/src/main.rs @@ -15,51 +15,7 @@ use std::{ use clap::Parser; use colored::*; use eyre::{eyre, Result}; -use iroha_crypto::*; -use iroha_data_model::{ - account::NewAccount, - asset::NewAssetDefinition, - block::{ - error::BlockRejectionReason, - stream::{BlockMessage, BlockSubscriptionRequest}, - BlockHeader, BlockPayload, SignedBlock, SignedBlockV1, - }, - domain::NewDomain, - events::pipeline::{BlockEventFilter, TransactionEventFilter}, - executor::Executor, - ipfs::IpfsPath, - isi::{ - error::{ - InstructionEvaluationError, InstructionExecutionError, InvalidParameterError, - MathError, MintabilityError, Mismatch, RepetitionError, TypeError, - }, - InstructionType, - }, - metadata::{MetadataError, MetadataValueBox, SizeError}, - parameter::ParameterValueBox, - permission::JsonString, - prelude::*, - query::{ - error::{FindError, QueryExecutionFail}, - predicate::{ - numerical::{SemiInterval, SemiRange}, - string::StringPredicate, - value::{AtIndex, Container, QueryOutputPredicate}, - GenericPredicateBox, NonTrivial, PredicateBox, - }, - ForwardCursor, Pagination, QueryOutputBox, Sorting, - }, - transaction::{ - error::TransactionLimitError, SignedTransactionV1, TransactionLimits, TransactionPayload, - }, - BatchedResponse, BatchedResponseV1, Level, -}; -use iroha_primitives::{ - addr::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrHost, SocketAddrV4, SocketAddrV6}, - const_vec::ConstVec, - conststr::ConstString, - unique_vec::UniqueVec, -}; +use iroha_schema_gen::complete_data_model::*; use parity_scale_codec::{DecodeAll, Encode}; use serde::{de::DeserializeOwned, Serialize}; From f02140e12245e1277d58fe2bdb54f9104c4068b0 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Tue, 28 May 2024 12:46:25 +0900 Subject: [PATCH 02/10] test: check upgrade event Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/upgrade.rs | 48 +++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index be51bbca183..251f50f5773 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -1,6 +1,7 @@ use std::{path::Path, str::FromStr as _}; use eyre::Result; +use futures_util::TryStreamExt as _; use iroha_client::{ client::{self, Client, QueryResult}, crypto::KeyPair, @@ -10,6 +11,7 @@ use iroha_logger::info; use serde_json::json; use test_network::*; use test_samples::ALICE_ID; +use tokio::sync::mpsc; const ADMIN_PUBLIC_KEY_MULTIHASH: &str = "ed012076E5CA9698296AF9BE2CA45F525CB3BCFDEB7EE068BA56F973E9DD90564EF4FC"; @@ -76,8 +78,8 @@ fn executor_upgrade_should_run_migration() -> Result<()> { let can_unregister_domain_token_id = "CanUnregisterDomain".parse().unwrap(); // Check that `CanUnregisterDomain` exists - let data_model = client.request(FindExecutorDataModel)?; - assert!(data_model + assert!(client + .request(FindExecutorDataModel)? .permissions() .iter() .any(|id| id == &can_unregister_domain_token_id)); @@ -197,7 +199,7 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { #[test] fn migration_fail_should_not_cause_any_effects() { - let (_rt, _peer, client) = ::new().with_port(10_995).start_with_runtime(); + let (_rt, _peer, client) = ::new().with_port(10_999).start_with_runtime(); wait_for_genesis_committed(&vec![client.clone()], 0); let assert_domain_does_not_exist = |client: &Client, domain_id: &DomainId| { @@ -225,6 +227,46 @@ fn migration_fail_should_not_cause_any_effects() { // been changed, because `executor_with_migration_fail` does not allow any queries } +#[test] +fn migration_should_cause_upgrade_event() { + let (rt, _peer, client) = ::new().with_port(10_996).start_with_runtime(); + wait_for_genesis_committed(&vec![client.clone()], 0); + + let (sender, mut receiver) = mpsc::channel(1); + let events_client = client.clone(); + + let _handle = rt.spawn(async move { + let mut stream = events_client + .listen_for_events_async([ExecutorEventFilter::new()]) + .await + .unwrap(); + while let Some(event) = stream.try_next().await.unwrap() { + if let EventBox::Data(DataEvent::Executor(ExecutorEvent::Upgraded(ExecutorUpgrade { + new_data_model, + }))) = event + { + let _ = sender.send(new_data_model).await; + } + } + }); + + upgrade_executor( + &client, + "tests/integration/smartcontracts/executor_with_custom_permission", + ) + .unwrap(); + + let data_model = rt + .block_on(async { + tokio::time::timeout(std::time::Duration::from_secs(60), receiver.recv()).await + }) + .ok() + .flatten() + .expect("should receive upgraded event immediately after upgrade"); + + assert!(!data_model.permissions.is_empty()); +} + fn upgrade_executor(client: &Client, executor: impl AsRef) -> Result<()> { info!("Building executor"); From 57a0058212f42035338e9570023281bebe08b111 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Tue, 28 May 2024 17:47:11 +0900 Subject: [PATCH 03/10] refactor: apply suggestions from code review Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/asset.rs | 2 +- .../integration/domain_owner_permissions.rs | 16 +++---- client/tests/integration/events/data.rs | 4 +- client/tests/integration/permissions.rs | 14 +++--- client/tests/integration/queries/role.rs | 2 +- client/tests/integration/roles.rs | 16 +++---- .../executor_remove_permission/src/lib.rs | 2 +- .../src/lib.rs | 11 +++-- .../integration/triggers/by_call_trigger.rs | 2 +- client/tests/integration/upgrade.rs | 6 +-- core/benches/blocks/common.rs | 8 ++-- core/test_network/src/lib.rs | 12 ++--- data_model/src/lib.rs | 46 +++---------------- data_model/src/permission.rs | 24 +--------- default_executor/src/lib.rs | 2 +- .../executor/derive/src/permission.rs | 28 ++++++++++- smart_contract/executor/src/default.rs | 32 ++++++------- .../executor/src/default/permissions.rs | 4 +- smart_contract/executor/src/lib.rs | 2 +- smart_contract/executor/src/permission.rs | 33 ++----------- tools/kagami/src/genesis.rs | 6 +-- 21 files changed, 111 insertions(+), 161 deletions(-) diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index b5912ddf528..43c34a49876 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -298,7 +298,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { let instruction = Grant::permission( Permission::new( "CanTransferUserAsset".parse().unwrap(), - &json!({ "asset_id": asset_id }), + json!({ "asset_id": asset_id }).into(), ), alice_id.clone(), ); diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index c5dd5d38a52..a12c7a05b22 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -48,7 +48,7 @@ fn domain_owner_domain_permissions() -> Result<()> { // Granting a respective token also allows "bob@kingdom" to do so let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; let transaction = TransactionBuilder::new(chain_id, bob_id.clone()) @@ -66,7 +66,7 @@ fn domain_owner_domain_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke domain related permission tokens let token = Permission::new( "CanUnregisterDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -106,7 +106,7 @@ fn domain_owner_account_permissions() -> Result<()> { let bob_id = BOB_ID.clone(); let token = Permission::new( "CanUnregisterAccount".parse().unwrap(), - &json!({ "account_id": mad_hatter_id }), + json!({ "account_id": mad_hatter_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -141,7 +141,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // Grant permission to register asset definitions to "bob@kingdom" let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); test_client.submit_blocking(Grant::permission(token, bob_id.clone()))?; @@ -172,7 +172,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke asset definition related permission tokens in her domain let token = Permission::new( "CanUnregisterAssetDefinition".parse().unwrap(), - &json!({ "asset_definition_id": coin_id }), + json!({ "asset_definition_id": coin_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -206,7 +206,7 @@ fn domain_owner_asset_permissions() -> Result<()> { // Grant permission to register asset definitions to "bob@kingdom" let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); test_client.submit_blocking(Grant::permission(token, bob_id.clone()))?; @@ -242,7 +242,7 @@ fn domain_owner_asset_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke asset related permission tokens in her domain let token = Permission::new( "CanUnregisterUserAsset".parse().unwrap(), - &json!({ "asset_id": bob_store_id }), + json!({ "asset_id": bob_store_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -293,7 +293,7 @@ fn domain_owner_trigger_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke trigger related permission tokens in her domain let token = Permission::new( "CanUnregisterUserTrigger".parse().unwrap(), - &json!({ "account_id": bob_id }), + json!({ "account_id": bob_id }).into(), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index 7dcbe278d2c..dce045a2189 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -201,11 +201,11 @@ fn produce_multiple_events() -> Result<()> { let role_id = RoleId::from_str("TEST_ROLE")?; let token_1 = Permission::new( "CanRemoveKeyValueInAccount".parse()?, - &json!({ "account_id": alice_id }), + json!({ "account_id": alice_id }).into(), ); let token_2 = Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": alice_id }), + json!({ "account_id": alice_id }).into(), ); let role = iroha_client::data_model::role::Role::new(role_id.clone()) .add_permission(token_1.clone()) diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index d2bc825b8c3..e8501c5f157 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -23,7 +23,7 @@ fn genesis_transactions_are_validated() { // Setting up genesis let genesis = GenesisNetwork::test_with_instructions([Grant::permission( - Permission::new("InvalidToken".parse().unwrap(), &json!(null)), + Permission::new("InvalidToken".parse().unwrap(), json!(null).into()), ALICE_ID.clone(), ) .into()]); @@ -233,7 +233,7 @@ fn permissions_differ_not_only_by_names() { let allow_alice_to_set_key_value_in_hats = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - &json!({ "asset_id": mouse_hat_id }), + json!({ "asset_id": mouse_hat_id }).into(), ), alice_id.clone(), ); @@ -269,7 +269,7 @@ fn permissions_differ_not_only_by_names() { let allow_alice_to_set_key_value_in_shoes = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - &json!({ "asset_id": mouse_shoes_id }), + json!({ "asset_id": mouse_shoes_id }).into(), ), alice_id, ); @@ -356,7 +356,7 @@ fn permissions_are_unified() { let allow_alice_to_transfer_rose_1 = Grant::permission( Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": format!("rose#wonderland#{alice_id}") }), + json!({ "asset_id": format!("rose#wonderland#{alice_id}") }).into(), ), alice_id.clone(), ); @@ -365,7 +365,7 @@ fn permissions_are_unified() { Permission::new( "CanTransferUserAsset".parse().unwrap(), // different content, but same meaning - json!({ "asset_id": format!("rose##{alice_id}") }), + json!({ "asset_id": format!("rose##{alice_id}") }).into(), ), alice_id, ); @@ -392,7 +392,7 @@ fn associated_permissions_removed_on_unregister() { let register_domain = Register::domain(kingdom); let bob_to_set_kv_in_domain_token = Permission::new( "CanSetKeyValueInDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); let allow_bob_to_set_kv_in_domain = Grant::permission(bob_to_set_kv_in_domain_token.clone(), bob_id.clone()); @@ -439,7 +439,7 @@ fn associated_permissions_removed_from_role_on_unregister() { let register_domain = Register::domain(kingdom); let set_kv_in_domain_token = Permission::new( "CanSetKeyValueInDomain".parse().unwrap(), - &json!({ "domain_id": kingdom_id }), + json!({ "domain_id": kingdom_id }).into(), ); let role = Role::new(role_id.clone()).add_permission(set_kv_in_domain_token.clone()); let register_role = Register::role(role); diff --git a/client/tests/integration/queries/role.rs b/client/tests/integration/queries/role.rs index 80391bbf673..84a97390d4c 100644 --- a/client/tests/integration/queries/role.rs +++ b/client/tests/integration/queries/role.rs @@ -133,7 +133,7 @@ fn find_roles_by_account_id() -> Result<()> { .map(|role_id| { Register::role(Role::new(role_id).add_permission(Permission::new( "CanSetKeyValueInAccount".parse().unwrap(), - &json!({ "account_id": alice_id }), + json!({ "account_id": alice_id }).into(), ))) }) .collect::>(); diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index a27875fff2a..302d54b479c 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -28,7 +28,7 @@ fn register_role_with_empty_token_params() -> Result<()> { wait_for_genesis_committed(&vec![test_client.clone()], 0); let role_id = "root".parse().expect("Valid"); - let token = Permission::new("token".parse()?, &json!(null)); + let token = Permission::new("token".parse()?, json!(null).into()); let role = Role::new(role_id).add_permission(token); test_client.submit(Register::role(role))?; @@ -64,11 +64,11 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { let role = Role::new(role_id.clone()) .add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": mouse_id }), + json!({ "account_id": mouse_id }).into(), )) .add_permission(Permission::new( "CanRemoveKeyValueInAccount".parse()?, - &json!({ "account_id": mouse_id }), + json!({ "account_id": mouse_id }).into(), )); let register_role = Register::role(role); test_client.submit_blocking(register_role)?; @@ -113,7 +113,7 @@ fn unregistered_role_removed_from_account() -> Result<()> { // Register root role let register_role = Register::role(Role::new(role_id.clone()).add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": alice_id }), + json!({ "account_id": alice_id }).into(), ))); test_client.submit_blocking(register_role)?; @@ -151,7 +151,7 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { .expect("should be valid"); let role = Role::new(role_id).add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": rose_asset_id }), + json!({ "account_id": rose_asset_id }).into(), )); let err = test_client @@ -178,13 +178,13 @@ fn role_permissions_are_deduplicated() { let allow_alice_to_transfer_rose_1 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), + json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }).into(), ); // Different content, but same meaning let allow_alice_to_transfer_rose_2 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), + json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }).into(), ); let role_id: RoleId = "role_id".parse().expect("Valid"); @@ -247,7 +247,7 @@ fn grant_revoke_role_permissions() -> Result<()> { ); let permission = Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": mouse_id }), + json!({ "account_id": mouse_id }).into(), ); let grant_role_permission = Grant::role_permission(permission.clone(), role_id.clone()); let revoke_role_permission = Revoke::role_permission(permission.clone(), role_id.clone()); diff --git a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs index a81d80355f4..a88a34fd123 100644 --- a/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_remove_permission/src/lib.rs @@ -28,7 +28,7 @@ pub fn migrate(_block_height: u64) -> MigrationResult { // So any added custom permission tokens will be also removed DataModelBuilder::with_default_permissions() .remove_permission::() - .set(); + .build_and_set(); Ok(()) } diff --git a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs index 1fb8612595f..aeab7729e09 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs @@ -95,7 +95,7 @@ impl Executor { })?; if let Ok(can_unregister_domain_token) = - iroha_executor::default::permissions::domain::CanUnregisterDomain::try_from_object( + iroha_executor::default::permissions::domain::CanUnregisterDomain::try_from( &token, ) { @@ -120,7 +120,7 @@ impl Executor { Revoke::permission( Permission::new( can_unregister_domain_definition_id.clone(), - &json!({ "domain_id": domain_id }), + json!({ "domain_id": domain_id }).into(), ), account.id().clone(), ) @@ -137,7 +137,10 @@ impl Executor { })?; Grant::permission( - Permission::new(can_control_domain_lives_definition_id.clone(), &json!(null)), + Permission::new( + can_control_domain_lives_definition_id.clone(), + json!(null).into(), + ), account.id().clone(), ) .execute() @@ -201,7 +204,7 @@ pub fn migrate(_block_height: u64) -> MigrationResult { DataModelBuilder::with_default_permissions() .remove_permission::() .add_permission::() - .set(); + .build_and_set(); Executor::replace_token(&accounts) } diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index 3e8f7496336..270757905a8 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -288,7 +288,7 @@ fn only_account_with_permission_can_register_trigger() -> Result<()> { // on behalf of alice let permission_on_registration = Permission::new( "CanRegisterUserTrigger".parse().unwrap(), - &json!({ "account_id": ALICE_ID.clone(), }), + json!({ "account_id": ALICE_ID.clone(), }).into(), ); // Trigger with 'alice' as authority diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index 251f50f5773..f55d6b5f84b 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -92,7 +92,7 @@ fn executor_upgrade_should_run_migration() -> Result<()> { .expect("Valid"); assert!(alice_tokens.contains(&Permission::new( can_unregister_domain_token_id.clone(), - &json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }), + json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }).into(), ))); upgrade_executor( @@ -121,7 +121,7 @@ fn executor_upgrade_should_run_migration() -> Result<()> { .expect("Valid"); assert!(alice_tokens.contains(&Permission::new( can_control_domain_lives_token_id, - &json!(null), + json!(null).into(), ))); Ok(()) @@ -135,7 +135,7 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Permission which will be removed by executor let can_unregister_domain_token = Permission::new( "CanUnregisterDomain".parse()?, - &json!({ "domain_id": DomainId::from_str("wonderland")? }), + json!({ "domain_id": DomainId::from_str("wonderland")? }).into(), ); // Register `TEST_ROLE` with permission diff --git a/core/benches/blocks/common.rs b/core/benches/blocks/common.rs index f2017fa3471..ae0601fbc6c 100644 --- a/core/benches/blocks/common.rs +++ b/core/benches/blocks/common.rs @@ -15,7 +15,7 @@ use iroha_data_model::{ isi::InstructionBox, prelude::*, transaction::TransactionLimits, - ChainId, + ChainId, JsonString, }; use iroha_primitives::unique_vec::UniqueVec; use serde_json::json; @@ -69,7 +69,7 @@ pub fn populate_state( let can_unregister_domain = Grant::permission( Permission::new( "CanUnregisterDomain".parse().unwrap(), - &json!({ "domain_id": domain_id.clone() }), + JsonString::from(&json!({ "domain_id": domain_id.clone() })), ), owner_id.clone(), ); @@ -81,7 +81,7 @@ pub fn populate_state( let can_unregister_account = Grant::permission( Permission::new( "CanUnregisterAccount".parse().unwrap(), - &json!({ "account_id": account_id.clone() }), + JsonString::from(&json!({ "account_id": account_id.clone() })), ), owner_id.clone(), ); @@ -94,7 +94,7 @@ pub fn populate_state( let can_unregister_asset_definition = Grant::permission( Permission::new( "CanUnregisterAssetDefinition".parse().unwrap(), - &json!({ "asset_definition_id": asset_definition_id }), + JsonString::from(&json!({ "asset_definition_id": asset_definition_id })), ), owner_id.clone(), ); diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index 363d4be93bb..e687ded1cf4 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -92,22 +92,22 @@ impl TestGenesis for GenesisNetwork { let mint_rose_permission = Permission::new( "CanMintAssetWithDefinition".parse().unwrap(), - &json!({ "asset_definition_id": rose_definition_id }), + json!({ "asset_definition_id": rose_definition_id }).into(), ); let burn_rose_permission = Permission::new( "CanBurnAssetWithDefinition".parse().unwrap(), - &json!({ "asset_definition_id": rose_definition_id }), + json!({ "asset_definition_id": rose_definition_id }).into(), ); let unregister_any_peer_permission = - Permission::new("CanUnregisterAnyPeer".parse().unwrap(), &json!(null)); + Permission::new("CanUnregisterAnyPeer".parse().unwrap(), json!(null).into()); let unregister_any_role_permission = - Permission::new("CanUnregisterAnyRole".parse().unwrap(), &json!(null)); + Permission::new("CanUnregisterAnyRole".parse().unwrap(), json!(null).into()); let unregister_wonderland_domain = Permission::new( "CanUnregisterDomain".parse().unwrap(), - &json!({ "domain_id": DomainId::from_str("wonderland").unwrap() } ), + json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }).into(), ); let upgrade_executor_permission = - Permission::new("CanUpgradeExecutor".parse().unwrap(), &json!(null)); + Permission::new("CanUpgradeExecutor".parse().unwrap(), json!(null).into()); let first_transaction = genesis .first_transaction_mut() diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 979a27147c4..4441c8504e0 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -617,8 +617,6 @@ pub mod parameter { #[model] #[allow(clippy::redundant_pub_crate)] mod model { - use iroha_schema::TypeId; - use super::*; /// Unique id of blockchain @@ -870,37 +868,13 @@ mod model { /// String containing serialized valid JSON. /// /// This string is guaranteed to be parsed as JSON. - #[derive(Display, Default, Debug, Clone, Eq, Encode, Decode, TypeId, Ord, PartialOrd)] + #[derive(Display, Default, Debug, Clone, Eq, Encode, Decode, Ord, PartialOrd, IntoSchema)] #[ffi_type(unsafe {robust})] #[repr(transparent)] #[display(fmt = "{}", "0")] pub struct JsonString(pub(super) String); } -/// A helper trait for polymorphism, implemented for various types -pub trait IntoJsonString { - /// Converts self into [`JsonString`] - fn into_json_string(self) -> JsonString; -} - -impl IntoJsonString for JsonString { - fn into_json_string(self) -> JsonString { - self - } -} - -impl IntoJsonString for &serde_json::Value { - fn into_json_string(self) -> JsonString { - JsonString::from(self) - } -} - -impl IntoJsonString for serde_json::Value { - fn into_json_string(self) -> JsonString { - (&self).into_json_string() - } -} - impl JsonString { /// Deserialize JSON into something /// # Errors @@ -936,6 +910,12 @@ impl From<&serde_json::Value> for JsonString { } } +impl From for JsonString { + fn from(value: serde_json::Value) -> Self { + Self::from(&value) + } +} + impl PartialEq for JsonString { fn eq(&self, other: &Self) -> bool { serde_json::from_str::(&self.0).unwrap() @@ -963,18 +943,6 @@ impl serde::ser::Serialize for JsonString { } } -impl IntoSchema for JsonString { - fn type_name() -> iroha_schema::Ident { - ::id() - } - - fn update_schema_map(map: &mut iroha_schema::MetaMap) { - if !map.contains_key::() { - map.insert::(iroha_schema::Metadata::String); - } - } -} - macro_rules! impl_encode_as_id_box { ($($ty:ty),+ $(,)?) => { $( impl $ty { diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index 4651554585c..46fc1e7ebcc 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -1,7 +1,6 @@ //! Permission Token and related impls #[cfg(not(feature = "std"))] use alloc::{collections::BTreeSet, format, string::String, vec::Vec}; -use core::borrow::Borrow; #[cfg(feature = "std")] use std::collections::BTreeSet; @@ -66,6 +65,7 @@ mod model { IntoSchema, Display, Getters, + Constructor, )] #[ffi_type] #[display(fmt = "PERMISSION `{id}` = `{payload}`")] @@ -82,28 +82,6 @@ mod model { } } -impl Permission { - /// Constructor - pub fn new(id: PermissionId, payload: impl IntoJsonString) -> Self { - Self { - id, - payload: payload.into_json_string(), - } - } -} - -impl Borrow for PermissionId { - fn borrow(&self) -> &str { - self.name.borrow() - } -} - -impl Borrow for Permission { - fn borrow(&self) -> &str { - self.id.borrow() - } -} - impl Permission { /// Getter // TODO: derive with getset once FFI impl is fixed diff --git a/default_executor/src/lib.rs b/default_executor/src/lib.rs index 546a6cab359..a506d4df6dc 100644 --- a/default_executor/src/lib.rs +++ b/default_executor/src/lib.rs @@ -52,7 +52,7 @@ impl Executor { pub fn migrate(block_height: u64) -> MigrationResult { Executor::ensure_genesis(block_height)?; - DataModelBuilder::with_default_permissions().set(); + DataModelBuilder::with_default_permissions().build_and_set(); Ok(()) } diff --git a/smart_contract/executor/derive/src/permission.rs b/smart_contract/executor/derive/src/permission.rs index 82c6a51fb14..422580e6316 100644 --- a/smart_contract/executor/derive/src/permission.rs +++ b/smart_contract/executor/derive/src/permission.rs @@ -26,9 +26,35 @@ pub fn impl_derive_permission(input: &syn::DeriveInput) -> TokenStream { res, "Failed to get permission token from cursor" )) - .filter_map(|token| Self::try_from_object(&token).ok()) + .filter_map(|token| Self::try_from(&token).ok()) .any(|token| self == &token) } } + + impl #impl_generics TryFrom<&::iroha_executor::data_model::permission::Permission> for #ident #ty_generics #where_clause { + type Error = ::iroha_executor::TryFromDataModelObjectError; + + fn try_from( + value: &::iroha_executor::data_model::permission::Permission, + ) -> core::result::Result { + if *value.id() != ::id() { + return Err(Self::Error::Id(value.id().name().clone())); + } + value + .payload() + .deserialize() + .map_err(Self::Error::Deserialize) + } + } + + impl #impl_generics From<#ident #ty_generics> for ::iroha_executor::data_model::permission::Permission #where_clause { + fn from(value: #ident #ty_generics) -> Self { + ::iroha_executor::data_model::permission::Permission::new( + <#ident as ::iroha_executor::permission::Permission>::id(), + ::iroha_executor::data_model::JsonString::serialize(&value) + .expect("failed to serialize concrete data model entity; this is a bug"), + ) + } + } } } diff --git a/smart_contract/executor/src/default.rs b/smart_contract/executor/src/default.rs index 57f0e1956e3..ca9fefa1ae0 100644 --- a/smart_contract/executor/src/default.rs +++ b/smart_contract/executor/src/default.rs @@ -43,7 +43,7 @@ pub use trigger::{ use crate::{ permission::Permission as _, - prelude::{Permission as PermissionObject, *}, + prelude::{Permission, *}, }; // NOTE: If any new `visit_..` functions are introduced in this module, one should @@ -293,7 +293,7 @@ pub mod domain { } #[allow(clippy::too_many_lines)] - fn is_token_domain_associated(permission: &PermissionObject, domain_id: &DomainId) -> bool { + fn is_token_domain_associated(permission: &Permission, domain_id: &DomainId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -537,7 +537,7 @@ pub mod account { ); } - fn is_token_account_associated(permission: &PermissionObject, account_id: &AccountId) -> bool { + fn is_token_account_associated(permission: &Permission, account_id: &AccountId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -780,7 +780,7 @@ pub mod asset_definition { } fn is_token_asset_definition_associated( - permission: &PermissionObject, + permission: &Permission, asset_definition_id: &AssetDefinitionId, ) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { @@ -1230,7 +1230,7 @@ pub mod role { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = PermissionObject::from(any_token.clone()); + let token = Permission::from(any_token.clone()); let isi = <$isi_type>::role_permission(token, role_id); if is_genesis($executor) { execute!($executor, isi); @@ -1268,7 +1268,7 @@ pub mod role { iroha_smart_contract::debug!(&format!("Checking `{token:?}`")); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = PermissionObject::from(any_token); + let token = Permission::from(any_token); new_role = new_role.add_permission(token); continue; } @@ -1324,17 +1324,17 @@ pub mod role { pub fn visit_grant_role_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_grant, Grant); } pub fn visit_revoke_role_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { - impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); + impl_validate_grant_revoke_role_permission!(executor, isi, authority, validate_revoke, Revoke); } } @@ -1563,7 +1563,7 @@ pub mod trigger { ); } - fn is_token_trigger_associated(permission: &PermissionObject, trigger_id: &TriggerId) -> bool { + fn is_token_trigger_associated(permission: &Permission, trigger_id: &TriggerId) -> bool { let Ok(permission) = AnyPermission::try_from(permission) else { return false; }; @@ -1628,7 +1628,7 @@ pub mod permission { let token = $isi.object(); if let Ok(any_token) = AnyPermission::try_from(token) { - let token = PermissionObject::from(any_token.clone()); + let token = Permission::from(any_token.clone()); let isi = <$isi_type>::permission(token, account_id); if is_genesis($executor) { execute!($executor, isi); @@ -1654,28 +1654,28 @@ pub mod permission { pub fn visit_grant_account_permission( executor: &mut V, authority: &AccountId, - isi: &Grant, + isi: &Grant, ) { impl_validate!( executor, authority, isi, validate_grant, - Grant + Grant ); } pub fn visit_revoke_account_permission( executor: &mut V, authority: &AccountId, - isi: &Revoke, + isi: &Revoke, ) { impl_validate!( executor, authority, isi, validate_revoke, - Revoke + Revoke ); } } diff --git a/smart_contract/executor/src/default/permissions.rs b/smart_contract/executor/src/default/permissions.rs index 8406a9afe2c..1ac643b451d 100644 --- a/smart_contract/executor/src/default/permissions.rs +++ b/smart_contract/executor/src/default/permissions.rs @@ -48,7 +48,7 @@ macro_rules! declare_permissions { fn try_from(token: &$crate::data_model::permission::Permission) -> Result { match token.id().name().as_ref() { $( stringify!($token_ty) => { - let token = <$($token_path::)+$token_ty>::try_from_object(token)?; + let token = <$($token_path::)+$token_ty>::try_from(token)?; Ok(Self::$token_ty(token)) } )+ _ => Err(Self::Error::Id(token.id().name().clone())) @@ -59,7 +59,7 @@ macro_rules! declare_permissions { impl From for $crate::data_model::permission::Permission { fn from(token: AnyPermission) -> Self { match token { $( - AnyPermission::$token_ty(token) => token.to_object(), )* + AnyPermission::$token_ty(token) => token.into(), )* } } } diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index 61fd61bc2cf..08281ae3b3d 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -237,7 +237,7 @@ impl DataModelBuilder { /// Set the data model of the executor via [`set_data_model`] #[cfg(not(test))] - pub fn set(self) { + pub fn build_and_set(self) { set_data_model(&ExecutorDataModel::new( self.permissions, data_model::JsonString::serialize(&self.schema) diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 5672b4a7114..904517b6a37 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -3,14 +3,11 @@ use alloc::borrow::ToOwned as _; use iroha_schema::IntoSchema; -use iroha_smart_contract::{data_model::JsonString, QueryOutputCursor}; +use iroha_smart_contract::QueryOutputCursor; use iroha_smart_contract_utils::debug::DebugExpectExt as _; use serde::{de::DeserializeOwned, Serialize}; -use crate::{ - prelude::{Permission as PermissionObject, *}, - TryFromDataModelObjectError, -}; +use crate::prelude::{Permission as PermissionObject, *}; /// Is used to check if the permission token is owned by the account. pub trait Permission: @@ -27,28 +24,6 @@ pub trait Permission: .dbg_expect("Failed to parse permission id as `Name`"), ) } - - /// Try to convert from [`PermissionObject`] - /// # Errors - /// See [`TryFromDataModelObjectError`] - fn try_from_object(object: &PermissionObject) -> Result { - if *object.id() != ::id() { - return Err(TryFromDataModelObjectError::Id(object.id().name().clone())); - } - object - .payload() - .deserialize() - .map_err(TryFromDataModelObjectError::Deserialize) - } - - /// Convert into [`PermissionObject`] - fn to_object(&self) -> PermissionObject { - PermissionObject::new( - ::id(), - JsonString::serialize(&self) - .expect("failed to serialize concrete data model entity; this is a bug"), - ) - } } /// Trait that should be implemented for all permission tokens. @@ -407,9 +382,9 @@ mod tests { let object = PermissionObject::new( "SampleToken".parse().unwrap(), - json!({ "can_do_whatever": false }), + json!({ "can_do_whatever": false }).into(), ); - let parsed = SampleToken::try_from_object(&object).expect("valid"); + let parsed = SampleToken::try_from(&object).expect("valid"); assert!(!parsed.can_do_whatever); } diff --git a/tools/kagami/src/genesis.rs b/tools/kagami/src/genesis.rs index 4f5b6203c66..f5e2cd14fe2 100644 --- a/tools/kagami/src/genesis.rs +++ b/tools/kagami/src/genesis.rs @@ -116,7 +116,7 @@ pub fn generate_default( AssetId::new("cabbage#garden_of_live_flowers".parse()?, ALICE_ID.clone()), ); let grant_permission_to_set_parameters = Grant::permission( - Permission::new("CanSetParameters".parse()?, &json!(null)), + Permission::new("CanSetParameters".parse()?, json!(null).into()), ALICE_ID.clone(), ); let transfer_rose_ownership = Transfer::asset_definition( @@ -133,11 +133,11 @@ pub fn generate_default( Role::new("ALICE_METADATA_ACCESS".parse()?) .add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - &json!({ "account_id": ALICE_ID.clone() }), + json!({ "account_id": ALICE_ID.clone() }).into(), )) .add_permission(Permission::new( "CanRemoveKeyValueInAccount".parse()?, - &json!({ "account_id": ALICE_ID.clone() }), + json!({ "account_id": ALICE_ID.clone() }).into(), )), ) .into(); From b8ce7022fe1f4d361045a36eb39b0a3b00ece444 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 08:57:45 +0900 Subject: [PATCH 04/10] refactor: use `impl Into` Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/asset.rs | 2 +- .../integration/domain_owner_permissions.rs | 16 ++++++++-------- client/tests/integration/events/data.rs | 4 ++-- client/tests/integration/permissions.rs | 14 +++++++------- client/tests/integration/queries/role.rs | 2 +- client/tests/integration/roles.rs | 16 ++++++++-------- .../executor_with_custom_permission/src/lib.rs | 7 ++----- .../integration/triggers/by_call_trigger.rs | 2 +- client/tests/integration/upgrade.rs | 6 +++--- core/test_network/src/lib.rs | 12 ++++++------ data_model/src/permission.rs | 9 ++++++++- smart_contract/executor/src/permission.rs | 2 +- tools/kagami/src/genesis.rs | 6 +++--- 13 files changed, 51 insertions(+), 47 deletions(-) diff --git a/client/tests/integration/asset.rs b/client/tests/integration/asset.rs index 43c34a49876..e27a69b91de 100644 --- a/client/tests/integration/asset.rs +++ b/client/tests/integration/asset.rs @@ -298,7 +298,7 @@ fn find_rate_and_make_exchange_isi_should_succeed() { let instruction = Grant::permission( Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": asset_id }).into(), + json!({ "asset_id": asset_id }), ), alice_id.clone(), ); diff --git a/client/tests/integration/domain_owner_permissions.rs b/client/tests/integration/domain_owner_permissions.rs index a12c7a05b22..e9751363444 100644 --- a/client/tests/integration/domain_owner_permissions.rs +++ b/client/tests/integration/domain_owner_permissions.rs @@ -48,7 +48,7 @@ fn domain_owner_domain_permissions() -> Result<()> { // Granting a respective token also allows "bob@kingdom" to do so let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; let transaction = TransactionBuilder::new(chain_id, bob_id.clone()) @@ -66,7 +66,7 @@ fn domain_owner_domain_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke domain related permission tokens let token = Permission::new( "CanUnregisterDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -106,7 +106,7 @@ fn domain_owner_account_permissions() -> Result<()> { let bob_id = BOB_ID.clone(); let token = Permission::new( "CanUnregisterAccount".parse().unwrap(), - json!({ "account_id": mad_hatter_id }).into(), + json!({ "account_id": mad_hatter_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -141,7 +141,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // Grant permission to register asset definitions to "bob@kingdom" let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); test_client.submit_blocking(Grant::permission(token, bob_id.clone()))?; @@ -172,7 +172,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke asset definition related permission tokens in her domain let token = Permission::new( "CanUnregisterAssetDefinition".parse().unwrap(), - json!({ "asset_definition_id": coin_id }).into(), + json!({ "asset_definition_id": coin_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -206,7 +206,7 @@ fn domain_owner_asset_permissions() -> Result<()> { // Grant permission to register asset definitions to "bob@kingdom" let token = Permission::new( "CanRegisterAssetDefinitionInDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); test_client.submit_blocking(Grant::permission(token, bob_id.clone()))?; @@ -242,7 +242,7 @@ fn domain_owner_asset_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke asset related permission tokens in her domain let token = Permission::new( "CanUnregisterUserAsset".parse().unwrap(), - json!({ "asset_id": bob_store_id }).into(), + json!({ "asset_id": bob_store_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; @@ -293,7 +293,7 @@ fn domain_owner_trigger_permissions() -> Result<()> { // check that "alice@wonderland" as owner of domain can grant and revoke trigger related permission tokens in her domain let token = Permission::new( "CanUnregisterUserTrigger".parse().unwrap(), - json!({ "account_id": bob_id }).into(), + json!({ "account_id": bob_id }), ); test_client.submit_blocking(Grant::permission(token.clone(), bob_id.clone()))?; test_client.submit_blocking(Revoke::permission(token, bob_id))?; diff --git a/client/tests/integration/events/data.rs b/client/tests/integration/events/data.rs index dce045a2189..1ce0094bcdd 100644 --- a/client/tests/integration/events/data.rs +++ b/client/tests/integration/events/data.rs @@ -201,11 +201,11 @@ fn produce_multiple_events() -> Result<()> { let role_id = RoleId::from_str("TEST_ROLE")?; let token_1 = Permission::new( "CanRemoveKeyValueInAccount".parse()?, - json!({ "account_id": alice_id }).into(), + json!({ "account_id": alice_id }), ); let token_2 = Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": alice_id }).into(), + json!({ "account_id": alice_id }), ); let role = iroha_client::data_model::role::Role::new(role_id.clone()) .add_permission(token_1.clone()) diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index e8501c5f157..59fef1f088e 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -23,7 +23,7 @@ fn genesis_transactions_are_validated() { // Setting up genesis let genesis = GenesisNetwork::test_with_instructions([Grant::permission( - Permission::new("InvalidToken".parse().unwrap(), json!(null).into()), + Permission::new("InvalidToken".parse().unwrap(), json!(null)), ALICE_ID.clone(), ) .into()]); @@ -233,7 +233,7 @@ fn permissions_differ_not_only_by_names() { let allow_alice_to_set_key_value_in_hats = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - json!({ "asset_id": mouse_hat_id }).into(), + json!({ "asset_id": mouse_hat_id }), ), alice_id.clone(), ); @@ -269,7 +269,7 @@ fn permissions_differ_not_only_by_names() { let allow_alice_to_set_key_value_in_shoes = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - json!({ "asset_id": mouse_shoes_id }).into(), + json!({ "asset_id": mouse_shoes_id }), ), alice_id, ); @@ -356,7 +356,7 @@ fn permissions_are_unified() { let allow_alice_to_transfer_rose_1 = Grant::permission( Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": format!("rose#wonderland#{alice_id}") }).into(), + json!({ "asset_id": format!("rose#wonderland#{alice_id}") }), ), alice_id.clone(), ); @@ -365,7 +365,7 @@ fn permissions_are_unified() { Permission::new( "CanTransferUserAsset".parse().unwrap(), // different content, but same meaning - json!({ "asset_id": format!("rose##{alice_id}") }).into(), + json!({ "asset_id": format!("rose##{alice_id}") }), ), alice_id, ); @@ -392,7 +392,7 @@ fn associated_permissions_removed_on_unregister() { let register_domain = Register::domain(kingdom); let bob_to_set_kv_in_domain_token = Permission::new( "CanSetKeyValueInDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); let allow_bob_to_set_kv_in_domain = Grant::permission(bob_to_set_kv_in_domain_token.clone(), bob_id.clone()); @@ -439,7 +439,7 @@ fn associated_permissions_removed_from_role_on_unregister() { let register_domain = Register::domain(kingdom); let set_kv_in_domain_token = Permission::new( "CanSetKeyValueInDomain".parse().unwrap(), - json!({ "domain_id": kingdom_id }).into(), + json!({ "domain_id": kingdom_id }), ); let role = Role::new(role_id.clone()).add_permission(set_kv_in_domain_token.clone()); let register_role = Register::role(role); diff --git a/client/tests/integration/queries/role.rs b/client/tests/integration/queries/role.rs index 84a97390d4c..c537f5b557d 100644 --- a/client/tests/integration/queries/role.rs +++ b/client/tests/integration/queries/role.rs @@ -133,7 +133,7 @@ fn find_roles_by_account_id() -> Result<()> { .map(|role_id| { Register::role(Role::new(role_id).add_permission(Permission::new( "CanSetKeyValueInAccount".parse().unwrap(), - json!({ "account_id": alice_id }).into(), + json!({ "account_id": alice_id }), ))) }) .collect::>(); diff --git a/client/tests/integration/roles.rs b/client/tests/integration/roles.rs index 302d54b479c..986f298fc23 100644 --- a/client/tests/integration/roles.rs +++ b/client/tests/integration/roles.rs @@ -28,7 +28,7 @@ fn register_role_with_empty_token_params() -> Result<()> { wait_for_genesis_committed(&vec![test_client.clone()], 0); let role_id = "root".parse().expect("Valid"); - let token = Permission::new("token".parse()?, json!(null).into()); + let token = Permission::new("token".parse()?, json!(null)); let role = Role::new(role_id).add_permission(token); test_client.submit(Register::role(role))?; @@ -64,11 +64,11 @@ fn register_and_grant_role_for_metadata_access() -> Result<()> { let role = Role::new(role_id.clone()) .add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": mouse_id }).into(), + json!({ "account_id": mouse_id }), )) .add_permission(Permission::new( "CanRemoveKeyValueInAccount".parse()?, - json!({ "account_id": mouse_id }).into(), + json!({ "account_id": mouse_id }), )); let register_role = Register::role(role); test_client.submit_blocking(register_role)?; @@ -113,7 +113,7 @@ fn unregistered_role_removed_from_account() -> Result<()> { // Register root role let register_role = Register::role(Role::new(role_id.clone()).add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": alice_id }).into(), + json!({ "account_id": alice_id }), ))); test_client.submit_blocking(register_role)?; @@ -151,7 +151,7 @@ fn role_with_invalid_permissions_is_not_accepted() -> Result<()> { .expect("should be valid"); let role = Role::new(role_id).add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": rose_asset_id }).into(), + json!({ "account_id": rose_asset_id }), )); let err = test_client @@ -178,13 +178,13 @@ fn role_permissions_are_deduplicated() { let allow_alice_to_transfer_rose_1 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }).into(), + json!({ "asset_id": "rose#wonderland#ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); // Different content, but same meaning let allow_alice_to_transfer_rose_2 = Permission::new( "CanTransferUserAsset".parse().unwrap(), - json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }).into(), + json!({ "asset_id": "rose##ed0120CE7FA46C9DCE7EA4B125E2E36BDB63EA33073E7590AC92816AE1E861B7048B03@wonderland" }), ); let role_id: RoleId = "role_id".parse().expect("Valid"); @@ -247,7 +247,7 @@ fn grant_revoke_role_permissions() -> Result<()> { ); let permission = Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": mouse_id }).into(), + json!({ "account_id": mouse_id }), ); let grant_role_permission = Grant::role_permission(permission.clone(), role_id.clone()); let revoke_role_permission = Revoke::role_permission(permission.clone(), role_id.clone()); diff --git a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs index aeab7729e09..72d553d5ed1 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_permission/src/lib.rs @@ -120,7 +120,7 @@ impl Executor { Revoke::permission( Permission::new( can_unregister_domain_definition_id.clone(), - json!({ "domain_id": domain_id }).into(), + json!({ "domain_id": domain_id }), ), account.id().clone(), ) @@ -137,10 +137,7 @@ impl Executor { })?; Grant::permission( - Permission::new( - can_control_domain_lives_definition_id.clone(), - json!(null).into(), - ), + Permission::new(can_control_domain_lives_definition_id.clone(), json!(null)), account.id().clone(), ) .execute() diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index 270757905a8..cd50142186c 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -288,7 +288,7 @@ fn only_account_with_permission_can_register_trigger() -> Result<()> { // on behalf of alice let permission_on_registration = Permission::new( "CanRegisterUserTrigger".parse().unwrap(), - json!({ "account_id": ALICE_ID.clone(), }).into(), + json!({ "account_id": ALICE_ID.clone(), }), ); // Trigger with 'alice' as authority diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index f55d6b5f84b..fc82bb0703d 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -92,7 +92,7 @@ fn executor_upgrade_should_run_migration() -> Result<()> { .expect("Valid"); assert!(alice_tokens.contains(&Permission::new( can_unregister_domain_token_id.clone(), - json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }).into(), + json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }), ))); upgrade_executor( @@ -121,7 +121,7 @@ fn executor_upgrade_should_run_migration() -> Result<()> { .expect("Valid"); assert!(alice_tokens.contains(&Permission::new( can_control_domain_lives_token_id, - json!(null).into(), + json!(null), ))); Ok(()) @@ -135,7 +135,7 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { // Permission which will be removed by executor let can_unregister_domain_token = Permission::new( "CanUnregisterDomain".parse()?, - json!({ "domain_id": DomainId::from_str("wonderland")? }).into(), + json!({ "domain_id": DomainId::from_str("wonderland")? }), ); // Register `TEST_ROLE` with permission diff --git a/core/test_network/src/lib.rs b/core/test_network/src/lib.rs index e687ded1cf4..51108f115e6 100644 --- a/core/test_network/src/lib.rs +++ b/core/test_network/src/lib.rs @@ -92,22 +92,22 @@ impl TestGenesis for GenesisNetwork { let mint_rose_permission = Permission::new( "CanMintAssetWithDefinition".parse().unwrap(), - json!({ "asset_definition_id": rose_definition_id }).into(), + json!({ "asset_definition_id": rose_definition_id }), ); let burn_rose_permission = Permission::new( "CanBurnAssetWithDefinition".parse().unwrap(), - json!({ "asset_definition_id": rose_definition_id }).into(), + json!({ "asset_definition_id": rose_definition_id }), ); let unregister_any_peer_permission = - Permission::new("CanUnregisterAnyPeer".parse().unwrap(), json!(null).into()); + Permission::new("CanUnregisterAnyPeer".parse().unwrap(), json!(null)); let unregister_any_role_permission = - Permission::new("CanUnregisterAnyRole".parse().unwrap(), json!(null).into()); + Permission::new("CanUnregisterAnyRole".parse().unwrap(), json!(null)); let unregister_wonderland_domain = Permission::new( "CanUnregisterDomain".parse().unwrap(), - json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }).into(), + json!({ "domain_id": DomainId::from_str("wonderland").unwrap() }), ); let upgrade_executor_permission = - Permission::new("CanUpgradeExecutor".parse().unwrap(), json!(null).into()); + Permission::new("CanUpgradeExecutor".parse().unwrap(), json!(null)); let first_transaction = genesis .first_transaction_mut() diff --git a/data_model/src/permission.rs b/data_model/src/permission.rs index 46fc1e7ebcc..253c1784d37 100644 --- a/data_model/src/permission.rs +++ b/data_model/src/permission.rs @@ -65,7 +65,6 @@ mod model { IntoSchema, Display, Getters, - Constructor, )] #[ffi_type] #[display(fmt = "PERMISSION `{id}` = `{payload}`")] @@ -83,6 +82,14 @@ mod model { } impl Permission { + /// Constructor + pub fn new(id: PermissionId, payload: impl Into) -> Self { + Self { + id, + payload: payload.into(), + } + } + /// Getter // TODO: derive with getset once FFI impl is fixed pub fn payload(&self) -> &JsonString { diff --git a/smart_contract/executor/src/permission.rs b/smart_contract/executor/src/permission.rs index 904517b6a37..fbf53365e8a 100644 --- a/smart_contract/executor/src/permission.rs +++ b/smart_contract/executor/src/permission.rs @@ -382,7 +382,7 @@ mod tests { let object = PermissionObject::new( "SampleToken".parse().unwrap(), - json!({ "can_do_whatever": false }).into(), + json!({ "can_do_whatever": false }), ); let parsed = SampleToken::try_from(&object).expect("valid"); diff --git a/tools/kagami/src/genesis.rs b/tools/kagami/src/genesis.rs index f5e2cd14fe2..3d9ff06b134 100644 --- a/tools/kagami/src/genesis.rs +++ b/tools/kagami/src/genesis.rs @@ -116,7 +116,7 @@ pub fn generate_default( AssetId::new("cabbage#garden_of_live_flowers".parse()?, ALICE_ID.clone()), ); let grant_permission_to_set_parameters = Grant::permission( - Permission::new("CanSetParameters".parse()?, json!(null).into()), + Permission::new("CanSetParameters".parse()?, json!(null)), ALICE_ID.clone(), ); let transfer_rose_ownership = Transfer::asset_definition( @@ -133,11 +133,11 @@ pub fn generate_default( Role::new("ALICE_METADATA_ACCESS".parse()?) .add_permission(Permission::new( "CanSetKeyValueInAccount".parse()?, - json!({ "account_id": ALICE_ID.clone() }).into(), + json!({ "account_id": ALICE_ID.clone() }), )) .add_permission(Permission::new( "CanRemoveKeyValueInAccount".parse()?, - json!({ "account_id": ALICE_ID.clone() }).into(), + json!({ "account_id": ALICE_ID.clone() }), )), ) .into(); From b59371cac1f692c47ed05c62b291f0705b2d876a Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 09:02:27 +0900 Subject: [PATCH 05/10] chore: change port Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/upgrade.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/tests/integration/upgrade.rs b/client/tests/integration/upgrade.rs index fc82bb0703d..622842d8226 100644 --- a/client/tests/integration/upgrade.rs +++ b/client/tests/integration/upgrade.rs @@ -199,7 +199,7 @@ fn executor_upgrade_should_revoke_removed_permissions() -> Result<()> { #[test] fn migration_fail_should_not_cause_any_effects() { - let (_rt, _peer, client) = ::new().with_port(10_999).start_with_runtime(); + let (_rt, _peer, client) = ::new().with_port(10_998).start_with_runtime(); wait_for_genesis_committed(&vec![client.clone()], 0); let assert_domain_does_not_exist = |client: &Client, domain_id: &DomainId| { From f55cd542ad47543689f0576368280187f621e40a Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 09:02:36 +0900 Subject: [PATCH 06/10] chore: dead comment Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- core/src/smartcontracts/isi/world.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/core/src/smartcontracts/isi/world.rs b/core/src/smartcontracts/isi/world.rs index 66faa222671..5c882cd54ed 100644 --- a/core/src/smartcontracts/isi/world.rs +++ b/core/src/smartcontracts/isi/world.rs @@ -387,7 +387,6 @@ pub mod isi { permissions_before: BTreeSet, ) -> Result<(), Error> { let world = state_transaction.world(); - // permissions_before.reta let permissions_after = world.executor_data_model().permissions(); let permissions_removed = permissions_before .into_iter() From 449f36db9a8069ef7285c42eeba1c91a269f73af Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 09:09:08 +0900 Subject: [PATCH 07/10] fix: remove unused macro branch Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- data_model/src/query/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/data_model/src/query/mod.rs b/data_model/src/query/mod.rs index 41472ed8b55..01ed6624e73 100644 --- a/data_model/src/query/mod.rs +++ b/data_model/src/query/mod.rs @@ -356,8 +356,6 @@ impl TryFrom for u64 { /// Implements [`Query`] and, additionally, either [`SingularQuery`] or [`IterableQuery`], /// depending on whether the output type is wrapped into a Vec (purely syntactically) macro_rules! impl_queries { - // base case for the tt-muncher - () => {}; // we can't delegate matching over `Vec<$item:ty>` to an inner macro, // as the moment a fragment is matched as `$output:ty` it becomes opaque and unmatchable to any literal // https://doc.rust-lang.org/nightly/reference/macros-by-example.html#forwarding-a-matched-fragment From a541b7161e0f093af64e840e543daf770e529e2e Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 17:38:36 +0900 Subject: [PATCH 08/10] chore: rename Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- client/tests/integration/permissions.rs | 2 +- data_model/src/lib.rs | 24 +++++++++++++++--------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/client/tests/integration/permissions.rs b/client/tests/integration/permissions.rs index 59fef1f088e..1f47541466b 100644 --- a/client/tests/integration/permissions.rs +++ b/client/tests/integration/permissions.rs @@ -318,7 +318,7 @@ fn stored_vs_granted_token_payload() -> Result<()> { let allow_alice_to_set_key_value_in_mouse_asset = Grant::permission( Permission::new( "CanSetKeyValueInUserAsset".parse().unwrap(), - JsonString::from_json_string_unchecked(format!( + JsonString::from_string_unchecked(format!( // Introducing some whitespaces // This way, if the executor compares just JSON strings, this test would fail r##"{{ "asset_id" : "xor#wonderland#{}" }}"##, diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 4441c8504e0..a68d5a3b0c6 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -868,7 +868,20 @@ mod model { /// String containing serialized valid JSON. /// /// This string is guaranteed to be parsed as JSON. - #[derive(Display, Default, Debug, Clone, Eq, Encode, Decode, Ord, PartialOrd, IntoSchema)] + #[derive( + Display, + Default, + Debug, + Clone, + Eq, + Encode, + Decode, + Ord, + PartialOrd, + Eq, + PartialEq, + IntoSchema, + )] #[ffi_type(unsafe {robust})] #[repr(transparent)] #[display(fmt = "{}", "0")] @@ -899,7 +912,7 @@ impl JsonString { /// Create without checking whether the input is a valid JSON string. /// /// The caller must guarantee that the value is valid. - pub fn from_json_string_unchecked(value: String) -> Self { + pub fn from_string_unchecked(value: String) -> Self { Self(value) } } @@ -916,13 +929,6 @@ impl From for JsonString { } } -impl PartialEq for JsonString { - fn eq(&self, other: &Self) -> bool { - serde_json::from_str::(&self.0).unwrap() - == serde_json::from_str::(&other.0).unwrap() - } -} - impl<'de> serde::de::Deserialize<'de> for JsonString { fn deserialize(deserializer: D) -> Result where From 3578c372d308b74f133863df163e1448efca9d90 Mon Sep 17 00:00:00 2001 From: 0x009922 <43530070+0x009922@users.noreply.github.com> Date: Wed, 29 May 2024 17:46:04 +0900 Subject: [PATCH 09/10] fix: chore Signed-off-by: 0x009922 <43530070+0x009922@users.noreply.github.com> --- data_model/src/lib.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index a68d5a3b0c6..4f411f1dca9 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -869,18 +869,7 @@ mod model { /// /// This string is guaranteed to be parsed as JSON. #[derive( - Display, - Default, - Debug, - Clone, - Eq, - Encode, - Decode, - Ord, - PartialOrd, - Eq, - PartialEq, - IntoSchema, + Display, Default, Debug, Clone, Encode, Decode, Ord, PartialOrd, Eq, PartialEq, IntoSchema, )] #[ffi_type(unsafe {robust})] #[repr(transparent)] From cd7601a4720f7e1a9177c5a15373d665076fb488 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Ver=C5=A1i=C4=87?= Date: Wed, 29 May 2024 12:21:07 +0300 Subject: [PATCH 10/10] fix: ui tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marin Veršić --- .../ui_fail/cant_filter_singular_query.rs | 2 +- .../ui_fail/cant_filter_singular_query.stderr | 12 ++++++------ .../ui_fail/cant_filter_singular_query.rs | 2 +- .../ui_fail/cant_filter_singular_query.stderr | 18 +++++++++--------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/tests/ui_fail/cant_filter_singular_query.rs b/client/tests/ui_fail/cant_filter_singular_query.rs index f900887c656..45078dbb2cc 100644 --- a/client/tests/ui_fail/cant_filter_singular_query.rs +++ b/client/tests/ui_fail/cant_filter_singular_query.rs @@ -10,7 +10,7 @@ fn main() { let client = Client::new(config); let result = client - .build_query(client::permission::permission_schema()) + .build_query(client::domain::by_id("domain".parse().unwrap())) .with_filter(PredicateBox::new( value::QueryOutputPredicate::Identifiable(string::StringPredicate::starts_with("xor_")), )) diff --git a/client/tests/ui_fail/cant_filter_singular_query.stderr b/client/tests/ui_fail/cant_filter_singular_query.stderr index 6533d07befc..6213c6dfb1e 100644 --- a/client/tests/ui_fail/cant_filter_singular_query.stderr +++ b/client/tests/ui_fail/cant_filter_singular_query.stderr @@ -1,24 +1,24 @@ -error[E0599]: the method `with_filter` exists for struct `QueryRequestBuilder<'_, FindPermissionSchema>`, but its trait bounds were not satisfied +error[E0599]: the method `with_filter` exists for struct `QueryRequestBuilder<'_, FindDomainById>`, but its trait bounds were not satisfied --> tests/ui_fail/cant_filter_singular_query.rs:14:10 | 12 | let result = client | __________________- -13 | | .build_query(client::permission::permission_schema()) +13 | | .build_query(client::domain::by_id("domain".parse().unwrap())) 14 | | .with_filter(PredicateBox::new( - | | -^^^^^^^^^^^ method cannot be called on `QueryRequestBuilder<'_, FindPermissionSchema>` due to unsatisfied trait bounds + | | -^^^^^^^^^^^ method cannot be called on `QueryRequestBuilder<'_, FindDomainById>` due to unsatisfied trait bounds | |_________| | | ::: $WORKSPACE/data_model/src/query/mod.rs | | / queries! { - | | /// Finds all registered permission tokens + | | /// [`FindAllDomains`] Iroha Query finds all [`Domain`]s presented in Iroha [`Peer`]. | | #[derive(Copy, Display)] - | | #[ffi_type] + | | #[display(fmt = "Find all domains")] ... | | | } | | } | |_____- doesn't satisfy `_: IterableQuery` | = note: the following trait bounds were not satisfied: - `iroha_client::iroha_data_model::prelude::FindPermissionSchema: IterableQuery` + `iroha_client::iroha_data_model::prelude::FindDomainById: IterableQuery` diff --git a/smart_contract/tests/ui_fail/cant_filter_singular_query.rs b/smart_contract/tests/ui_fail/cant_filter_singular_query.rs index 83d0654e181..40016927f24 100644 --- a/smart_contract/tests/ui_fail/cant_filter_singular_query.rs +++ b/smart_contract/tests/ui_fail/cant_filter_singular_query.rs @@ -4,7 +4,7 @@ use iroha_smart_contract::{ }; fn main() { - FindPermissionSchema + FindDomainById::new("domain".parse().unwrap()) .filter(QueryOutputPredicate::Identifiable( StringPredicate::starts_with("xor_"), )) diff --git a/smart_contract/tests/ui_fail/cant_filter_singular_query.stderr b/smart_contract/tests/ui_fail/cant_filter_singular_query.stderr index e207ea2a095..8bc8b9f6284 100644 --- a/smart_contract/tests/ui_fail/cant_filter_singular_query.stderr +++ b/smart_contract/tests/ui_fail/cant_filter_singular_query.stderr @@ -1,25 +1,25 @@ -error[E0599]: the method `filter` exists for struct `FindPermissionSchema`, but its trait bounds were not satisfied +error[E0599]: the method `filter` exists for struct `FindDomainById`, but its trait bounds were not satisfied --> tests/ui_fail/cant_filter_singular_query.rs:8:10 | -7 | / FindPermissionSchema +7 | / FindDomainById::new("domain".parse().unwrap()) 8 | | .filter(QueryOutputPredicate::Identifiable( - | | -^^^^^^ method cannot be called on `FindPermissionSchema` due to unsatisfied trait bounds + | | -^^^^^^ method cannot be called on `FindDomainById` due to unsatisfied trait bounds | |_________| | | ::: $WORKSPACE/data_model/src/query/mod.rs | | / queries! { - | | /// Finds all registered permission tokens + | | /// [`FindAllDomains`] Iroha Query finds all [`Domain`]s presented in Iroha [`Peer`]. | | #[derive(Copy, Display)] - | | #[ffi_type] + | | #[display(fmt = "Find all domains")] ... | | | } | | } | |_____- doesn't satisfy `_: ExecuteIterableQueryOnHost`, `_: IterableQuery` or `_: Iterator` | = note: the following trait bounds were not satisfied: - `iroha_smart_contract::prelude::FindPermissionSchema: IterableQuery` - which is required by `iroha_smart_contract::prelude::FindPermissionSchema: iroha_smart_contract::ExecuteIterableQueryOnHost` - `iroha_smart_contract::prelude::FindPermissionSchema: Iterator` - which is required by `&mut iroha_smart_contract::prelude::FindPermissionSchema: Iterator` + `iroha_smart_contract::prelude::FindDomainById: IterableQuery` + which is required by `iroha_smart_contract::prelude::FindDomainById: iroha_smart_contract::ExecuteIterableQueryOnHost` + `iroha_smart_contract::prelude::FindDomainById: Iterator` + which is required by `&mut iroha_smart_contract::prelude::FindDomainById: Iterator`