From de9ede0d5c61c073054595db0352bff34677bf5c Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Mon, 15 Jun 2020 22:39:04 +0200 Subject: [PATCH 1/9] NFT object and basic operations --- CMakeLists.txt | 2 +- libraries/chain/CMakeLists.txt | 2 + libraries/chain/db_init.cpp | 12 +++- libraries/chain/db_notify.cpp | 12 ++++ .../include/graphene/chain/nft_evaluator.hpp | 43 +++++++++++++ .../include/graphene/chain/nft_object.hpp | 47 ++++++++++++++ .../graphene/chain/protocol/nft_ops.hpp | 62 +++++++++++++++++++ .../graphene/chain/protocol/operations.hpp | 7 ++- .../include/graphene/chain/protocol/types.hpp | 5 ++ libraries/chain/nft_evaluator.cpp | 58 +++++++++++++++++ programs/js_operation_serializer/main.cpp | 1 + 11 files changed, 248 insertions(+), 3 deletions(-) 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/protocol/nft_ops.hpp create mode 100644 libraries/chain/nft_evaluator.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/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 07f1ea0a..a46201f7 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -115,6 +115,8 @@ add_library( graphene_chain affiliate_payout.cpp + nft_evaluator.cpp + ${HEADERS} ${PROTOCOL_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 4e30029b..6e6364ad 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -49,7 +49,7 @@ #include #include #include - +#include #include #include @@ -77,6 +77,7 @@ #include #include #include +#include #include @@ -163,6 +164,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; @@ -243,6 +247,10 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -285,6 +293,8 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + //Implementation object indexes add_index< primary_index >(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index e91eaa6b..4d716c4b 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -293,6 +293,18 @@ struct get_impacted_account_visitor void operator()( const sweeps_vesting_claim_operation& op ) { _impacted.insert( op.account ); } + void operator()( const nft_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const nft_safe_transfer_from_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const nft_approve_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const nft_set_approval_for_all_operation& op ) { + _impacted.insert( op.payer ); + } }; void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set& result ) 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..4e732a6f --- /dev/null +++ b/libraries/chain/include/graphene/chain/nft_evaluator.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + + class nft_create_evaluator : public evaluator + { + public: + typedef nft_create_operation operation_type; + void_result do_evaluate( const nft_create_operation& o ); + void_result do_apply( const nft_create_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 ); + void_result 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 ); + void_result 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..2e612240 --- /dev/null +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + class nft_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = nft_object_type; + + account_id_type owner; + vector approved_operators; + std::string metadata; + }; + + 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_unique< tag, + composite_key, + member + > + > + > + >; + using nft_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), + (owner) + (approved_operators) + (metadata) ) + 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..cdc416c8 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -0,0 +1,62 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + struct nft_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type payer; + nft_id_type nft_id; + + 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 payer; + nft_id_type nft_id; + + account_id_type fee_payer()const { return payer; } + }; + + struct nft_approve_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + account_id_type payer; + nft_id_type nft_id; + + account_id_type fee_payer()const { return payer; } + }; + + 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 payer; + nft_id_type nft_id; + + account_id_type fee_payer()const { return payer; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::nft_create_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) (payer) (nft_id) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (payer) (nft_id) ) +FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (payer) (nft_id) ) +FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (payer) (nft_id) ) + diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index cb9a83a1..47dcdee8 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -45,6 +45,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -135,7 +136,11 @@ namespace graphene { namespace chain { ticket_purchase_operation, lottery_reward_operation, lottery_end_operation, - sweeps_vesting_claim_operation + sweeps_vesting_claim_operation, + nft_create_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 8ea3a8af..b2ee5102 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_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -230,6 +231,7 @@ namespace graphene { namespace chain { class betting_market_group_object; class betting_market_object; class bet_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 +258,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_object_type, nft_object> nft_id_type; // implementation types class global_property_object; @@ -436,6 +439,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_group_object_type) (betting_market_object_type) (bet_object_type) + (nft_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -505,6 +509,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_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..c8f5ad4f --- /dev/null +++ b/libraries/chain/nft_evaluator.cpp @@ -0,0 +1,58 @@ +#include +#include + +namespace graphene { namespace chain { + +void_result nft_create_evaluator::do_evaluate( const nft_create_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result nft_create_evaluator::do_apply( const nft_create_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transfer_from_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transfer_from_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_approve_evaluator::do_evaluate( const nft_approve_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result nft_approve_evaluator::do_apply( const nft_approve_operation& op ) +{ try { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + +void_result nft_set_approval_for_all_evaluator::do_evaluate( const nft_set_approval_for_all_operation& op ) +{ try { + + 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 { + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // graphene::chain + diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 8994b36b..b58a016f 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -43,6 +43,7 @@ #include #include #include +#include #include #include From 9f03e9a6ff7555036abbf785e6bf89bf199c1c80 Mon Sep 17 00:00:00 2001 From: Roshan Syed Date: Tue, 16 Jun 2020 16:53:58 +0000 Subject: [PATCH 2/9] ci: update .gitlab-ci.yml --- .gitlab-ci.yml | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 42ec77fc..94966ab6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,26 +30,4 @@ test: - ./tests/chain_test - ./tests/cli_test tags: - - builder - -code_quality: - stage: test - image: docker:stable - variables: - DOCKER_DRIVER: overlay2 - allow_failure: true - services: - - docker:stable-dind - script: - - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - - docker run - --env SOURCE_CODE="$PWD" - --volume "$PWD":/code - --volume /var/run/docker.sock:/var/run/docker.sock - "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code - artifacts: - paths: [gl-code-quality-report.json] - expire_in: 1 week - except: - variables: - - $CODE_QUALITY_DISABLED + - builder \ No newline at end of file From 31ed9b4ddb9267cf1521a8467eb142aa64a3b2fa Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Thu, 18 Jun 2020 04:52:58 +0200 Subject: [PATCH 3/9] NFT evaluators and basic tests, no evaluator checks --- libraries/chain/db_notify.cpp | 11 +- .../include/graphene/chain/nft_evaluator.hpp | 6 +- .../include/graphene/chain/nft_object.hpp | 2 +- .../graphene/chain/protocol/nft_ops.hpp | 39 +-- libraries/chain/nft_evaluator.cpp | 51 +++- tests/tests/nft_tests.cpp | 272 ++++++++++++++++++ 6 files changed, 346 insertions(+), 35 deletions(-) create mode 100644 tests/tests/nft_tests.cpp diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 4d716c4b..08985625 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -294,16 +294,19 @@ struct get_impacted_account_visitor _impacted.insert( op.account ); } void operator()( const nft_create_operation& op ) { - _impacted.insert( op.payer ); + _impacted.insert( op.owner ); } void operator()( const nft_safe_transfer_from_operation& op ) { - _impacted.insert( op.payer ); + _impacted.insert( op.from ); + _impacted.insert( op.to ); } void operator()( const nft_approve_operation& op ) { - _impacted.insert( op.payer ); + _impacted.insert( op.owner ); + _impacted.insert( op.approved ); } void operator()( const nft_set_approval_for_all_operation& op ) { - _impacted.insert( op.payer ); + _impacted.insert( op.owner ); + _impacted.insert( op.operator_ ); } }; diff --git a/libraries/chain/include/graphene/chain/nft_evaluator.hpp b/libraries/chain/include/graphene/chain/nft_evaluator.hpp index 4e732a6f..6d189f0d 100644 --- a/libraries/chain/include/graphene/chain/nft_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/nft_evaluator.hpp @@ -12,7 +12,7 @@ namespace graphene { namespace chain { public: typedef nft_create_operation operation_type; void_result do_evaluate( const nft_create_operation& o ); - void_result do_apply( const nft_create_operation& o ); + object_id_type do_apply( const nft_create_operation& o ); }; class nft_safe_transfer_from_evaluator : public evaluator @@ -20,7 +20,7 @@ namespace graphene { namespace chain { public: typedef nft_safe_transfer_from_operation operation_type; void_result do_evaluate( const nft_safe_transfer_from_operation& o ); - void_result do_apply( 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 @@ -28,7 +28,7 @@ namespace graphene { namespace chain { public: typedef nft_approve_operation operation_type; void_result do_evaluate( const nft_approve_operation& o ); - void_result do_apply( const nft_approve_operation& o ); + object_id_type do_apply( const nft_approve_operation& o ); }; class nft_set_approval_for_all_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 2e612240..901d902c 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -14,7 +14,7 @@ namespace graphene { namespace chain { account_id_type owner; vector approved_operators; - std::string metadata; + std::string metadata; }; struct by_owner; diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index cdc416c8..fe55f7c3 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -9,10 +9,11 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; - account_id_type payer; - nft_id_type nft_id; + account_id_type owner; + vector approved_operators; + std::string metadata; - account_id_type fee_payer()const { return payer; } + account_id_type fee_payer()const { return owner; } }; struct nft_safe_transfer_from_operation : public base_operation @@ -20,10 +21,12 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; - account_id_type payer; - nft_id_type nft_id; + account_id_type from; + account_id_type to; + nft_id_type token_id; + string data; - account_id_type fee_payer()const { return payer; } + account_id_type fee_payer()const { return from; } }; struct nft_approve_operation : public base_operation @@ -31,10 +34,12 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; - account_id_type payer; - nft_id_type nft_id; + account_id_type owner; - account_id_type fee_payer()const { return payer; } + account_id_type approved; + nft_id_type token_id; + + account_id_type fee_payer()const { return owner; } }; struct nft_set_approval_for_all_operation : public base_operation @@ -42,10 +47,12 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; - account_id_type payer; - nft_id_type nft_id; + account_id_type owner; - account_id_type fee_payer()const { return payer; } + account_id_type operator_; + bool approved; + + account_id_type fee_payer()const { return owner; } }; } } // graphene::chain @@ -55,8 +62,8 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::nft_create_operation, (fee) (payer) (nft_id) ) -FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (payer) (nft_id) ) -FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (payer) (nft_id) ) -FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (payer) (nft_id) ) +FC_REFLECT( graphene::chain::nft_create_operation, (fee) (owner) (approved_operators) (metadata) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (from) (to) (token_id) (data) ) +FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (owner) (approved) (token_id) ) +FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) ) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index c8f5ad4f..84fbe608 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -9,23 +9,35 @@ void_result nft_create_evaluator::do_evaluate( const nft_create_operation& op ) return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result nft_create_evaluator::do_apply( const nft_create_operation& op ) +object_id_type nft_create_evaluator::do_apply( const nft_create_operation& op ) { try { - - return void_result(); + const auto& new_nft_object = db().create( [&]( nft_object& obj ){ + obj.owner = op.owner; + obj.approved_operators = op.approved_operators; + obj.metadata = op.metadata; + }); + 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 { - return void_result(); + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transfer_from_operation& op ) +object_id_type nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transfer_from_operation& op ) { try { - - return void_result(); + 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_operators.clear(); + }); + } + return op.token_id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -35,10 +47,17 @@ void_result nft_approve_evaluator::do_evaluate( const nft_approve_operation& op return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } -void_result nft_approve_evaluator::do_apply( const nft_approve_operation& op ) +object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& op ) { try { - - return void_result(); + 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_operators.push_back(op.approved); + }); + } + return op.token_id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -50,7 +69,17 @@ void_result nft_set_approval_for_all_evaluator::do_evaluate( const nft_set_appro 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) { + if (op.approved) { + obj.approved_operators.push_back(op.operator_); + } else { + // + } + }); + }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp new file mode 100644 index 00000000..f7034411 --- /dev/null +++ b/tests/tests/nft_tests.cpp @@ -0,0 +1,272 @@ +#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_create_test ) { + + BOOST_TEST_MESSAGE("nft_create_test"); + + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)); + ACTORS((bob)); + ACTORS((operator1)); + ACTORS((operator2)); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation"); + + nft_create_operation op; + op.owner = 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); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_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 == 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_CHECK( obj->metadata == "metadata" ); +} + + +BOOST_AUTO_TEST_CASE( nft_safe_transfer_from_test ) { + + BOOST_TEST_MESSAGE("nft_safe_transfer_from_test"); + + INVOKE(nft_create_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_create_test); + + GET_ACTOR(alice); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + + ACTORS((operator3)); + + { + BOOST_TEST_MESSAGE("Send nft_approve_operation"); + + nft_approve_operation op; + op.owner = 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_operators.size() == 3 ); + BOOST_CHECK( obj->approved_operators.at(0) == operator1_id ); + BOOST_CHECK( obj->approved_operators.at(1) == operator2_id ); + BOOST_CHECK( obj->approved_operators.at(2) == operator3_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)); + + generate_block(); + set_expiration(db, trx); + + BOOST_TEST_MESSAGE("Create NFT assets"); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation 1"); + + nft_create_operation op; + op.owner = alice_id; + op.metadata = "metadata 1"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation 2"); + + nft_create_operation op; + op.owner = bob_id; + op.metadata = "metadata 2"; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation 3"); + + nft_create_operation op; + op.owner = alice_id; + op.metadata = "metadata 3"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation 4"); + + nft_create_operation op; + op.owner = bob_id; + op.metadata = "metadata 4"; + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Send nft_create_operation 5"); + + nft_create_operation op; + op.owner = alice_id; + op.metadata = "metadata 5"; + + 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() == 1 ); + // }); + //} +} + +BOOST_AUTO_TEST_SUITE_END() + From 775eac55a97999848c5c4261e76ee85eeb6bb529 Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Fri, 19 Jun 2020 02:44:02 +0200 Subject: [PATCH 4/9] Evaluator checks in place --- libraries/chain/nft_evaluator.cpp | 50 +++++++++++++++++++++++--- tests/tests/nft_tests.cpp | 60 +++++++++++++++++++------------ 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index 84fbe608..e7e80aa1 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -22,8 +22,25 @@ object_id_type nft_create_evaluator::do_apply( const nft_create_operation& 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(); - return void_result(); + 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(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" ); + + 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.from); + FC_ASSERT( (itr_nft->owner == itr_from->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_safe_transfer_from_evaluator::do_apply( const nft_safe_transfer_from_operation& op ) @@ -43,6 +60,20 @@ object_id_type nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transf 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.owner); + 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.owner); + 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) ) } @@ -54,7 +85,10 @@ object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& op if (itr != idx.end()) { db().modify(*itr, [&op](nft_object &obj) { - obj.approved_operators.push_back(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; @@ -63,6 +97,10 @@ object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& 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) ) } @@ -73,10 +111,12 @@ void_result nft_set_approval_for_all_evaluator::do_apply( const nft_set_approval 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) { - if (op.approved) { + 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_); - } else { - // + } + if ((!op.approved) && (itr != obj.approved_operators.end())) { + obj.approved_operators.erase(itr); } }); }); diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index f7034411..de95225f 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -218,6 +218,20 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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"); @@ -243,29 +257,29 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { }); } - //{ - // 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() == 1 ); - // }); - //} + { + 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() From d0c72f7cf775f73e664f6893ad4432398207a5f4 Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Mon, 22 Jun 2020 21:36:42 +0200 Subject: [PATCH 5/9] Database API --- libraries/app/database_api.cpp | 74 +++++++++++++++++++ .../app/include/graphene/app/database_api.hpp | 40 ++++++++++ .../include/graphene/chain/nft_object.hpp | 2 + .../graphene/chain/protocol/nft_ops.hpp | 9 ++- libraries/chain/nft_evaluator.cpp | 19 +++-- tests/tests/nft_tests.cpp | 5 +- 6 files changed, 138 insertions(+), 11 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index df6458f3..67abe782 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -184,6 +184,12 @@ 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; + //private: const account_object* get_account_from_string( const std::string& name_or_id, bool throw_if_not_found = true ) const; @@ -2240,6 +2246,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 +2310,73 @@ 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; +} + ////////////////////////////////////////////////////////////////////// // // // Private methods // diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index dc8aba52..b7e9f5c3 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,6 +711,37 @@ 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; private: @@ -848,4 +881,11 @@ 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) + ) diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index 901d902c..8930cc91 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -13,6 +13,7 @@ namespace graphene { namespace chain { static const uint8_t type_id = nft_object_type; account_id_type owner; + account_id_type approved; vector approved_operators; std::string metadata; }; @@ -42,6 +43,7 @@ namespace graphene { namespace chain { FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object), (owner) + (approved) (approved_operators) (metadata) ) diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index fe55f7c3..bfe63eab 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -10,6 +10,7 @@ namespace graphene { namespace chain { asset fee; account_id_type owner; + account_id_type approved; vector approved_operators; std::string metadata; @@ -21,12 +22,14 @@ namespace graphene { namespace chain { 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 from; } + account_id_type fee_payer()const { return operator_; } }; struct nft_approve_operation : public base_operation @@ -62,8 +65,8 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) ) -FC_REFLECT( graphene::chain::nft_create_operation, (fee) (owner) (approved_operators) (metadata) ) -FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (from) (to) (token_id) (data) ) +FC_REFLECT( graphene::chain::nft_create_operation, (fee) (owner) (approved) (approved_operators) (metadata) ) +FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) ) FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (owner) (approved) (token_id) ) FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) ) diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index e7e80aa1..8374e2e9 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -13,6 +13,7 @@ object_id_type nft_create_evaluator::do_apply( const nft_create_operation& op ) { try { const auto& new_nft_object = db().create( [&]( nft_object& obj ){ obj.owner = op.owner; + obj.approved = op.approved; obj.approved_operators = op.approved_operators; obj.metadata = op.metadata; }); @@ -28,17 +29,21 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf 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.from); - FC_ASSERT( (itr_nft->owner == itr_from->id) || (itr_approved_op != itr_nft->approved_operators.end()), "Sender is not NFT owner or approved operator" ); + 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) ) } @@ -51,6 +56,7 @@ object_id_type nft_safe_transfer_from_evaluator::do_apply( const nft_safe_transf { db().modify(*itr, [&op](nft_object &obj) { obj.owner = op.to; + obj.approved = {}; obj.approved_operators.clear(); }); } @@ -85,10 +91,11 @@ object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& op if (itr != idx.end()) { db().modify(*itr, [&op](nft_object &obj) { - 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); - } + 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; diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index de95225f..28e238b5 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -30,6 +30,7 @@ BOOST_AUTO_TEST_CASE( nft_create_test ) { nft_create_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"; @@ -130,10 +131,10 @@ BOOST_AUTO_TEST_CASE( nft_approve_operation_test ) { BOOST_REQUIRE( idx.size() == 1 ); auto obj = idx.begin(); BOOST_REQUIRE( obj != idx.end() ); - BOOST_CHECK( obj->approved_operators.size() == 3 ); + 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_CHECK( obj->approved_operators.at(2) == operator3_id ); } } From 0d79d5d0eea6ff15fa37e6db77521600802d518b Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Tue, 23 Jun 2020 03:14:33 +0200 Subject: [PATCH 6/9] Wallet API --- libraries/chain/db_notify.cpp | 2 +- .../graphene/chain/protocol/nft_ops.hpp | 6 +- libraries/chain/nft_evaluator.cpp | 4 +- .../wallet/include/graphene/wallet/wallet.hpp | 112 ++++++++++++++++ libraries/wallet/wallet.cpp | 121 ++++++++++++++++++ tests/tests/nft_tests.cpp | 2 +- 6 files changed, 240 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 08985625..c45369fb 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -301,7 +301,7 @@ struct get_impacted_account_visitor _impacted.insert( op.to ); } void operator()( const nft_approve_operation& op ) { - _impacted.insert( op.owner ); + _impacted.insert( op.operator_ ); _impacted.insert( op.approved ); } void operator()( const nft_set_approval_for_all_operation& op ) { diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index bfe63eab..9196bd3f 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -37,12 +37,12 @@ namespace graphene { namespace chain { struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; - account_id_type owner; + account_id_type operator_; account_id_type approved; nft_id_type token_id; - account_id_type fee_payer()const { return owner; } + account_id_type fee_payer()const { return operator_; } }; struct nft_set_approval_for_all_operation : public base_operation @@ -67,6 +67,6 @@ FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_ FC_REFLECT( graphene::chain::nft_create_operation, (fee) (owner) (approved) (approved_operators) (metadata) ) FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) ) -FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (owner) (approved) (token_id) ) +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/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index 8374e2e9..1ef5f199 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -72,13 +72,13 @@ void_result nft_approve_evaluator::do_evaluate( const nft_approve_operation& op 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.owner); + 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.owner); + 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(); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 7f4c7859..6b91a702 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1895,6 +1895,110 @@ class wallet_api bool is_gpos, bool broadcast); + ///////// + // NFT // + ///////// + /** + * @brief Creates NFT + * @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 broadcast true to broadcast transaction to the network + * @return Signed transaction transfering the funds + */ + signed_transaction nft_create(string owner_account_id_or_name, + string approved_account_id_or_name, + string metadata, + 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 +2249,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..d62cafbe 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6157,6 +6157,127 @@ 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) +{ + 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; + op.owner = owner_account.id; + op.approved = approved_account.id; + op.metadata = metadata; + + 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/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index 28e238b5..504d3acc 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -114,7 +114,7 @@ BOOST_AUTO_TEST_CASE( nft_approve_operation_test ) { BOOST_TEST_MESSAGE("Send nft_approve_operation"); nft_approve_operation op; - op.owner = alice_id; + op.operator_ = alice_id; op.approved = operator3_id; op.token_id = nft_id_type(0); From 51cb17e2d813af3b7689061d1a62469160580f12 Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Wed, 1 Jul 2020 01:08:31 +0200 Subject: [PATCH 7/9] 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); From e2c375e2a2d841f5f0c40cc7caa3bbb143e0bdee Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Wed, 1 Jul 2020 12:13:53 +0200 Subject: [PATCH 8/9] Fix NFT tests --- .../graphene/chain/protocol/nft_ops.hpp | 4 +- libraries/chain/nft_evaluator.cpp | 1 + .../wallet/include/graphene/wallet/wallet.hpp | 4 +- libraries/wallet/wallet.cpp | 5 +- tests/tests/nft_tests.cpp | 122 ++++++++++++++++-- 5 files changed, 124 insertions(+), 12 deletions(-) diff --git a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp index 6d563920..678d2bf3 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_ops.hpp @@ -36,13 +36,15 @@ namespace graphene { namespace chain { 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 owner; } + account_id_type fee_payer()const { return payer; } }; struct nft_safe_transfer_from_operation : public base_operation diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index 340e3165..21c4f79a 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -53,6 +53,7 @@ void_result nft_mint_evaluator::do_evaluate( const nft_mint_operation& op ) 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) ) } diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 8df4cca6..76da2ac9 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1930,6 +1930,7 @@ class wallet_api /** * @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 @@ -1937,7 +1938,8 @@ class wallet_api * @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, + 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, diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index b608cc0c..86d28100 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -6201,16 +6201,19 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na return my->sign_transaction( trx, broadcast ); } -signed_transaction wallet_api::nft_create(nft_metadata_id_type metadata_id, +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; diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index 0d83a29f..9b6c5e40 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -10,25 +10,111 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( nft_tests, database_fixture ) -BOOST_AUTO_TEST_CASE( nft_create_test ) { +BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) { - BOOST_TEST_MESSAGE("nft_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); @@ -57,7 +143,7 @@ BOOST_AUTO_TEST_CASE( nft_safe_transfer_from_test ) { BOOST_TEST_MESSAGE("nft_safe_transfer_from_test"); - INVOKE(nft_create_test); + INVOKE(nft_mint_test); GET_ACTOR(alice); GET_ACTOR(bob); @@ -100,7 +186,7 @@ BOOST_AUTO_TEST_CASE( nft_approve_operation_test ) { BOOST_TEST_MESSAGE("nft_approve_operation_test"); - INVOKE(nft_create_test); + INVOKE(nft_mint_test); GET_ACTOR(alice); GET_ACTOR(operator1); @@ -146,19 +232,29 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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, alice_private_key); + sign(trx, mdowner_private_key); PUSH_TX(db, trx, ~0); } generate_block(); @@ -167,10 +263,12 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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, bob_private_key); + sign(trx, mdowner_private_key); PUSH_TX(db, trx, ~0); } generate_block(); @@ -179,10 +277,12 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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, alice_private_key); + sign(trx, mdowner_private_key); PUSH_TX(db, trx, ~0); } generate_block(); @@ -191,10 +291,12 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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, bob_private_key); + sign(trx, mdowner_private_key); PUSH_TX(db, trx, ~0); } generate_block(); @@ -203,10 +305,12 @@ BOOST_AUTO_TEST_CASE( nft_set_approval_for_all_test ) { 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, alice_private_key); + sign(trx, mdowner_private_key); PUSH_TX(db, trx, ~0); } generate_block(); From 3a8d8b9e98654639a9a771fe57585ff75f6c829d Mon Sep 17 00:00:00 2001 From: Srdjan Obucina Date: Thu, 2 Jul 2020 00:23:09 +0200 Subject: [PATCH 9/9] Database API for NFT metadata and enumerables --- libraries/app/database_api.cpp | 104 ++++++++++++++++++ .../app/include/graphene/app/database_api.hpp | 50 +++++++++ .../include/graphene/chain/nft_object.hpp | 7 ++ 3 files changed, 161 insertions(+) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 67abe782..0f3d4b6b 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -189,6 +189,12 @@ class database_api_impl : public std::enable_shared_from_this 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, @@ -2377,6 +2383,104 @@ bool database_api_impl::nft_is_approved_for_all(const account_id_type owner, con 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 b7e9f5c3..0082f3bf 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -743,6 +743,50 @@ class database_api */ 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; @@ -887,5 +931,11 @@ FC_API(graphene::app::database_api, (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/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index b9d463af..34d31bf4 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -50,6 +50,7 @@ namespace graphene { namespace chain { 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< @@ -61,6 +62,12 @@ namespace graphene { namespace chain { ordered_non_unique< tag, member >, + ordered_non_unique< tag, + composite_key, + member + > + >, ordered_non_unique< tag, member >,