Skip to content

Commit

Permalink
[feature] #3871: Register<AssetDefinition> permissions
Browse files Browse the repository at this point in the history
Signed-off-by: Ilia Churin <[email protected]>
  • Loading branch information
ilchu committed Nov 29, 2023
1 parent 7a6fe6b commit cbe8d0d
Show file tree
Hide file tree
Showing 13 changed files with 152 additions and 13 deletions.
61 changes: 58 additions & 3 deletions client/tests/integration/domain_owner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,60 @@ fn domain_owner_domain_permissions() -> Result<()> {
wait_for_genesis_committed(&[test_client.clone()], 0);

let kingdom_id: DomainId = "kingdom".parse()?;
let bob_id: AccountId = "bob@kingdom".parse()?;
let coin_id: AssetDefinitionId = "coin#kingdom".parse()?;
let coin = AssetDefinition::quantity(coin_id.clone());

// "alice@wonderland" is owner of "kingdom" domain
let kingdom = Domain::new(kingdom_id.clone());
test_client.submit_blocking(RegisterExpr::new(kingdom))?;

let bob_keypair = KeyPair::generate()?;
let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]);
test_client.submit_blocking(RegisterExpr::new(bob))?;

// Asset definitions can't be registered by "bob@kingdom" by default
let transaction = TransactionBuilder::new(bob_id.clone())
.with_instructions([RegisterExpr::new(coin.clone())])
.sign(bob_keypair.clone())?;
let err = test_client
.submit_transaction_blocking(&transaction)
.expect_err("Tx should fail due to permissions");

let rejection_reason = err
.downcast_ref::<PipelineRejectionReason>()
.unwrap_or_else(|| panic!("Error {err} is not PipelineRejectionReason"));

assert!(matches!(
rejection_reason,
&PipelineRejectionReason::Transaction(TransactionRejectionReason::Validation(
ValidationFail::NotPermitted(_)
))
));

// "alice@wonderland" owns the domain and can register AssetDefinitions by default as domain owner
test_client.submit_blocking(RegisterExpr::new(coin.clone()))?;
test_client.submit_blocking(UnregisterExpr::new(coin_id))?;

// Granting a respective token also allows "bob@kingdom" to do so
let token = PermissionToken::new(
"CanRegisterAssetDefinitionInDomain".parse().unwrap(),
&json!({ "domain_id": kingdom_id }),
);
test_client.submit_blocking(GrantExpr::new(token.clone(), bob_id.clone()))?;
let transaction = TransactionBuilder::new(bob_id.clone())
.with_instructions([RegisterExpr::new(coin)])
.sign(bob_keypair)?;
test_client.submit_transaction_blocking(&transaction)?;
test_client.submit_blocking(RevokeExpr::new(token, bob_id.clone()))?;

// check that "alice@wonderland" as owner of domain can edit metadata in her domain
let key: Name = "key".parse()?;
let value: Name = "value".parse()?;
test_client.submit_blocking(SetKeyValueExpr::new(kingdom_id.clone(), key.clone(), value))?;
test_client.submit_blocking(RemoveKeyValueExpr::new(kingdom_id.clone(), key))?;

