diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 6b14f2bc..252d4e7a 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -217,6 +217,9 @@ class database_api_impl : public std::enable_shared_from_this vector get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const; vector get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const; + // Account Role + vector get_account_roles_by_owner(account_id_type owner) const; + //private: const account_object* get_account_from_string( const std::string& name_or_id, bool throw_if_not_found = true ) const; @@ -2888,6 +2891,23 @@ vector database_api_impl::get_offer_history_by_bidder(cons return result; } + +vector database_api::get_account_roles_by_owner(account_id_type owner) const +{ + return my->get_account_roles_by_owner(owner); +} + +vector database_api_impl::get_account_roles_by_owner(account_id_type owner) const +{ + const auto &idx_aro = _db.get_index_type().indices().get(); + auto idx_aro_range = idx_aro.equal_range(owner); + vector result; + for (auto itr = idx_aro_range.first; itr != idx_aro_range.second; ++itr) + { + result.push_back(*itr); + } + return result; +} ////////////////////////////////////////////////////////////////////// // // // Private methods // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 0b141125..9f4a625b 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -829,6 +830,11 @@ class database_api vector get_offer_history_by_issuer(const offer_history_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const; vector get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const; vector get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const; + + ////////////////// + // ACCOUNT ROLE // + ////////////////// + vector get_account_roles_by_owner(account_id_type owner) const; private: std::shared_ptr< database_api_impl > my; }; @@ -999,4 +1005,7 @@ FC_API(graphene::app::database_api, (get_offer_history_by_issuer) (get_offer_history_by_item) (get_offer_history_by_bidder) + + // Account Roles + (get_account_roles_by_owner) ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 88d868e5..8fda8a15 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -124,6 +124,8 @@ add_library( graphene_chain offer_evaluator.cpp nft_evaluator.cpp protocol/nft.cpp + protocol/account_role.cpp + account_role_evaluator.cpp ${HEADERS} ${PROTOCOL_HEADERS} diff --git a/libraries/chain/account_role_evaluator.cpp b/libraries/chain/account_role_evaluator.cpp new file mode 100644 index 00000000..bdb82a22 --- /dev/null +++ b/libraries/chain/account_role_evaluator.cpp @@ -0,0 +1,147 @@ +#include + +#include +#include +#include +#include + +namespace graphene +{ + namespace chain + { + + void_result account_role_create_evaluator::do_evaluate(const account_role_create_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + + rbac_operation_hardfork_visitor arvtor(now); + for (const auto &op_type : op.allowed_operations) + { + arvtor(op_type); + } + + for (const auto &acc : op.whitelisted_accounts) + { + acc(d); + } + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + object_id_type account_role_create_evaluator::do_apply(const account_role_create_operation &op) + { + try + { + database &d = db(); + return d.create([&op](account_role_object &obj) mutable { + obj.owner = op.owner; + obj.name = op.name; + obj.metadata = op.metadata; + obj.allowed_operations = op.allowed_operations; + obj.whitelisted_accounts = op.whitelisted_accounts; + }) + .id; + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_update_evaluator::do_evaluate(const account_role_update_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + const account_role_object &aobj = op.account_role_id(d); + FC_ASSERT(aobj.owner == op.owner, "Only owner account can update account role object"); + + for (const auto &op_type : op.allowed_operations_to_remove) + { + FC_ASSERT(aobj.allowed_operations.find(op_type) != aobj.allowed_operations.end(), + "Cannot remove non existent operation"); + } + + for (const auto &acc : op.accounts_to_remove) + { + FC_ASSERT(aobj.whitelisted_accounts.find(acc) != aobj.whitelisted_accounts.end(), + "Cannot remove non existent account"); + } + + rbac_operation_hardfork_visitor arvtor(now); + for (const auto &op_type : op.allowed_operations_to_add) + { + arvtor(op_type); + } + FC_ASSERT((aobj.allowed_operations.size() + op.allowed_operations_to_add.size() - op.allowed_operations_to_remove.size()) > 0, "Allowed operations should be positive"); + + for (const auto &acc : op.accounts_to_add) + { + acc(d); + } + FC_ASSERT((aobj.whitelisted_accounts.size() + op.accounts_to_add.size() - op.accounts_to_remove.size()) > 0, "Accounts should be positive"); + + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_update_evaluator::do_apply(const account_role_update_operation &op) + { + try + { + database &d = db(); + const account_role_object &aobj = op.account_role_id(d); + d.modify(aobj, [&op](account_role_object &obj) { + if (op.name) + obj.name = *op.name; + if (op.metadata) + obj.metadata = *op.metadata; + obj.allowed_operations.insert(op.allowed_operations_to_add.begin(), op.allowed_operations_to_add.end()); + obj.whitelisted_accounts.insert(op.accounts_to_add.begin(), op.accounts_to_add.end()); + for (const auto &op_type : op.allowed_operations_to_remove) + obj.allowed_operations.erase(op_type); + for (const auto &acc : op.accounts_to_remove) + obj.whitelisted_accounts.erase(acc); + }); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_delete_evaluator::do_evaluate(const account_role_delete_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(d); + const account_role_object &aobj = op.account_role_id(d); + FC_ASSERT(aobj.owner == op.owner, "Only owner account can delete account role object"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result account_role_delete_evaluator::do_apply(const account_role_delete_operation &op) + { + try + { + database &d = db(); + const account_role_object &aobj = op.account_role_id(d); + // TODO: Remove from Resource Objects + d.remove(aobj); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/custom_account_authority_evaluator.cpp b/libraries/chain/custom_account_authority_evaluator.cpp index 200590f6..f3a1af18 100644 --- a/libraries/chain/custom_account_authority_evaluator.cpp +++ b/libraries/chain/custom_account_authority_evaluator.cpp @@ -4,37 +4,13 @@ #include #include #include +#include namespace graphene { namespace chain { -struct rbac_operation_hardfork_visitor -{ - typedef void result_type; - const fc::time_point_sec block_time; - - rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {} - void operator()(int op_type) const - { - int first_allowed_op = operation::tag::value; - switch (op_type) - { - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - case operation::tag::value: - FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permission not allowed on this operation yet!"); - break; - default: - FC_ASSERT(op_type < first_allowed_op, "Custom permission not allowed on this operation!"); - } - } -}; - void_result create_custom_account_authority_evaluator::do_evaluate(const custom_account_authority_create_operation &op) { try diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 0f7af1a8..71710695 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -192,4 +193,10 @@ bool database::item_locked(const nft_id_type &item) const auto items_itr = market_items._locked_items.find(item); return (items_itr != market_items._locked_items.end()); } + +bool database::account_role_valid(const account_role_object& aro, account_id_type account, optional op_type) const +{ + return (aro.whitelisted_accounts.find(account) != aro.whitelisted_accounts.end()) && + (!op_type || (aro.allowed_operations.find(*op_type) != aro.allowed_operations.end())); +} } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 9ae1fb96..84203e4a 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -85,6 +86,7 @@ #include #include #include +#include #include @@ -186,6 +188,9 @@ const uint8_t offer_object::type_id; const uint8_t offer_history_object::space_id; const uint8_t offer_history_object::type_id; +const uint8_t account_role_object::space_id; +const uint8_t account_role_object::type_id; + void database::initialize_evaluators() { _operation_evaluators.resize(255); @@ -275,6 +280,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -323,6 +331,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index f56e3d8b..6300e685 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -344,6 +344,15 @@ struct get_impacted_account_visitor void operator()( const finalize_offer_operation& op ) { _impacted.insert( op.fee_paying_account ); } + void operator()( const account_role_create_operation& op ){ + _impacted.insert( op.owner ); + } + void operator()( const account_role_update_operation& op ){ + _impacted.insert( op.owner ); + } + void operator()( const account_role_delete_operation& op ){ + _impacted.insert( op.owner ); + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) @@ -432,6 +441,12 @@ void get_relevant_accounts( const object* obj, flat_set& accoun } case balance_object_type:{ /** these are free from any accounts */ break; + } case account_role_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->owner ); + accounts.insert( aobj->whitelisted_accounts.begin(), aobj->whitelisted_accounts.end() ); + break; } } } diff --git a/libraries/chain/include/graphene/chain/account_role_evaluator.hpp b/libraries/chain/include/graphene/chain/account_role_evaluator.hpp new file mode 100644 index 00000000..29c4ada6 --- /dev/null +++ b/libraries/chain/include/graphene/chain/account_role_evaluator.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + + class account_role_create_evaluator : public evaluator + { + public: + typedef account_role_create_operation operation_type; + void_result do_evaluate( const account_role_create_operation& o ); + object_id_type do_apply( const account_role_create_operation& o ); + }; + + class account_role_update_evaluator : public evaluator + { + public: + typedef account_role_update_operation operation_type; + void_result do_evaluate( const account_role_update_operation& o ); + void_result do_apply( const account_role_update_operation& o ); + }; + + class account_role_delete_evaluator : public evaluator + { + public: + typedef account_role_delete_operation operation_type; + void_result do_evaluate( const account_role_delete_operation& o ); + void_result do_apply( const account_role_delete_operation& o ); + }; + +} } // graphene::chain + diff --git a/libraries/chain/include/graphene/chain/account_role_object.hpp b/libraries/chain/include/graphene/chain/account_role_object.hpp new file mode 100644 index 00000000..7ba14301 --- /dev/null +++ b/libraries/chain/include/graphene/chain/account_role_object.hpp @@ -0,0 +1,42 @@ +#pragma once +#include +#include +#include + +namespace graphene +{ + namespace chain + { + using namespace graphene::db; + + class account_role_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = account_role_type; + + account_id_type owner; + std::string name; + std::string metadata; + flat_set allowed_operations; + flat_set whitelisted_accounts; + }; + + struct by_owner; + using account_role_multi_index_type = multi_index_container< + account_role_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + > + > + >; + using account_role_index = generic_index; + } // namespace chain +} // namespace graphene + +FC_REFLECT_DERIVED(graphene::chain::account_role_object, (graphene::db::object), + (owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 8ecb4b91..b513ac7c 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -283,6 +283,7 @@ namespace graphene { namespace chain { uint64_t get_random_bits( uint64_t bound ); const witness_schedule_object& get_witness_schedule_object()const; bool item_locked(const nft_id_type& item)const; + bool account_role_valid(const account_role_object& aro, account_id_type account, optional op_type = optional()) const; time_point_sec head_block_time()const; uint32_t head_block_num()const; diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 1994a92e..6a150852 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -20,6 +20,7 @@ namespace graphene { namespace chain { optional revenue_split; bool is_transferable = false; bool is_sellable = true; + optional account_role; }; class nft_object : public abstract_object @@ -95,7 +96,8 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object) (revenue_partner) (revenue_split) (is_transferable) - (is_sellable) ) + (is_sellable) + (account_role) ) FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), (nft_metadata_id) diff --git a/libraries/chain/include/graphene/chain/protocol/account_role.hpp b/libraries/chain/include/graphene/chain/protocol/account_role.hpp new file mode 100644 index 00000000..719a16f1 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/account_role.hpp @@ -0,0 +1,80 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + + struct account_role_create_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + asset fee; + + account_id_type owner; + std::string name; + std::string metadata; + flat_set allowed_operations; + flat_set whitelisted_accounts; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct account_role_update_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + asset fee; + + account_id_type owner; + account_role_id_type account_role_id; + optional name; + optional metadata; + flat_set allowed_operations_to_add; + flat_set allowed_operations_to_remove; + flat_set accounts_to_add; + flat_set accounts_to_remove; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct account_role_delete_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type owner; + account_role_id_type account_role_id; + extensions_type extensions; + + account_id_type fee_payer() const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } + }; + } // namespace chain +} // namespace graphene + +FC_REFLECT(graphene::chain::account_role_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) +FC_REFLECT(graphene::chain::account_role_update_operation::fee_parameters_type, (fee)(price_per_kbyte)) +FC_REFLECT(graphene::chain::account_role_delete_operation::fee_parameters_type, (fee)) + +FC_REFLECT(graphene::chain::account_role_create_operation, (fee)(owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)(extensions)) +FC_REFLECT(graphene::chain::account_role_update_operation, (fee)(owner)(account_role_id)(name)(metadata)(allowed_operations_to_add)(allowed_operations_to_remove)(accounts_to_add)(accounts_to_remove)(extensions)) +FC_REFLECT(graphene::chain::account_role_delete_operation, (fee)(owner)(account_role_id)(owner)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp index f5f8c1cd..db3c9a78 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp @@ -20,6 +20,7 @@ struct custom_account_authority_create_operation : public base_operation time_point_sec valid_from; time_point_sec valid_to; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -38,6 +39,7 @@ struct custom_account_authority_update_operation : public base_operation optional new_valid_from; optional new_valid_to; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -54,6 +56,7 @@ struct custom_account_authority_delete_operation : public base_operation asset fee; custom_account_authority_id_type auth_id; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -64,10 +67,10 @@ struct custom_account_authority_delete_operation : public base_operation } // namespace graphene FC_REFLECT(graphene::chain::custom_account_authority_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) -FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account)) +FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_account_authority_update_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account)) +FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_account_authority_delete_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account)) \ No newline at end of file +FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account)(extensions)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp index 8093ef07..611904bd 100644 --- a/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp +++ b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp @@ -18,6 +18,7 @@ struct custom_permission_create_operation : public base_operation account_id_type owner_account; string permission_name; authority auth; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -35,6 +36,7 @@ struct custom_permission_update_operation : public base_operation custom_permission_id_type permission_id; optional new_auth; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -51,6 +53,7 @@ struct custom_permission_delete_operation : public base_operation asset fee; custom_permission_id_type permission_id; account_id_type owner_account; + extensions_type extensions; account_id_type fee_payer() const { return owner_account; } void validate() const; @@ -61,10 +64,10 @@ struct custom_permission_delete_operation : public base_operation } // namespace graphene FC_REFLECT(graphene::chain::custom_permission_create_operation::fee_parameters_type, (fee)(price_per_kbyte)) -FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth)) +FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth)(extensions)) FC_REFLECT(graphene::chain::custom_permission_update_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account)) +FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account)(extensions)) FC_REFLECT(graphene::chain::custom_permission_delete_operation::fee_parameters_type, (fee)) -FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account)) \ No newline at end of file +FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account)(extensions)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index 41e77b06..843df403 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -21,6 +21,9 @@ namespace graphene { namespace chain { optional revenue_split; bool is_transferable = false; bool is_sellable = true; + // Accounts Role + optional account_role; + extensions_type extensions; account_id_type fee_payer()const { return owner; } void validate() const; @@ -29,7 +32,11 @@ namespace graphene { namespace chain { struct nft_metadata_update_operation : public base_operation { - struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; + }; asset fee; account_id_type owner; @@ -41,6 +48,9 @@ namespace graphene { namespace chain { optional revenue_split; optional is_transferable; optional is_sellable; + // Accounts Role + optional account_role; + extensions_type extensions; account_id_type fee_payer()const { return owner; } void validate() const; @@ -63,6 +73,7 @@ namespace graphene { namespace chain { account_id_type approved; vector approved_operators; std::string token_uri; + extensions_type extensions; account_id_type fee_payer()const { return payer; } void validate() const; @@ -84,6 +95,7 @@ namespace graphene { namespace chain { account_id_type to; nft_id_type token_id; string data; + extensions_type extensions; account_id_type fee_payer()const { return operator_; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -98,6 +110,7 @@ namespace graphene { namespace chain { account_id_type approved; nft_id_type token_id; + extensions_type extensions; account_id_type fee_payer()const { return operator_; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -112,6 +125,7 @@ namespace graphene { namespace chain { account_id_type operator_; bool approved; + extensions_type extensions; account_id_type fee_payer()const { return owner; } share_type calculate_fee(const fee_parameters_type &k) const; @@ -126,10 +140,10 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) ) -FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) ) -FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) ) -FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) ) -FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) ) -FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) ) +FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) ) +FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) (extensions) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) (extensions) ) +FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) (extensions) ) +FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) (extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 1285d353..a8c51781 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -49,6 +49,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -155,7 +156,10 @@ namespace graphene { namespace chain { nft_mint_operation, nft_safe_transfer_from_operation, nft_approve_operation, - nft_set_approval_for_all_operation + nft_set_approval_for_all_operation, + account_role_create_operation, + account_role_update_operation, + account_role_delete_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 6fa2ab4d..c9794e8e 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -176,6 +176,7 @@ namespace graphene { namespace chain { offer_object_type, nft_metadata_type, nft_object_type, + account_role_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -241,6 +242,7 @@ namespace graphene { namespace chain { class offer_object; class nft_metadata_object; class nft_object; + class account_role_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -272,6 +274,7 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, offer_object_type, offer_object> offer_id_type; typedef object_id< protocol_ids, nft_metadata_type, nft_metadata_object> nft_metadata_id_type; typedef object_id< protocol_ids, nft_object_type, nft_object> nft_id_type; + typedef object_id< protocol_ids, account_role_type, account_role_object> account_role_id_type; // implementation types class global_property_object; @@ -459,6 +462,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (offer_object_type) (nft_metadata_type) (nft_object_type) + (account_role_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -535,6 +539,7 @@ FC_REFLECT_TYPENAME( graphene::chain::custom_account_authority_id_type ) FC_REFLECT_TYPENAME( graphene::chain::offer_history_id_type ) FC_REFLECT_TYPENAME( graphene::chain::nft_metadata_id_type ) FC_REFLECT_TYPENAME( graphene::chain::nft_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::account_role_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp new file mode 100644 index 00000000..26c993fb --- /dev/null +++ b/libraries/chain/include/graphene/chain/rbac_hardfork_visitor.hpp @@ -0,0 +1,48 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + struct rbac_operation_hardfork_visitor + { + typedef void result_type; + const fc::time_point_sec block_time; + + rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {} + void operator()(int op_type) const + { + int first_allowed_op = operation::tag::value; + switch (op_type) + { + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + case operation::tag::value: + FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permissions and roles not allowed on this operation yet!"); + break; + default: + FC_ASSERT(op_type < first_allowed_op, "Custom permissions and roles not allowed on this operation!"); + } + } + }; + + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index ace3f91b..f7f007ff 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include namespace graphene { namespace chain { @@ -18,6 +20,10 @@ void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_creat (*op.revenue_partner)(db()); FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); } + if(op.account_role) { + const auto& ar_obj = (*op.account_role)(db()); + FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached"); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -32,6 +38,7 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat obj.revenue_split = op.revenue_split; obj.is_transferable = op.is_transferable; obj.is_sellable = op.is_sellable; + obj.account_role = op.account_role; }); return new_nft_metadata_object.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -57,6 +64,10 @@ void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_updat (*op.revenue_partner)(db()); FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); } + if(op.account_role) { + const auto& ar_obj = (*op.account_role)(db()); + FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached"); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -77,6 +88,8 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o obj.is_transferable = *op.is_transferable; if( op.is_sellable.valid() ) obj.is_sellable = *op.is_sellable; + if( op.account_role.valid() ) + obj.account_role = op.account_role; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -144,6 +157,17 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf const auto& nft_meta_obj = itr_nft->nft_metadata_id(db()); FC_ASSERT( nft_meta_obj.is_transferable == true, "NFT is not transferable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.operator_, get_type()), "Account role not valid"); + FC_ASSERT(db().account_role_valid(*ar_itr, op.to), "Account role not valid"); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/offer_evaluator.cpp b/libraries/chain/offer_evaluator.cpp index 0d1b1947..bdb26382 100644 --- a/libraries/chain/offer_evaluator.cpp +++ b/libraries/chain/offer_evaluator.cpp @@ -2,6 +2,8 @@ #include #include #include +#include +#include #include #include #include @@ -38,6 +40,15 @@ namespace graphene } const auto &nft_meta_obj = nft_obj.nft_metadata_id(d); FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.issuer, get_type()), "Account role not valid"); + } + } } FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future"); FC_ASSERT(op.fee.amount >= 0, "Invalid fee"); @@ -100,6 +111,18 @@ namespace graphene FC_ASSERT(!is_approved, "Bidder cannot already be an approved account of the item"); FC_ASSERT(!is_approved_operator, "Bidder cannot already be an approved operator of the item"); } + + const auto &nft_meta_obj = nft_obj.nft_metadata_id(d); + FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable"); + if (nft_meta_obj.account_role) + { + const auto &ar_idx = db().get_index_type().indices().get(); + auto ar_itr = ar_idx.find(*nft_meta_obj.account_role); + if(ar_itr != ar_idx.end()) + { + FC_ASSERT(db().account_role_valid(*ar_itr, op.bidder, get_type()), "Account role not valid"); + } + } } FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch"); diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index ba714c21..ce2045e6 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -196,6 +196,18 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_set_approval_for_all_operation not allowed yet!" ); } + void operator()(const account_role_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_create_operation not allowed yet!" ); + } + + void operator()(const account_role_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_update_operation not allowed yet!" ); + } + + void operator()(const account_role_delete_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_delete_operation not allowed yet!" ); + } + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { diff --git a/libraries/chain/protocol/account_role.cpp b/libraries/chain/protocol/account_role.cpp new file mode 100644 index 00000000..1d71d6cc --- /dev/null +++ b/libraries/chain/protocol/account_role.cpp @@ -0,0 +1,61 @@ +#include +#include + +namespace graphene +{ + namespace chain + { + + void account_role_create_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(allowed_operations.size() > 0, "Allowed operations should be positive"); + FC_ASSERT(whitelisted_accounts.size() > 0, "Whitelisted accounts should be positive"); + } + + void account_role_update_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + for (auto aop : allowed_operations_to_add) + { + FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid"); + FC_ASSERT(allowed_operations_to_remove.find(aop) == allowed_operations_to_remove.end(), + "Cannot add and remove allowed operation at the same time."); + } + for (auto aop : allowed_operations_to_remove) + { + FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid"); + FC_ASSERT(allowed_operations_to_add.find(aop) == allowed_operations_to_add.end(), + "Cannot add and remove allowed operation at the same time."); + } + + for (auto acc : accounts_to_add) + { + FC_ASSERT(accounts_to_remove.find(acc) == accounts_to_remove.end(), + "Cannot add and remove accounts at the same time."); + } + + for (auto acc : accounts_to_remove) + { + FC_ASSERT(accounts_to_add.find(acc) == accounts_to_add.end(), + "Cannot add and remove accounts at the same time."); + } + } + + void account_role_delete_operation::validate() const + { + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + } + + share_type account_role_create_operation::calculate_fee(const fee_parameters_type &k) const + { + return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte); + } + + share_type account_role_update_operation::calculate_fee(const fee_parameters_type &k) const + { + return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte); + } + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/protocol/nft.cpp b/libraries/chain/protocol/nft.cpp index 802bf425..4a66f330 100644 --- a/libraries/chain/protocol/nft.cpp +++ b/libraries/chain/protocol/nft.cpp @@ -45,7 +45,6 @@ void nft_metadata_create_operation::validate() const FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); FC_ASSERT(is_valid_nft_token_name(name), "Invalid NFT name provided"); FC_ASSERT(is_valid_nft_token_name(symbol), "Invalid NFT symbol provided"); - FC_ASSERT(base_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI"); } void nft_metadata_update_operation::validate() const @@ -55,14 +54,11 @@ void nft_metadata_update_operation::validate() const FC_ASSERT(is_valid_nft_token_name(*name), "Invalid NFT name provided"); if(symbol) FC_ASSERT(is_valid_nft_token_name(*symbol), "Invalid NFT symbol provided"); - if(base_uri) - FC_ASSERT((*base_uri).length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI"); } void nft_mint_operation::validate() const { FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); - FC_ASSERT(token_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Token URI"); } share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_type &k) const @@ -72,7 +68,7 @@ share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_typ share_type nft_metadata_update_operation::calculate_fee(const fee_parameters_type &k) const { - return k.fee; + return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); } share_type nft_mint_operation::calculate_fee(const fee_parameters_type &k) const diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 7f591328..40dd12cd 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1950,6 +1950,7 @@ class wallet_api optional revenue_split, bool is_transferable, bool is_sellable, + optional role_id, bool broadcast); /** @@ -1975,6 +1976,7 @@ class wallet_api optional revenue_split, optional is_transferable, optional is_sellable, + optional role_id, bool broadcast); /** @@ -2113,6 +2115,26 @@ class wallet_api vector get_offer_history_by_item(const nft_id_type item, uint32_t limit, optional lower_id) const; vector get_offer_history_by_bidder(string bidder_account_id_or_name, uint32_t limit, optional lower_id) const; + signed_transaction create_account_role(string owner_account_id_or_name, + string name, + string metadata, + flat_set allowed_operations, + flat_set whitelisted_accounts, + bool broadcast); + signed_transaction update_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + optional name, + optional metadata, + flat_set operations_to_add, + flat_set operations_to_remove, + flat_set accounts_to_add, + flat_set accounts_to_remove, + bool broadcast); + signed_transaction delete_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + bool broadcast); + vector get_account_roles_by_owner(string owner_account_id_or_name) const; + void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); void dbg_push_blocks( std::string src_filename, uint32_t count ); @@ -2387,6 +2409,10 @@ FC_API( graphene::wallet::wallet_api, (get_offer_history_by_issuer) (get_offer_history_by_item) (get_offer_history_by_bidder) + (create_account_role) + (update_account_role) + (delete_account_role) + (get_account_roles_by_owner) (get_upcoming_tournaments) (get_tournaments) (get_tournaments_by_state) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 97b31370..bbaa41ac 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6375,6 +6375,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na optional revenue_split, bool is_transferable, bool is_sellable, + optional role_id, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6397,6 +6398,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na } op.is_transferable = is_transferable; op.is_sellable = is_sellable; + op.account_role = role_id; signed_transaction trx; trx.operations.push_back(op); @@ -6415,6 +6417,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na optional revenue_split, optional is_transferable, optional is_sellable, + optional role_id, bool broadcast) { account_object owner_account = my->get_account(owner_account_id_or_name); @@ -6438,6 +6441,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na } op.is_transferable = is_transferable; op.is_sellable = is_sellable; + op.account_role = role_id; signed_transaction trx; trx.operations.push_back(op); @@ -6719,6 +6723,84 @@ vector wallet_api::get_offer_history_by_bidder(string bidd account_object bidder_account = my->get_account(bidder_account_id_or_name); return my->_remote_db->get_offer_history_by_bidder(lb_id, bidder_account.id, limit); } + +signed_transaction wallet_api::create_account_role(string owner_account_id_or_name, + string name, + string metadata, + flat_set allowed_operations, + flat_set whitelisted_accounts, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_create_operation op; + op.owner = owner_account.id; + op.name = name; + op.metadata = metadata; + op.allowed_operations = allowed_operations; + op.whitelisted_accounts = whitelisted_accounts; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +signed_transaction wallet_api::update_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + optional name, + optional metadata, + flat_set operations_to_add, + flat_set operations_to_remove, + flat_set accounts_to_add, + flat_set accounts_to_remove, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_update_operation op; + op.owner = owner_account.id; + op.account_role_id = role_id; + op.name = name; + op.metadata = metadata; + op.allowed_operations_to_add = operations_to_add; + op.allowed_operations_to_remove = operations_to_remove; + op.accounts_to_add = accounts_to_add; + op.accounts_to_remove = accounts_to_remove; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +signed_transaction wallet_api::delete_account_role(string owner_account_id_or_name, + account_role_id_type role_id, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + account_role_delete_operation op; + op.owner = owner_account.id; + op.account_role_id = role_id; + + signed_transaction trx; + trx.operations.push_back(op); + my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees ); + trx.validate(); + + return my->sign_transaction( trx, broadcast ); +} + +vector wallet_api::get_account_roles_by_owner(string owner_account_id_or_name) const +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + return my->_remote_db->get_account_roles_by_owner(owner_account.id); +} // default ctor necessary for FC_REFLECT signed_block_with_info::signed_block_with_info() { diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 94a3296a..63afc816 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include diff --git a/tests/tests/account_role_tests.cpp b/tests/tests/account_role_tests.cpp new file mode 100644 index 00000000..ded613d6 --- /dev/null +++ b/tests/tests/account_role_tests.cpp @@ -0,0 +1,282 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(account_role_tests, database_fixture) + +BOOST_AUTO_TEST_CASE(account_role_create_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_create_test"); + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + generate_block(); + set_expiration(db, trx); + ACTORS((resourceowner)(alice)(bob)); + { + BOOST_TEST_MESSAGE("Send account_role_create_operation"); + + account_role_create_operation op; + op.owner = resourceowner_id; + op.name = "Test Account Role"; + op.metadata = "{\"country\": \"earth\", \"race\": \"human\" }"; + + int ops[] = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + op.allowed_operations.insert(ops, ops + 6); + op.whitelisted_accounts.emplace(alice_id); + op.whitelisted_accounts.emplace(bob_id); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.begin(); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->owner == resourceowner_id); + BOOST_CHECK(obj->name == "Test Account Role"); + BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\" }"); + flat_set expected_allowed_operations = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + BOOST_CHECK(obj->allowed_operations == expected_allowed_operations); + flat_set expected_whitelisted_accounts = {alice_id, bob_id}; + BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_role_update_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_create_test"); + INVOKE(account_role_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(resourceowner); + ACTORS((charlie)); + set_expiration(db, trx); + { + BOOST_TEST_MESSAGE("Send account_role_update_operation"); + + account_role_update_operation op; + op.owner = resourceowner_id; + op.account_role_id = account_role_id_type(0); + op.name = "Test Account Role Update"; + op.metadata = "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }"; + + int ops_add[] = {operation::tag::value}; + int ops_delete[] = {operation::tag::value, operation::tag::value}; + op.allowed_operations_to_add.insert(ops_add, ops_add + 1); + op.allowed_operations_to_remove.insert(ops_delete, ops_delete + 2); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.begin(); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->owner == resourceowner_id); + BOOST_CHECK(obj->name == "Test Account Role Update"); + BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }"); + flat_set expected_allowed_operations = {operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value, + operation::tag::value}; + BOOST_CHECK(obj->allowed_operations == expected_allowed_operations); + flat_set expected_whitelisted_accounts = {alice_id, bob_id}; + BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_role_delete_test) +{ + try + { + BOOST_TEST_MESSAGE("account_role_delete_test"); + INVOKE(account_role_create_test); + GET_ACTOR(resourceowner); + set_expiration(db, trx); + { + BOOST_TEST_MESSAGE("Send account_role_delete_operation"); + + account_role_delete_operation op; + op.owner = resourceowner_id; + op.account_role_id = account_role_id_type(0); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(nft_metadata_create_mint_with_account_role_test) +{ + try + { + BOOST_TEST_MESSAGE("nft_metadata_create_mint_with_account_role_test"); + INVOKE(account_role_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(resourceowner); + + { + BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); + + nft_metadata_create_operation op; + op.owner = resourceowner_id; + op.name = "NFT Test"; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.revenue_partner = resourceowner_id; + op.revenue_split = 1000; + op.is_transferable = true; + op.is_sellable = true; + op.account_role = account_role_id_type(0); + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + + const auto &nftmd_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nftmd_idx.size() == 1); + auto nftmd_obj = nftmd_idx.begin(); + BOOST_REQUIRE(nftmd_obj != nftmd_idx.end()); + BOOST_CHECK(nftmd_obj->owner == resourceowner_id); + BOOST_CHECK(nftmd_obj->name == "NFT Test"); + BOOST_CHECK(nftmd_obj->symbol == "NFT"); + BOOST_CHECK(nftmd_obj->base_uri == "http://nft.example.com"); + BOOST_CHECK(nftmd_obj->account_role == account_role_id_type(0)); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation"); + + nft_mint_operation op; + op.payer = resourceowner_id; + op.nft_metadata_id = nftmd_obj->id; + op.owner = alice_id; + op.approved = alice_id; + + trx.operations.push_back(op); + sign(trx, resourceowner_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + + BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + + const auto &nft_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nft_idx.size() == 1); + auto nft_obj = nft_idx.begin(); + BOOST_REQUIRE(nft_obj != nft_idx.end()); + BOOST_CHECK(nft_obj->owner == alice_id); + BOOST_CHECK(nft_obj->approved == alice_id); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(nft_safe_transfer_account_role_test) +{ + try + { + BOOST_TEST_MESSAGE("nft_safe_transfer_account_role_test"); + INVOKE(nft_metadata_create_mint_with_account_role_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + ACTORS((charlie)); + + { + BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation"); + + nft_safe_transfer_from_operation op; + op.operator_ = alice_id; + op.from = alice_id; + op.to = bob_id; + op.token_id = nft_id_type(0); + op.data = "data"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_safe_transfer_from_operation results"); + + const auto &nft_idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(nft_idx.size() == 1); + auto nft_obj = nft_idx.begin(); + BOOST_REQUIRE(nft_obj != nft_idx.end()); + BOOST_CHECK(nft_obj->owner == bob_id); + + { + BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation"); + + nft_safe_transfer_from_operation op; + op.operator_ = bob_id; + op.from = bob_id; + op.to = charlie_id; + op.token_id = nft_id_type(0); + op.data = "data"; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + // Charlie is not whitelisted by resource creator + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file