From db9a35fa17417bf7100335d1d480a626176687fe Mon Sep 17 00:00:00 2001 From: sierra19XX <15652887+sierra19XX@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:51:18 +0000 Subject: [PATCH] ppy marketplace 4 - Add tests NFT+Marketplace --- libraries/chain/CMakeLists.txt | 1 + libraries/chain/db_getter.cpp | 10 + libraries/chain/db_init.cpp | 3 +- libraries/chain/db_update.cpp | 2 +- .../chain/include/graphene/chain/database.hpp | 1 + .../graphene/chain/offer_evaluator.hpp | 48 +- .../include/graphene/chain/offer_object.hpp | 161 ++-- .../include/graphene/chain/protocol/offer.hpp | 180 +++-- libraries/chain/nft_evaluator.cpp | 4 +- libraries/chain/offer_evaluator.cpp | 391 +++++---- libraries/chain/offer_object.cpp | 50 ++ libraries/chain/protocol/offer.cpp | 73 +- tests/tests/marketplace_tests.cpp | 756 ++++++++++++++++++ tests/tests/nft_tests.cpp | 1 + tests/tests/operation_tests.cpp | 488 ----------- 15 files changed, 1289 insertions(+), 880 deletions(-) create mode 100644 libraries/chain/offer_object.cpp create mode 100644 tests/tests/marketplace_tests.cpp diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 57005e7b..bdab34f0 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -116,6 +116,7 @@ add_library( graphene_chain affiliate_payout.cpp + offer_object.cpp offer_evaluator.cpp nft_evaluator.cpp diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index a6f7af19..6accc7d3 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include @@ -159,4 +160,13 @@ const witness_schedule_object& database::get_witness_schedule_object()const return *_p_witness_schedule_obj; } +bool database::item_locked(const nft_id_type &item) const +{ + const auto &offer_idx = get_index_type(); + const auto &oidx = dynamic_cast(offer_idx); + const auto &market_items = oidx.get_secondary_index(); + + auto items_itr = market_items._locked_items.find(item); + return (items_itr != market_items._locked_items.end()); +} } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index f035964e..5bb60558 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -305,7 +305,8 @@ void database::initialize_indexes() tournament_details_idx->add_secondary_index(); add_index< primary_index >(); add_index< primary_index >(); - add_index< primary_index >(); + auto offer_idx = add_index< primary_index >(); + offer_idx->add_secondary_index(); add_index< primary_index >(); add_index< primary_index >(); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index df37c741..96f9e3ab 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -722,7 +722,7 @@ void database::finalize_expired_offers(){ finalize_offer_operation finalize; finalize.fee_paying_account = offer.issuer; finalize.offer_id = offer.id; - finalize.fee = current_fee_schedule().calculate_fee( finalize ); + finalize.fee = asset( 0, asset_id_type() ); finalize.result = offer.bidder ? result_type::Expired : result_type::ExpiredNoBid; cancel_context.skip_fee_schedule_check = true; diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index b297f477..5978260a 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -282,6 +282,7 @@ namespace graphene { namespace chain { std::vector get_seeds( asset_id_type for_asset, uint8_t count_winners )const; uint64_t get_random_bits( uint64_t bound ); const witness_schedule_object& get_witness_schedule_object()const; + bool item_locked(const nft_id_type& item)const; time_point_sec head_block_time()const; uint32_t head_block_num()const; diff --git a/libraries/chain/include/graphene/chain/offer_evaluator.hpp b/libraries/chain/include/graphene/chain/offer_evaluator.hpp index d566410d..1db48b0d 100644 --- a/libraries/chain/include/graphene/chain/offer_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/offer_evaluator.hpp @@ -4,35 +4,35 @@ namespace graphene { -namespace chain -{ + namespace chain + { -class offer_evaluator : public evaluator -{ -public: - typedef offer_operation operation_type; + class offer_evaluator : public evaluator + { + public: + typedef offer_operation operation_type; - void_result do_evaluate(const offer_operation &o); - object_id_type do_apply(const offer_operation &o); -}; + void_result do_evaluate(const offer_operation &o); + object_id_type do_apply(const offer_operation &o); + }; -class bid_evaluator : public evaluator -{ -public: - typedef bid_operation operation_type; + class bid_evaluator : public evaluator + { + public: + typedef bid_operation operation_type; - void_result do_evaluate(const bid_operation &o); - void_result do_apply(const bid_operation &o); -}; + void_result do_evaluate(const bid_operation &o); + void_result do_apply(const bid_operation &o); + }; -class finalize_offer_evaluator : public evaluator -{ -public: - typedef finalize_offer_operation operation_type; + class finalize_offer_evaluator : public evaluator + { + public: + typedef finalize_offer_operation operation_type; - void_result do_evaluate(const finalize_offer_operation &op); - void_result do_apply(const finalize_offer_operation &op); -}; + void_result do_evaluate(const finalize_offer_operation &op); + void_result do_apply(const finalize_offer_operation &op); + }; -} // namespace chain + } // namespace chain } // namespace graphene diff --git a/libraries/chain/include/graphene/chain/offer_object.hpp b/libraries/chain/include/graphene/chain/offer_object.hpp index 9fe29a46..8fecc4da 100644 --- a/libraries/chain/include/graphene/chain/offer_object.hpp +++ b/libraries/chain/include/graphene/chain/offer_object.hpp @@ -4,104 +4,105 @@ namespace graphene { -namespace chain -{ -class database; + namespace chain + { + class database; -/** - * @brief This class represents an offer on the object graph - * @ingroup object - * @ingroup protocol - * - */ -struct by_expiration_date -{ -}; -class offer_object : public graphene::db::abstract_object -{ -public: - static const uint8_t space_id = protocol_ids; - static const uint8_t type_id = offer_object_type; + struct by_expiration_date + { + }; + class offer_object : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = offer_object_type; - account_id_type issuer; + account_id_type issuer; - set item_ids; - optional bidder; - optional bid_price; - asset minimum_price; - asset maximum_price; + set item_ids; + optional bidder; + optional bid_price; + asset minimum_price; + asset maximum_price; - bool buying_item; - fc::time_point_sec offer_expiration_date; + bool buying_item; + fc::time_point_sec offer_expiration_date; - offer_id_type get_id() const { return id; } -}; + offer_id_type get_id() const { return id; } + }; -class offer_history_object : public graphene::db::abstract_object -{ -public: - static const uint8_t space_id = implementation_ids; - static const uint8_t type_id = impl_offer_history_object_type; + class offer_history_object + : public graphene::db::abstract_object + { + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_offer_history_object_type; - account_id_type issuer; + account_id_type issuer; - set item_ids; - optional bidder; - optional bid_price; - asset minimum_price; - asset maximum_price; + set item_ids; + optional bidder; + optional bid_price; + asset minimum_price; + asset maximum_price; - bool buying_item; - fc::time_point_sec offer_expiration_date; + bool buying_item; + fc::time_point_sec offer_expiration_date; - offer_history_id_type get_id() const { return id; } -}; + offer_history_id_type get_id() const { return id; } + }; -struct compare_by_expiration_date -{ - bool operator()(const fc::time_point_sec &o1, const fc::time_point_sec &o2) const - { - return o1 > o2; - } -}; + class offer_item_index : public secondary_index + { + public: + virtual void object_inserted(const object &obj) override; + virtual void object_removed(const object &obj) override; + virtual void about_to_modify(const object &before) override{}; + virtual void object_modified(const object &after) override; -/** - * @ingroup object_index - */ -typedef multi_index_container< - offer_object, - indexed_by< - ordered_unique, member>, - ordered_non_unique< - tag, - member, - compare_by_expiration_date>>> - offer_multi_index_type; + set _locked_items; + }; -typedef multi_index_container< - offer_history_object, - indexed_by< - ordered_unique, member>, - ordered_non_unique< - tag, - member, - compare_by_expiration_date>>> - offer_history_multi_index_type; + struct compare_by_expiration_date + { + bool operator()(const fc::time_point_sec &o1, + const fc::time_point_sec &o2) const + { + return o1 > o2; + } + }; -/** - * @ingroup object_index - */ -typedef generic_index offer_index; + using offer_multi_index_type = multi_index_container< + offer_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, + member, + compare_by_expiration_date>>>; -typedef generic_index offer_history_index; + using offer_history_multi_index_type = multi_index_container< + offer_history_object, + indexed_by< + ordered_unique, member>, + ordered_non_unique, + member, + compare_by_expiration_date>>>; -} // namespace chain + using offer_index = generic_index; + + using offer_history_index = + generic_index; + + } // namespace chain } // namespace graphene -FC_REFLECT_DERIVED(graphene::chain::offer_object, - (graphene::db::object), - (issuer)(item_ids)(bidder)(bid_price)(minimum_price)(maximum_price)(buying_item)(offer_expiration_date)) +FC_REFLECT_DERIVED(graphene::chain::offer_object, (graphene::db::object), + (issuer)(item_ids)(bidder)(bid_price)(minimum_price)( + maximum_price)(buying_item)(offer_expiration_date)) FC_REFLECT_DERIVED(graphene::chain::offer_history_object, (graphene::db::object), - (issuer)(item_ids)(bidder)(bid_price)(minimum_price)(maximum_price)(buying_item)(offer_expiration_date)) + (issuer)(item_ids)(bidder)(bid_price)(minimum_price)( + maximum_price)(buying_item)(offer_expiration_date)) diff --git a/libraries/chain/include/graphene/chain/protocol/offer.hpp b/libraries/chain/include/graphene/chain/protocol/offer.hpp index 627766be..c2e66053 100644 --- a/libraries/chain/include/graphene/chain/protocol/offer.hpp +++ b/libraries/chain/include/graphene/chain/protocol/offer.hpp @@ -4,113 +4,123 @@ namespace graphene { -namespace chain -{ - -/* - * @class offer_operation - * @brief To place an offer to buy or sell an item, a user broadcasts a proposed transaction - * @ingroup operations - * @pre amount.asset_id->issuer == issuer - * @pre issuer != from because this is pointless, use a normal transfer operation - */ -struct offer_operation : public base_operation -{ - struct fee_parameters_type + namespace chain { - uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. - }; - set item_ids; + /* + * @class offer_operation + * @brief To place an offer to buy or sell an item, a user broadcasts a + * proposed transaction + * @ingroup operations + * operation + */ + struct offer_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = + 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + }; - // /** - // * minimum_price.asset_id == maximum_price.asset_id. - // * to set fixed price without auction minimum_price == maximum_price - // * If buying_item is true, and minimum_price != maximum_price, the user is proposing a “reverse auction” - // * where bidders can offer to sell the item for progressively lower prices. - // * In this case, minimum_price functions as the sell-it-now price for the reverse auction - // */ + set item_ids; - asset fee; - account_id_type issuer; + // /** + // * minimum_price.asset_id == maximum_price.asset_id. + // * to set fixed price without auction minimum_price == maximum_price + // * If buying_item is true, and minimum_price != maximum_price, the user is + // proposing a “reverse auction” + // * where bidders can offer to sell the item for progressively lower prices. + // * In this case, minimum_price functions as the sell-it-now price for the + // reverse auction + // */ - /// minimum_price is minimum bid price. 0 if no minimum_price required - asset minimum_price; - /// buy_it_now price. 0 if no maximum price - asset maximum_price; - /// true means user wants to buy item, false mean user is selling item - bool buying_item; - /// not transaction expiration date - fc::time_point_sec offer_expiration_date; + asset fee; + account_id_type issuer; - /// User provided data encrypted to the memo key of the "to" account - optional memo; - extensions_type extensions; + /// minimum_price is minimum bid price. 0 if no minimum_price required + asset minimum_price; + /// buy_it_now price. 0 if no maximum price + asset maximum_price; + /// true means user wants to buy item, false mean user is selling item + bool buying_item; + /// not transaction expiration date + fc::time_point_sec offer_expiration_date; - account_id_type fee_payer() const { return issuer; } - void validate() const; - share_type calculate_fee(const fee_parameters_type &k) const; -}; + /// User provided data encrypted to the memo key of the "to" account + optional memo; + extensions_type extensions; -struct bid_operation : public base_operation -{ - struct fee_parameters_type - { - uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. - }; + account_id_type fee_payer() const { return issuer; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; - asset fee; - account_id_type bidder; + struct bid_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = + 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. + }; - asset bid_price; - offer_id_type offer_id; + asset fee; + account_id_type bidder; - extensions_type extensions; + asset bid_price; + offer_id_type offer_id; - account_id_type fee_payer() const { return bidder; } - void validate() const; - share_type calculate_fee(const fee_parameters_type &k) const; -}; + extensions_type extensions; -enum class result_type -{ - Expired = 0, - ExpiredNoBid = 1 -}; + account_id_type fee_payer() const { return bidder; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; -struct finalize_offer_operation : public base_operation -{ - struct fee_parameters_type - { - uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; - uint32_t price_per_kbyte = 10 * GRAPHENE_BLOCKCHAIN_PRECISION; /// only required for large memos. - }; + enum class result_type + { + Expired = 0, + ExpiredNoBid = 1 + }; - asset fee; - account_id_type fee_paying_account; + struct finalize_offer_operation : public base_operation + { + struct fee_parameters_type + { + uint64_t fee = 0; + }; - offer_id_type offer_id; + asset fee; + account_id_type fee_paying_account; - fc::enum_type result; + offer_id_type offer_id; - extensions_type extensions; + fc::enum_type result; - account_id_type fee_payer() const { return fee_paying_account; } - void validate() const; - share_type calculate_fee(const fee_parameters_type &k) const; -}; + extensions_type extensions; -} // namespace chain + account_id_type fee_payer() const { return fee_paying_account; } + void validate() const; + share_type calculate_fee(const fee_parameters_type &k) const; + }; + + } // namespace chain } // namespace graphene -FC_REFLECT(graphene::chain::offer_operation::fee_parameters_type, (fee)(price_per_kbyte)); -FC_REFLECT(graphene::chain::offer_operation, (fee)(issuer)(memo)(minimum_price)(maximum_price)(buying_item)(item_ids)(offer_expiration_date)(extensions)); +FC_REFLECT(graphene::chain::offer_operation::fee_parameters_type, + (fee)(price_per_kbyte)); +FC_REFLECT(graphene::chain::offer_operation, + (fee)(issuer)(memo)(minimum_price)(maximum_price)(buying_item)( + item_ids)(offer_expiration_date)(extensions)); -FC_REFLECT(graphene::chain::bid_operation::fee_parameters_type, (fee)(price_per_kbyte)); -FC_REFLECT(graphene::chain::bid_operation, (fee)(bidder)(bid_price)(offer_id)(extensions)); +FC_REFLECT(graphene::chain::bid_operation::fee_parameters_type, + (fee)(price_per_kbyte)); +FC_REFLECT(graphene::chain::bid_operation, + (fee)(bidder)(bid_price)(offer_id)(extensions)); FC_REFLECT_ENUM(graphene::chain::result_type, (Expired)(ExpiredNoBid)); -FC_REFLECT(graphene::chain::finalize_offer_operation::fee_parameters_type, (fee)(price_per_kbyte)); -FC_REFLECT(graphene::chain::finalize_offer_operation, (fee)(fee_paying_account)(offer_id)(result)(extensions)); +FC_REFLECT(graphene::chain::finalize_offer_operation::fee_parameters_type, + (fee)); +FC_REFLECT(graphene::chain::finalize_offer_operation, + (fee)(fee_paying_account)(offer_id)(result)(extensions)); diff --git a/libraries/chain/nft_evaluator.cpp b/libraries/chain/nft_evaluator.cpp index 21c4f79a..26bb9b18 100644 --- a/libraries/chain/nft_evaluator.cpp +++ b/libraries/chain/nft_evaluator.cpp @@ -78,6 +78,8 @@ 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" ); + FC_ASSERT(!db().item_locked(op.token_id), "Item(s) is already on sale on market, transfer is not allowed"); + auto itr_operator = idx_acc.find(op.operator_); FC_ASSERT( itr_operator != idx_acc.end(), "Operator account does not exists" ); @@ -92,7 +94,7 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf 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.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" ); + FC_ASSERT( (itr_nft->owner == op.operator_) || (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) ) } diff --git a/libraries/chain/offer_evaluator.cpp b/libraries/chain/offer_evaluator.cpp index d75b6408..b43d353a 100644 --- a/libraries/chain/offer_evaluator.cpp +++ b/libraries/chain/offer_evaluator.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -8,179 +9,245 @@ namespace graphene { -namespace chain -{ - -void_result offer_evaluator::do_evaluate(const offer_operation &op) -{ - try + namespace chain { - database &d = db(); - FC_ASSERT(op.offer_expiration_date > d.head_block_time()); - FC_ASSERT(op.fee.amount >= 0); - FC_ASSERT(op.minimum_price.amount >= 0 && op.maximum_price.amount > 0); - FC_ASSERT(op.minimum_price.asset_id == op.maximum_price.asset_id); - FC_ASSERT(op.maximum_price >= op.minimum_price); - - return void_result(); - } - FC_CAPTURE_AND_RETHROW((op)) -} - -object_id_type offer_evaluator::do_apply(const offer_operation &o) -{ - try - { - database &d = db(); - - if (o.buying_item) + void_result offer_evaluator::do_evaluate(const offer_operation &op) { - d.adjust_balance(o.issuer, -o.maximum_price); - } - - const auto &offer_obj = db().create([&](offer_object &obj) { - obj.issuer = o.issuer; - - obj.item_ids = o.item_ids; - - obj.minimum_price = o.minimum_price; - obj.maximum_price = o.maximum_price; - - obj.buying_item = o.buying_item; - obj.offer_expiration_date = o.offer_expiration_date; - auto &idx = d.get_index_type().indices().get(); - auto acc = idx.find(o.issuer); - FC_ASSERT(acc != idx.end()); - }); - - return offer_obj.id; - } - FC_CAPTURE_AND_RETHROW((o)) -} - -void_result bid_evaluator::do_evaluate(const bid_operation &op) -{ - try - { - database &d = db(); - offer_object offer = op.offer_id(d); - - FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id); - FC_ASSERT(offer.minimum_price.amount == 0 || op.bid_price >= offer.minimum_price); - FC_ASSERT(offer.maximum_price.amount == 0 || op.bid_price <= offer.maximum_price); - if (offer.bidder) - { - FC_ASSERT((offer.buying_item && op.bid_price < *offer.bid_price) || (!offer.buying_item && op.bid_price > *offer.bid_price)); - } - return void_result(); - } - FC_CAPTURE_AND_RETHROW((op)) -} - -void_result bid_evaluator::do_apply(const bid_operation &op) -{ - try - { - database &d = db(); - - offer_object offer = op.offer_id(d); - - if (!offer.buying_item) - { - if (offer.bidder) + try { - d.adjust_balance(*offer.bidder, *offer.bid_price); + const database &d = db(); + op.issuer(d); + for (const auto &item : op.item_ids) + { + const auto &nft_obj = item(d); + FC_ASSERT(!d.item_locked(item), "Item(s) is already on sale"); + bool is_owner = (nft_obj.owner == op.issuer); + bool is_approved = (nft_obj.approved == op.issuer); + bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.issuer) != nft_obj.approved_operators.end()); + if (op.buying_item) + { + FC_ASSERT(!is_owner, "Buyer cannot already be an onwer of the item"); + FC_ASSERT(!is_approved, "Buyer cannot already be approved account of the item"); + FC_ASSERT(!is_approved_operator, "Buyer cannot already be an approved operator of the item"); + } + else + { + FC_ASSERT(is_owner || is_approved || is_approved_operator, "Issuer has no authority to sell the item"); + } + } + FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future"); + FC_ASSERT(op.fee.amount >= 0, "Invalid fee"); + FC_ASSERT(op.minimum_price.amount >= 0 && op.maximum_price.amount > 0, "Invalid amount"); + FC_ASSERT(op.minimum_price.asset_id == op.maximum_price.asset_id, "Asset ID mismatch"); + FC_ASSERT(op.maximum_price >= op.minimum_price, "Invalid max min prices"); + return void_result(); } - d.adjust_balance(op.bidder, -op.bid_price); + FC_CAPTURE_AND_RETHROW((op)) } - d.modify(op.offer_id(d), [&](offer_object &o) { - if (op.bid_price == (offer.buying_item ? offer.minimum_price : offer.maximum_price)) + + object_id_type offer_evaluator::do_apply(const offer_operation &op) + { + try { - o.offer_expiration_date = d.head_block_time(); + database &d = db(); + if (op.buying_item) + { + d.adjust_balance(op.issuer, -op.maximum_price); + } + + const auto &offer_obj = db().create([&](offer_object &obj) { + obj.issuer = op.issuer; + + obj.item_ids = op.item_ids; + + obj.minimum_price = op.minimum_price; + obj.maximum_price = op.maximum_price; + + obj.buying_item = op.buying_item; + obj.offer_expiration_date = op.offer_expiration_date; + }); + return offer_obj.id; } - o.bidder = op.bidder; - o.bid_price = op.bid_price; - }); - return void_result(); - } - FC_CAPTURE_AND_RETHROW((op)) -} - -void_result finalize_offer_evaluator::do_evaluate(const finalize_offer_operation &op) -{ - try - { - database &d = db(); - offer_object offer = op.offer_id(d); - - if (op.result != result_type::ExpiredNoBid) - { - FC_ASSERT(offer.bidder); - FC_ASSERT((*offer.bid_price).amount >= 0); - } - else - { - FC_ASSERT(!offer.bidder); + FC_CAPTURE_AND_RETHROW((op)) } - switch (op.result) + void_result bid_evaluator::do_evaluate(const bid_operation &op) { - case result_type::Expired: - case result_type::ExpiredNoBid: - FC_ASSERT(offer.offer_expiration_date <= d.head_block_time()); - break; - default: - FC_THROW_EXCEPTION(fc::assert_exception, "finalize_offer_operation: unknown result type."); - break; - } - - return void_result(); - } - FC_CAPTURE_AND_RETHROW((op)) -} - -void_result finalize_offer_evaluator::do_apply(const finalize_offer_operation &op) -{ - database &d = db(); - - offer_object offer = op.offer_id(d); - - if (op.result != result_type::ExpiredNoBid) - { - if (offer.buying_item) - { - d.adjust_balance(*offer.bidder, *offer.bid_price); - if (offer.bid_price < offer.maximum_price) + try { - d.adjust_balance(offer.issuer, offer.maximum_price - *offer.bid_price); - } - } - else - { - d.adjust_balance(offer.issuer, *offer.bid_price); - } - } - else - { - if (offer.buying_item) - { - d.adjust_balance(offer.issuer, offer.maximum_price); - } - } - d.create([&](offer_history_object &obj) { - obj.issuer = offer.issuer; - obj.item_ids = offer.item_ids; - obj.bidder = offer.bidder; - obj.bid_price = offer.bid_price; - obj.minimum_price = offer.minimum_price; - obj.maximum_price = offer.maximum_price; - obj.buying_item = offer.buying_item; - obj.offer_expiration_date = offer.offer_expiration_date; - }); - d.remove(op.offer_id(d)); - return void_result(); -} + const database &d = db(); + const auto &offer = op.offer_id(d); + op.bidder(d); + for (const auto &item : offer.item_ids) + { + const auto &nft_obj = item(d); + bool is_owner = (nft_obj.owner == op.bidder); + bool is_approved = (nft_obj.approved == op.bidder); + bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.bidder) != nft_obj.approved_operators.end()); + if (offer.buying_item) + { + FC_ASSERT(is_owner || is_approved || is_approved_operator, "Bidder has no authority to sell the item"); + } + else + { + FC_ASSERT(!is_owner, "Bidder cannot already be an onwer of the item"); + FC_ASSERT(!is_approved, "Bidder cannot already be an approved account of the item"); + FC_ASSERT(!is_approved_operator, "Bidder cannot already be an approved operator of the item"); + } + } -} // namespace chain + FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch"); + FC_ASSERT(offer.minimum_price.amount == 0 || op.bid_price >= offer.minimum_price); + FC_ASSERT(offer.maximum_price.amount == 0 || op.bid_price <= offer.maximum_price); + if (offer.bidder) + { + FC_ASSERT((offer.buying_item && op.bid_price < *offer.bid_price) || (!offer.buying_item && op.bid_price > *offer.bid_price), "There is already a better bid than this"); + } + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result bid_evaluator::do_apply(const bid_operation &op) + { + try + { + database &d = db(); + + const auto &offer = op.offer_id(d); + + if (!offer.buying_item) + { + if (offer.bidder) + { + d.adjust_balance(*offer.bidder, *offer.bid_price); + } + d.adjust_balance(op.bidder, -op.bid_price); + } + d.modify(op.offer_id(d), [&](offer_object &o) { + if (op.bid_price == (offer.buying_item ? offer.minimum_price : offer.maximum_price)) + { + o.offer_expiration_date = d.head_block_time(); + } + o.bidder = op.bidder; + o.bid_price = op.bid_price; + }); + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result finalize_offer_evaluator::do_evaluate(const finalize_offer_operation &op) + { + try + { + const database &d = db(); + const auto &offer = op.offer_id(d); + + if (op.result != result_type::ExpiredNoBid) + { + FC_ASSERT(offer.bidder, "No valid bidder"); + FC_ASSERT((*offer.bid_price).amount >= 0, "Invalid bid price"); + } + else + { + FC_ASSERT(!offer.bidder, "There should not be a valid bidder"); + } + + switch (op.result) + { + case result_type::Expired: + case result_type::ExpiredNoBid: + FC_ASSERT(offer.offer_expiration_date <= d.head_block_time(), "Offer finalized beyong expiration time"); + break; + default: + FC_THROW_EXCEPTION(fc::assert_exception, "finalize_offer_operation: unknown result type."); + break; + } + + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + + void_result finalize_offer_evaluator::do_apply(const finalize_offer_operation &op) + { + try + { + database &d = db(); + + offer_object offer = op.offer_id(d); + vector xfer_ops; + + if (op.result != result_type::ExpiredNoBid) + { + if (offer.buying_item) + { + d.adjust_balance(*offer.bidder, *offer.bid_price); + if (offer.bid_price < offer.maximum_price) + { + d.adjust_balance(offer.issuer, offer.maximum_price - *offer.bid_price); + } + } + else + { + d.adjust_balance(offer.issuer, *offer.bid_price); + } + + for (const auto &item : offer.item_ids) + { + const auto &nft_obj = item(d); + nft_safe_transfer_from_operation xfer_op; + xfer_op.from = nft_obj.owner; + xfer_op.token_id = item; + xfer_op.fee = asset(0, asset_id_type()); + if (offer.buying_item) + { + xfer_op.to = offer.issuer; + xfer_op.operator_ = *offer.bidder; + } + else + { + xfer_op.to = *offer.bidder; + xfer_op.operator_ = offer.issuer; + } + xfer_ops.push_back(std::move(xfer_op)); + } + } + else + { + if (offer.buying_item) + { + d.adjust_balance(offer.issuer, offer.maximum_price); + } + } + d.create([&](offer_history_object &obj) { + obj.issuer = offer.issuer; + obj.item_ids = offer.item_ids; + obj.bidder = offer.bidder; + obj.bid_price = offer.bid_price; + obj.minimum_price = offer.minimum_price; + obj.maximum_price = offer.maximum_price; + obj.buying_item = offer.buying_item; + obj.offer_expiration_date = offer.offer_expiration_date; + }); + // This should unlock the item + d.remove(op.offer_id(d)); + // Send safe transfer from ops + if (xfer_ops.size() > 0) + { + transaction_evaluation_state xfer_context(&d); + xfer_context.skip_fee_schedule_check = true; + for (const auto &xfer_op : xfer_ops) + { + d.apply_operation(xfer_context, xfer_op); + } + } + + return void_result(); + } + FC_CAPTURE_AND_RETHROW((op)) + } + } // namespace chain } // namespace graphene \ No newline at end of file diff --git a/libraries/chain/offer_object.cpp b/libraries/chain/offer_object.cpp new file mode 100644 index 00000000..35ac47d2 --- /dev/null +++ b/libraries/chain/offer_object.cpp @@ -0,0 +1,50 @@ +#include +#include + +namespace graphene +{ + namespace chain + { + + void offer_item_index::object_inserted(const object &obj) + { + assert(dynamic_cast(&obj)); + const offer_object &oo = static_cast(obj); + if (!oo.buying_item) + { + for (const auto &id : oo.item_ids) + { + _locked_items.emplace(id); + } + } + } + + void offer_item_index::object_modified(const object &after) + { + assert(dynamic_cast(&after)); + const offer_object &oo = static_cast(after); + if (oo.buying_item && oo.bidder) + { + for (const auto &id : oo.item_ids) + { + _locked_items.emplace(id); + } + } + } + + void offer_item_index::object_removed(const object &obj) + { + assert(dynamic_cast(&obj)); + const offer_object &oo = static_cast(obj); + + if (!oo.buying_item || oo.bidder) + { + for (const auto &id : oo.item_ids) + { + _locked_items.erase(id); + } + } + } + + } // namespace chain +} // namespace graphene \ No newline at end of file diff --git a/libraries/chain/protocol/offer.cpp b/libraries/chain/protocol/offer.cpp index 3909e5cd..4fc60fc4 100644 --- a/libraries/chain/protocol/offer.cpp +++ b/libraries/chain/protocol/offer.cpp @@ -2,48 +2,45 @@ namespace graphene { -namespace chain -{ -share_type offer_operation::calculate_fee(const fee_parameters_type &schedule) const -{ - share_type core_fee_required = schedule.fee; - return core_fee_required; -} - -void offer_operation::validate() const -{ - for (const auto &item_id : item_ids) + namespace chain { - FC_ASSERT(item_id > 0); - } - FC_ASSERT(fee.amount >= 0); - FC_ASSERT(minimum_price.asset_id == maximum_price.asset_id); - FC_ASSERT(minimum_price.amount >= 0 && maximum_price.amount > 0); - FC_ASSERT(maximum_price >= minimum_price); -} + share_type offer_operation::calculate_fee(const fee_parameters_type &schedule) const + { + share_type core_fee_required = schedule.fee; + return core_fee_required; + } -share_type bid_operation::calculate_fee(const fee_parameters_type &schedule) const -{ - share_type core_fee_required = schedule.fee; - return core_fee_required; -} + void offer_operation::validate() const + { + FC_ASSERT(item_ids.size() > 0); + FC_ASSERT(fee.amount >= 0); + FC_ASSERT(minimum_price.asset_id == maximum_price.asset_id); + FC_ASSERT(minimum_price.amount >= 0 && maximum_price.amount > 0); + FC_ASSERT(maximum_price >= minimum_price); + } -void bid_operation::validate() const -{ - FC_ASSERT(fee.amount.value >= 0); - FC_ASSERT(bid_price.amount.value >= 0); -} + share_type bid_operation::calculate_fee(const fee_parameters_type &schedule) const + { + share_type core_fee_required = schedule.fee; + return core_fee_required; + } -void finalize_offer_operation::validate() const -{ - FC_ASSERT(fee.amount.value >= 0); -} + void bid_operation::validate() const + { + FC_ASSERT(fee.amount.value >= 0); + FC_ASSERT(bid_price.amount.value >= 0); + } -share_type finalize_offer_operation::calculate_fee(const fee_parameters_type &k) const -{ - share_type core_fee_required = k.fee; - return core_fee_required; -} + void finalize_offer_operation::validate() const + { + FC_ASSERT(fee.amount.value >= 0); + } -} // namespace chain + share_type finalize_offer_operation::calculate_fee(const fee_parameters_type &k) const + { + share_type core_fee_required = k.fee; + return core_fee_required; + } + + } // namespace chain } // namespace graphene \ No newline at end of file diff --git a/tests/tests/marketplace_tests.cpp b/tests/tests/marketplace_tests.cpp new file mode 100644 index 00000000..0327da58 --- /dev/null +++ b/tests/tests/marketplace_tests.cpp @@ -0,0 +1,756 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(marketplace_tests, database_fixture) +offer_id_type buy_offer; +offer_id_type sell_offer; +BOOST_AUTO_TEST_CASE(nft_metadata_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_mint_test) +{ + + BOOST_TEST_MESSAGE("nft_mint_test"); + + generate_block(); + set_expiration(db, trx); + + INVOKE(nft_metadata_create_test); + + ACTORS((alice)(bob)(charlie)(operator1)(operator2)); + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + upgrade_to_lifetime_member(charlie); + transfer(committee_account, alice_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, bob_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + transfer(committee_account, charlie_id, asset(1000 * GRAPHENE_BLOCKCHAIN_PRECISION)); + + 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); + op.approved_operators.push_back(operator2_id); + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check nft_mint_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); + + { + 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); + trx.clear(); + } + generate_block(); + BOOST_REQUIRE(idx.size() == 2); + 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); + const auto &nft2 = nft_id_type(1)(db); + BOOST_CHECK(nft2.owner == alice_id); + BOOST_CHECK(nft2.approved_operators.size() == 2); + BOOST_CHECK(nft2.approved_operators.at(0) == operator1_id); + BOOST_CHECK(nft2.approved_operators.at(1) == operator2_id); +} + +BOOST_AUTO_TEST_CASE(create_sell_offer_test) +{ + try + { + INVOKE(nft_mint_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + const asset_object &bitusd = create_bitasset("STUB"); + { + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.item_ids.emplace(nft_id_type(1)); + offer_op.issuer = alice_id; + offer_op.buying_item = false; + offer_op.maximum_price = asset(10000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + trx.operations.push_back(offer_op); + auto op = trx.operations.back().get(); + REQUIRE_THROW_WITH_VALUE(op, offer_expiration_date, db.head_block_time()); + REQUIRE_THROW_WITH_VALUE(op, issuer, bob_id); + // positive prices + REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(-1)); + REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(-1)); + REQUIRE_OP_VALIDATION_FAILURE(op, fee, asset(-1)); + // min price > max price check + REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(1)); + // different asset for min/max + REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(1, bitusd.id)); + + trx.clear(); + trx.operations.push_back(offer_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + //generate_block(); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + const offer_object &d = offer_id_type(0)(db); + + BOOST_CHECK(d.space_id == protocol_ids); + BOOST_CHECK(d.type_id == offer_object_type); + //empty bid + BOOST_CHECK(!d.bid_price); + BOOST_CHECK(!d.bidder); + // data integrity + BOOST_CHECK(d.issuer == alice_id); + BOOST_CHECK(d.maximum_price == asset(10000)); + BOOST_CHECK(d.minimum_price == asset(10)); + BOOST_CHECK(d.buying_item == false); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == true); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == true); + sell_offer = d.id; + } + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(buy_bid_for_sell_offer_test) +{ + try + { + INVOKE(create_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount + 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + asset exp_delta_bidder = -bid_op.bid_price; + int64_t bidder_balance = get_balance(bob_id(db), asset_id_type()(db)); + + auto op = trx.operations.back().get(); + // Positive asset values + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(-1, asset_id_type())); + // Max price limit + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(offer_obj.maximum_price.amount + 1, offer_obj.minimum_price.asset_id)); + // Min Price Limit + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(offer_obj.minimum_price.amount - 1, offer_obj.minimum_price.asset_id)); + // Invalid offer + REQUIRE_THROW_WITH_VALUE(op, offer_id, offer_id_type(6)); + // Owner bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, alice_id); + // Operator bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, operator1_id); + // Different asset + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(50, asset_id_type(1))); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bidder_balance + exp_delta_bidder.amount).value); + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(second_buy_bid_for_sell_offer_test) +{ + try + { + INVOKE(buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t charlie_balance = get_balance(charlie_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset((*offer_obj.bid_price).amount + 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = charlie_id; + trx.operations.push_back(bid_op); + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = *offer_obj.bid_price; + asset exp_delta_bidder2 = -bid; + + auto op = trx.operations.back().get(); + // Not a better bid than previous + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset((*offer_obj.bid_price).amount, offer_obj.minimum_price.asset_id)); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, charlie_private_key); + PUSH_TX(db, trx); + trx.clear(); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder1.amount).value); + BOOST_CHECK_EQUAL(get_balance(charlie_id(db), asset_id_type()(db)), + (charlie_balance + exp_delta_bidder2.amount).value); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + + // data integrity + BOOST_CHECK(offer_obj.bidder == charlie_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(best_buy_bid_for_sell_offer) +{ + try + { + INVOKE(second_buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t charlie_balance = get_balance(charlie_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.maximum_price.amount, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = *offer_obj.bid_price; + asset exp_delta_bidder2 = -bid; + + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + // Check balances + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder2.amount).value); + BOOST_CHECK_EQUAL(get_balance(charlie_id(db), asset_id_type()(db)), + (charlie_balance + exp_delta_bidder1.amount).value); + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(10000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + auto cached_offer_obj = offer_obj; + // Generate a block and offer should be finalized with bid + generate_block(); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == bob_id) && (nft_id_type(1)(db).owner == bob_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(expire_with_bid_for_sell_offer_test) +{ + INVOKE(second_buy_bid_for_sell_offer_test); + GET_ACTOR(alice); + GET_ACTOR(charlie); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + (*cached_offer_obj.bid_price).amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == charlie_id) && (nft_id_type(1)(db).owner == charlie_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); +} + +BOOST_AUTO_TEST_CASE(expire_no_bid_for_sell_offer_test) +{ + INVOKE(create_sell_offer_test); + GET_ACTOR(alice); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = sell_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + alice_balance); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 1); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(0)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); +} + +BOOST_AUTO_TEST_CASE(create_buy_offer_test) +{ + try + { + INVOKE(best_buy_bid_for_sell_offer); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + GET_ACTOR(operator2); + { + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + offer_operation offer_op; + offer_op.item_ids.emplace(nft_id_type(0)); + offer_op.item_ids.emplace(nft_id_type(1)); + offer_op.issuer = alice_id; + offer_op.buying_item = true; + offer_op.maximum_price = asset(11000); + offer_op.minimum_price = asset(10); + offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15); + trx.operations.push_back(offer_op); + auto op = trx.operations.back().get(); + REQUIRE_THROW_WITH_VALUE(op, issuer, bob_id); + + trx.clear(); + trx.operations.push_back(offer_op); + sign(trx, alice_private_key); + PUSH_TX(db, trx); + trx.clear(); + + asset exp_delta_bidder2 = -offer_op.maximum_price; + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + exp_delta_bidder2.amount).value); + + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + const offer_object &d = offer_id_type(1)(db); + + BOOST_CHECK(d.space_id == protocol_ids); + BOOST_CHECK(d.type_id == offer_object_type); + // empty bid + BOOST_CHECK(!d.bid_price); + BOOST_CHECK(!d.bidder); + // data integrity + BOOST_CHECK(d.issuer == alice_id); + BOOST_CHECK(d.maximum_price == asset(11000)); + BOOST_CHECK(d.minimum_price == asset(10)); + BOOST_CHECK(d.buying_item == true); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + buy_offer = d.id; + } + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(sell_bid_for_buy_offer_test) +{ + try + { + INVOKE(create_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(operator1); + + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount + 2, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + auto op = trx.operations.back().get(); + // Non Owner bidder + REQUIRE_THROW_WITH_VALUE(op, bidder, alice_id); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(second_sell_bid_for_buy_offer_test) +{ + try + { + INVOKE(sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + GET_ACTOR(charlie); + GET_ACTOR(operator1); + + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset((*offer_obj.bid_price).amount - 1, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + trx.operations.push_back(bid_op); + + auto op = trx.operations.back().get(); + // Not a better bid than previous + REQUIRE_THROW_WITH_VALUE(op, bid_price, asset((*offer_obj.bid_price).amount, offer_obj.minimum_price.asset_id)); + + trx.clear(); + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid_op.bid_price); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(best_sell_bid_for_buy_offer) +{ + try + { + INVOKE(second_sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + + bid_operation bid_op; + bid_op.offer_id = offer_obj.id; + bid_op.bid_price = asset(offer_obj.minimum_price.amount, offer_obj.minimum_price.asset_id); + bid_op.bidder = bob_id; + + asset bid = bid_op.bid_price; + asset exp_delta_bidder1 = offer_obj.minimum_price; + asset exp_delta_bidder2 = offer_obj.maximum_price - offer_obj.minimum_price; + + trx.operations.push_back(bid_op); + sign(trx, bob_private_key); + PUSH_TX(db, trx); + trx.clear(); + + //not empty bid + BOOST_CHECK(offer_obj.bid_price); + BOOST_CHECK(offer_obj.bidder); + // data integrity + BOOST_CHECK(offer_obj.bidder == bob_id); + BOOST_CHECK(offer_obj.issuer == alice_id); + BOOST_CHECK(offer_obj.maximum_price == asset(11000)); + BOOST_CHECK(offer_obj.minimum_price == asset(10)); + BOOST_CHECK(offer_obj.bid_price == bid); + BOOST_CHECK(db.item_locked(nft_id_type(0))); + BOOST_CHECK(db.item_locked(nft_id_type(1))); + auto cached_offer_obj = offer_obj; + // Generate a block and offer should be finalized with bid + generate_block(); + // Check balances + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + exp_delta_bidder1.amount).value); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + exp_delta_bidder2.amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE(expire_with_bid_for_buy_offer_test) +{ + INVOKE(second_sell_bid_for_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount - (*cached_offer_obj.bid_price).amount).value); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)), + (bob_balance + (*cached_offer_obj.bid_price).amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == alice_id) && (nft_id_type(1)(db).owner == alice_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); +} + +BOOST_AUTO_TEST_CASE(expire_no_bid_for_buy_offer_test) +{ + INVOKE(create_buy_offer_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db)); + const auto &offer_obj = buy_offer(db); + auto cached_offer_obj = offer_obj; + generate_blocks(5); + BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)), + (alice_balance + cached_offer_obj.maximum_price.amount).value); + const auto &oidx = db.get_index_type().indices().get(); + const auto &ohidx = db.get_index_type().indices().get(); + BOOST_REQUIRE(oidx.size() == 0); + BOOST_REQUIRE(ohidx.size() == 2); + BOOST_CHECK(db.item_locked(nft_id_type(0)) == false); + BOOST_CHECK(db.item_locked(nft_id_type(1)) == false); + BOOST_CHECK((nft_id_type(0)(db).owner == bob_id) && (nft_id_type(1)(db).owner == bob_id)); + // Get offer history object + const auto &history_obj = offer_history_id_type(1)(db); + // History object data check + BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); + BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); + BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); + BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); + BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); + BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); + BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); + BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index 9b6c5e40..3c058093 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -161,6 +161,7 @@ BOOST_AUTO_TEST_CASE( nft_safe_transfer_from_test ) { BOOST_TEST_MESSAGE("Send nft_safe_transfer_operation"); nft_safe_transfer_from_operation op; + op.operator_ = alice_id; op.from = alice_id; op.to = bob_id; op.token_id = nft_id_type(0); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 0f5dbf8d..3093f0b0 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include @@ -1822,493 +1821,6 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) // TODO: Test with non-core asset and Bob account } FC_LOG_AND_RETHROW() } -offer_id_type buy_offer; -offer_id_type sell_offer; - -BOOST_AUTO_TEST_CASE(create_offer_test) -{ - try - { - - fc::ecc::private_key agent_pk = generate_private_key("agent"); - fc::ecc::private_key issuer_pk = generate_private_key("issuer"); - - const asset_object &bitusd = create_bitasset("STUB"); - const account_object &agent = create_account("agent", agent_pk.get_public_key()); - const account_object &issuer = create_account("issuer", issuer_pk.get_public_key()); - - transfer(committee_account(db), issuer, asset(10000)); - transfer(committee_account(db), agent, asset(10000)); - upgrade_to_lifetime_member(agent); - - bool params[] = {true, false}; - std::for_each(params, params + 1, [&](bool is_buying_offer) { - offer_operation offer_op; - - offer_op.item_ids.emplace(13); - offer_op.item_ids.emplace(14); - offer_op.issuer = issuer.id; - offer_op.buying_item = is_buying_offer; - offer_op.maximum_price = asset(100); - offer_op.minimum_price = asset(10); - // +1 second - offer_op.offer_expiration_date = db.head_block_time() + fc::microseconds(3000000); - - trx.operations.push_back(offer_op); - offer_operation &op = trx.operations.back().get(); - - //REQUIRE_THROW_WITH_VALUE(op, offer_expiration_date, db.head_block_time()); - - // //positive prices - REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(-1)); - REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(-1)); - - REQUIRE_OP_VALIDATION_FAILURE(op, fee, asset(-1)); - - // // min price > max price check - REQUIRE_OP_VALIDATION_FAILURE(op, maximum_price, asset(1)); - - // different asset for min/max - REQUIRE_OP_VALIDATION_FAILURE(op, minimum_price, asset(1, bitusd.id)); - - offer_id_type offer_id = db.get_index_type>>().get_next_id(); - - PUSH_TX(db, trx, ~0); - - const offer_object &d = offer_id(db); - - BOOST_CHECK(d.space_id == protocol_ids); - BOOST_CHECK(d.type_id == offer_object_type); - - //empty bid - BOOST_CHECK(!d.bid_price); - BOOST_CHECK(!d.bidder); - - // data integrity - BOOST_CHECK(d.issuer == issuer.id); - BOOST_CHECK(d.maximum_price == asset(100)); - BOOST_CHECK(d.minimum_price == asset(10)); - BOOST_CHECK(d.buying_item == is_buying_offer); - - if (is_buying_offer) - { - buy_offer = offer_id; - } - else - { - sell_offer = offer_id; - } - }); - } - catch (fc::exception &e) - { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(bid_test) -{ - try - { - INVOKE(create_offer_test); - const account_object &agent = get_account("agent"); - const account_object &issuer = get_account("issuer"); - const account_object &bidder = create_account("bidder", generate_private_key("bidder").get_public_key()); - transfer(committee_account(db), bidder, asset(10000)); - - account_id_type bidder_id = bidder.id; - account_id_type agent_id = agent.id; - account_id_type issuer_id = issuer.id; - - const offer_object &offer_obj = buy_offer(db); - - bid_operation bid_op; - bid_op.offer_id = offer_obj.id; - bid_op.bid_price = asset(offer_obj.minimum_price.amount + 1, offer_obj.minimum_price.asset_id); - bid_op.bidder = bidder.id; - trx.operations.push_back(bid_op); - - trx.operations.clear(); - - //adding whitelist - account_whitelist_operation wop; - wop.authorizing_account = agent.id; - wop.account_to_list = bidder.id; - wop.new_listing = account_whitelist_operation::white_listed; - trx.operations.clear(); - trx.operations.push_back(wop); - PUSH_TX(db, trx, ~0); - - trx.operations.clear(); - - bool params[] = {true, false}; - std::for_each(params, params + 1, [&](bool is_buying_offer) { - asset bid; - asset exp_delta_agent; - asset exp_delta_bidder; - - int64_t issuer_balance = get_balance(issuer_id(db), asset_id_type()(db)); - int64_t bidder_balance = get_balance(bidder_id(db), asset_id_type()(db)); - - const offer_object &offer_obj = is_buying_offer ? buy_offer(db) : sell_offer(db); - - if (is_buying_offer) - { - bid = asset(offer_obj.maximum_price.amount - 1, asset_id_type()); - exp_delta_agent = asset(0); - exp_delta_bidder = asset(0); - } - else - { - bid = asset(offer_obj.minimum_price.amount + 1, asset_id_type()); - exp_delta_agent = bid; - exp_delta_bidder = -bid; - } - - bid_op.offer_id = offer_obj.id; - - bid_op.bid_price = bid; - bid_op.bidder = bidder.id; - - trx.operations.push_back(bid_op); - bid_operation &op = trx.operations.back().get(); - - //REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(-1, asset_id_type())); - //REQUIRE_THROW_WITH_VALUE(op, bid_price, asset(offer_obj.maximum_price.amount + 1, offer_obj.minimum_price.asset_id)); - // REQUIRE_THROW_WITH_VALUE(op, offer_id, offer_id_type()); - - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL(get_balance(bidder_id(db), asset_id_type()(db)), - (bidder_balance + exp_delta_bidder.amount).value); - - //not empty bid - BOOST_CHECK(offer_obj.bid_price); - BOOST_CHECK(offer_obj.bidder); - - // data integrity - BOOST_CHECK(offer_obj.bidder == bidder.id); - BOOST_CHECK(offer_obj.issuer == issuer.id); - BOOST_CHECK(offer_obj.maximum_price == asset(100)); - BOOST_CHECK(offer_obj.minimum_price == asset(10)); - BOOST_CHECK(offer_obj.bid_price == bid); - }); - } - catch (fc::exception &e) - { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(second_bid_test) -{ - try - { - INVOKE(bid_test); - const account_object &agent = get_account("agent"); - const account_object &bidder = get_account("bidder"); - const account_object &bidder2 = create_account("bidder2", generate_private_key("bidder2").get_public_key()); - - //adding whitelist - account_whitelist_operation wop; - wop.authorizing_account = agent.id; - wop.account_to_list = bidder2.id; - wop.new_listing = account_whitelist_operation::white_listed; - trx.operations.push_back(wop); - PUSH_TX(db, trx, ~0); - - account_id_type agent_id = agent.id; - account_id_type bidder_id = bidder.id; - account_id_type bidder2_id = bidder2.id; - - transfer(committee_account(db), bidder2, asset(10000)); - - bool params[] = {true, false}; - std::for_each(params, params + 1, [&](bool is_buying_offer) { - asset bid; - asset exp_delta_agent; - asset exp_delta_bidder; - asset exp_delta_bidder2; - - int64_t agent_balance = get_balance(agent_id(db), asset_id_type()(db)); - int64_t bidder_balance = get_balance(bidder_id(db), asset_id_type()(db)); - int64_t bidder2_balance = get_balance(bidder2_id(db), asset_id_type()(db)); - - const offer_object &offer_obj = is_buying_offer ? buy_offer(db) : sell_offer(db); - - if (is_buying_offer) - { - bid = asset((*offer_obj.bid_price).amount - 1, asset_id_type()); - exp_delta_agent = asset(0); - exp_delta_bidder = asset(0); - exp_delta_bidder2 = asset(0); - } - else - { - bid = asset((*offer_obj.bid_price).amount + 1, asset_id_type()); - exp_delta_agent = bid - (*offer_obj.bid_price); - exp_delta_bidder = *offer_obj.bid_price; - exp_delta_bidder2 = -bid; - } - - bid_operation bid_op; - trx.operations.push_back(bid_op); - bid_op.offer_id = offer_obj.id; - bid_op.bid_price = bid; - bid_op.bidder = bidder2.id; - - trx.operations.clear(); - trx.operations.push_back(bid_op); - - PUSH_TX(db, trx, ~0); - - BOOST_CHECK_EQUAL(get_balance(bidder_id(db), asset_id_type()(db)), - (bidder_balance + exp_delta_bidder.amount).value); - BOOST_CHECK_EQUAL(get_balance(bidder2_id(db), asset_id_type()(db)), - (bidder2_balance + exp_delta_bidder2.amount).value); - - //not empty bid - BOOST_CHECK(offer_obj.bid_price); - BOOST_CHECK(offer_obj.bidder); - - // data integrity - BOOST_CHECK(offer_obj.bidder == bidder2.id); - BOOST_CHECK(offer_obj.maximum_price == asset(100)); - BOOST_CHECK(offer_obj.minimum_price == asset(10)); - BOOST_CHECK(offer_obj.bid_price == bid); - }); - - const offer_object &offer_obj = *db.get_index_type().indices().get().begin(); - } - catch (fc::exception &e) - { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(buyout_offer_test) -{ - try - { - INVOKE(create_offer_test); - const account_object &agent = get_account("agent"); - const account_object &issuer = get_account("issuer"); - const account_object &bidder = create_account("bidder", generate_private_key("bidder").get_public_key()); - - account_id_type agent_id = agent.id; - account_id_type issuer_id = issuer.id; - account_id_type bidder_id = bidder.id; - - //adding whitelist - account_whitelist_operation wop; - wop.authorizing_account = agent.id; - wop.account_to_list = bidder.id; - wop.new_listing = account_whitelist_operation::white_listed; - trx.operations.push_back(wop); - PUSH_TX(db, trx, ~0); - - transfer(committee_account(db), bidder, asset(10000)); - - bool params[] = {true, false}; - std::for_each(params, params + 1, [&](bool is_buying_offer) { - const offer_object &offer_obj = is_buying_offer ? buy_offer(db) : sell_offer(db); - - asset bid; - asset exp_delta_agent; - asset exp_delta_bidder; - asset exp_delta_issuer; - - int64_t agent_balance = get_balance(agent_id(db), asset_id_type()(db)); - int64_t bidder_balance = get_balance(bidder_id(db), asset_id_type()(db)); - int64_t issuer_balance = get_balance(issuer_id(db), asset_id_type()(db)); - - if (is_buying_offer) - { - bid = asset(offer_obj.minimum_price.amount, asset_id_type()); - exp_delta_agent = -offer_obj.maximum_price; - exp_delta_bidder = offer_obj.minimum_price; - exp_delta_issuer = offer_obj.maximum_price - offer_obj.minimum_price; - } - else - { - bid = asset(offer_obj.maximum_price.amount, asset_id_type()); - exp_delta_agent = asset(0); - exp_delta_bidder = -offer_obj.maximum_price.amount; - exp_delta_issuer = (offer_obj.maximum_price).amount; - } - - bid_operation bid_op; - trx.operations.push_back(bid_op); - bid_op.offer_id = offer_obj.id; - bid_op.bid_price = bid; - bid_op.bidder = bidder.id; - - trx.operations.clear(); - trx.operations.push_back(bid_op); - - PUSH_TX(db, trx, ~0); - - offer_object cached_offer_obj = offer_obj; - - offer_history_id_type history_id = db.get_index().get_next_id(); - - generate_block(); - - BOOST_CHECK_EQUAL(get_balance(bidder_id(db), asset_id_type()(db)), - (bidder_balance + exp_delta_bidder.amount).value); - BOOST_CHECK_EQUAL(get_balance(issuer_id(db), asset_id_type()(db)), - (issuer_balance + exp_delta_issuer.amount).value); - - BOOST_CHECK(!db.find(buy_offer)); - BOOST_CHECK(!db.find(sell_offer)); - - BOOST_CHECK(db.find(history_id)); - - offer_history_object history_obj = history_id(db); - - BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); - BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); - BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); - BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); - BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); - BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); - BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); - BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); - }); - } - catch (fc::exception &e) - { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(expire_with_bid_offer_test) -{ - - bool params[] = {true, false}; - - std::for_each(params, params + 1, [&](bool is_buying_offer) { - INVOKE(bid_test); - const offer_object &offer_obj = is_buying_offer ? buy_offer(db) : sell_offer(db); - const account_object &agent = get_account("agent"); - const account_object &issuer = get_account("issuer"); - const account_object &bidder = get_account("bidder"); - - account_id_type agent_id = agent.id; - account_id_type issuer_id = issuer.id; - account_id_type bidder_id = bidder.id; - - int64_t agent_balance = get_balance(agent_id(db), asset_id_type()(db)); - int64_t issuer_balance = get_balance(issuer_id(db), asset_id_type()(db)); - int64_t bidder_balance = get_balance(bidder_id(db), asset_id_type()(db)); - - asset exp_delta_agent; - asset exp_delta_issuer; - asset exp_delta_bidder; - - if (is_buying_offer) - { - exp_delta_agent = -offer_obj.maximum_price; - exp_delta_issuer = offer_obj.maximum_price - *offer_obj.bid_price; - exp_delta_bidder = *offer_obj.bid_price; - } - else - { - exp_delta_agent = -(*offer_obj.bid_price); - exp_delta_issuer = *offer_obj.bid_price; - exp_delta_bidder = asset(0); - } - - offer_object cached_offer_obj = offer_obj; - - offer_history_id_type history_id = db.get_index().get_next_id(); - - generate_blocks(5); - - BOOST_CHECK_EQUAL(get_balance(issuer_id(db), asset_id_type()(db)), - (issuer_balance + exp_delta_issuer.amount).value); - BOOST_CHECK_EQUAL(get_balance(bidder_id(db), asset_id_type()(db)), - (bidder_balance + exp_delta_bidder.amount).value); - - BOOST_CHECK(!db.find(buy_offer)); - BOOST_CHECK(!db.find(sell_offer)); - - BOOST_CHECK(db.find(history_id)); - - offer_history_object history_obj = history_id(db); - - BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); - BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); - BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); - BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); - BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); - BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); - BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); - BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); - }); -} - -BOOST_AUTO_TEST_CASE(expire_no_bid_offer_test) -{ - - bool params[] = {true, false}; - - std::for_each(params, params + 1, [&](bool is_buying_offer) { - INVOKE(create_offer_test); - const offer_object &offer_obj = is_buying_offer ? buy_offer(db) : sell_offer(db); - const account_object &agent = get_account("agent"); - const account_object &issuer = get_account("issuer"); - - account_id_type agent_id = agent.id; - account_id_type issuer_id = issuer.id; - - int64_t agent_balance = get_balance(agent_id(db), asset_id_type()(db)); - int64_t issuer_balance = get_balance(issuer_id(db), asset_id_type()(db)); - - asset exp_delta_agent; - asset exp_delta_issuer; - - if (is_buying_offer) - { - exp_delta_agent = -offer_obj.maximum_price; - exp_delta_issuer = offer_obj.maximum_price; - } - else - { - exp_delta_agent = asset(0); - exp_delta_issuer = asset(0); - } - - offer_object cached_offer_obj = offer_obj; - - offer_history_id_type history_id = db.get_index().get_next_id(); - - generate_blocks(5); - - BOOST_CHECK_EQUAL(get_balance(issuer_id(db), asset_id_type()(db)), - (issuer_balance + exp_delta_issuer.amount).value); - - BOOST_CHECK(!db.find(buy_offer)); - BOOST_CHECK(!db.find(sell_offer)); - - BOOST_CHECK(db.find(history_id)); - - offer_history_object history_obj = history_id(db); - - BOOST_CHECK(cached_offer_obj.bid_price == history_obj.bid_price); - BOOST_CHECK(cached_offer_obj.bidder == history_obj.bidder); - BOOST_CHECK(cached_offer_obj.buying_item == history_obj.buying_item); - BOOST_CHECK(cached_offer_obj.issuer == history_obj.issuer); - BOOST_CHECK(cached_offer_obj.maximum_price == history_obj.maximum_price); - BOOST_CHECK(cached_offer_obj.minimum_price == history_obj.minimum_price); - BOOST_CHECK(cached_offer_obj.offer_expiration_date == history_obj.offer_expiration_date); - BOOST_CHECK(cached_offer_obj.item_ids == history_obj.item_ids); - }); -} // TODO: Write linear VBO tests BOOST_AUTO_TEST_SUITE_END()