ppy marketplace 4 - Add tests NFT+Marketplace
This commit is contained in:
parent
99a24a6e39
commit
db9a35fa17
15 changed files with 1289 additions and 880 deletions
|
|
@ -116,6 +116,7 @@ add_library( graphene_chain
|
|||
|
||||
affiliate_payout.cpp
|
||||
|
||||
offer_object.cpp
|
||||
offer_evaluator.cpp
|
||||
nft_evaluator.cpp
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/chain_property_object.hpp>
|
||||
#include <graphene/chain/global_property_object.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
|
||||
#include <fc/smart_ref_impl.hpp>
|
||||
|
||||
|
|
@ -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<offer_index>();
|
||||
const auto &oidx = dynamic_cast<const base_primary_index &>(offer_idx);
|
||||
const auto &market_items = oidx.get_secondary_index<graphene::chain::offer_item_index>();
|
||||
|
||||
auto items_itr = market_items._locked_items.find(item);
|
||||
return (items_itr != market_items._locked_items.end());
|
||||
}
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -305,7 +305,8 @@ void database::initialize_indexes()
|
|||
tournament_details_idx->add_secondary_index<tournament_players_index>();
|
||||
add_index< primary_index<match_index> >();
|
||||
add_index< primary_index<game_index> >();
|
||||
add_index< primary_index<offer_index> >();
|
||||
auto offer_idx = add_index< primary_index<offer_index> >();
|
||||
offer_idx->add_secondary_index<offer_item_index>();
|
||||
|
||||
add_index< primary_index<nft_metadata_index > >();
|
||||
add_index< primary_index<nft_index > >();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -282,6 +282,7 @@ namespace graphene { namespace chain {
|
|||
std::vector<uint32_t> 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;
|
||||
|
|
|
|||
|
|
@ -4,35 +4,35 @@
|
|||
|
||||
namespace graphene
|
||||
{
|
||||
namespace chain
|
||||
{
|
||||
namespace chain
|
||||
{
|
||||
|
||||
class offer_evaluator : public evaluator<offer_evaluator>
|
||||
{
|
||||
public:
|
||||
typedef offer_operation operation_type;
|
||||
class offer_evaluator : public evaluator<offer_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<bid_evaluator>
|
||||
{
|
||||
public:
|
||||
typedef bid_operation operation_type;
|
||||
class bid_evaluator : public evaluator<bid_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<finalize_offer_evaluator>
|
||||
{
|
||||
public:
|
||||
typedef finalize_offer_operation operation_type;
|
||||
class finalize_offer_evaluator : public evaluator<finalize_offer_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
|
||||
|
|
|
|||
|
|
@ -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<offer_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<offer_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<uint32_t> item_ids;
|
||||
optional<account_id_type> bidder;
|
||||
optional<asset> bid_price;
|
||||
asset minimum_price;
|
||||
asset maximum_price;
|
||||
set<nft_id_type> item_ids;
|
||||
optional<account_id_type> bidder;
|
||||
optional<asset> 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<offer_history_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<offer_history_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<uint32_t> item_ids;
|
||||
optional<account_id_type> bidder;
|
||||
optional<asset> bid_price;
|
||||
asset minimum_price;
|
||||
asset maximum_price;
|
||||
set<nft_id_type> item_ids;
|
||||
optional<account_id_type> bidder;
|
||||
optional<asset> 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<tag<by_id>, member<object, object_id_type, &object::id>>,
|
||||
ordered_non_unique<
|
||||
tag<by_expiration_date>,
|
||||
member<offer_object, fc::time_point_sec, &offer_object::offer_expiration_date>,
|
||||
compare_by_expiration_date>>>
|
||||
offer_multi_index_type;
|
||||
set<nft_id_type> _locked_items;
|
||||
};
|
||||
|
||||
typedef multi_index_container<
|
||||
offer_history_object,
|
||||
indexed_by<
|
||||
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id>>,
|
||||
ordered_non_unique<
|
||||
tag<by_expiration_date>,
|
||||
member<offer_history_object, fc::time_point_sec, &offer_history_object::offer_expiration_date>,
|
||||
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_object, offer_multi_index_type> offer_index;
|
||||
using offer_multi_index_type = multi_index_container<
|
||||
offer_object,
|
||||
indexed_by<
|
||||
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id>>,
|
||||
ordered_non_unique<tag<by_expiration_date>,
|
||||
member<offer_object, fc::time_point_sec,
|
||||
&offer_object::offer_expiration_date>,
|
||||
compare_by_expiration_date>>>;
|
||||
|
||||
typedef generic_index<offer_history_object, offer_history_multi_index_type> offer_history_index;
|
||||
using offer_history_multi_index_type = multi_index_container<
|
||||
offer_history_object,
|
||||
indexed_by<
|
||||
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id>>,
|
||||
ordered_non_unique<tag<by_expiration_date>,
|
||||
member<offer_history_object, fc::time_point_sec,
|
||||
&offer_history_object::offer_expiration_date>,
|
||||
compare_by_expiration_date>>>;
|
||||
|
||||
} // namespace chain
|
||||
using offer_index = generic_index<offer_object, offer_multi_index_type>;
|
||||
|
||||
using offer_history_index =
|
||||
generic_index<offer_history_object, offer_history_multi_index_type>;
|
||||
|
||||
} // 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))
|
||||
|
|
|
|||
|
|
@ -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<uint32_t> 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<nft_id_type> 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_data> 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_data> 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<uint8_t, result_type> result;
|
||||
offer_id_type offer_id;
|
||||
|
||||
extensions_type extensions;
|
||||
fc::enum_type<uint8_t, result_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));
|
||||
|
|
|
|||
|
|
@ -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) ) }
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <graphene/chain/offer_evaluator.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
#include <graphene/chain/nft_object.hpp>
|
||||
#include <graphene/chain/exceptions.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
|
|
@ -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>([&](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<account_index>().indices().get<by_id>();
|
||||
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>([&](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>([&](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<nft_safe_transfer_from_operation> 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>([&](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
|
||||
50
libraries/chain/offer_object.cpp
Normal file
50
libraries/chain/offer_object.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
|
||||
namespace graphene
|
||||
{
|
||||
namespace chain
|
||||
{
|
||||
|
||||
void offer_item_index::object_inserted(const object &obj)
|
||||
{
|
||||
assert(dynamic_cast<const offer_object *>(&obj));
|
||||
const offer_object &oo = static_cast<const offer_object &>(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<const offer_object *>(&after));
|
||||
const offer_object &oo = static_cast<const offer_object &>(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<const offer_object *>(&obj));
|
||||
const offer_object &oo = static_cast<const offer_object &>(obj);
|
||||
|
||||
if (!oo.buying_item || oo.bidder)
|
||||
{
|
||||
for (const auto &id : oo.item_ids)
|
||||
{
|
||||
_locked_items.erase(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chain
|
||||
} // namespace graphene
|
||||
|
|
@ -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
|
||||
756
tests/tests/marketplace_tests.cpp
Normal file
756
tests/tests/marketplace_tests.cpp
Normal file
|
|
@ -0,0 +1,756 @@
|
|||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "../common/database_fixture.hpp"
|
||||
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
#include <graphene/chain/nft_object.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
|
||||
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<nft_metadata_index>().indices().get<by_id>();
|
||||
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<nft_metadata_index>().indices().get<by_id>();
|
||||
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<nft_index>().indices().get<by_id>();
|
||||
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<nft_metadata_index>().indices().get<by_id>();
|
||||
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<offer_operation>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
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<bid_operation>();
|
||||
// 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<bid_operation>();
|
||||
// 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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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<offer_operation>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
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<bid_operation>();
|
||||
// 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<bid_operation>();
|
||||
// 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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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<offer_index>().indices().get<by_id>();
|
||||
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
|
||||
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()
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@
|
|||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
#include <graphene/chain/withdraw_permission_object.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/chain/offer_object.hpp>
|
||||
#include <graphene/account_history/account_history_plugin.hpp>
|
||||
|
||||
#include <fc/crypto/digest.hpp>
|
||||
|
|
@ -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<offer_operation>();
|
||||
|
||||
//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<primary_index<simple_index<offer_object>>>().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<bid_operation>();
|
||||
|
||||
//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<offer_index>().indices().get<by_id>().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<offer_history_object>().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<offer_history_object>().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<offer_history_object>().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()
|
||||
|
|
|
|||
Loading…
Reference in a new issue