diff --git a/Cargo.lock b/Cargo.lock index 814f2081e57..aab41d8f06a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3040,9 +3040,14 @@ dependencies = [ name = "iroha_executor_derive" version = "2.0.0-pre-rc.20" dependencies = [ + "darling", + "iroha_data_model", + "iroha_macro_utils", + "manyhow", "proc-macro2", "quote", "syn 1.0.109", + "syn 2.0.38", ] [[package]] diff --git a/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs index 740a1124a62..1b3b6443ef8 100644 --- a/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_admin/src/lib.rs @@ -2,175 +2,33 @@ //! If authority is not `admin@admin` then default validation is used as a backup. #![no_std] -#![allow(missing_docs, clippy::missing_errors_doc)] #[cfg(not(test))] extern crate panic_halt; -use iroha_executor::{ - data_model::evaluate::{EvaluationError, ExpressionEvaluator}, - parse, - prelude::*, - smart_contract, -}; +use iroha_executor::{parse, prelude::*, smart_contract}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); +#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[visit(custom(visit_instruction))] struct Executor { verdict: Result, block_height: u64, host: smart_contract::Host, } -impl Executor { - /// Construct [`Self`] - pub fn new(block_height: u64) -> Self { - Self { - verdict: Ok(()), - block_height, - host: smart_contract::Host, - } +fn visit_instruction(executor: &mut Executor, authority: &AccountId, isi: &InstructionExpr) { + if parse!("admin@admin" as AccountId) == *authority { + pass!(executor); } -} - -macro_rules! defaults { - ( $($executor:ident $(<$param:ident $(: $bound:path)?>)?($operation:ty)),+ $(,)? ) => { $( - fn $executor $(<$param $(: $bound)?>)?(&mut self, authority: &AccountId, operation: $operation) { - iroha_executor::default::$executor(self, authority, operation) - } )+ - }; -} - -impl Visit for Executor { - fn visit_instruction(&mut self, authority: &AccountId, isi: &InstructionExpr) { - if parse!("admin@admin" as AccountId) == *authority { - pass!(self); - } - - iroha_executor::default::visit_instruction(self, authority, isi); - } - - defaults! { - visit_unsupported(T), - - visit_transaction(&SignedTransaction), - visit_expression(&EvaluatesTo), - visit_sequence(&SequenceExpr), - visit_if(&ConditionalExpr), - visit_pair(&PairExpr), - - // Peer validation - visit_unregister_peer(Unregister), - - // Domain validation - visit_unregister_domain(Unregister), - visit_transfer_domain(Transfer), - visit_set_domain_key_value(SetKeyValue), - visit_remove_domain_key_value(RemoveKeyValue), - // Account validation - visit_unregister_account(Unregister), - visit_mint_account_public_key(Mint), - visit_burn_account_public_key(Burn), - visit_mint_account_signature_check_condition(Mint), - visit_set_account_key_value(SetKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - - // Asset validation - visit_register_asset(Register), - visit_unregister_asset(Unregister), - visit_mint_asset(Mint), - visit_burn_asset(Burn), - visit_transfer_asset(Transfer), - visit_set_asset_key_value(SetKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - - // AssetDefinition validation - visit_unregister_asset_definition(Unregister), - visit_transfer_asset_definition(Transfer), - visit_set_asset_definition_key_value(SetKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - - // Permission validation - visit_grant_account_permission(Grant), - visit_revoke_account_permission(Revoke), - - // Role validation - visit_register_role(Register), - visit_unregister_role(Unregister), - visit_grant_account_role(Grant), - visit_revoke_account_role(Revoke), - - // Trigger validation - visit_unregister_trigger(Unregister>), - visit_mint_trigger_repetitions(Mint>), - visit_burn_trigger_repetitions(Burn>), - visit_execute_trigger(ExecuteTrigger), - - // Parameter validation - visit_set_parameter(SetParameter), - visit_new_parameter(NewParameter), - - // Upgrade validation - visit_upgrade_executor(Upgrade), - } -} - -impl Validate for Executor { - fn verdict(&self) -> &Result { - &self.verdict - } - - fn block_height(&self) -> u64 { - self.block_height - } - - fn deny(&mut self, reason: ValidationFail) { - self.verdict = Err(reason); - } -} - -impl ExpressionEvaluator for Executor { - fn evaluate(&self, expression: &E) -> Result { - self.host.evaluate(expression) - } + iroha_executor::default::visit_instruction(executor, authority, isi); } #[entrypoint] pub fn migrate(_block_height: u64) -> MigrationResult { Ok(()) } - -#[entrypoint] -pub fn validate_transaction( - authority: AccountId, - transaction: SignedTransaction, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_transaction(&authority, &transaction); - core::mem::forget(transaction); - executor.verdict -} - -#[entrypoint] -pub fn validate_instruction( - authority: AccountId, - instruction: InstructionExpr, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_instruction(&authority, &instruction); - core::mem::forget(instruction); - executor.verdict -} - -#[entrypoint] -pub fn validate_query(authority: AccountId, query: QueryBox, block_height: u64) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_query(&authority, &query); - core::mem::forget(query); - executor.verdict -} diff --git a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs index 0ab279562c1..d757913c836 100644 --- a/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_custom_token/src/lib.rs @@ -11,7 +11,6 @@ //! get access to control all domains. Remember that this is just a test example. #![no_std] -#![allow(missing_docs, clippy::missing_errors_doc)] extern crate alloc; #[cfg(not(test))] @@ -21,11 +20,7 @@ use alloc::{borrow::ToOwned, string::String}; use anyhow::anyhow; use iroha_executor::{ - data_model::evaluate::{EvaluationError, ExpressionEvaluator}, - default::default_permission_token_schema, - permission::Token as _, - prelude::*, - smart_contract, + default::default_permission_token_schema, permission::Token as _, prelude::*, smart_contract, }; use iroha_schema::IntoSchema; use lol_alloc::{FreeListAllocator, LockedAllocator}; @@ -59,6 +54,8 @@ mod token { pub struct CanControlDomainLives; } +#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] +#[visit(custom(visit_register_domain, visit_unregister_domain))] struct Executor { verdict: Result, block_height: u64, @@ -66,15 +63,6 @@ struct Executor { } impl Executor { - /// Construct [`Self`] - pub fn new(block_height: u64) -> Self { - Self { - verdict: Ok(()), - block_height, - host: smart_contract::Host, - } - } - fn get_all_accounts_with_can_unregister_domain_permission( ) -> Result, MigrationError> { let accounts = FindAllAccounts.execute().map_err(|error| { @@ -182,121 +170,33 @@ impl Executor { } } -macro_rules! defaults { - ( $($executor:ident $(<$param:ident $(: $bound:path)?>)?($operation:ty)),+ $(,)? ) => { $( - fn $executor $(<$param $(: $bound)?>)?(&mut self, authority: &AccountId, operation: $operation) { - iroha_executor::default::$executor(self, authority, operation) - } )+ - }; -} - -impl Visit for Executor { - fn visit_register_domain(&mut self, authority: &AccountId, _isi: Register) { - if self.block_height() == 0 { - pass!(self); - } - if token::CanControlDomainLives.is_owned_by(authority) { - pass!(self); - } - - deny!(self, "You don't have permission to register a new domain"); +fn visit_register_domain(executor: &mut Executor, authority: &AccountId, _isi: Register) { + if executor.block_height() == 0 { + pass!(executor) } - - fn visit_unregister_domain(&mut self, authority: &AccountId, _isi: Unregister) { - if self.block_height() == 0 { - pass!(self); - } - if token::CanControlDomainLives.is_owned_by(authority) { - pass!(self); - } - - deny!(self, "You don't have permission to unregister domain"); + if token::CanControlDomainLives.is_owned_by(authority) { + pass!(executor); } - defaults! { - visit_unsupported(T), - - visit_transaction(&SignedTransaction), - visit_instruction(&InstructionExpr), - visit_expression(&EvaluatesTo), - visit_sequence(&SequenceExpr), - visit_if(&ConditionalExpr), - visit_pair(&PairExpr), - - // Peer validation - visit_unregister_peer(Unregister), - - // Domain validation - visit_transfer_domain(Transfer), - visit_set_domain_key_value(SetKeyValue), - visit_remove_domain_key_value(RemoveKeyValue), - - // Account validation - visit_unregister_account(Unregister), - visit_mint_account_public_key(Mint), - visit_burn_account_public_key(Burn), - visit_mint_account_signature_check_condition(Mint), - visit_set_account_key_value(SetKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - - // Asset validation - visit_register_asset(Register), - visit_unregister_asset(Unregister), - visit_mint_asset(Mint), - visit_burn_asset(Burn), - visit_transfer_asset(Transfer), - visit_set_asset_key_value(SetKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - - // AssetDefinition validation - visit_unregister_asset_definition(Unregister), - visit_transfer_asset_definition(Transfer), - visit_set_asset_definition_key_value(SetKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - - // Permission validation - visit_grant_account_permission(Grant), - visit_revoke_account_permission(Revoke), - - // Role validation - visit_register_role(Register), - visit_unregister_role(Unregister), - visit_grant_account_role(Grant), - visit_revoke_account_role(Revoke), - - // Trigger validation - visit_unregister_trigger(Unregister>), - visit_mint_trigger_repetitions(Mint>), - visit_burn_trigger_repetitions(Burn>), - visit_execute_trigger(ExecuteTrigger), - - // Parameter validation - visit_set_parameter(SetParameter), - visit_new_parameter(NewParameter), - - // Upgrade validation - visit_upgrade_executor(Upgrade), - } + deny!( + executor, + "You don't have permission to register a new domain" + ); } -impl Validate for Executor { - fn verdict(&self) -> &Result { - &self.verdict - } - - fn block_height(&self) -> u64 { - self.block_height +fn visit_unregister_domain( + executor: &mut Executor, + authority: &AccountId, + _isi: Unregister, +) { + if executor.block_height() == 0 { + pass!(executor); } - - fn deny(&mut self, reason: ValidationFail) { - self.verdict = Err(reason); + if token::CanControlDomainLives.is_owned_by(authority) { + pass!(executor); } -} -impl ExpressionEvaluator for Executor { - fn evaluate(&self, expression: &E) -> Result { - self.host.evaluate(expression) - } + deny!(executor, "You don't have permission to unregister domain"); } #[entrypoint] @@ -314,35 +214,3 @@ pub fn migrate(_block_height: u64) -> MigrationResult { Executor::replace_token(&accounts) } - -#[entrypoint] -pub fn validate_transaction( - authority: AccountId, - transaction: SignedTransaction, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_transaction(&authority, &transaction); - core::mem::forget(transaction); - executor.verdict -} - -#[entrypoint] -pub fn validate_instruction( - authority: AccountId, - instruction: InstructionExpr, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_instruction(&authority, &instruction); - core::mem::forget(instruction); - executor.verdict -} - -#[entrypoint] -pub fn validate_query(authority: AccountId, query: QueryBox, block_height: u64) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_query(&authority, &query); - core::mem::forget(query); - executor.verdict -} diff --git a/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs b/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs index c7035a42055..437e01b9bdd 100644 --- a/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_with_migration_fail/src/lib.rs @@ -1,7 +1,6 @@ //! Runtime Executor which copies default validation logic but forbids any queries and fails to migrate. #![no_std] -#![allow(missing_docs, clippy::missing_errors_doc)] extern crate alloc; #[cfg(not(test))] @@ -10,139 +9,19 @@ extern crate panic_halt; use alloc::{borrow::ToOwned as _, format}; use anyhow::anyhow; -use iroha_executor::{ - data_model::{ - evaluate::{EvaluationError, ExpressionEvaluator}, - ValidationFail, - }, - parse, - prelude::*, - smart_contract, -}; +use iroha_executor::{parse, prelude::*, smart_contract}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); +#[derive(Constructor, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] struct Executor { verdict: Result, block_height: u64, host: smart_contract::Host, } -impl Executor { - /// Construct [`Self`] - pub fn new(block_height: u64) -> Self { - Self { - verdict: Ok(()), - block_height, - host: smart_contract::Host, - } - } -} - -macro_rules! defaults { - ( $($executor:ident $(<$param:ident $(: $bound:path)?>)?($operation:ty)),+ $(,)? ) => { $( - fn $executor $(<$param $(: $bound)?>)?(&mut self, authority: &AccountId, operation: $operation) { - iroha_executor::default::$executor(self, authority, operation) - } )+ - }; -} - -impl Visit for Executor { - fn visit_query(&mut self, _authority: &AccountId, _query: &QueryBox) { - self.deny(ValidationFail::NotPermitted( - "All queries are forbidden".to_owned(), - )); - } - - defaults! { - visit_unsupported(T), - - visit_transaction(&SignedTransaction), - visit_instruction(&InstructionExpr), - visit_expression(&EvaluatesTo), - visit_sequence(&SequenceExpr), - visit_if(&ConditionalExpr), - visit_pair(&PairExpr), - - // Peer validation - visit_unregister_peer(Unregister), - - // Domain validation - visit_unregister_domain(Unregister), - visit_transfer_domain(Transfer), - visit_set_domain_key_value(SetKeyValue), - visit_remove_domain_key_value(RemoveKeyValue), - - // Account validation - visit_unregister_account(Unregister), - visit_mint_account_public_key(Mint), - visit_burn_account_public_key(Burn), - visit_mint_account_signature_check_condition(Mint), - visit_set_account_key_value(SetKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - - // Asset validation - visit_register_asset(Register), - visit_unregister_asset(Unregister), - visit_mint_asset(Mint), - visit_burn_asset(Burn), - visit_transfer_asset(Transfer), - visit_set_asset_key_value(SetKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - - // AssetDefinition validation - visit_unregister_asset_definition(Unregister), - visit_transfer_asset_definition(Transfer), - visit_set_asset_definition_key_value(SetKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - - // Permission validation - visit_grant_account_permission(Grant), - visit_revoke_account_permission(Revoke), - - // Role validation - visit_register_role(Register), - visit_unregister_role(Unregister), - visit_grant_account_role(Grant), - visit_revoke_account_role(Revoke), - - // Trigger validation - visit_unregister_trigger(Unregister>), - visit_mint_trigger_repetitions(Mint>), - visit_burn_trigger_repetitions(Burn>), - visit_execute_trigger(ExecuteTrigger), - - // Parameter validation - visit_set_parameter(SetParameter), - visit_new_parameter(NewParameter), - - // Upgrade validation - visit_upgrade_executor(Upgrade), - } -} - -impl Validate for Executor { - fn verdict(&self) -> &Result { - &self.verdict - } - - fn block_height(&self) -> u64 { - self.block_height - } - - fn deny(&mut self, reason: ValidationFail) { - self.verdict = Err(reason); - } -} - -impl ExpressionEvaluator for Executor { - fn evaluate(&self, expression: &E) -> Result { - self.host.evaluate(expression) - } -} - #[entrypoint] pub fn migrate(_block_height: u64) -> MigrationResult { // Performing side-effects to check in the test that it won't be applied after failure @@ -160,35 +39,3 @@ pub fn migrate(_block_height: u64) -> MigrationResult { Err("This executor always fails to migrate".to_owned()) } - -#[entrypoint] -pub fn validate_transaction( - authority: AccountId, - transaction: SignedTransaction, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_transaction(&authority, &transaction); - core::mem::forget(transaction); - executor.verdict -} - -#[entrypoint] -pub fn validate_instruction( - authority: AccountId, - instruction: InstructionExpr, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_instruction(&authority, &instruction); - core::mem::forget(instruction); - executor.verdict -} - -#[entrypoint] -pub fn validate_query(authority: AccountId, query: QueryBox, block_height: u64) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_query(&authority, &query); - core::mem::forget(query); - executor.verdict -} diff --git a/default_executor/src/lib.rs b/default_executor/src/lib.rs index cb7d66e639b..4834bcc9228 100644 --- a/default_executor/src/lib.rs +++ b/default_executor/src/lib.rs @@ -1,7 +1,6 @@ //! Iroha default executor. #![no_std] -#![allow(missing_docs, clippy::missing_errors_doc)] extern crate alloc; #[cfg(not(test))] @@ -9,10 +8,7 @@ extern crate panic_halt; use alloc::borrow::ToOwned as _; -use iroha_executor::{ - data_model::evaluate::ExpressionEvaluator, default::default_permission_token_schema, - prelude::*, smart_contract, -}; +use iroha_executor::{default::default_permission_token_schema, prelude::*, smart_contract}; use lol_alloc::{FreeListAllocator, LockedAllocator}; #[global_allocator] @@ -23,7 +19,7 @@ static ALLOC: LockedAllocator = LockedAllocator::new(FreeList /// # Warning /// /// The defaults are not guaranteed to be stable. -#[derive(Debug, Clone)] +#[derive(Clone, Constructor, Debug, ValidateEntrypoints, ExpressionEvaluator, Validate, Visit)] pub struct Executor { verdict: Result, block_height: u64, @@ -31,15 +27,6 @@ pub struct Executor { } impl Executor { - /// Construct [`Self`] - pub fn new(block_height: u64) -> Self { - Self { - verdict: Ok(()), - block_height, - host: smart_contract::Host, - } - } - fn ensure_genesis(block_height: u64) -> MigrationResult { if block_height != 0 { return Err("Default Executor is intended to be used only in genesis. \ @@ -51,104 +38,6 @@ impl Executor { } } -macro_rules! defaults { - ( $($executor:ident $(<$param:ident $(: $bound:path)?>)?($operation:ty)),+ $(,)? ) => { $( - fn $executor $(<$param $(: $bound)?>)?(&mut self, authority: &AccountId, operation: $operation) { - iroha_executor::default::$executor(self, authority, operation) - } )+ - }; -} - -impl Visit for Executor { - defaults! { - visit_unsupported(T), - - visit_transaction(&SignedTransaction), - visit_instruction(&InstructionExpr), - visit_expression(&EvaluatesTo), - visit_sequence(&SequenceExpr), - visit_if(&ConditionalExpr), - visit_pair(&PairExpr), - - // Peer validation - visit_unregister_peer(Unregister), - - // Domain validation - visit_unregister_domain(Unregister), - visit_set_domain_key_value(SetKeyValue), - visit_remove_domain_key_value(RemoveKeyValue), - - // Account validation - visit_unregister_account(Unregister), - visit_mint_account_public_key(Mint), - visit_burn_account_public_key(Burn), - visit_mint_account_signature_check_condition(Mint), - visit_set_account_key_value(SetKeyValue), - visit_remove_account_key_value(RemoveKeyValue), - - // Asset validation - visit_register_asset(Register), - visit_unregister_asset(Unregister), - visit_mint_asset(Mint), - visit_burn_asset(Burn), - visit_transfer_asset(Transfer), - visit_set_asset_key_value(SetKeyValue), - visit_remove_asset_key_value(RemoveKeyValue), - - // AssetDefinition validation - visit_unregister_asset_definition(Unregister), - visit_transfer_asset_definition(Transfer), - visit_set_asset_definition_key_value(SetKeyValue), - visit_remove_asset_definition_key_value(RemoveKeyValue), - - // Permission validation - visit_grant_account_permission(Grant), - visit_revoke_account_permission(Revoke), - - // Role validation - visit_register_role(Register), - visit_unregister_role(Unregister), - visit_grant_account_role(Grant), - visit_revoke_account_role(Revoke), - - // Trigger validation - visit_unregister_trigger(Unregister>), - visit_mint_trigger_repetitions(Mint>), - visit_burn_trigger_repetitions(Burn>), - visit_execute_trigger(ExecuteTrigger), - - // Parameter validation - visit_set_parameter(SetParameter), - visit_new_parameter(NewParameter), - - // Upgrade validation - visit_upgrade_executor(Upgrade), - } -} - -impl Validate for Executor { - fn verdict(&self) -> &Result { - &self.verdict - } - - fn block_height(&self) -> u64 { - self.block_height - } - - fn deny(&mut self, reason: ValidationFail) { - self.verdict = Err(reason); - } -} - -impl ExpressionEvaluator for Executor { - fn evaluate( - &self, - expression: &E, - ) -> core::result::Result { - self.host.evaluate(expression) - } -} - /// Migrate previous executor to the current version. /// Called by Iroha once just before upgrading executor. /// @@ -170,35 +59,3 @@ pub fn migrate(block_height: u64) -> MigrationResult { Ok(()) } - -#[entrypoint] -pub fn validate_transaction( - authority: AccountId, - transaction: SignedTransaction, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_transaction(&authority, &transaction); - core::mem::forget(transaction); - executor.verdict -} - -#[entrypoint] -pub fn validate_instruction( - authority: AccountId, - instruction: InstructionExpr, - block_height: u64, -) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_instruction(&authority, &instruction); - core::mem::forget(instruction); - executor.verdict -} - -#[entrypoint] -pub fn validate_query(authority: AccountId, query: QueryBox, block_height: u64) -> Result { - let mut executor = Executor::new(block_height); - executor.visit_query(&authority, &query); - core::mem::forget(query); - executor.verdict -} diff --git a/smart_contract/executor/derive/Cargo.toml b/smart_contract/executor/derive/Cargo.toml index 5c9503883c9..ce43975f33a 100644 --- a/smart_contract/executor/derive/Cargo.toml +++ b/smart_contract/executor/derive/Cargo.toml @@ -15,6 +15,11 @@ workspace = true proc-macro = true [dependencies] -syn.workspace = true +iroha_data_model.workspace = true +iroha_macro_utils.workspace = true +syn = { workspace = true, features = ["full", "derive"] } +syn2 = { workspace = true, features = ["full", "derive"] } quote.workspace = true proc-macro2.workspace = true +manyhow.workspace = true +darling.workspace = true diff --git a/smart_contract/executor/derive/src/default.rs b/smart_contract/executor/derive/src/default.rs new file mode 100644 index 00000000000..2e458f86992 --- /dev/null +++ b/smart_contract/executor/derive/src/default.rs @@ -0,0 +1,367 @@ +use darling::{ast::NestedMeta, FromDeriveInput, FromMeta}; +use iroha_macro_utils::Emitter; +use manyhow::emit; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::{quote, ToTokens}; +use syn2::{parse_quote, Ident}; + +type ExecutorData = darling::ast::Data; + +#[derive(Debug)] +struct Custom(Vec); + +impl FromMeta for Custom { + fn from_list(items: &[NestedMeta]) -> darling::Result { + let mut res = Vec::new(); + for item in items { + if let NestedMeta::Meta(syn2::Meta::Path(p)) = item { + let fn_name = p.get_ident().expect("Path should be ident"); + res.push(fn_name.clone()); + } else { + return Err(darling::Error::custom( + "Invalid path list supplied to `omit` attribute", + )); + } + } + Ok(Self(res)) + } +} + +#[derive(FromDeriveInput, Debug)] +#[darling(supports(struct_named), attributes(visit, entrypoints))] +struct ExecutorDeriveInput { + ident: Ident, + data: ExecutorData, + custom: Option, +} + +pub fn impl_derive_entrypoints(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { + let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { + return quote!(); + }; + let ExecutorDeriveInput { + ident, + data, + custom, + .. + } = &input; + check_required_fields(data, emitter); + + let (custom_idents, custom_args) = custom_field_idents_and_fn_args(data); + + let mut entrypoint_fns: Vec = vec![ + parse_quote! { + #[::iroha_executor::prelude::entrypoint] + pub fn validate_instruction( + authority: ::iroha_executor::prelude::AccountId, + instruction: ::iroha_executor::prelude::InstructionExpr, + block_height: u64, + #(#custom_args),* + ) -> ::iroha_executor::prelude::Result { + let mut executor = #ident::new(block_height, #(#custom_idents),*); + executor.visit_instruction(&authority, &instruction); + ::core::mem::forget(instruction); + executor.verdict + } + }, + parse_quote! { + #[::iroha_executor::prelude::entrypoint] + pub fn validate_transaction( + authority: ::iroha_executor::prelude::AccountId, + transaction: ::iroha_executor::prelude::SignedTransaction, + block_height: u64, + #(#custom_args),* + ) -> ::iroha_executor::prelude::Result { + let mut executor = #ident::new(block_height, #(#custom_idents),*); + executor.visit_transaction(&authority, &transaction); + ::core::mem::forget(transaction); + executor.verdict + } + }, + parse_quote! { + #[::iroha_executor::prelude::entrypoint] + pub fn validate_query( + authority: ::iroha_executor::prelude::AccountId, + query: ::iroha_executor::prelude::QueryBox, + block_height: u64, + #(#custom_args),* + ) -> ::iroha_executor::prelude::Result { + let mut executor = #ident::new(block_height, #(#custom_idents),*); + executor.visit_query(&authority, &query); + ::core::mem::forget(query); + executor.verdict + } + }, + ]; + if let Some(custom) = custom { + entrypoint_fns.retain(|entrypoint| { + !custom + .0 + .iter() + .any(|fn_name| fn_name == &entrypoint.sig.ident) + }); + } + + quote! { + #(#entrypoint_fns)* + } +} + +pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { + let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { + return quote!(); + }; + let ExecutorDeriveInput { ident, custom, .. } = &input; + let default_visit_sigs: Vec = [ + "fn visit_unsupported(operation: T)", + "fn visit_transaction(operation: &SignedTransaction)", + "fn visit_instruction(operation: &InstructionExpr)", + "fn visit_expression(operation: &EvaluatesTo)", + "fn visit_sequence(operation: &SequenceExpr)", + "fn visit_if(operation: &ConditionalExpr)", + "fn visit_pair(operation: &PairExpr)", + "fn visit_unregister_peer(operation: Unregister)", + "fn visit_unregister_domain(operation: Unregister)", + "fn visit_transfer_domain(operation: Transfer)", + "fn visit_set_domain_key_value(operation: SetKeyValue)", + "fn visit_remove_domain_key_value(operation: RemoveKeyValue)", + "fn visit_unregister_account(operation: Unregister)", + "fn visit_mint_account_public_key(operation: Mint)", + "fn visit_burn_account_public_key(operation: Burn)", + "fn visit_mint_account_signature_check_condition(operation: Mint)", + "fn visit_set_account_key_value(operation: SetKeyValue)", + "fn visit_remove_account_key_value(operation: RemoveKeyValue)", + "fn visit_register_asset(operation: Register)", + "fn visit_unregister_asset(operation: Unregister)", + "fn visit_mint_asset(operation: Mint)", + "fn visit_burn_asset(operation: Burn)", + "fn visit_transfer_asset(operation: Transfer)", + "fn visit_set_asset_key_value(operation: SetKeyValue)", + "fn visit_remove_asset_key_value(operation: RemoveKeyValue)", + "fn visit_unregister_asset_definition(operation: Unregister)", + "fn visit_transfer_asset_definition(operation: Transfer)", + "fn visit_set_asset_definition_key_value(operation: SetKeyValue)", + "fn visit_remove_asset_definition_key_value(operation: RemoveKeyValue)", + "fn visit_grant_account_permission(operation: Grant)", + "fn visit_revoke_account_permission(operation: Revoke)", + "fn visit_register_role(operation: Register)", + "fn visit_unregister_role(operation: Unregister)", + "fn visit_grant_account_role(operation: Grant)", + "fn visit_revoke_account_role(operation: Revoke)", + "fn visit_unregister_trigger(operation: Unregister>)", + "fn visit_mint_trigger_repetitions(operation: Mint>)", + "fn visit_burn_trigger_repetitions(operation: Burn>)", + "fn visit_execute_trigger(operation: ExecuteTrigger)", + "fn visit_set_parameter(operation: SetParameter)", + "fn visit_new_parameter(operation: NewParameter)", + "fn visit_upgrade_executor(operation: Upgrade)", + ] + .into_iter() + .map(|item| { + let mut sig: syn2::Signature = + syn2::parse_str(item).expect("Function names and operation signatures should be valid"); + let recv_arg: syn2::Receiver = parse_quote!(&mut self); + let auth_arg: syn2::FnArg = parse_quote!(authority: &AccountId); + sig.inputs.insert(0, recv_arg.into()); + sig.inputs.insert(1, auth_arg); + sig + }) + .collect(); + + let visit_items = default_visit_sigs + .iter() + .map(|visit_sig| { + let curr_fn_name = &visit_sig.ident; + let local_override_fn = quote! { + #visit_sig { + #curr_fn_name(self, authority, operation) + } + }; + let default_override_fn = quote! { + #visit_sig { + ::iroha_executor::default::#curr_fn_name(self, authority, operation) + } + }; + if let Some(fns_to_exclude) = custom { + if fns_to_exclude + .0 + .iter() + .any(|fn_name| fn_name == &visit_sig.ident) + { + local_override_fn + } else { + default_override_fn + } + } else { + default_override_fn + } + }) + .collect::>(); + + println!("{}", quote!(#(#visit_items)*)); + quote! { + impl ::iroha_executor::prelude::Visit for #ident { + #(#visit_items)* + } + } +} + +pub fn impl_derive_validate(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { + let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { + return quote!(); + }; + let ExecutorDeriveInput { ident, data, .. } = &input; + check_required_fields(data, emitter); + quote! { + impl ::iroha_executor::Validate for #ident { + fn verdict(&self) -> &::iroha_executor::prelude::Result { + &self.verdict + } + + fn block_height(&self) -> u64 { + self.block_height + } + + fn deny(&mut self, reason: ::iroha_executor::prelude::ValidationFail) { + self.verdict = Err(reason); + } + } + } +} + +pub fn impl_derive_expression_evaluator( + emitter: &mut Emitter, + input: &syn2::DeriveInput, +) -> TokenStream2 { + let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { + return quote!(); + }; + let ExecutorDeriveInput { ident, data, .. } = &input; + check_required_fields(data, emitter); + quote! { + impl ::iroha_executor::data_model::evaluate::ExpressionEvaluator for #ident { + fn evaluate( + &self, + expression: &E, + ) -> ::core::result::Result + { + self.host.evaluate(expression) + } + } + + } +} + +pub fn impl_derive_constructor(emitter: &mut Emitter, input: &syn2::DeriveInput) -> TokenStream2 { + let Some(input) = emitter.handle(ExecutorDeriveInput::from_derive_input(input)) else { + return quote!(); + }; + let ExecutorDeriveInput { ident, data, .. } = &input; + + check_required_fields(data, emitter); + + let (custom_idents, custom_args) = custom_field_idents_and_fn_args(data); + + // Returning an inherent impl is okay here as there can be multiple + quote! { + impl #ident { + pub fn new(block_height: u64, #(#custom_args),*) -> Self { + Self { + verdict: Ok(()), + block_height, + host: ::iroha_executor::smart_contract::Host, + #(#custom_idents),* + } + } + } + + } +} + +fn check_required_fields(ast: &ExecutorData, emitter: &mut Emitter) { + let required_fields: syn2::FieldsNamed = parse_quote!({ verdict: ::iroha_executor::prelude::Result, block_height: u64, host: ::iroha_executor::smart_contract::Host }); + let struct_fields = ast + .as_ref() + .take_struct() + .expect("BUG: ExecutorDeriveInput is allowed to contain struct data only") + .fields; + required_fields.named.iter().for_each(|required_field| { + if !struct_fields.iter().any(|struct_field| { + struct_field.ident == required_field.ident + && check_type_equivalence(&required_field.ty, &struct_field.ty) + }) { + emit!( + emitter, + Span::call_site(), + "The struct didn't have the required field named `{}` of type `{}`", + required_field + .ident + .as_ref() + .expect("Required field should be named"), + required_field.ty.to_token_stream() + ) + } + }); +} + +/// Check that the required fields of an `Executor` are of the correct types. As +/// the types can be completely or partially unqualified, we need to go through the type path segments to +/// determine equivalence. We can't account for any aliases though +fn check_type_equivalence(full_ty: &syn2::Type, given_ty: &syn2::Type) -> bool { + match (full_ty, given_ty) { + (syn2::Type::Path(full_ty_path), syn2::Type::Path(given_ty_path)) => { + if full_ty_path.path.segments.len() == given_ty_path.path.segments.len() { + full_ty_path == given_ty_path + } else { + full_ty_path + .path + .segments + .iter() + .rev() + .zip(given_ty_path.path.segments.iter().rev()) + .all(|(full_seg, given_seg)| full_seg == given_seg) + } + } + _ => false, + } +} + +/// Processes an `Executor` by draining it of default fields and returning the idents of the +/// custom fields and the corresponding function arguments for use in the constructor +fn custom_field_idents_and_fn_args(ast: &ExecutorData) -> (Vec<&Ident>, Vec) { + let required_idents: Vec = ["verdict", "block_height", "host"] + .iter() + .map(|s| Ident::new(s, Span::call_site())) + .collect(); + let mut custom_fields = ast + .as_ref() + .take_struct() + .expect("BUG: ExecutorDeriveInput is allowed to contain struct data only") + .fields; + custom_fields.retain(|field| { + let curr_ident = field + .ident + .as_ref() + .expect("BUG: Struct should have named fields"); + !required_idents.iter().any(|ident| ident == curr_ident) + }); + let custom_idents = custom_fields + .iter() + .map(|field| { + field + .ident + .as_ref() + .expect("BUG: Struct should have named fields") + }) + .collect::>(); + let custom_args = custom_fields + .iter() + .map(|field| { + let ident = &field.ident; + let ty = &field.ty; + let field_arg: syn2::FnArg = parse_quote!(#ident: #ty); + field_arg + }) + .collect::>(); + (custom_idents, custom_args) +} diff --git a/smart_contract/executor/derive/src/lib.rs b/smart_contract/executor/derive/src/lib.rs index e2fb3bef25e..d822a1887e1 100644 --- a/smart_contract/executor/derive/src/lib.rs +++ b/smart_contract/executor/derive/src/lib.rs @@ -1,10 +1,14 @@ //! Crate with executor-related derive macros. +use iroha_macro_utils::Emitter; +use manyhow::manyhow; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{parse_macro_input, parse_quote, DeriveInput}; mod conversion; +mod default; mod entrypoint; mod token; mod validate; @@ -144,8 +148,8 @@ pub fn derive_token(input: TokenStream) -> TokenStream { ValidateGrantRevoke, attributes(validate, validate_grant, validate_revoke) )] -pub fn derive_validate(input: TokenStream) -> TokenStream { - validate::impl_derive_validate(input) +pub fn derive_validate_grant_revoke(input: TokenStream) -> TokenStream { + validate::impl_derive_validate_grant_revoke(input) } /// Should be used together with [`ValidateGrantRevoke`] derive macro to derive a conversion @@ -195,3 +199,155 @@ pub fn derive_ref_into_account_owner(input: TokenStream) -> TokenStream { pub fn derive_ref_into_domain_owner(input: TokenStream) -> TokenStream { conversion::impl_derive_ref_into_domain_owner(input) } + +/// Implements the `iroha_executor::Validate` trait for the given `Executor` struct. As +/// this trait has a `iroha_executor::prelude::Visit`, and the latter has an +/// `iroha_executor::iroha_data_model::evaluate::ExpressionEvaluator` +/// bound, at least these two should be implemented as well. +/// +/// Emits a compile error if the struct didn't have all the expected fields with corresponding +/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and +/// `host`: `iroha_executor::smart_contract::Host`, though technically only `verdict` and +/// `block_height` are needed. The types can be unqualified, but not aliased. +#[manyhow] +#[proc_macro_derive(Validate)] +pub fn derive_validate(input: TokenStream2) -> TokenStream2 { + let mut emitter = Emitter::new(); + + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream(); + }; + + let result = default::impl_derive_validate(&mut emitter, &input); + + emitter.finish_token_stream_with(result) +} + +/// Implements the `iroha_executor::prelude::Visit` trait on a given `Executor` struct. +/// Users can supply custom overrides for any of the visit functions as freestanding functions +/// in the same module via the `#[visit(custom(...))]` attribute by +/// supplying corresponding visit function names inside of it, otherwise a default +/// implementation from `iroha_executor::default` module is used. +/// +/// Emits a compile error if the struct didn't have all the expected fields with corresponding +/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and +/// `host`: `iroha_executor::smart_contract::Host`, though technically only `verdict` +/// is needed. The types can be unqualified, but not aliased. +/// +/// # Example +/// +/// ```ignore +/// use iroha_executor::{smart_contract, prelude::*}; +/// +/// #[derive(Constructor, Entrypoints, ExpressionEvaluator, Validate, Visit)] +/// #[visit(custom(visit_query)] +/// pub struct Executor { +/// verdict: Result, +/// block_height: u64, +/// host: smart_contract::Host, +/// } +/// +/// // Custom visit function should supply a `&mut Executor` as first argument +/// fn visit_query(executor: &mut Executor, _authority: &AccountId, _query: &QueryBox) { +/// executor.deny(ValidationFail::NotPermitted( +/// "All queries are forbidden".to_owned(), +/// )); +/// } +/// ``` +#[manyhow] +#[proc_macro_derive(Visit, attributes(visit))] +pub fn derive_visit(input: TokenStream2) -> TokenStream2 { + let mut emitter = Emitter::new(); + + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream(); + }; + + let result = default::impl_derive_visit(&mut emitter, &input); + + emitter.finish_token_stream_with(result) +} + +/// Implements three default entrypoints on a given `Executor` struct: `validate_transaction`, +/// `validate_query` and `validate_instruction`. The `migrate` entrypoint is implied to be +/// implemented manually by the user at all times. +/// +/// Users can supply custom overrides for any of the entrypoint functions as freestanding functions +/// in the same module via the `#[entrypoints(custom(...))]` attribute by +/// supplying corresponding entrypoint function names inside of it. +/// +/// Emits a compile error if the struct didn't have all the expected fields with corresponding +/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and +/// `host`: `iroha_executor::smart_contract::Host`, though technically only `verdict` +/// is needed. The types can be unqualified, but not aliased. +/// +/// # Example +/// +/// ```ignore +/// use iroha_executor::{smart_contract, prelude::*}; +/// +/// #[derive(Constructor, Entrypoints, ExpressionEvaluator, Validate, Visit)] +/// #[entrypoints(custom(validate_query))] +/// pub struct Executor { +/// verdict: Result, +/// block_height: u64, +/// host: smart_contract::Host, +/// } +/// +/// ``` +#[manyhow] +#[proc_macro_derive(ValidateEntrypoints, attributes(entrypoints))] +pub fn derive_entrypoints(input: TokenStream2) -> TokenStream2 { + let mut emitter = Emitter::new(); + + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream(); + }; + + let result = default::impl_derive_entrypoints(&mut emitter, &input); + + emitter.finish_token_stream_with(result) +} + +/// Implements `iroha_executor::iroha_data_model::evaluate::ExpressionEvaluator` trait +/// for the given `Executor` struct. +/// +/// Emits a compile error if the struct didn't have all the expected fields with corresponding +/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and +/// `host`: `iroha_executor::smart_contract::Host`, though technically only `host` is needed. +/// The types can be unqualified, but not aliased. +#[manyhow] +#[proc_macro_derive(ExpressionEvaluator)] +pub fn derive_expression_evaluator(input: TokenStream2) -> TokenStream2 { + let mut emitter = Emitter::new(); + + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream(); + }; + + let result = default::impl_derive_expression_evaluator(&mut emitter, &input); + + emitter.finish_token_stream_with(result) +} + +/// Implements a constructor for the given `Executor` struct. If the `Executor` has any custom fields +/// (i.e. different from the expected fields listed below), they will be included into the constructor +/// automatically and will need to be passed into `new()` function explicitly. In the default case, +/// only the `block_height` needs to be supplied manually. +/// +/// Emits a compile error if the struct didn't have all the expected fields with corresponding +/// types, i.e. `verdict`: `iroha_executor::prelude::Result`, `block_height`: `u64` and +/// `host`: `iroha_executor::smart_contract::Host`. The types can be unqualified, but not aliased. +#[manyhow] +#[proc_macro_derive(Constructor)] +pub fn derive_constructor(input: TokenStream2) -> TokenStream2 { + let mut emitter = Emitter::new(); + + let Some(input) = emitter.handle(syn2::parse2(input)) else { + return emitter.finish_token_stream(); + }; + + let result = default::impl_derive_constructor(&mut emitter, &input); + + emitter.finish_token_stream_with(result) +} diff --git a/smart_contract/executor/derive/src/validate.rs b/smart_contract/executor/derive/src/validate.rs index 449a9dc943b..de6cc982cf1 100644 --- a/smart_contract/executor/derive/src/validate.rs +++ b/smart_contract/executor/derive/src/validate.rs @@ -6,7 +6,7 @@ use syn::{Attribute, Ident, Path, Type}; use super::*; /// [`derive_validate`](crate::derive_validate()) macro implementation -pub fn impl_derive_validate(input: TokenStream) -> TokenStream { +pub fn impl_derive_validate_grant_revoke(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveInput); let ident = input.ident; diff --git a/smart_contract/executor/src/lib.rs b/smart_contract/executor/src/lib.rs index baa00432883..a6644def1fd 100644 --- a/smart_contract/executor/src/lib.rs +++ b/smart_contract/executor/src/lib.rs @@ -252,7 +252,10 @@ pub mod prelude { visit::Visit, ValidationFail, }; - pub use iroha_executor_derive::{entrypoint, Token, ValidateGrantRevoke}; + pub use iroha_executor_derive::{ + entrypoint, Constructor, ExpressionEvaluator, Token, Validate, ValidateEntrypoints, + ValidateGrantRevoke, Visit, + }; pub use iroha_smart_contract::{prelude::*, Context}; pub use super::{deny, pass, PermissionTokenSchema, Validate};