diff --git a/client/tests/integration/mod.rs b/client/tests/integration/mod.rs index 13b8bd2528c..c76d8dbbee5 100644 --- a/client/tests/integration/mod.rs +++ b/client/tests/integration/mod.rs @@ -3,6 +3,7 @@ mod asset_propagation; mod domain_owner_permissions; mod events; mod extra_functional; +mod multisig; mod non_mintable; mod pagination; mod permissions; diff --git a/client/tests/integration/multisig.rs b/client/tests/integration/multisig.rs new file mode 100644 index 00000000000..ffd131e1b0b --- /dev/null +++ b/client/tests/integration/multisig.rs @@ -0,0 +1,150 @@ +use std::{collections::BTreeMap, str::FromStr}; + +use executor_custom_data_model::multisig::{MultisigArgs, MultisigRegisterArgs}; +use eyre::Result; +use iroha::{ + client::{self, ClientQueryError}, + crypto::KeyPair, + data_model::{ + prelude::*, + transaction::{TransactionBuilder, WasmSmartContract}, + }, +}; +use iroha_data_model::parameter::SmartContractParameter; +use nonzero_ext::nonzero; +use test_network::*; +use test_samples::{gen_account_in, ALICE_ID}; + +#[test] +fn mutlisig() -> Result<()> { + let (_rt, _peer, test_client) = ::new().with_port(11_400).start_with_runtime(); + wait_for_genesis_committed(&vec![test_client.clone()], 0); + + test_client.submit_all_blocking([ + SetParameter::new(Parameter::SmartContract(SmartContractParameter::Fuel( + nonzero!(100_000_000_u64), + ))), + SetParameter::new(Parameter::Executor(SmartContractParameter::Fuel(nonzero!( + 100_000_000_u64 + )))), + ])?; + + let account_id = ALICE_ID.clone(); + let multisig_register_trigger_id = TriggerId::from_str("multisig_register")?; + + let wasm = + iroha_wasm_builder::Builder::new("tests/integration/smartcontracts/multisig_register") + .show_output() + .build()? + .optimize()? + .into_bytes()?; + let wasm = WasmSmartContract::from_compiled(wasm); + + let trigger = Trigger::new( + multisig_register_trigger_id.clone(), + Action::new( + wasm, + Repeats::Indefinitely, + account_id.clone(), + ExecuteTriggerEventFilter::new().for_trigger(multisig_register_trigger_id.clone()), + ), + ); + + // Register trigger which would allow multisig account creation in wonderland domain + // Access to call this trigger shouldn't be restricted + test_client.submit_blocking(Register::trigger(trigger))?; + + // Create multisig account id and destroy it's private key + let multisig_account_id = gen_account_in("wonderland").0; + + let multisig_trigger_id: TriggerId = format!( + "{}_{}_multisig_trigger", + multisig_account_id.signatory(), + multisig_account_id.domain() + ) + .parse()?; + + let signatories = core::iter::repeat_with(|| gen_account_in("wonderland")) + .take(5) + .collect::>(); + + let args = MultisigRegisterArgs { + account: Account::new(multisig_account_id.clone()), + signatories: signatories.keys().cloned().collect(), + }; + + test_client.submit_all_blocking( + signatories + .keys() + .cloned() + .map(Account::new) + .map(Register::account), + )?; + + let call_trigger = ExecuteTrigger::new(multisig_register_trigger_id).with_args(&args); + test_client.submit_blocking(call_trigger)?; + + // Check that multisig account exist + let account = test_client + .request(client::account::by_id(multisig_account_id.clone())) + .expect("multisig account should be created after the call to register multisig trigger"); + + assert_eq!(account.id(), &multisig_account_id); + + // Check that multisig trigger exist + let trigger = test_client + .request(client::trigger::by_id(multisig_trigger_id.clone())) + .expect("multisig trigger should be created after the call to register multisig trigger"); + + assert_eq!(trigger.id(), &multisig_trigger_id); + + let domain_id: DomainId = "domain_controlled_by_multisig".parse().unwrap(); + let isi = vec![InstructionBox::from(Register::domain(Domain::new( + domain_id.clone(), + )))]; + let isi_hash = HashOf::new(&isi); + + let mut signatories_iter = signatories.into_iter(); + + if let Some((signatory, key_pair)) = signatories_iter.next() { + let args = MultisigArgs::Instructions(isi); + let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args); + test_client.submit_transaction_blocking( + &TransactionBuilder::new(test_client.chain.clone(), signatory) + .with_instructions([call_trigger]) + .sign(key_pair.private_key()), + )?; + } + + // Check that domain isn't created yet + let err = test_client + .request(client::domain::by_id(domain_id.clone())) + .expect_err("domain shouldn't be created before enough votes are collected"); + assert!(matches!( + err, + ClientQueryError::Validation(iroha_data_model::ValidationFail::QueryFailed( + iroha_data_model::query::error::QueryExecutionFail::Find( + iroha_data_model::query::error::FindError::Domain(err_domain_id) + ) + )) if domain_id == err_domain_id + )); + + for (signatory, key_pair) in signatories_iter { + let args = MultisigArgs::Vote(isi_hash); + let call_trigger = ExecuteTrigger::new(multisig_trigger_id.clone()).with_args(&args); + test_client.submit_transaction_blocking( + &TransactionBuilder::new(test_client.chain.clone(), signatory) + .with_instructions([call_trigger]) + .sign(key_pair.private_key()), + )?; + } + + // Check that new domain was created and multisig account is owner + let domain = test_client + .request(client::domain::by_id(domain_id.clone())) + .expect("domain should be created after enough votes are collected"); + + assert_eq!(domain.owned_by(), &multisig_account_id); + + Ok(()) +} diff --git a/client/tests/integration/smartcontracts/Cargo.toml b/client/tests/integration/smartcontracts/Cargo.toml index 0f3b30ee49c..3e058bf4d77 100644 --- a/client/tests/integration/smartcontracts/Cargo.toml +++ b/client/tests/integration/smartcontracts/Cargo.toml @@ -11,6 +11,7 @@ resolver = "2" members = [ "create_nft_for_every_user_trigger", "mint_rose_trigger", + "mint_rose_trigger_args", "executor_with_admin", "executor_with_custom_permission", "executor_with_custom_parameter", @@ -21,6 +22,8 @@ members = [ "executor_custom_data_model", "query_assets_and_save_cursor", "smart_contract_can_filter_queries", + "multisig_register", + "multisig", ] [profile.dev] diff --git a/client/tests/integration/smartcontracts/executor_custom_data_model/src/lib.rs b/client/tests/integration/smartcontracts/executor_custom_data_model/src/lib.rs index 4db9f68bde5..62cf2d4c6a2 100644 --- a/client/tests/integration/smartcontracts/executor_custom_data_model/src/lib.rs +++ b/client/tests/integration/smartcontracts/executor_custom_data_model/src/lib.rs @@ -5,6 +5,8 @@ extern crate alloc; pub mod complex_isi; +pub mod mint_rose_args; +pub mod multisig; pub mod parameters; pub mod permissions; pub mod simple_isi; diff --git a/client/tests/integration/smartcontracts/executor_custom_data_model/src/mint_rose_args.rs b/client/tests/integration/smartcontracts/executor_custom_data_model/src/mint_rose_args.rs new file mode 100644 index 00000000000..a07fb859706 --- /dev/null +++ b/client/tests/integration/smartcontracts/executor_custom_data_model/src/mint_rose_args.rs @@ -0,0 +1,10 @@ +//! Arguments to mint rose with args trigger + +use serde::{Deserialize, Serialize}; + +/// Arguments to mint rose with args trigger +#[derive(Serialize, Deserialize)] +pub struct MintRoseArgs { + // Amount to mint + pub val: u32, +} diff --git a/client/tests/integration/smartcontracts/executor_custom_data_model/src/multisig.rs b/client/tests/integration/smartcontracts/executor_custom_data_model/src/multisig.rs new file mode 100644 index 00000000000..916fa06eaf6 --- /dev/null +++ b/client/tests/integration/smartcontracts/executor_custom_data_model/src/multisig.rs @@ -0,0 +1,24 @@ +//! Arguments to register and manage multisig account + +use alloc::{collections::btree_set::BTreeSet, vec::Vec}; + +use iroha_data_model::{account::NewAccount, prelude::*}; +use serde::{Deserialize, Serialize}; + +/// Arguments to multisig account register trigger +#[derive(Serialize, Deserialize)] +pub struct MultisigRegisterArgs { + // Account id of multisig account should be manually checked to not have corresponding private key (or having master key is ok) + pub account: NewAccount, + // List of accounts responsible for handling multisig account + pub signatories: BTreeSet, +} + +/// Arguments to multisig account manager trigger +#[derive(Serialize, Deserialize)] +pub enum MultisigArgs { + /// Accept instructions proposal and initialize votes with the proposer's one + Instructions(Vec), + /// Accept vote for certain instructions + Vote(HashOf>), +} diff --git a/client/tests/integration/smartcontracts/mint_rose_trigger_args/Cargo.toml b/client/tests/integration/smartcontracts/mint_rose_trigger_args/Cargo.toml new file mode 100644 index 00000000000..19715ff3417 --- /dev/null +++ b/client/tests/integration/smartcontracts/mint_rose_trigger_args/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "mint_rose_args" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_trigger.workspace = true +executor_custom_data_model.workspace = true + +panic-halt.workspace = true +lol_alloc.workspace = true +getrandom.workspace = true + +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, default-features = false } diff --git a/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs b/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs new file mode 100644 index 00000000000..eda3d291902 --- /dev/null +++ b/client/tests/integration/smartcontracts/mint_rose_trigger_args/src/lib.rs @@ -0,0 +1,41 @@ +//! trigger which mints rose for its owner based on input args. + +#![no_std] + +#[cfg(not(test))] +extern crate panic_halt; + +use core::str::FromStr as _; + +use executor_custom_data_model::mint_rose_args::MintRoseArgs; +use iroha_trigger::{debug::dbg_panic, prelude::*}; +use lol_alloc::{FreeListAllocator, LockedAllocator}; +use serde::{Deserialize, Serialize}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +getrandom::register_custom_getrandom!(iroha_trigger::stub_getrandom); + +/// Mint 1 rose for owner +#[iroha_trigger::main] +fn main(_id: TriggerId, owner: AccountId, event: EventBox) { + let rose_definition_id = AssetDefinitionId::from_str("rose#wonderland") + .dbg_expect("Failed to parse `rose#wonderland` asset definition id"); + let rose_id = AssetId::new(rose_definition_id, owner); + + let args: MintRoseArgs = match event { + EventBox::ExecuteTrigger(event) => event + .args() + .dbg_expect("Trigger expect parameters") + .try_into_any() + .dbg_expect("Failed to parse args"), + _ => dbg_panic("Only work as by call trigger"), + }; + + let val = args.val; + + Mint::asset_numeric(val, rose_id) + .execute() + .dbg_expect("Failed to mint rose"); +} diff --git a/client/tests/integration/smartcontracts/multisig/Cargo.toml b/client/tests/integration/smartcontracts/multisig/Cargo.toml new file mode 100644 index 00000000000..3c4c6db935b --- /dev/null +++ b/client/tests/integration/smartcontracts/multisig/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "multisig" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_trigger.workspace = true +executor_custom_data_model.workspace = true + +panic-halt.workspace = true +lol_alloc.workspace = true +getrandom.workspace = true + +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, default-features = false } + +[build-dependencies] +iroha_wasm_builder = { version = "=2.0.0-pre-rc.21", path = "../../../../../wasm_builder" } diff --git a/client/tests/integration/smartcontracts/multisig/src/lib.rs b/client/tests/integration/smartcontracts/multisig/src/lib.rs new file mode 100644 index 00000000000..f26a9af2c5a --- /dev/null +++ b/client/tests/integration/smartcontracts/multisig/src/lib.rs @@ -0,0 +1,122 @@ +//! Trigger to control multisignature account + +#![no_std] + +extern crate alloc; +#[cfg(not(test))] +extern crate panic_halt; + +use alloc::{collections::btree_set::BTreeSet, format, vec::Vec}; + +use executor_custom_data_model::multisig::MultisigArgs; +use iroha_trigger::{debug::dbg_panic, prelude::*}; +use lol_alloc::{FreeListAllocator, LockedAllocator}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +getrandom::register_custom_getrandom!(iroha_trigger::stub_getrandom); + +#[iroha_trigger::main] +fn main(id: TriggerId, _owner: AccountId, event: EventBox) { + let (args, signatory): (MultisigArgs, AccountId) = match event { + EventBox::ExecuteTrigger(event) => ( + event + .args() + .dbg_expect("trigger expect args") + .try_into_any() + .dbg_expect("failed to parse arguments"), + event.authority().clone(), + ), + _ => dbg_panic("only work as by call trigger"), + }; + + let instructions_hash = match &args { + MultisigArgs::Instructions(instructions) => HashOf::new(instructions), + MultisigArgs::Vote(instructions_hash) => *instructions_hash, + }; + let votes_metadata_key: Name = format!("{instructions_hash}/votes").parse().unwrap(); + let instructions_metadata_key: Name = + format!("{instructions_hash}/instructions").parse().unwrap(); + + let (votes, instructions) = match args { + MultisigArgs::Instructions(instructions) => { + FindTriggerKeyValueByIdAndKey::new(id.clone(), votes_metadata_key.clone()) + .execute() + .expect_err("instructions are already submitted"); + + let votes = BTreeSet::from([signatory.clone()]); + + SetKeyValue::trigger( + id.clone(), + instructions_metadata_key.clone(), + JsonString::new(&instructions), + ) + .execute() + .dbg_unwrap(); + + SetKeyValue::trigger( + id.clone(), + votes_metadata_key.clone(), + JsonString::new(&votes), + ) + .execute() + .dbg_unwrap(); + + (votes, instructions) + } + MultisigArgs::Vote(_instructions_hash) => { + let mut votes: BTreeSet = + FindTriggerKeyValueByIdAndKey::new(id.clone(), votes_metadata_key.clone()) + .execute() + .dbg_expect("instructions should be submitted first") + .into_inner() + .try_into_any() + .dbg_unwrap(); + + votes.insert(signatory.clone()); + + SetKeyValue::trigger( + id.clone(), + votes_metadata_key.clone(), + JsonString::new(&votes), + ) + .execute() + .dbg_unwrap(); + + let instructions: Vec = + FindTriggerKeyValueByIdAndKey::new(id.clone(), instructions_metadata_key.clone()) + .execute() + .dbg_unwrap() + .into_inner() + .try_into_any() + .dbg_unwrap(); + + (votes, instructions) + } + }; + + let signatories: BTreeSet = + FindTriggerKeyValueByIdAndKey::new(id.clone(), "signatories".parse().unwrap()) + .execute() + .dbg_unwrap() + .into_inner() + .try_into_any() + .dbg_unwrap(); + + // Require N of N signatures + if votes.is_superset(&signatories) { + // Cleanup votes and instructions + RemoveKeyValue::trigger(id.clone(), votes_metadata_key) + .execute() + .dbg_unwrap(); + RemoveKeyValue::trigger(id.clone(), instructions_metadata_key) + .execute() + .dbg_unwrap(); + + // Execute instructions proposal which collected enough votes + for isi in instructions { + isi.execute().dbg_unwrap(); + } + } +} diff --git a/client/tests/integration/smartcontracts/multisig_register/Cargo.toml b/client/tests/integration/smartcontracts/multisig_register/Cargo.toml new file mode 100644 index 00000000000..09d62f6c25e --- /dev/null +++ b/client/tests/integration/smartcontracts/multisig_register/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "multisig_register" + +edition.workspace = true +version.workspace = true +authors.workspace = true + +license.workspace = true + +[lib] +crate-type = ['cdylib'] + +[dependencies] +iroha_trigger.workspace = true +iroha_executor_data_model.workspace = true +executor_custom_data_model.workspace = true + +panic-halt.workspace = true +lol_alloc.workspace = true +getrandom.workspace = true + +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, default-features = false } + +[build-dependencies] +iroha_wasm_builder = { version = "=2.0.0-pre-rc.21", path = "../../../../../wasm_builder" } diff --git a/client/tests/integration/smartcontracts/multisig_register/build.rs b/client/tests/integration/smartcontracts/multisig_register/build.rs new file mode 100644 index 00000000000..2195810702a --- /dev/null +++ b/client/tests/integration/smartcontracts/multisig_register/build.rs @@ -0,0 +1,20 @@ +//! Compile trigger to handle multisig actions + +use std::{io::Write, path::Path}; + +const TRIGGER_DIR: &str = "../multisig"; + +fn main() -> Result<(), Box> { + println!("cargo::rerun-if-changed={}", TRIGGER_DIR); + + let out_dir = std::env::var("OUT_DIR").unwrap(); + let wasm = iroha_wasm_builder::Builder::new(TRIGGER_DIR) + .show_output() + .build()? + .optimize()? + .into_bytes()?; + + let mut file = std::fs::File::create(Path::new(&out_dir).join("multisig.wasm"))?; + file.write_all(&wasm)?; + Ok(()) +} diff --git a/client/tests/integration/smartcontracts/multisig_register/src/lib.rs b/client/tests/integration/smartcontracts/multisig_register/src/lib.rs new file mode 100644 index 00000000000..f3f2dfb6530 --- /dev/null +++ b/client/tests/integration/smartcontracts/multisig_register/src/lib.rs @@ -0,0 +1,94 @@ +//! Trigger which register multisignature account and create trigger to control it + +#![no_std] + +extern crate alloc; +#[cfg(not(test))] +extern crate panic_halt; + +use alloc::format; + +use executor_custom_data_model::multisig::MultisigRegisterArgs; +use iroha_executor_data_model::permission::trigger::CanExecuteUserTrigger; +use iroha_trigger::{debug::dbg_panic, prelude::*}; +use lol_alloc::{FreeListAllocator, LockedAllocator}; + +#[global_allocator] +static ALLOC: LockedAllocator = LockedAllocator::new(FreeListAllocator::new()); + +getrandom::register_custom_getrandom!(iroha_trigger::stub_getrandom); + +// Trigger wasm code for handling multisig logic +const WASM: &[u8] = core::include_bytes!(concat!(core::env!("OUT_DIR"), "/multisig.wasm")); + +#[iroha_trigger::main] +fn main(_id: TriggerId, _owner: AccountId, event: EventBox) { + let args: MultisigRegisterArgs = match event { + EventBox::ExecuteTrigger(event) => event + .args() + .dbg_expect("trigger expect args") + .try_into_any() + .dbg_expect("failed to parse args"), + _ => dbg_panic("Only work as by call trigger"), + }; + + let account_id = args.account.id().clone(); + + Register::account(args.account) + .execute() + .dbg_expect("failed to register multisig account"); + + let trigger_id: TriggerId = format!( + "{}_{}_multisig_trigger", + account_id.signatory(), + account_id.domain() + ) + .parse() + .dbg_expect("failed to parse trigger id"); + + let payload = WasmSmartContract::from_compiled(WASM.to_vec()); + let trigger = Trigger::new( + trigger_id.clone(), + Action::new( + payload, + Repeats::Indefinitely, + account_id.clone(), + ExecuteTriggerEventFilter::new().for_trigger(trigger_id.clone()), + ), + ); + + Register::trigger(trigger) + .execute() + .dbg_expect("failed to register multisig trigger"); + + let role_id: RoleId = format!( + "{}_{}_signatories", + account_id.signatory(), + account_id.domain() + ) + .parse() + .dbg_expect("failed to parse role"); + + let can_execute_multisig_trigger = CanExecuteUserTrigger { + trigger: trigger_id.clone(), + }; + let role = Role::new(role_id.clone()).add_permission(can_execute_multisig_trigger); + + Register::role(role) + .execute() + .dbg_expect("failed to register multisig role"); + + SetKeyValue::trigger( + trigger_id, + "signatories".parse().unwrap(), + JsonString::new(&args.signatories), + ) + .execute() + .dbg_unwrap(); + + for signatory in args.signatories { + Grant::role(role_id.clone(), signatory) + .execute() + .dbg_expect("failed to grant multisig role to account"); + } +} diff --git a/client/tests/integration/triggers/by_call_trigger.rs b/client/tests/integration/triggers/by_call_trigger.rs index 52a3096d1e7..293982cee0e 100644 --- a/client/tests/integration/triggers/by_call_trigger.rs +++ b/client/tests/integration/triggers/by_call_trigger.rs @@ -1,5 +1,6 @@ use std::{str::FromStr as _, sync::mpsc, thread, time::Duration}; +use executor_custom_data_model::mint_rose_args::MintRoseArgs; use eyre::{eyre, Result, WrapErr}; use iroha::{ client::{self, Client}, @@ -648,3 +649,45 @@ fn build_register_trigger_isi( ), )) } + +#[test] +fn call_execute_trigger_with_args() -> Result<()> { + let (_rt, _peer, mut test_client) = ::new().with_port(11_265).start_with_runtime(); + wait_for_genesis_committed(&vec![test_client.clone()], 0); + + let asset_definition_id = "rose#wonderland".parse()?; + let account_id = ALICE_ID.clone(); + let asset_id = AssetId::new(asset_definition_id, account_id.clone()); + let prev_value = get_asset_value(&mut test_client, asset_id.clone()); + + let trigger_id = TriggerId::from_str(TRIGGER_NAME)?; + let wasm = + iroha_wasm_builder::Builder::new("tests/integration/smartcontracts/mint_rose_trigger_args") + .show_output() + .build()? + .optimize()? + .into_bytes()?; + let wasm = WasmSmartContract::from_compiled(wasm); + let trigger = Trigger::new( + trigger_id.clone(), + Action::new( + wasm, + Repeats::Indefinitely, + account_id.clone(), + ExecuteTriggerEventFilter::new() + .for_trigger(trigger_id.clone()) + .under_authority(account_id.clone()), + ), + ); + + test_client.submit_blocking(Register::trigger(trigger))?; + + let args: MintRoseArgs = MintRoseArgs { val: 42 }; + let call_trigger = ExecuteTrigger::new(trigger_id).with_args(&args); + test_client.submit_blocking(call_trigger)?; + + let new_value = get_asset_value(&mut test_client, asset_id); + assert_eq!(new_value, prev_value.checked_add(numeric!(42)).unwrap()); + + Ok(()) +} diff --git a/configs/swarm/executor.wasm b/configs/swarm/executor.wasm index 2fb1146f22d..5a17bbb7aa8 100644 Binary files a/configs/swarm/executor.wasm and b/configs/swarm/executor.wasm differ diff --git a/core/src/smartcontracts/isi/triggers/mod.rs b/core/src/smartcontracts/isi/triggers/mod.rs index 03e15e11d27..a6950a792ab 100644 --- a/core/src/smartcontracts/isi/triggers/mod.rs +++ b/core/src/smartcontracts/isi/triggers/mod.rs @@ -276,6 +276,12 @@ pub mod isi { ) -> Result<(), Error> { let id = &self.trigger; + let event = ExecuteTriggerEvent { + trigger_id: id.clone(), + authority: authority.clone(), + args: self.args, + }; + state_transaction .world .triggers @@ -283,11 +289,6 @@ pub mod isi { let allow_execute = if let TriggeringEventFilterBox::ExecuteTrigger(filter) = action.clone_and_box().filter { - let event = ExecuteTriggerEvent { - trigger_id: id.clone(), - authority: authority.clone(), - }; - filter.matches(&event) || action.authority() == authority } else { false @@ -305,9 +306,7 @@ pub mod isi { .ok_or_else(|| Error::Find(FindError::Trigger(id.clone()))) .and_then(core::convert::identity)?; - state_transaction - .world - .execute_trigger(id.clone(), authority); + state_transaction.world.execute_trigger(event); Ok(()) } diff --git a/core/src/state.rs b/core/src/state.rs index 2c711280929..7335d11de48 100644 --- a/core/src/state.rs +++ b/core/src/state.rs @@ -904,12 +904,7 @@ impl WorldTransaction<'_, '_> { /// then *trigger* will be executed on the **current** block /// - If this method is called by ISI inside *trigger*, /// then *trigger* will be executed on the **next** block - pub fn execute_trigger(&mut self, trigger_id: TriggerId, authority: &AccountId) { - let event = ExecuteTriggerEvent { - trigger_id, - authority: authority.clone(), - }; - + pub fn execute_trigger(&mut self, event: ExecuteTriggerEvent) { self.triggers.handle_execute_trigger_event(event.clone()); self.events_buffer.push(event.into()); } diff --git a/data_model/src/events/execute_trigger.rs b/data_model/src/events/execute_trigger.rs index 4a43f3b9f19..59324f91329 100644 --- a/data_model/src/events/execute_trigger.rs +++ b/data_model/src/events/execute_trigger.rs @@ -33,6 +33,9 @@ mod model { pub trigger_id: TriggerId, /// Authority of user who tries to execute trigger pub authority: AccountId, + /// Args to pass for trigger execution + #[getset(skip)] + pub args: Option, } /// Filter for trigger execution [`Event`] @@ -59,6 +62,13 @@ mod model { } } +impl ExecuteTriggerEvent { + /// Args to pass for trigger execution + pub fn args(&self) -> Option<&JsonString> { + self.args.as_ref() + } +} + impl ExecuteTriggerEventFilter { /// Creates a new [`ExecuteTriggerEventFilter`] accepting all [`ExecuteTriggerEvent`]s #[must_use] diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index b9203bb93c5..ca8e675c034 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -924,13 +924,30 @@ mod transparent { isi! { /// Instruction to execute specified trigger - #[derive(Constructor, Display)] + #[derive(Display)] #[display(fmt = "EXECUTE `{trigger}`")] - #[serde(transparent)] - #[repr(transparent)] pub struct ExecuteTrigger { /// Id of a trigger to execute pub trigger: TriggerId, + /// Arguments to trigger execution + pub args: Option, + } + } + + impl ExecuteTrigger { + /// Constructor for [`Self`] + pub fn new(trigger: TriggerId) -> Self { + Self { + trigger, + args: None, + } + } + + /// Add trigger execution args + #[must_use] + pub fn with_args(mut self, args: &T) -> Self { + self.args = Some(JsonString::new(args)); + self } } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 5b7417b53fa..a86d786812b 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -635,7 +635,7 @@ mod ffi { #[allow(ambiguous_glob_reexports)] pub mod prelude { //! Prelude: re-export of most commonly used traits, structs and macros in this crate. - pub use iroha_crypto::PublicKey; + pub use iroha_crypto::{HashOf, PublicKey}; pub use iroha_primitives::{ json::*, numeric::{numeric, Numeric, NumericSpec}, diff --git a/docs/source/references/schema.json b/docs/source/references/schema.json index 1ad22b2b46f..b8ef73c6e7e 100644 --- a/docs/source/references/schema.json +++ b/docs/source/references/schema.json @@ -1379,6 +1379,10 @@ { "name": "trigger", "type": "TriggerId" + }, + { + "name": "args", + "type": "Option" } ] }, @@ -1391,6 +1395,10 @@ { "name": "authority", "type": "AccountId" + }, + { + "name": "args", + "type": "Option" } ] }, @@ -2593,6 +2601,9 @@ "Option": { "Option": "IpfsPath" }, + "Option": { + "Option": "JsonString" + }, "Option": { "Option": "Name" },