From ca69a692cce0b660806b699b6a1fb1b1b536c5ea Mon Sep 17 00:00:00 2001 From: timur <12267899-timur.5@users.noreply.gitlab.com> Date: Wed, 21 Dec 2022 17:45:42 +0000 Subject: [PATCH] Add or revive a few NFT-listing APIs --- libraries/app/database_api.cpp | 61 ++- .../app/include/graphene/app/database_api.hpp | 16 +- .../include/graphene/chain/nft_object.hpp | 3 + .../wallet/include/graphene/wallet/wallet.hpp | 24 +- libraries/wallet/wallet.cpp | 23 +- tests/tests/nft_tests.cpp | 361 +++++++++++++----- 6 files changed, 384 insertions(+), 104 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 64fb82c2..e22e1dc4 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -269,8 +269,9 @@ public: 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; - vector nft_get_all_tokens() const; - vector nft_get_tokens_by_owner(const account_id_type owner) const; + vector nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const; + vector nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const; + vector nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const; // Marketplace vector list_offers(const offer_id_type lower_id, uint32_t limit) const; @@ -291,6 +292,7 @@ public: uint32_t api_limit_get_limit_orders_by_account = 101; uint32_t api_limit_get_order_book = 50; uint32_t api_limit_all_offers_count = 100; + uint32_t api_limit_nft_tokens = 100; uint32_t api_limit_lookup_accounts = 1000; uint32_t api_limit_lookup_witness_accounts = 1000; uint32_t api_limit_lookup_committee_member_accounts = 1000; @@ -3102,30 +3104,61 @@ nft_object database_api_impl::nft_token_of_owner_by_index(const nft_metadata_id_ return {}; } -vector database_api::nft_get_all_tokens() const { - return my->nft_get_all_tokens(); +vector database_api::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const { + return my->nft_get_all_tokens(lower_id, limit); } -vector database_api_impl::nft_get_all_tokens() const { +vector database_api_impl::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft tokens can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); + const auto &idx_nft = _db.get_index_type().indices().get(); vector result; - for (auto itr = idx_nft.begin(); itr != idx_nft.end(); ++itr) { - result.push_back(*itr); - } + result.reserve(limit); + auto itr = idx_nft.lower_bound(lower_id); + while (limit-- && itr != idx_nft.end()) + result.emplace_back(*itr++); return result; } -vector database_api::nft_get_tokens_by_owner(const account_id_type owner) const { - return my->nft_get_tokens_by_owner(owner); +vector database_api::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const { + return my->nft_get_tokens_by_owner(owner, lower_id, limit); } -vector database_api_impl::nft_get_tokens_by_owner(const account_id_type owner) const { +vector database_api_impl::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft tokens can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); const auto &idx_nft = _db.get_index_type().indices().get(); auto idx_nft_range = idx_nft.equal_range(owner); vector result; - for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) { - result.push_back(*itr); - } + result.reserve(limit); + auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_object &obj) { + return !(obj.id.instance() < lower_id.instance); + }); + while (limit-- && itr != idx_nft_range.second) + result.emplace_back(*itr++); + return result; +} + +vector database_api::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const { + return my->nft_get_metadata_by_owner(owner, lower_id, limit); +} + +vector database_api_impl::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft metadata objects can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); + const auto &idx_nft = _db.get_index_type().indices().get(); + auto idx_nft_range = idx_nft.equal_range(owner); + vector result; + result.reserve(limit); + auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_metadata_object &obj) { + return !(obj.id.instance() < lower_id.instance); + }); + while (limit-- && itr != idx_nft_range.second) + result.emplace_back(*itr++); return result; } diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index b53c8eeb..55da8e5f 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -1027,14 +1027,25 @@ public: * @brief Returns list of all available NTF's * @return List of all available NFT's */ - vector nft_get_all_tokens() const; + vector nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const; /** * @brief Returns NFT's owned by owner * @param owner NFT owner + * @param lower_id ID of the first NFT to return + * @param limit Maximum number of results to return * @return List of NFT owned by owner */ - vector nft_get_tokens_by_owner(const account_id_type owner) const; + vector nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const; + + /** + * @brief Returns NFT metadata owned by owner + * @param owner NFT owner + * @param lower_id ID of the first NFT metadata to return + * @param limit Maximum number of results to return + * @return List of NFT owned by owner + */ + vector nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const; ////////////////// // MARKET PLACE // @@ -1249,6 +1260,7 @@ FC_API(graphene::app::database_api, (nft_token_of_owner_by_index) (nft_get_all_tokens) (nft_get_tokens_by_owner) + (nft_get_metadata_by_owner) // Marketplace (list_offers) diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index fe026da5..a54992c4 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -130,6 +130,9 @@ namespace graphene { namespace chain { std::greater< uint32_t >, std::greater< object_id_type > > + >, + ordered_non_unique< tag, + member > > >; diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index a7807596..3d8d5b06 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -2571,9 +2571,29 @@ class wallet_api /** * @brief Returns all tokens + * @param limit the maximum number of NFT objects to return (max: 100) + * @param lower_id ID of the first NFT object to include in the list. * @return Returns vector of NFT objects, empty vector if none */ - vector nft_get_all_tokens() const; + vector nft_get_all_tokens(uint32_t limit, optional lower_id) const; + + /** + * @brief Returns all tokens owned by owner + * @param owner NFT owner account ID + * @param limit the maximum number of NFT objects to return (max: 100) + * @param lower_id ID of the first NFT object to include in the list. + * @return Returns vector of NFT objects, empty vector if none + */ + vector nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const; + + /** + * @brief Returns all NFT metadata objects owned by owner + * @param owner NFT owner account ID + * @param limit the maximum number of NFT metadata objects to return (max: 100) + * @param lower_id ID of the first NFT metadata object to include in the list. + * @return Returns vector of NFT metadata objects, empty vector if none + */ + vector nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const; signed_transaction nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast ); signed_transaction create_offer(set item_ids, @@ -2943,6 +2963,8 @@ FC_API( graphene::wallet::wallet_api, (nft_get_approved) (nft_is_approved_for_all) (nft_get_all_tokens) + (nft_get_tokens_by_owner) + (nft_get_metadata_by_owner) (nft_lottery_buy_ticket) (create_offer) (create_bid) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 7eefae27..bf239679 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -7027,9 +7027,28 @@ bool wallet_api::nft_is_approved_for_all(string owner_account_id_or_name, string return my->_remote_db->nft_is_approved_for_all(owner_account.id, operator_account.id); } -vector wallet_api::nft_get_all_tokens() const +vector wallet_api::nft_get_all_tokens(uint32_t limit, optional lower_id) const { - return my->_remote_db->nft_get_all_tokens(); + nft_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_all_tokens(lb_id, limit); +} + +vector wallet_api::nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const +{ + nft_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_tokens_by_owner(owner, lb_id, limit); +} + +vector wallet_api::nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const +{ + nft_metadata_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_metadata_by_owner(owner, lb_id, limit); } signed_transaction wallet_api::nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast ) diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index b8fd19ea..83643eed 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -4,76 +4,204 @@ #include #include +#include + using namespace graphene::chain; using namespace graphene::chain::test; + +class nft_test_helper +{ + database_fixture& fixture_; + +public: + nft_test_helper(database_fixture& fixture): + fixture_(fixture) + { + fixture_.generate_blocks(HARDFORK_NFT_TIME); + fixture_.generate_block(); + fixture_.generate_block(); + set_expiration(fixture_.db, fixture_.trx); + } + + nft_metadata_object create_metadata(const std::string& name, const std::string& symbol, const std::string& uri, const account_id_type& owner, const fc::ecc::private_key &priv_key) + { + const auto& idx_by_id = fixture_.db.get_index_type().indices().get(); + size_t obj_count0 = idx_by_id.size(); + + fixture_.generate_block(); + + signed_transaction trx; + set_expiration(fixture_.db, trx); + + nft_metadata_create_operation op; + op.owner = owner; + op.symbol = symbol; + op.base_uri = uri; + op.name = name; + op.is_transferable = true; + BOOST_CHECK_NO_THROW(op.validate()); + trx.operations.push_back(op); + fixture_.sign(trx, priv_key); + PUSH_TX(fixture_.db, trx, ~0); + fixture_.generate_block(); + + BOOST_REQUIRE( idx_by_id.size() == obj_count0 + 1 ); // one more metadata created + + const auto& idx_by_name = fixture_.db.get_index_type().indices().get(); + auto obj = idx_by_name.find(name); + BOOST_CHECK( obj->owner == owner ); + BOOST_CHECK( obj->name == name ); + BOOST_CHECK( obj->symbol == symbol ); + BOOST_CHECK( obj->base_uri == uri ); + return *obj; + } + + + nft_object mint(const nft_metadata_id_type& metadata, const account_id_type& owner, const account_id_type& payer, + const fc::optional& approved, const std::vector& approved_operators, + const fc::ecc::private_key &priv_key) + { + const auto& idx_by_id = fixture_.db.get_index_type().indices().get(); + size_t obj_count0 = idx_by_id.size(); + + fixture_.generate_block(); + + signed_transaction trx; + set_expiration(fixture_.db, trx); + + nft_mint_operation op; + op.nft_metadata_id = metadata; + op.payer = payer; + op.owner = owner; + if (approved) + op.approved = *approved; + op.approved_operators = approved_operators; + + trx.operations.push_back(op); + fixture_.sign(trx, priv_key); + PUSH_TX(fixture_.db, trx, ~0); + + fixture_.generate_block(); + + BOOST_REQUIRE(idx_by_id.size() == obj_count0 + 1); // one more created + + auto obj = idx_by_id.rbegin(); + + BOOST_REQUIRE(obj != idx_by_id.rend()); + BOOST_CHECK(obj->owner == owner); + BOOST_CHECK(obj->approved_operators.size() == approved_operators.size()); + BOOST_CHECK(obj->approved_operators == approved_operators); + + return *obj; + } +}; + + BOOST_FIXTURE_TEST_SUITE( nft_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( nft_metadata_name_validation_test ) { + BOOST_TEST_MESSAGE("nft_metadata_name_validation_test"); + ACTORS((mdowner)); + nft_metadata_create_operation op; + op.owner = mdowner_id; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.name = "123"; + op.is_transferable = true; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ""; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "1ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ".abc"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "abc."; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ABC"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abcdefghijklmnopq"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "***"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "a12"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "a1b"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc123defg12345"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "NFT Test"; + BOOST_CHECK_NO_THROW(op.validate()); +} + + BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) { BOOST_TEST_MESSAGE("nft_metadata_create_test"); - generate_blocks(HARDFORK_NFT_TIME); - generate_block(); - generate_block(); - set_expiration(db, trx); - + nft_test_helper nfth(*this); ACTORS((mdowner)); + nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key); +} - generate_block(); - set_expiration(db, trx); +BOOST_AUTO_TEST_CASE( nft_metadata_listing_test ) { + + BOOST_TEST_MESSAGE("nft_metadata_listing_test"); + + nft_test_helper nfth(*this); + + ACTORS((mdowner1)); + ACTORS((mdowner2)); + + // prepare metadata set + for (int i=0; i < 200; i++) { - BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); - - nft_metadata_create_operation op; - op.owner = mdowner_id; - op.symbol = "NFT"; - op.base_uri = "http://nft.example.com"; - op.name = "123"; - op.is_transferable = true; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = ""; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "1ab"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = ".abc"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "abc."; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "ABC"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abcdefghijklmnopq"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "ab"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "***"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "a12"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "a1b"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abc"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abc123defg12345"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "NFT Test"; - trx.operations.push_back(op); - sign(trx, mdowner_private_key); - PUSH_TX(db, trx, ~0); + string sfx = fc::to_pretty_string(i); + nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner1_id, mdowner1_private_key); + BOOST_REQUIRE(md.id == nft_metadata_id_type(i)); + } + for (int i=200; i < 250; i++) + { + string sfx = fc::to_pretty_string(i); + nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner2_id, mdowner2_private_key); + BOOST_REQUIRE(md.id == nft_metadata_id_type(i)); } - generate_block(); - BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + graphene::app::database_api db_api(db); + vector listed; - 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" ); + // first 100 returned + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(99)); + + // 100 starting from 50 + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(50), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 50)); + BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(149)); + + // the last 5 must be returned + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(listed[0].id == nft_metadata_id_type(195)); + BOOST_REQUIRE(listed[4].id == nft_metadata_id_type(199)); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 101), fc::exception); + + // the last 40 must be returned + listed = db_api.nft_get_metadata_by_owner(mdowner2_id, nft_metadata_id_type(210), 100); + BOOST_REQUIRE(listed.size() == 40); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type(210)); + BOOST_REQUIRE(listed[39].id == nft_metadata_id_type(249)); } @@ -120,49 +248,112 @@ BOOST_AUTO_TEST_CASE( nft_mint_test ) { generate_block(); set_expiration(db, trx); - INVOKE(nft_metadata_create_test); + nft_test_helper nfth(*this); + ACTORS((mdowner)); ACTORS((alice)); ACTORS((bob)); ACTORS((operator1)); ACTORS((operator2)); - GET_ACTOR(mdowner); + nft_metadata_object md = nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key); - generate_block(); - set_expiration(db, trx); + nfth.mint(md.id, alice_id, mdowner_id, alice_id, {operator1_id, operator2_id}, alice_private_key); +} + +BOOST_AUTO_TEST_CASE( nft_object_listing_test ) { + + BOOST_TEST_MESSAGE("nft_object_listing_test"); + + nft_test_helper nfth(*this); + + ACTORS((mdowner1)); + ACTORS((mdowner2)); + ACTORS((alice)); + ACTORS((bob)); + + nft_metadata_object md1 = nfth.create_metadata("NFT Test 1", "NFT1", "http://nft.example.com", mdowner1_id, mdowner1_private_key); + nft_metadata_object md2 = nfth.create_metadata("NFT Test 2", "NFT2", "http://nft.example.com", mdowner2_id, mdowner2_private_key); + + // create NFT objects: 200 owned by alice and 200 by bob + for (int i=0; i < 200; i++) { - BOOST_TEST_MESSAGE("Send nft_mint_operation"); - - const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 1 ); - auto nft_md_obj = idx.begin(); - - nft_mint_operation op; - op.payer = mdowner_id; - op.nft_metadata_id = nft_md_obj->id; - op.owner = alice_id; - op.approved = alice_id; - op.approved_operators.push_back(operator1_id); - op.approved_operators.push_back(operator2_id); - - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + nft_object nft = nfth.mint(md1.id, alice_id, mdowner1_id, alice_id, {}, alice_private_key); + BOOST_REQUIRE(nft.id == nft_id_type(i)); + } + for (int i=200; i < 250; i++) + { + nft_object nft = nfth.mint(md1.id, bob_id, mdowner1_id, bob_id, {}, bob_private_key); + BOOST_REQUIRE(nft.id == nft_id_type(i)); } - generate_block(); - BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + graphene::app::database_api db_api(db); + vector listed; - 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 ); + // + // listing all tokens: + // + // first 100 returned, all alice's + listed = db_api.nft_get_all_tokens(nft_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_id_type(99)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + + // 100 starting from 50, all alice's + listed = db_api.nft_get_all_tokens(nft_id_type(50), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 50)); + BOOST_REQUIRE(listed[99].id == nft_id_type(149)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + + // the last 5 must be returned, all bob's + listed = db_api.nft_get_all_tokens(nft_id_type(245), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(listed[0].id == nft_id_type(245)); + BOOST_REQUIRE(listed[4].id == nft_id_type(249)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; })); + + // 10 from the middle of the set, half alice's, half bob's + listed = db_api.nft_get_all_tokens(nft_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 10); + BOOST_REQUIRE(listed[0].id == nft_id_type(195)); + BOOST_REQUIRE(listed[9].id == nft_id_type(204)); + BOOST_REQUIRE(listed[0].owner == alice_id); + BOOST_REQUIRE(listed[4].owner == alice_id); + BOOST_REQUIRE(listed[5].owner == bob_id); + BOOST_REQUIRE(listed[9].owner == bob_id); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_all_tokens(nft_id_type(0), 101), fc::exception); + + // + // listing tokens by owner: + // + // first 100 alice's + listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_id_type(99)); + + // the last 5 alice's must be returned + listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + BOOST_REQUIRE(listed[0].id == nft_id_type(195)); + BOOST_REQUIRE(listed[4].id == nft_id_type(199)); + + // all 50 bob's + listed = db_api.nft_get_tokens_by_owner(bob_id, nft_id_type(0), 60); + BOOST_REQUIRE(listed.size() == 50); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; })); + BOOST_REQUIRE(listed[ 0].id == nft_id_type(200)); + BOOST_REQUIRE(listed[49].id == nft_id_type(249)); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 101), fc::exception); }