diff --git a/core/src/smartcontracts/isi/account.rs b/core/src/smartcontracts/isi/account.rs index b2360b2d9c4..62eed1a286a 100644 --- a/core/src/smartcontracts/isi/account.rs +++ b/core/src/smartcontracts/isi/account.rs @@ -11,7 +11,7 @@ use crate::prelude::*; /// - minting/burning signature condition check /// - update metadata /// - grant permissions and roles -/// - TODO Revoke permissions or roles +/// - Revoke permissions or roles pub mod isi { use super::{super::prelude::*, *}; @@ -142,6 +142,25 @@ pub mod isi { } } + impl Execute for Revoke { + type Error = Error; + type Diff = DataEvent; + + #[metrics(+"revoke_account_permission_token")] + fn execute( + self, + _authority: ::Id, + wsv: &WorldStateView, + ) -> Result { + let id = self.destination_id.clone(); + wsv.modify_account(&id, |account| { + let _ = account.permission_tokens.remove(&self.object); + Ok(()) + })?; + Ok(self.into()) + } + } + #[cfg(feature = "roles")] impl Execute for Grant { type Error = Error; @@ -166,6 +185,31 @@ pub mod isi { Ok(self.into()) } } + + #[cfg(feature = "roles")] + impl Execute for Revoke { + type Error = Error; + type Diff = DataEvent; + + #[metrics(+"revoke_account_role")] + fn execute( + self, + _authority: ::Id, + wsv: &WorldStateView, + ) -> Result { + wsv.world() + .roles + .get(&self.object) + .ok_or_else(|| FindError::Role(self.object.clone()))?; + + let id = self.destination_id.clone(); + wsv.modify_account(&id, |account| { + let _ = account.roles.remove(&self.object); + Ok(()) + })?; + Ok(self.into()) + } + } } /// Account-related [`Query`] instructions. diff --git a/core/src/smartcontracts/isi/mod.rs b/core/src/smartcontracts/isi/mod.rs index f1cf0a55cda..6b79f762618 100644 --- a/core/src/smartcontracts/isi/mod.rs +++ b/core/src/smartcontracts/isi/mod.rs @@ -105,9 +105,9 @@ pub mod error { /// Register. Register, /// Set key-value pair. - SetKV, + SetKeyValue, /// Remove key-value pair. - RemKV, + RemoveKeyValue, /// Grant Grant, /// Transfer @@ -116,6 +116,8 @@ pub mod error { Burn, /// Un-register. Unregister, + /// Revoke + Revoke, } impl std::fmt::Display for InstructionType { @@ -290,6 +292,7 @@ impl Execute for Instruction { Ok(remove_key_value.execute(authority, wsv)?.into()) } Grant(grant_box) => Ok(grant_box.execute(authority, wsv)?.into()), + Revoke(revoke_box) => Ok(revoke_box.execute(authority, wsv)?.into()), } } } @@ -472,7 +475,7 @@ impl Execute for SetKeyValueBox { IdBox::DomainId(id) => { SetKeyValue::::new(id, key, value).execute(authority, wsv) } - _ => Err(Error::Unsupported(InstructionType::SetKV)), + _ => Err(Error::Unsupported(InstructionType::SetKeyValue)), } } } @@ -500,7 +503,7 @@ impl Execute for RemoveKeyValueBox { IdBox::AccountId(account_id) => { RemoveKeyValue::::new(account_id, key).execute(authority, wsv) } - _ => Err(Error::Unsupported(InstructionType::RemKV)), + _ => Err(Error::Unsupported(InstructionType::RemoveKeyValue)), } } } @@ -602,6 +605,34 @@ impl Execute for GrantBox { } } +impl Execute for RevokeBox { + type Error = Error; + type Diff = DataEvent; + + #[log] + fn execute( + self, + authority: ::Id, + wsv: &WorldStateView, + ) -> Result { + let context = Context::new(); + match ( + self.destination_id.evaluate(wsv, &context)?, + self.object.evaluate(wsv, &context)?, + ) { + (IdBox::AccountId(account_id), Value::PermissionToken(permission_token)) => { + Revoke::::new(permission_token, account_id) + .execute(authority, wsv) + } + #[cfg(feature = "roles")] + (IdBox::AccountId(account_id), Value::Id(IdBox::RoleId(role_id))) => { + Revoke::::new(role_id, account_id).execute(authority, wsv) + } + _ => Err(Error::Unsupported(InstructionType::Revoke)), + } + } +} + pub mod prelude { //! Re-exports important traits and types. Meant to be glob imported when using `Iroha`. pub use super::{account::isi::*, asset::isi::*, domain::isi::*, world::isi::*, *}; diff --git a/core/src/smartcontracts/isi/permissions.rs b/core/src/smartcontracts/isi/permissions.rs index 15baaaa63d7..7b69e6a6d32 100644 --- a/core/src/smartcontracts/isi/permissions.rs +++ b/core/src/smartcontracts/isi/permissions.rs @@ -5,7 +5,7 @@ use std::iter; use eyre::Result; -use iroha_data_model::prelude::*; +use iroha_data_model::{isi::RevokeBox, prelude::*}; use super::prelude::WorldTrait; #[cfg(feature = "roles")] @@ -27,11 +27,12 @@ pub type DenialReason = String; /// Implement this to provide custom permission checks for the Iroha based blockchain. pub trait IsAllowed { - /// Checks if the `authority` is allowed to perform `instruction` given the current state of `wsv`. + /// Checks if the `authority` is allowed to perform `instruction` + /// given the current state of `wsv`. /// /// # Errors - /// In the case when the execution of `instruction` under given `authority` with the current state of `wsv` - /// is unallowed. + /// If the execution of `instruction` under given `authority` with + /// the current state of `wsv` is disallowed. fn check( &self, authority: &AccountId, @@ -67,7 +68,7 @@ impl>> Validator } } -/// `check` will succeed if either `first` or `second` validator succeeds. +/// `check` succeeds if either `first` or `second` validator succeeds. pub struct Or { first: IsAllowedBoxed, second: IsAllowedBoxed, @@ -101,8 +102,9 @@ impl From> for IsAllowedBo } } -/// Wraps validator to check nested permissions. -/// Pay attention to wrap only validators that do not check nested intructions by themselves. +/// Wraps validator to check nested permissions. Pay attention to +/// wrap only validators that do not check nested intructions by +/// themselves. pub struct CheckNested { validator: IsAllowedBoxed, } @@ -130,6 +132,7 @@ impl IsAllowed for CheckNested { | Instruction::RemoveKeyValue(_) | Instruction::Transfer(_) | Instruction::Grant(_) + | Instruction::Revoke(_) | Instruction::Fail(_) => self.validator.check(authority, instruction, wsv), Instruction::If(if_box) => { self.check(authority, &if_box.then, wsv) @@ -149,14 +152,17 @@ impl IsAllowed for CheckNested { } } -/// Checks an expression recursively to evaluate if there is a query inside of it and if -/// the user has permission to execute this query. +/// Checks an expression recursively to evaluate if there is a query +/// inside of it and if the user has permission to execute this query. /// -/// As the function is recursive, caution should be exercised to have a limit of nestedness, that would not cause stack overflow. -/// Up to 2^13 calls were tested and are ok. This is within default instruction limit. +/// As the function is recursive, caution should be exercised to have +/// a limit of nestedness, that would not cause stack overflow. Up to +/// 2^13 calls were tested and are ok. This is within default +/// instruction limit. /// /// # Errors -/// Returns an error if a user is not allowed to execute one of the inner queries, given the current `validator`. +/// If a user is not allowed to execute one of the inner queries, +/// given the current `validator`. pub fn check_query_in_expression( authority: &AccountId, expression: &Expression, @@ -236,14 +242,18 @@ pub fn check_query_in_expression( } } -/// Checks an instruction recursively to evaluate if there is a query inside of it and if -/// the user has permission to execute this query. +/// Checks an instruction recursively to evaluate if there is a query +/// inside of it and if the user has permission to execute this query. /// -/// As the function is recursive, caution should be exercised to have a limit of nestedness, that would not cause stack overflow. -/// Up to 2^13 calls were tested and are ok. This is within default instruction limit. +/// As the function is recursive, caution should be exercised to have +/// a limit of nesting, that would not cause stack overflow. Up to +/// 2^13 calls were tested and are ok. This is within default +/// instruction limit. /// /// # Errors -/// Returns an error if a user is not allowed to execute one of the inner queries, given the current `validator`. +/// If a user is not allowed to execute one of the inner queries, +/// given the current `validator`. +#[allow(clippy::too_many_lines)] pub fn check_query_in_instruction( authority: &AccountId, instruction: &Instruction, @@ -324,6 +334,15 @@ pub fn check_query_in_instruction( validator, )) } + Instruction::Revoke(instruction) => { + check_query_in_expression(authority, &instruction.object.expression, wsv, validator) + .and(check_query_in_expression( + authority, + &instruction.destination_id.expression, + wsv, + validator, + )) + } Instruction::If(if_box) => { check_query_in_instruction(authority, &if_box.then, wsv, validator).and_then(|_| { match &if_box.otherwise { @@ -569,7 +588,7 @@ pub trait IsGrantAllowed { /// Checks the [`GrantBox`] instruction. /// /// # Errors - /// Should return error if this particular validator does not approve this Grant instruction. + /// If this validator doesn't approve this Grant instruction. fn check_grant( &self, authority: &AccountId, @@ -578,6 +597,23 @@ pub trait IsGrantAllowed { ) -> Result<(), DenialReason>; } +/// Boxed validator implementing the [`IsRevokeAllowed`] trait. +pub type IsRevokeAllowedBoxed = Box + Send + Sync>; + +/// Checks the [`RevokeBox`] instruction. +pub trait IsRevokeAllowed { + /// Checks the [`RevokeBox`] instruction. + /// + /// # Errors + /// If this validator doesn't approve this Revoke instruction. + fn check_revoke( + &self, + authority: &AccountId, + instruction: &RevokeBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason>; +} + impl IsAllowed for IsGrantAllowedBoxed { fn check( &self, @@ -593,18 +629,40 @@ impl IsAllowed for IsGrantAllowedBoxed { } } +impl IsAllowed for IsRevokeAllowedBoxed { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + if let Instruction::Revoke(isi) = instruction { + self.check_revoke(authority, isi, wsv) + } else { + Ok(()) + } + } +} + impl From> for IsInstructionAllowedBoxed { fn from(validator: IsGrantAllowedBoxed) -> Self { Box::new(validator) } } -/// Unpacks instruction if it is Grant of a Role into several Grants fo Permission Token. -/// If instruction is not Grant of Role, returns it as inly instruction inside the vec. -/// Should be called before permission checks by validators. +impl From> for IsInstructionAllowedBoxed { + fn from(validator: IsRevokeAllowedBoxed) -> Self { + Box::new(validator) + } +} + +/// Unpacks instruction if it is Grant of a Role into several Grants +/// fo Permission Token. If instruction is not Grant of Role, returns +/// it as inly instruction inside the vec. Should be called before +/// permission checks by validators. /// -/// Semantically means that user can grant a role only if they can grant each of the permission tokens -/// that the role consists of. +/// Semantically means that user can grant a role only if they can +/// grant each of the permission tokens that the role consists of. /// /// # Errors /// Evaluation failure of instruction fields. @@ -637,11 +695,54 @@ pub fn unpack_if_role_grant( Ok(instructions) } +/// Unpack instruction if it is a Revoke of a Role, into several +/// Revocations of Permission Tokens. If the instruction is not a +/// Revoke of Role, returns it as an internal instruction inside the +/// vec. +/// +/// This `fn` should be called before permission checks (by +/// validators). +/// +/// Semantically: the user can revoke a role only if they can revoke +/// each of the permission tokens that the role consists of of. +/// +/// # Errors +/// Evaluation failure of each of the instruction fields. +#[cfg(feature = "roles")] +pub fn unpack_if_role_revoke( + instruction: Instruction, + wsv: &WorldStateView, +) -> Result> { + let revoke = if let Instruction::Revoke(revoke) = &instruction { + revoke + } else { + return Ok(vec![instruction]); + }; + let id = if let Value::Id(IdBox::RoleId(id)) = revoke.object.evaluate(wsv, &Context::new())? { + id + } else { + return Ok(vec![instruction]); + }; + + let instructions = if let Some(role) = wsv.world.roles.get(&id) { + let destination_id = revoke.destination_id.evaluate(wsv, &Context::new())?; + role.permissions + .iter() + .cloned() + .map(|permission_token| RevokeBox::new(permission_token, destination_id.clone()).into()) + .collect() + } else { + Vec::new() + }; + Ok(instructions) +} + pub mod prelude { //! Exports common types for permissions. pub use super::{ AllowAll, DenialReason, HasTokenBoxed, IsAllowedBoxed, IsGrantAllowed, IsGrantAllowedBoxed, + IsRevokeAllowed, IsRevokeAllowedBoxed, }; } @@ -703,6 +804,8 @@ mod tests { struct GrantedToken; + // TODO: ADD some Revoke tests. + impl HasToken for GrantedToken { fn token( &self, diff --git a/core/src/wsv.rs b/core/src/wsv.rs index 8093e058ba9..6a9b2126a61 100644 --- a/core/src/wsv.rs +++ b/core/src/wsv.rs @@ -294,7 +294,7 @@ impl WorldStateView { /// Returns iterator over blockchain blocks /// /// **Locking behaviour**: Holding references to blocks stored in the blockchain can induce - /// deadlock. This limitation is imposed by the fact that blockchain is backed by [`Dashmap`] + /// deadlock. This limitation is imposed by the fact that blockchain is backed by [`dashmap::Dashmap`] pub fn blocks( &self, ) -> impl Iterator + '_> + '_ { diff --git a/data_model/src/events/data.rs b/data_model/src/events/data.rs index e3c660fdc02..74647ea91a8 100644 --- a/data_model/src/events/data.rs +++ b/data_model/src/events/data.rs @@ -419,6 +419,19 @@ mod account { } } + impl From> for DataEvent { + fn from(src: Revoke) -> Self { + Self::new(src.destination_id, Updated::Permission) + } + } + + #[cfg(feature = "roles")] + impl From> for DataEvent { + fn from(src: Revoke) -> Self { + Self::new(src.destination_id, Updated::Permission) + } + } + #[cfg(feature = "roles")] impl From> for DataEvent { fn from(src: Grant) -> Self { diff --git a/data_model/src/isi.rs b/data_model/src/isi.rs index 1a4a598bef5..f2818280bf6 100644 --- a/data_model/src/isi.rs +++ b/data_model/src/isi.rs @@ -40,6 +40,8 @@ pub enum Instruction { RemoveKeyValue(RemoveKeyValueBox), /// `Grant` variant. Grant(GrantBox), + /// `Revoke` variant. + Revoke(RevokeBox), } impl Instruction { @@ -60,6 +62,7 @@ impl Instruction { SetKeyValue(set_key_value) => set_key_value.len(), RemoveKeyValue(remove_key_value) => remove_key_value.len(), Grant(grant_box) => grant_box.len(), + Revoke(revoke_box) => revoke_box.len(), } } } @@ -177,6 +180,15 @@ pub struct GrantBox { pub destination_id: EvaluatesTo, } +/// Sized structure for all possible Grants. +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, IntoSchema)] +pub struct RevokeBox { + /// Object to grant. + pub object: EvaluatesTo, + /// Entity to which to grant this token. + pub destination_id: EvaluatesTo, +} + /// Generic instruction to set value to the object. #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct Set @@ -289,6 +301,19 @@ where pub destination_id: D::Id, } +/// Generic instruction for revoking permission from an entity. +#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] +pub struct Revoke +where + D: Identifiable, + O: ValueMarker, +{ + /// Object to revoke. + pub object: O, + /// Entity which is being revoked this token from. + pub destination_id: D::Id, +} + impl SetKeyValue where O: Identifiable, @@ -396,16 +421,51 @@ where O: ValueMarker, { /// Constructor. + #[inline] + pub fn new(object: O, destination_id: D::Id) -> Self { + Self { + object, + destination_id, + } + } +} + +impl Revoke +where + D: Identifiable, + O: ValueMarker, +{ + /// Constructor + #[inline] pub fn new(object: O, destination_id: D::Id) -> Self { - Grant { + Self { object, destination_id, } } } +impl RevokeBox { + /// Compute the number of contained instructions and expressions. + #[inline] + pub fn len(&self) -> usize { + self.object.len() + self.destination_id.len() + 1 + } + + /// Generic constructor. + pub fn new>, I: Into>>( + object: P, + destination_id: I, + ) -> Self { + Self { + destination_id: destination_id.into(), + object: object.into(), + } + } +} + impl GrantBox { - /// Calculates number of contained instructions and expressions. + /// Compute the number of contained instructions and expressions. pub fn len(&self) -> usize { self.object.len() + self.destination_id.len() + 1 } @@ -423,7 +483,8 @@ impl GrantBox { } impl SetKeyValueBox { - /// Calculates number of underneath instructions and expressions + /// Calculate number of underneath instructions and expressions + #[inline] pub fn len(&self) -> usize { self.object_id.len() + self.key.len() + self.value.len() + 1 } @@ -704,7 +765,7 @@ mod tests { pub mod prelude { pub use super::{ Burn, BurnBox, FailBox, Grant, GrantBox, If as IfInstruction, Instruction, Mint, MintBox, - Pair, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, SequenceBox, SetKeyValue, - SetKeyValueBox, Transfer, TransferBox, Unregister, UnregisterBox, + Pair, Register, RegisterBox, RemoveKeyValue, RemoveKeyValueBox, Revoke, RevokeBox, + SequenceBox, SetKeyValue, SetKeyValueBox, Transfer, TransferBox, Unregister, UnregisterBox, }; } diff --git a/data_model/src/lib.rs b/data_model/src/lib.rs index 5b634a122ea..d2a521a77b8 100644 --- a/data_model/src/lib.rs +++ b/data_model/src/lib.rs @@ -176,7 +176,7 @@ pub enum IdBox { AssetId(asset::Id), /// [`AssetDefinitionId`](`asset::DefinitionId`) variant. AssetDefinitionId(asset::DefinitionId), - /// [`DomainId`](`Id`) variant. + /// [`DomainId`](`domain::Id`) variant. DomainId(domain::Id), /// [`PeerId`](`peer::Id`) variant. PeerId(peer::Id), @@ -218,7 +218,7 @@ pub enum IdentifiableBox { /// [`Role`](`role::Role`) variant. #[cfg(feature = "roles")] Role(Box), - /// [`World`](`world::World`). + /// `World`. World, } @@ -252,7 +252,7 @@ pub enum Value { String(String), /// [`Name`] value. Name(Name), - /// [`Fixed`] value + /// [`fixed::Fixed`] value Fixed(fixed::Fixed), /// [`Vec`] of `Value`. Vec( @@ -262,9 +262,9 @@ pub enum Value { ), /// Recursive inclusion of LimitedMetadata, LimitedMetadata(metadata::Metadata), - /// [`Id`] of [`Asset`], [`Account`], etc. + /// `Id` of `Asset`, `Account`, etc. Id(IdBox), - /// [`Identifiable`] as [`Asset`], [`Account`] etc. + /// `Identifiable` as `Asset`, `Account` etc. Identifiable(IdentifiableBox), /// [`PublicKey`]. PublicKey(PublicKey), @@ -274,9 +274,9 @@ pub enum Value { SignatureCheckCondition(SignatureCheckCondition), /// Committed or rejected transactions TransactionValue(TransactionValue), - /// Permission token. + /// [`PermissionToken`]. PermissionToken(PermissionToken), - /// Hash + /// [`struct@Hash`] Hash(Hash), } diff --git a/data_model/src/transaction.rs b/data_model/src/transaction.rs index 5ed7d0c6d34..793c69bfd6c 100644 --- a/data_model/src/transaction.rs +++ b/data_model/src/transaction.rs @@ -181,7 +181,7 @@ impl From for VersionedTransaction { pub struct Transaction { /// [`Transaction`] payload. pub payload: Payload, - /// [`Transaction`]'s [`Signature`]s. + /// [`SignatureOf`] [`Transaction`]. pub signatures: BTreeSet>, } @@ -381,7 +381,7 @@ impl Txn for VersionedValidTransaction { pub struct ValidTransaction { /// The [`Transaction`]'s payload. pub payload: Payload, - /// [`Transaction`]'s [`Signature`]s. + /// [`SignatureOf`] [`Transaction`]. pub signatures: SignaturesOf, } @@ -436,7 +436,7 @@ impl Txn for VersionedRejectedTransaction { pub struct RejectedTransaction { /// The [`Transaction`]'s payload. pub payload: Payload, - /// [`Transaction`]'s [`Signature`]s. + /// [`SignatureOf`] [`Transaction`]. pub signatures: SignaturesOf, /// The reason for rejecting this transaction during the validation pipeline. pub rejection_reason: TransactionRejectionReason, @@ -495,6 +495,7 @@ impl Display for InstructionExecutionFail { SetKeyValue(_) => "set key-value pair", RemoveKeyValue(_) => "remove key-value pair", Grant(_) => "grant", + Revoke(_) => "revoke", }; write!( f, diff --git a/permissions_validators/src/lib.rs b/permissions_validators/src/lib.rs index eb3212a98f1..e533ec7c6a9 100644 --- a/permissions_validators/src/lib.rs +++ b/permissions_validators/src/lib.rs @@ -1,7 +1,5 @@ //! Out of box implementations for common permission checks. -#![allow(clippy::module_name_repetitions)] - use std::collections::BTreeMap; use iroha_core::{ @@ -73,2428 +71,33 @@ macro_rules! impl_from_item_for_grant_instruction_validator_box { }; } -macro_rules! try_into_or_exit { - ( $ident:ident ) => { - if let Ok(into) = $ident.try_into() { - into - } else { - return Ok(()); - } - }; -} - -/// Permission checks asociated with use cases that can be summarized as private blockchains (e.g. CBDC). -pub mod private_blockchain { - - use super::*; - - /// A preconfigured set of permissions for simple use cases. - pub fn default_instructions_permissions() -> IsInstructionAllowedBoxed { - ValidatorBuilder::new() - .with_recursive_validator( - register::ProhibitRegisterDomains.or(register::GrantedAllowedRegisterDomains), - ) - .all_should_succeed() - } - - /// A preconfigured set of permissions for simple use cases. - pub fn default_query_permissions() -> IsQueryAllowedBoxed { - ValidatorBuilder::new().all_should_succeed() - } - - /// Prohibits using `Grant` instruction at runtime. - /// This means `Grant` instruction will only be used in genesis to specify rights. - #[derive(Debug, Copy, Clone)] - pub struct ProhibitGrant; - - impl_from_item_for_grant_instruction_validator_box!(ProhibitGrant); - - impl IsGrantAllowed for ProhibitGrant { - fn check_grant( - &self, - _authority: &AccountId, - _instruction: &GrantBox, - _wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - Err("Granting at runtime is prohibited.".to_owned()) - } - } - - pub mod register { - //! Module with permissions for registering. - - use std::collections::BTreeMap; - - use super::*; - - /// Can register domains permission token name. - pub static CAN_REGISTER_DOMAINS_TOKEN: Lazy = - Lazy::new(|| Name::test("can_register_domains")); - - /// Prohibits registering domains. - #[derive(Debug, Copy, Clone)] - pub struct ProhibitRegisterDomains; - - impl_from_item_for_instruction_validator_box!(ProhibitRegisterDomains); - - impl IsAllowed for ProhibitRegisterDomains { - fn check( - &self, - _authority: &AccountId, - instruction: &Instruction, - _wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let _register_box = if let Instruction::Register(register) = instruction { - register - } else { - return Ok(()); - }; - Err("Domain registration is prohibited.".to_owned()) - } - } - - /// Validator that allows to register domains for accounts with the corresponding permission token. - #[derive(Debug, Clone, Copy)] - pub struct GrantedAllowedRegisterDomains; - - impl_from_item_for_granted_token_validator_box!(GrantedAllowedRegisterDomains); - - impl HasToken for GrantedAllowedRegisterDomains { - fn token( - &self, - _authority: &AccountId, - _instruction: &Instruction, - _wsv: &WorldStateView, - ) -> Result { - Ok(PermissionToken::new( - CAN_REGISTER_DOMAINS_TOKEN.clone(), - BTreeMap::new(), - )) - } - } - } - - /// Query Permissions. - pub mod query { - use super::*; - - /// Allow queries that only access the data of the domain of the signer. - #[derive(Debug, Copy, Clone)] - pub struct OnlyAccountsDomain; - - impl IsAllowed for OnlyAccountsDomain { - #[allow(clippy::too_many_lines, clippy::match_same_arms)] - fn check( - &self, - authority: &AccountId, - query: &QueryBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - use QueryBox::*; - let context = Context::new(); - match query { - FindAssetsByAssetDefinitionId(_) | FindAssetsByName(_) | FindAllAssets(_) => { - Err("Only access to the assets of the same domain is permitted.".to_owned()) - } - FindAllAccounts(_) | FindAccountsByName(_) => Err( - "Only access to the accounts of the same domain is permitted.".to_owned(), - ), - FindAllAssetsDefinitions(_) => Err( - "Only access to the asset definitions of the same domain is permitted." - .to_owned(), - ), - FindAllDomains(_) => { - Err("Only access to the domain of the account is permitted.".to_owned()) - } - #[cfg(feature = "roles")] - FindAllRoles(_) => Ok(()), - FindAllPeers(_) => Ok(()), - FindAccountById(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - FindAccountKeyValueByIdAndKey(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - FindAccountsByDomainId(query) => { - let domain_id = query - .domain_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access accounts from a different domain with name {}.", - domain_id - )) - } - } - FindAssetById(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if asset_id.account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different domain.", - asset_id - )) - } - } - FindAssetsByAccountId(query) => { - let account_id = query - .account_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - FindAssetsByDomainId(query) => { - let domain_id = query - .domain_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access assets from a different domain with name {}.", - domain_id - )) - } - } - FindAssetsByDomainIdAndAssetDefinitionId(query) => { - let domain_id = query - .domain_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access assets from a different domain with name {}.", - domain_id - )) - } - } - FindAssetDefinitionKeyValueByIdAndKey(query) => { - let asset_definition_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if asset_definition_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access asset definition from a different domain. Asset definition domain: {}. Signers account domain {}.", - asset_definition_id.domain_id, - authority.domain_id - )) - } - } - FindAssetQuantityById(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if asset_id.account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different domain.", - asset_id - )) - } - } - FindAssetKeyValueByIdAndKey(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if asset_id.account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different domain.", - asset_id - )) - } - } - FindDomainById(query::FindDomainById { id }) - | FindDomainKeyValueByIdAndKey(query::FindDomainKeyValueByIdAndKey { - id, - .. - }) => { - let domain_id = - id.evaluate(wsv, &context).map_err(|err| err.to_string())?; - if domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!("Cannot access a different domain: {}.", domain_id)) - } - } - FindTransactionsByAccountId(query) => { - let account_id = query - .account_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - FindTransactionByHash(_query) => Ok(()), - #[cfg(feature = "roles")] - FindRolesByAccountId(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - FindPermissionTokensByAccountId(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if account_id.domain_id == authority.domain_id { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as it is in a different domain.", - account_id - )) - } - } - } +macro_rules! impl_from_item_for_revoke_instruction_validator_box { + ( $ty:ty ) => { + impl From<$ty> for IsRevokeAllowedBoxed { + fn from(validator: $ty) -> Self { + Box::new(validator) } } - impl_from_item_for_query_validator_box!(OnlyAccountsDomain); - - /// Allow queries that only access the signers account data. - #[derive(Debug, Copy, Clone)] - pub struct OnlyAccountsData; - - impl IsAllowed for OnlyAccountsData { - #[allow(clippy::too_many_lines, clippy::match_same_arms)] - fn check( - &self, - authority: &AccountId, - query: &QueryBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - use QueryBox::*; - - let context = Context::new(); - match query { - FindAccountsByName(_) - | FindAccountsByDomainId(_) - | FindAllAccounts(_) - | FindAllAssetsDefinitions(_) - | FindAssetsByAssetDefinitionId(_) - | FindAssetsByDomainId(_) - | FindAssetsByName(_) - | FindAllDomains(_) - | FindDomainById(_) - | FindDomainKeyValueByIdAndKey(_) - | FindAssetsByDomainIdAndAssetDefinitionId(_) - | FindAssetDefinitionKeyValueByIdAndKey(_) - | FindAllAssets(_) => { - Err("Only access to the assets of the same domain is permitted.".to_owned()) - } - #[cfg(feature = "roles")] - FindAllRoles(_) => Ok(()), - FindAllPeers(_) => Ok(()), - FindAccountById(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as only access to your own account is permitted..", - account_id - )) - } - } - FindAccountKeyValueByIdAndKey(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access account {} as only access to your own account is permitted..", - account_id - )) - } - } - FindAssetById(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &asset_id.account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different account.", - asset_id - )) - } - } - FindAssetsByAccountId(query) => { - let account_id = query - .account_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access a different account: {}.", - account_id - )) - } - } - - FindAssetQuantityById(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &asset_id.account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different account.", - asset_id - )) - } - } - FindAssetKeyValueByIdAndKey(query) => { - let asset_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &asset_id.account_id == authority { - Ok(()) - } else { - Err(format!( - "Cannot access asset {} as it is in a different account.", - asset_id - )) - } - } - - FindTransactionsByAccountId(query) => { - let account_id = query - .account_id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!("Cannot access another account: {}.", account_id)) - } - } - FindTransactionByHash(_query) => Ok(()), - #[cfg(feature = "roles")] - FindRolesByAccountId(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!("Cannot access another account: {}.", account_id)) - } - } - FindPermissionTokensByAccountId(query) => { - let account_id = query - .id - .evaluate(wsv, &context) - .map_err(|err| err.to_string())?; - if &account_id == authority { - Ok(()) - } else { - Err(format!("Cannot access another account: {}.", account_id)) - } - } - } + impl From<$ty> for IsInstructionAllowedBoxed { + fn from(validator: $ty) -> Self { + let validator: IsRevokeAllowedBoxed = validator.into(); + Box::new(validator) } } - - impl_from_item_for_query_validator_box!(OnlyAccountsData); - } + }; } -/// Permission checks asociated with use cases that can be summarized as public blockchains. -pub mod public_blockchain { - use super::*; - - /// Origin asset id param used in permission tokens. - pub static ASSET_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("asset_id")); - /// Origin account id param used in permission tokens. - pub static ACCOUNT_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("account_id")); - /// Origin asset definition param used in permission tokens. - pub static ASSET_DEFINITION_ID_TOKEN_PARAM_NAME: Lazy = - Lazy::new(|| Name::test("asset_definition_id")); - - /// A preconfigured set of permissions for simple use cases. - pub fn default_permissions() -> IsInstructionAllowedBoxed { - // Grant instruction checks are or unioned, so that if one permission validator approves this Grant it will succeed. - let grant_instruction_validator = ValidatorBuilder::new() - .with_validator(transfer::GrantMyAssetAccess) - .with_validator(unregister::GrantRegisteredByMeAccess) - .with_validator(mint::GrantRegisteredByMeAccess) - .with_validator(burn::GrantMyAssetAccess) - .with_validator(burn::GrantRegisteredByMeAccess) - .with_validator(key_value::GrantMyAssetAccessRemove) - .with_validator(key_value::GrantMyAssetAccessSet) - .with_validator(key_value::GrantMyMetadataAccessSet) - .with_validator(key_value::GrantMyMetadataAccessRemove) - .with_validator(key_value::GrantMyAssetDefinitionSet) - .with_validator(key_value::GrantMyAssetDefinitionRemove) - .any_should_succeed("Grant instruction validator."); - ValidatorBuilder::new() - .with_recursive_validator(grant_instruction_validator) - .with_recursive_validator(transfer::OnlyOwnedAssets.or(transfer::GrantedByAssetOwner)) - .with_recursive_validator( - unregister::OnlyAssetsCreatedByThisAccount.or(unregister::GrantedByAssetCreator), - ) - .with_recursive_validator( - mint::OnlyAssetsCreatedByThisAccount.or(mint::GrantedByAssetCreator), - ) - .with_recursive_validator(burn::OnlyOwnedAssets.or(burn::GrantedByAssetOwner)) - .with_recursive_validator( - burn::OnlyAssetsCreatedByThisAccount.or(burn::GrantedByAssetCreator), - ) - .with_recursive_validator( - key_value::AccountSetOnlyForSignerAccount.or(key_value::SetGrantedByAccountOwner), - ) - .with_recursive_validator( - key_value::AccountRemoveOnlyForSignerAccount - .or(key_value::RemoveGrantedByAccountOwner), - ) - .with_recursive_validator( - key_value::AssetSetOnlyForSignerAccount.or(key_value::SetGrantedByAssetOwner), - ) - .with_recursive_validator( - key_value::AssetRemoveOnlyForSignerAccount.or(key_value::RemoveGrantedByAssetOwner), - ) - .with_recursive_validator( - key_value::AssetDefinitionSetOnlyForSignerAccount - .or(key_value::SetGrantedByAssetDefinitionOwner), - ) - .with_recursive_validator( - key_value::AssetDefinitionRemoveOnlyForSignerAccount - .or(key_value::RemoveGrantedByAssetDefinitionOwner), - ) - .all_should_succeed() - } - - /// Checks that `authority` is account owner for account supplied in `permission_token`. - /// - /// # Errors - /// - The `permission_token` is of improper format. - /// - Account owner is not `authority` - pub fn check_account_owner_for_token( - permission_token: &PermissionToken, - authority: &AccountId, - ) -> Result<(), String> { - let account_id = if let Value::Id(IdBox::AccountId(account_id)) = permission_token - .params - .get(&ACCOUNT_ID_TOKEN_PARAM_NAME.clone()) - .ok_or(format!( - "Failed to find permission param {}.", - ACCOUNT_ID_TOKEN_PARAM_NAME.clone() - ))? { - account_id - } else { - return Err(format!( - "Permission param {} is not an AccountId.", - ACCOUNT_ID_TOKEN_PARAM_NAME.clone() - )); - }; - if account_id != authority { - return Err("Account specified in permission token is not owned by signer.".to_owned()); - } - Ok(()) - } - - /// Checks that `authority` is asset owner for asset supplied in `permission_token`. - /// - /// # Errors - /// - The `permission_token` is of improper format. - /// - Asset owner is not `authority` - pub fn check_asset_owner_for_token( - permission_token: &PermissionToken, - authority: &AccountId, - ) -> Result<(), String> { - let asset_id = if let Value::Id(IdBox::AssetId(asset_id)) = permission_token - .params - .get(&ASSET_ID_TOKEN_PARAM_NAME.clone()) - .ok_or(format!( - "Failed to find permission param {}.", - ASSET_ID_TOKEN_PARAM_NAME.clone() - ))? { - asset_id - } else { - return Err(format!( - "Permission param {} is not an AssetId.", - ASSET_ID_TOKEN_PARAM_NAME.clone() - )); - }; - if &asset_id.account_id != authority { - return Err("Asset specified in permission token is not owned by signer.".to_owned()); - } - Ok(()) - } - - /// Checks that asset creator is `authority` in the supplied `permission_token`. - /// - /// # Errors - /// - The `permission_token` is of improper format. - /// - Asset creator is not `authority` - pub fn check_asset_creator_for_token( - permission_token: &PermissionToken, - authority: &AccountId, - wsv: &WorldStateView, - ) -> Result<(), String> { - let definition_id = if let Value::Id(IdBox::AssetDefinitionId(definition_id)) = - permission_token - .params - .get(&ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone()) - .ok_or(format!( - "Failed to find permission param {}.", - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone() - ))? { - definition_id +macro_rules! try_into_or_exit { + ( $ident:ident ) => { + if let Ok(into) = $ident.try_into() { + into } else { - return Err(format!( - "Permission param {} is not an AssetDefinitionId.", - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone() - )); - }; - let registered_by_signer_account = wsv - .asset_definition_entry(definition_id) - .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) - .unwrap_or(false); - if !registered_by_signer_account { - return Err( - "Can not grant access for assets, registered by another account.".to_owned(), - ); - } - Ok(()) - } - - pub mod transfer { - //! Module with permission for transfering - - use super::*; - - /// Can transfer user's assets permission token name. - pub static CAN_TRANSFER_USER_ASSETS_TOKEN: Lazy = - Lazy::new(|| Name::test("can_transfer_user_assets")); - - /// Checks that account transfers only the assets that he owns. - #[derive(Debug, Copy, Clone)] - pub struct OnlyOwnedAssets; - - impl_from_item_for_instruction_validator_box!(OnlyOwnedAssets); - - impl IsAllowed for OnlyOwnedAssets { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let transfer_box = if let Instruction::Transfer(transfer) = instruction { - transfer - } else { - return Ok(()); - }; - let source_id = transfer_box - .source_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let source_id: AssetId = try_into_or_exit!(source_id); - - if &source_id.account_id != authority { - return Err("Can't transfer assets of the other account.".to_owned()); - } - Ok(()) - } - } - - /// Allows transfering user's assets from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct GrantedByAssetOwner; - - impl_from_item_for_granted_token_validator_box!(GrantedByAssetOwner); - - impl HasToken for GrantedByAssetOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let transfer_box = if let Instruction::Transfer(transfer_box) = instruction { - transfer_box - } else { - return Err("Instruction is not transfer.".to_owned()); - }; - let source_id = transfer_box - .source_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let source_id: AssetId = if let Ok(id) = source_id.try_into() { - id - } else { - return Err("Source id is not an AssetId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), source_id.into()); - Ok(PermissionToken::new( - CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetAccess; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccess); - - impl IsGrantAllowed for GrantMyAssetAccess { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_TRANSFER_USER_ASSETS_TOKEN.clone() { - return Err("Grant instruction is not for transfer permission.".to_owned()); - } - check_asset_owner_for_token(&permission_token, authority) - } - } - } - - pub mod unregister { - //! Module with permission for unregistering - - use super::*; - - /// Can unregister asset with the corresponding asset definition. - pub static CAN_UNREGISTER_ASSET_WITH_DEFINITION: Lazy = - Lazy::new(|| Name::test("can_unregister_asset_with_definition")); - - /// Checks that account can unregister only the assets which were registered by this account in the first place. - #[derive(Debug, Copy, Clone)] - pub struct OnlyAssetsCreatedByThisAccount; - - impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); - - impl IsAllowed for OnlyAssetsCreatedByThisAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let unregister_box = if let Instruction::Unregister(unregister) = instruction { - unregister - } else { - return Ok(()); - }; - let object_id = unregister_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_definition_id: AssetDefinitionId = try_into_or_exit!(object_id); - let registered_by_signer_account = wsv - .asset_definition_entry(&asset_definition_id) - .map(|asset_definition_entry| { - &asset_definition_entry.registered_by == authority - }) - .unwrap_or(false); - if !registered_by_signer_account { - return Err("Can't unregister assets registered by other accounts.".to_owned()); - } - Ok(()) - } - } - - /// Allows unregistering user's assets from a different account if the corresponding user granted the permission token - /// for a specific asset. - #[derive(Debug, Clone, Copy)] - pub struct GrantedByAssetCreator; - - impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); - - impl HasToken for GrantedByAssetCreator { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let unregister_box = if let Instruction::Unregister(unregister) = instruction { - unregister - } else { - return Err("Instruction is not unregister.".to_owned()); - }; - let object_id = unregister_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AssetDefinitionId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), - object_id.into(), - ); - Ok(PermissionToken::new( - CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantRegisteredByMeAccess; - - impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); - - impl IsGrantAllowed for GrantRegisteredByMeAccess { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone() { - return Err("Grant instruction is not for unregister permission.".to_owned()); - } - check_asset_creator_for_token(&permission_token, authority, wsv) - } - } - } - - pub mod mint { - //! Module with permission for minting - - use super::*; - - /// Can mint asset with the corresponding asset definition. - pub static CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN: Lazy = - Lazy::new(|| Name::test("can_mint_user_asset_definitions")); - - /// Checks that account can mint only the assets which were registered by this account. - #[derive(Debug, Copy, Clone)] - pub struct OnlyAssetsCreatedByThisAccount; - - impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); - - impl IsAllowed for OnlyAssetsCreatedByThisAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let mint_box = if let Instruction::Mint(mint) = instruction { - mint - } else { - return Ok(()); - }; - let destination_id = mint_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_id: AssetId = try_into_or_exit!(destination_id); - let registered_by_signer_account = wsv - .asset_definition_entry(&asset_id.definition_id) - .map(|asset_definition_entry| { - &asset_definition_entry.registered_by == authority - }) - .unwrap_or(false); - if !registered_by_signer_account { - return Err("Can't mint assets registered by other accounts.".to_owned()); - } - Ok(()) - } - } - - /// Allows minting assets from a different account if the corresponding user granted the permission token - /// for a specific asset. - #[derive(Debug, Clone, Copy)] - pub struct GrantedByAssetCreator; - - impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); - - impl HasToken for GrantedByAssetCreator { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let mint_box = if let Instruction::Mint(mint) = instruction { - mint - } else { - return Err("Instruction is not mint.".to_owned()); - }; - let destination_id = mint_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { - dest_id - } else { - return Err("Destination is not an Asset.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - asset_id.definition_id.into(), - ); - Ok(PermissionToken::new( - CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantRegisteredByMeAccess; - - impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); - - impl IsGrantAllowed for GrantRegisteredByMeAccess { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone() { - return Err("Grant instruction is not for mint permission.".to_owned()); - } - check_asset_creator_for_token(&permission_token, authority, wsv) - } - } - } - - pub mod burn { - //! Module with permission for burning - - use super::*; - - /// Can burn asset with the corresponding asset definition. - pub static CAN_BURN_ASSET_WITH_DEFINITION: Lazy = - Lazy::new(|| Name::test("can_burn_asset_with_definition")); - /// Can burn user's assets permission token name. - pub static CAN_BURN_USER_ASSETS_TOKEN: Lazy = - Lazy::new(|| Name::test("can_burn_user_assets")); - - /// Checks that account can burn only the assets which were registered by this account. - #[derive(Debug, Copy, Clone)] - pub struct OnlyAssetsCreatedByThisAccount; - - impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); - - impl IsAllowed for OnlyAssetsCreatedByThisAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let burn_box = if let Instruction::Burn(burn) = instruction { - burn - } else { - return Ok(()); - }; - let destination_id = burn_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_id: AssetId = try_into_or_exit!(destination_id); - let registered_by_signer_account = wsv - .asset_definition_entry(&asset_id.definition_id) - .map(|asset_definition_entry| { - &asset_definition_entry.registered_by == authority - }) - .unwrap_or(false); - if !registered_by_signer_account { - return Err("Can't burn assets registered by other accounts.".to_owned()); - } - Ok(()) - } - } - - /// Allows burning assets from a different account than the creator's of this asset if the corresponding user granted the permission token - /// for a specific asset. - #[derive(Debug, Clone, Copy)] - pub struct GrantedByAssetCreator; - - impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); - - impl HasToken for GrantedByAssetCreator { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let burn_box = if let Instruction::Burn(burn) = instruction { - burn - } else { - return Err("Instruction is not burn.".to_owned()); - }; - let destination_id = burn_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { - dest_id - } else { - return Err("Destination is not an Asset.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - asset_id.definition_id.into(), - ); - Ok(PermissionToken::new( - CAN_BURN_ASSET_WITH_DEFINITION.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantRegisteredByMeAccess; - - impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); - - impl IsGrantAllowed for GrantRegisteredByMeAccess { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_BURN_ASSET_WITH_DEFINITION.clone() { - return Err("Grant instruction is not for burn permission.".to_owned()); - } - check_asset_creator_for_token(&permission_token, authority, wsv) - } - } - - /// Checks that account can burn only the assets that he currently owns. - #[derive(Debug, Copy, Clone)] - pub struct OnlyOwnedAssets; - - impl_from_item_for_instruction_validator_box!(OnlyOwnedAssets); - - impl IsAllowed for OnlyOwnedAssets { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let burn_box = if let Instruction::Burn(burn) = instruction { - burn - } else { - return Ok(()); - }; - let destination_id = burn_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let asset_id: AssetId = try_into_or_exit!(destination_id); - if &asset_id.account_id != authority { - return Err("Can't burn assets from another account.".to_owned()); - } - Ok(()) - } - } - - /// Allows burning user's assets from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct GrantedByAssetOwner; - - impl_from_item_for_granted_token_validator_box!(GrantedByAssetOwner); - - impl HasToken for GrantedByAssetOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let burn_box = if let Instruction::Burn(burn_box) = instruction { - burn_box - } else { - return Err("Instruction is not burn.".to_owned()); - }; - let destination_id = burn_box - .destination_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let destination_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { - dest_id - } else { - return Err("Source id is not an AssetId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), destination_id.into()); - Ok(PermissionToken::new( - CAN_BURN_USER_ASSETS_TOKEN.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetAccess; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccess); - - impl IsGrantAllowed for GrantMyAssetAccess { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_BURN_USER_ASSETS_TOKEN.clone() { - return Err("Grant instruction is not for burn permission.".to_owned()); - } - check_asset_owner_for_token(&permission_token, authority)?; - Ok(()) - } - } - } - - pub mod key_value { - //! Module with permission for burning - - use super::*; - - /// Can set key value in user's assets permission token name. - pub static CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN: Lazy = - Lazy::new(|| Name::test("can_set_key_value_in_user_assets")); - /// Can remove key value in user's assets permission token name. - pub static CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS: Lazy = - Lazy::new(|| Name::test("can_remove_key_value_in_user_assets")); - /// Can burn user's assets permission token name. - pub static CAN_SET_KEY_VALUE_IN_USER_METADATA: Lazy = - Lazy::new(|| Name::test("can_set_key_value_in_user_metadata")); - /// Can burn user's assets permission token name. - pub static CAN_REMOVE_KEY_VALUE_IN_USER_METADATA: Lazy = - Lazy::new(|| Name::test("can_remove_key_value_in_user_metadata")); - /// Can set key value in the corresponding asset definition. - pub static CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION: Lazy = - Lazy::new(|| Name::test("can_set_key_value_in_asset_definition")); - /// Can remove key value in the corresponding asset definition. - pub static CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION: Lazy = - Lazy::new(|| Name::test("can_remove_key_value_in_asset_definition")); - /// Target account id for setting and removing key value permission tokens. - pub static ACCOUNT_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("account_id")); - - /// Checks that account can set keys for assets only for the signer account. - #[derive(Debug, Copy, Clone)] - pub struct AssetSetOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AssetSetOnlyForSignerAccount); - - impl IsAllowed for AssetSetOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Ok(()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - - match object_id { - IdBox::AssetId(asset_id) if &asset_id.account_id != authority => { - Err("Can't set value to asset store from another account.".to_owned()) - } - _ => Ok(()), - } - } - } - - /// Allows setting user's assets key value map from a different account - /// if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct SetGrantedByAssetOwner; - - impl_from_item_for_granted_token_validator_box!(SetGrantedByAssetOwner); - - impl HasToken for SetGrantedByAssetOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Err("Instruction is not set.".to_owned()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AssetId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AssetId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); - Ok(PermissionToken::new( - CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetAccessSet; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccessSet); - - impl IsGrantAllowed for GrantMyAssetAccessSet { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN.clone() { - return Err("Grant instruction is not for set permission.".to_owned()); - } - check_asset_owner_for_token(&permission_token, authority)?; - Ok(()) - } - } - - /// Checks that account can set keys only the for signer account. - #[derive(Debug, Copy, Clone)] - pub struct AccountSetOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AccountSetOnlyForSignerAccount); - - impl IsAllowed for AccountSetOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Ok(()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - - match &object_id { - IdBox::AccountId(account_id) if account_id != authority => { - Err("Can't set value to account store from another account.".to_owned()) - } - _ => Ok(()), - } - } - } - - /// Allows setting user's metadata key value pairs from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct SetGrantedByAccountOwner; - - impl_from_item_for_granted_token_validator_box!(SetGrantedByAccountOwner); - - impl HasToken for SetGrantedByAccountOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Err("Instruction is not set.".to_owned()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AccountId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AccountId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ACCOUNT_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); - Ok(PermissionToken::new( - CAN_SET_KEY_VALUE_IN_USER_METADATA.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyMetadataAccessSet; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyMetadataAccessSet); - - impl IsGrantAllowed for GrantMyMetadataAccessSet { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_SET_KEY_VALUE_IN_USER_METADATA.clone() { - return Err("Grant instruction is not for set permission.".to_owned()); - } - check_account_owner_for_token(&permission_token, authority)?; - Ok(()) - } - } - - /// Checks that account can remove keys for assets only the for signer account. - #[derive(Debug, Copy, Clone)] - pub struct AssetRemoveOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AssetRemoveOnlyForSignerAccount); - - impl IsAllowed for AssetRemoveOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { - rem_kv - } else { - return Ok(()); - }; - let object_id = rem_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - match object_id { - IdBox::AssetId(asset_id) if &asset_id.account_id != authority => { - Err("Can't remove value from asset store from another account.".to_owned()) - } - _ => Ok(()), - } - } - } - - /// Allows removing user's assets key value pairs from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct RemoveGrantedByAssetOwner; - - impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAssetOwner); - - impl HasToken for RemoveGrantedByAssetOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { - rem_kv - } else { - return Err("Instruction is not set.".to_owned()); - }; - let object_id = rem_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AssetId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AssetId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); - Ok(PermissionToken::new( - CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetAccessRemove; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccessRemove); - - impl IsGrantAllowed for GrantMyAssetAccessRemove { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS.clone() { - return Err("Grant instruction is not for set permission.".to_owned()); - } - check_asset_owner_for_token(&permission_token, authority)?; - Ok(()) - } - } - - /// Checks that account can remove keys only the for signer account. - #[derive(Debug, Copy, Clone)] - pub struct AccountRemoveOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AccountRemoveOnlyForSignerAccount); - - impl IsAllowed for AccountRemoveOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { - rem_kv - } else { - return Ok(()); - }; - let object_id = rem_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - - match object_id { - IdBox::AccountId(account_id) if &account_id != authority => Err( - "Can't remove value from account store from another account.".to_owned(), - ), - _ => Ok(()), - } - } - } - - /// Allows removing user's metadata key value pairs from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct RemoveGrantedByAccountOwner; - - impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAccountOwner); - - impl HasToken for RemoveGrantedByAccountOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { - rem_kv - } else { - return Err("Instruction is not remove.".to_owned()); - }; - let object_id = rem_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AccountId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AccountId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert(ACCOUNT_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); - Ok(PermissionToken::new( - CAN_REMOVE_KEY_VALUE_IN_USER_METADATA.clone(), - params, - )) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the metadata - /// of the signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyMetadataAccessRemove; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyMetadataAccessRemove); - - impl IsGrantAllowed for GrantMyMetadataAccessRemove { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_USER_METADATA.clone() { - return Err("Grant instruction is not for remove permission.".to_owned()); - } - check_account_owner_for_token(&permission_token, authority)?; - Ok(()) - } - } - - /// Validator that checks Grant instruction so that the access is granted to the assets defintion - /// registered by signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetDefinitionSet; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetDefinitionSet); - - impl IsGrantAllowed for GrantMyAssetDefinitionSet { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION.clone() { - return Err( - "Grant instruction is not for set key value in asset definition permission." - .to_owned(), - ); - } - check_asset_creator_for_token(&permission_token, authority, wsv) - } - } - - // Validator that checks Grant instruction so that the access is granted to the assets defintion - /// registered by signer account. - #[derive(Debug, Clone, Copy)] - pub struct GrantMyAssetDefinitionRemove; - - impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetDefinitionRemove); - - impl IsGrantAllowed for GrantMyAssetDefinitionRemove { - fn check_grant( - &self, - authority: &AccountId, - instruction: &GrantBox, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let permission_token: PermissionToken = instruction - .object - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())? - .try_into() - .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; - if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION.clone() { - return Err( - "Grant instruction is not for remove key value in asset definition permission." - .to_owned(), - ); - } - check_asset_creator_for_token(&permission_token, authority, wsv) - } - } - - /// Checks that account can set keys for asset definitions only registered by the signer account. - #[derive(Debug, Copy, Clone)] - pub struct AssetDefinitionSetOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AssetDefinitionSetOnlyForSignerAccount); - - impl IsAllowed for AssetDefinitionSetOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Ok(()); - }; - let obj_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - - let object_id: AssetDefinitionId = try_into_or_exit!(obj_id); - let registered_by_signer_account = wsv - .asset_definition_entry(&object_id) - .map(|asset_definition_entry| { - &asset_definition_entry.registered_by == authority - }) - .unwrap_or(false); - if !registered_by_signer_account { - return Err( - "Can't set key value to asset definition registered by other accounts." - .to_owned(), - ); - } - Ok(()) - } - } - - /// Checks that account can set keys for asset definitions only registered by the signer account. - #[derive(Debug, Copy, Clone)] - pub struct AssetDefinitionRemoveOnlyForSignerAccount; - - impl_from_item_for_instruction_validator_box!(AssetDefinitionRemoveOnlyForSignerAccount); - - impl IsAllowed for AssetDefinitionRemoveOnlyForSignerAccount { - fn check( - &self, - authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result<(), DenialReason> { - let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { - rem_kv - } else { - return Ok(()); - }; - let obj_id = rem_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - - let object_id: AssetDefinitionId = try_into_or_exit!(obj_id); - let registered_by_signer_account = wsv - .asset_definition_entry(&object_id) - .map(|asset_definition_entry| { - &asset_definition_entry.registered_by == authority - }) - .unwrap_or(false); - if !registered_by_signer_account { - return Err( - "Can't remove key value to asset definition registered by other accounts." - .to_owned(), - ); - } - Ok(()) - } - } - - /// Allows setting asset definition's metadata key value pairs from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct SetGrantedByAssetDefinitionOwner; - - impl_from_item_for_granted_token_validator_box!(SetGrantedByAssetDefinitionOwner); - - impl HasToken for SetGrantedByAssetDefinitionOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { - set_kv - } else { - return Err("Instruction is not set.".to_owned()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AssetDefinitionId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - object_id.into(), - ); - Ok(PermissionToken::new( - CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION.clone(), - params, - )) - } - } - - /// Allows setting asset definition's metadata key value pairs from a different account if the corresponding user granted this permission token. - #[derive(Debug, Clone, Copy)] - pub struct RemoveGrantedByAssetDefinitionOwner; - - impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAssetDefinitionOwner); - - impl HasToken for RemoveGrantedByAssetDefinitionOwner { - fn token( - &self, - _authority: &AccountId, - instruction: &Instruction, - wsv: &WorldStateView, - ) -> Result { - let set_kv_box = if let Instruction::RemoveKeyValue(set_kv) = instruction { - set_kv - } else { - return Err("Instruction is not remove key value.".to_owned()); - }; - let object_id = set_kv_box - .object_id - .evaluate(wsv, &Context::new()) - .map_err(|e| e.to_string())?; - let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { - obj_id - } else { - return Err("Source id is not an AssetDefinitionId.".to_owned()); - }; - let mut params = BTreeMap::new(); - params.insert( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - object_id.into(), - ); - Ok(PermissionToken::new( - CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION.clone(), - params, - )) - } - } - } - - #[cfg(test)] - mod tests { - #![allow(clippy::restriction)] - - use std::collections::{BTreeMap, BTreeSet}; - - use iroha_core::wsv::World; - - use super::*; - - fn new_xor_definition(xor_id: &AssetDefinitionId) -> AssetDefinition { - AssetDefinition::new_quantity(xor_id.clone()) - } - - #[test] - fn transfer_only_owned_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_xor_id = ::Id::test("xor", "test", "bob", "test"); - let wsv = WorldStateView::::new(World::new()); - let transfer = Instruction::Transfer(TransferBox { - source_id: IdBox::AssetId(alice_xor_id).into(), - object: Value::U32(10).into(), - destination_id: IdBox::AssetId(bob_xor_id).into(), - }); - assert!(transfer::OnlyOwnedAssets - .check(&alice_id, &transfer, &wsv) - .is_ok()); - assert!(transfer::OnlyOwnedAssets - .check(&bob_id, &transfer, &wsv) - .is_err()); - } - - #[test] - fn transfer_granted_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_xor_id = ::Id::test("xor", "test", "bob", "test"); - let mut domain = Domain::test("test"); - let mut bob_account = Account::new(bob_id.clone()); - let _ = bob_account.permission_tokens.insert(PermissionToken::new( - transfer::CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), - [( - ASSET_ID_TOKEN_PARAM_NAME.clone(), - alice_xor_id.clone().into(), - )], - )); - domain.accounts.insert(bob_id.clone(), bob_account); - let domains = vec![(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, BTreeSet::new())); - let transfer = Instruction::Transfer(TransferBox { - source_id: IdBox::AssetId(alice_xor_id).into(), - object: Value::U32(10).into(), - destination_id: IdBox::AssetId(bob_xor_id).into(), - }); - let validator: IsInstructionAllowedBoxed = transfer::OnlyOwnedAssets - .or(transfer::GrantedByAssetOwner) - .into(); - assert!(validator.check(&alice_id, &transfer, &wsv).is_ok()); - assert!(validator.check(&bob_id, &transfer, &wsv).is_ok()); - } - - #[test] - fn grant_transfer_of_my_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let permission_token_to_alice = PermissionToken::new( - transfer::CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), - [(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), alice_xor_id.into())], - ); - let wsv = WorldStateView::::new(World::new()); - let grant = Instruction::Grant(GrantBox { - object: permission_token_to_alice.into(), - destination_id: IdBox::AccountId(bob_id.clone()).into(), - }); - let validator: IsInstructionAllowedBoxed = transfer::GrantMyAssetAccess.into(); - assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); - assert!(validator.check(&bob_id, &grant, &wsv).is_err()); - } - - #[test] - fn unregister_only_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let wsv = WorldStateView::::new(World::with( - [( - DomainId::test("test"), - Domain { - accounts: BTreeMap::new(), - id: DomainId::test("test"), - asset_definitions: [( - xor_id.clone(), - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone(), - }, - )] - .into(), - metadata: Metadata::new(), - }, - )], - [], - )); - let unregister = - Instruction::Unregister(UnregisterBox::new(IdBox::AssetDefinitionId(xor_id))); - assert!(unregister::OnlyAssetsCreatedByThisAccount - .check(&alice_id, &unregister, &wsv) - .is_ok()); - assert!(unregister::OnlyAssetsCreatedByThisAccount - .check(&bob_id, &unregister, &wsv) - .is_err()); - } - - #[test] - fn unregister_granted_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let mut domain = Domain::test("test"); - let mut bob_account = Account::new(bob_id.clone()); - let _ = bob_account.permission_tokens.insert(PermissionToken::new( - unregister::CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), - xor_id.clone().into(), - )], - )); - domain.accounts.insert(bob_id.clone(), bob_account); - domain.asset_definitions.insert( - xor_id.clone(), - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, [])); - let instruction = Instruction::Unregister(UnregisterBox::new(xor_id)); - let validator: IsInstructionAllowedBoxed = - unregister::OnlyAssetsCreatedByThisAccount - .or(unregister::GrantedByAssetCreator) - .into(); - assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); - assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); - } - - #[test] - fn grant_unregister_of_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let permission_token_to_alice = PermissionToken::new( - unregister::CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - xor_id.clone().into(), - )], - ); - let mut domain = Domain::test("test"); - domain.asset_definitions.insert( - xor_id, - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - - let wsv = WorldStateView::::new(World::with(domains, [])); - let grant = Instruction::Grant(GrantBox { - object: permission_token_to_alice.into(), - destination_id: IdBox::AccountId(bob_id.clone()).into(), - }); - let validator: IsInstructionAllowedBoxed = - unregister::GrantRegisteredByMeAccess.into(); - assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); - assert!(validator.check(&bob_id, &grant, &wsv).is_err()); - } - - #[test] - fn mint_only_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let wsv = WorldStateView::::new(World::with( - [( - DomainId::test("test"), - Domain { - accounts: BTreeMap::new(), - id: DomainId::test("test"), - asset_definitions: [( - xor_id, - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone(), - }, - )] - .into(), - metadata: Metadata::new(), - }, - )], - [], - )); - let mint = Instruction::Mint(MintBox { - object: Value::U32(100).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - assert!(mint::OnlyAssetsCreatedByThisAccount - .check(&alice_id, &mint, &wsv) - .is_ok()); - assert!(mint::OnlyAssetsCreatedByThisAccount - .check(&bob_id, &mint, &wsv) - .is_err()); - } - - #[test] - fn mint_granted_assets() { - let alice_id = ::Id::test("alice", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let mut domain = Domain::test("test"); - let mut bob_account = Account::new(bob_id.clone()); - let _ = bob_account.permission_tokens.insert(PermissionToken::new( - mint::CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), - xor_id.clone().into(), - )], - )); - domain.accounts.insert(bob_id.clone(), bob_account); - domain.asset_definitions.insert( - xor_id, - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, [])); - let instruction = Instruction::Mint(MintBox { - object: Value::U32(100).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - let validator: IsInstructionAllowedBoxed = mint::OnlyAssetsCreatedByThisAccount - .or(mint::GrantedByAssetCreator) - .into(); - assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); - assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); - } - - #[test] - fn grant_mint_of_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let permission_token_to_alice = PermissionToken::new( - mint::CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - xor_id.clone().into(), - )], - ); - let mut domain = Domain::test("test"); - domain.asset_definitions.insert( - xor_id, - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, vec![])); - let grant = Instruction::Grant(GrantBox { - object: permission_token_to_alice.into(), - destination_id: IdBox::AccountId(bob_id.clone()).into(), - }); - let validator: IsInstructionAllowedBoxed = - mint::GrantRegisteredByMeAccess.into(); - assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); - assert!(validator.check(&bob_id, &grant, &wsv).is_err()); - } - - #[test] - fn burn_only_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let wsv = WorldStateView::::new(World::with( - [( - DomainId::test("test"), - Domain { - accounts: [].into(), - id: DomainId::test("test"), - asset_definitions: [( - xor_id, - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone(), - }, - )] - .into(), - metadata: Metadata::new(), - }, - )], - [], - )); - let burn = Instruction::Burn(BurnBox { - object: Value::U32(100).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - assert!(burn::OnlyAssetsCreatedByThisAccount - .check(&alice_id, &burn, &wsv) - .is_ok()); - assert!(burn::OnlyAssetsCreatedByThisAccount - .check(&bob_id, &burn, &wsv) - .is_err()); - } - - #[test] - fn burn_granted_asset_definition() { - let alice_id = ::Id::test("alice", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let mut domain = Domain::test("test"); - let mut bob_account = Account::new(bob_id.clone()); - let _ = bob_account.permission_tokens.insert(PermissionToken::new( - burn::CAN_BURN_ASSET_WITH_DEFINITION.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), - xor_id.clone().into(), - )], - )); - domain.accounts.insert(bob_id.clone(), bob_account); - domain.asset_definitions.insert( - xor_id, - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, vec![])); - let instruction = Instruction::Burn(BurnBox { - object: Value::U32(100).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - let validator: IsInstructionAllowedBoxed = burn::OnlyAssetsCreatedByThisAccount - .or(burn::GrantedByAssetCreator) - .into(); - assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); - assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); - } - - #[test] - fn grant_burn_of_assets_created_by_this_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let permission_token_to_alice = PermissionToken::new( - burn::CAN_BURN_ASSET_WITH_DEFINITION.clone(), - [( - ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), - xor_id.clone().into(), - )], - ); - let mut domain = Domain::test("test"); - domain.asset_definitions.insert( - xor_id, - AssetDefinitionEntry::new(xor_definition, alice_id.clone()), - ); - let domains = [(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, vec![])); - let grant = Instruction::Grant(GrantBox { - object: permission_token_to_alice.into(), - destination_id: IdBox::AccountId(bob_id.clone()).into(), - }); - let validator: IsInstructionAllowedBoxed = - burn::GrantRegisteredByMeAccess.into(); - assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); - assert!(validator.check(&bob_id, &grant, &wsv).is_err()); - } - - #[test] - fn burn_only_owned_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let wsv = WorldStateView::::new(World::new()); - let burn = Instruction::Burn(BurnBox { - object: Value::U32(100).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - assert!(burn::OnlyOwnedAssets.check(&alice_id, &burn, &wsv).is_ok()); - assert!(burn::OnlyOwnedAssets.check(&bob_id, &burn, &wsv).is_err()); - } - - #[test] - fn burn_granted_assets() -> Result<(), String> { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let mut domain = Domain::test("test"); - let mut bob_account = Account::new(bob_id.clone()); - let _ = bob_account.permission_tokens.insert(PermissionToken::new( - burn::CAN_BURN_USER_ASSETS_TOKEN.clone(), - [( - ASSET_ID_TOKEN_PARAM_NAME.clone(), - alice_xor_id.clone().into(), - )], - )); - domain.accounts.insert(bob_id.clone(), bob_account); - let domains = vec![(DomainId::test("test"), domain)]; - let wsv = WorldStateView::::new(World::with(domains, vec![])); - let transfer = Instruction::Burn(BurnBox { - object: Value::U32(10).into(), - destination_id: IdBox::AssetId(alice_xor_id).into(), - }); - let validator: IsInstructionAllowedBoxed = - burn::OnlyOwnedAssets.or(burn::GrantedByAssetOwner).into(); - validator.check(&alice_id, &transfer, &wsv)?; - assert!(validator.check(&bob_id, &transfer, &wsv).is_ok()); - Ok(()) - } - - #[test] - fn grant_burn_of_my_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let permission_token_to_alice = PermissionToken::new( - burn::CAN_BURN_USER_ASSETS_TOKEN.clone(), - [(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), alice_xor_id.into())], - ); - let wsv = WorldStateView::::new(World::new()); - let grant = Instruction::Grant(GrantBox { - object: permission_token_to_alice.into(), - destination_id: IdBox::AccountId(bob_id.clone()).into(), - }); - let validator: IsInstructionAllowedBoxed = burn::GrantMyAssetAccess.into(); - assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); - assert!(validator.check(&bob_id, &grant, &wsv).is_err()); - } - - #[test] - fn set_to_only_owned_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let wsv = WorldStateView::::new(World::new()); - let set = Instruction::SetKeyValue(SetKeyValueBox::new( - IdBox::AssetId(alice_xor_id), - Value::from("key".to_owned()), - Value::from("value".to_owned()), - )); - assert!(key_value::AssetSetOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AssetSetOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); - } - - #[test] - fn remove_to_only_owned_assets() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); - let wsv = WorldStateView::::new(World::new()); - let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( - IdBox::AssetId(alice_xor_id), - Value::from("key".to_owned()), - )); - assert!(key_value::AssetRemoveOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AssetRemoveOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); - } - - #[test] - fn set_to_only_owned_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let wsv = WorldStateView::::new(World::new()); - let set = Instruction::SetKeyValue(SetKeyValueBox::new( - IdBox::AccountId(alice_id.clone()), - Value::from("key".to_owned()), - Value::from("value".to_owned()), - )); - assert!(key_value::AccountSetOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AccountSetOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); - } - - #[test] - fn remove_to_only_owned_account() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let wsv = WorldStateView::::new(World::new()); - let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( - IdBox::AccountId(alice_id.clone()), - Value::from("key".to_owned()), - )); - assert!(key_value::AccountRemoveOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AccountRemoveOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); - } - - #[test] - fn set_to_only_owned_asset_definition() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let wsv = WorldStateView::::new(World::with( - [( - DomainId::test("test"), - Domain { - accounts: BTreeMap::new(), - id: DomainId::test("test"), - asset_definitions: [( - xor_id.clone(), - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone(), - }, - )] - .into(), - metadata: Metadata::new(), - }, - )], - [], - )); - let set = Instruction::SetKeyValue(SetKeyValueBox::new( - IdBox::AssetDefinitionId(xor_id), - Value::from("key".to_owned()), - Value::from("value".to_owned()), - )); - assert!(key_value::AssetDefinitionSetOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AssetDefinitionSetOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); - } - - #[test] - fn remove_to_only_owned_asset_definition() { - let alice_id = ::Id::test("alice", "test"); - let bob_id = ::Id::test("bob", "test"); - let xor_id = ::Id::test("xor", "test"); - let xor_definition = new_xor_definition(&xor_id); - let wsv = WorldStateView::::new(World::with( - [( - DomainId::test("test"), - Domain { - accounts: BTreeMap::new(), - id: DomainId::test("test"), - asset_definitions: [( - xor_id.clone(), - AssetDefinitionEntry { - definition: xor_definition, - registered_by: alice_id.clone(), - }, - )] - .into(), - metadata: Metadata::new(), - }, - )], - [], - )); - let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( - IdBox::AssetDefinitionId(xor_id), - Value::from("key".to_owned()), - )); - assert!(key_value::AssetDefinitionRemoveOnlyForSignerAccount - .check(&alice_id, &set, &wsv) - .is_ok()); - assert!(key_value::AssetDefinitionRemoveOnlyForSignerAccount - .check(&bob_id, &set, &wsv) - .is_err()); + return Ok(()); } - } + }; } + +// I need to put these modules after the macro definitions. +pub mod private_blockchain; +pub mod public_blockchain; diff --git a/permissions_validators/src/private_blockchain/mod.rs b/permissions_validators/src/private_blockchain/mod.rs new file mode 100644 index 00000000000..252f67691a5 --- /dev/null +++ b/permissions_validators/src/private_blockchain/mod.rs @@ -0,0 +1,40 @@ +//! Permission checks asociated with use cases that can be summarized as private blockchains (e.g. CBDC). + +use super::*; + +pub mod query; +pub mod register; + +/// A preconfigured set of permissions for simple use cases. +pub fn default_instructions_permissions() -> IsInstructionAllowedBoxed { + ValidatorBuilder::new() + .with_recursive_validator( + register::ProhibitRegisterDomains.or(register::GrantedAllowedRegisterDomains), + ) + .all_should_succeed() +} + +/// A preconfigured set of permissions for simple use cases. +pub fn default_query_permissions() -> IsQueryAllowedBoxed { + ValidatorBuilder::new().all_should_succeed() +} + +/// Prohibits using the [`Grant`] instruction at runtime. This means +/// `Grant` instruction will only be used in genesis to specify +/// rights. The rationale is that we don't want to be able to create a +/// super-user in a blockchain. +#[derive(Debug, Copy, Clone)] +pub struct ProhibitGrant; + +impl_from_item_for_grant_instruction_validator_box!(ProhibitGrant); + +impl IsGrantAllowed for ProhibitGrant { + fn check_grant( + &self, + _authority: &AccountId, + _instruction: &GrantBox, + _wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + Err("Granting at runtime is prohibited.".to_owned()) + } +} diff --git a/permissions_validators/src/private_blockchain/query.rs b/permissions_validators/src/private_blockchain/query.rs new file mode 100644 index 00000000000..7c0346cfdb2 --- /dev/null +++ b/permissions_validators/src/private_blockchain/query.rs @@ -0,0 +1,394 @@ +//! Query Permissions. + +use super::*; + +/// Allow queries that only access the data of the domain of the signer. +#[derive(Debug, Copy, Clone)] +pub struct OnlyAccountsDomain; + +impl IsAllowed for OnlyAccountsDomain { + #[allow(clippy::too_many_lines, clippy::match_same_arms)] + fn check( + &self, + authority: &AccountId, + query: &QueryBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + use QueryBox::*; + let context = Context::new(); + match query { + FindAssetsByAssetDefinitionId(_) | FindAssetsByName(_) | FindAllAssets(_) => { + Err("Only access to the assets of the same domain is permitted.".to_owned()) + } + FindAllAccounts(_) | FindAccountsByName(_) => { + Err("Only access to the accounts of the same domain is permitted.".to_owned()) + } + FindAllAssetsDefinitions(_) => Err( + "Only access to the asset definitions of the same domain is permitted.".to_owned(), + ), + FindAllDomains(_) => { + Err("Only access to the domain of the account is permitted.".to_owned()) + } + #[cfg(feature = "roles")] + FindAllRoles(_) => Ok(()), + FindAllPeers(_) => Ok(()), + FindAccountById(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + FindAccountKeyValueByIdAndKey(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + FindAccountsByDomainId(query) => { + let domain_id = query + .domain_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access accounts from a different domain with name {}.", + domain_id + )) + } + } + FindAssetById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + FindAssetsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + FindAssetsByDomainId(query) => { + let domain_id = query + .domain_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access assets from a different domain with name {}.", + domain_id + )) + } + } + FindAssetsByDomainIdAndAssetDefinitionId(query) => { + let domain_id = query + .domain_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access assets from a different domain with name {}.", + domain_id + )) + } + } + FindAssetDefinitionKeyValueByIdAndKey(query) => { + let asset_definition_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_definition_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access asset definition from a different domain. Asset definition domain: {}. Signers account domain {}.", + asset_definition_id.domain_id, + authority.domain_id + )) + } + } + FindAssetQuantityById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + FindAssetKeyValueByIdAndKey(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if asset_id.account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different domain.", + asset_id + )) + } + } + FindDomainById(query::FindDomainById { id }) + | FindDomainKeyValueByIdAndKey(query::FindDomainKeyValueByIdAndKey { id, .. }) => { + let domain_id = id.evaluate(wsv, &context).map_err(|err| err.to_string())?; + if domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!("Cannot access a different domain: {}.", domain_id)) + } + } + FindTransactionsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + FindTransactionByHash(_query) => Ok(()), + #[cfg(feature = "roles")] + FindRolesByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + FindPermissionTokensByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if account_id.domain_id == authority.domain_id { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as it is in a different domain.", + account_id + )) + } + } + } + } +} + +impl_from_item_for_query_validator_box!(OnlyAccountsDomain); + +/// Allow queries that only access the signers account data. +#[derive(Debug, Copy, Clone)] +pub struct OnlyAccountsData; + +impl IsAllowed for OnlyAccountsData { + #[allow(clippy::too_many_lines, clippy::match_same_arms)] + fn check( + &self, + authority: &AccountId, + query: &QueryBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + use QueryBox::*; + + let context = Context::new(); + match query { + FindAccountsByName(_) + | FindAccountsByDomainId(_) + | FindAllAccounts(_) + | FindAllAssetsDefinitions(_) + | FindAssetsByAssetDefinitionId(_) + | FindAssetsByDomainId(_) + | FindAssetsByName(_) + | FindAllDomains(_) + | FindDomainById(_) + | FindDomainKeyValueByIdAndKey(_) + | FindAssetsByDomainIdAndAssetDefinitionId(_) + | FindAssetDefinitionKeyValueByIdAndKey(_) + | FindAllAssets(_) => { + Err("Only access to the assets of the same domain is permitted.".to_owned()) + } + #[cfg(feature = "roles")] + FindAllRoles(_) => Ok(()), + FindAllPeers(_) => Ok(()), + FindAccountById(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as only access to your own account is permitted..", + account_id + )) + } + } + FindAccountKeyValueByIdAndKey(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access account {} as only access to your own account is permitted..", + account_id + )) + } + } + FindAssetById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + FindAssetsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access a different account: {}.", + account_id + )) + } + } + + FindAssetQuantityById(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + FindAssetKeyValueByIdAndKey(query) => { + let asset_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &asset_id.account_id == authority { + Ok(()) + } else { + Err(format!( + "Cannot access asset {} as it is in a different account.", + asset_id + )) + } + } + + FindTransactionsByAccountId(query) => { + let account_id = query + .account_id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + FindTransactionByHash(_query) => Ok(()), + #[cfg(feature = "roles")] + FindRolesByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + FindPermissionTokensByAccountId(query) => { + let account_id = query + .id + .evaluate(wsv, &context) + .map_err(|err| err.to_string())?; + if &account_id == authority { + Ok(()) + } else { + Err(format!("Cannot access another account: {}.", account_id)) + } + } + } + } +} + +impl_from_item_for_query_validator_box!(OnlyAccountsData); diff --git a/permissions_validators/src/private_blockchain/register.rs b/permissions_validators/src/private_blockchain/register.rs new file mode 100644 index 00000000000..cc01198a01a --- /dev/null +++ b/permissions_validators/src/private_blockchain/register.rs @@ -0,0 +1,49 @@ +//! Module with permissions for registering. + +use super::*; + +/// Can register domains permission token name. +pub static CAN_REGISTER_DOMAINS_TOKEN: Lazy = + Lazy::new(|| Name::test("can_register_domains")); + +/// Prohibits registering domains. +#[derive(Debug, Copy, Clone)] +pub struct ProhibitRegisterDomains; + +impl_from_item_for_instruction_validator_box!(ProhibitRegisterDomains); + +impl IsAllowed for ProhibitRegisterDomains { + fn check( + &self, + _authority: &AccountId, + instruction: &Instruction, + _wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let _register_box = if let Instruction::Register(register) = instruction { + register + } else { + return Ok(()); + }; + Err("Domain registration is prohibited.".to_owned()) + } +} + +/// Validator that allows to register domains for accounts with the corresponding permission token. +#[derive(Debug, Clone, Copy)] +pub struct GrantedAllowedRegisterDomains; + +impl_from_item_for_granted_token_validator_box!(GrantedAllowedRegisterDomains); + +impl HasToken for GrantedAllowedRegisterDomains { + fn token( + &self, + _authority: &AccountId, + _instruction: &Instruction, + _wsv: &WorldStateView, + ) -> Result { + Ok(PermissionToken::new( + CAN_REGISTER_DOMAINS_TOKEN.clone(), + BTreeMap::new(), + )) + } +} diff --git a/permissions_validators/src/public_blockchain/burn.rs b/permissions_validators/src/public_blockchain/burn.rs new file mode 100644 index 00000000000..978d90362cc --- /dev/null +++ b/permissions_validators/src/public_blockchain/burn.rs @@ -0,0 +1,205 @@ +//! Module with permission for burning + +use super::*; + +/// Can burn asset with the corresponding asset definition. +pub static CAN_BURN_ASSET_WITH_DEFINITION: Lazy = + Lazy::new(|| Name::test("can_burn_asset_with_definition")); +/// Can burn user's assets permission token name. +pub static CAN_BURN_USER_ASSETS_TOKEN: Lazy = + Lazy::new(|| Name::test("can_burn_user_assets")); + +/// Checks that account can burn only the assets which were registered by this account. +#[derive(Debug, Copy, Clone)] +pub struct OnlyAssetsCreatedByThisAccount; + +impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); + +impl IsAllowed for OnlyAssetsCreatedByThisAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let burn_box = if let Instruction::Burn(burn) = instruction { + burn + } else { + return Ok(()); + }; + let destination_id = burn_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_id: AssetId = try_into_or_exit!(destination_id); + let registered_by_signer_account = wsv + .asset_definition_entry(&asset_id.definition_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err("Can't burn assets registered by other accounts.".to_owned()); + } + Ok(()) + } +} + +/// Allows burning assets from a different account than the creator's of this asset if the corresponding user granted the permission token +/// for a specific asset. +#[derive(Debug, Clone, Copy)] +pub struct GrantedByAssetCreator; + +impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); + +impl HasToken for GrantedByAssetCreator { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let burn_box = if let Instruction::Burn(burn) = instruction { + burn + } else { + return Err("Instruction is not burn.".to_owned()); + }; + let destination_id = burn_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { + dest_id + } else { + return Err("Destination is not an Asset.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + asset_id.definition_id.into(), + ); + Ok(PermissionToken::new( + CAN_BURN_ASSET_WITH_DEFINITION.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantRegisteredByMeAccess; + +impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); + +impl IsGrantAllowed for GrantRegisteredByMeAccess { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_BURN_ASSET_WITH_DEFINITION.clone() { + return Err("Grant instruction is not for burn permission.".to_owned()); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +} + +/// Checks that account can burn only the assets that he currently owns. +#[derive(Debug, Copy, Clone)] +pub struct OnlyOwnedAssets; + +impl_from_item_for_instruction_validator_box!(OnlyOwnedAssets); + +impl IsAllowed for OnlyOwnedAssets { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let burn_box = if let Instruction::Burn(burn) = instruction { + burn + } else { + return Ok(()); + }; + let destination_id = burn_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_id: AssetId = try_into_or_exit!(destination_id); + if &asset_id.account_id != authority { + return Err("Can't burn assets from another account.".to_owned()); + } + Ok(()) + } +} + +/// Allows burning user's assets from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct GrantedByAssetOwner; + +impl_from_item_for_granted_token_validator_box!(GrantedByAssetOwner); + +impl HasToken for GrantedByAssetOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let burn_box = if let Instruction::Burn(burn_box) = instruction { + burn_box + } else { + return Err("Instruction is not burn.".to_owned()); + }; + let destination_id = burn_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let destination_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { + dest_id + } else { + return Err("Source id is not an AssetId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), destination_id.into()); + Ok(PermissionToken::new( + CAN_BURN_USER_ASSETS_TOKEN.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetAccess; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccess); + +impl IsGrantAllowed for GrantMyAssetAccess { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_BURN_USER_ASSETS_TOKEN.clone() { + return Err("Grant instruction is not for burn permission.".to_owned()); + } + check_asset_owner_for_token(&permission_token, authority)?; + Ok(()) + } +} diff --git a/permissions_validators/src/public_blockchain/key_value.rs b/permissions_validators/src/public_blockchain/key_value.rs new file mode 100644 index 00000000000..e517b9b9638 --- /dev/null +++ b/permissions_validators/src/public_blockchain/key_value.rs @@ -0,0 +1,621 @@ +//! Module with permission for burning + +use super::*; + +/// Can set key value in user's assets permission token name. +pub static CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN: Lazy = + Lazy::new(|| Name::test("can_set_key_value_in_user_assets")); +/// Can remove key value in user's assets permission token name. +pub static CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS: Lazy = + Lazy::new(|| Name::test("can_remove_key_value_in_user_assets")); +/// Can burn user's assets permission token name. +pub static CAN_SET_KEY_VALUE_IN_USER_METADATA: Lazy = + Lazy::new(|| Name::test("can_set_key_value_in_user_metadata")); +/// Can burn user's assets permission token name. +pub static CAN_REMOVE_KEY_VALUE_IN_USER_METADATA: Lazy = + Lazy::new(|| Name::test("can_remove_key_value_in_user_metadata")); +/// Can set key value in the corresponding asset definition. +pub static CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION: Lazy = + Lazy::new(|| Name::test("can_set_key_value_in_asset_definition")); +/// Can remove key value in the corresponding asset definition. +pub static CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION: Lazy = + Lazy::new(|| Name::test("can_remove_key_value_in_asset_definition")); +/// Target account id for setting and removing key value permission tokens. +pub static ACCOUNT_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("account_id")); + +/// Checks that account can set keys for assets only for the signer account. +#[derive(Debug, Copy, Clone)] +pub struct AssetSetOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AssetSetOnlyForSignerAccount); + +impl IsAllowed for AssetSetOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Ok(()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + + match object_id { + IdBox::AssetId(asset_id) if &asset_id.account_id != authority => { + Err("Can't set value to asset store from another account.".to_owned()) + } + _ => Ok(()), + } + } +} + +/// Allows setting user's assets key value map from a different account +/// if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct SetGrantedByAssetOwner; + +impl_from_item_for_granted_token_validator_box!(SetGrantedByAssetOwner); + +impl HasToken for SetGrantedByAssetOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Err("Instruction is not set.".to_owned()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AssetId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AssetId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); + Ok(PermissionToken::new( + CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetAccessSet; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccessSet); + +impl IsGrantAllowed for GrantMyAssetAccessSet { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_SET_KEY_VALUE_USER_ASSETS_TOKEN.clone() { + return Err("Grant instruction is not for set permission.".to_owned()); + } + check_asset_owner_for_token(&permission_token, authority)?; + Ok(()) + } +} + +/// Checks that account can set keys only the for signer account. +#[derive(Debug, Copy, Clone)] +pub struct AccountSetOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AccountSetOnlyForSignerAccount); + +impl IsAllowed for AccountSetOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Ok(()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + + match &object_id { + IdBox::AccountId(account_id) if account_id != authority => { + Err("Can't set value to account store from another account.".to_owned()) + } + _ => Ok(()), + } + } +} + +/// Allows setting user's metadata key value pairs from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct SetGrantedByAccountOwner; + +impl_from_item_for_granted_token_validator_box!(SetGrantedByAccountOwner); + +impl HasToken for SetGrantedByAccountOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Err("Instruction is not set.".to_owned()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AccountId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AccountId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ACCOUNT_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); + Ok(PermissionToken::new( + CAN_SET_KEY_VALUE_IN_USER_METADATA.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyMetadataAccessSet; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyMetadataAccessSet); + +impl IsGrantAllowed for GrantMyMetadataAccessSet { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_SET_KEY_VALUE_IN_USER_METADATA.clone() { + return Err("Grant instruction is not for set permission.".to_owned()); + } + check_account_owner_for_token(&permission_token, authority)?; + Ok(()) + } +} + +/// Checks that account can remove keys for assets only the for signer account. +#[derive(Debug, Copy, Clone)] +pub struct AssetRemoveOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AssetRemoveOnlyForSignerAccount); + +impl IsAllowed for AssetRemoveOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { + rem_kv + } else { + return Ok(()); + }; + let object_id = rem_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + match object_id { + IdBox::AssetId(asset_id) if &asset_id.account_id != authority => { + Err("Can't remove value from asset store from another account.".to_owned()) + } + _ => Ok(()), + } + } +} + +/// Allows removing user's assets key value pairs from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct RemoveGrantedByAssetOwner; + +impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAssetOwner); + +impl HasToken for RemoveGrantedByAssetOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { + rem_kv + } else { + return Err("Instruction is not set.".to_owned()); + }; + let object_id = rem_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AssetId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AssetId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); + Ok(PermissionToken::new( + CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetAccessRemove; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccessRemove); + +impl IsGrantAllowed for GrantMyAssetAccessRemove { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_USER_ASSETS.clone() { + return Err("Grant instruction is not for set permission.".to_owned()); + } + check_asset_owner_for_token(&permission_token, authority)?; + Ok(()) + } +} + +/// Checks that account can remove keys only the for signer account. +#[derive(Debug, Copy, Clone)] +pub struct AccountRemoveOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AccountRemoveOnlyForSignerAccount); + +impl IsAllowed for AccountRemoveOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { + rem_kv + } else { + return Ok(()); + }; + let object_id = rem_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + + match object_id { + IdBox::AccountId(account_id) if &account_id != authority => { + Err("Can't remove value from account store from another account.".to_owned()) + } + _ => Ok(()), + } + } +} + +/// Allows removing user's metadata key value pairs from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct RemoveGrantedByAccountOwner; + +impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAccountOwner); + +impl HasToken for RemoveGrantedByAccountOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { + rem_kv + } else { + return Err("Instruction is not remove.".to_owned()); + }; + let object_id = rem_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AccountId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AccountId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ACCOUNT_ID_TOKEN_PARAM_NAME.to_owned(), object_id.into()); + Ok(PermissionToken::new( + CAN_REMOVE_KEY_VALUE_IN_USER_METADATA.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the metadata +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyMetadataAccessRemove; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyMetadataAccessRemove); + +impl IsGrantAllowed for GrantMyMetadataAccessRemove { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_USER_METADATA.clone() { + return Err("Grant instruction is not for remove permission.".to_owned()); + } + check_account_owner_for_token(&permission_token, authority)?; + Ok(()) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets defintion +/// registered by signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetDefinitionSet; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetDefinitionSet); + +impl IsGrantAllowed for GrantMyAssetDefinitionSet { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION.clone() { + return Err( + "Grant instruction is not for set key value in asset definition permission." + .to_owned(), + ); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +} + +// Validator that checks Grant instruction so that the access is granted to the assets defintion +/// registered by signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetDefinitionRemove; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetDefinitionRemove); + +impl IsGrantAllowed for GrantMyAssetDefinitionRemove { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION.clone() { + return Err( + "Grant instruction is not for remove key value in asset definition permission." + .to_owned(), + ); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +} + +/// Checks that account can set keys for asset definitions only registered by the signer account. +#[derive(Debug, Copy, Clone)] +pub struct AssetDefinitionSetOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AssetDefinitionSetOnlyForSignerAccount); + +impl IsAllowed for AssetDefinitionSetOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Ok(()); + }; + let obj_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + + let object_id: AssetDefinitionId = try_into_or_exit!(obj_id); + let registered_by_signer_account = wsv + .asset_definition_entry(&object_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err( + "Can't set key value to asset definition registered by other accounts.".to_owned(), + ); + } + Ok(()) + } +} + +/// Checks that account can set keys for asset definitions only registered by the signer account. +#[derive(Debug, Copy, Clone)] +pub struct AssetDefinitionRemoveOnlyForSignerAccount; + +impl_from_item_for_instruction_validator_box!(AssetDefinitionRemoveOnlyForSignerAccount); + +impl IsAllowed for AssetDefinitionRemoveOnlyForSignerAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let rem_kv_box = if let Instruction::RemoveKeyValue(rem_kv) = instruction { + rem_kv + } else { + return Ok(()); + }; + let obj_id = rem_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + + let object_id: AssetDefinitionId = try_into_or_exit!(obj_id); + let registered_by_signer_account = wsv + .asset_definition_entry(&object_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err( + "Can't remove key value to asset definition registered by other accounts." + .to_owned(), + ); + } + Ok(()) + } +} + +/// Allows setting asset definition's metadata key value pairs from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct SetGrantedByAssetDefinitionOwner; + +impl_from_item_for_granted_token_validator_box!(SetGrantedByAssetDefinitionOwner); + +impl HasToken for SetGrantedByAssetDefinitionOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let set_kv_box = if let Instruction::SetKeyValue(set_kv) = instruction { + set_kv + } else { + return Err("Instruction is not set.".to_owned()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AssetDefinitionId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + object_id.into(), + ); + Ok(PermissionToken::new( + CAN_SET_KEY_VALUE_IN_ASSET_DEFINITION.clone(), + params, + )) + } +} + +/// Allows setting asset definition's metadata key value pairs from a different account if the corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct RemoveGrantedByAssetDefinitionOwner; + +impl_from_item_for_granted_token_validator_box!(RemoveGrantedByAssetDefinitionOwner); + +impl HasToken for RemoveGrantedByAssetDefinitionOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let set_kv_box = if let Instruction::RemoveKeyValue(set_kv) = instruction { + set_kv + } else { + return Err("Instruction is not remove key value.".to_owned()); + }; + let object_id = set_kv_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AssetDefinitionId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + object_id.into(), + ); + Ok(PermissionToken::new( + CAN_REMOVE_KEY_VALUE_IN_ASSET_DEFINITION.clone(), + params, + )) + } +} diff --git a/permissions_validators/src/public_blockchain/mint.rs b/permissions_validators/src/public_blockchain/mint.rs new file mode 100644 index 00000000000..990bc5e55c5 --- /dev/null +++ b/permissions_validators/src/public_blockchain/mint.rs @@ -0,0 +1,108 @@ +//! Module with permission for minting + +use super::*; + +/// Can mint asset with the corresponding asset definition. +pub static CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN: Lazy = + Lazy::new(|| Name::test("can_mint_user_asset_definitions")); + +/// Checks that account can mint only the assets which were registered by this account. +#[derive(Debug, Copy, Clone)] +pub struct OnlyAssetsCreatedByThisAccount; + +impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); + +impl IsAllowed for OnlyAssetsCreatedByThisAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let mint_box = if let Instruction::Mint(mint) = instruction { + mint + } else { + return Ok(()); + }; + let destination_id = mint_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_id: AssetId = try_into_or_exit!(destination_id); + let registered_by_signer_account = wsv + .asset_definition_entry(&asset_id.definition_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err("Can't mint assets registered by other accounts.".to_owned()); + } + Ok(()) + } +} + +/// Allows minting assets from a different account if the corresponding user granted the permission token +/// for a specific asset. +#[derive(Debug, Clone, Copy)] +pub struct GrantedByAssetCreator; + +impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); + +impl HasToken for GrantedByAssetCreator { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let mint_box = if let Instruction::Mint(mint) = instruction { + mint + } else { + return Err("Instruction is not mint.".to_owned()); + }; + let destination_id = mint_box + .destination_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_id: AssetId = if let Ok(dest_id) = destination_id.try_into() { + dest_id + } else { + return Err("Destination is not an Asset.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + asset_id.definition_id.into(), + ); + Ok(PermissionToken::new( + CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is granted to the assets +/// of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantRegisteredByMeAccess; + +impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); + +impl IsGrantAllowed for GrantRegisteredByMeAccess { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone() { + return Err("Grant instruction is not for mint permission.".to_owned()); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +} diff --git a/permissions_validators/src/public_blockchain/mod.rs b/permissions_validators/src/public_blockchain/mod.rs new file mode 100644 index 00000000000..a1889ffb460 --- /dev/null +++ b/permissions_validators/src/public_blockchain/mod.rs @@ -0,0 +1,751 @@ +//! Permission checks asociated with use cases that can be summarized as public blockchains. +use super::*; + +pub mod burn; +pub mod key_value; +pub mod mint; +pub mod transfer; +pub mod unregister; + +/// Origin asset id param used in permission tokens. +pub static ASSET_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("asset_id")); +/// Origin account id param used in permission tokens. +pub static ACCOUNT_ID_TOKEN_PARAM_NAME: Lazy = Lazy::new(|| Name::test("account_id")); +/// Origin asset definition param used in permission tokens. +pub static ASSET_DEFINITION_ID_TOKEN_PARAM_NAME: Lazy = + Lazy::new(|| Name::test("asset_definition_id")); + +/// A preconfigured set of permissions for simple use cases. +pub fn default_permissions() -> IsInstructionAllowedBoxed { + // Grant instruction checks are or unioned, so that if one permission validator approves this Grant it will succeed. + let grant_instruction_validator = ValidatorBuilder::new() + .with_validator(transfer::GrantMyAssetAccess) + .with_validator(unregister::GrantRegisteredByMeAccess) + .with_validator(mint::GrantRegisteredByMeAccess) + .with_validator(burn::GrantMyAssetAccess) + .with_validator(burn::GrantRegisteredByMeAccess) + .with_validator(key_value::GrantMyAssetAccessRemove) + .with_validator(key_value::GrantMyAssetAccessSet) + .with_validator(key_value::GrantMyMetadataAccessSet) + .with_validator(key_value::GrantMyMetadataAccessRemove) + .with_validator(key_value::GrantMyAssetDefinitionSet) + .with_validator(key_value::GrantMyAssetDefinitionRemove) + .any_should_succeed("Grant instruction validator."); + ValidatorBuilder::new() + .with_recursive_validator(grant_instruction_validator) + .with_recursive_validator(transfer::OnlyOwnedAssets.or(transfer::GrantedByAssetOwner)) + .with_recursive_validator( + unregister::OnlyAssetsCreatedByThisAccount.or(unregister::GrantedByAssetCreator), + ) + .with_recursive_validator( + mint::OnlyAssetsCreatedByThisAccount.or(mint::GrantedByAssetCreator), + ) + .with_recursive_validator(burn::OnlyOwnedAssets.or(burn::GrantedByAssetOwner)) + .with_recursive_validator( + burn::OnlyAssetsCreatedByThisAccount.or(burn::GrantedByAssetCreator), + ) + .with_recursive_validator( + key_value::AccountSetOnlyForSignerAccount.or(key_value::SetGrantedByAccountOwner), + ) + .with_recursive_validator( + key_value::AccountRemoveOnlyForSignerAccount.or(key_value::RemoveGrantedByAccountOwner), + ) + .with_recursive_validator( + key_value::AssetSetOnlyForSignerAccount.or(key_value::SetGrantedByAssetOwner), + ) + .with_recursive_validator( + key_value::AssetRemoveOnlyForSignerAccount.or(key_value::RemoveGrantedByAssetOwner), + ) + .with_recursive_validator( + key_value::AssetDefinitionSetOnlyForSignerAccount + .or(key_value::SetGrantedByAssetDefinitionOwner), + ) + .with_recursive_validator( + key_value::AssetDefinitionRemoveOnlyForSignerAccount + .or(key_value::RemoveGrantedByAssetDefinitionOwner), + ) + .all_should_succeed() +} + +/// Checks that `authority` is account owner for account supplied in `permission_token`. +/// +/// # Errors +/// - The `permission_token` is of improper format. +/// - Account owner is not `authority` +pub fn check_account_owner_for_token( + permission_token: &PermissionToken, + authority: &AccountId, +) -> Result<(), String> { + let account_id = if let Value::Id(IdBox::AccountId(account_id)) = permission_token + .params + .get(&ACCOUNT_ID_TOKEN_PARAM_NAME.clone()) + .ok_or(format!( + "Failed to find permission param {}.", + ACCOUNT_ID_TOKEN_PARAM_NAME.clone() + ))? { + account_id + } else { + return Err(format!( + "Permission param {} is not an AccountId.", + ACCOUNT_ID_TOKEN_PARAM_NAME.clone() + )); + }; + if account_id != authority { + return Err("Account specified in permission token is not owned by signer.".to_owned()); + } + Ok(()) +} + +/// Checks that `authority` is asset owner for asset supplied in `permission_token`. +/// +/// # Errors +/// - The `permission_token` is of improper format. +/// - Asset owner is not `authority` +pub fn check_asset_owner_for_token( + permission_token: &PermissionToken, + authority: &AccountId, +) -> Result<(), String> { + let asset_id = if let Value::Id(IdBox::AssetId(asset_id)) = permission_token + .params + .get(&ASSET_ID_TOKEN_PARAM_NAME.clone()) + .ok_or(format!( + "Failed to find permission param {}.", + ASSET_ID_TOKEN_PARAM_NAME.clone() + ))? { + asset_id + } else { + return Err(format!( + "Permission param {} is not an AssetId.", + ASSET_ID_TOKEN_PARAM_NAME.clone() + )); + }; + if &asset_id.account_id != authority { + return Err("Asset specified in permission token is not owned by signer.".to_owned()); + } + Ok(()) +} + +/// Checks that asset creator is `authority` in the supplied `permission_token`. +/// +/// # Errors +/// - The `permission_token` is of improper format. +/// - Asset creator is not `authority` +pub fn check_asset_creator_for_token( + permission_token: &PermissionToken, + authority: &AccountId, + wsv: &WorldStateView, +) -> Result<(), String> { + let definition_id = if let Value::Id(IdBox::AssetDefinitionId(definition_id)) = permission_token + .params + .get(&ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone()) + .ok_or(format!( + "Failed to find permission param {}.", + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone() + ))? { + definition_id + } else { + return Err(format!( + "Permission param {} is not an AssetDefinitionId.", + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone() + )); + }; + let registered_by_signer_account = wsv + .asset_definition_entry(definition_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err("Can not grant access for assets, registered by another account.".to_owned()); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + #![allow(clippy::restriction)] + + use std::collections::{BTreeMap, BTreeSet}; + + use iroha_core::wsv::World; + + use super::*; + + fn new_xor_definition(xor_id: &AssetDefinitionId) -> AssetDefinition { + AssetDefinition::new_quantity(xor_id.clone()) + } + + #[test] + fn transfer_only_owned_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_xor_id = ::Id::test("xor", "test", "bob", "test"); + let wsv = WorldStateView::::new(World::new()); + let transfer = Instruction::Transfer(TransferBox { + source_id: IdBox::AssetId(alice_xor_id).into(), + object: Value::U32(10).into(), + destination_id: IdBox::AssetId(bob_xor_id).into(), + }); + assert!(transfer::OnlyOwnedAssets + .check(&alice_id, &transfer, &wsv) + .is_ok()); + assert!(transfer::OnlyOwnedAssets + .check(&bob_id, &transfer, &wsv) + .is_err()); + } + + #[test] + fn transfer_granted_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_xor_id = ::Id::test("xor", "test", "bob", "test"); + let mut domain = Domain::test("test"); + let mut bob_account = Account::new(bob_id.clone()); + let _ = bob_account.permission_tokens.insert(PermissionToken::new( + transfer::CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), + [( + ASSET_ID_TOKEN_PARAM_NAME.clone(), + alice_xor_id.clone().into(), + )], + )); + domain.accounts.insert(bob_id.clone(), bob_account); + let domains = vec![(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, BTreeSet::new())); + let transfer = Instruction::Transfer(TransferBox { + source_id: IdBox::AssetId(alice_xor_id).into(), + object: Value::U32(10).into(), + destination_id: IdBox::AssetId(bob_xor_id).into(), + }); + let validator: IsInstructionAllowedBoxed = transfer::OnlyOwnedAssets + .or(transfer::GrantedByAssetOwner) + .into(); + assert!(validator.check(&alice_id, &transfer, &wsv).is_ok()); + assert!(validator.check(&bob_id, &transfer, &wsv).is_ok()); + } + + #[test] + fn grant_transfer_of_my_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let permission_token_to_alice = PermissionToken::new( + transfer::CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), + [(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), alice_xor_id.into())], + ); + let wsv = WorldStateView::::new(World::new()); + let grant = Instruction::Grant(GrantBox { + object: permission_token_to_alice.into(), + destination_id: IdBox::AccountId(bob_id.clone()).into(), + }); + let validator: IsInstructionAllowedBoxed = transfer::GrantMyAssetAccess.into(); + assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); + assert!(validator.check(&bob_id, &grant, &wsv).is_err()); + } + + #[test] + fn unregister_only_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let wsv = WorldStateView::::new(World::with( + [( + DomainId::test("test"), + Domain { + accounts: BTreeMap::new(), + id: DomainId::test("test"), + asset_definitions: [( + xor_id.clone(), + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone(), + }, + )] + .into(), + metadata: Metadata::new(), + }, + )], + [], + )); + let unregister = + Instruction::Unregister(UnregisterBox::new(IdBox::AssetDefinitionId(xor_id))); + assert!(unregister::OnlyAssetsCreatedByThisAccount + .check(&alice_id, &unregister, &wsv) + .is_ok()); + assert!(unregister::OnlyAssetsCreatedByThisAccount + .check(&bob_id, &unregister, &wsv) + .is_err()); + } + + #[test] + fn unregister_granted_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let mut domain = Domain::test("test"); + let mut bob_account = Account::new(bob_id.clone()); + let _ = bob_account.permission_tokens.insert(PermissionToken::new( + unregister::CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), + xor_id.clone().into(), + )], + )); + domain.accounts.insert(bob_id.clone(), bob_account); + domain.asset_definitions.insert( + xor_id.clone(), + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, [])); + let instruction = Instruction::Unregister(UnregisterBox::new(xor_id)); + let validator: IsInstructionAllowedBoxed = + unregister::OnlyAssetsCreatedByThisAccount + .or(unregister::GrantedByAssetCreator) + .into(); + assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); + assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); + } + + #[test] + fn grant_unregister_of_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let permission_token_to_alice = PermissionToken::new( + unregister::CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + xor_id.clone().into(), + )], + ); + let mut domain = Domain::test("test"); + domain.asset_definitions.insert( + xor_id, + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + + let wsv = WorldStateView::::new(World::with(domains, [])); + let grant = Instruction::Grant(GrantBox { + object: permission_token_to_alice.into(), + destination_id: IdBox::AccountId(bob_id.clone()).into(), + }); + let validator: IsInstructionAllowedBoxed = + unregister::GrantRegisteredByMeAccess.into(); + assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); + assert!(validator.check(&bob_id, &grant, &wsv).is_err()); + } + + #[test] + fn mint_only_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let wsv = WorldStateView::::new(World::with( + [( + DomainId::test("test"), + Domain { + accounts: BTreeMap::new(), + id: DomainId::test("test"), + asset_definitions: [( + xor_id, + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone(), + }, + )] + .into(), + metadata: Metadata::new(), + }, + )], + [], + )); + let mint = Instruction::Mint(MintBox { + object: Value::U32(100).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + assert!(mint::OnlyAssetsCreatedByThisAccount + .check(&alice_id, &mint, &wsv) + .is_ok()); + assert!(mint::OnlyAssetsCreatedByThisAccount + .check(&bob_id, &mint, &wsv) + .is_err()); + } + + #[test] + fn mint_granted_assets() { + let alice_id = ::Id::test("alice", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let mut domain = Domain::test("test"); + let mut bob_account = Account::new(bob_id.clone()); + let _ = bob_account.permission_tokens.insert(PermissionToken::new( + mint::CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), + xor_id.clone().into(), + )], + )); + domain.accounts.insert(bob_id.clone(), bob_account); + domain.asset_definitions.insert( + xor_id, + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, [])); + let instruction = Instruction::Mint(MintBox { + object: Value::U32(100).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + let validator: IsInstructionAllowedBoxed = mint::OnlyAssetsCreatedByThisAccount + .or(mint::GrantedByAssetCreator) + .into(); + assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); + assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); + } + + #[test] + fn grant_mint_of_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let permission_token_to_alice = PermissionToken::new( + mint::CAN_MINT_USER_ASSET_DEFINITIONS_TOKEN.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + xor_id.clone().into(), + )], + ); + let mut domain = Domain::test("test"); + domain.asset_definitions.insert( + xor_id, + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, vec![])); + let grant = Instruction::Grant(GrantBox { + object: permission_token_to_alice.into(), + destination_id: IdBox::AccountId(bob_id.clone()).into(), + }); + let validator: IsInstructionAllowedBoxed = mint::GrantRegisteredByMeAccess.into(); + assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); + assert!(validator.check(&bob_id, &grant, &wsv).is_err()); + } + + #[test] + fn burn_only_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let wsv = WorldStateView::::new(World::with( + [( + DomainId::test("test"), + Domain { + accounts: [].into(), + id: DomainId::test("test"), + asset_definitions: [( + xor_id, + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone(), + }, + )] + .into(), + metadata: Metadata::new(), + }, + )], + [], + )); + let burn = Instruction::Burn(BurnBox { + object: Value::U32(100).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + assert!(burn::OnlyAssetsCreatedByThisAccount + .check(&alice_id, &burn, &wsv) + .is_ok()); + assert!(burn::OnlyAssetsCreatedByThisAccount + .check(&bob_id, &burn, &wsv) + .is_err()); + } + + #[test] + fn burn_granted_asset_definition() { + let alice_id = ::Id::test("alice", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let mut domain = Domain::test("test"); + let mut bob_account = Account::new(bob_id.clone()); + let _ = bob_account.permission_tokens.insert(PermissionToken::new( + burn::CAN_BURN_ASSET_WITH_DEFINITION.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), + xor_id.clone().into(), + )], + )); + domain.accounts.insert(bob_id.clone(), bob_account); + domain.asset_definitions.insert( + xor_id, + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, vec![])); + let instruction = Instruction::Burn(BurnBox { + object: Value::U32(100).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + let validator: IsInstructionAllowedBoxed = burn::OnlyAssetsCreatedByThisAccount + .or(burn::GrantedByAssetCreator) + .into(); + assert!(validator.check(&alice_id, &instruction, &wsv).is_ok()); + assert!(validator.check(&bob_id, &instruction, &wsv).is_ok()); + } + + #[test] + fn grant_burn_of_assets_created_by_this_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let permission_token_to_alice = PermissionToken::new( + burn::CAN_BURN_ASSET_WITH_DEFINITION.clone(), + [( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.to_owned(), + xor_id.clone().into(), + )], + ); + let mut domain = Domain::test("test"); + domain.asset_definitions.insert( + xor_id, + AssetDefinitionEntry::new(xor_definition, alice_id.clone()), + ); + let domains = [(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, vec![])); + let grant = Instruction::Grant(GrantBox { + object: permission_token_to_alice.into(), + destination_id: IdBox::AccountId(bob_id.clone()).into(), + }); + let validator: IsInstructionAllowedBoxed = burn::GrantRegisteredByMeAccess.into(); + assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); + assert!(validator.check(&bob_id, &grant, &wsv).is_err()); + } + + #[test] + fn burn_only_owned_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let wsv = WorldStateView::::new(World::new()); + let burn = Instruction::Burn(BurnBox { + object: Value::U32(100).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + assert!(burn::OnlyOwnedAssets.check(&alice_id, &burn, &wsv).is_ok()); + assert!(burn::OnlyOwnedAssets.check(&bob_id, &burn, &wsv).is_err()); + } + + #[test] + fn burn_granted_assets() -> Result<(), String> { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let mut domain = Domain::test("test"); + let mut bob_account = Account::new(bob_id.clone()); + let _ = bob_account.permission_tokens.insert(PermissionToken::new( + burn::CAN_BURN_USER_ASSETS_TOKEN.clone(), + [( + ASSET_ID_TOKEN_PARAM_NAME.clone(), + alice_xor_id.clone().into(), + )], + )); + domain.accounts.insert(bob_id.clone(), bob_account); + let domains = vec![(DomainId::test("test"), domain)]; + let wsv = WorldStateView::::new(World::with(domains, vec![])); + let transfer = Instruction::Burn(BurnBox { + object: Value::U32(10).into(), + destination_id: IdBox::AssetId(alice_xor_id).into(), + }); + let validator: IsInstructionAllowedBoxed = + burn::OnlyOwnedAssets.or(burn::GrantedByAssetOwner).into(); + validator.check(&alice_id, &transfer, &wsv)?; + assert!(validator.check(&bob_id, &transfer, &wsv).is_ok()); + Ok(()) + } + + #[test] + fn grant_burn_of_my_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let permission_token_to_alice = PermissionToken::new( + burn::CAN_BURN_USER_ASSETS_TOKEN.clone(), + [(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), alice_xor_id.into())], + ); + let wsv = WorldStateView::::new(World::new()); + let grant = Instruction::Grant(GrantBox { + object: permission_token_to_alice.into(), + destination_id: IdBox::AccountId(bob_id.clone()).into(), + }); + let validator: IsInstructionAllowedBoxed = burn::GrantMyAssetAccess.into(); + assert!(validator.check(&alice_id, &grant, &wsv).is_ok()); + assert!(validator.check(&bob_id, &grant, &wsv).is_err()); + } + + #[test] + fn set_to_only_owned_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let wsv = WorldStateView::::new(World::new()); + let set = Instruction::SetKeyValue(SetKeyValueBox::new( + IdBox::AssetId(alice_xor_id), + Value::from("key".to_owned()), + Value::from("value".to_owned()), + )); + assert!(key_value::AssetSetOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AssetSetOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } + + #[test] + fn remove_to_only_owned_assets() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let alice_xor_id = ::Id::test("xor", "test", "alice", "test"); + let wsv = WorldStateView::::new(World::new()); + let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( + IdBox::AssetId(alice_xor_id), + Value::from("key".to_owned()), + )); + assert!(key_value::AssetRemoveOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AssetRemoveOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } + + #[test] + fn set_to_only_owned_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let wsv = WorldStateView::::new(World::new()); + let set = Instruction::SetKeyValue(SetKeyValueBox::new( + IdBox::AccountId(alice_id.clone()), + Value::from("key".to_owned()), + Value::from("value".to_owned()), + )); + assert!(key_value::AccountSetOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AccountSetOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } + + #[test] + fn remove_to_only_owned_account() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let wsv = WorldStateView::::new(World::new()); + let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( + IdBox::AccountId(alice_id.clone()), + Value::from("key".to_owned()), + )); + assert!(key_value::AccountRemoveOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AccountRemoveOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } + + #[test] + fn set_to_only_owned_asset_definition() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let wsv = WorldStateView::::new(World::with( + [( + DomainId::test("test"), + Domain { + accounts: BTreeMap::new(), + id: DomainId::test("test"), + asset_definitions: [( + xor_id.clone(), + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone(), + }, + )] + .into(), + metadata: Metadata::new(), + }, + )], + [], + )); + let set = Instruction::SetKeyValue(SetKeyValueBox::new( + IdBox::AssetDefinitionId(xor_id), + Value::from("key".to_owned()), + Value::from("value".to_owned()), + )); + assert!(key_value::AssetDefinitionSetOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AssetDefinitionSetOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } + + #[test] + fn remove_to_only_owned_asset_definition() { + let alice_id = ::Id::test("alice", "test"); + let bob_id = ::Id::test("bob", "test"); + let xor_id = ::Id::test("xor", "test"); + let xor_definition = new_xor_definition(&xor_id); + let wsv = WorldStateView::::new(World::with( + [( + DomainId::test("test"), + Domain { + accounts: BTreeMap::new(), + id: DomainId::test("test"), + asset_definitions: [( + xor_id.clone(), + AssetDefinitionEntry { + definition: xor_definition, + registered_by: alice_id.clone(), + }, + )] + .into(), + metadata: Metadata::new(), + }, + )], + [], + )); + let set = Instruction::RemoveKeyValue(RemoveKeyValueBox::new( + IdBox::AssetDefinitionId(xor_id), + Value::from("key".to_owned()), + )); + assert!(key_value::AssetDefinitionRemoveOnlyForSignerAccount + .check(&alice_id, &set, &wsv) + .is_ok()); + assert!(key_value::AssetDefinitionRemoveOnlyForSignerAccount + .check(&bob_id, &set, &wsv) + .is_err()); + } +} diff --git a/permissions_validators/src/public_blockchain/transfer.rs b/permissions_validators/src/public_blockchain/transfer.rs new file mode 100644 index 00000000000..10f1a707450 --- /dev/null +++ b/permissions_validators/src/public_blockchain/transfer.rs @@ -0,0 +1,102 @@ +//! Module with permission for transfering + +use super::*; + +/// Can transfer user's assets permission token name. +pub static CAN_TRANSFER_USER_ASSETS_TOKEN: Lazy = + Lazy::new(|| Name::test("can_transfer_user_assets")); + +/// Checks that account transfers only the assets that he owns. +#[derive(Debug, Copy, Clone)] +pub struct OnlyOwnedAssets; + +impl_from_item_for_instruction_validator_box!(OnlyOwnedAssets); + +impl IsAllowed for OnlyOwnedAssets { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let transfer_box = if let Instruction::Transfer(transfer) = instruction { + transfer + } else { + return Ok(()); + }; + let source_id = transfer_box + .source_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let source_id: AssetId = try_into_or_exit!(source_id); + + if &source_id.account_id != authority { + return Err("Can't transfer assets of the other account.".to_owned()); + } + Ok(()) + } +} + +/// Allows transfering user's assets from a different account if the +/// corresponding user granted this permission token. +#[derive(Debug, Clone, Copy)] +pub struct GrantedByAssetOwner; + +impl_from_item_for_granted_token_validator_box!(GrantedByAssetOwner); + +impl HasToken for GrantedByAssetOwner { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let transfer_box = if let Instruction::Transfer(transfer_box) = instruction { + transfer_box + } else { + return Err("Instruction is not transfer.".to_owned()); + }; + let source_id = transfer_box + .source_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let source_id: AssetId = if let Ok(id) = source_id.try_into() { + id + } else { + return Err("Source id is not an AssetId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert(ASSET_ID_TOKEN_PARAM_NAME.to_owned(), source_id.into()); + Ok(PermissionToken::new( + CAN_TRANSFER_USER_ASSETS_TOKEN.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is +/// granted to the assets of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantMyAssetAccess; + +impl_from_item_for_grant_instruction_validator_box!(GrantMyAssetAccess); + +impl IsGrantAllowed for GrantMyAssetAccess { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_TRANSFER_USER_ASSETS_TOKEN.clone() { + return Err("Grant instruction is not for transfer permission.".to_owned()); + } + check_asset_owner_for_token(&permission_token, authority) + } +} diff --git a/permissions_validators/src/public_blockchain/unregister.rs b/permissions_validators/src/public_blockchain/unregister.rs new file mode 100644 index 00000000000..099d94cd959 --- /dev/null +++ b/permissions_validators/src/public_blockchain/unregister.rs @@ -0,0 +1,138 @@ +//! Module with permission for unregistering + +use super::*; + +/// Can un-register asset with the corresponding asset definition. +pub static CAN_UNREGISTER_ASSET_WITH_DEFINITION: Lazy = + Lazy::new(|| Name::test("can_unregister_asset_with_definition")); + +/// Checks that account can un-register only the assets which were +/// registered by this account in the first place. +#[derive(Debug, Copy, Clone)] +pub struct OnlyAssetsCreatedByThisAccount; + +impl_from_item_for_instruction_validator_box!(OnlyAssetsCreatedByThisAccount); + +impl IsAllowed for OnlyAssetsCreatedByThisAccount { + fn check( + &self, + authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let unregister_box = if let Instruction::Unregister(unregister) = instruction { + unregister + } else { + return Ok(()); + }; + let object_id = unregister_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let asset_definition_id: AssetDefinitionId = try_into_or_exit!(object_id); + let registered_by_signer_account = wsv + .asset_definition_entry(&asset_definition_id) + .map(|asset_definition_entry| &asset_definition_entry.registered_by == authority) + .unwrap_or(false); + if !registered_by_signer_account { + return Err("Can't unregister assets registered by other accounts.".to_owned()); + } + Ok(()) + } +} + +/// Allows un-registering a user's assets from a different account if +/// the corresponding user granted the permission token for a specific +/// asset. +#[derive(Debug, Clone, Copy)] +pub struct GrantedByAssetCreator; + +impl_from_item_for_granted_token_validator_box!(GrantedByAssetCreator); + +impl HasToken for GrantedByAssetCreator { + fn token( + &self, + _authority: &AccountId, + instruction: &Instruction, + wsv: &WorldStateView, + ) -> Result { + let unregister_box = if let Instruction::Unregister(unregister) = instruction { + unregister + } else { + return Err("Instruction is not unregister.".to_owned()); + }; + let object_id = unregister_box + .object_id + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())?; + let object_id: AssetDefinitionId = if let Ok(obj_id) = object_id.try_into() { + obj_id + } else { + return Err("Source id is not an AssetDefinitionId.".to_owned()); + }; + let mut params = BTreeMap::new(); + params.insert( + ASSET_DEFINITION_ID_TOKEN_PARAM_NAME.clone(), + object_id.into(), + ); + Ok(PermissionToken::new( + CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone(), + params, + )) + } +} + +/// Validator that checks Grant instruction so that the access is +/// granted to the assets of the signer account. +#[derive(Debug, Clone, Copy)] +pub struct GrantRegisteredByMeAccess; + +impl_from_item_for_grant_instruction_validator_box!(GrantRegisteredByMeAccess); + +impl IsGrantAllowed for GrantRegisteredByMeAccess { + fn check_grant( + &self, + authority: &AccountId, + instruction: &GrantBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone() { + return Err("Grant instruction is not for unregister permission.".to_owned()); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +} + +/// Validator that checks Revoke instructions, such that the access is +/// revoked and the assets of the signer's account are no longer +/// accessible. +#[derive(Debug, Clone, Copy)] +pub struct RevokeRegisteredByMeAccess; + +impl_from_item_for_revoke_instruction_validator_box!(RevokeRegisteredByMeAccess); + +impl IsRevokeAllowed for RevokeRegisteredByMeAccess { + fn check_revoke( + &self, + authority: &AccountId, + instruction: &RevokeBox, + wsv: &WorldStateView, + ) -> Result<(), DenialReason> { + let permission_token: PermissionToken = instruction + .object + .evaluate(wsv, &Context::new()) + .map_err(|e| e.to_string())? + .try_into() + .map_err(|e: ErrorTryFromEnum<_, _>| e.to_string())?; + if permission_token.name != CAN_UNREGISTER_ASSET_WITH_DEFINITION.clone() { + return Err("Revoke instruction is not for unregister permission.".to_owned()); + } + check_asset_creator_for_token(&permission_token, authority, wsv) + } +}