From 51cb17e2d813af3b7689061d1a62469160580f12 Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Wed, 1 Jul 2020 01:08:31 +0200 Subject: [PATCH] NFT metadata implemented --- libraries/chain/db_init.cpp | 5 +- libraries/chain/db_notify.cpp | 8 ++- .../include/graphene/chain/nft_evaluator.hpp | 24 ++++++-- .../include/graphene/chain/nft_object.hpp | 46 ++++++++++++++- .../graphene/chain/protocol/nft_ops.hpp | 40 +++++++++++-- .../graphene/chain/protocol/operations.hpp | 4 +- .../include/graphene/chain/protocol/types.hpp | 5 ++ libraries/chain/nft_evaluator.cpp | 54 ++++++++++++++++- .../wallet/include/graphene/wallet/wallet.hpp | 43 ++++++++++++-- libraries/wallet/wallet.cpp | 58 +++++++++++++++++-- tests/tests/nft_tests.cpp | 33 +++++------ 11 files changed, 272 insertions(+), 48 deletions(-) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 6e6364ad..3009375d 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -247,7 +247,9 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); - register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); @@ -293,6 +295,7 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); add_index< primary_index >(); //Implementation object indexes diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index c45369fb..c3ec412d 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -293,7 +293,13 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } - void operator()( const nft_create_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 ) { diff --git a/libraries/chain/include/graphene/chain/nft_evaluator.hpp b/libraries/chain/include/graphene/chain/nft_evaluator.hpp index 6d189f0d..0d0f5f51 100644 --- a/libraries/chain/include/graphene/chain/nft_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/nft_evaluator.hpp @@ -7,12 +7,28 @@ namespace graphene { namespace chain { - class nft_create_evaluator : public evaluator + class nft_metadata_create_evaluator : public evaluator { public: - typedef nft_create_operation operation_type; - void_result do_evaluate( const nft_create_operation& o ); - object_id_type do_apply( const nft_create_operation& o ); + 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 diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 8930cc91..b9d463af 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -6,18 +6,50 @@ 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 metadata; + 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_owner; struct by_owner_and_id; using nft_multi_index_type = multi_index_container< @@ -26,6 +58,9 @@ namespace graphene { namespace chain { ordered_unique< tag, member >, + ordered_non_unique< tag, + member + >, ordered_non_unique< tag, member >, @@ -41,9 +76,16 @@ namespace graphene { namespace chain { } } // 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) - (metadata) ) + (token_uri) ) diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index 9196bd3f..6d563920 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -4,15 +4,43 @@ namespace graphene { namespace chain { - struct nft_create_operation : public base_operation + 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; + + optional nft_metadata_id; account_id_type owner; account_id_type approved; vector approved_operators; - std::string metadata; + std::string token_uri; account_id_type fee_payer()const { return owner; } }; @@ -60,12 +88,16 @@ namespace graphene { namespace chain { } } // graphene::chain -FC_REFLECT( graphene::chain::nft_create_operation::fee_parameters_type, (fee) ) +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_create_operation, (fee) (owner) (approved) (approved_operators) (metadata) ) +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 47dcdee8..9dfe7f08 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -137,7 +137,9 @@ namespace graphene { namespace chain { lottery_reward_operation, lottery_end_operation, sweeps_vesting_claim_operation, - nft_create_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 diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index b2ee5102..4d226538 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -171,6 +171,7 @@ namespace graphene { namespace chain { betting_market_group_object_type, betting_market_object_type, bet_object_type, + nft_metadata_type, nft_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -231,6 +232,7 @@ namespace graphene { namespace chain { class betting_market_group_object; class betting_market_object; class bet_object; + class nft_metadata_object; class nft_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; @@ -258,6 +260,7 @@ 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, 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 @@ -439,6 +442,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_group_object_type) (betting_market_object_type) (bet_object_type) + (nft_metadata_type) (nft_object_type) (OBJECT_TYPE_COUNT) ) @@ -509,6 +513,7 @@ 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::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 index 1ef5f199..340e3165 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -3,19 +3,67 @@ namespace graphene { namespace chain { -void_result nft_create_evaluator::do_evaluate( const nft_create_operation& op ) +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) ) } -object_id_type nft_create_evaluator::do_apply( const nft_create_operation& 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" ); + + 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.metadata = op.metadata; }); return new_nft_object.id; } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 6b91a702..8df4cca6 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1899,17 +1899,48 @@ class wallet_api // NFT // ///////// /** - * @brief Creates NFT + * @brief Creates NFT metadata * @param owner_account_id_or_name Owner account ID or name - * @param approved_account_id_or_name Approved account ID or name - * @param approved_operators_id_or_name Approved operator IDs or names - * @param metadata NFT metadata + * @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_create(string owner_account_id_or_name, + 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_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(nft_metadata_id_type metadata_id, + string owner_account_id_or_name, string approved_account_id_or_name, - string metadata, + string token_uri, bool broadcast); /** diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d62cafbe..b608cc0c 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6157,18 +6157,64 @@ signed_transaction wallet_api::create_vesting_balance(string owner, return my->sign_transaction( trx, broadcast ); } -signed_transaction wallet_api::nft_create(string owner_account_id_or_name, - string approved_account_id_or_name, - string metadata, - bool 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(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 owner_account = my->get_account(owner_account_id_or_name); account_object approved_account = my->get_account(approved_account_id_or_name); - nft_create_operation op; + nft_mint_operation op; + op.nft_metadata_id = op.nft_metadata_id; op.owner = owner_account.id; op.approved = approved_account.id; - op.metadata = metadata; + op.token_uri = token_uri; signed_transaction trx; trx.operations.push_back(op); diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index 504d3acc..0d83a29f 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -26,14 +26,13 @@ BOOST_AUTO_TEST_CASE( nft_create_test ) { set_expiration(db, trx); { - BOOST_TEST_MESSAGE("Send nft_create_operation"); + BOOST_TEST_MESSAGE("Send nft_mint_operation"); - nft_create_operation op; + nft_mint_operation op; op.owner = alice_id; op.approved = alice_id; op.approved_operators.push_back(operator1_id); op.approved_operators.push_back(operator2_id); - op.metadata = "metadata"; trx.operations.push_back(op); sign(trx, alice_private_key); @@ -41,7 +40,7 @@ BOOST_AUTO_TEST_CASE( nft_create_test ) { } generate_block(); - BOOST_TEST_MESSAGE("Check nft_create_operation results"); + BOOST_TEST_MESSAGE("Check nft_mint_operation results"); const auto& idx = db.get_index_type().indices().get(); BOOST_REQUIRE( idx.size() == 1 ); @@ -51,7 +50,6 @@ BOOST_AUTO_TEST_CASE( nft_create_test ) { 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_CHECK( obj->metadata == "metadata" ); } @@ -154,11 +152,10 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { BOOST_TEST_MESSAGE("Create NFT assets"); { - BOOST_TEST_MESSAGE("Send nft_create_operation 1"); + BOOST_TEST_MESSAGE("Send nft_mint_operation 1"); - nft_create_operation op; + nft_mint_operation op; op.owner = alice_id; - op.metadata = "metadata 1"; trx.operations.push_back(op); sign(trx, alice_private_key); @@ -167,11 +164,10 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { generate_block(); { - BOOST_TEST_MESSAGE("Send nft_create_operation 2"); + BOOST_TEST_MESSAGE("Send nft_mint_operation 2"); - nft_create_operation op; + nft_mint_operation op; op.owner = bob_id; - op.metadata = "metadata 2"; trx.operations.push_back(op); sign(trx, bob_private_key); @@ -180,11 +176,10 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { generate_block(); { - BOOST_TEST_MESSAGE("Send nft_create_operation 3"); + BOOST_TEST_MESSAGE("Send nft_mint_operation 3"); - nft_create_operation op; + nft_mint_operation op; op.owner = alice_id; - op.metadata = "metadata 3"; trx.operations.push_back(op); sign(trx, alice_private_key); @@ -193,11 +188,10 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { generate_block(); { - BOOST_TEST_MESSAGE("Send nft_create_operation 4"); + BOOST_TEST_MESSAGE("Send nft_mint_operation 4"); - nft_create_operation op; + nft_mint_operation op; op.owner = bob_id; - op.metadata = "metadata 4"; trx.operations.push_back(op); sign(trx, bob_private_key); @@ -206,11 +200,10 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { generate_block(); { - BOOST_TEST_MESSAGE("Send nft_create_operation 5"); + BOOST_TEST_MESSAGE("Send nft_mint_operation 5"); - nft_create_operation op; + nft_mint_operation op; op.owner = alice_id; - op.metadata = "metadata 5"; trx.operations.push_back(op); sign(trx, alice_private_key);