diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 08ecf2f9b..b49439cf7 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -97,6 +97,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 66c74cc48..a31851cec 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -537,7 +537,7 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ // 1. due to expiration: always deduct a fee if there is any fee deferred // 2. due to cull_small: deduct a fee after hard fork 604, but not before (will set skip_cancel_fee) const account_statistics_object* seller_acc_stats = nullptr; - const asset_dynamic_data_object* fee_asset_dyn_data = nullptr; + const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr; limit_order_cancel_operation vop; share_type deferred_fee = order.deferred_fee; asset deferred_paid_fee = order.deferred_paid_fee; @@ -576,8 +576,8 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ fee128 /= order.deferred_fee.value; share_type cancel_fee_amount = static_cast(fee128); // cancel_fee should be positive, pay it to asset's accumulated_fees - fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); + modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { addo.accumulated_fees += cancel_fee_amount; }); // cancel_fee should be no more than deferred_paid_fee @@ -613,9 +613,9 @@ void database::cancel_limit_order( const limit_order_object& order, bool create_ { adjust_balance(order.seller, deferred_paid_fee); // be here, must have: fee_asset != CORE - if( !fee_asset_dyn_data ) - fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); - modify( *fee_asset_dyn_data, [&](asset_dynamic_data_object& addo) { + if( !deferred_fee_asset_dyn_data ) + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(*this).dynamic_asset_data_id(*this); + modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) { addo.fee_pool += deferred_fee; }); } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 899f77db1..e1a354ad3 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -56,6 +56,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.fee_payer() ); // seller } + void operator()(const limit_order_update_operation& op) + { + _impacted.insert(op.fee_payer()); // seller + } void operator()( const limit_order_cancel_operation& op ) { _impacted.insert( op.fee_payer() ); // fee_paying_account diff --git a/libraries/chain/exceptions.cpp b/libraries/chain/exceptions.cpp index 05b371258..1db241def 100644 --- a/libraries/chain/exceptions.cpp +++ b/libraries/chain/exceptions.cpp @@ -84,6 +84,10 @@ namespace graphene { namespace chain { GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6, "Insufficient balance" ) + GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_update ); + GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1, "Order does not exist" ) + GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2, "Order owned by someone else" ) + GRAPHENE_IMPLEMENT_OP_BASE_EXCEPTIONS( limit_order_cancel ); GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1, "Order does not exist" ) GRAPHENE_IMPLEMENT_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2, "Order owned by someone else" ) diff --git a/libraries/chain/hardfork.d/CORE_1604.hf b/libraries/chain/hardfork.d/CORE_1604.hf new file mode 100644 index 000000000..078890df5 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_1604.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #1604 Operation to modify existing limit order +#ifndef HARDFORK_CORE_1604_TIME +#define HARDFORK_CORE_1604_TIME (fc::time_point_sec(1893456000)) // Jan 1 00:00:00 2030 (Not yet scheduled) +#define HARDFORK_CORE_1604_PASSED(head_block_time) (head_block_time >= HARDFORK_CORE_1604_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/exceptions.hpp b/libraries/chain/include/graphene/chain/exceptions.hpp index 07d2968c1..54d723d9f 100644 --- a/libraries/chain/include/graphene/chain/exceptions.hpp +++ b/libraries/chain/include/graphene/chain/exceptions.hpp @@ -138,6 +138,10 @@ namespace graphene { namespace chain { GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( receiving_asset_unauthorized, limit_order_create, 5 ) GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( insufficient_balance, limit_order_create, 6 ) + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_update ); + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_update, 1 ) + GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_update, 2 ) + GRAPHENE_DECLARE_OP_BASE_EXCEPTIONS( limit_order_cancel ); GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( nonexist_order, limit_order_cancel, 1 ) GRAPHENE_DECLARE_OP_EVALUATE_EXCEPTION( owner_mismatch, limit_order_cancel, 2 ) diff --git a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp index de8232f3d..321a9b9bb 100644 --- a/libraries/chain/include/graphene/chain/hardfork_visitor.hpp +++ b/libraries/chain/include/graphene/chain/hardfork_visitor.hpp @@ -47,6 +47,7 @@ struct hardfork_visitor { using BSIP_40_ops = fc::typelist::list< protocol::custom_authority_create_operation, protocol::custom_authority_update_operation, protocol::custom_authority_delete_operation>; + using hf1604_ops = fc::typelist::list< protocol::limit_order_update_operation>; using hf2103_ops = fc::typelist::list< protocol::ticket_create_operation, protocol::ticket_update_operation>; using liquidity_pool_ops = fc::typelist::list< protocol::liquidity_pool_create_operation, @@ -82,6 +83,9 @@ struct hardfork_visitor { std::enable_if_t(), bool> visit() { return HARDFORK_BSIP_40_PASSED(now); } template + std::enable_if_t(), bool> + visit() { return HARDFORK_CORE_1604_PASSED(now); } + template std::enable_if_t(), bool> visit() { return HARDFORK_CORE_2103_PASSED(now); } template diff --git a/libraries/chain/include/graphene/chain/market_evaluator.hpp b/libraries/chain/include/graphene/chain/market_evaluator.hpp index f9d94ddd5..26d90c229 100644 --- a/libraries/chain/include/graphene/chain/market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/market_evaluator.hpp @@ -42,14 +42,14 @@ namespace graphene { namespace chain { void_result do_evaluate( const limit_order_create_operation& o ); object_id_type do_apply( const limit_order_create_operation& o ) const; - /** override the default behavior defined by generic_evalautor + /** override the default behavior defined by generic_evaluator */ - virtual void convert_fee() override; + void convert_fee() override; - /** override the default behavior defined by generic_evalautor which is to + /** override the default behavior defined by generic_evaluator which is to * post the fee to fee_paying_account_stats.pending_fees */ - virtual void pay_fee() override; + void pay_fee() override; private: share_type _deferred_fee = 0; @@ -59,6 +59,32 @@ namespace graphene { namespace chain { const asset_object* _receive_asset = nullptr; }; + class limit_order_update_evaluator : public evaluator + { + public: + using operation_type = limit_order_update_operation; + + void_result do_evaluate(const limit_order_update_operation& o); + void_result do_apply(const limit_order_update_operation& o); + + /** override the default behavior defined by generic_evaluator + */ + void convert_fee() override; + + /** override the default behavior defined by generic_evaluator which is to + * post the fee to fee_paying_account_stats.pending_fees + */ + void pay_fee() override; + + private: + void process_deferred_fee(); + + share_type _deferred_fee; + asset _deferred_paid_fee; + const limit_order_object* _order = nullptr; + const account_statistics_object* _seller_acc_stats = nullptr; + }; + class limit_order_cancel_evaluator : public evaluator { public: @@ -68,7 +94,7 @@ namespace graphene { namespace chain { asset do_apply( const limit_order_cancel_operation& o ) const; private: - const limit_order_object* _order; + const limit_order_object* _order = nullptr; }; class call_order_update_evaluator : public evaluator diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 11aee840a..886d5bc26 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -33,6 +33,7 @@ #include #include +#include namespace graphene { namespace chain { void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op) @@ -134,6 +135,236 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o return order_id; } FC_CAPTURE_AND_RETHROW( (op) ) } +void limit_order_update_evaluator::convert_fee() +{ + if( !trx_state->skip_fee && fee_asset->get_id() != asset_id_type() ) + { + db().modify(*fee_asset_dyn_data, [this](asset_dynamic_data_object& addo) { + addo.fee_pool -= core_fee_paid; + }); + } +} + +void limit_order_update_evaluator::pay_fee() +{ + _deferred_fee = core_fee_paid; + if( fee_asset->get_id() != asset_id_type() ) + _deferred_paid_fee = fee_from_account; +} + +void_result limit_order_update_evaluator::do_evaluate(const limit_order_update_operation& o) +{ + const database& d = db(); + FC_ASSERT( HARDFORK_CORE_1604_PASSED( d.head_block_time() ) , "Operation has not activated yet"); + + _order = d.find( o.order ); + + GRAPHENE_ASSERT( _order != nullptr, + limit_order_update_nonexist_order, + "Limit order ${oid} does not exist, cannot update", + ("oid", o.order) ); + + // Check this is my order + GRAPHENE_ASSERT( o.seller == _order->seller, + limit_order_update_owner_mismatch, + "Limit order ${oid} is owned by someone else, cannot update", + ("oid", o.order) ); + + // Check new price is compatible and appropriate + if (o.new_price) { + auto base_id = o.new_price->base.asset_id; + auto quote_id = o.new_price->quote.asset_id; + FC_ASSERT(base_id == _order->sell_price.base.asset_id && quote_id == _order->sell_price.quote.asset_id, + "Cannot update limit order with incompatible price"); + + // Do not allow inappropriate price manipulation + auto max_amount_for_sale = std::max( _order->for_sale, _order->sell_price.base.amount ); + if( o.delta_amount_to_sell ) + max_amount_for_sale += o.delta_amount_to_sell->amount; + FC_ASSERT( o.new_price->base.amount <= max_amount_for_sale, + "The base amount in the new price cannot be greater than the estimated maximum amount for sale" ); + + } + + // Check delta asset is compatible + if (o.delta_amount_to_sell) { + const auto& delta = *o.delta_amount_to_sell; + FC_ASSERT(delta.asset_id == _order->sell_price.base.asset_id, + "Cannot update limit order with incompatible asset"); + if (delta.amount < 0) + FC_ASSERT(_order->for_sale > -delta.amount, + "Cannot deduct all or more from order than order contains"); + // Note: if the delta amount is positive, account balance will be checked when calling adjust_balance() + } + + // Check dust + if (o.new_price || (o.delta_amount_to_sell && o.delta_amount_to_sell->amount < 0)) { + auto new_price = o.new_price? *o.new_price : _order->sell_price; + auto new_amount = _order->amount_for_sale(); + if (o.delta_amount_to_sell) + new_amount += *o.delta_amount_to_sell; + auto new_amount_to_receive = new_amount * new_price; + + FC_ASSERT( new_amount_to_receive.amount > 0, + "Cannot update limit order: order becomes too small; cancel order instead" ); + } + + // Check expiration is in the future + if (o.new_expiration) + FC_ASSERT( *o.new_expiration >= d.head_block_time(), "Cannot update limit order to expire in the past" ); + + // Check asset authorization + // TODO refactor to fix duplicate code (see limit_order_create) + const auto sell_asset_id = _order->sell_asset_id(); + const auto receive_asset_id = _order->receive_asset_id(); + const auto& sell_asset = sell_asset_id(d); + const auto& receive_asset = receive_asset_id(d); + const auto& seller = *this->fee_paying_account; + + if( !sell_asset.options.whitelist_markets.empty() ) + { + FC_ASSERT( sell_asset.options.whitelist_markets.find( receive_asset_id ) + != sell_asset.options.whitelist_markets.end(), + "This market has not been whitelisted by the selling asset" ); + } + if( !sell_asset.options.blacklist_markets.empty() ) + { + FC_ASSERT( sell_asset.options.blacklist_markets.find( receive_asset_id ) + == sell_asset.options.blacklist_markets.end(), + "This market has been blacklisted by the selling asset" ); + } + + FC_ASSERT( is_authorized_asset( d, seller, sell_asset ), + "The account is not allowed to transact the selling asset" ); + + FC_ASSERT( is_authorized_asset( d, seller, receive_asset ), + "The account is not allowed to transact the receiving asset" ); + + return {}; +} + +void limit_order_update_evaluator::process_deferred_fee() +{ + // Attempt to deduct a possibly discounted order cancellation fee, and refund the remainder. + // Only deduct fee if there is any fee deferred. + // TODO fix duplicate code (see database::cancel_limit_order()) + if( _order->deferred_fee <= 0 ) + return; + + database& d = db(); + + share_type deferred_fee = _order->deferred_fee; + asset deferred_paid_fee = _order->deferred_paid_fee; + const asset_dynamic_data_object* deferred_fee_asset_dyn_data = nullptr; + const auto& current_fees = d.current_fee_schedule(); + asset core_cancel_fee = current_fees.calculate_fee( limit_order_cancel_operation() ); + if( core_cancel_fee.amount > 0 ) + { + // maybe-discounted cancel_fee calculation: + // limit_order_cancel_fee * limit_order_update_fee / limit_order_create_fee + asset core_create_fee = current_fees.calculate_fee( limit_order_create_operation() ); + fc::uint128_t fee128( core_cancel_fee.amount.value ); + if( core_create_fee.amount > 0 ) + { + asset core_update_fee = current_fees.calculate_fee( limit_order_update_operation() ); + fee128 *= core_update_fee.amount.value; + fee128 /= core_create_fee.amount.value; + } + // cap the fee + if( fee128 > static_cast( deferred_fee.value ) ) + fee128 = deferred_fee.value; + core_cancel_fee.amount = static_cast( fee128 ); + } + + // if there is any CORE fee to deduct, redirect it to referral program + if( core_cancel_fee.amount > 0 ) + { + if( !_seller_acc_stats ) + _seller_acc_stats = &d.get_account_stats_by_owner( _order->seller ); + d.modify( *_seller_acc_stats, [&core_cancel_fee, &d]( account_statistics_object& obj ) { + obj.pay_fee( core_cancel_fee.amount, d.get_global_properties().parameters.cashback_vesting_threshold ); + } ); + deferred_fee -= core_cancel_fee.amount; + // handle originally paid fee if any: + // to_deduct = round_up( paid_fee * core_cancel_fee / deferred_core_fee_before_deduct ) + if( deferred_paid_fee.amount > 0 ) + { + fc::uint128_t fee128( deferred_paid_fee.amount.value ); + fee128 *= core_cancel_fee.amount.value; + // to round up + fee128 += _order->deferred_fee.value; + fee128 -= 1; + fee128 /= _order->deferred_fee.value; + share_type cancel_fee_amount = static_cast(fee128); + // cancel_fee should be positive, pay it to asset's accumulated_fees + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d); + d.modify( *deferred_fee_asset_dyn_data, [&cancel_fee_amount](asset_dynamic_data_object& addo) { + addo.accumulated_fees += cancel_fee_amount; + }); + // cancel_fee should be no more than deferred_paid_fee + deferred_paid_fee.amount -= cancel_fee_amount; + } + } + + // refund fee + if( 0 == _order->deferred_paid_fee.amount ) + { + // be here, order.create_time <= HARDFORK_CORE_604_TIME, or fee paid in CORE, or no fee to refund. + // if order was created before hard fork 604, + // see it as fee paid in CORE, deferred_fee should be refunded to order owner but not fee pool + d.adjust_balance( _order->seller, deferred_fee ); + } + else // need to refund fee in originally paid asset + { + d.adjust_balance( _order->seller, deferred_paid_fee ); + // be here, must have: fee_asset != CORE + if( !deferred_fee_asset_dyn_data ) + deferred_fee_asset_dyn_data = &deferred_paid_fee.asset_id(d).dynamic_asset_data_id(d); + d.modify( *deferred_fee_asset_dyn_data, [&deferred_fee](asset_dynamic_data_object& addo) { + addo.fee_pool += deferred_fee; + }); + } + +} + +void_result limit_order_update_evaluator::do_apply(const limit_order_update_operation& o) +{ + database& d = db(); + + // Adjust account balance + if( o.delta_amount_to_sell ) + { + d.adjust_balance( o.seller, -*o.delta_amount_to_sell ); + if( o.delta_amount_to_sell->asset_id == asset_id_type() ) + { + _seller_acc_stats = &d.get_account_stats_by_owner( o.seller ); + d.modify( *_seller_acc_stats, [&o]( account_statistics_object& bal ) { + bal.total_core_in_orders += o.delta_amount_to_sell->amount; + }); + } + } + + // Process deferred fee in the order. + process_deferred_fee(); + + // Update order + d.modify(*_order, [&o,this](limit_order_object& loo) { + if (o.new_price) + loo.sell_price = *o.new_price; + if (o.delta_amount_to_sell) + loo.for_sale += o.delta_amount_to_sell->amount; + if (o.new_expiration) + loo.expiration = *o.new_expiration; + loo.deferred_fee = _deferred_fee; + loo.deferred_paid_fee = _deferred_paid_fee; + }); + + // Perform order matching if necessary + d.apply_order(*_order); + + return {}; +} + void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o) { try { const database& d = db(); diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 37eb48d96..164cb1252 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -69,6 +69,12 @@ struct proposal_operation_hardfork_visitor template void operator()(const T &v) const {} + // TODO review and cleanup code below after hard fork + // hf_1604 + void operator()(const graphene::chain::limit_order_update_operation &) const { + FC_ASSERT( HARDFORK_CORE_1604_PASSED(block_time), "Operation is not enabled yet" ); + } + void operator()(const graphene::chain::asset_create_operation &v) const { detail::check_asset_options_hf_1774(block_time, v.common_options); detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); @@ -143,6 +149,10 @@ struct proposal_operation_hardfork_visitor FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); } + if (!HARDFORK_CORE_1604_PASSED(block_time)) { + FC_ASSERT(!op.new_parameters.current_fees->exists(), + "Cannot set fees for limit_order_update_operation before its hardfork time"); + } if (!HARDFORK_BSIP_40_PASSED(block_time)) { FC_ASSERT(!op.new_parameters.extensions.value.custom_authority_options.valid(), "Unable to set Custom Authority Options before hardfork BSIP 40"); diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index f69bb98cd..759d46515 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -3630,6 +3630,8 @@ namespace graphene { namespace net { namespace detail { case graphene::chain::limit_order_create_selling_asset_unauthorized::code_enum::code_value : case graphene::chain::limit_order_create_receiving_asset_unauthorized::code_enum::code_value : case graphene::chain::limit_order_create_insufficient_balance::code_enum::code_value : + case graphene::chain::limit_order_update_nonexist_order::code_enum::code_value : + case graphene::chain::limit_order_update_owner_mismatch::code_enum::code_value : case graphene::chain::limit_order_cancel_nonexist_order::code_enum::code_value : case graphene::chain::limit_order_cancel_owner_mismatch::code_enum::code_value : case graphene::chain::liquidity_pool_exchange_unfillable_price::code_enum::code_value : diff --git a/libraries/protocol/include/graphene/protocol/market.hpp b/libraries/protocol/include/graphene/protocol/market.hpp index 02e83cfdc..bfed55ba1 100644 --- a/libraries/protocol/include/graphene/protocol/market.hpp +++ b/libraries/protocol/include/graphene/protocol/market.hpp @@ -73,6 +73,29 @@ namespace graphene { namespace protocol { price get_price()const { return amount_to_sell / min_to_receive; } }; + /** + * @ingroup operations + * Used to update an existing limit order. + */ + struct limit_order_update_operation : public base_operation + { + struct fee_params_t { + uint64_t fee = ( GRAPHENE_BLOCKCHAIN_PRECISION * 3 ) / 8; + }; + + asset fee; + account_id_type seller; + limit_order_id_type order; + optional new_price; + optional delta_amount_to_sell; + optional new_expiration; + + extensions_type extensions; + + account_id_type fee_payer() const { return seller; } + void validate() const override; + }; + /** * @ingroup operations * Used to cancel an existing limit order. Both fee_pay_account and the @@ -219,6 +242,7 @@ namespace graphene { namespace protocol { } } // graphene::protocol FC_REFLECT( graphene::protocol::limit_order_create_operation::fee_params_t, (fee) ) +FC_REFLECT( graphene::protocol::limit_order_update_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::limit_order_cancel_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::call_order_update_operation::fee_params_t, (fee) ) FC_REFLECT( graphene::protocol::bid_collateral_operation::fee_params_t, (fee) ) @@ -229,6 +253,8 @@ FC_REFLECT( graphene::protocol::call_order_update_operation::options_type, (targ FC_REFLECT( graphene::protocol::limit_order_create_operation, (fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions)) +FC_REFLECT( graphene::protocol::limit_order_update_operation, + (fee)(seller)(order)(new_price)(delta_amount_to_sell)(new_expiration)(extensions)) FC_REFLECT( graphene::protocol::limit_order_cancel_operation, (fee)(fee_paying_account)(order)(extensions) ) FC_REFLECT( graphene::protocol::call_order_update_operation, @@ -242,10 +268,12 @@ FC_REFLECT( graphene::protocol::execute_bid_operation, GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation ) diff --git a/libraries/protocol/include/graphene/protocol/operations.hpp b/libraries/protocol/include/graphene/protocol/operations.hpp index 4cc722bae..0540f5721 100644 --- a/libraries/protocol/include/graphene/protocol/operations.hpp +++ b/libraries/protocol/include/graphene/protocol/operations.hpp @@ -129,7 +129,8 @@ namespace graphene { namespace protocol { /* 73 */ credit_deal_repay_operation, /* 74 */ credit_deal_expired_operation, // VIRTUAL /* 75 */ liquidity_pool_update_operation, - /* 76 */ credit_deal_update_operation + /* 76 */ credit_deal_update_operation, + /* 77 */ limit_order_update_operation >; /// @} // operations group diff --git a/libraries/protocol/market.cpp b/libraries/protocol/market.cpp index a6b4eb54e..7f8472cdb 100644 --- a/libraries/protocol/market.cpp +++ b/libraries/protocol/market.cpp @@ -35,6 +35,17 @@ void limit_order_create_operation::validate()const FC_ASSERT( min_to_receive.amount > 0 ); } +void limit_order_update_operation::validate() const +{ + FC_ASSERT(fee.amount >= 0, "Fee must not be negative"); + FC_ASSERT(new_price || delta_amount_to_sell || new_expiration, + "Cannot update limit order if nothing is specified to update"); + if (new_price) + new_price->validate(); + if (delta_amount_to_sell) + FC_ASSERT(delta_amount_to_sell->amount != 0, "Cannot change limit order amount by zero"); +} + void limit_order_cancel_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); @@ -60,10 +71,12 @@ void bid_collateral_operation::validate()const GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation::fee_params_t ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation::fee_params_t ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_create_operation ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::limit_order_cancel_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::call_order_update_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bid_collateral_operation ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 5678d04da..9b0287903 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1185,7 +1185,7 @@ const limit_order_object* database_fixture_base::create_sell_order( const accoun buy_order.amount_to_sell = amount; buy_order.min_to_receive = recv; buy_order.expiration = order_expiration; - trx.operations.push_back(buy_order); + trx.operations = {buy_order}; for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate); trx.validate(); auto processed = PUSH_TX(db, trx, ~0); @@ -1194,6 +1194,51 @@ const limit_order_object* database_fixture_base::create_sell_order( const accoun return db.find( processed.operation_results[0].get() ); } +limit_order_update_operation database_fixture_base::make_limit_order_update_op( + account_id_type seller_id, + limit_order_id_type order_id, + fc::optional new_price, + fc::optional delta_amount, + fc::optional new_expiration )const +{ + limit_order_update_operation update_order; + update_order.seller = seller_id; + update_order.order = order_id; + update_order.new_price = new_price; + update_order.delta_amount_to_sell = delta_amount; + update_order.new_expiration = new_expiration; + return update_order; +} + +void database_fixture_base::update_limit_order(const limit_order_object& order, + fc::optional new_price, + fc::optional delta_amount, + fc::optional new_expiration, + const price& fee_core_exchange_rate ) +{ + limit_order_update_operation update_order; + update_order.seller = order.seller; + update_order.order = order.id; + update_order.new_price = new_price; + update_order.delta_amount_to_sell = delta_amount; + update_order.new_expiration = new_expiration; + trx.operations = {update_order}; + for(auto& op : trx.operations) db.current_fee_schedule().set_fee(op, fee_core_exchange_rate); + trx.validate(); + auto processed = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + verify_asset_supplies(db); +} + +void database_fixture_base::update_limit_order(limit_order_id_type order_id, + fc::optional new_price, + fc::optional delta_amount, + fc::optional new_expiration, + const price& fee_core_exchange_rate ) +{ + update_limit_order(order_id(db), new_price, delta_amount, new_expiration, fee_core_exchange_rate); +} + asset database_fixture_base::cancel_limit_order( const limit_order_object& order ) { limit_order_cancel_operation cancel_order; @@ -1205,7 +1250,7 @@ asset database_fixture_base::cancel_limit_order( const limit_order_object& order trx.validate(); auto processed = PUSH_TX(db, trx, ~0); trx.operations.clear(); - verify_asset_supplies(db); + verify_asset_supplies(db); return processed.operation_results[0].get(); } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 87be2e8fe..2a876dde3 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -417,6 +417,22 @@ struct database_fixture_base { const limit_order_object* create_sell_order( const account_object& user, const asset& amount, const asset& recv, const time_point_sec order_expiration = time_point_sec::maximum(), const price& fee_core_exchange_rate = price::unit_price() ); + limit_order_update_operation make_limit_order_update_op( + account_id_type seller_id, + limit_order_id_type order_id, + fc::optional new_price = {}, + fc::optional delta_amount = {}, + fc::optional new_expiration = {} )const; + void update_limit_order(const limit_order_object& order, + fc::optional new_price = {}, + fc::optional delta_amount = {}, + fc::optional new_expiration = {}, + const price& fee_core_exchange_rate = price::unit_price() ); + void update_limit_order(limit_order_id_type order_id, + fc::optional new_price = {}, + fc::optional delta_amount = {}, + fc::optional new_expiration = {}, + const price& fee_core_exchange_rate = price::unit_price() ); asset cancel_limit_order( const limit_order_object& order ); void transfer( account_id_type from, account_id_type to, const asset& amount, const asset& fee = asset() ); void transfer( const account_object& from, const account_object& to, const asset& amount, const asset& fee = asset() ); diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index fa3c952bf..4af356856 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -733,6 +733,7 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) issue_uia( bob_id, asset( bob_b0, usd_id ) ); int64_t order_create_fee = 537; + int64_t order_update_fee = 437; int64_t order_cancel_fee = 129; uint32_t skip = database::skip_witness_signature @@ -745,13 +746,19 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) generate_block( skip ); - for( int i=0; i<2; i++ ) + for( int i=0; i<5; i++ ) { if( i == 1 ) { generate_blocks( HARDFORK_445_TIME, true, skip ); generate_block( skip ); } + else if( i == 2 ) + { + generate_blocks( HARDFORK_CORE_1604_TIME, true, skip ); + generate_block( skip ); + } + // enable_fees() and change_fees() modifies DB directly, and results will be overwritten by block generation // so we have to do it every time we stop generating/popping blocks and start doing tx's @@ -771,6 +778,11 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) create_fee_params.fee = order_create_fee; new_fees.insert( create_fee_params ); } + { + limit_order_update_operation::fee_params_t update_fee_params; + update_fee_params.fee = order_update_fee; + new_fees.insert( update_fee_params ); + } { limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee; @@ -795,6 +807,21 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - order_create_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 500 ); + int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee ; + int64_t bob_update_fees = 0; + if( i == 2 ) + { + // Bob updates order + update_limit_order( bo1_id, {}, asset(100, usd_id) ); + + bob_update_fees += update_net_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - order_update_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 600 ); + } + // Bob cancels order cancel_limit_order( bo1_id(db) ); @@ -806,7 +833,7 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - 1000 - order_create_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); // Alice cancels order @@ -814,27 +841,62 @@ BOOST_AUTO_TEST_CASE( fee_refund_test ) BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 ); // Check partial fill const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id) ); + + BOOST_REQUIRE( ao2 != nullptr ); + + int64_t alice_update_fees = order_create_fee; + int64_t alice_update_amounts = 0; + if( i == 3 ) + { + // Alice updates order + update_limit_order( *ao2, {}, asset(100) ); + + alice_update_fees = update_net_fee + order_update_fee; + alice_update_amounts = 100; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 ); + } + const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( ao2 != nullptr ); BOOST_CHECK( bo2 == nullptr ); - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 1000 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + if( i == 4 ) + { + // Alice updates order + update_limit_order( *ao2, {}, asset(100) ); + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees + - 1000 - alice_update_amounts + - order_update_fee - 100 ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); + } + // cancel Alice order, show that entire deferred_fee was consumed by partial match cancel_limit_order( *ao2 ); - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - order_create_fee - 500 - order_cancel_fee ); + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_b0 - cancel_net_fee - alice_update_fees - 500 + - order_cancel_fee ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_b0 + 100 ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - cancel_net_fee - order_create_fee + 500 ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_b0 - bob_update_fees - cancel_net_fee + - order_create_fee + 500 ); BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_b0 - 100 ); // TODO: Check multiple fill @@ -921,7 +983,8 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) // prepare params uint32_t blocks_generated = 0; time_point_sec max_exp = time_point_sec::maximum(); - time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then expired at current block + time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then + // expired at current block price cer( asset(1), asset(1, usd_id) ); const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db ); @@ -1028,10 +1091,10 @@ BOOST_AUTO_TEST_CASE( non_core_fee_refund_test ) // Check partial fill const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); - const limit_order_id_type ao2id = ao2->get_id(); + const limit_order_id_type ao2_id = ao2->get_id(); const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( db.find( ao2id ) != nullptr ); + BOOST_CHECK( db.find( ao2_id ) != nullptr ); BOOST_CHECK( bo2 == nullptr ); // data after order created @@ -1764,6 +1827,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) fund_fee_pool( committee_account( db ), usd_obj, pool_0 ); int64_t order_create_fee = 547; + int64_t order_update_fee = 487; int64_t order_cancel_fee; int64_t order_cancel_fee1 = 139; int64_t order_cancel_fee2 = 829; @@ -1787,6 +1851,12 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) new_fees1.insert( create_fee_params ); new_fees2.insert( create_fee_params ); } + { + limit_order_update_operation::fee_params_t update_fee_params; + update_fee_params.fee = order_update_fee; + new_fees1.insert( update_fee_params ); + new_fees2.insert( update_fee_params ); + } { limit_order_cancel_operation::fee_params_t cancel_fee_params; cancel_fee_params.fee = order_cancel_fee1; @@ -1805,13 +1875,14 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) new_fees2.insert( transfer_fee_params ); } - for( int i=0; i<12; i++ ) + for( int i=0; i<16; i++ ) { bool expire_order = ( i % 2 != 0 ); bool high_cancel_fee = ( i % 4 >= 2 ); bool before_hardfork_445 = ( i < 4 ); bool after_bsip26 = ( i >= 8 ); - idump( (before_hardfork_445)(after_bsip26)(expire_order)(high_cancel_fee) ); + bool after_core_hf1604 = ( i >= 12 ); + idump( (before_hardfork_445)(after_bsip26)(after_core_hf1604)(expire_order)(high_cancel_fee) ); if( i == 4 ) { BOOST_TEST_MESSAGE( "Hard fork 445" ); @@ -1824,6 +1895,12 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) generate_blocks( HARDFORK_CORE_604_TIME, true, skip ); generate_block( skip ); } + else if( i == 12 ) + { + BOOST_TEST_MESSAGE( "Hard fork core-1604" ); + generate_blocks( HARDFORK_CORE_1604_TIME, true, skip ); + generate_block( skip ); + } if( high_cancel_fee ) { @@ -1838,9 +1915,12 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) int64_t usd_create_fee = order_create_fee * cer_usd_amount / cer_core_amount; if( usd_create_fee * cer_core_amount != order_create_fee * cer_usd_amount ) usd_create_fee += 1; + int64_t usd_update_fee = order_update_fee * cer_usd_amount / cer_core_amount; + if( usd_update_fee * cer_core_amount != order_update_fee * cer_usd_amount ) usd_update_fee += 1; int64_t usd_cancel_fee = order_cancel_fee * cer_usd_amount / cer_core_amount; if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1; int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount; + int64_t core_update_fee = usd_update_fee * cer_core_amount / cer_usd_amount; int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount; BOOST_CHECK( core_cancel_fee >= order_cancel_fee ); @@ -1857,7 +1937,8 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // prepare params uint32_t blocks_generated = 0; time_point_sec max_exp = time_point_sec::maximum(); - time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then expired at current block + time_point_sec exp = db.head_block_time(); // order will be accepted when pushing trx then + // expired at current block price cer = usd_id( db ).options.core_exchange_rate; const auto* usd_stat = &usd_id( db ).dynamic_asset_data_id( db ); @@ -1904,6 +1985,15 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) accum_on_fill = 0; pool_refund = 0; } + // refund data related to order update + int64_t core_update_fee_refund_core = order_update_fee; + int64_t core_update_fee_refund_usd = 0; + int64_t usd_update_fee_refund_core = 0; + int64_t usd_update_fee_refund_usd = usd_update_fee; + int64_t accum_on_fill_after_update = usd_update_fee; + int64_t pool_refund_after_update = core_update_fee; + + int64_t update_net_fee = order_cancel_fee * order_update_fee / order_create_fee; // Check non-overlapping // Alice creates order @@ -1922,6 +2012,27 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Alice updates order + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order ao1" ); + update_limit_order( ao1_id, {}, asset(100), {}, cer ); + + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee + + alice_bc += order_create_fee; // refund + alice_bc -= std::min( order_create_fee, update_net_fee ); // charge a fee (adjusted cancel_fee, capped) + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + // Alice cancels order if( !expire_order ) { @@ -1951,25 +2062,55 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) transfer( account_id_type(), bob_id, asset( bob_bu, usd_id) ); } - - if( !expire_order ) - alice_bc -= order_cancel_fee; // manual cancellation always need a fee - else if( before_hardfork_445 ) - { // do nothing: before hard fork 445, no fee on expired order - } - else if( !after_bsip26 ) + if( after_core_hf1604 ) { - // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee - alice_bc -= std::min( order_cancel_fee, order_create_fee ); + if( !expire_order ) + alice_bc -= order_cancel_fee; // manual cancellation always need a fee + else // bsip26 + { + // when expired, should have core_update_fee in deferred, usd_update_fee in deferred_paid + + // charge a cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_update_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee; + if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_update_fee ) + capped_usd_cancel_fee = usd_update_fee; + alice_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + } + alice_bc += 100; // delta + alice_bc += usd_update_fee_refund_core; + alice_bu += usd_update_fee_refund_usd; + pool_b += pool_refund_after_update; } - else // bsip26 + else { - // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee - alice_bc -= std::min( order_cancel_fee, order_create_fee ); + if( !expire_order ) + alice_bc -= order_cancel_fee; // manual cancellation always need a fee + else if( before_hardfork_445 ) + { // do nothing: before hard fork 445, no fee on expired order + } + else if( !after_bsip26 ) + { + // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee + alice_bc -= std::min( order_cancel_fee, order_create_fee ); + } + else // bsip26 + { + // charge a cancellation fee in core, capped by deffered_fee which is order_create_fee + alice_bc -= std::min( order_cancel_fee, order_create_fee ); + } + alice_bc += core_fee_refund_core; + alice_bu += core_fee_refund_usd; } alice_bc += 1000; - alice_bc += core_fee_refund_core; - alice_bu += core_fee_refund_usd; BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -1994,6 +2135,44 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Bob updates order + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order bo1" ); + update_limit_order( bo1_id, {}, asset(100, usd_id) ); + + bob_bu -= 100; // delta + bob_bc -= order_update_fee; // fee + + // there should have core_create_fee in deferred, usd_create_fee in deferred_paid + + // refund + pool_b += core_create_fee; + bob_bu += usd_create_fee; + + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee; + if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_create_fee ) + capped_usd_cancel_fee = usd_create_fee; + bob_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + // Bob cancels order if( !expire_order ) { @@ -2023,22 +2202,93 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) transfer( account_id_type(), bob_id, asset( bob_bu, usd_id) ); } - if( !expire_order ) - bob_bc -= order_cancel_fee; // manual cancellation always need a fee - else if( before_hardfork_445 ) - { // do nothing: before hard fork 445, no fee on expired order + if( after_core_hf1604 ) + { + if( !expire_order ) + bob_bc -= order_cancel_fee; // manual cancellation always need a fee + else + { + // charge a cancellation fee in core, capped by deffered_fee which is order_update_fee + bob_bc -= std::min( order_cancel_fee, order_update_fee ); + } + bob_bu += 100; // delta + bob_bc += core_update_fee_refund_core; + bob_bu += core_update_fee_refund_usd; } - else if( !after_bsip26 ) + else { - // charge a cancellation fee in core, capped by deffered_fee which is core_create_fee - bob_bc -= std::min( order_cancel_fee, core_create_fee ); + if( !expire_order ) + bob_bc -= order_cancel_fee; // manual cancellation always need a fee + else if( before_hardfork_445 ) + { // do nothing: before hard fork 445, no fee on expired order + } + else if( !after_bsip26 ) + { + // charge a cancellation fee in core, capped by deffered_fee which is core_create_fee + bob_bc -= std::min( order_cancel_fee, core_create_fee ); + } + else // bsip26 + { + // when expired, should have core_create_fee in deferred, usd_create_fee in deferred_paid + + // charge a cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_create_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_create_fee / core_create_fee; + if( capped_usd_cancel_fee * core_create_fee != capped_core_cancel_fee * usd_create_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_create_fee ) + capped_usd_cancel_fee = usd_create_fee; + bob_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + } + bob_bc += usd_fee_refund_core; + bob_bu += usd_fee_refund_usd; + pool_b += pool_refund; // bo1 } - else // bsip26 + bob_bu += 500; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + + + // Check partial fill + BOOST_TEST_MESSAGE( "Creating ao2" ); + const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); + const limit_order_id_type ao2_id = ao2->get_id(); + + // data after order created + alice_bc -= 1000; + alice_bu -= usd_create_fee; + pool_b -= core_create_fee; + accum_b += accum_on_new; + + // Alice updates order + if( after_core_hf1604 ) { - // when expired, should have core_create_fee in deferred, usd_create_fee in deferred_paid + BOOST_TEST_MESSAGE( "Update order ao2" ); + update_limit_order( ao2_id, {}, asset(100), {}, cer ); + + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee + + // there should have core_create_fee in deferred, usd_create_fee in deferred_paid - // charge a cancellation fee in core from fee_pool, capped by deffered - int64_t capped_core_cancel_fee = std::min( order_cancel_fee, core_create_fee ); + // refund + pool_b += core_create_fee; + alice_bu += usd_create_fee; + + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_create_fee ); pool_b -= capped_core_cancel_fee; // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped @@ -2047,45 +2297,75 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) capped_usd_cancel_fee += 1; if( capped_usd_cancel_fee > usd_create_fee ) capped_usd_cancel_fee = usd_create_fee; - bob_bu -= capped_usd_cancel_fee; + alice_bu -= capped_usd_cancel_fee; // cancellation fee goes to accumulated fees accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); } - bob_bc += usd_fee_refund_core; - bob_bu += 500; - bob_bu += usd_fee_refund_usd; - pool_b += pool_refund; // bo1 - BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); - BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); - BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); - BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); - BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); - BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + // Alice updates order again + if( after_core_hf1604 ) + { + BOOST_TEST_MESSAGE( "Update order ao2 again" ); + update_limit_order( ao2_id, {}, asset(100), {}, cer ); + alice_bc -= 100; // delta + alice_bu -= usd_update_fee; // fee + pool_b -= core_update_fee; // pool fee - // Check partial fill - BOOST_TEST_MESSAGE( "Creating ao2, then be partially filled by bo2" ); - const limit_order_object* ao2 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), exp, cer ); - const limit_order_id_type ao2id = ao2->get_id(); + // there should have core_update_fee in deferred, usd_update_fee in deferred_paid + + // refund + pool_b += core_update_fee; + alice_bu += usd_update_fee; + + // charge an adjusted cancellation fee in core from fee_pool, capped by deffered + int64_t capped_core_cancel_fee = std::min( update_net_fee, core_update_fee ); + pool_b -= capped_core_cancel_fee; + + // charge a coresponding cancellation fee in usd from deffered_paid, round up, capped + int64_t capped_usd_cancel_fee = capped_core_cancel_fee * usd_update_fee / core_update_fee; + if( capped_usd_cancel_fee * core_update_fee != capped_core_cancel_fee * usd_update_fee ) + capped_usd_cancel_fee += 1; + if( capped_usd_cancel_fee > usd_update_fee ) + capped_usd_cancel_fee = usd_update_fee; + alice_bu -= capped_usd_cancel_fee; + + // cancellation fee goes to accumulated fees + accum_b += capped_usd_cancel_fee; + + BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); + BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); + BOOST_CHECK_EQUAL( get_balance( bob_id, core_id ), bob_bc ); + BOOST_CHECK_EQUAL( get_balance( bob_id, usd_id ), bob_bu ); + BOOST_CHECK_EQUAL( usd_stat->fee_pool.value, pool_b ); + BOOST_CHECK_EQUAL( usd_stat->accumulated_fees.value, accum_b ); + } + + BOOST_TEST_MESSAGE( "Creating bo2, partially fill ao2" ); const limit_order_object* bo2 = create_sell_order( bob_id, asset(100, usd_id), asset(500) ); - BOOST_CHECK( db.find( ao2id ) != nullptr ); + BOOST_CHECK( db.find( ao2_id ) != nullptr ); BOOST_CHECK( bo2 == nullptr ); // data after order created - alice_bc -= 1000; - alice_bu -= usd_create_fee; - pool_b -= core_create_fee; - accum_b += accum_on_new; bob_bc -= order_create_fee; bob_bu -= 100; // data after order filled alice_bu += 100; bob_bc += 500; - accum_b += accum_on_fill; // ao2 + if( after_core_hf1604 ) + accum_b += accum_on_fill_after_update; // ao2 + else + accum_b += accum_on_fill; // ao2 BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2130,6 +2410,8 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // before hard fork 445, no fee when order is expired; // after hard fork 445, when partially filled order expired, order cancel fee is capped at 0 alice_bc += 500; + if( after_core_hf1604 ) + alice_bc += 200; // delta * 2 BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2141,11 +2423,11 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) // Check multiple fill // Alice creating multiple orders BOOST_TEST_MESSAGE( "Creating ao31-ao35" ); - const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000, usd_id), max_exp, cer ); - const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); - const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer ); + const limit_order_object* ao31 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao32 = create_sell_order( alice_id, asset(1000), asset(2000,usd_id), max_exp, cer); + const limit_order_object* ao33 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao34 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); + const limit_order_object* ao35 = create_sell_order( alice_id, asset(1000), asset(200, usd_id), max_exp, cer); const limit_order_id_type ao31id = ao31->get_id(); const limit_order_id_type ao32id = ao32->get_id(); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 54014695c..aad129cdb 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -50,8 +51,8 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture ) BOOST_AUTO_TEST_CASE( feed_limit_logic_test ) -{ - try { +{ try { + asset usd(1000,asset_id_type(1)); asset core(1000,asset_id_type(0)); price_feed feed; @@ -74,15 +75,422 @@ BOOST_AUTO_TEST_CASE( feed_limit_logic_test ) BOOST_CHECK( usd * feed.maintenance_price() < usd * feed.max_short_squeeze_price() ); */ - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( limit_order_update_hardfork_time_test ) +{ try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_2362_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(1500)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price(asset(500), munee.amount(1000)); + limit_order_id_type order_id = create_sell_order(nathan, asset(500), munee.amount(1000), expiration)->get_id(); + + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Cannot update order yet + sell_price.base = asset(499); + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::assert_exception ); + + // Cannot propose + limit_order_update_operation louop = make_limit_order_update_op( nathan_id, order_id, sell_price ); + BOOST_CHECK_THROW( propose( louop ), fc::exception ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)(dan)); + + const auto& bitusd = create_bitasset("USDBIT", nathan_id); + const auto& munee = create_user_issued_asset("MUNEE"); + const auto& core = asset_id_type()(db); + + update_feed_producers(bitusd, {nathan_id}); + price_feed current_feed; + current_feed.settlement_price = bitusd.amount(200) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; + publish_feed(bitusd, nathan, current_feed); + + transfer(committee_account, nathan_id, asset(1500)); + issue_uia(nathan, munee.amount(100)); + borrow(nathan_id, bitusd.amount(100), asset(500)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price(asset(500), bitusd.amount(1000)); + limit_order_id_type order_id = create_sell_order(nathan, asset(500), bitusd.amount(1000), expiration)->get_id(); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Cannot update order without changing anything + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id), fc::exception); + // Cannot update order to use inverted price assets + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), asset(1))), fc::exception); + // Cannot update order to use negative price + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(-1), bitusd.amount(2))), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(1), bitusd.amount(-2))), fc::exception); + // Cannot update order to use different assets + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(bitusd.amount(2), munee.amount(1))), + fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(munee.amount(2), bitusd.amount(1))), + fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(2), munee.amount(1))), fc::exception); + // Cannot update order to expire in the past + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 10), fc::exception); + // Cannot update order with a zero delta + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset()), fc::exception); + // Cannot update order to add more funds than seller has + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(501)), fc::exception); + // Cannot update order to remove more funds than order has + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-501)), fc::exception); + // Cannot update order to remove all funds in order + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-500)), fc::exception); + // Cannot update order to add or remove different kind of funds + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, bitusd.amount(-50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(50)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, munee.amount(-50)), fc::exception); + + // Cannot update someone else's order + limit_order_update_operation louop = make_limit_order_update_op( dan_id, order_id, {}, asset(-1) ); + trx.operations.clear(); + trx.operations.push_back( louop ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Can propose + propose( louop ); + + // Cannot update an order which does not exist + louop = make_limit_order_update_op( nathan_id, order_id + 1, {}, asset(-1) ); + trx.operations.clear(); + trx.operations.push_back( louop ); + GRAPHENE_REQUIRE_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Try changing price + sell_price.base = asset(501); + // Cannot update base amount in the new price to be more than the amount for sale + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, sell_price), fc::exception ); + sell_price.base = asset(500); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.base = asset(499); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.base = asset(500); + sell_price.quote = bitusd.amount(999); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + sell_price.quote = bitusd.amount(1000); + update_limit_order(order_id, sell_price); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + + // Try changing expiration + expiration += 50; + update_limit_order(order_id, {}, {}, expiration); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + // Cannot change expiration to a time in the past + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, {}, db.head_block_time() - 1 ), fc::exception); + + // Try adding funds + update_limit_order(order_id, {}, asset(50)); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 550); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 450); + + // Try removing funds + update_limit_order(order_id, {}, asset(-100)); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 450); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 550); + + // Try changing everything at once + expiration += 50; + sell_price.base = asset(499); + sell_price.quote = bitusd.amount(1001); + update_limit_order(order_id, sell_price, 50, expiration); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + BOOST_REQUIRE_EQUAL(order_id(db).amount_for_sale().amount.value, 500); + BOOST_REQUIRE_EQUAL(db.get_balance(nathan_id, core.get_id()).amount.value, 500); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( limit_order_update_asset_authorization_test ) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)(dan)(whitey)(blacky)); + + const auto& munee = create_user_issued_asset("MUNEE", dan, white_list ); + const auto& noomo = create_user_issued_asset("NOOMO", dan, white_list ); + + issue_uia(nathan, munee.amount(100)); + issue_uia(nathan, noomo.amount(100)); + + auto expiration = db.head_block_time() + 1000; + auto sell_price = price( munee.amount(50), noomo.amount(60) ); + limit_order_id_type order_id = create_sell_order(nathan, munee.amount(50), noomo.amount(60), expiration) + ->get_id(); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 50); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Make a whitelist + { + BOOST_TEST_MESSAGE( "Setting up whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + // The whitelist is managed by Whitey + uop.new_options.whitelist_authorities.insert(whitey_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Whitey so that he can manage the whitelist + upgrade_to_lifetime_member( whitey_id ); + + // Add Dan to the whitelist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = whitey_id; + wop.account_to_list = dan_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 49); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Add Nathan to the whitelist + wop.account_to_list = nathan_id; + wop.new_listing = account_whitelist_operation::white_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a blacklist + { + BOOST_TEST_MESSAGE( "Setting up blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = noomo.id; + uop.issuer = dan_id; + uop.new_options = noomo.options; + // The blacklist is managed by Blacky + uop.new_options.blacklist_authorities.insert(blacky_id); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Upgrade Blacky so that he can manage the blacklist + upgrade_to_lifetime_member( blacky_id ); + + // Add Nathan to the blacklist, but do not add others + account_whitelist_operation wop; + wop.authorizing_account = blacky_id; + wop.account_to_list = nathan_id; + wop.new_listing = account_whitelist_operation::black_listed; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 48); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Clear blacklist + wop.new_listing = account_whitelist_operation::no_listing; + trx.operations.clear(); + trx.operations.push_back(wop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a market whitelist + { + BOOST_TEST_MESSAGE( "Setting up market whitelisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + uop.new_options.whitelist_markets.insert( asset_id_type() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 47); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Add Noomo to the whitelist + uop.new_options.whitelist_markets.insert( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + // Make a market blacklist + { + BOOST_TEST_MESSAGE( "Setting up market blacklisting" ); + asset_update_operation uop; + uop.asset_to_update = munee.id; + uop.issuer = dan_id; + uop.new_options = munee.options; + uop.new_options.whitelist_markets.clear(); + uop.new_options.blacklist_markets.insert( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Cannot update order + GRAPHENE_REQUIRE_THROW( update_limit_order(order_id, {}, munee.amount(-1)), fc::exception ); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 46); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + + // Remove Noomo from the blacklist + uop.new_options.blacklist_markets.erase( noomo.get_id() ); + trx.operations.clear(); + trx.operations.push_back(uop); + PUSH_TX( db, trx, ~0 ); + + // Can update order + update_limit_order(order_id, {}, munee.amount(-1)); + BOOST_REQUIRE_EQUAL(order_id(db).for_sale.value, 45); + BOOST_REQUIRE_EQUAL(fc::json::to_string(order_id(db).sell_price), fc::json::to_string(sell_price)); + BOOST_REQUIRE_EQUAL(order_id(db).expiration.sec_since_epoch(), expiration.sec_since_epoch()); + } + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_dust_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(nathan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id = create_sell_order(nathan, asset(1000), munee.amount(100), expiration)->get_id(); + + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, {}, asset(-995)), fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(1000000), munee.amount(100))), + fc::exception); + GRAPHENE_REQUIRE_THROW(update_limit_order(order_id, price(asset(2000), munee.amount(100)), asset(-985)), + fc::exception); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_match_test) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(nathan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id(); + limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id(); + + update_limit_order(order_id_1, price(asset(1000), munee.amount(99)), asset(1)); + BOOST_REQUIRE( !db.find(order_id_1) ); + BOOST_REQUIRE_EQUAL(db.find(order_id_2)->amount_for_sale().amount.value, 1); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE(limit_order_update_match_test_2) +{ try { + + generate_blocks(HARDFORK_CORE_1604_TIME + 10); + set_expiration( db, trx ); + + ACTORS((nathan)); + + const auto& munee = create_user_issued_asset("MUNEE"); + + transfer(committee_account, nathan_id, asset(10000)); + issue_uia(nathan, munee.amount(1000)); + + auto expiration = db.head_block_time() + 1000; + limit_order_id_type order_id_1 = create_sell_order(nathan, asset(999), munee.amount(100), expiration)->get_id(); + limit_order_id_type order_id_2 = create_sell_order(nathan, munee.amount(100),asset(1001), expiration)->get_id(); + + update_limit_order(order_id_2, price(munee.amount(100), asset(999))); + BOOST_REQUIRE( !db.find(order_id_1) ); + BOOST_REQUIRE( !db.find(order_id_2) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_test ) -{ - try { +{ try { ACTORS((dan)(sam)); const auto& bitusd = create_bitasset("USDBIT", sam.get_id()); @@ -174,15 +582,10 @@ BOOST_AUTO_TEST_CASE( call_order_update_test ) BOOST_TEST_MESSAGE( "attempting change call price to be below minimum for debt/collateral ratio" ); GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) -{ - try { +{ try { auto hf_time = HARDFORK_CORE_583_TIME; if( bsip77 ) @@ -281,15 +684,11 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) BOOST_TEST_MESSAGE( "attempting change call price to be below minimum for debt/collateral ratio" ); GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), asset(0)), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -438,15 +837,11 @@ BOOST_AUTO_TEST_CASE( call_order_update_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -604,15 +999,11 @@ BOOST_AUTO_TEST_CASE( asset_settle_operation_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_973_TIME - fc::days(1) ); set_expiration( db, trx ); @@ -765,14 +1156,11 @@ BOOST_AUTO_TEST_CASE( bid_collateral_operation_asset_auth_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) -{ +{ try { + set_expiration( db, trx ); BOOST_TEST_MESSAGE( "Creating a proposal containing a asset_settle_cancel_operation" ); @@ -825,15 +1213,15 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) return false; }); } -} + +} FC_LOG_AND_RETHROW() } /// Test case for bsip77: /// * the "initial_collateral_ratio" parameter can only be set after the BSIP77 hard fork /// * the parameter should be within a range // TODO removed the hard fork part after the hard fork, keep the valid range part BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test ) -{ - try { +{ try { // Proceeds to a recent hard fork generate_blocks( HARDFORK_CORE_583_TIME ); @@ -1013,11 +1401,7 @@ BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test ) generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) { @@ -1026,8 +1410,7 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_ } BOOST_AUTO_TEST_CASE( more_call_order_update_test ) -{ - try { +{ try { ACTORS((dan)(sam)(alice)(bob)); const auto& bitusd = create_bitasset("USDBIT", sam.get_id()); @@ -1121,16 +1504,10 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test ) BOOST_TEST_MESSAGE( "dan adding 1 core as collateral should not be allowed..." ); GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(0), core.amount(1) ), fc::exception ); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) -{ - try { +{ try { auto hf_time = HARDFORK_CORE_583_TIME; if( bsip77 ) @@ -1238,11 +1615,7 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 5003 more core should not be allowed..." ); GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) { @@ -1251,8 +1624,7 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_set ) -{ - try { +{ try { auto hf_time = HARDFORK_BSIP_77_TIME; generate_blocks( hf_time ); @@ -1445,15 +1817,10 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_fed ) -{ - try { +{ try { auto hf_time = HARDFORK_BSIP_77_TIME; generate_blocks( hf_time ); @@ -1628,14 +1995,11 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr generate_block(); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) -{ +{ try { + call_order_update_operation op; // throw on default values @@ -1666,7 +2030,8 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) op.extensions.value.target_collateral_ratio = 65535; op.validate(); // still valid -} + +} FC_LOG_AND_RETHROW() } /** * This test sets up a situation where a margin call will be executed and ensures that @@ -1684,6 +2049,7 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) */ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) { try { + ACTORS((buyer)(seller)(borrower)(borrower2)(feedproducer)); const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); @@ -1744,14 +2110,12 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) // this should trigger margin call without protection from the price feed. order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) ); BOOST_CHECK( order != nullptr ); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( prediction_market ) { try { + ACTORS((judge)(dan)(nathan)); const auto& pmark = create_prediction_market("PMARK", judge_id); @@ -1802,14 +2166,12 @@ BOOST_AUTO_TEST_CASE( prediction_market ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 ) { try { + ACTORS((judge)(dan)(nathan)); const auto& pmark = create_prediction_market("PMARK", judge_id); @@ -1840,18 +2202,15 @@ BOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); -} catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } /*** * Prediction markets should not suffer a black swan (Issue #460) */ BOOST_AUTO_TEST_CASE( prediction_market_black_swan ) -{ - try { +{ try { + ACTORS((judge)(dan)(nathan)); // progress to recent hardfork @@ -1902,15 +2261,12 @@ BOOST_AUTO_TEST_CASE( prediction_market_black_swan ) generate_block(~database::skip_transaction_dupe_check); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); generate_block(); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_account_test ) -{ - try { +{ try { + generate_blocks( HARDFORK_CORE_143_TIME ); set_expiration( db, trx ); trx.operations.push_back(make_account()); @@ -1982,15 +2338,11 @@ BOOST_AUTO_TEST_CASE( create_account_test ) BOOST_CHECK_EQUAL( nathan_id(db).creation_block_num, db.head_block_num() ); BOOST_CHECK( nathan_id(db).creation_time == db.head_block_time() ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_account ) -{ - try { +{ try { + const account_object& nathan = create_account("nathan", init_account_pub_key); const fc::ecc::private_key nathan_new_key = fc::ecc::private_key::generate(); const public_key_type key_id = nathan_new_key.get_public_key(); @@ -2032,15 +2384,12 @@ BOOST_AUTO_TEST_CASE( update_account ) } BOOST_CHECK( nathan.is_lifetime_member() ); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( transfer_core_asset ) -{ - try { +{ try { + INVOKE(create_account_test); account_id_type committee_account; @@ -2080,15 +2429,11 @@ BOOST_AUTO_TEST_CASE( transfer_core_asset ) BOOST_CHECK_EQUAL(get_balance(nathan_account, asset_id_type()(db)), 8000 - fee.amount.value); BOOST_CHECK_EQUAL(get_balance(account_id_type()(db), asset_id_type()(db)), committee_balance.amount.value + 2000); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_committee_member ) -{ - try { +{ try { + committee_member_create_operation op; op.committee_member_account = account_id_type(); op.fee = asset(); @@ -2103,29 +2448,23 @@ BOOST_AUTO_TEST_CASE( create_committee_member ) const committee_member_object& d = committee_member_id(db); BOOST_CHECK(d.committee_member_account == account_id_type()); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_mia ) -{ - try { +{ try { + const asset_object& bitusd = create_bitasset( "USDBIT" ); BOOST_CHECK(bitusd.symbol == "USDBIT"); BOOST_CHECK(bitusd.bitasset_data(db).options.short_backing_asset == asset_id_type()); BOOST_CHECK(bitusd.dynamic_asset_data_id(db).current_supply == 0); GRAPHENE_REQUIRE_THROW( create_bitasset("USDBIT"), fc::exception); - } catch ( const fc::exception& e ) { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_mia ) -{ - try { +{ try { + INVOKE(create_mia); generate_block(); const asset_object& bit_usd = get_asset("USDBIT"); @@ -2172,16 +2511,12 @@ BOOST_AUTO_TEST_CASE( update_mia ) trx.operations.back() = op; PUSH_TX( db, trx, ~0 ); BOOST_CHECK(bit_usd.issuer == account_id_type()); - } catch ( const fc::exception& e ) { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_uia ) -{ - try { +{ try { + asset_id_type test_asset_id { db.get_index().get_next_id() }; asset_create_operation creator; creator.issuer = account_id_type(); @@ -2230,16 +2565,11 @@ BOOST_AUTO_TEST_CASE( create_uia ) BOOST_CHECK_EQUAL( test_asset_id(db).creation_block_num, db.head_block_num() ); BOOST_CHECK( test_asset_id(db).creation_time == db.head_block_time() ); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_uia ) -{ - using namespace graphene; - try { +{ try { + INVOKE(create_uia); const auto& test = get_asset(UIA_TEST_SYMBOL); const auto& nathan = create_account("nathan"); @@ -2299,7 +2629,7 @@ BOOST_AUTO_TEST_CASE( update_uia ) issue_op.issue_to_account = nathan.get_id(); trx.operations.push_back(issue_op); PUSH_TX(db, trx, ~0); - + BOOST_TEST_MESSAGE( "Make sure white_list can't be re-enabled (after tokens issued)" ); op.new_options.issuer_permissions = test.options.issuer_permissions; op.new_options.flags = test.options.flags; @@ -2313,18 +2643,11 @@ BOOST_AUTO_TEST_CASE( update_uia ) op.issuer = account_id_type(); GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0 ), fc::exception); op.new_issuer.reset(); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( update_uia_issuer ) -{ - using namespace graphene; - using namespace graphene::chain; - using namespace graphene::chain::test; - try { +{ try { // Lambda for creating accounts with 2 different keys auto create_account_2_keys = [&]( const string name, @@ -2440,17 +2763,11 @@ BOOST_AUTO_TEST_CASE( update_uia_issuer ) BOOST_CHECK(test_id(db).issuer == bob_id); - } - catch( const fc::exception& e ) - { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( issue_uia ) -{ - try { +{ try { + INVOKE(create_uia); INVOKE(create_account_test); @@ -2482,15 +2799,12 @@ BOOST_AUTO_TEST_CASE( issue_uia ) BOOST_CHECK(test_dynamic_data.current_supply == 10000000); BOOST_CHECK(test_dynamic_data.accumulated_fees == 0); BOOST_CHECK(test_dynamic_data.fee_pool == 0); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( transfer_uia ) -{ - try { +{ try { + INVOKE(issue_uia); const asset_object& uia = *db.get_index_type().indices().get().find(UIA_TEST_SYMBOL); @@ -2511,15 +2825,12 @@ BOOST_AUTO_TEST_CASE( transfer_uia ) PUSH_TX( db, trx, ~0 ); BOOST_CHECK_EQUAL(get_balance(nathan, uia), 10000000 - 10000); BOOST_CHECK_EQUAL(get_balance(committee, uia), 10000); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new ) { try { + INVOKE( issue_uia ); const asset_object& core_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& test_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2550,16 +2861,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 200 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 297 ); BOOST_CHECK_EQUAL( core_asset.dynamic_asset_data_id(db).accumulated_fees.value , 3 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_exact_match_uia ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2590,17 +2897,13 @@ BOOST_AUTO_TEST_CASE( create_buy_exact_match_uia ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 99 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 100 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 1 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2631,16 +2934,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse ) BOOST_CHECK_EQUAL( get_balance( seller_account, test_asset ), 198 ); BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 300 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const asset_object& core_asset = get_asset( GRAPHENE_SYMBOL ); @@ -2674,18 +2973,12 @@ BOOST_AUTO_TEST_CASE( create_buy_uia_multiple_match_new_reverse_fract ) BOOST_CHECK_EQUAL( get_balance( buyer_account, core_asset ), 30 ); BOOST_CHECK_EQUAL( get_balance( seller_account, core_asset ), 0 ); BOOST_CHECK_EQUAL( test_asset.dynamic_asset_data_id(db).accumulated_fees.value , 2 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( uia_fees ) -{ - try { +{ try { + INVOKE( issue_uia ); enable_fees(); @@ -2744,14 +3037,12 @@ BOOST_AUTO_TEST_CASE( uia_fees ) BOOST_CHECK_EQUAL(get_balance(committee_account, test_asset), 200); BOOST_CHECK(asset_dynamic.accumulated_fees == fee.amount.value * 3); BOOST_CHECK(asset_dynamic.fee_pool == 1000*prec - core_fee.amount.value * 3); - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try { + INVOKE( issue_uia ); const asset_object& test_asset = get_asset( UIA_TEST_SYMBOL ); const account_object& buyer_account = create_account( "buyer" ); @@ -2764,18 +3055,12 @@ BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) auto refunded = cancel_limit_order( *sell_order ); BOOST_CHECK( refunded == asset(1000) ); BOOST_CHECK_EQUAL( get_balance(buyer_account, asset_id_type()(db)), 10000 ); - } - catch ( const fc::exception& e ) - { - elog( "${e}", ("e", e.to_detail_string() ) ); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( witness_feeds ) -{ - using namespace graphene::chain; - try { +{ try { + INVOKE( create_mia ); { auto& current = get_asset( "USDBIT" ); @@ -2825,11 +3110,8 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) BOOST_CHECK_EQUAL(bitasset.current_feed.settlement_price.to_real(), 30.0 / GRAPHENE_BLOCKCHAIN_PRECISION); BOOST_CHECK(bitasset.current_feed.maintenance_collateral_ratio == GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO); - } catch (const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } /** * Create an order that cannot be filled immediately and have the @@ -2837,6 +3119,7 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) */ BOOST_AUTO_TEST_CASE( limit_order_fill_or_kill ) { try { + INVOKE(issue_uia); const account_object& nathan = get_account("nathan"); const asset_object& test = get_asset(UIA_TEST_SYMBOL); @@ -2854,6 +3137,7 @@ BOOST_AUTO_TEST_CASE( limit_order_fill_or_kill ) op.fill_or_kill = false; trx.operations.back() = op; PUSH_TX( db, trx, ~0 ); + } FC_LOG_AND_RETHROW() } /// Shameless code coverage plugging. Otherwise, these calls never happen. @@ -2986,9 +3270,8 @@ BOOST_AUTO_TEST_CASE( witness_pay_test ) * can be burned, and all supplies add up. */ BOOST_AUTO_TEST_CASE( reserve_asset_test ) -{ - try - { +{ try { + ACTORS((alice)(bob)(sam)(judge)); const auto& basset = create_bitasset("USDBIT", judge_id); const auto& uasset = create_user_issued_asset(UIA_TEST_SYMBOL); @@ -3059,25 +3342,19 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test ) BOOST_CHECK_EQUAL( get_balance( alice, uasset ), init_balance - reserve_amount ); BOOST_CHECK_EQUAL( (uasset.reserved( db ) - initial_reserve).value, reserve_amount ); verify_asset_supplies(db); - } - catch (fc::exception& e) - { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) -{ - try - { +{ try { + ACTORS( (alice) (bob) ); transfer(committee_account, alice_id, asset(10000000 * GRAPHENE_BLOCKCHAIN_PRECISION)); const auto& core = asset_id_type()(db); // attempt to increase current supply beyond max_supply - const auto& bitjmj = create_bitasset( "JMJBIT", alice_id, 100, charge_market_fee, 2U, + const auto& bitjmj = create_bitasset( "JMJBIT", alice_id, 100, charge_market_fee, 2U, asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 ); auto bitjmj_id = bitjmj.get_id(); share_type original_max_supply = bitjmj.options.max_supply; @@ -3112,7 +3389,7 @@ BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) BOOST_REQUIRE_GT(newbitjmj.options.max_supply.value, original_max_supply.value); // now try with an asset after the hardfork - const auto& bitusd = create_bitasset( "USDBIT", alice_id, 100, charge_market_fee, 2U, + const auto& bitusd = create_bitasset( "USDBIT", alice_id, 100, charge_market_fee, 2U, asset_id_type{}, GRAPHENE_MAX_SHARE_SUPPLY / 2 ); { @@ -3171,17 +3448,16 @@ BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test ) set_expiration( db, tx ); PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ); } - } FC_LOG_AND_RETHROW() -} + +} FC_LOG_AND_RETHROW() } /** * This test demonstrates how using the call_order_update_operation to * trigger a margin call is legal if there is a matching order. */ BOOST_AUTO_TEST_CASE( cover_with_collateral_test ) -{ - try - { +{ try { + ACTORS((alice)(bob)(sam)); const auto& bitusd = create_bitasset("USDBIT", sam_id); const auto& core = asset_id_type()(db); @@ -3249,13 +3525,8 @@ BOOST_AUTO_TEST_CASE( cover_with_collateral_test ) BOOST_CHECK( db.find( order1_id ) == nullptr ); BOOST_CHECK( db.find( order2_id ) == nullptr ); - } - catch (fc::exception& e) - { - edump((e.to_detail_string())); - throw; - } -} + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( vesting_balance_create_test ) { try {