// check that "alice@wonderland" as owner of domain can grant and revoke domain related permission tokens
let bob_id: AccountId = "bob@wonderland".parse()?;
let token = PermissionToken::new(
"CanUnregisterDomain".parse().unwrap(),
&json!({ "domain_id": kingdom_id }),
Expand Down Expand Up @@ -108,7 +149,7 @@ fn domain_owner_asset_definition_permissions() -> Result<()> {
let coin_id: AssetDefinitionId = "coin#kingdom".parse()?;

// "alice@wonderland" is owner of "kingdom" domain
let kingdom = Domain::new(kingdom_id);
let kingdom = Domain::new(kingdom_id.clone());
test_client.submit_blocking(RegisterExpr::new(kingdom))?;

let bob_keypair = KeyPair::generate()?;
Expand All @@ -118,6 +159,13 @@ fn domain_owner_asset_definition_permissions() -> Result<()> {
let rabbit = Account::new(rabbit_id.clone(), []);
test_client.submit_blocking(RegisterExpr::new(rabbit))?;

// Grant permission to register asset definitions to "bob@kingdom"
let token = PermissionToken::new(
"CanRegisterAssetDefinitionInDomain".parse().unwrap(),
&json!({ "domain_id": kingdom_id }),
);
test_client.submit_blocking(GrantExpr::new(token, bob_id.clone()))?;

// register asset definitions by "bob@kingdom" so he is owner of it
let coin = AssetDefinition::quantity(coin_id.clone());
let transaction = TransactionBuilder::new(bob_id.clone())
Expand Down Expand Up @@ -161,13 +209,20 @@ fn domain_owner_asset_permissions() -> Result<()> {
let store_id: AssetDefinitionId = "store#kingdom".parse()?;

// "alice@wonderland" is owner of "kingdom" domain
let kingdom = Domain::new(kingdom_id);
let kingdom = Domain::new(kingdom_id.clone());
test_client.submit_blocking(RegisterExpr::new(kingdom))?;

let bob_keypair = KeyPair::generate()?;
let bob = Account::new(bob_id.clone(), [bob_keypair.public_key().clone()]);
test_client.submit_blocking(RegisterExpr::new(bob))?;

// Grant permission to register asset definitions to "bob@kingdom"
let token = PermissionToken::new(
"CanRegisterAssetDefinitionInDomain".parse().unwrap(),
&json!({ "domain_id": kingdom_id }),
);
test_client.submit_blocking(GrantExpr::new(token, bob_id.clone()))?;

// register asset definitions by "bob@kingdom" so he is owner of it
let coin = AssetDefinition::quantity(coin_id.clone());
let store = AssetDefinition::store(store_id.clone());
Expand Down
13 changes: 11 additions & 2 deletions client/tests/integration/restart_peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use std::{str::FromStr, sync::Arc};

use eyre::Result;
use iroha_client::client::{self, QueryResult};
use iroha_data_model::prelude::*;
use iroha_data_model::{prelude::*, domain::DomainId};
use iroha_primitives::unique_vec;
use tempfile::TempDir;
use test_network::*;
use serde_json::json;
use tokio::runtime::Runtime;

use super::Configuration;
Expand All @@ -19,7 +20,13 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> {
configuration.sumeragi.trusted_peers.peers = unique_vec![peer.id.clone()];

let account_id = AccountId::from_str("alice@wonderland").unwrap();
let asset_definition_id = AssetDefinitionId::from_str("xor#wonderland").unwrap();
let kingdom_id: DomainId = "kingdom".parse()?;
let asset_definition_id = AssetDefinitionId::from_str("xor#kingdom").unwrap();
let kingdom = Domain::new(kingdom_id.clone());
let token = PermissionToken::new(
"CanRegisterAssetDefinitionInDomain".parse().unwrap(),
&json!({ "domain_id": kingdom_id }),
);
let create_asset = RegisterExpr::new(AssetDefinition::quantity(asset_definition_id.clone()));
let quantity: u32 = 200;

Expand All @@ -35,6 +42,8 @@ fn restarted_peer_should_have_the_same_asset_amount() -> Result<()> {
);
wait_for_genesis_committed(&vec![iroha_client.clone()], 0);

iroha_client.submit_blocking(RegisterExpr::new(kingdom))?;
iroha_client.submit_blocking(GrantExpr::new(token, account_id.clone()))?;
iroha_client.submit_blocking(create_asset)?;
let mint_asset = MintExpr::new(
quantity.to_value(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ fn main(_owner: AccountId, _event: Event) {

let limits = MetadataLimits::new(256, 256);

let genesis_id: DomainId = "genesis".parse().dbg_unwrap();

for account in accounts_cursor {
let account = account.dbg_unwrap();

if account.id().domain_id() == &genesis_id {
continue;
}

let mut metadata = Metadata::new();
let name = format!(
"nft_for_{}_in_{}",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ impl Executor {
}
}

// TODO (#4049): Fix unused `visit_register_domain()`
fn visit_register_domain(executor: &mut Executor, authority: &AccountId, _isi: Register<Domain>) {
if executor.block_height() == 0 {
pass!(executor)
Expand Down
Binary file modified configs/peer/executor.wasm
Binary file not shown.
5 changes: 5 additions & 0 deletions core/test_network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ impl TestGenesis for GenesisNetwork {
);
let upgrade_executor_permission =
PermissionToken::new("CanUpgradeExecutor".parse().unwrap(), &json!(null));
let register_asset_definitions_permission = PermissionToken::new(
"CanRegisterAssetDefinitionInDomain".parse().unwrap(),
&json!({ "domain_id": DomainId::from_str("wonderland").unwrap() } ),
);

let first_transaction = genesis
.first_transaction_mut()
Expand All @@ -116,6 +120,7 @@ impl TestGenesis for GenesisNetwork {
unregister_any_role_permission,
unregister_wonderland_domain,
upgrade_executor_permission,
register_asset_definitions_permission,
] {
first_transaction
.append_instruction(GrantExpr::new(permission, alice_id.clone()).into());
Expand Down
2 changes: 1 addition & 1 deletion smart_contract/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ workspace = true

[features]
# Enables debugging tools such as `dbg()` and `DebugUnwrapExt`
debug = []
debug = ["iroha_smart_contract_utils/debug"]

[dependencies]
iroha_data_model.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions smart_contract/executor/derive/src/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn2::DeriveInput) -> To
"fn visit_if(operation: &ConditionalExpr)",
"fn visit_pair(operation: &PairExpr)",
"fn visit_unregister_peer(operation: Unregister<Peer>)",
"fn visit_register_domain(operation: Register<Domain>)",
"fn visit_unregister_domain(operation: Unregister<Domain>)",
"fn visit_transfer_domain(operation: Transfer<Account, DomainId, Account>)",
"fn visit_set_domain_key_value(operation: SetKeyValue<Domain>)",
Expand All @@ -138,6 +139,7 @@ pub fn impl_derive_visit(emitter: &mut Emitter, input: &syn2::DeriveInput) -> To
"fn visit_transfer_asset(operation: Transfer<Asset, NumericValue, Account>)",
"fn visit_set_asset_key_value(operation: SetKeyValue<Asset>)",
"fn visit_remove_asset_key_value(operation: RemoveKeyValue<Asset>)",
"fn visit_register_asset_definition(operation: Register<AssetDefinition>)",
"fn visit_unregister_asset_definition(operation: Unregister<AssetDefinition>)",
"fn visit_transfer_asset_definition(operation: Transfer<Account, AssetDefinitionId, Account>)",
"fn visit_set_asset_definition_key_value(operation: SetKeyValue<AssetDefinition>)",
Expand Down
1 change: 1 addition & 0 deletions smart_contract/executor/derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub fn derive_token(input: TokenStream) -> TokenStream {
/// - `asset_definition::Owner` - checks if the authority is the asset definition owner;
/// - `asset::Owner` - checks if the authority is the asset owner;
/// - `account::Owner` - checks if the authority is the account owner.
/// - `domain::Owner` - checks if the authority is the domain owner.
/// - `AlwaysPass` - checks nothing and always passes.
/// - `OnlyGenesis` - checks that block height is 0.
///
Expand Down
59 changes: 54 additions & 5 deletions smart_contract/executor/src/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ pub use asset::{
visit_set_asset_key_value, visit_transfer_asset, visit_unregister_asset,
};
pub use asset_definition::{
visit_remove_asset_definition_key_value, visit_set_asset_definition_key_value,
visit_transfer_asset_definition, visit_unregister_asset_definition,
visit_register_asset_definition, visit_remove_asset_definition_key_value,
visit_set_asset_definition_key_value, visit_transfer_asset_definition,
visit_unregister_asset_definition,
};
pub use domain::{
visit_remove_domain_key_value, visit_set_domain_key_value, visit_transfer_domain,
visit_unregister_domain,
visit_register_domain, visit_remove_domain_key_value, visit_set_domain_key_value,
visit_transfer_domain, visit_unregister_domain,
};
pub use executor::visit_upgrade_executor;
use iroha_smart_contract::debug::DebugExpectExt as _;
Expand Down Expand Up @@ -63,6 +64,11 @@ pub fn default_permission_token_schema() -> PermissionTokenSchema {
schema
}

// NOTE: If any new `visit_..` functions are introduced in this module, one should
// not forget to update the default executor boilerplate too, specifically the
// `iroha_executor::derive::default::impl_derive_visit` function
// signature list.

/// Default validation for [`SignedTransaction`].
///
/// # Warning
Expand Down Expand Up @@ -315,6 +321,17 @@ pub mod domain {

use super::*;

// Just the blank leaf implementation here for overriding, as in
// `iroha_client::tests::integration::smartcontracts::executor_with_custom_token`
// example.
pub fn visit_register_domain<V: Validate + ?Sized>(
executor: &mut V,
authority: &AccountId,
isi: Register<Domain>,
) {
iroha_data_model::visit::visit_register_domain(executor, authority, isi)
}

pub fn visit_unregister_domain<V: Validate + ?Sized>(
executor: &mut V,
authority: &AccountId,
Expand Down Expand Up @@ -563,10 +580,42 @@ pub mod account {
}

pub mod asset_definition {
use permission::{account::is_account_owner, asset_definition::is_asset_definition_owner};
use permission::{
account::is_account_owner, asset_definition::is_asset_definition_owner,
domain::is_domain_owner,
};

use super::*;

pub fn visit_register_asset_definition<V: Validate + ?Sized>(
executor: &mut V,
authority: &AccountId,
isi: Register<AssetDefinition>,
) {
let domain_id = isi.object.id().domain_id();

if is_genesis(executor) {
pass!(executor);
}
match is_domain_owner(domain_id, authority) {
Err(err) => deny!(executor, err),
Ok(true) => pass!(executor),
Ok(false) => {}
}
let can_register_asset_definition_token =
tokens::domain::CanRegisterAssetDefinitionInDomain {
domain_id: domain_id.clone(),
};
if can_register_asset_definition_token.is_owned_by(authority) {
pass!(executor);
}

deny!(
executor,
"New `AssetDefinition` can only be registered by domain owner or if granted `CanRegisterAssetDefinitionInDomain` token"
);
}

pub fn visit_unregister_asset_definition<V: Validate + ?Sized>(
executor: &mut V,
authority: &AccountId,
Expand Down
9 changes: 9 additions & 0 deletions smart_contract/executor/src/default/tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ declare_tokens! {
crate::default::tokens::domain::{CanUnregisterDomain},
crate::default::tokens::domain::{CanSetKeyValueInDomain},
crate::default::tokens::domain::{CanRemoveKeyValueInDomain},
crate::default::tokens::domain::{CanRegisterAssetDefinitionInDomain},

crate::default::tokens::account::{CanUnregisterAccount},
crate::default::tokens::account::{CanMintUserPublicKeys},
Expand Down Expand Up @@ -145,6 +146,14 @@ pub mod domain {
pub domain_id: DomainId,
}
}

token! {
#[derive(ValidateGrantRevoke, permission::derive_conversions::domain::Owner)]
#[validate(permission::domain::Owner)]
pub struct CanRegisterAssetDefinitionInDomain {
pub domain_id: DomainId,
}
}
}

pub mod account {
Expand Down
3 changes: 3 additions & 0 deletions smart_contract/utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ license.workspace = true
[lints]
workspace = true

[features]
debug = []

[dependencies]
iroha_data_model.workspace = true

Expand Down
3 changes: 2 additions & 1 deletion smart_contract/utils/src/debug.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! WASM debugging utilities
use core::fmt::Debug;
use alloc::format;

#[cfg(not(test))]
mod host {
Expand Down Expand Up @@ -32,7 +33,7 @@ pub fn dbg<T: Debug + ?Sized>(_obj: &T) {
#[allow(clippy::used_underscore_binding)]
let s = format!("{:?}", _obj);
// Safety: `host_dbg` doesn't take ownership of it's pointer parameter
unsafe { iroha_smart_contract_utils::encode_and_execute(&s, host_dbg) }
unsafe { crate::encode_and_execute(&s, host_dbg) }
}
}

Expand Down

0 comments on commit cbe8d0d

Please sign in to comment.