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