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/docs b/docs index 8d8b69d8..8df8f663 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 8d8b69d82482101279460fa02f814d0e4030966f +Subproject commit 8df8f66389853df73ab8f6dd73981be2a6957df8 diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index df6458f3..0f3d4b6b 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -184,6 +184,18 @@ class database_api_impl : public std::enable_shared_from_this // gpos gpos_info get_gpos_info(const account_id_type account) 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; + //private: const account_object* get_account_from_string( const std::string& name_or_id, bool throw_if_not_found = true ) const; @@ -2240,6 +2252,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 +2316,171 @@ graphene::app::gpos_info database_api_impl::get_gpos_info(const account_id_type return result; } +////////////////////////////////////////////////////////////////////// +// // +// 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 +{ + const auto &idx_nft = _db.get_index_type().indices().get(); + auto itr_nft = idx_nft.find(token_id); + if (itr_nft != idx_nft.end()) { + 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()) { + return itr_nft_md->base_uri + itr_nft->token_uri; + } + } + return ""; +} + +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 {}; +} + ////////////////////////////////////////////////////////////////////// // // // Private methods // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index dc8aba52..0082f3bf 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -48,6 +48,8 @@ #include #include +#include + #include #include @@ -709,7 +711,82 @@ class database_api */ gpos_info get_gpos_info(const account_id_type account) 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 URI + * @param nft_metadata_id NFT metadata ID + * @param owner NFT owner + * @param token_idx NFT index in the list of tokens + * @return NFT URI + */ + 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; private: std::shared_ptr< database_api_impl > my; @@ -848,4 +925,17 @@ FC_API(graphene::app::database_api, // gpos (get_gpos_info) + + // 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) + ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index d9c0e042..57005e7b 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -117,6 +117,7 @@ add_library( graphene_chain affiliate_payout.cpp offer_evaluator.cpp + nft_evaluator.cpp ${HEADERS} ${PROTOCOL_HEADERS} diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 65b589ff..f035964e 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -51,6 +51,7 @@ #include #include +#include #include #include @@ -79,6 +80,7 @@ #include #include #include +#include #include @@ -165,6 +167,9 @@ 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; @@ -253,6 +258,12 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -296,6 +307,9 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index db4050ca..a6336ae4 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -293,17 +293,34 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } - - void operator()( const offer_operation& op ) - { + 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 ) - { + void operator()( const bid_operation& op ) { _impacted.insert( op.bidder ); } - void operator()( const finalize_offer_operation& op ) - { + void operator()( const finalize_offer_operation& op ) { _impacted.insert( op.fee_paying_account ); } }; 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..34d31bf4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -0,0 +1,98 @@ +#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; + }; + + 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) ) + +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/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp new file mode 100644 index 00000000..678d2bf3 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -0,0 +1,106 @@ +#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; }; + asset fee; + + account_id_type owner; + std::string name; + std::string symbol; + std::string base_uri; + + account_id_type fee_payer()const { return owner; } + }; + + 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; + + account_id_type fee_payer()const { return owner; } + }; + + struct nft_mint_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type payer; + + optional 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; } + }; + + struct nft_safe_transfer_from_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 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_; } + }; + + 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_; } + }; + + 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; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::nft_metadata_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::nft_mint_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_type, (fee) ) +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) ) +FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) ) +FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (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/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 2751bd03..93974d4b 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -46,6 +46,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -139,7 +140,13 @@ namespace graphene { namespace chain { sweeps_vesting_claim_operation, offer_operation, bid_operation, - finalize_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/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 2d883bbb..e194c0d8 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -172,6 +172,8 @@ namespace graphene { namespace chain { betting_market_object_type, bet_object_type, offer_object_type, + nft_metadata_type, + nft_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -233,6 +235,8 @@ namespace graphene { namespace chain { class betting_market_object; class bet_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; @@ -260,6 +264,8 @@ namespace graphene { namespace chain { 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, 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; @@ -443,6 +449,8 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_object_type) (bet_object_type) (offer_object_type) + (nft_metadata_type) + (nft_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -515,6 +523,8 @@ 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::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..21c4f79a --- /dev/null +++ b/libraries/chain/nft_evaluator.cpp @@ -0,0 +1,183 @@ +#include +#include + +namespace graphene { namespace chain { + +void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_create_operation& op ) +{ try { + 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" ); + 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; + }); + 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 { + 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" ); + + 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; + }); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_mint_evaluator::do_evaluate( const nft_mint_operation& op ) +{ try { + 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; + }); + 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 { + 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_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 == itr_owner->id) || (itr_nft->approved == itr_operator->id) || (itr_approved_op != itr_nft->approved_operators.end()), "Operator is not NFT owner or approved operator" ); + + 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 { + 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 { + 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/fc b/libraries/fc index a76b9ff8..0358ca25 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit a76b9ff81c6887ebe1dc9fa03ef15e1433029c65 +Subproject commit 0358ca257e4ce9d66c1097cd2e8e2d34ff89a297 diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 7f4c7859..76da2ac9 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1895,6 +1895,143 @@ class wallet_api bool is_gpos, bool broadcast); + ///////// + // 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 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, + bool broadcast); + + /** + * @brief Updates 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 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, + string name, + string symbol, + string base_uri, + 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; + 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 +2282,14 @@ FC_API( graphene::wallet::wallet_api, (tournament_leave) (rps_throw) (create_vesting_balance) + (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) (get_upcoming_tournaments) (get_tournaments) (get_tournaments_by_state) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 0bbf305a..86d28100 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6157,6 +6157,176 @@ 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, + 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; + + 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, + string name, + string symbol, + string base_uri, + bool broadcast) +{ + account_object owner_account = my->get_account(owner_account_id_or_name); + + nft_metadata_update_operation op; + op.owner = owner_account.id; + op.name = name; + op.symbol = symbol; + op.base_uri = base_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 ); +} + +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 = op.nft_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); +} + // 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 e469c885..3e464993 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp new file mode 100644 index 00000000..9b6c5e40 --- /dev/null +++ b/tests/tests/nft_tests.cpp @@ -0,0 +1,384 @@ +#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_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"; + + 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.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() +