From 6514f8fadd48ba16bec62bb43f4f2742aa6213e7 Mon Sep 17 00:00:00 2001 From: sierra19XX <15652887+sierra19XX@users.noreply.github.com> Date: Tue, 11 Aug 2020 01:42:59 +1000 Subject: [PATCH] NFT Marketplace HRP Beatrice Merge (#371) * private-key option update * ppy marketplace 1 - add evaluators and objects * NFT object and basic operations * ci: update .gitlab-ci.yml * ci: update .gitlab-ci.yml * NFT evaluators and basic tests, no evaluator checks * Evaluator checks in place * ppy marketplace 2 - batch sale, offer_object escrow * Database API * Wallet API * NFT metadata implemented * Fix NFT tests * Database API for NFT metadata and enumerables * ppy marketplace 4 - Add tests NFT+Marketplace * ppy marketplace 5 - Add revenue split * ppy marketplace 6 - Remove unnecessary files * ppy marketplace 7 - Add db, wallet changes and some NFT fixes * ppy marketplace 8 - Add pagination for list APIs * ci: update .gitlab-ci.yml * New DB API, list all NFTs, list NFTs by owner * Marketplace + NFT + RBAC (#368) * rbac1 - evaluators and op validators added * rbac2 - op_type hf checks * rbac3 - tx auth verify changes * Update .gitlab-ci.yml * rbac4 - basic op tests * rbac5 - clear expired and deleted permission linked auths * rbac6 - more tests * rbac7 - more tests * rbac8 - more tests * rbac9 - wallet and db api changes * rbac10 - db api changes for required signature fetch * rbac11 - add db_api tests * rbac12 - add missing code for key auths Co-authored-by: Roshan Syed Co-authored-by: sierra19XX <15652887+sierra19XX@users.noreply.github.com> * Fix nft_get_token_uri returning empty string * Fix nft_mint_evaluator to save token_uri * Fix cli_wallet to properly pass metadata id for nft_create * ppy marketplace 9 - FC_REFLECT offer create op * Add stricter checks to NFTs * GPOS2 HF - Handle rolling period on missing blocks (#369) * Mainnet chain halt 5050 Issue (#370) * Unlisting offers, add result in offer history object * Reverting genesis.json wrong commit * Add non-transferable non-sellable properties to NFTs * Review comments - change variable names, use scoped enums * nft_metadata_update changes * NFT HF checks and op fee addition changes * NFT make revenue_split integer from double * revenue_split condition check allow zero or above * Peerplays Marketplace + NFT (#367) * ppy marketplace 1 - add evaluators and objects * NFT object and basic operations * ci: update .gitlab-ci.yml * ci: update .gitlab-ci.yml * NFT evaluators and basic tests, no evaluator checks * Evaluator checks in place * ppy marketplace 2 - batch sale, offer_object escrow * Database API * Wallet API * NFT metadata implemented * Fix NFT tests * Database API for NFT metadata and enumerables * ppy marketplace 4 - Add tests NFT+Marketplace * ppy marketplace 5 - Add revenue split * ppy marketplace 6 - Remove unnecessary files * ppy marketplace 7 - Add db, wallet changes and some NFT fixes * ppy marketplace 8 - Add pagination for list APIs * New DB API, list all NFTs, list NFTs by owner * Marketplace + NFT + RBAC (#368) * rbac1 - evaluators and op validators added * rbac2 - op_type hf checks * rbac3 - tx auth verify changes * Update .gitlab-ci.yml * rbac4 - basic op tests * rbac5 - clear expired and deleted permission linked auths * rbac6 - more tests * rbac7 - more tests * rbac8 - more tests * rbac9 - wallet and db api changes * rbac10 - db api changes for required signature fetch * rbac11 - add db_api tests * rbac12 - add missing code for key auths Co-authored-by: Roshan Syed Co-authored-by: sierra19XX <15652887+sierra19XX@users.noreply.github.com> * Fix nft_get_token_uri returning empty string * Fix nft_mint_evaluator to save token_uri * Fix cli_wallet to properly pass metadata id for nft_create * ppy marketplace 9 - FC_REFLECT offer create op * Add stricter checks to NFTs * Unlisting offers, add result in offer history object * Reverting genesis.json wrong commit * Add non-transferable non-sellable properties to NFTs * Review comments - change variable names, use scoped enums * nft_metadata_update changes * NFT HF checks and op fee addition changes * NFT make revenue_split integer from double * revenue_split condition check allow zero or above Co-authored-by: Srdjan Obucina Co-authored-by: Roshan Syed Co-authored-by: obucina <11353193+obucina@users.noreply.github.com> * Beatrice NFT HF Co-authored-by: pbattu123 <43043205+pbattu123@users.noreply.github.com> Co-authored-by: pbattu123 Co-authored-by: Srdjan Obucina Co-authored-by: Roshan Syed Co-authored-by: obucina <11353193+obucina@users.noreply.github.com> --- CMakeLists.txt | 2 +- docker/peerplaysentry.sh | 5 + libraries/app/database_api.cpp | 585 ++++++ .../app/include/graphene/app/database_api.hpp | 151 ++ libraries/chain/CMakeLists.txt | 10 + libraries/chain/asset_object.cpp | 2 +- .../custom_account_authority_evaluator.cpp | 152 ++ .../chain/custom_permission_evaluator.cpp | 133 ++ libraries/chain/db_block.cpp | 6 +- libraries/chain/db_getter.cpp | 33 + libraries/chain/db_init.cpp | 40 + libraries/chain/db_maint.cpp | 21 +- libraries/chain/db_notify.cpp | 51 + libraries/chain/db_update.cpp | 26 + libraries/chain/hardfork.d/NFT.hf | 4 + .../chain/include/graphene/chain/config.hpp | 10 + .../custom_account_authority_evaluator.hpp | 38 + .../chain/custom_account_authority_object.hpp | 55 + .../chain/custom_permission_evaluator.hpp | 38 + .../chain/custom_permission_object.hpp | 49 + .../chain/include/graphene/chain/database.hpp | 3 + .../include/graphene/chain/nft_evaluator.hpp | 59 + .../include/graphene/chain/nft_object.hpp | 106 ++ .../graphene/chain/offer_evaluator.hpp | 47 + .../include/graphene/chain/offer_object.hpp | 109 ++ .../chain/protocol/chain_parameters.hpp | 18 +- .../protocol/custom_account_authority.hpp | 73 + .../chain/protocol/custom_permission.hpp | 70 + .../graphene/chain/protocol/nft_ops.hpp | 135 ++ .../include/graphene/chain/protocol/offer.hpp | 143 ++ .../graphene/chain/protocol/operations.hpp | 22 +- .../graphene/chain/protocol/transaction.hpp | 4 + .../include/graphene/chain/protocol/types.hpp | 32 +- libraries/chain/nft_evaluator.cpp | 238 +++ libraries/chain/offer_evaluator.cpp | 338 ++++ libraries/chain/offer_object.cpp | 50 + libraries/chain/proposal_evaluator.cpp | 65 + libraries/chain/proposal_object.cpp | 2 + .../protocol/custom_account_authority.cpp | 43 + .../chain/protocol/custom_permission.cpp | 85 + libraries/chain/protocol/nft.cpp | 99 + libraries/chain/protocol/offer.cpp | 57 + libraries/chain/protocol/transaction.cpp | 71 +- .../plugins/debug_witness/debug_witness.cpp | 6 +- .../wallet/include/graphene/wallet/wallet.hpp | 254 +++ libraries/wallet/wallet.cpp | 562 ++++++ programs/js_operation_serializer/main.cpp | 4 + tests/common/database_fixture.cpp | 17 +- tests/tests/authority_tests.cpp | 26 +- tests/tests/custom_permission_tests.cpp | 1647 +++++++++++++++++ tests/tests/gpos_tests.cpp | 4 +- tests/tests/marketplace_tests.cpp | 941 ++++++++++ tests/tests/nft_tests.cpp | 412 +++++ 53 files changed, 7120 insertions(+), 33 deletions(-) create mode 100644 libraries/chain/custom_account_authority_evaluator.cpp create mode 100644 libraries/chain/custom_permission_evaluator.cpp create mode 100644 libraries/chain/hardfork.d/NFT.hf create mode 100644 libraries/chain/include/graphene/chain/custom_account_authority_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/custom_account_authority_object.hpp create mode 100644 libraries/chain/include/graphene/chain/custom_permission_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/custom_permission_object.hpp create mode 100644 libraries/chain/include/graphene/chain/nft_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/nft_object.hpp create mode 100644 libraries/chain/include/graphene/chain/offer_evaluator.hpp create mode 100644 libraries/chain/include/graphene/chain/offer_object.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/custom_permission.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/nft_ops.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/offer.hpp create mode 100644 libraries/chain/nft_evaluator.cpp create mode 100644 libraries/chain/offer_evaluator.cpp create mode 100644 libraries/chain/offer_object.cpp create mode 100644 libraries/chain/protocol/custom_account_authority.cpp create mode 100644 libraries/chain/protocol/custom_permission.cpp create mode 100644 libraries/chain/protocol/nft.cpp create mode 100644 libraries/chain/protocol/offer.cpp create mode 100644 tests/tests/custom_permission_tests.cpp create mode 100644 tests/tests/marketplace_tests.cpp create mode 100644 tests/tests/nft_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d7b01087..cd539a9c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ else( WIN32 ) # Apple AND Linux endif( APPLE ) if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-class-memaccess -Wno-parentheses -Wno-terminate -Wno-invalid-offsetof" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-parentheses -Wno-terminate -Wno-invalid-offsetof" ) elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization" ) diff --git a/docker/peerplaysentry.sh b/docker/peerplaysentry.sh index 820ad9f4..31caeef2 100644 --- a/docker/peerplaysentry.sh +++ b/docker/peerplaysentry.sh @@ -14,6 +14,7 @@ VERSION=`cat /etc/peerplays/version` # * $PEERPLAYSD_P2P_ENDPOINT # * $PEERPLAYSD_WITNESS_ID # * $PEERPLAYSD_PRIVATE_KEY +# * $PEERPLAYSD_DEBUG_PRIVATE_KEY # * $PEERPLAYSD_TRACK_ACCOUNTS # * $PEERPLAYSD_PARTIAL_OPERATIONS # * $PEERPLAYSD_MAX_OPS_PER_ACCOUNT @@ -51,6 +52,10 @@ if [[ ! -z "$PEERPLAYSD_PRIVATE_KEY" ]]; then ARGS+=" --private-key=$PEERPLAYSD_PRIVATE_KEY" fi +if [[ ! -z "$PEERPLAYSD_DEBUG_PRIVATE_KEY" ]]; then + ARGS+=" --debug-private-key=$PEERPLAYSD_DEBUG_PRIVATE_KEY" +fi + if [[ ! -z "$PEERPLAYSD_TRACK_ACCOUNTS" ]]; then for ACCOUNT in $PEERPLAYSD_TRACK_ACCOUNTS ; do ARGS+=" --track-account=$ACCOUNT" diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index df6458f3..6b14f2bc 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -184,6 +184,39 @@ class database_api_impl : public std::enable_shared_from_this // gpos gpos_info get_gpos_info(const account_id_type account) const; + // rbac + vector get_custom_permissions(const account_id_type account) const; + fc::optional get_custom_permission_by_name(const account_id_type account, const string& permission_name) const; + vector get_custom_account_authorities(const account_id_type account) const; + vector get_custom_account_authorities_by_permission_id(const custom_permission_id_type permission_id) const; + vector get_custom_account_authorities_by_permission_name(const account_id_type account, const string& permission_name) const; + vector get_active_custom_account_authorities_by_operation(const account_id_type account, int operation_type) const; + + // NFT + uint64_t nft_get_balance(const account_id_type owner) const; + optional nft_owner_of(const nft_id_type token_id) const; + optional nft_get_approved(const nft_id_type token_id) const; + bool nft_is_approved_for_all(const account_id_type owner, const account_id_type operator_) const; + string nft_get_name(const nft_metadata_id_type nft_metadata_id) const; + string nft_get_symbol(const nft_metadata_id_type nft_metadata_id) const; + string nft_get_token_uri(const nft_id_type token_id) const; + uint64_t nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const; + nft_object nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const; + nft_object nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const; + vector nft_get_all_tokens() const; + vector nft_get_tokens_by_owner(const account_id_type owner) const; + + // Marketplace + vector list_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_sell_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_buy_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_offer_history(const offer_history_id_type lower_id, uint32_t limit) const; + vector get_offers_by_issuer(const offer_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const; + vector get_offers_by_item(const offer_id_type lower_id, const nft_id_type item, uint32_t limit) const; + 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; + //private: const account_object* get_account_from_string( const std::string& name_or_id, bool throw_if_not_found = true ) const; @@ -1857,6 +1890,9 @@ set database_api_impl::get_required_signatures( const signed_tr available_keys, [&]( account_id_type id ){ return &id(_db).active; }, [&]( account_id_type id ){ return &id(_db).owner; }, + [&]( account_id_type id, const operation& op ) { + return _db.get_account_custom_authorities(id, op); + }, _db.get_global_properties().parameters.max_authority_depth ); wdump((result)); return result; @@ -1892,6 +1928,17 @@ set database_api_impl::get_potential_signatures( const signed_t result.insert(k); return &auth; }, + [&]( account_id_type id, const operation& op ) { + vector custom_auths = _db.get_account_custom_authorities(id, op); + for (const auto& cauth: custom_auths) + { + for (const auto& k : cauth.get_keys()) + { + result.insert(k); + } + } + return custom_auths; + }, _db.get_global_properties().parameters.max_authority_depth ); @@ -1919,6 +1966,9 @@ set
database_api_impl::get_potential_address_signatures( const signed_t result.insert(k); return &auth; }, + [&]( account_id_type id, const operation& op ) { + return _db.get_account_custom_authorities(id, op); + }, _db.get_global_properties().parameters.max_authority_depth ); return result; @@ -1934,6 +1984,8 @@ bool database_api_impl::verify_authority( const signed_transaction& trx )const trx.verify_authority( _db.get_chain_id(), [this]( account_id_type id ){ return &id(_db).active; }, [this]( account_id_type id ){ return &id(_db).owner; }, + [this]( account_id_type id, const operation& op ) { + return _db.get_account_custom_authorities(id, op); }, _db.get_global_properties().parameters.max_authority_depth ); return true; } @@ -2240,6 +2292,7 @@ graphene::app::gpos_info database_api::get_gpos_info(const account_id_type accou return my->get_gpos_info(account); } + graphene::app::gpos_info database_api_impl::get_gpos_info(const account_id_type account) const { FC_ASSERT( _db.head_block_time() > HARDFORK_GPOS_TIME); //Can be deleted after GPOS hardfork time @@ -2303,6 +2356,538 @@ graphene::app::gpos_info database_api_impl::get_gpos_info(const account_id_type return result; } +////////////////////////////////////////////////////////////////////// +// // +// RBAC methods // +// // +////////////////////////////////////////////////////////////////////// + +vector database_api::get_custom_permissions(const account_id_type account) const +{ + return my->get_custom_permissions(account); +} + +vector database_api_impl::get_custom_permissions(const account_id_type account) const +{ + const auto& pindex = _db.get_index_type().indices().get(); + auto prange = pindex.equal_range(boost::make_tuple(account)); + vector custom_permissions; + for(const custom_permission_object& pobj : boost::make_iterator_range(prange.first, prange.second)) + { + custom_permissions.push_back(pobj); + } + return custom_permissions; +} + +fc::optional database_api::get_custom_permission_by_name(const account_id_type account, const string& permission_name) const +{ + return my->get_custom_permission_by_name(account, permission_name); +} + +fc::optional database_api_impl::get_custom_permission_by_name(const account_id_type account, const string& permission_name) const +{ + const auto& pindex = _db.get_index_type().indices().get(); + auto prange = pindex.equal_range(boost::make_tuple(account, permission_name)); + for(const custom_permission_object& pobj : boost::make_iterator_range(prange.first, prange.second)) + { + return pobj; + } + return {}; +} + +////////////////////////////////////////////////////////////////////// +// // +// NFT methods // +// // +////////////////////////////////////////////////////////////////////// + +uint64_t database_api::nft_get_balance(const account_id_type owner) const +{ + return my->nft_get_balance(owner); +} + +uint64_t database_api_impl::nft_get_balance(const account_id_type owner) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + const auto &idx_nft_range = idx_nft.equal_range(owner); + return std::distance(idx_nft_range.first, idx_nft_range.second); +} + +optional database_api::nft_owner_of(const nft_id_type token_id) const +{ + return my->nft_owner_of(token_id); +} + +optional database_api_impl::nft_owner_of(const nft_id_type token_id) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto itr_nft = idx_nft.find(token_id); + if (itr_nft != idx_nft.end()) { + return itr_nft->owner; + } + return {}; +} + +optional database_api::nft_get_approved(const nft_id_type token_id) const +{ + return my->nft_get_approved(token_id); +} + +optional database_api_impl::nft_get_approved(const nft_id_type token_id) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto itr_nft = idx_nft.find(token_id); + if (itr_nft != idx_nft.end()) { + return itr_nft->approved; + } + return {}; +} + +bool database_api::nft_is_approved_for_all(const account_id_type owner, const account_id_type operator_) const +{ + return my->nft_is_approved_for_all(owner, operator_); +} + +bool database_api_impl::nft_is_approved_for_all(const account_id_type owner, const account_id_type operator_) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + const auto &idx_nft_range = idx_nft.equal_range(owner); + if (std::distance(idx_nft_range.first, idx_nft_range.second) == 0) { + return false; + } + bool result = true; + std::for_each(idx_nft_range.first, idx_nft_range.second, [&](const nft_object &obj) { + result = result && (obj.approved == operator_); + }); + return result; +} + +string database_api::nft_get_name(const nft_metadata_id_type nft_metadata_id) const +{ + return my->nft_get_name(nft_metadata_id); +} + +string database_api_impl::nft_get_name(const nft_metadata_id_type nft_metadata_id) const +{ + const auto &idx_nft_md = _db.get_index_type().indices().get(); + auto itr_nft_md = idx_nft_md.find(nft_metadata_id); + if (itr_nft_md != idx_nft_md.end()) { + return itr_nft_md->name; + } + return ""; +} + +string database_api::nft_get_symbol(const nft_metadata_id_type nft_metadata_id) const +{ + return my->nft_get_symbol(nft_metadata_id); +} + +string database_api_impl::nft_get_symbol(const nft_metadata_id_type nft_metadata_id) const +{ + const auto &idx_nft_md = _db.get_index_type().indices().get(); + auto itr_nft_md = idx_nft_md.find(nft_metadata_id); + if (itr_nft_md != idx_nft_md.end()) { + return itr_nft_md->symbol; + } + return ""; +} + +string database_api::nft_get_token_uri(const nft_id_type token_id) const +{ + return my->nft_get_token_uri(token_id); +} + +string database_api_impl::nft_get_token_uri(const nft_id_type token_id) const +{ + string result = ""; + const auto &idx_nft = _db.get_index_type().indices().get(); + auto itr_nft = idx_nft.find(token_id); + if (itr_nft != idx_nft.end()) { + result = itr_nft->token_uri; + const auto &idx_nft_md = _db.get_index_type().indices().get(); + auto itr_nft_md = idx_nft_md.find(itr_nft->nft_metadata_id); + if (itr_nft_md != idx_nft_md.end()) { + result = itr_nft_md->base_uri + itr_nft->token_uri; + } + } + return result; +} + +uint64_t database_api::nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const +{ + return my->nft_get_total_supply(nft_metadata_id); +} + +uint64_t database_api_impl::nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const +{ + const auto &idx_nft_md = _db.get_index_type().indices().get(); + return idx_nft_md.size(); +} + +nft_object database_api::nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const +{ + return my->nft_token_by_index(nft_metadata_id, token_idx); +} + +nft_object database_api_impl::nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto idx_nft_range = idx_nft.equal_range(nft_metadata_id); + uint64_t tmp_idx = token_idx; + for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) { + if (tmp_idx == 0) { + return *itr; + } + tmp_idx = tmp_idx - 1; + } + return {}; +} + +nft_object database_api::nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const +{ + return my->nft_token_of_owner_by_index(nft_metadata_id, owner, token_idx); +} + +nft_object database_api_impl::nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto idx_nft_range = idx_nft.equal_range(std::make_tuple(nft_metadata_id, owner)); + uint64_t tmp_idx = token_idx; + for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) { + if (tmp_idx == 0) { + return *itr; + } + tmp_idx = tmp_idx - 1; + } + return {}; +} + +vector database_api::nft_get_all_tokens() const +{ + return my->nft_get_all_tokens(); +} + +vector database_api_impl::nft_get_all_tokens() const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + vector result; + for (auto itr = idx_nft.begin(); itr != idx_nft.end(); ++itr) { + result.push_back(*itr); + } + return result; +} + +vector database_api::nft_get_tokens_by_owner(const account_id_type owner) const +{ + return my->nft_get_tokens_by_owner(owner); +} + +vector database_api_impl::nft_get_tokens_by_owner(const account_id_type owner) const +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto idx_nft_range = idx_nft.equal_range(owner); + vector result; + for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) { + result.push_back(*itr); + } + return result; +} + +vector database_api::get_custom_account_authorities(const account_id_type account) const +{ + return my->get_custom_account_authorities(account); +} + +vector database_api_impl::get_custom_account_authorities(const account_id_type account) const +{ + const auto& pindex = _db.get_index_type().indices().get(); + const auto& cindex = _db.get_index_type().indices().get(); + vector custom_account_auths; + auto prange = pindex.equal_range(boost::make_tuple(account)); + for(const custom_permission_object& pobj : boost::make_iterator_range(prange.first, prange.second)) + { + auto crange = cindex.equal_range(boost::make_tuple(pobj.id)); + for(const custom_account_authority_object& cobj : boost::make_iterator_range(crange.first, crange.second)) + { + custom_account_auths.push_back(cobj); + } + } + return custom_account_auths; +} + +vector database_api::get_custom_account_authorities_by_permission_id(const custom_permission_id_type permission_id) const +{ + return my->get_custom_account_authorities_by_permission_id(permission_id); +} + +vector database_api_impl::get_custom_account_authorities_by_permission_id(const custom_permission_id_type permission_id) const +{ + const auto& cindex = _db.get_index_type().indices().get(); + vector custom_account_auths; + auto crange = cindex.equal_range(boost::make_tuple(permission_id)); + for(const custom_account_authority_object& cobj : boost::make_iterator_range(crange.first, crange.second)) + { + custom_account_auths.push_back(cobj); + } + return custom_account_auths; +} + +vector database_api::get_custom_account_authorities_by_permission_name(const account_id_type account, const string& permission_name) const +{ + return my->get_custom_account_authorities_by_permission_name(account, permission_name); +} + +vector database_api_impl::get_custom_account_authorities_by_permission_name(const account_id_type account, const string& permission_name) const +{ + vector custom_account_auths; + fc::optional pobj = get_custom_permission_by_name(account, permission_name); + if(!pobj) + { + return custom_account_auths; + } + const auto& cindex = _db.get_index_type().indices().get(); + auto crange = cindex.equal_range(boost::make_tuple(pobj->id)); + for(const custom_account_authority_object& cobj : boost::make_iterator_range(crange.first, crange.second)) + { + custom_account_auths.push_back(cobj); + } + return custom_account_auths; +} + +vector database_api::get_active_custom_account_authorities_by_operation(const account_id_type account, int operation_type) const +{ + return my->get_active_custom_account_authorities_by_operation(account, operation_type); +} + +vector database_api_impl::get_active_custom_account_authorities_by_operation(const account_id_type account, int operation_type) const +{ + operation op; + op.set_which(operation_type); + return _db.get_account_custom_authorities(account, op); +} + +// Marketplace +vector database_api::list_offers(const offer_id_type lower_id, uint32_t limit) const +{ + return my->list_offers(lower_id, limit); +} + +vector database_api_impl::list_offers(const offer_id_type lower_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& offers_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = offers_idx.lower_bound(lower_id); + + while(limit-- && itr != offers_idx.end()) + result.emplace_back(*itr++); + + return result; +} + +vector database_api::list_sell_offers(const offer_id_type lower_id, uint32_t limit) const +{ + return my->list_sell_offers(lower_id, limit); +} + +vector database_api_impl::list_sell_offers(const offer_id_type lower_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& offers_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = offers_idx.lower_bound(lower_id); + + while(limit && itr != offers_idx.end()) + { + if(itr->buying_item == false) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + return result; +} + +vector database_api::list_buy_offers(const offer_id_type lower_id, uint32_t limit) const +{ + return my->list_buy_offers(lower_id, limit); +} + +vector database_api_impl::list_buy_offers(const offer_id_type lower_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& offers_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = offers_idx.lower_bound(lower_id); + + while(limit && itr != offers_idx.end()) + { + if(itr->buying_item == true) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + + return result; +} + +vector database_api::list_offer_history(const offer_history_id_type lower_id, uint32_t limit) const +{ + return my->list_offer_history(lower_id, limit); +} + +vector database_api_impl::list_offer_history(const offer_history_id_type lower_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& oh_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = oh_idx.lower_bound(lower_id); + + while(limit-- && itr != oh_idx.end()) + result.emplace_back(*itr++); + + return result; +} + +vector database_api::get_offers_by_issuer(const offer_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const +{ + return my->get_offers_by_issuer(lower_id, issuer_account_id, limit); +} + +vector database_api_impl::get_offers_by_issuer(const offer_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& offers_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + auto itr = offers_idx.lower_bound(lower_id); + while(limit && itr != offers_idx.end()) + { + if(itr->issuer == issuer_account_id) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + return result; +} + +vector database_api::get_offers_by_item(const offer_id_type lower_id, const nft_id_type item, uint32_t limit) const +{ + return my->get_offers_by_item(lower_id, item, limit); +} + +vector database_api_impl::get_offers_by_item(const offer_id_type lower_id, const nft_id_type item, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& offers_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = offers_idx.lower_bound(lower_id); + while(limit && itr != offers_idx.end()) + { + if(itr->item_ids.find(item) != itr->item_ids.end()) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + return result; +} + +vector database_api::get_offer_history_by_issuer(const offer_history_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const +{ + return my->get_offer_history_by_issuer(lower_id, issuer_account_id, limit); +} + +vector database_api::get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const +{ + return my->get_offer_history_by_item(lower_id, item, limit); +} + +vector database_api::get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const +{ + return my->get_offer_history_by_bidder(lower_id, bidder_account_id, limit); +} + +vector database_api_impl::get_offer_history_by_issuer(const offer_history_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& oh_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = oh_idx.lower_bound(lower_id); + + while(limit && itr != oh_idx.end()) + { + if(itr->issuer == issuer_account_id) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + return result; +} + +vector database_api_impl::get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& oh_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = oh_idx.lower_bound(lower_id); + + while(limit && itr != oh_idx.end()) + { + if(itr->item_ids.find(item) != itr->item_ids.end()) + { + result.emplace_back(*itr); + limit--; + } + itr++; + } + + return result; +} + +vector database_api_impl::get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const +{ + FC_ASSERT( limit <= 100 ); + const auto& oh_idx = _db.get_index_type().indices().get(); + vector result; + result.reserve(limit); + + auto itr = oh_idx.lower_bound(lower_id); + + while(limit && itr != oh_idx.end()) + { + if(itr->bidder && *itr->bidder == bidder_account_id) + { + result.emplace_back(*itr); + limit--; + } + 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 dc8aba52..0b141125 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -48,6 +48,11 @@ #include #include +#include +#include +#include +#include + #include #include @@ -709,8 +714,121 @@ class database_api */ gpos_info get_gpos_info(const account_id_type account) const; + ////////// + // RBAC // + ////////// + /** + * @return account and custom permissions/account-authorities info + */ + vector get_custom_permissions(const account_id_type account) const; + fc::optional get_custom_permission_by_name(const account_id_type account, const string& permission_name) const; + vector get_custom_account_authorities(const account_id_type account) const; + vector get_custom_account_authorities_by_permission_id(const custom_permission_id_type permission_id) const; + vector get_custom_account_authorities_by_permission_name(const account_id_type account, const string& permission_name) const; + vector get_active_custom_account_authorities_by_operation(const account_id_type account, int operation_type) const; + ///////// + // NFT // + ///////// + /** + * @brief Returns the number of NFT owned by account + * @param owner Owner account ID + * @return Number of NFTs owned by account + */ + uint64_t nft_get_balance(const account_id_type owner) const; + /** + * @brief Returns the NFT owner + * @param token_id NFT ID + * @return NFT owner account ID + */ + optional nft_owner_of(const nft_id_type token_id) const; + + /** + * @brief Returns the NFT approved account ID + * @param token_id NFT ID + * @return NFT approved account ID + */ + optional nft_get_approved(const nft_id_type token_id) const; + + /** + * @brief Returns operator approved state for all NFT owned by owner + * @param owner NFT owner account ID + * @param token_id NFT ID + * @return True if operator is approved for all NFT owned by owner, else False + */ + bool nft_is_approved_for_all(const account_id_type owner, const account_id_type operator_) const; + + /** + * @brief Returns NFT name from NFT metadata + * @param nft_metadata_id NFT metadata ID + * @return NFT name + */ + string nft_get_name(const nft_metadata_id_type nft_metadata_id) const; + + /** + * @brief Returns NFT symbol from NFT metadata + * @param nft_metadata_id NFT metadata ID + * @return NFT symbol + */ + string nft_get_symbol(const nft_metadata_id_type nft_metadata_id) const; + + /** + * @brief Returns NFT URI + * @param token_id NFT ID + * @return NFT URI + */ + string nft_get_token_uri(const nft_id_type token_id) const; + + /** + * @brief Returns total number of NFTs assigned to NFT metadata + * @param nft_metadata_id NFT metadata ID + * @return Total number of NFTs assigned to NFT metadata + */ + uint64_t nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const; + + /** + * @brief Returns NFT by index from NFT metadata + * @param nft_metadata_id NFT metadata ID + * @param token_idx NFT index in the list of tokens + * @return NFT symbol + */ + nft_object nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const; + + /** + * @brief Returns NFT by owner and index + * @param nft_metadata_id NFT metadata ID + * @param owner NFT owner + * @param token_idx NFT index in the list of tokens + * @return NFT object + */ + nft_object nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const; + + /** + * @brief Returns list of all available NTF's + * @return List of all available NFT's + */ + vector nft_get_all_tokens() const; + + /** + * @brief Returns NFT's owned by owner + * @param owner NFT owner + * @return List of NFT owned by owner + */ + vector nft_get_tokens_by_owner(const account_id_type owner) const; + + ////////////////// + // MARKET PLACE // + ////////////////// + vector list_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_sell_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_buy_offers(const offer_id_type lower_id, uint32_t limit) const; + vector list_offer_history(const offer_history_id_type lower_id, uint32_t limit) const; + vector get_offers_by_issuer(const offer_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const; + vector get_offers_by_item(const offer_id_type lower_id, const nft_id_type item, uint32_t limit) const; + 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; private: std::shared_ptr< database_api_impl > my; }; @@ -848,4 +966,37 @@ FC_API(graphene::app::database_api, // gpos (get_gpos_info) + + //rbac + (get_custom_permissions) + (get_custom_permission_by_name) + (get_custom_account_authorities) + (get_custom_account_authorities_by_permission_id) + (get_custom_account_authorities_by_permission_name) + (get_active_custom_account_authorities_by_operation) + + // NFT + (nft_get_balance) + (nft_owner_of) + (nft_get_approved) + (nft_is_approved_for_all) + (nft_get_name) + (nft_get_symbol) + (nft_get_token_uri) + (nft_get_total_supply) + (nft_token_by_index) + (nft_token_of_owner_by_index) + (nft_get_all_tokens) + (nft_get_tokens_by_owner) + + // Marketplace + (list_offers) + (list_sell_offers) + (list_buy_offers) + (list_offer_history) + (get_offers_by_issuer) + (get_offers_by_item) + (get_offer_history_by_issuer) + (get_offer_history_by_item) + (get_offer_history_by_bidder) ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 07f1ea0a..88d868e5 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -61,6 +61,9 @@ add_library( graphene_chain protocol/vote.cpp protocol/tournament.cpp protocol/small_ops.cpp + protocol/custom_permission.cpp + protocol/custom_account_authority.cpp + protocol/offer.cpp genesis_state.cpp get_config.cpp @@ -112,9 +115,16 @@ add_library( graphene_chain betting_market_evaluator.cpp betting_market_object.cpp betting_market_group_object.cpp + custom_permission_evaluator.cpp + custom_account_authority_evaluator.cpp affiliate_payout.cpp + offer_object.cpp + offer_evaluator.cpp + nft_evaluator.cpp + protocol/nft.cpp + ${HEADERS} ${PROTOCOL_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 82951d79..79ad88ad 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -272,7 +272,7 @@ map< account_id_type, vector< uint16_t > > asset_object::distribute_winners_part reward_op.lottery = get_id(); reward_op.is_benefactor_reward = false; reward_op.winner = holders[winner_num]; - if(db.head_block_time() > HARDFORK_5050_1_TIME && ticket_ids.size() >= winner_num) + if(db.head_block_time() > HARDFORK_5050_1_TIME && ticket_ids.size() > winner_num) { const static_variant tkt_id = ticket_ids[winner_num]; reward_op.winner_ticket_id = tkt_id; diff --git a/libraries/chain/custom_account_authority_evaluator.cpp b/libraries/chain/custom_account_authority_evaluator.cpp new file mode 100644 index 00000000..200590f6 --- /dev/null +++ b/libraries/chain/custom_account_authority_evaluator.cpp @@ -0,0 +1,152 @@ +#include + +#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 + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner_account(d); + const custom_permission_object &pobj = op.permission_id(d); + FC_ASSERT(pobj.account == op.owner_account, "Only owner account can update account authority object"); + FC_ASSERT(op.valid_to > now, "valid_to expiry should be in future"); + FC_ASSERT((op.valid_to - op.valid_from) <= fc::seconds(d.get_global_properties().parameters.rbac_max_account_authority_lifetime()), "Validity of the auth beyond max expiry"); + rbac_operation_hardfork_visitor rvtor(now); + rvtor(op.operation_type); + const auto& cindex = d.get_index_type().indices().get(); + auto count = cindex.count(boost::make_tuple(op.permission_id)); + FC_ASSERT(count < d.get_global_properties().parameters.rbac_max_authorities_per_permission(), "Max operations that can be linked to a permission reached"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type create_custom_account_authority_evaluator::do_apply(const custom_account_authority_create_operation &op) +{ + try + { + database &d = db(); + return d.create([&op](custom_account_authority_object &obj) mutable { + obj.permission_id = op.permission_id; + obj.operation_type = op.operation_type; + obj.valid_from = op.valid_from; + obj.valid_to = op.valid_to; + }) + .id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result update_custom_account_authority_evaluator::do_evaluate(const custom_account_authority_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_account(d); + const custom_account_authority_object &aobj = op.auth_id(d); + const custom_permission_object &pobj = aobj.permission_id(d); + FC_ASSERT(pobj.account == op.owner_account, "Only owner account can update account authority object"); + auto valid_from = aobj.valid_from; + auto valid_to = aobj.valid_to; + if (op.new_valid_from) + { + valid_from = *op.new_valid_from; + } + + if (op.new_valid_to) + { + FC_ASSERT(*op.new_valid_to > now, "New valid_to expiry should be in the future"); + valid_to = *op.new_valid_to; + } + FC_ASSERT(valid_from < valid_to, "valid_from should be before valid_to"); + FC_ASSERT((valid_to - valid_from) <= fc::seconds(d.get_global_properties().parameters.rbac_max_account_authority_lifetime()), "Validity of the auth beyond max expiry"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type update_custom_account_authority_evaluator::do_apply(const custom_account_authority_update_operation &op) +{ + try + { + database &d = db(); + const custom_account_authority_object &aobj = op.auth_id(d); + d.modify(aobj, [&op](custom_account_authority_object &obj) { + if (op.new_valid_from) + obj.valid_from = *op.new_valid_from; + if (op.new_valid_to) + obj.valid_to = *op.new_valid_to; + }); + return op.auth_id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result delete_custom_account_authority_evaluator::do_evaluate(const custom_account_authority_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_account(d); + const custom_account_authority_object &aobj = op.auth_id(d); + const custom_permission_object &pobj = aobj.permission_id(d); + FC_ASSERT(pobj.account == op.owner_account, "Only owner account can delete account authority object"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result delete_custom_account_authority_evaluator::do_apply(const custom_account_authority_delete_operation &op) +{ + try + { + database &d = db(); + const custom_account_authority_object &aobj = op.auth_id(d); + d.remove(aobj); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +} // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/custom_permission_evaluator.cpp b/libraries/chain/custom_permission_evaluator.cpp new file mode 100644 index 00000000..77105e8e --- /dev/null +++ b/libraries/chain/custom_permission_evaluator.cpp @@ -0,0 +1,133 @@ +#include + +#include +#include +#include +#include + +namespace graphene +{ +namespace chain +{ + +void_result create_custom_permission_evaluator::do_evaluate(const custom_permission_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_account(d); + for (const auto &account_weight_pair : op.auth.account_auths) + { + account_weight_pair.first(d); + } + + const auto &pindex = d.get_index_type().indices().get(); + auto pitr = pindex.find(boost::make_tuple(op.owner_account, op.permission_name)); + FC_ASSERT(pitr == pindex.end(), "Permission name already exists for the given account"); + auto count = pindex.count(boost::make_tuple(op.owner_account)); + FC_ASSERT(count < d.get_global_properties().parameters.rbac_max_permissions_per_account(), "Max permissions per account reached"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type create_custom_permission_evaluator::do_apply(const custom_permission_create_operation &op) +{ + try + { + database &d = db(); + return d.create([&op](custom_permission_object &obj) mutable { + obj.account = op.owner_account; + obj.permission_name = op.permission_name; + obj.auth = op.auth; + }) + .id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result update_custom_permission_evaluator::do_evaluate(const custom_permission_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_account(d); + const custom_permission_object &pobj = op.permission_id(d); + FC_ASSERT(pobj.account == op.owner_account, "Only owner account can update permission object"); + if (op.new_auth) + { + FC_ASSERT(!(*op.new_auth == pobj.auth), "New authority provided is not different from old authority"); + for (const auto &account_weight_pair : op.new_auth->account_auths) + { + account_weight_pair.first(d); + } + } + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +object_id_type update_custom_permission_evaluator::do_apply(const custom_permission_update_operation &op) +{ + try + { + database &d = db(); + const custom_permission_object &pobj = op.permission_id(d); + d.modify(pobj, [&op](custom_permission_object &obj) { + if (op.new_auth) + obj.auth = *op.new_auth; + }); + + return op.permission_id; + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result delete_custom_permission_evaluator::do_evaluate(const custom_permission_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_account(d); + const custom_permission_object &pobj = op.permission_id(d); + FC_ASSERT(pobj.account == op.owner_account, "Only owner account can delete permission object"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +void_result delete_custom_permission_evaluator::do_apply(const custom_permission_delete_operation &op) +{ + try + { + database &d = db(); + const custom_permission_object &pobj = op.permission_id(d); + // Remove the account authority objects linked to this permission + const auto& cindex = d.get_index_type().indices().get(); + vector> custom_auths; + auto crange = cindex.equal_range(boost::make_tuple(pobj.id)); + // Store the references to the account authorities + for(const custom_account_authority_object& cobj : boost::make_iterator_range(crange.first, crange.second)) + { + custom_auths.push_back(cobj); + } + // Now remove the account authorities + for(const auto& cauth : custom_auths) + { + d.remove(cauth); + } + // Now finally remove the permission + d.remove(pobj); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) +} + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index b45d922b..e2fc9aab 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -721,6 +721,7 @@ void database::_apply_block( const signed_block& next_block ) update_withdraw_permissions(); update_tournaments(); update_betting_markets(next_block.timestamp); + finalize_expired_offers(); // n.b., update_maintenance_flag() happens this late // because get_slot_time() / get_slot_at_time() is needed above @@ -790,7 +791,10 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx { auto get_active = [&]( account_id_type id ) { return &id(*this).active; }; auto get_owner = [&]( account_id_type id ) { return &id(*this).owner; }; - trx.verify_authority( chain_id, get_active, get_owner, get_global_properties().parameters.max_authority_depth ); + auto get_custom = [&]( account_id_type id, const operation& op ) { + return get_account_custom_authorities(id, op); + }; + trx.verify_authority( chain_id, get_active, get_owner, get_custom, get_global_properties().parameters.max_authority_depth ); } //Skip all manner of expiration and TaPoS checking if we're on block 1; It's impossible that the transaction is diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index a6f7af19..0f7af1a8 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include @@ -159,4 +162,34 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } +vector database::get_account_custom_authorities(account_id_type account, const operation& op)const +{ + const auto& pindex = get_index_type().indices().get(); + const auto& cindex = get_index_type().indices().get(); + auto prange = pindex.equal_range(boost::make_tuple(account)); + time_point_sec now = head_block_time(); + vector custom_auths; + for(const custom_permission_object& pobj : boost::make_iterator_range(prange.first, prange.second)) + { + auto crange = cindex.equal_range(boost::make_tuple(pobj.id, op.which())); + for(const custom_account_authority_object& cobj : boost::make_iterator_range(crange.first, crange.second)) + { + if(now >= cobj.valid_from && now < cobj.valid_to) + { + custom_auths.push_back(pobj.auth); + } + } + } + return custom_auths; +} + +bool database::item_locked(const nft_id_type &item) const +{ + const auto &offer_idx = get_index_type(); + const auto &oidx = dynamic_cast(offer_idx); + const auto &market_items = oidx.get_secondary_index(); + + auto items_itr = market_items._locked_items.find(item); + return (items_itr != market_items._locked_items.end()); +} } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 4e30029b..9ae1fb96 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -49,7 +49,11 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -77,6 +81,10 @@ #include #include #include +#include +#include +#include +#include #include @@ -163,12 +171,20 @@ const uint8_t betting_market_object::type_id; const uint8_t bet_object::space_id; const uint8_t bet_object::type_id; +const uint8_t nft_object::space_id; +const uint8_t nft_object::type_id; + const uint8_t betting_market_position_object::space_id; const uint8_t betting_market_position_object::type_id; const uint8_t global_betting_statistics_object::space_id; const uint8_t global_betting_statistics_object::type_id; +const uint8_t offer_object::space_id; +const uint8_t offer_object::type_id; + +const uint8_t offer_history_object::space_id; +const uint8_t offer_history_object::type_id; void database::initialize_evaluators() { @@ -243,6 +259,22 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -284,6 +316,13 @@ void database::initialize_indexes() tournament_details_idx->add_secondary_index(); add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + auto offer_idx = add_index< primary_index >(); + offer_idx->add_secondary_index(); + + add_index< primary_index >(); + add_index< primary_index >(); //Implementation object indexes add_index< primary_index >(); @@ -313,6 +352,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); } diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 11a2b426..03d2a274 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #define USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // vesting_balance_object by_asset_balance index needed @@ -803,8 +804,6 @@ uint32_t database::get_gpos_current_subperiod() const auto now = this->head_block_time(); auto seconds_since_period_start = now.sec_since_epoch() - period_start.sec_since_epoch(); - FC_ASSERT(period_start <= now && now <= period_end); - // get in what sub period we are uint32_t current_subperiod = 0; std::list period_list(number_of_subperiods); @@ -926,13 +925,22 @@ void rolling_period_start(database& db) if(now.sec_since_epoch() >= (period_start + vesting_period)) { // roll - db.modify(db.get_global_properties(), [now](global_property_object& p) { - p.parameters.extensions.value.gpos_period_start = now.sec_since_epoch(); + db.modify(db.get_global_properties(), [period_start, vesting_period](global_property_object& p) { + p.parameters.extensions.value.gpos_period_start = period_start + vesting_period; }); } } } +void clear_expired_custom_account_authorities(database& db) +{ + const auto& cindex = db.get_index_type().indices().get(); + while(!cindex.empty() && cindex.begin()->valid_to < db.head_block_time()) + { + db.remove(*cindex.begin()); + } +} + // Schedules payouts from a dividend distribution account to the current holders of the // dividend-paying asset. This takes any deposits made to the dividend distribution account // since the last time it was called, and distributes them to the current owners of the @@ -1707,7 +1715,10 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g //for( const asset_bitasset_data_object* d : get_index_type() ) for( const auto& d : get_index_type().indices() ) modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; }); - + // Ideally we have to do this after every block but that leads to longer block applicaiton/replay times. + // So keep it here as it is not critical. valid_to check ensures + // these custom account auths are not usable. + clear_expired_custom_account_authorities(*this); // process_budget needs to run at the bottom because // it needs to know the next_maintenance_time process_budget(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index e91eaa6b..f56e3d8b 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -293,6 +293,57 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } + void operator()( const custom_permission_create_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const custom_permission_update_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const custom_permission_delete_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const custom_account_authority_create_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const custom_account_authority_update_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const custom_account_authority_delete_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const nft_metadata_create_operation& op ) { + _impacted.insert( op.owner ); + } + void operator()( const nft_metadata_update_operation& op ) { + _impacted.insert( op.owner ); + } + void operator()( const nft_mint_operation& op ) { + _impacted.insert( op.owner ); + } + void operator()( const nft_safe_transfer_from_operation& op ) { + _impacted.insert( op.from ); + _impacted.insert( op.to ); + } + void operator()( const nft_approve_operation& op ) { + _impacted.insert( op.operator_ ); + _impacted.insert( op.approved ); + } + void operator()( const nft_set_approval_for_all_operation& op ) { + _impacted.insert( op.owner ); + _impacted.insert( op.operator_ ); + } + void operator()( const offer_operation& op ) { + _impacted.insert( op.issuer ); + } + void operator()( const bid_operation& op ) { + _impacted.insert( op.bidder ); + } + void operator()( const cancel_offer_operation& op ) { + _impacted.insert( op.issuer ); + } + void operator()( const finalize_offer_operation& op ) { + _impacted.insert( op.fee_paying_account ); + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 5c0fbfc9..96f9e3ab 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -705,4 +705,30 @@ void database::update_betting_markets(fc::time_point_sec current_block_time) remove_completed_events(); } +void database::finalize_expired_offers(){ + try { + detail::with_skip_flags( *this, + get_node_properties().skip_flags | skip_authority_check, [&](){ + transaction_evaluation_state cancel_context(this); + + //Cancel expired limit orders + auto& limit_index = get_index_type().indices().get(); + auto itr = limit_index.begin(); + while( itr != limit_index.end() && itr->offer_expiration_date <= head_block_time() ) + { + const offer_object& offer = *itr; + ++itr; + + finalize_offer_operation finalize; + finalize.fee_paying_account = offer.issuer; + finalize.offer_id = offer.id; + finalize.fee = asset( 0, asset_id_type() ); + finalize.result = offer.bidder ? result_type::Expired : result_type::ExpiredNoBid; + + cancel_context.skip_fee_schedule_check = true; + apply_operation(cancel_context, finalize); + } + }); +} FC_CAPTURE_AND_RETHROW()} + } } diff --git a/libraries/chain/hardfork.d/NFT.hf b/libraries/chain/hardfork.d/NFT.hf new file mode 100644 index 00000000..2c4b541d --- /dev/null +++ b/libraries/chain/hardfork.d/NFT.hf @@ -0,0 +1,4 @@ +// NFT HARDFORK Sat, 15-Aug-20 00:00:00 UTC +#ifndef HARDFORK_NFT_TIME +#define HARDFORK_NFT_TIME (fc::time_point_sec( 1597449600 )) +#endif diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 710db6c5..42c2fd13 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -234,3 +234,13 @@ #define GPOS_PERIOD (60*60*24*30*6) // 6 months #define GPOS_SUBPERIOD (60*60*24*30) // 1 month #define GPOS_VESTING_LOCKIN_PERIOD (60*60*24*30) // 1 month + +#define RBAC_MIN_PERMISSION_NAME_LENGTH 3 +#define RBAC_MAX_PERMISSION_NAME_LENGTH 10 +#define RBAC_MAX_PERMISSIONS_PER_ACCOUNT 5 // 5 per account +#define RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME 180*24*60*60 // 6 months +#define RBAC_MAX_AUTHS_PER_PERMISSION 15 // 15 ops linked per permission + +#define NFT_TOKEN_MIN_LENGTH 3 +#define NFT_TOKEN_MAX_LENGTH 15 +#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/custom_account_authority_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_account_authority_evaluator.hpp new file mode 100644 index 00000000..3fe1f6f9 --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_account_authority_evaluator.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +namespace graphene +{ +namespace chain +{ + +class create_custom_account_authority_evaluator : public evaluator +{ +public: + typedef custom_account_authority_create_operation operation_type; + + void_result do_evaluate(const custom_account_authority_create_operation &o); + object_id_type do_apply(const custom_account_authority_create_operation &o); +}; + +class update_custom_account_authority_evaluator : public evaluator +{ +public: + typedef custom_account_authority_update_operation operation_type; + + void_result do_evaluate(const custom_account_authority_update_operation &o); + object_id_type do_apply(const custom_account_authority_update_operation &o); +}; + +class delete_custom_account_authority_evaluator : public evaluator +{ +public: + typedef custom_account_authority_delete_operation operation_type; + + void_result do_evaluate(const custom_account_authority_delete_operation &o); + void_result do_apply(const custom_account_authority_delete_operation &o); +}; + +} // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/custom_account_authority_object.hpp b/libraries/chain/include/graphene/chain/custom_account_authority_object.hpp new file mode 100644 index 00000000..acca8bcf --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_account_authority_object.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class custom_account_authority_object + * @brief Tracks the mappings between permission and operation types. + * @ingroup object + */ + class custom_account_authority_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = custom_account_authority_object_type; + + custom_permission_id_type permission_id; + int operation_type; + time_point_sec valid_from; + time_point_sec valid_to; + }; + + struct by_id; + struct by_permission_and_op; + struct by_expiration; + using custom_account_authority_multi_index_type = multi_index_container< + custom_account_authority_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member, + member + > + >, + ordered_unique, + composite_key, + member + > + > + > + >; + using custom_account_authority_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::custom_account_authority_object, (graphene::db::object), + (permission_id)(operation_type)(valid_from)(valid_to) ) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/custom_permission_evaluator.hpp b/libraries/chain/include/graphene/chain/custom_permission_evaluator.hpp new file mode 100644 index 00000000..c9bc2801 --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_permission_evaluator.hpp @@ -0,0 +1,38 @@ +#pragma once +#include +#include + +namespace graphene +{ +namespace chain +{ + +class create_custom_permission_evaluator : public evaluator +{ +public: + typedef custom_permission_create_operation operation_type; + + void_result do_evaluate(const custom_permission_create_operation &o); + object_id_type do_apply(const custom_permission_create_operation &o); +}; + +class update_custom_permission_evaluator : public evaluator +{ +public: + typedef custom_permission_update_operation operation_type; + + void_result do_evaluate(const custom_permission_update_operation &o); + object_id_type do_apply(const custom_permission_update_operation &o); +}; + +class delete_custom_permission_evaluator : public evaluator +{ +public: + typedef custom_permission_delete_operation operation_type; + + void_result do_evaluate(const custom_permission_delete_operation &o); + void_result do_apply(const custom_permission_delete_operation &o); +}; + +} // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/custom_permission_object.hpp b/libraries/chain/include/graphene/chain/custom_permission_object.hpp new file mode 100644 index 00000000..72789ef4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/custom_permission_object.hpp @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class custom_permission_object + * @brief Tracks all the custom permission of an account. + * @ingroup object + */ + class custom_permission_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = custom_permission_object_type; + + // Account for which this permission is being created + account_id_type account; + // Permission name + string permission_name; + // Authority required for this permission + authority auth; + }; + + struct by_id; + struct by_account_and_permission; + using custom_permission_multi_index_type = multi_index_container< + custom_permission_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member + > + > + > + >; + using custom_permission_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::custom_permission_object, (graphene::db::object), + (account)(permission_name)(auth) ) \ 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 e697b797..8ecb4b91 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -282,6 +282,7 @@ namespace graphene { namespace chain { std::vector get_seeds( asset_id_type for_asset, uint8_t count_winners )const; 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; time_point_sec head_block_time()const; uint32_t head_block_num()const; @@ -294,6 +295,7 @@ namespace graphene { namespace chain { uint32_t last_non_undoable_block_num() const; + vector get_account_custom_authorities(account_id_type account, const operation& op)const; //////////////////// db_init.cpp //////////////////// void initialize_evaluators(); @@ -519,6 +521,7 @@ namespace graphene { namespace chain { void update_betting_markets(fc::time_point_sec current_block_time); bool check_for_blackswan( const asset_object& mia, bool enable_black_swan = true, const asset_bitasset_data_object* bitasset_ptr = nullptr ); + void finalize_expired_offers(); ///Steps performed only at maintenance intervals ///@{ diff --git a/libraries/chain/include/graphene/chain/nft_evaluator.hpp b/libraries/chain/include/graphene/chain/nft_evaluator.hpp new file mode 100644 index 00000000..0d0f5f51 --- /dev/null +++ b/libraries/chain/include/graphene/chain/nft_evaluator.hpp @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + + class nft_metadata_create_evaluator : public evaluator + { + public: + typedef nft_metadata_create_operation operation_type; + void_result do_evaluate( const nft_metadata_create_operation& o ); + object_id_type do_apply( const nft_metadata_create_operation& o ); + }; + + class nft_metadata_update_evaluator : public evaluator + { + public: + typedef nft_metadata_update_operation operation_type; + void_result do_evaluate( const nft_metadata_update_operation& o ); + void_result do_apply( const nft_metadata_update_operation& o ); + }; + + class nft_mint_evaluator : public evaluator + { + public: + typedef nft_mint_operation operation_type; + void_result do_evaluate( const nft_mint_operation& o ); + object_id_type do_apply( const nft_mint_operation& o ); + }; + + class nft_safe_transfer_from_evaluator : public evaluator + { + public: + typedef nft_safe_transfer_from_operation operation_type; + void_result do_evaluate( const nft_safe_transfer_from_operation& o ); + object_id_type do_apply( const nft_safe_transfer_from_operation& o ); + }; + + class nft_approve_evaluator : public evaluator + { + public: + typedef nft_approve_operation operation_type; + void_result do_evaluate( const nft_approve_operation& o ); + object_id_type do_apply( const nft_approve_operation& o ); + }; + + class nft_set_approval_for_all_evaluator : public evaluator + { + public: + typedef nft_set_approval_for_all_operation operation_type; + void_result do_evaluate( const nft_set_approval_for_all_operation& o ); + void_result do_apply( const nft_set_approval_for_all_operation& o ); + }; + +} } // graphene::chain + diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp new file mode 100644 index 00000000..1994a92e --- /dev/null +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -0,0 +1,106 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + class nft_metadata_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = nft_metadata_type; + + account_id_type owner; + std::string name; + std::string symbol; + std::string base_uri; + optional revenue_partner; + optional revenue_split; + bool is_transferable = false; + bool is_sellable = true; + }; + + class nft_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = nft_object_type; + + nft_metadata_id_type nft_metadata_id; + account_id_type owner; + account_id_type approved; + vector approved_operators; + std::string token_uri; + }; + + struct by_name; + struct by_symbol; + using nft_metadata_multi_index_type = multi_index_container< + nft_metadata_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using nft_metadata_index = generic_index; + + struct by_metadata; + struct by_metadata_and_owner; + struct by_owner; + struct by_owner_and_id; + using nft_multi_index_type = multi_index_container< + nft_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member + > + >, + ordered_non_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member + > + > + > + >; + using nft_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object), + (owner) + (name) + (symbol) + (base_uri) + (revenue_partner) + (revenue_split) + (is_transferable) + (is_sellable) ) + +FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), + (nft_metadata_id) + (owner) + (approved) + (approved_operators) + (token_uri) ) + diff --git a/libraries/chain/include/graphene/chain/offer_evaluator.hpp b/libraries/chain/include/graphene/chain/offer_evaluator.hpp new file mode 100644 index 00000000..46f7045b --- /dev/null +++ b/libraries/chain/include/graphene/chain/offer_evaluator.hpp @@ -0,0 +1,47 @@ +#include +#include +#include + +namespace graphene +{ + namespace chain + { + + class offer_evaluator : public evaluator + { + public: + typedef offer_operation operation_type; + + void_result do_evaluate(const offer_operation &o); + object_id_type do_apply(const offer_operation &o); + }; + + class bid_evaluator : public evaluator + { + public: + typedef bid_operation operation_type; + + void_result do_evaluate(const bid_operation &o); + void_result do_apply(const bid_operation &o); + }; + + class cancel_offer_evaluator : public evaluator + { + public: + typedef cancel_offer_operation operation_type; + + void_result do_evaluate(const cancel_offer_operation &o); + void_result do_apply(const cancel_offer_operation &o); + }; + + class finalize_offer_evaluator : public evaluator + { + public: + typedef finalize_offer_operation operation_type; + + void_result do_evaluate(const finalize_offer_operation &op); + void_result do_apply(const finalize_offer_operation &op); + }; + + } // namespace chain +} // namespace graphene diff --git a/libraries/chain/include/graphene/chain/offer_object.hpp b/libraries/chain/include/graphene/chain/offer_object.hpp new file mode 100644 index 00000000..fe176332 --- /dev/null +++ b/libraries/chain/include/graphene/chain/offer_object.hpp @@ -0,0 +1,109 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + class database; + + struct by_expiration_date + { + }; + class offer_object : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = offer_object_type; + + account_id_type issuer; + + set item_ids; + optional bidder; + optional bid_price; + asset minimum_price; + asset maximum_price; + + bool buying_item; + fc::time_point_sec offer_expiration_date; + + offer_id_type get_id() const { return id; } + }; + + class offer_history_object + : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_offer_history_object_type; + + account_id_type issuer; + + set item_ids; + optional bidder; + optional bid_price; + asset minimum_price; + asset maximum_price; + + bool buying_item; + fc::time_point_sec offer_expiration_date; + result_type result; + + offer_history_id_type get_id() const { return id; } + }; + + class offer_item_index : public secondary_index + { + public: + virtual void object_inserted(const object &obj) override; + virtual void object_removed(const object &obj) override; + virtual void about_to_modify(const object &before) override{}; + virtual void object_modified(const object &after) override; + + set _locked_items; + }; + + struct compare_by_expiration_date + { + bool operator()(const fc::time_point_sec &o1, + const fc::time_point_sec &o2) const + { + return o1 < o2; + } + }; + + using offer_multi_index_type = multi_index_container< + offer_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, + member, + compare_by_expiration_date>>>; + + using offer_history_multi_index_type = multi_index_container< + offer_history_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, + member, + compare_by_expiration_date>>>; + + using offer_index = generic_index; + + using offer_history_index = + generic_index; + + } // namespace chain +} // namespace graphene + +FC_REFLECT_DERIVED(graphene::chain::offer_object, (graphene::db::object), + (issuer)(item_ids)(bidder)(bid_price)(minimum_price)( + maximum_price)(buying_item)(offer_expiration_date)) + +FC_REFLECT_DERIVED(graphene::chain::offer_history_object, + (graphene::db::object), + (issuer)(item_ids)(bidder)(bid_price)(minimum_price)( + maximum_price)(buying_item)(offer_expiration_date)(result)) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 5ab8ae7c..ddac3115 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -48,6 +48,10 @@ namespace graphene { namespace chain { optional < uint32_t > gpos_subperiod = GPOS_SUBPERIOD; optional < uint32_t > gpos_period_start = HARDFORK_GPOS_TIME.sec_since_epoch(); optional < uint32_t > gpos_vesting_lockin_period = GPOS_VESTING_LOCKIN_PERIOD; + /* rbac parameters */ + optional < uint16_t > rbac_max_permissions_per_account = RBAC_MAX_PERMISSIONS_PER_ACCOUNT; + optional < uint32_t > rbac_max_account_authority_lifetime = RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME; + optional < uint16_t > rbac_max_authorities_per_permission = RBAC_MAX_AUTHS_PER_PERMISSION; }; struct chain_parameters @@ -138,7 +142,16 @@ namespace graphene { namespace chain { } inline uint32_t gpos_vesting_lockin_period()const { return extensions.value.gpos_vesting_lockin_period.valid() ? *extensions.value.gpos_vesting_lockin_period : GPOS_VESTING_LOCKIN_PERIOD; /// GPOS vesting lockin period - } + } + inline uint16_t rbac_max_permissions_per_account()const { + return extensions.value.rbac_max_permissions_per_account.valid() ? *extensions.value.rbac_max_permissions_per_account : RBAC_MAX_PERMISSIONS_PER_ACCOUNT; + } + inline uint32_t rbac_max_account_authority_lifetime()const { + return extensions.value.rbac_max_account_authority_lifetime.valid() ? *extensions.value.rbac_max_account_authority_lifetime : RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME; + } + inline uint16_t rbac_max_authorities_per_permission()const { + return extensions.value.rbac_max_authorities_per_permission.valid() ? *extensions.value.rbac_max_authorities_per_permission : RBAC_MAX_AUTHS_PER_PERMISSION; + } }; } } // graphene::chain @@ -156,6 +169,9 @@ FC_REFLECT( graphene::chain::parameter_extension, (gpos_subperiod) (gpos_period_start) (gpos_vesting_lockin_period) + (rbac_max_permissions_per_account) + (rbac_max_account_authority_lifetime) + (rbac_max_authorities_per_permission) ) FC_REFLECT( graphene::chain::chain_parameters, diff --git a/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp new file mode 100644 index 00000000..f5f8c1cd --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/custom_account_authority.hpp @@ -0,0 +1,73 @@ +#pragma once +#include + +namespace graphene +{ +namespace chain +{ + +struct custom_account_authority_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; + custom_permission_id_type permission_id; + int operation_type; + time_point_sec valid_from; + time_point_sec valid_to; + account_id_type owner_account; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; +}; + +struct custom_account_authority_update_operation : public base_operation +{ + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + custom_account_authority_id_type auth_id; + optional new_valid_from; + optional new_valid_to; + account_id_type owner_account; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } +}; + +struct custom_account_authority_delete_operation : public base_operation +{ + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + custom_account_authority_id_type auth_id; + account_id_type owner_account; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } +}; + +} // namespace chain +} // 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_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_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 diff --git a/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp new file mode 100644 index 00000000..8093ef07 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/custom_permission.hpp @@ -0,0 +1,70 @@ +#pragma once +#include + +namespace graphene +{ +namespace chain +{ + +struct custom_permission_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_account; + string permission_name; + authority auth; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; +}; + +struct custom_permission_update_operation : public base_operation +{ + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + custom_permission_id_type permission_id; + optional new_auth; + account_id_type owner_account; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } +}; + +struct custom_permission_delete_operation : public base_operation +{ + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + custom_permission_id_type permission_id; + account_id_type owner_account; + + account_id_type fee_payer() const { return owner_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; } +}; + +} // namespace chain +} // 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_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_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 diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp new file mode 100644 index 00000000..41e77b06 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -0,0 +1,135 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + struct nft_metadata_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 symbol; + std::string base_uri; + optional revenue_partner; + optional revenue_split; + bool is_transferable = false; + bool is_sellable = true; + + account_id_type fee_payer()const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct nft_metadata_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type owner; + nft_metadata_id_type nft_metadata_id; + optional name; + optional symbol; + optional base_uri; + optional revenue_partner; + optional revenue_split; + optional is_transferable; + optional is_sellable; + + account_id_type fee_payer()const { return owner; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct nft_mint_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 payer; + + nft_metadata_id_type nft_metadata_id; + account_id_type owner; + account_id_type approved; + vector approved_operators; + std::string token_uri; + + account_id_type fee_payer()const { return payer; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct nft_safe_transfer_from_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 operator_; + + account_id_type from; + account_id_type to; + nft_id_type token_id; + string data; + + account_id_type fee_payer()const { return operator_; } + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct nft_approve_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type operator_; + + account_id_type approved; + nft_id_type token_id; + + account_id_type fee_payer()const { return operator_; } + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct nft_set_approval_for_all_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type owner; + + account_id_type operator_; + bool approved; + + account_id_type fee_payer()const { return owner; } + share_type calculate_fee(const fee_parameters_type &k) const; + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::nft_metadata_create_operation::fee_parameters_type, (fee) (price_per_kbyte) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::nft_mint_operation::fee_parameters_type, (fee) (price_per_kbyte) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_type, (fee) (price_per_kbyte) ) +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) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/offer.hpp b/libraries/chain/include/graphene/chain/protocol/offer.hpp new file mode 100644 index 00000000..2bf3dfc2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/offer.hpp @@ -0,0 +1,143 @@ +#pragma once +#include +#include + +namespace graphene +{ + namespace chain + { + + /* + * @class offer_operation + * @brief To place an offer to buy or sell an item, a user broadcasts a + * proposed transaction + * @ingroup operations + * operation + */ + struct offer_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + }; + asset fee; + set item_ids; + // /** + // * minimum_price.asset_id == maximum_price.asset_id. + // * to set fixed price without auction minimum_price == maximum_price + // * If buying_item is true, and minimum_price != maximum_price, the user is + // proposing a “reverse auction” + // * where bidders can offer to sell the item for progressively lower prices. + // * In this case, minimum_price functions as the sell-it-now price for the + // reverse auction + // */ + account_id_type issuer; + /// minimum_price is minimum bid price. 0 if no minimum_price required + asset minimum_price; + /// buy_it_now price. 0 if no maximum price + asset maximum_price; + /// true means user wants to buy item, false mean user is selling item + bool buying_item; + /// not transaction expiration date + fc::time_point_sec offer_expiration_date; + + /// User provided data encrypted to the memo key of the "to" account + optional memo; + extensions_type extensions; + + account_id_type fee_payer() const { return issuer; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct bid_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type bidder; + + asset bid_price; + offer_id_type offer_id; + + extensions_type extensions; + + account_id_type fee_payer() const { return bidder; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + struct cancel_offer_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + + account_id_type issuer; + offer_id_type offer_id; + + extensions_type extensions; + + account_id_type fee_payer() const { return issuer; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + enum class result_type + { + Expired = 0, + ExpiredNoBid = 1, + Cancelled = 2 + }; + + struct finalize_offer_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = 0; + }; + + asset fee; + account_id_type fee_paying_account; + + offer_id_type offer_id; + + result_type result; + + extensions_type extensions; + + account_id_type fee_payer() const { return fee_paying_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + } // namespace chain +} // namespace graphene + +FC_REFLECT(graphene::chain::offer_operation::fee_parameters_type, + (fee)(price_per_kbyte)); +FC_REFLECT(graphene::chain::offer_operation, + (fee)(item_ids)(issuer)(minimum_price)(maximum_price)(buying_item)(offer_expiration_date)(memo)(extensions)); + +FC_REFLECT(graphene::chain::bid_operation::fee_parameters_type, + (fee)); +FC_REFLECT(graphene::chain::bid_operation, + (fee)(bidder)(bid_price)(offer_id)(extensions)); + +FC_REFLECT(graphene::chain::cancel_offer_operation::fee_parameters_type, + (fee)); +FC_REFLECT(graphene::chain::cancel_offer_operation, + (fee)(issuer)(offer_id)(extensions)); + +FC_REFLECT_ENUM(graphene::chain::result_type, (Expired)(ExpiredNoBid)(Cancelled)); +FC_REFLECT(graphene::chain::finalize_offer_operation::fee_parameters_type, + (fee)); +FC_REFLECT(graphene::chain::finalize_offer_operation, + (fee)(fee_paying_account)(offer_id)(result)(extensions)); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index cb9a83a1..1285d353 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -45,6 +45,10 @@ #include #include #include +#include +#include +#include +#include namespace graphene { namespace chain { @@ -135,7 +139,23 @@ namespace graphene { namespace chain { ticket_purchase_operation, lottery_reward_operation, lottery_end_operation, - sweeps_vesting_claim_operation + sweeps_vesting_claim_operation, + custom_permission_create_operation, + custom_permission_update_operation, + custom_permission_delete_operation, + custom_account_authority_create_operation, + custom_account_authority_update_operation, + custom_account_authority_delete_operation, + offer_operation, + bid_operation, + cancel_offer_operation, + finalize_offer_operation, + nft_metadata_create_operation, + nft_metadata_update_operation, + nft_mint_operation, + nft_safe_transfer_from_operation, + nft_approve_operation, + nft_set_approval_for_all_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/transaction.hpp b/libraries/chain/include/graphene/chain/protocol/transaction.hpp index 2a9909a5..ec8f3f53 100644 --- a/libraries/chain/include/graphene/chain/protocol/transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/transaction.hpp @@ -141,6 +141,7 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; @@ -148,6 +149,7 @@ namespace graphene { namespace chain { const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH )const; /** @@ -162,6 +164,7 @@ namespace graphene { namespace chain { const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH ) const; @@ -194,6 +197,7 @@ namespace graphene { namespace chain { void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion = GRAPHENE_MAX_SIG_CHECK_DEPTH, bool allow_committe = false, const flat_set& active_aprovals = flat_set(), diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 8ea3a8af..6fa2ab4d 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -171,6 +171,11 @@ namespace graphene { namespace chain { betting_market_group_object_type, betting_market_object_type, bet_object_type, + custom_permission_object_type, + custom_account_authority_object_type, + offer_object_type, + nft_metadata_type, + nft_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -199,7 +204,8 @@ namespace graphene { namespace chain { impl_betting_market_position_object_type, impl_global_betting_statistics_object_type, impl_lottery_balance_object_type, - impl_sweeps_vesting_balance_object_type + impl_sweeps_vesting_balance_object_type, + impl_offer_history_object_type }; //typedef fc::unsigned_int object_id_type; @@ -230,6 +236,11 @@ namespace graphene { namespace chain { class betting_market_group_object; class betting_market_object; class bet_object; + class custom_permission_object; + class custom_account_authority_object; + class offer_object; + class nft_metadata_object; + class nft_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; @@ -256,6 +267,11 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type; typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type; typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type; + typedef object_id< protocol_ids, custom_permission_object_type, custom_permission_object> custom_permission_id_type; + typedef object_id< protocol_ids, custom_account_authority_object_type, custom_account_authority_object> custom_account_authority_id_type; + 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; // implementation types class global_property_object; @@ -279,6 +295,7 @@ namespace graphene { namespace chain { class global_betting_statistics_object; class lottery_balance_object; class sweeps_vesting_balance_object; + class offer_history_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; @@ -307,6 +324,7 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_global_betting_statistics_object_type, global_betting_statistics_object > global_betting_statistics_id_type; typedef object_id< implementation_ids, impl_lottery_balance_object_type, lottery_balance_object > lottery_balance_id_type; typedef object_id< implementation_ids, impl_sweeps_vesting_balance_object_type, sweeps_vesting_balance_object> sweeps_vesting_balance_id_type; + typedef object_id< implementation_ids, impl_offer_history_object_type, offer_history_object> offer_history_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -436,6 +454,11 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_group_object_type) (betting_market_object_type) (bet_object_type) + (custom_permission_object_type) + (custom_account_authority_object_type) + (offer_object_type) + (nft_metadata_type) + (nft_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -463,6 +486,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_global_betting_statistics_object_type) (impl_lottery_balance_object_type) (impl_sweeps_vesting_balance_object_type) + (impl_offer_history_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) @@ -489,6 +513,7 @@ FC_REFLECT_TYPENAME( graphene::chain::betting_market_group_id_type ) FC_REFLECT_TYPENAME( graphene::chain::betting_market_id_type ) FC_REFLECT_TYPENAME( graphene::chain::bet_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::offer_id_type ) FC_REFLECT_TYPENAME( graphene::chain::global_property_id_type ) FC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_id_type ) FC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_id_type ) @@ -505,6 +530,11 @@ FC_REFLECT_TYPENAME( graphene::chain::fba_accumulator_id_type ) FC_REFLECT_TYPENAME( graphene::chain::betting_market_position_id_type ) FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::custom_permission_id_type ) +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( graphene::chain::void_t, ) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp new file mode 100644 index 00000000..ace3f91b --- /dev/null +++ b/libraries/chain/nft_evaluator.cpp @@ -0,0 +1,238 @@ +#include +#include +#include + +namespace graphene { namespace chain { + +void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_create_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(db()); + const auto& idx_nft_md_by_name = db().get_index_type().indices().get(); + FC_ASSERT( idx_nft_md_by_name.find(op.name) == idx_nft_md_by_name.end(), "NFT name already in use" ); + const auto& idx_nft_md_by_symbol = db().get_index_type().indices().get(); + FC_ASSERT( idx_nft_md_by_symbol.find(op.symbol) == idx_nft_md_by_symbol.end(), "NFT symbol already in use" ); + FC_ASSERT((op.revenue_partner && op.revenue_split) || (!op.revenue_partner && !op.revenue_split), "NFT revenue partner info invalid"); + if (op.revenue_partner) { + (*op.revenue_partner)(db()); + FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_create_operation& op ) +{ try { + const auto& new_nft_metadata_object = db().create( [&]( nft_metadata_object& obj ){ + obj.owner = op.owner; + obj.name = op.name; + obj.symbol = op.symbol; + obj.base_uri = op.base_uri; + obj.revenue_partner = op.revenue_partner; + obj.revenue_split = op.revenue_split; + obj.is_transferable = op.is_transferable; + obj.is_sellable = op.is_sellable; + }); + return new_nft_metadata_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_update_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(db()); + const auto& idx_nft_md = db().get_index_type().indices().get(); + auto itr_nft_md = idx_nft_md.find(op.nft_metadata_id); + FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" ); + FC_ASSERT( itr_nft_md->owner == op.owner, "Only owner can modify NFT metadata" ); + const auto& idx_nft_md_by_name = db().get_index_type().indices().get(); + const auto& idx_nft_md_by_symbol = db().get_index_type().indices().get(); + if (op.name.valid()) + FC_ASSERT((itr_nft_md->name != *op.name) && (idx_nft_md_by_name.find(*op.name) == idx_nft_md_by_name.end()), "NFT name already in use"); + if (op.symbol.valid()) + FC_ASSERT((itr_nft_md->symbol != *op.symbol) && (idx_nft_md_by_symbol.find(*op.symbol) == idx_nft_md_by_symbol.end()), "NFT symbol already in use"); + FC_ASSERT((op.revenue_partner && op.revenue_split) || (!op.revenue_partner && !op.revenue_split), "NFT revenue partner info invalid"); + if (op.revenue_partner) { + (*op.revenue_partner)(db()); + FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid"); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_operation& op ) +{ try { + db().modify(db().get(op.nft_metadata_id), [&] ( nft_metadata_object& obj ) { + if( op.name.valid() ) + obj.name = *op.name; + if( op.symbol.valid() ) + obj.symbol = *op.symbol; + if( op.base_uri.valid() ) + obj.base_uri = *op.base_uri; + if( op.revenue_partner.valid() ) + obj.revenue_partner = op.revenue_partner; + if( op.revenue_split.valid() ) + obj.revenue_split = op.revenue_split; + if( op.is_transferable.valid() ) + obj.is_transferable = *op.is_transferable; + if( op.is_sellable.valid() ) + obj.is_sellable = *op.is_sellable; + }); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_mint_evaluator::do_evaluate( const nft_mint_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.payer(db()); + op.owner(db()); + op.approved(db()); + for(const auto& op_iter: op.approved_operators) { + op_iter(db()); + } + const auto& idx_nft_md = db().get_index_type().indices().get(); + auto itr_nft_md = idx_nft_md.find(op.nft_metadata_id); + FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" ); + FC_ASSERT( itr_nft_md->owner == op.payer, "Only metadata owner can mint NFT" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type nft_mint_evaluator::do_apply( const nft_mint_operation& op ) +{ try { + const auto& new_nft_object = db().create( [&]( nft_object& obj ){ + obj.nft_metadata_id = op.nft_metadata_id; + obj.owner = op.owner; + obj.approved = op.approved; + obj.approved_operators = op.approved_operators; + obj.token_uri = op.token_uri; + }); + return new_nft_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transfer_from_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + const auto& idx_nft = db().get_index_type().indices().get(); + const auto& idx_acc = db().get_index_type().indices().get(); + + auto itr_nft = idx_nft.find(op.token_id); + FC_ASSERT( itr_nft != idx_nft.end(), "NFT does not exists" ); + + FC_ASSERT(!db().item_locked(op.token_id), "Item(s) is already on sale on market, transfer is not allowed"); + + auto itr_operator = idx_acc.find(op.operator_); + FC_ASSERT( itr_operator != idx_acc.end(), "Operator account does not exists" ); + + auto itr_owner = idx_acc.find(itr_nft->owner); + FC_ASSERT( itr_owner != idx_acc.end(), "Owner account does not exists" ); + + auto itr_from = idx_acc.find(op.from); + FC_ASSERT( itr_from != idx_acc.end(), "Sender account does not exists" ); + FC_ASSERT( itr_from->id == itr_owner->id, "Sender account is not owner of this NFT" ); + + auto itr_to = idx_acc.find(op.to); + FC_ASSERT( itr_to != idx_acc.end(), "Receiver account does not exists" ); + + auto itr_approved_op = std::find(itr_nft->approved_operators.begin(), itr_nft->approved_operators.end(), op.operator_); + FC_ASSERT( (itr_nft->owner == op.operator_) || (itr_nft->approved == itr_operator->id) || (itr_approved_op != itr_nft->approved_operators.end()), "Operator is not NFT owner or approved operator" ); + + const auto& nft_meta_obj = itr_nft->nft_metadata_id(db()); + FC_ASSERT( nft_meta_obj.is_transferable == true, "NFT is not transferable"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transfer_from_operation& op ) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.token_id); + if (itr != idx.end()) + { + db().modify(*itr, [&op](nft_object &obj) { + obj.owner = op.to; + obj.approved = {}; + obj.approved_operators.clear(); + }); + } + return op.token_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_approve_evaluator::do_evaluate( const nft_approve_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + const auto& idx_nft = db().get_index_type().indices().get(); + const auto& idx_acc = db().get_index_type().indices().get(); + + auto itr_nft = idx_nft.find(op.token_id); + FC_ASSERT( itr_nft != idx_nft.end(), "NFT does not exists" ); + + auto itr_owner = idx_acc.find(op.operator_); + FC_ASSERT( itr_owner != idx_acc.end(), "Owner account does not exists" ); + + auto itr_approved = idx_acc.find(op.approved); + FC_ASSERT( itr_approved != idx_acc.end(), "Approved account does not exists" ); + + auto itr_approved_op = std::find(itr_nft->approved_operators.begin(), itr_nft->approved_operators.end(), op.operator_); + FC_ASSERT( (itr_nft->owner == itr_owner->id) || (itr_approved_op != itr_nft->approved_operators.end()), "Sender is not NFT owner or approved operator" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& op ) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.token_id); + if (itr != idx.end()) + { + db().modify(*itr, [&op](nft_object &obj) { + obj.approved = op.approved; + //auto itr = std::find(obj.approved_operators.begin(), obj.approved_operators.end(), op.approved); + //if (itr == obj.approved_operators.end()) { + // obj.approved_operators.push_back(op.approved); + //} + }); + } + return op.token_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_set_approval_for_all_evaluator::do_evaluate( const nft_set_approval_for_all_operation& op ) +{ try { + auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + op.owner(db()); + const auto& idx_acc = db().get_index_type().indices().get(); + + auto itr_operator = idx_acc.find(op.operator_); + FC_ASSERT( itr_operator != idx_acc.end(), "Operator account does not exists" ); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result nft_set_approval_for_all_evaluator::do_apply( const nft_set_approval_for_all_operation& op ) +{ try { + const auto &idx = db().get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(op.owner); + std::for_each(idx_range.first, idx_range.second, [&](const nft_object &obj) { + db().modify(obj, [&op](nft_object &obj) { + auto itr = std::find(obj.approved_operators.begin(), obj.approved_operators.end(), op.operator_); + if ((op.approved) && (itr == obj.approved_operators.end())) { + obj.approved_operators.push_back(op.operator_); + } + if ((!op.approved) && (itr != obj.approved_operators.end())) { + obj.approved_operators.erase(itr); + } + }); + }); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // graphene::chain + diff --git a/libraries/chain/offer_evaluator.cpp b/libraries/chain/offer_evaluator.cpp new file mode 100644 index 00000000..0d1b1947 --- /dev/null +++ b/libraries/chain/offer_evaluator.cpp @@ -0,0 +1,338 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +namespace graphene +{ + namespace chain + { + void_result offer_evaluator::do_evaluate(const offer_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.issuer(d); + for (const auto &item : op.item_ids) + { + const auto &nft_obj = item(d); + FC_ASSERT(!d.item_locked(item), "Item(s) is already on sale"); + bool is_owner = (nft_obj.owner == op.issuer); + bool is_approved = (nft_obj.approved == op.issuer); + bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.issuer) != nft_obj.approved_operators.end()); + if (op.buying_item) + { + FC_ASSERT(!is_owner, "Buyer cannot already be an onwer of the item"); + FC_ASSERT(!is_approved, "Buyer cannot already be approved account of the item"); + FC_ASSERT(!is_approved_operator, "Buyer cannot already be an approved operator of the item"); + } + else + { + FC_ASSERT(is_owner, "Issuer has no authority to sell 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"); + } + FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future"); + FC_ASSERT(op.fee.amount >= 0, "Invalid fee"); + FC_ASSERT(op.minimum_price.amount >= 0 && op.maximum_price.amount > 0, "Invalid amount"); + FC_ASSERT(op.minimum_price.asset_id == op.maximum_price.asset_id, "Asset ID mismatch"); + FC_ASSERT(op.maximum_price >= op.minimum_price, "Invalid max min prices"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + object_id_type offer_evaluator::do_apply(const offer_operation &op) + { + try + { + database &d = db(); + if (op.buying_item) + { + d.adjust_balance(op.issuer, -op.maximum_price); + } + + const auto &offer_obj = db().create([&](offer_object &obj) { + obj.issuer = op.issuer; + + obj.item_ids = op.item_ids; + + obj.minimum_price = op.minimum_price; + obj.maximum_price = op.maximum_price; + + obj.buying_item = op.buying_item; + obj.offer_expiration_date = op.offer_expiration_date; + }); + return offer_obj.id; + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result bid_evaluator::do_evaluate(const bid_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + const auto &offer = op.offer_id(d); + op.bidder(d); + for (const auto &item : offer.item_ids) + { + const auto &nft_obj = item(d); + bool is_owner = (nft_obj.owner == op.bidder); + bool is_approved = (nft_obj.approved == op.bidder); + bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.bidder) != nft_obj.approved_operators.end()); + if (offer.buying_item) + { + FC_ASSERT(is_owner, "Bidder has no authority to sell the item"); + } + else + { + FC_ASSERT(!is_owner, "Bidder cannot already be an onwer of the item"); + 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"); + } + } + + FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch"); + FC_ASSERT(offer.minimum_price.amount == 0 || op.bid_price >= offer.minimum_price); + FC_ASSERT(offer.maximum_price.amount == 0 || op.bid_price <= offer.maximum_price); + if (offer.bidder) + { + FC_ASSERT((offer.buying_item && op.bid_price < *offer.bid_price) || (!offer.buying_item && op.bid_price > *offer.bid_price), "There is already a better bid than this"); + } + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result bid_evaluator::do_apply(const bid_operation &op) + { + try + { + database &d = db(); + + const auto &offer = op.offer_id(d); + + if (!offer.buying_item) + { + if (offer.bidder) + { + d.adjust_balance(*offer.bidder, *offer.bid_price); + } + d.adjust_balance(op.bidder, -op.bid_price); + } + d.modify(op.offer_id(d), [&](offer_object &o) { + if (op.bid_price == (offer.buying_item ? offer.minimum_price : offer.maximum_price)) + { + o.offer_expiration_date = d.head_block_time(); + } + o.bidder = op.bidder; + o.bid_price = op.bid_price; + }); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result cancel_offer_evaluator::do_evaluate(const cancel_offer_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + const auto &offer = op.offer_id(d); + op.issuer(d); + FC_ASSERT(op.issuer == offer.issuer, "Only offer issuer can cancel the offer"); + FC_ASSERT(offer.offer_expiration_date > d.head_block_time(), "Expiration should be in future when cancelling the offer"); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result cancel_offer_evaluator::do_apply(const cancel_offer_operation &op) + { + try + { + database &d = db(); + + const auto &offer = op.offer_id(d); + if (offer.buying_item) + { + // Refund the max price to issuer + d.adjust_balance(offer.issuer, offer.maximum_price); + } + else + { + if (offer.bidder) + { + // Refund the bid price to the best bidder till now + d.adjust_balance(*offer.bidder, *offer.bid_price); + } + } + + d.create([&](offer_history_object &obj) { + obj.issuer = offer.issuer; + obj.item_ids = offer.item_ids; + obj.bidder = offer.bidder; + obj.bid_price = offer.bid_price; + obj.minimum_price = offer.minimum_price; + obj.maximum_price = offer.maximum_price; + obj.buying_item = offer.buying_item; + obj.offer_expiration_date = offer.offer_expiration_date; + obj.result = result_type::Cancelled; + }); + // This should unlock the item + d.remove(op.offer_id(d)); + + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result finalize_offer_evaluator::do_evaluate(const finalize_offer_operation &op) + { + try + { + const database &d = db(); + auto now = d.head_block_time(); + FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF"); + const auto &offer = op.offer_id(d); + + if (op.result != result_type::ExpiredNoBid) + { + FC_ASSERT(offer.bidder, "No valid bidder"); + FC_ASSERT((*offer.bid_price).amount >= 0, "Invalid bid price"); + } + else + { + FC_ASSERT(!offer.bidder, "There should not be a valid bidder"); + } + + switch (op.result) + { + case result_type::Expired: + case result_type::ExpiredNoBid: + FC_ASSERT(offer.offer_expiration_date <= d.head_block_time(), "Offer finalized beyong expiration time"); + break; + default: + FC_THROW_EXCEPTION(fc::assert_exception, "finalize_offer_operation: unknown result type."); + break; + } + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result finalize_offer_evaluator::do_apply(const finalize_offer_operation &op) + { + try + { + database &d = db(); + offer_object offer = op.offer_id(d); + // Calculate the fees for all revenue partners of the items + auto calc_fee = [&](int64_t &tot_fees) { + map fee_map; + for (const auto &item : offer.item_ids) + { + const auto &nft_obj = item(d); + const auto &nft_meta_obj = nft_obj.nft_metadata_id(d); + if (nft_meta_obj.revenue_partner && *nft_meta_obj.revenue_split > 0) + { + const auto &rev_partner = *nft_meta_obj.revenue_partner; + const auto &rev_split = *nft_meta_obj.revenue_split; + int64_t item_fee = static_cast((rev_split * (*offer.bid_price).amount.value / GRAPHENE_100_PERCENT) / offer.item_ids.size()); + const auto &fee_asset = asset(item_fee, (*offer.bid_price).asset_id); + auto ret_val = fee_map.insert({rev_partner, fee_asset}); + if (ret_val.second == false) + { + fee_map[rev_partner] += fee_asset; + } + tot_fees += item_fee; + } + } + return fee_map; + }; + + if (op.result != result_type::ExpiredNoBid) + { + int64_t tot_fees = 0; + auto &&fee_map = calc_fee(tot_fees); + // Transfer all the fee + for (const auto &fee_itr : fee_map) + { + auto &acc = fee_itr.first; + auto &acc_fee = fee_itr.second; + d.adjust_balance(acc, acc_fee); + } + // Calculate the remaining seller amount after the fee is deducted + auto &&seller_amount = *offer.bid_price - asset(tot_fees, (*offer.bid_price).asset_id); + // Buy Offer + if (offer.buying_item) + { + // Send the seller his amount + d.adjust_balance(*offer.bidder, seller_amount); + if (offer.bid_price < offer.maximum_price) + { + // Send the buyer the delta + d.adjust_balance(offer.issuer, offer.maximum_price - *offer.bid_price); + } + } + else + { + // Sell Offer, send the seller his amount + d.adjust_balance(offer.issuer, seller_amount); + } + // Tranfer the NFTs + for (auto item : offer.item_ids) + { + auto &nft_obj = item(d); + d.modify(nft_obj, [&offer](nft_object &obj) { + if (offer.buying_item) + { + obj.owner = offer.issuer; + } + else + { + obj.owner = *offer.bidder; + } + obj.approved = {}; + obj.approved_operators.clear(); + }); + } + } + else + { + if (offer.buying_item) + { + d.adjust_balance(offer.issuer, offer.maximum_price); + } + } + d.create([&](offer_history_object &obj) { + obj.issuer = offer.issuer; + obj.item_ids = offer.item_ids; + obj.bidder = offer.bidder; + obj.bid_price = offer.bid_price; + obj.minimum_price = offer.minimum_price; + obj.maximum_price = offer.maximum_price; + obj.buying_item = offer.buying_item; + obj.offer_expiration_date = offer.offer_expiration_date; + obj.result = op.result; + }); + // This should unlock the item + d.remove(op.offer_id(d)); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/offer_object.cpp b/libraries/chain/offer_object.cpp new file mode 100644 index 00000000..35ac47d2 --- /dev/null +++ b/libraries/chain/offer_object.cpp @@ -0,0 +1,50 @@ +#include +#include + +namespace graphene +{ + namespace chain + { + + void offer_item_index::object_inserted(const object &obj) + { + assert(dynamic_cast(&obj)); + const offer_object &oo = static_cast(obj); + if (!oo.buying_item) + { + for (const auto &id : oo.item_ids) + { + _locked_items.emplace(id); + } + } + } + + void offer_item_index::object_modified(const object &after) + { + assert(dynamic_cast(&after)); + const offer_object &oo = static_cast(after); + if (oo.buying_item && oo.bidder) + { + for (const auto &id : oo.item_ids) + { + _locked_items.emplace(id); + } + } + } + + void offer_item_index::object_removed(const object &obj) + { + assert(dynamic_cast(&obj)); + const offer_object &oo = static_cast(obj); + + if (!oo.buying_item || oo.bidder) + { + for (const auto &id : oo.item_ids) + { + _locked_items.erase(id); + } + } + } + + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 88d985ff..ba714c21 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -132,6 +132,71 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( vbco.balance_type == vesting_balance_type::normal, "balance_type in vesting create not allowed yet!" ); } + void operator()(const custom_permission_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_permission_create_operation not allowed yet!" ); + } + + void operator()(const custom_permission_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_permission_update_operation not allowed yet!" ); + } + + void operator()(const custom_permission_delete_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_permission_delete_operation not allowed yet!" ); + } + + void operator()(const custom_account_authority_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_account_authority_create_operation not allowed yet!" ); + } + + void operator()(const custom_account_authority_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_account_authority_update_operation not allowed yet!" ); + } + + void operator()(const custom_account_authority_delete_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "custom_account_authority_delete_operation not allowed yet!" ); + } + + void operator()(const offer_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "offer_operation not allowed yet!" ); + } + + void operator()(const bid_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "bid_operation not allowed yet!" ); + } + + void operator()(const cancel_offer_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "cancel_offer_operation not allowed yet!" ); + } + + void operator()(const finalize_offer_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "finalize_offer_operation not allowed yet!" ); + } + + void operator()(const nft_metadata_create_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_metadata_create_operation not allowed yet!" ); + } + + void operator()(const nft_metadata_update_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_metadata_update_operation not allowed yet!" ); + } + + void operator()(const nft_mint_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_mint_operation not allowed yet!" ); + } + + void operator()(const nft_safe_transfer_from_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_safe_transfer_from_operation not allowed yet!" ); + } + + void operator()(const nft_approve_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_approve_operation not allowed yet!" ); + } + + void operator()(const nft_set_approval_for_all_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_set_approval_for_all_operation not allowed yet!" ); + } + + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) diff --git a/libraries/chain/proposal_object.cpp b/libraries/chain/proposal_object.cpp index 1d5a8706..a2f6d1ae 100644 --- a/libraries/chain/proposal_object.cpp +++ b/libraries/chain/proposal_object.cpp @@ -36,6 +36,8 @@ bool proposal_object::is_authorized_to_execute(database& db) const available_key_approvals, [&]( account_id_type id ){ return &id(db).active; }, [&]( account_id_type id ){ return &id(db).owner; }, + [&]( account_id_type id, const operation& op ){ + return db.get_account_custom_authorities(id, op); }, db.get_global_properties().parameters.max_authority_depth, true, /* allow committee */ available_active_approvals, diff --git a/libraries/chain/protocol/custom_account_authority.cpp b/libraries/chain/protocol/custom_account_authority.cpp new file mode 100644 index 00000000..a74234d7 --- /dev/null +++ b/libraries/chain/protocol/custom_account_authority.cpp @@ -0,0 +1,43 @@ +#include +#include + +namespace graphene +{ +namespace chain +{ + +void custom_account_authority_create_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); + FC_ASSERT(valid_from < valid_to, "valid_from should be earlier than valid_to"); + FC_ASSERT(operation_type >= 0 && operation_type < operation::count(), "operation_type is not valid"); +} + +void custom_account_authority_update_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); + FC_ASSERT(new_valid_from.valid() || new_valid_to.valid(), "Something must be updated"); + if (new_valid_from && new_valid_to) + { + FC_ASSERT(*new_valid_from < *new_valid_to, "valid_from should be earlier than valid_to"); + } +} + +void custom_account_authority_delete_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); +} + +share_type custom_account_authority_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 ); +} + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/protocol/custom_permission.cpp b/libraries/chain/protocol/custom_permission.cpp new file mode 100644 index 00000000..c0919cbd --- /dev/null +++ b/libraries/chain/protocol/custom_permission.cpp @@ -0,0 +1,85 @@ +#include +#include + +namespace graphene +{ +namespace chain +{ + +bool is_valid_permission_name(const string &name) +{ + try + { + const size_t len = name.size(); + // RBAC_MIN_PERMISSION_NAME_LENGTH <= len minimum length check + if (len < RBAC_MIN_PERMISSION_NAME_LENGTH) + { + return false; + } + // len <= RBAC_MAX_PERMISSION_NAME_LENGTH max length check + if (len > RBAC_MAX_PERMISSION_NAME_LENGTH) + { + return false; + } + // First character should be a letter between a-z + if (!(name[0] >= 'a' && name[0] <= 'z')) + { + return false; + } + // Any character of a permission name should either be a small case letter a-z or a digit 0-9 + for (const auto &ch : name) + { + if (!((ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9'))) + { + return false; + } + } + // Don't accept active and owner permissions as we already have them by default + // This is for removing ambiguity for users, accepting them doesn't create any problems + if (name == "active" || name == "owner") + { + return false; + } + + return true; + } + FC_CAPTURE_AND_RETHROW((name)) +} + +void custom_permission_create_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(is_valid_permission_name(permission_name), "Invalid permission name provided"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); + FC_ASSERT(!auth.is_impossible(), "Impossible authority threshold auth provided"); + FC_ASSERT(auth.address_auths.size() == 0, "Only account and key auths supported"); +} + +void custom_permission_update_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); + FC_ASSERT(new_auth.valid(), "Something must be updated"); + if (new_auth) + { + FC_ASSERT(!new_auth->is_impossible(), "Impossible authority threshold auth provided"); + FC_ASSERT(new_auth->address_auths.size() == 0, "Only account and key auths supported"); + } +} + +void custom_permission_delete_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(owner_account != GRAPHENE_TEMP_ACCOUNT && owner_account != GRAPHENE_COMMITTEE_ACCOUNT && owner_account != GRAPHENE_WITNESS_ACCOUNT && owner_account != GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, + "Custom permissions and account auths cannot be created for special accounts"); +} + +share_type custom_permission_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 ); +} + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/protocol/nft.cpp b/libraries/chain/protocol/nft.cpp new file mode 100644 index 00000000..802bf425 --- /dev/null +++ b/libraries/chain/protocol/nft.cpp @@ -0,0 +1,99 @@ +#include +#include + +namespace graphene +{ +namespace chain +{ + +bool is_valid_nft_token_name(const string &name) +{ + try + { + const size_t len = name.size(); + // NFT_TOKEN_MIN_LENGTH <= len minimum length check + if (len < NFT_TOKEN_MIN_LENGTH) + { + return false; + } + // len <= NFT_TOKEN_MAX_LENGTH max length check + if (len > NFT_TOKEN_MAX_LENGTH) + { + return false; + } + // First character should be a letter between a-z/A-Z + if (!((name[0] >= 'a' && name[0] <= 'z') || (name[0] >= 'A' && name[0] <= 'Z'))) + { + return false; + } + // Any character should either be a small case letter a-z or a digit 0-9 + for (const auto &ch : name) + { + if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == ' '))) + { + return false; + } + } + + return true; + } + FC_CAPTURE_AND_RETHROW((name)) +} + +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 +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + if(name) + 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 +{ + return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); +} + +share_type nft_metadata_update_operation::calculate_fee(const fee_parameters_type &k) const +{ + return k.fee; +} + +share_type nft_mint_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 nft_safe_transfer_from_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 nft_approve_operation::calculate_fee(const fee_parameters_type &k) const +{ + return k.fee; +} + +share_type nft_set_approval_for_all_operation::calculate_fee(const fee_parameters_type &k) const +{ + return k.fee; +} + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/protocol/offer.cpp b/libraries/chain/protocol/offer.cpp new file mode 100644 index 00000000..e83af3f8 --- /dev/null +++ b/libraries/chain/protocol/offer.cpp @@ -0,0 +1,57 @@ +#include +#include + +namespace graphene +{ + namespace chain + { + share_type offer_operation::calculate_fee(const fee_parameters_type &schedule) const + { + return schedule.fee + calculate_data_fee( fc::raw::pack_size(*this), schedule.price_per_kbyte ); + } + + void offer_operation::validate() const + { + FC_ASSERT(item_ids.size() > 0); + FC_ASSERT(fee.amount >= 0); + FC_ASSERT(minimum_price.asset_id == maximum_price.asset_id); + FC_ASSERT(minimum_price.amount >= 0 && maximum_price.amount > 0); + FC_ASSERT(maximum_price >= minimum_price); + } + + share_type bid_operation::calculate_fee(const fee_parameters_type &schedule) const + { + share_type core_fee_required = schedule.fee; + return core_fee_required; + } + + void bid_operation::validate() const + { + FC_ASSERT(fee.amount.value >= 0); + FC_ASSERT(bid_price.amount.value >= 0); + } + + void cancel_offer_operation::validate() const + { + FC_ASSERT(fee.amount.value >= 0); + } + + share_type cancel_offer_operation::calculate_fee(const fee_parameters_type &schedule) const + { + share_type core_fee_required = schedule.fee; + return core_fee_required; + } + + void finalize_offer_operation::validate() const + { + FC_ASSERT(fee.amount.value >= 0); + } + + share_type finalize_offer_operation::calculate_fee(const fee_parameters_type &schedule) const + { + share_type core_fee_required = schedule.fee; + return core_fee_required; + } + + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/protocol/transaction.cpp b/libraries/chain/protocol/transaction.cpp index 093e7833..62419948 100644 --- a/libraries/chain/protocol/transaction.cpp +++ b/libraries/chain/protocol/transaction.cpp @@ -248,6 +248,7 @@ struct sign_state void verify_authority( const vector& ops, const flat_set& sigs, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion_depth, bool allow_committe, const flat_set& active_aprovals, @@ -257,13 +258,6 @@ void verify_authority( const vector& ops, const flat_set required_owner; vector other; - for( const auto& op : ops ) - operation_get_required_authorities( op, required_active, required_owner, other ); - - if( !allow_committe ) - GRAPHENE_ASSERT( required_active.find(GRAPHENE_COMMITTEE_ACCOUNT) == required_active.end(), - invalid_committee_approval, "Committee account may only propose transactions" ); - sign_state s(sigs,get_active); s.max_recursion = max_recursion_depth; for( auto& id : active_aprovals ) @@ -271,6 +265,35 @@ void verify_authority( const vector& ops, const flat_set operation_required_active; + operation_get_required_authorities( op, operation_required_active, required_owner, other ); + + auto itr = operation_required_active.begin(); + while ( itr != operation_required_active.end() ) { + if ( approved_by_custom_authority( *itr, op ) ) + itr = operation_required_active.erase( itr ); + else + ++itr; + } + + required_active.insert( operation_required_active.begin(), operation_required_active.end() ); + } + + if( !allow_committe ) + GRAPHENE_ASSERT( required_active.find(GRAPHENE_COMMITTEE_ACCOUNT) == required_active.end(), + invalid_committee_approval, "Committee account may only propose transactions" ); + + for( const auto& auth : other ) { GRAPHENE_ASSERT( s.check_authority(&auth), tx_missing_other_auth, "Missing Authority", ("auth",auth)("sigs",sigs) ); @@ -325,17 +348,41 @@ set signed_transaction::get_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion_depth )const { flat_set required_active; flat_set required_owner; vector other; - get_required_authorities( required_active, required_owner, other ); const flat_set& signature_keys = get_signature_keys( chain_id ); sign_state s( signature_keys, get_active, available_keys ); s.max_recursion = max_recursion_depth; + auto approved_by_custom_authority = [&s, &get_custom]( + account_id_type account, + operation op ) mutable { + auto custom_auths = get_custom( account, op ); + for( const auto& auth : custom_auths ) + if( s.check_authority( &auth ) ) return true; + return false; + }; + + for( const auto& op : operations ) { + flat_set operation_required_active; + operation_get_required_authorities( op, operation_required_active, required_owner, other ); + + auto itr = operation_required_active.begin(); + while ( itr != operation_required_active.end() ) { + if ( approved_by_custom_authority( *itr, op ) ) + itr = operation_required_active.erase( itr ); + else + ++itr; + } + + required_active.insert( operation_required_active.begin(), operation_required_active.end() ); + } + for( const auto& auth : other ) s.check_authority(&auth); for( auto& owner : required_owner ) @@ -359,10 +406,11 @@ set signed_transaction::minimize_required_signatures( const flat_set& available_keys, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion ) const { - set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner, max_recursion ); + set< public_key_type > s = get_required_signatures( chain_id, available_keys, get_active, get_owner, get_custom, max_recursion ); flat_set< public_key_type > result( s.begin(), s.end() ); for( const public_key_type& k : s ) @@ -370,7 +418,7 @@ set signed_transaction::minimize_required_signatures( result.erase( k ); try { - graphene::chain::verify_authority( operations, result, get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, result, get_active, get_owner, get_custom, max_recursion ); continue; // element stays erased if verify_authority is ok } catch( const tx_missing_owner_auth& e ) {} @@ -385,9 +433,10 @@ void signed_transaction::verify_authority( const chain_id_type& chain_id, const std::function& get_active, const std::function& get_owner, + const std::function(account_id_type, const operation&)>& get_custom, uint32_t max_recursion )const { try { - graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, max_recursion ); + graphene::chain::verify_authority( operations, get_signature_keys( chain_id ), get_active, get_owner, get_custom, max_recursion ); } FC_CAPTURE_AND_RETHROW( (*this) ) } } } // graphene::chain diff --git a/libraries/plugins/debug_witness/debug_witness.cpp b/libraries/plugins/debug_witness/debug_witness.cpp index aea03e32..66ef2f58 100644 --- a/libraries/plugins/debug_witness/debug_witness.cpp +++ b/libraries/plugins/debug_witness/debug_witness.cpp @@ -47,7 +47,7 @@ void debug_witness_plugin::plugin_set_program_options( { auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))); command_line_options.add_options() - ("private-key", bpo::value>()->composing()->multitoken()-> + ("debug-private-key", bpo::value>()->composing()->multitoken()-> DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))), "Tuple of [PublicKey, WIF private key] (may specify multiple times)"); config_file_options.add(command_line_options); @@ -63,9 +63,9 @@ void debug_witness_plugin::plugin_initialize(const boost::program_options::varia ilog("debug_witness plugin: plugin_initialize() begin"); _options = &options; - if( options.count("private-key") ) + if( options.count("debug-private-key") ) { - const std::vector key_id_to_wif_pair_strings = options["private-key"].as>(); + const std::vector key_id_to_wif_pair_strings = options["debug-private-key"].as>(); for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings) { auto key_id_to_wif_pair = graphene::app::dejsonify >(key_id_to_wif_pair_string, GRAPHENE_MAX_NESTED_OBJECTS); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 7f4c7859..7f591328 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1895,6 +1895,224 @@ class wallet_api bool is_gpos, bool broadcast); + signed_transaction create_custom_permission(string owner, + string permission_name, + authority auth, + bool broadcast = true); + signed_transaction update_custom_permission(string owner, + custom_permission_id_type permission_id, + fc::optional new_auth, + bool broadcast = true); + signed_transaction delete_custom_permission(string owner, + custom_permission_id_type permission_id, + bool broadcast = true); + signed_transaction create_custom_account_authority(string owner, + custom_permission_id_type permission_id, + int operation_type, + fc::time_point_sec valid_from, + fc::time_point_sec valid_to, + bool broadcast = true); + signed_transaction update_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + fc::optional new_valid_from, + fc::optional new_valid_to, + bool broadcast = true); + signed_transaction delete_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + bool broadcast = true); + vector get_custom_permissions(string owner) const; + fc::optional get_custom_permission_by_name(string owner, string permission_name) const; + vector get_custom_account_authorities(string owner) const; + vector get_custom_account_authorities_by_permission_id(custom_permission_id_type permission_id) const; + vector get_custom_account_authorities_by_permission_name(string owner, string permission_name) const; + vector get_active_custom_account_authorities_by_operation(string owner, int operation_type) const; + ///////// + // NFT // + ///////// + /** + * @brief Creates NFT metadata + * @param owner_account_id_or_name Owner account ID or name + * @param name Name of the token group + * @param symbol Symbol of the token group + * @param base_uri Base URI for token URI + * @param revenue_partner revenue partner for this type of Token + * @param revenue_split revenue split for the sale + * @param is_transferable can transfer the NFT or not + * @param is_sellable can sell NFT or not + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction transfering the funds + */ + signed_transaction nft_metadata_create(string owner_account_id_or_name, + string name, + string symbol, + string base_uri, + optional revenue_partner, + optional revenue_split, + bool is_transferable, + bool is_sellable, + bool broadcast); + + /** + * @brief Updates NFT metadata + * @param owner_account_id_or_name Owner account ID or name + * @param nft_metadata_id Metadata ID to modify + * @param name Name of the token group + * @param symbol Symbol of the token group + * @param base_uri Base URI for token URI + * @param revenue_partner revenue partner for this type of Token + * @param revenue_split revenue split for the sale + * @param is_transferable can transfer the NFT or not + * @param is_sellable can sell NFT or not + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction transfering the funds + */ + signed_transaction nft_metadata_update(string owner_account_id_or_name, + nft_metadata_id_type nft_metadata_id, + optional name, + optional symbol, + optional base_uri, + optional revenue_partner, + optional revenue_split, + optional is_transferable, + optional is_sellable, + bool broadcast); + + /** + * @brief Creates NFT + * @param metadata_owner_account_id_or_name NFT metadata owner account ID or name + * @param metadata_id NFT metadata ID to which token will belong + * @param owner_account_id_or_name Owner account ID or name + * @param approved_account_id_or_name Approved account ID or name + * @param token_uri Token URI (Will be combined with metadata base_uri if its not empty) + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction transfering the funds + */ + signed_transaction nft_create(string metadata_owner_account_id_or_name, + nft_metadata_id_type metadata_id, + string owner_account_id_or_name, + string approved_account_id_or_name, + string token_uri, + bool broadcast); + + /** + * @brief Returns the number of NFT owned by account + * @param owner_account_id_or_name Owner account ID or name + * @return Number of NFTs owned by account + */ + uint64_t nft_get_balance(string owner_account_id_or_name) const; + + /** + * @brief Returns the NFT owner + * @param token_id NFT ID + * @return NFT owner account ID + */ + optional nft_owner_of(const nft_id_type token_id) const; + + /** + * @brief Transfers NFT safely + * @param operator_account_id_or_name Operators account ID or name + * @param from_account_id_or_name Senders account ID or name + * @param to_account_id_or_name Receivers account ID or name + * @param token_id NFT ID + * @param data Non mandatory data + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction transfering NFT + */ + signed_transaction nft_safe_transfer_from(string operator_account_id_or_name, + string from_account_id_or_name, + string to_account_id_or_name, + nft_id_type token_id, + string data, + bool broadcast); + + /** + * @brief Transfers NFT + * @param operator_account_id_or_name Operators account ID or name + * @param from_account_id_or_name Senders account ID or name + * @param to_account_id_or_name Receivers account ID or name + * @param token_id NFT ID + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction transfering NFT + */ + signed_transaction nft_transfer_from(string operator_account_id_or_name, + string from_account_id_or_name, + string to_account_id_or_name, + nft_id_type token_id, + bool broadcast); + + /** + * @brief Sets approved account for NFT + * @param operator_account_id_or_name Operators account ID or name + * @param approved_account_id_or_name Senders account ID or name + * @param token_id NFT ID + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction setting approving account for NFT + */ + signed_transaction nft_approve(string operator_account_id_or_name, + string approved_account_id_or_name, + nft_id_type token_id, + bool broadcast); + + /** + * @brief Sets approval for all NFT owned by owner + * @param owner_account_id_or_name Owner account ID or name + * @param operator_account_id_or_name Operator account ID or name + * @param approved true if approved + * @param broadcast true to broadcast transaction to the network + * @return Signed transaction setting approvals for all NFT owned by owner + */ + signed_transaction nft_set_approval_for_all(string owner_account_id_or_name, + string operator_account_id_or_name, + bool approved, + bool broadcast); + + /** + * @brief Returns the NFT approved account ID + * @param token_id NFT ID + * @return NFT approved account ID + */ + optional nft_get_approved(const nft_id_type token_id) const; + + /** + * @brief Returns operator approved state for all NFT owned by owner + * @param owner NFT owner account ID + * @param token_id NFT ID + * @return True if operator is approved for all NFT owned by owner, else False + */ + bool nft_is_approved_for_all(string owner_account_id_or_name, string operator_account_id_or_name) const; + + /** + * @brief Returns all tokens + * @return Returns vector of NFT objects, empty vector if none + */ + vector nft_get_all_tokens() const; + + signed_transaction create_offer(set item_ids, + string issuer_accound_id_or_name, + asset minimum_price, + asset maximum_price, + bool buying_item, + time_point_sec offer_expiration_date, + optional memo, + bool broadcast); + signed_transaction create_bid(string bidder_account_id_or_name, + asset bid_price, + offer_id_type offer_id, + bool broadcast); + signed_transaction cancel_offer(string issuer_account_id_or_name, + offer_id_type offer_id, + bool broadcast); + vector list_offers(uint32_t limit, optional lower_id) const; + vector list_sell_offers(uint32_t limit, optional lower_id) const; + vector list_buy_offers(uint32_t limit, optional lower_id) const; + vector list_offer_history(uint32_t limit, optional lower_id) const; + vector get_offers_by_issuer(string issuer_account_id_or_name, + uint32_t limit, optional lower_id) const; + vector get_offers_by_item(const nft_id_type item, uint32_t limit, optional lower_id) const; + vector get_offer_history_by_issuer(string issuer_account_id_or_name, uint32_t limit, optional lower_id) const; + 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; + 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 ); @@ -2145,6 +2363,30 @@ FC_API( graphene::wallet::wallet_api, (tournament_leave) (rps_throw) (create_vesting_balance) + (nft_metadata_create) + (nft_metadata_update) + (nft_create) + (nft_get_balance) + (nft_owner_of) + (nft_safe_transfer_from) + (nft_transfer_from) + (nft_approve) + (nft_set_approval_for_all) + (nft_get_approved) + (nft_is_approved_for_all) + (nft_get_all_tokens) + (create_offer) + (create_bid) + (cancel_offer) + (list_offers) + (list_sell_offers) + (list_buy_offers) + (list_offer_history) + (get_offers_by_issuer) + (get_offers_by_item) + (get_offer_history_by_issuer) + (get_offer_history_by_item) + (get_offer_history_by_bidder) (get_upcoming_tournaments) (get_tournaments) (get_tournaments_by_state) @@ -2157,4 +2399,16 @@ FC_API( graphene::wallet::wallet_api, (get_all_matched_bets_for_bettor) (buy_ticket) (quit) + (create_custom_permission) + (update_custom_permission) + (delete_custom_permission) + (create_custom_account_authority) + (update_custom_account_authority) + (delete_custom_account_authority) + (get_custom_permissions) + (get_custom_permission_by_name) + (get_custom_account_authorities) + (get_custom_account_authorities_by_permission_id) + (get_custom_account_authorities_by_permission_name) + (get_active_custom_account_authorities_by_operation) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 0bbf305a..97b31370 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -3242,6 +3242,140 @@ public: return sign_transaction(tx, broadcast); } + signed_transaction create_custom_permission(string owner, + string permission_name, + authority auth, + bool broadcast) + { + custom_permission_create_operation create_op; + create_op.owner_account = get_account(owner).id; + create_op.permission_name = permission_name; + create_op.auth = auth; + + signed_transaction tx; + tx.operations.push_back(create_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + signed_transaction update_custom_permission(string owner, + custom_permission_id_type permission_id, + fc::optional new_auth, + bool broadcast) + { + custom_permission_update_operation update_op; + update_op.owner_account = get_account(owner).id; + update_op.permission_id = permission_id; + update_op.new_auth = new_auth; + + signed_transaction tx; + tx.operations.push_back(update_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + signed_transaction delete_custom_permission(string owner, + custom_permission_id_type permission_id, + bool broadcast) + { + custom_permission_delete_operation delete_op; + delete_op.owner_account = get_account(owner).id; + delete_op.permission_id = permission_id; + + signed_transaction tx; + tx.operations.push_back(delete_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + signed_transaction create_custom_account_authority(string owner, + custom_permission_id_type permission_id, + int operation_type, + fc::time_point_sec valid_from, + fc::time_point_sec valid_to, + bool broadcast) + { + custom_account_authority_create_operation create_op; + create_op.owner_account = get_account(owner).id; + create_op.permission_id = permission_id; + create_op.operation_type = operation_type; + create_op.valid_from = valid_from; + create_op.valid_to = valid_to; + + signed_transaction tx; + tx.operations.push_back(create_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + signed_transaction update_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + fc::optional new_valid_from, + fc::optional new_valid_to, + bool broadcast) + { + custom_account_authority_update_operation update_op; + update_op.owner_account = get_account(owner).id; + update_op.auth_id = auth_id; + update_op.new_valid_from = new_valid_from; + update_op.new_valid_to = new_valid_to; + + signed_transaction tx; + tx.operations.push_back(update_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + signed_transaction delete_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + bool broadcast) + { + custom_account_authority_delete_operation delete_op; + delete_op.owner_account = get_account(owner).id; + delete_op.auth_id = auth_id; + + signed_transaction tx; + tx.operations.push_back(delete_op); + set_operation_fees(tx, get_global_properties().parameters.current_fees); + tx.validate(); + return sign_transaction(tx, broadcast); + } + + vector get_custom_permissions(string owner) const + { + return _remote_db->get_custom_permissions(get_account(owner).id); + } + + fc::optional get_custom_permission_by_name(string owner, string permission_name) const + { + return _remote_db->get_custom_permission_by_name(get_account(owner).id, permission_name); + } + + vector get_custom_account_authorities(string owner) const + { + return _remote_db->get_custom_account_authorities(get_account(owner).id); + } + + vector get_custom_account_authorities_by_permission_id(custom_permission_id_type permission_id) const + { + return _remote_db->get_custom_account_authorities_by_permission_id(permission_id); + } + + vector get_custom_account_authorities_by_permission_name(string owner, string permission_name) const + { + return _remote_db->get_custom_account_authorities_by_permission_name(get_account(owner).id, permission_name); + } + + vector get_active_custom_account_authorities_by_operation(string owner, int operation_type) const + { + return _remote_db->get_active_custom_account_authorities_by_operation(get_account(owner).id, operation_type); + } + void dbg_make_uia(string creator, string symbol) { asset_options opts; @@ -4541,8 +4675,84 @@ signed_transaction wallet_api::approve_proposal( return my->approve_proposal( fee_paying_account, proposal_id, delta, broadcast ); } +signed_transaction wallet_api::create_custom_permission(string owner, + string permission_name, + authority auth, + bool broadcast) +{ + return my->create_custom_permission(owner, permission_name, auth, broadcast); +} +signed_transaction wallet_api::update_custom_permission(string owner, + custom_permission_id_type permission_id, + fc::optional new_auth, + bool broadcast) +{ + return my->update_custom_permission(owner, permission_id, new_auth, broadcast); +} +signed_transaction wallet_api::delete_custom_permission(string owner, + custom_permission_id_type permission_id, + bool broadcast) +{ + return my->delete_custom_permission(owner, permission_id, broadcast); +} + +signed_transaction wallet_api::create_custom_account_authority(string owner, + custom_permission_id_type permission_id, + int operation_type, + fc::time_point_sec valid_from, + fc::time_point_sec valid_to, + bool broadcast) +{ + return my->create_custom_account_authority(owner, permission_id, operation_type, valid_from, valid_to, broadcast); +} + +signed_transaction wallet_api::update_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + fc::optional new_valid_from, + fc::optional new_valid_to, + bool broadcast) +{ + return my->update_custom_account_authority(owner, auth_id, new_valid_from, new_valid_to, broadcast); +} + +signed_transaction wallet_api::delete_custom_account_authority(string owner, + custom_account_authority_id_type auth_id, + bool broadcast) +{ + return my->delete_custom_account_authority(owner, auth_id, broadcast); +} + +vector wallet_api::get_custom_permissions(string owner) const +{ + return my->get_custom_permissions(owner); +} + +fc::optional wallet_api::get_custom_permission_by_name(string owner, string permission_name) const +{ + return my->get_custom_permission_by_name(owner, permission_name); +} + +vector wallet_api::get_custom_account_authorities(string owner) const +{ + return my->get_custom_account_authorities(owner); +} + +vector wallet_api::get_custom_account_authorities_by_permission_id(custom_permission_id_type permission_id) const +{ + return my->get_custom_account_authorities_by_permission_id(permission_id); +} + +vector wallet_api::get_custom_account_authorities_by_permission_name(string owner, string permission_name) const +{ + return my->get_custom_account_authorities_by_permission_name(owner, permission_name); +} + +vector wallet_api::get_active_custom_account_authorities_by_operation(string owner, int operation_type) const +{ + return my->get_active_custom_account_authorities_by_operation(owner, operation_type); +} global_property_object wallet_api::get_global_properties() const { @@ -6157,6 +6367,358 @@ signed_transaction wallet_api::create_vesting_balance(string owner, return my->sign_transaction( trx, broadcast ); } +signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_name, + string name, + string symbol, + string base_uri, + optional revenue_partner, + optional revenue_split, + bool is_transferable, + bool is_sellable, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + nft_metadata_create_operation op; + op.owner = owner_account.id; + op.name = name; + op.symbol = symbol; + op.base_uri = base_uri; + if( revenue_partner ) + { + account_object partner_account = my->get_account(*revenue_partner); + op.revenue_partner = partner_account.id; + uint16_t rev_split = 0; + if( revenue_split ) + { + rev_split = *revenue_split; + } + op.revenue_split = rev_split; + } + op.is_transferable = is_transferable; + op.is_sellable = is_sellable; + + 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::nft_metadata_update(string owner_account_id_or_name, + nft_metadata_id_type nft_metadata_id, + optional name, + optional symbol, + optional base_uri, + optional revenue_partner, + optional revenue_split, + optional is_transferable, + optional is_sellable, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + nft_metadata_update_operation op; + op.nft_metadata_id = nft_metadata_id; + op.owner = owner_account.id; + op.name = name; + op.symbol = symbol; + op.base_uri = base_uri; + if( revenue_partner ) + { + account_object partner_account = my->get_account(*revenue_partner); + op.revenue_partner = partner_account.id; + uint16_t rev_split = 0; + if( revenue_split ) + { + rev_split = *revenue_split; + } + op.revenue_split = rev_split; + } + op.is_transferable = is_transferable; + op.is_sellable = is_sellable; + + 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::nft_create(string metadata_owner_account_id_or_name, + nft_metadata_id_type metadata_id, + string owner_account_id_or_name, + string approved_account_id_or_name, + string token_uri, + bool broadcast) +{ + account_object metadata_owner_account = my->get_account(metadata_owner_account_id_or_name); + account_object owner_account = my->get_account(owner_account_id_or_name); + account_object approved_account = my->get_account(approved_account_id_or_name); + + nft_mint_operation op; + op.payer = metadata_owner_account.id; + op.nft_metadata_id = metadata_id; + op.owner = owner_account.id; + op.approved = approved_account.id; + op.token_uri = token_uri; + + 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 ); +} + +uint64_t wallet_api::nft_get_balance(string owner_account_id_or_name) const +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + return my->_remote_db->nft_get_balance(owner_account.id); +} + +optional wallet_api::nft_owner_of(const nft_id_type token_id) const +{ + return my->_remote_db->nft_owner_of(token_id); +} + +signed_transaction wallet_api::nft_safe_transfer_from(string operator_account_id_or_name, + string from_account_id_or_name, + string to_account_id_or_name, + nft_id_type token_id, + string data, + bool broadcast) +{ + account_object operator_account = my->get_account(operator_account_id_or_name); + account_object from_account = my->get_account(from_account_id_or_name); + account_object to_account = my->get_account(to_account_id_or_name); + + nft_safe_transfer_from_operation op; + op.operator_ = operator_account.id; + op.from = from_account.id; + op.to = to_account.id; + op.token_id = token_id; + op.data = data; + + 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::nft_transfer_from(string operator_account_id_or_name, + string from_account_id_or_name, + string to_account_id_or_name, + nft_id_type token_id, + bool broadcast) +{ + return nft_safe_transfer_from(operator_account_id_or_name, from_account_id_or_name, to_account_id_or_name, token_id, "", broadcast); +} + +signed_transaction wallet_api::nft_approve(string operator_account_id_or_name, + string approved_account_id_or_name, + nft_id_type token_id, + bool broadcast) +{ + account_object operator_account = my->get_account(operator_account_id_or_name); + account_object approved_account = my->get_account(approved_account_id_or_name); + + nft_approve_operation op; + op.operator_ = operator_account.id; + op.approved = approved_account.id; + op.token_id = token_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 ); +} + +signed_transaction wallet_api::nft_set_approval_for_all(string owner_account_id_or_name, + string operator_account_id_or_name, + bool approved, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + account_object operator_account = my->get_account(operator_account_id_or_name); + + nft_set_approval_for_all_operation op; + op.owner = owner_account.id; + op.operator_ = operator_account.id; + op.approved = approved; + + 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 ); +} + +optional wallet_api::nft_get_approved(const nft_id_type token_id) const +{ + return my->_remote_db->nft_get_approved(token_id); +} + +bool wallet_api::nft_is_approved_for_all(string owner_account_id_or_name, string operator_account_id_or_name) const +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + account_object operator_account = my->get_account(operator_account_id_or_name); + return my->_remote_db->nft_is_approved_for_all(owner_account.id, operator_account.id); +} + +vector wallet_api::nft_get_all_tokens() const +{ + return my->_remote_db->nft_get_all_tokens(); +} + +signed_transaction wallet_api::create_offer(set item_ids, + string issuer_accound_id_or_name, + asset minimum_price, + asset maximum_price, + bool buying_item, + time_point_sec offer_expiration_date, + optional memo, + bool broadcast) +{ + account_object issuer_account = my->get_account(issuer_accound_id_or_name); + + offer_operation op; + op.item_ids = item_ids; + op.issuer = issuer_account.id; + op.minimum_price = minimum_price; + op.maximum_price = maximum_price; + op.buying_item = buying_item; + op.offer_expiration_date = offer_expiration_date; + op.memo = memo; + + 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::create_bid(string bidder_account_id_or_name, + asset bid_price, + offer_id_type offer_id, + bool broadcast) +{ + account_object bidder_account = my->get_account(bidder_account_id_or_name); + + bid_operation op; + op.bidder = bidder_account.id; + op.offer_id = offer_id; + op.bid_price = bid_price; + + 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::cancel_offer(string issuer_account_id_or_name, + offer_id_type offer_id, + bool broadcast) +{ + account_object issuer_account = my->get_account(issuer_account_id_or_name); + + cancel_offer_operation op; + op.issuer = issuer_account.id; + op.offer_id = offer_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::list_offers(uint32_t limit, optional lower_id) const +{ + offer_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->list_offers(lb_id, limit); +} + +vector wallet_api::list_sell_offers(uint32_t limit, optional lower_id) const +{ + offer_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->list_sell_offers(lb_id, limit); +} + +vector wallet_api::list_buy_offers(uint32_t limit, optional lower_id) const +{ + offer_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->list_buy_offers(lb_id, limit); +} + +vector wallet_api::list_offer_history(uint32_t limit, optional lower_id) const +{ + offer_history_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->list_offer_history(lb_id, limit); +} + +vector wallet_api::get_offers_by_issuer(string issuer_account_id_or_name, + uint32_t limit, optional lower_id) const +{ + offer_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + account_object issuer_account = my->get_account(issuer_account_id_or_name); + return my->_remote_db->get_offers_by_issuer(lb_id, issuer_account.id, limit); +} + +vector wallet_api::get_offers_by_item(const nft_id_type item, uint32_t limit, optional lower_id) const +{ + offer_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->get_offers_by_item(lb_id, item, limit); +} + +vector wallet_api::get_offer_history_by_issuer(string issuer_account_id_or_name, uint32_t limit, optional lower_id) const +{ + offer_history_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + account_object issuer_account = my->get_account(issuer_account_id_or_name); + return my->_remote_db->get_offer_history_by_issuer(lb_id, issuer_account.id, limit); +} + +vector wallet_api::get_offer_history_by_item(const nft_id_type item, uint32_t limit, optional lower_id) const +{ + offer_history_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->get_offer_history_by_item(lb_id, item, limit); +} + +vector wallet_api::get_offer_history_by_bidder(string bidder_account_id_or_name, uint32_t limit, optional lower_id) const +{ + offer_history_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + 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); +} // 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 8994b36b..94a3296a 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -43,6 +43,10 @@ #include #include #include +#include +#include +#include +#include #include #include diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index edddfb42..3a381585 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include @@ -307,7 +308,21 @@ void database_fixture::verify_asset_supplies( const database& db ) total_balances[betting_market_group.asset_id] += o.fees_collected; } - + for (const offer_object &o : db.get_index_type().indices()) + { + if (o.buying_item) + { + total_balances[o.maximum_price.asset_id] += o.maximum_price.amount; + } + else + { + if (o.bid_price) + { + total_balances[o.bid_price->asset_id] += o.bid_price->amount; + } + } + } + uint64_t sweeps_vestings = 0; for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() ) sweeps_vestings += svbo.balance; diff --git a/tests/tests/authority_tests.cpp b/tests/tests/authority_tests.cpp index 2afd12a6..a6169489 100644 --- a/tests/tests/authority_tests.cpp +++ b/tests/tests/authority_tests.cpp @@ -1189,6 +1189,14 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) return &(aid(db).owner); } ; + auto get_custom = [&]( + account_id_type id, + const operation& op + ) -> vector + { + return db.get_account_custom_authorities(id, op); + } ; + auto chk = [&]( const signed_transaction& tx, flat_set available_keys, @@ -1196,7 +1204,7 @@ BOOST_FIXTURE_TEST_CASE( get_required_signatures_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); //wdump( (result_set)(ref_set) ); return result_set == ref_set; } ; @@ -1303,6 +1311,14 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) return &(aid(db).owner); } ; + auto get_custom = [&]( + account_id_type id, + const operation& op + ) -> vector + { + return db.get_account_custom_authorities(id, op); + } ; + auto chk = [&]( const signed_transaction& tx, flat_set available_keys, @@ -1310,7 +1326,7 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); + set result_set = tx.get_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); //wdump( (result_set)(ref_set) ); return result_set == ref_set; } ; @@ -1322,7 +1338,7 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) ) -> bool { //wdump( (tx)(available_keys) ); - set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner ); + set result_set = tx.minimize_required_signatures( db.get_chain_id(), available_keys, get_active, get_owner, get_custom ); //wdump( (result_set)(ref_set) ); return result_set == ref_set; } ; @@ -1341,9 +1357,9 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture ) BOOST_CHECK( chk( tx, { alice_public_key, bob_public_key }, { alice_public_key, bob_public_key } ) ); BOOST_CHECK( chk_min( tx, { alice_public_key, bob_public_key }, { alice_public_key } ) ); - GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner ), fc::exception ); + GRAPHENE_REQUIRE_THROW( tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom ), fc::exception ); sign( tx, alice_private_key ); - tx.verify_authority( db.get_chain_id(), get_active, get_owner ); + tx.verify_authority( db.get_chain_id(), get_active, get_owner, get_custom ); } catch(fc::exception& e) { diff --git a/tests/tests/custom_permission_tests.cpp b/tests/tests/custom_permission_tests.cpp new file mode 100644 index 00000000..4aad1897 --- /dev/null +++ b/tests/tests/custom_permission_tests.cpp @@ -0,0 +1,1647 @@ +#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(custom_permission_tests, database_fixture) + +BOOST_AUTO_TEST_CASE(permission_create_fail_test) +{ + try + { + ACTORS((alice)(bob)); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + transfer(committee_account, alice_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + const auto &pidx = db.get_index_type().indices().get(); + { + custom_permission_create_operation op; + op.permission_name = "abc"; + op.owner_account = alice_id; + op.auth = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + // Fail, not RBAC HF time yet + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 0); + } + // alice fails to create custom permission + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + set_expiration(db, trx); + { + custom_permission_create_operation op; + op.owner_account = alice_id; + op.auth = authority(1, bob_id, 1); + op.permission_name = "123"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = ""; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "1ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = ".abc"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "abc."; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "ABC"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "active"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "owner"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "abcdefghijk"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "***"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.permission_name = "a12"; + BOOST_CHECK_NO_THROW(op.validate()); + op.permission_name = "a1b"; + BOOST_CHECK_NO_THROW(op.validate()); + op.permission_name = "abc"; + BOOST_CHECK_NO_THROW(op.validate()); + op.permission_name = "abc123defg"; + BOOST_CHECK_NO_THROW(op.validate()); + BOOST_REQUIRE(pidx.size() == 0); + } + { + custom_permission_create_operation op; + op.permission_name = "abc"; + // No valid auth + BOOST_CHECK_THROW(op.validate(), fc::exception); + const fc::ecc::private_key tpvk = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("test"))); + const public_key_type tpbk(tpvk.get_public_key()); + op.auth = authority(1, address(tpbk), 1); + // Address auth not supported + BOOST_CHECK_THROW(op.validate(), fc::exception); + BOOST_REQUIRE(pidx.size() == 0); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(permission_create_success_test) +{ + try + { + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + set_expiration(db, trx); + ACTORS((alice)(bob)(charlie)(dave)(erin)); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + upgrade_to_lifetime_member(charlie); + upgrade_to_lifetime_member(dave); + upgrade_to_lifetime_member(erin); + transfer(committee_account, alice_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, charlie_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, dave_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, erin_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + const auto &pidx = db.get_index_type().indices().get(); + // Alice creates a permission abc + { + custom_permission_create_operation op; + op.permission_name = "abc"; + op.owner_account = alice_id; + op.auth = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 1); + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, bob_id, 1)); + } + // Alice tries to create a permission with same name but fails + { + custom_permission_create_operation op; + op.permission_name = "abc"; + op.owner_account = alice_id; + op.auth = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 1); + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, bob_id, 1)); + } + // Alice creates a permission def + { + custom_permission_create_operation op; + op.permission_name = "def"; + op.owner_account = alice_id; + op.auth = authority(1, charlie_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(custom_permission_id_type(1)(db).permission_name == "def"); + BOOST_REQUIRE(custom_permission_id_type(1)(db).auth == authority(1, charlie_id, 1)); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(permission_update_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + const auto &pidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + // Alice tries to update permission with same auth but fails + { + custom_permission_update_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = alice_id; + op.new_auth = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice tries to update permission with no auth but fails + { + custom_permission_update_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice tries to update permission with charlie onwer_account but fails + { + custom_permission_update_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = charlie_id; + op.new_auth = authority(1, charlie_id, 1); + trx.operations.push_back(op); + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice updates permission abc with wrong permission_id + { + custom_permission_update_operation op; + op.permission_id = custom_permission_id_type(1); + op.owner_account = alice_id; + op.new_auth = authority(1, charlie_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice updates permission abc with new auth + { + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, bob_id, 1)); + custom_permission_update_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = alice_id; + op.new_auth = authority(1, charlie_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, charlie_id, 1)); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_authority_create_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = db.head_block_time(); + op.valid_to = db.head_block_time() + fc::seconds(10 * db.block_interval()); + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // Alice creates the same account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = db.head_block_time(); + op.valid_to = db.head_block_time() + fc::seconds(11 * db.block_interval()); + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 2); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_authority_update_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(cidx.size() == 2); + generate_block(); + // Alice update the account auth linking with permission abc + { + custom_account_authority_update_operation op; + op.auth_id = custom_account_authority_id_type(0); + fc::time_point_sec expiry = db.head_block_time() + fc::seconds(50 * db.block_interval()); + op.new_valid_to = expiry; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(cidx.size() == 2); + BOOST_REQUIRE(custom_account_authority_id_type(0)(db).valid_to == expiry); + BOOST_REQUIRE(custom_account_authority_id_type(1)(db).valid_to < expiry); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_authority_delete_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(cidx.size() == 2); + generate_block(); + // Alice deletes account auth linking with permission abc + { + custom_account_authority_delete_operation op; + op.auth_id = custom_account_authority_id_type(0); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(cidx.size() == 1); + } + // Alice deletes the account auth linking with permission abc + { + custom_account_authority_delete_operation op; + op.auth_id = custom_account_authority_id_type(1); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(cidx.size() == 0); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(permission_delete_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, bob_id, 1)); + BOOST_REQUIRE(cidx.size() == 2); + // Alice tries to delete permission abc with wrong owner_account + { + custom_permission_delete_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = bob_id; + trx.operations.push_back(op); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice tries to delete permission abc with wrong permission_id + { + custom_permission_delete_operation op; + op.permission_id = custom_permission_id_type(2); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 2); + } + // Alice deletes permission abc + { + custom_permission_delete_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 1); + BOOST_REQUIRE(cidx.size() == 0); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(authority_validity_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + time_point_sec valid_from = db.head_block_time() + fc::seconds(20 * db.block_interval()); + time_point_sec valid_to = db.head_block_time() + fc::seconds(30 * db.block_interval()); + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // alice->bob transfer_operation op with active auth, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // alice->bob fail as block time < valid_from + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + generate_blocks(valid_from); + // alice->bob fail as block time < valid_from + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // time >= valid_from + // alice->bob transfer_operation op with bob active auth sig, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + generate_blocks(valid_to); + // alice->bob fail as block time >= valid_to + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + // alice->bob fail as block time > valid_to + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(transfer_op_custom_permission_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(cidx.size() == 2); + // alice->bob transfer_operation op with active auth, success + generate_block(); + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // alice->bob transfer_operation op with the created custom account auth, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // alice->bob transfer_operation op with extra unnecessary sigs (both active and the custom auth), fails + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + // bob->alice transfer_operation op with alice active auth sig, fails + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = bob_id; + op.to = alice_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + // bob->alice transfer_operation op with bob active auth sig, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = bob_id; + op.to = alice_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Alice deletes permission abc + { + custom_permission_delete_operation op; + op.permission_id = custom_permission_id_type(0); + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 1); + BOOST_REQUIRE(cidx.size() == 0); + generate_block(); + } + // alice->bob transfer_operation op with active auth, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // alice->bob transfer_operation op with the deleted custom account auth, fail + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(transfer_op_auhtorized_auth_change_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(pidx.size() == 2); + BOOST_REQUIRE(cidx.size() == 2); + // alice->bob transfer_operation op with the created custom account auth, success + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // bob changes his auth by changing his auth key + fc::ecc::private_key test_private_key = generate_private_key("test"); + public_key_type test_public_key = public_key_type(test_private_key.get_public_key()); + { + account_update_operation op; + op.account = bob.get_id(); + op.active = authority(1, test_public_key, 1); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // alice->bob transfer_operation op with bob first private key, fails + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + // alice->bob transfer_operation op with bob first private key, fails + { + transfer_operation op; + op.amount.asset_id = asset_id_type(0); + op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + op.from = alice_id; + op.to = bob_id; + op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(op); + sign(trx, test_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(transfer_op_multi_ops_in_single_trx_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + // bob->alice xfer op + transfer_operation bob_to_alice_xfer_op; + bob_to_alice_xfer_op.amount.asset_id = asset_id_type(0); + bob_to_alice_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + bob_to_alice_xfer_op.from = bob_id; + bob_to_alice_xfer_op.to = alice_id; + bob_to_alice_xfer_op.fee.asset_id = asset_id_type(0); + // Change bob's active auth to alice's auth + { + account_update_operation op; + op.account = bob_id; + op.active = authority(1, alice_id, 1); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Success -> alice active key + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Fail -> custom account auth is bob active auth which is alice active key + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + // Success -> bob's active key is alice's auth active key + trx.operations = {bob_to_alice_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> bob's owner key + trx.operations = {bob_to_alice_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> alice active key is auth for both alice and bob + trx.operations = {alice_to_bob_xfer_op, bob_to_alice_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Fail -> custom account auth is bob active auth which is alice active key + trx.operations = {alice_to_bob_xfer_op, bob_to_alice_xfer_op}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + // Fail -> alice active auth satisfies everything, bob owner key is not used + trx.operations = {alice_to_bob_xfer_op, bob_to_alice_xfer_op}; + sign(trx, bob_private_key); + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + // Fail -> extra unnecessary signature of charlie + trx.operations = {alice_to_bob_xfer_op, bob_to_alice_xfer_op}; + sign(trx, bob_private_key); + sign(trx, alice_private_key); + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(transfer_op_multi_sig_with_common_auth_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(dave); + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + // Change alice's active auth to multisig 2-of-3 bob, charlie, dave + { + account_update_operation op; + op.account = alice_id; + op.active = authority(2, bob_id, 1, charlie_id, 1, dave_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Success -> alice owner key + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> alice custom auth is bob + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> 2-of-3 auth satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, charlie_private_key); + sign(trx, dave_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Fail -> Custom auth(bob private key) itself satisfies + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + sign(trx, charlie_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + // Fail -> Custom auth(bob private key) itself satisfies + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + sign(trx, dave_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(transfer_op_multi_sig_with_out_common_auth_test) +{ + try + { + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + set_expiration(db, trx); + ACTORS((alice)(bob)(charlie)(dave)); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + upgrade_to_lifetime_member(charlie); + upgrade_to_lifetime_member(dave); + transfer(committee_account, alice_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, charlie_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, dave_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + fc::ecc::private_key test_private_key = generate_private_key("test"); + public_key_type test_public_key = public_key_type(test_private_key.get_public_key()); + { + custom_permission_create_operation op; + op.permission_name = "abc"; + op.owner_account = alice_id; + op.auth = authority(1, test_public_key, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(pidx.size() == 1); + BOOST_REQUIRE(custom_permission_id_type(0)(db).permission_name == "abc"); + BOOST_REQUIRE(custom_permission_id_type(0)(db).auth == authority(1, test_public_key, 1)); + generate_block(); + } + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = db.head_block_time(); + op.valid_to = db.head_block_time() + fc::seconds(10 * db.block_interval()); + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // Multisig with common account auth + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + // Change alice's active auth to multisig 2-of-3 bob, charlie, dave + { + account_update_operation op; + op.account = alice_id; + op.active = authority(2, bob_id, 1, charlie_id, 1, dave_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Success -> alice owner key + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Fail -> auth not satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + // Success -> custom key auth satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, test_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> 2-of-3 auth satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, charlie_private_key); + sign(trx, dave_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> 2-of-3 auth satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + // Success -> 2-of-3 auth satisfied + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + sign(trx, dave_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(proposal_op_test) +{ + try + { + INVOKE(account_authority_create_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(dave); + generate_block(); + const auto &prop_idx = db.get_index_type().indices().get(); + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + + // bob->alice xfer op + transfer_operation bob_to_alice_xfer_op; + bob_to_alice_xfer_op.amount.asset_id = asset_id_type(0); + bob_to_alice_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + bob_to_alice_xfer_op.from = bob_id; + bob_to_alice_xfer_op.to = alice_id; + bob_to_alice_xfer_op.fee.asset_id = asset_id_type(0); + { + set_expiration(db, trx); + proposal_create_operation prop; + prop.fee_paying_account = alice_id; + prop.proposed_ops = {op_wrapper(alice_to_bob_xfer_op), op_wrapper(bob_to_alice_xfer_op)}; + prop.expiration_time = db.head_block_time() + 21600; + trx.operations = {prop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + proposal_update_operation approve_prop; + approve_prop.proposal = proposal_id_type(0); + approve_prop.fee_paying_account = bob_id; + approve_prop.active_approvals_to_add = {bob_id}; + trx.operations = {approve_prop}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(prop_idx.find(proposal_id_type(0)) == prop_idx.end()); + } + { + set_expiration(db, trx); + custom_account_authority_create_operation authorize_xfer_op; + authorize_xfer_op.permission_id = custom_permission_id_type(0); + authorize_xfer_op.valid_from = db.head_block_time(); + authorize_xfer_op.valid_to = db.head_block_time() + fc::seconds(10 * db.block_interval()); + authorize_xfer_op.operation_type = operation::tag::value; + authorize_xfer_op.owner_account = alice_id; + trx.operations = {authorize_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + proposal_create_operation prop; + prop.fee_paying_account = alice_id; + prop.proposed_ops = {op_wrapper(alice_to_bob_xfer_op), op_wrapper(bob_to_alice_xfer_op)}; + prop.expiration_time = db.head_block_time() + 21600; + trx.operations = {prop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + proposal_update_operation approve_prop; + approve_prop.proposal = proposal_id_type(1); + approve_prop.fee_paying_account = bob_id; + approve_prop.active_approvals_to_add = {bob_id}; + trx.operations = {approve_prop}; + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + + approve_prop.proposal = proposal_id_type(1); + approve_prop.fee_paying_account = bob_id; + approve_prop.active_approvals_to_add = {alice_id, bob_id}; + trx.operations = {approve_prop}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(prop_idx.find(proposal_id_type(1)) == prop_idx.end()); + } + { + set_expiration(db, trx); + custom_account_authority_create_operation authorize_xfer_op; + authorize_xfer_op.permission_id = custom_permission_id_type(1); + authorize_xfer_op.valid_from = db.head_block_time(); + authorize_xfer_op.valid_to = db.head_block_time() + fc::seconds(10 * db.block_interval()); + authorize_xfer_op.operation_type = operation::tag::value; + authorize_xfer_op.owner_account = alice_id; + + proposal_create_operation prop; + prop.fee_paying_account = alice_id; + prop.proposed_ops = {op_wrapper(authorize_xfer_op)}; + prop.expiration_time = db.head_block_time() + 21600; + trx.operations = {prop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + proposal_update_operation approve_prop; + approve_prop.proposal = proposal_id_type(2); + approve_prop.fee_paying_account = alice_id; + approve_prop.active_approvals_to_add = {alice_id}; + trx.operations = {approve_prop}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + BOOST_REQUIRE(prop_idx.find(proposal_id_type(2)) == prop_idx.end()); + + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + + trx.operations = {bob_to_alice_xfer_op}; + sign(trx, alice_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + + trx.operations = {bob_to_alice_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_authority_delete_after_expiry_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + time_point_sec valid_from = db.head_block_time() + fc::seconds(20 * db.block_interval()); + time_point_sec valid_to = db.head_block_time() + fc::seconds(30 * db.block_interval()); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = db.get_dynamic_global_properties().next_maintenance_time; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 2); + generate_block(); + } + generate_blocks(valid_to); + generate_block(); + BOOST_REQUIRE(cidx.size() == 2); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + BOOST_REQUIRE(cidx.size() == 1); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); + BOOST_REQUIRE(cidx.size() == 0); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(account_owner_authority_fail_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + time_point_sec valid_from = db.head_block_time(); + time_point_sec valid_to = db.head_block_time() + fc::seconds(30 * db.block_interval()); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 2); + generate_block(); + } + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + trx.operations.push_back(alice_to_bob_xfer_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + { + account_update_operation op; + op.account = alice_id; + op.owner = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + { + account_update_operation op; + op.account = alice_id; + op.active = authority(1, bob_id, 1); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(multisig_combined_op_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(dave); + GET_ACTOR(erin); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + time_point_sec valid_from = db.head_block_time(); + time_point_sec valid_to = db.head_block_time() + fc::seconds(30 * db.block_interval()); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + { + account_update_operation op; + op.account = alice_id; + op.active = authority(2, bob_id, 1, charlie_id, 1, dave_id, 1); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + // alice account update + account_update_operation auop; + auop.account = alice_id; + auop.active = authority(1, erin_id, 1); + trx.operations = {alice_to_bob_xfer_op, auop}; + sign(trx, bob_private_key); + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, erin_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + { + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(db_api_test) +{ + try + { + INVOKE(permission_create_success_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(dave); + GET_ACTOR(erin); + auto alice_public_key = alice_private_key.get_public_key(); + auto bob_public_key = bob_private_key.get_public_key(); + auto charlie_public_key = charlie_private_key.get_public_key(); + auto dave_public_key = dave_private_key.get_public_key(); + auto erin_public_key = erin_private_key.get_public_key(); + const auto &pidx = db.get_index_type().indices().get(); + const auto &cidx = db.get_index_type().indices().get(); + time_point_sec valid_from = db.head_block_time(); + time_point_sec valid_to = db.head_block_time() + fc::seconds(30 * db.block_interval()); + BOOST_REQUIRE(pidx.size() == 2); + generate_block(); + // alice->bob xfer op + transfer_operation alice_to_bob_xfer_op; + alice_to_bob_xfer_op.amount.asset_id = asset_id_type(0); + alice_to_bob_xfer_op.amount.amount = 100 * GRAPHENE_BLOCKCHAIN_PRECISION; + alice_to_bob_xfer_op.from = alice_id; + alice_to_bob_xfer_op.to = bob_id; + alice_to_bob_xfer_op.fee.asset_id = asset_id_type(0); + // alice account update + account_update_operation auop1; + auop1.account = alice_id; + auop1.active = authority(2, bob_id, 1, charlie_id, 1, dave_id, 1); + // alice account update + account_update_operation auop2; + auop2.account = alice_id; + auop2.active = authority(1, erin_id, 1); + // alice owner update + account_update_operation auop3; + auop3.account = alice_id; + auop3.owner = authority(1, bob_id, 1); + // get_required_signatures Auth Lambdas + set result; + auto get_active_rs = [&](account_id_type aid) -> const authority * { + return &(aid(db).active); + }; + + auto get_owner_rs = [&](account_id_type aid) -> const authority * { + return &(aid(db).owner); + }; + + auto get_custom = [&](account_id_type id, const operation &op) -> vector { + return db.get_account_custom_authorities(id, op); + }; + + // get_potential_signatures Auth lambdas + auto get_active_ps = [&](account_id_type id) -> const authority * { + const auto &auth = id(db).active; + for (const auto &k : auth.get_keys()) + result.insert(k); + return &auth; + }; + + auto get_owner_ps = [&](account_id_type id) -> const authority * { + const auto &auth = id(db).owner; + for (const auto &k : auth.get_keys()) + result.insert(k); + return &auth; + }; + // Transfer before custom account auth creation + { + result.clear(); + trx.operations = {alice_to_bob_xfer_op}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{alice_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{alice_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Alice creates a new account auth linking with permission abc + { + custom_account_authority_create_operation op; + op.permission_id = custom_permission_id_type(0); + op.valid_from = valid_from; + op.valid_to = valid_to; + op.operation_type = operation::tag::value; + op.owner_account = alice_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + BOOST_REQUIRE(cidx.size() == 1); + generate_block(); + } + // Transfer after custom account auth creation + { + result.clear(); + trx.operations = {alice_to_bob_xfer_op}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{alice_public_key, bob_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{bob_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Alice account update after custom account auth creation + { + result.clear(); + trx.operations = {auop1}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{alice_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{alice_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Alice account update and transfer after custom account auth creation + { + result.clear(); + trx.operations = {alice_to_bob_xfer_op, auop2}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{bob_public_key, charlie_public_key, dave_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{bob_public_key, charlie_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, bob_private_key); + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Transfer after alice account update again + { + result.clear(); + trx.operations = {alice_to_bob_xfer_op}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{erin_public_key, bob_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{bob_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, erin_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + // Alice owner auth update + { + result.clear(); + trx.operations = {auop3}; + trx.get_required_signatures( + db.get_chain_id(), + flat_set(), + get_active_ps, + get_owner_ps, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + set exp_result_ps{alice_public_key, erin_public_key}; + BOOST_REQUIRE(result == exp_result_ps); + set exp_result_rs{alice_public_key, erin_public_key}; + set result_rs = trx.get_required_signatures( + db.get_chain_id(), + flat_set(exp_result_ps.begin(), exp_result_ps.end()), + get_active_rs, + get_owner_rs, + get_custom, + db.get_global_properties().parameters.max_authority_depth); + BOOST_REQUIRE(result_rs == exp_result_rs); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + generate_block(); + } + // Transfer with custom account auth + { + trx.operations = {alice_to_bob_xfer_op}; + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + generate_block(); + } + } + FC_LOG_AND_RETHROW() +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/gpos_tests.cpp b/tests/tests/gpos_tests.cpp index aa9969ee..6de53eb7 100644 --- a/tests/tests/gpos_tests.cpp +++ b/tests/tests/gpos_tests.cpp @@ -630,6 +630,7 @@ BOOST_AUTO_TEST_CASE( voting ) generate_blocks( HARDFORK_GPOS_TIME ); generate_block(); + auto now = HARDFORK_GPOS_TIME; const auto& core = asset_id_type()(db); // send some asset to alice and bob @@ -651,7 +652,6 @@ BOOST_AUTO_TEST_CASE( voting ) BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), HARDFORK_GPOS_TIME.sec_since_epoch()); // update default gpos for test speed - auto now = db.head_block_time(); // 5184000 = 60x60x24x60 = 60 days // 864000 = 60x60x24x10 = 10 days update_gpos_global(5184000, 864000, now); @@ -754,7 +754,7 @@ BOOST_AUTO_TEST_CASE( voting ) advance_x_maint(5); // a new GPOS period is in but vote from user is before the start. Whoever votes in 6th sub-period, votes will carry now = db.head_block_time(); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), now.sec_since_epoch()); + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.gpos_period_start(), HARDFORK_GPOS_TIME.sec_since_epoch() + db.get_global_properties().parameters.gpos_period()); generate_block(); diff --git a/tests/tests/marketplace_tests.cpp b/tests/tests/marketplace_tests.cpp new file mode 100644 index 00000000..bbde669c --- /dev/null +++ b/tests/tests/marketplace_tests.cpp @@ -0,0 +1,941 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(marketplace_tests, database_fixture) +offer_id_type buy_offer; +offer_id_type sell_offer; +BOOST_AUTO_TEST_CASE(nft_metadata_create_test) +{ + + BOOST_TEST_MESSAGE("nft_metadata_create_test"); + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + set_expiration(db, trx); + + ACTORS((mdowner)); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); + + nft_metadata_create_operation op; + op.owner = mdowner_id; + op.name = "NFT Test"; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.revenue_partner = mdowner_id; + op.revenue_split = 1000; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + + 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 == mdowner_id); + BOOST_CHECK(obj->name == "NFT Test"); + BOOST_CHECK(obj->symbol == "NFT"); + BOOST_CHECK(obj->base_uri == "http://nft.example.com"); +} + +BOOST_AUTO_TEST_CASE(nft_mint_test) +{ + + BOOST_TEST_MESSAGE("nft_mint_test"); + + INVOKE(nft_metadata_create_test); + set_expiration(db, trx); + + ACTORS((alice)(bob)(charlie)(operator1)(operator2)); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + upgrade_to_lifetime_member(charlie); + transfer(committee_account, alice_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, charlie_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + GET_ACTOR(mdowner); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation"); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto nft_md_obj = idx.begin(); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + op.approved = alice_id; + op.approved_operators.push_back(operator1_id); + op.approved_operators.push_back(operator2_id); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + + 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 == alice_id); + BOOST_CHECK(obj->approved_operators.size() == 2); + BOOST_CHECK(obj->approved_operators.at(0) == operator1_id); + BOOST_CHECK(obj->approved_operators.at(1) == operator2_id); + + { + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto nft_md_obj = idx.begin(); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + op.approved = alice_id; + op.approved_operators.push_back(operator1_id); + op.approved_operators.push_back(operator2_id); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + BOOST_REQUIRE(idx.size() == 2); + obj = idx.begin(); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->owner == alice_id); + BOOST_CHECK(obj->approved_operators.size() == 2); + BOOST_CHECK(obj->approved_operators.at(0) == operator1_id); + BOOST_CHECK(obj->approved_operators.at(1) == operator2_id); + const auto &nft2 = nft_id_type(1)(db); + BOOST_CHECK(nft2.owner == alice_id); + BOOST_CHECK(nft2.approved_operators.size() == 2); + BOOST_CHECK(nft2.approved_operators.at(0) == operator1_id); + BOOST_CHECK(nft2.approved_operators.at(1) == operator2_id); +} + +BOOST_AUTO_TEST_CASE(create_sell_offer_test) +{ + try + { + INVOKE(nft_mint_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + const asset_object &bitusd = create_bitasset("STUB"); + { + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.item_ids.emplace(nft_id_type(1)); + offer_op.issuer = alice_id; + offer_op.buying_item = false; + offer_op.maximum_price = asset(10000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + trx.operations.push_back(offer_op); + auto op = trx.operations.back().get(); + REQUIRE_THROW_WITH_VALUE(op, offer_expiration_date, db.head_block_time()); + REQUIRE_THROW_WITH_VALUE(op, issuer, bob_id); + // positive prices + REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(-1)); + REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(-1)); + REQUIRE_OP_VALIDATION_FAILURE(op, fee, asset(-1)); + // min price > max price check + REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(1)); + // different asset for min/max + REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(1, bitusd.id)); + + trx.clear(); + trx.operations.push_back(offer_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + //generate_block(); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + const offer_object &d = offer_id_type(0)(db); + + BOOST_CHECK(d.space_id == protocol_ids); + BOOST_CHECK(d.type_id == offer_object_type); + //empty bid + BOOST_CHECK(!d.bid_price); + BOOST_CHECK(!d.bidder); + // data integrity + BOOST_CHECK(d.issuer == alice_id); + BOOST_CHECK(d.maximum_price == asset(10000)); + BOOST_CHECK(d.minimum_price == asset(10)); + BOOST_CHECK(d.buying_item == false); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == true); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == true); + sell_offer = d.id; + } + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(buy_bid_for_sell_offer_test) +{ + try + { + INVOKE(create_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount + 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + asset exp_delta_bidder = -bid_op.bid_price; + int64_t bidder_balance = get_balance(bob_id(db), asset_id_type()(db)); + + auto op = trx.operations.back().get(); + // Positive asset values + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(-1, asset_id_type())); + // Max price limit + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(offer_obj.maximum_price.amount + 1, offer_obj.minimum_price.asset_id)); + // Min Price Limit + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(offer_obj.minimum_price.amount - 1, offer_obj.minimum_price.asset_id)); + // Invalid offer + REQUIRE_THROW_WITH_VALUE(op, offer_id, offer_id_type(6)); + // Owner bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, alice_id); + // Operator bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, operator1_id); + // Different asset + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(50, asset_id_type(1))); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bidder_balance + exp_delta_bidder.amount).value); + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(second_buy_bid_for_sell_offer_test) +{ + try + { + INVOKE(buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t charlie_balance = get_balance(charlie_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset((*offer_obj.bid_price).amount + 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = charlie_id; + trx.operations.push_back(bid_op); + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = *offer_obj.bid_price; + asset exp_delta_bidder2 = -bid; + + auto op = trx.operations.back().get(); + // Not a better bid than previous + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset((*offer_obj.bid_price).amount, offer_obj.minimum_price.asset_id)); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder1.amount).value); + BOOST_CHECK_EQUAL(get_balance(charlie_id(db), asset_id_type()(db)), + (charlie_balance + exp_delta_bidder2.amount).value); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + + // data integrity + BOOST_CHECK(offer_obj.bidder == charlie_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(best_buy_bid_for_sell_offer) +{ + try + { + INVOKE(second_buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + GET_ACTOR(mdowner); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t charlie_balance = get_balance(charlie_id(db), asset_id_type()(db)); + int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.maximum_price.amount, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = *offer_obj.bid_price; + asset exp_delta_bidder2 = -bid; + + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + // Check balances + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder2.amount).value); + BOOST_CHECK_EQUAL(get_balance(charlie_id(db), asset_id_type()(db)), + (charlie_balance + exp_delta_bidder1.amount).value); + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + auto cached_offer_obj = offer_obj; + // Generate a block and offer should be finalized with bid + generate_block(); + int64_t partner_fee = 2 * static_cast((0.1 * (*cached_offer_obj.bid_price).amount.value)/2); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount).value - partner_fee); + BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)), + mdowner_balance + partner_fee); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == bob_id) && (nft_id_type(1)(db).owner == bob_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Expired == history_obj.result); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(expire_with_bid_for_sell_offer_test) +{ + INVOKE(second_buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(charlie); + GET_ACTOR(mdowner); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + int64_t partner_fee = 2 * static_cast((0.1 * (*cached_offer_obj.bid_price).amount.value)/2); + BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)), + mdowner_balance + partner_fee); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + (*cached_offer_obj.bid_price).amount).value - partner_fee); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == charlie_id) && (nft_id_type(1)(db).owner == charlie_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Expired == history_obj.result); +} + +BOOST_AUTO_TEST_CASE(expire_no_bid_for_sell_offer_test) +{ + INVOKE(create_sell_offer_test); + GET_ACTOR(alice); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + alice_balance); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::ExpiredNoBid == history_obj.result); +} + +BOOST_AUTO_TEST_CASE(create_buy_offer_test) +{ + try + { + INVOKE(best_buy_bid_for_sell_offer); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + { + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.item_ids.emplace(nft_id_type(1)); + offer_op.issuer = alice_id; + offer_op.buying_item = true; + offer_op.maximum_price = asset(11000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + trx.operations.push_back(offer_op); + auto op = trx.operations.back().get(); + REQUIRE_THROW_WITH_VALUE(op, issuer, bob_id); + + trx.clear(); + trx.operations.push_back(offer_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + + asset exp_delta_bidder2 = -offer_op.maximum_price; + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + exp_delta_bidder2.amount).value); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + const offer_object &d = offer_id_type(1)(db); + + BOOST_CHECK(d.space_id == protocol_ids); + BOOST_CHECK(d.type_id == offer_object_type); + // empty bid + BOOST_CHECK(!d.bid_price); + BOOST_CHECK(!d.bidder); + // data integrity + BOOST_CHECK(d.issuer == alice_id); + BOOST_CHECK(d.maximum_price == asset(11000)); + BOOST_CHECK(d.minimum_price == asset(10)); + BOOST_CHECK(d.buying_item == true); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + buy_offer = d.id; + } + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(sell_bid_for_buy_offer_test) +{ + try + { + INVOKE(create_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount + 2, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + auto op = trx.operations.back().get(); + // Non Owner bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, alice_id); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(second_sell_bid_for_buy_offer_test) +{ + try + { + INVOKE(sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset((*offer_obj.bid_price).amount - 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + auto op = trx.operations.back().get(); + // Not a better bid than previous + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset((*offer_obj.bid_price).amount, offer_obj.minimum_price.asset_id)); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(best_sell_bid_for_buy_offer) +{ + try + { + INVOKE(second_sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(mdowner); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = offer_obj.minimum_price; + asset exp_delta_bidder2 = offer_obj.maximum_price - offer_obj.minimum_price; + + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + auto cached_offer_obj = offer_obj; + // Generate a block and offer should be finalized with bid + generate_block(); + // Check balances + int64_t partner_fee = 2 * static_cast((0.1 * (*cached_offer_obj.bid_price).amount.value)/2); + BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)), + mdowner_balance + partner_fee); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder1.amount).value - partner_fee); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + exp_delta_bidder2.amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Expired == history_obj.result); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(expire_with_bid_for_buy_offer_test) +{ + INVOKE(second_sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(mdowner); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + int64_t partner_fee = 2 * static_cast((0.1 * (*cached_offer_obj.bid_price).amount.value)/2); + BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)), + mdowner_balance + partner_fee); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount - (*cached_offer_obj.bid_price).amount).value); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + (*cached_offer_obj.bid_price).amount).value - partner_fee); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Expired == history_obj.result); +} + +BOOST_AUTO_TEST_CASE(expire_no_bid_for_buy_offer_test) +{ + INVOKE(create_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == bob_id) && (nft_id_type(1)(db).owner == bob_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::ExpiredNoBid == history_obj.result); +} + +BOOST_AUTO_TEST_CASE(cancel_sell_offer_no_bid_test) +{ + try + { + INVOKE(create_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + + cancel_offer_operation cancel_op; + cancel_op.offer_id = offer_obj.id; + // Add non-issuer + cancel_op.issuer = bob_id; + trx.clear(); + trx.operations.push_back(cancel_op); + sign(trx, bob_private_key); + BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception); + trx.clear(); + // Add issuer + cancel_op.issuer = alice_id; + trx.operations.push_back(cancel_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Cancelled == history_obj.result); + + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(cancel_sell_offer_with_bid_test) +{ + try + { + INVOKE(buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + + cancel_offer_operation cancel_op; + cancel_op.offer_id = offer_obj.id; + // Add issuer + cancel_op.issuer = alice_id; + trx.clear(); + trx.operations.push_back(cancel_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + (*cached_offer_obj.bid_price).amount).value); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Cancelled == history_obj.result); + + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(cancel_buy_offer_with_bid_test) +{ + try + { + INVOKE(sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = buy_offer(db); + auto cached_offer_obj = offer_obj; + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + + cancel_offer_operation cancel_op; + cancel_op.offer_id = offer_obj.id; + cancel_op.issuer = alice_id; + + trx.clear(); + trx.operations.push_back(cancel_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + + generate_block(); + + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount).value); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + BOOST_CHECK(result_type::Cancelled == history_obj.result); + + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp new file mode 100644 index 00000000..b8fd19ea --- /dev/null +++ b/tests/tests/nft_tests.cpp @@ -0,0 +1,412 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( nft_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) { + + BOOST_TEST_MESSAGE("nft_metadata_create_test"); + generate_blocks(HARDFORK_NFT_TIME); + generate_block(); + generate_block(); + set_expiration(db, trx); + + ACTORS((mdowner)); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); + + nft_metadata_create_operation op; + op.owner = mdowner_id; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.name = "123"; + op.is_transferable = true; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ""; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "1ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ".abc"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "abc."; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ABC"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abcdefghijklmnopq"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "***"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "a12"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "a1b"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc123defg12345"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "NFT Test"; + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + + 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 == mdowner_id ); + BOOST_CHECK( obj->name == "NFT Test" ); + BOOST_CHECK( obj->symbol == "NFT" ); + BOOST_CHECK( obj->base_uri == "http://nft.example.com" ); +} + + +BOOST_AUTO_TEST_CASE( nft_metadata_update_test ) { + + BOOST_TEST_MESSAGE("nft_metadata_update_test"); + + INVOKE(nft_metadata_create_test); + + GET_ACTOR(mdowner); + + { + BOOST_TEST_MESSAGE("Send nft_metadata_update_operation"); + + nft_metadata_update_operation op; + op.owner = mdowner_id; + op.name = "New NFT Test"; + op.symbol = "New NFT"; + op.base_uri = "new http://nft.example.com"; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_metadata_update_operation results"); + + 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 == mdowner_id ); + BOOST_CHECK( obj->name == "New NFT Test" ); + BOOST_CHECK( obj->symbol == "New NFT" ); + BOOST_CHECK( obj->base_uri == "new http://nft.example.com" ); +} + + +BOOST_AUTO_TEST_CASE( nft_mint_test ) { + + BOOST_TEST_MESSAGE("nft_mint_test"); + + generate_block(); + set_expiration(db, trx); + + INVOKE(nft_metadata_create_test); + + ACTORS((alice)); + ACTORS((bob)); + ACTORS((operator1)); + ACTORS((operator2)); + + GET_ACTOR(mdowner); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto nft_md_obj = idx.begin(); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + op.approved = alice_id; + op.approved_operators.push_back(operator1_id); + op.approved_operators.push_back(operator2_id); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + + 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 == alice_id ); + BOOST_CHECK( obj->approved_operators.size() == 2 ); + BOOST_CHECK( obj->approved_operators.at(0) == operator1_id ); + BOOST_CHECK( obj->approved_operators.at(1) == operator2_id ); +} + + +BOOST_AUTO_TEST_CASE( nft_safe_transfer_from_test ) { + + BOOST_TEST_MESSAGE("nft_safe_transfer_from_test"); + + INVOKE(nft_mint_test); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + { + BOOST_TEST_MESSAGE("Check nft_safe_transfer_operation preconditions"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.begin(); + BOOST_REQUIRE( obj->owner == alice_id ); + } + + { + BOOST_TEST_MESSAGE("Send nft_safe_transfer_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, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check nft_safe_transfer_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.begin(); + BOOST_REQUIRE( obj->owner == bob_id ); + } +} + +BOOST_AUTO_TEST_CASE( nft_approve_operation_test ) { + + BOOST_TEST_MESSAGE("nft_approve_operation_test"); + + INVOKE(nft_mint_test); + + GET_ACTOR(alice); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + + ACTORS((operator3)); + + { + BOOST_TEST_MESSAGE("Send nft_approve_operation"); + + nft_approve_operation op; + op.operator_ = alice_id; + op.approved = operator3_id; + op.token_id = nft_id_type(0); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check nft_approve_operation results"); + + 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->approved == operator3_id ); + BOOST_CHECK( obj->approved_operators.size() == 2 ); + BOOST_CHECK( obj->approved_operators.at(0) == operator1_id ); + BOOST_CHECK( obj->approved_operators.at(1) == operator2_id ); + } +} + +BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { + + BOOST_TEST_MESSAGE("nft_set_approval_for_all_test"); + + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)); + ACTORS((bob)); + + INVOKE(nft_metadata_create_test); + + GET_ACTOR(mdowner); + + generate_block(); + set_expiration(db, trx); + + BOOST_TEST_MESSAGE("Create NFT assets"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto nft_md_obj = idx.begin(); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation 1"); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation 2"); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = bob_id; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation 3"); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation 4"); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = bob_id; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_mint_operation 5"); + + nft_mint_operation op; + op.payer = mdowner_id; + op.nft_metadata_id = nft_md_obj->id; + op.owner = alice_id; + + trx.operations.push_back(op); + sign(trx, mdowner_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + + { + BOOST_TEST_MESSAGE("Send nft_approve_operation"); + + nft_set_approval_for_all_operation op; + op.owner = alice_id; + op.operator_ = bob_id; + op.approved = true; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_approve_operation"); + + nft_set_approval_for_all_operation op; + op.owner = alice_id; + op.operator_ = bob_id; + op.approved = true; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check nft_approve_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(alice_id); + std::for_each(idx_range.first, idx_range.second, [&](const nft_object &obj) { + BOOST_CHECK( obj.approved_operators.size() == 1 ); + BOOST_CHECK( obj.approved_operators.at(0) == bob_id ); + }); + } + + { + BOOST_TEST_MESSAGE("Send nft_approve_operation"); + + nft_set_approval_for_all_operation op; + op.owner = alice_id; + op.operator_ = bob_id; + op.approved = false; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check nft_approve_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(alice_id); + std::for_each(idx_range.first, idx_range.second, [&](const nft_object &obj) { + BOOST_CHECK( obj.approved_operators.size() == 0 ); + }); + } +} + +BOOST_AUTO_TEST_SUITE_END() +