Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: reimplement multisignature transactions #4788

Merged
merged 7 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/tests/integration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
150 changes: 150 additions & 0 deletions client/tests/integration/multisig.rs
Original file line number Diff line number Diff line change
@@ -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) = <PeerBuilder>::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::<BTreeMap<AccountId, KeyPair>>();

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(())
}
3 changes: 3 additions & 0 deletions client/tests/integration/smartcontracts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -21,6 +22,8 @@ members = [
"executor_custom_data_model",
"query_assets_and_save_cursor",
"smart_contract_can_filter_queries",
"multisig_register",
"multisig",
]

[profile.dev]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Original file line number Diff line number Diff line change
@@ -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,
}
Original file line number Diff line number Diff line change
@@ -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<AccountId>,
}

/// 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<InstructionBox>),
/// Accept vote for certain instructions
Vote(HashOf<Vec<InstructionBox>>),
}
Original file line number Diff line number Diff line change
@@ -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 }
Original file line number Diff line number Diff line change
@@ -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<FreeListAllocator> = 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");
}
25 changes: 25 additions & 0 deletions client/tests/integration/smartcontracts/multisig/Cargo.toml
Original file line number Diff line number Diff line change
@@ -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" }
Loading
Loading