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() +