From 10298cae580cff9c6e55ed03bbc4754fa17a48fe Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 6 Feb 2018 21:11:54 +0000 Subject: [PATCH 01/29] Test case reproduces #338 #343 #453 #606 #625 #649 --- tests/tests/market_tests.cpp | 126 ++++++++++++++++++++++++++++++++--- 1 file changed, 116 insertions(+), 10 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index d6ee8c7f05..9fed320473 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -39,24 +39,28 @@ using namespace graphene::wallet; BOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture) /*** - * Reproduce bitshares-core issue #338 + * Reproduce bitshares-core issue #338 #343 #453 #606 #625 #649 */ -BOOST_AUTO_TEST_CASE(issue_338) +BOOST_AUTO_TEST_CASE(issue_338_etc) { try { generate_blocks(HARDFORK_436_TIME); generate_block(); set_expiration( db, trx ); - ACTORS((buyer)(seller)(borrower)(feedproducer)); + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; int64_t init_balance(1000000); transfer(committee_account, buyer_id, asset(init_balance)); transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); update_feed_producers( bitusd, {feedproducer.id} ); price_feed current_feed; @@ -66,6 +70,13 @@ BOOST_AUTO_TEST_CASE(issue_338) publish_feed( bitusd, feedproducer, current_feed ); // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; transfer(borrower, seller, bitusd.amount(1000)); BOOST_CHECK_EQUAL( 1000, call.debt.value ); @@ -78,11 +89,11 @@ BOOST_AUTO_TEST_CASE(issue_338) publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 1/10, mssp = 1/11 - // This order slightly below the call price will not be matched + // This order slightly below the call price will not be matched #606 limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(7), core.amount(59))->id; // This order above the MSSP will not be matched limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; - // This would match but is blocked by sell_low?! + // This would match but is blocked by sell_low?! #606 limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(60))->id; cancel_limit_order( sell_med(db) ); @@ -90,14 +101,14 @@ BOOST_AUTO_TEST_CASE(issue_338) cancel_limit_order( sell_low(db) ); // current implementation: an incoming limit order will be filled at the - // requested price + // requested price #338 BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) ); BOOST_CHECK_EQUAL( 993, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 60, get_balance(seller, core) ); BOOST_CHECK_EQUAL( 993, call.debt.value ); BOOST_CHECK_EQUAL( 14940, call.collateral.value ); - auto buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->id; + limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->id; // margin call takes precedence BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(60)) ); BOOST_CHECK_EQUAL( 986, get_balance(seller, bitusd) ); @@ -105,7 +116,7 @@ BOOST_AUTO_TEST_CASE(issue_338) BOOST_CHECK_EQUAL( 986, call.debt.value ); BOOST_CHECK_EQUAL( 14880, call.collateral.value ); - auto buy_med = create_sell_order(buyer, asset(105), bitusd.amount(10))->id; + limit_order_id_type buy_med = create_sell_order(buyer, asset(105), bitusd.amount(10))->id; // margin call takes precedence BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(70)) ); BOOST_CHECK_EQUAL( 979, get_balance(seller, bitusd) ); @@ -113,13 +124,108 @@ BOOST_AUTO_TEST_CASE(issue_338) BOOST_CHECK_EQUAL( 979, call.debt.value ); BOOST_CHECK_EQUAL( 14810, call.collateral.value ); - auto buy_high = create_sell_order(buyer, asset(115), bitusd.amount(10))->id; - // margin call still has precedence (!)) + limit_order_id_type buy_high = create_sell_order(buyer, asset(115), bitusd.amount(10))->id; + // margin call still has precedence (!) #625 BOOST_CHECK( !create_sell_order(seller, bitusd.amount(7), core.amount(77)) ); BOOST_CHECK_EQUAL( 972, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 267, get_balance(seller, core) ); BOOST_CHECK_EQUAL( 972, call.debt.value ); BOOST_CHECK_EQUAL( 14733, call.collateral.value ); + + cancel_limit_order( buy_high(db) ); + cancel_limit_order( buy_med(db) ); + cancel_limit_order( buy_low(db) ); + + // call with more usd + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(7700)) ); + BOOST_CHECK_EQUAL( 272, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 7967, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 272, call.debt.value ); + BOOST_CHECK_EQUAL( 7033, call.collateral.value ); + + // at this moment, collateralization of call is 7033 / 272 = 25.8 + // collateralization of call2 is 15500 / 1000 = 15.5 + // collateralization of call3 is 16000 / 1000 = 16 + + // call more, still matches with the first call order #343 + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(10), core.amount(110)) ); + BOOST_CHECK_EQUAL( 262, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 262, call.debt.value ); + BOOST_CHECK_EQUAL( 6923, call.collateral.value ); + + // at this moment, collateralization of call is 6923 / 262 = 26.4 + // collateralization of call2 is 15500 / 1000 = 15.5 + // collateralization of call3 is 16000 / 1000 = 16 + + // force settle + force_settle( seller, bitusd.amount(10) ); + BOOST_CHECK_EQUAL( 252, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 8077, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 262, call.debt.value ); + BOOST_CHECK_EQUAL( 6923, call.collateral.value ); + + // generate blocks to let the settle order execute + generate_blocks( HARDFORK_436_TIME + fc::days(2) ); + // call2 get settled #343 + BOOST_CHECK_EQUAL( 252, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 8177, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 262, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 6923, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 990, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15400, call2_id(db).collateral.value ); + + set_expiration( db, trx ); + update_feed_producers( usd_id(db), {feedproducer_id} ); + + // at this moment, collateralization of call is 8177 / 252 = 32.4 + // collateralization of call2 is 15400 / 990 = 15.5 + // collateralization of call3 is 16000 / 1000 = 16 + + // adjust price feed to get call2 into black swan territory, but not the first call order + current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id); + publish_feed( usd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 1/20, mssp = 1/22 + + // black swan event doesn't occur #649 + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + + // generate a block + generate_block(); + + set_expiration( db, trx ); + update_feed_producers( usd_id(db), {feedproducer_id} ); + + // adjust price feed back + current_feed.settlement_price = asset(1, usd_id) / asset(10, core_id); + publish_feed( usd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 1/10, mssp = 1/11 + + transfer(borrower2_id, seller_id, asset(1000, usd_id)); + transfer(borrower3_id, seller_id, asset(1000, usd_id)); + + // Re-create sell_low, slightly below the call price, will not be matched, will expire soon + sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time() )->id; + // This would match but is blocked by sell_low, it has an amount same as call's debt which will be full filled later + sell_med = create_sell_order(seller_id(db), asset(262, usd_id), asset(2620))->id; // 1/10 + // Another big order above sell_med, blocked + limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->id; // 1/10.1 + // Another small order above sell_med2, blocked + limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2 + + // generate a block, sell_low will expire + generate_block(); + BOOST_CHECK( db.find( sell_low ) == nullptr ); + + // #453 multiple order matching issue occurs + BOOST_CHECK( db.find( sell_med ) == nullptr ); // sell_med get filled + BOOST_CHECK( db.find( sell_med2 ) != nullptr ); // sell_med2 is still there + BOOST_CHECK( db.find( sell_med3 ) == nullptr ); // sell_med3 get filled + BOOST_CHECK( db.find( call_id ) == nullptr ); // the first call order get filled + BOOST_CHECK( db.find( call2_id ) == nullptr ); // the second call order get filled + BOOST_CHECK( db.find( call3_id ) != nullptr ); // the third call order is still there + + } FC_LOG_AND_RETHROW() } /*** From ceb480a66ad24ed49302a34d10385e8c0b87670c Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 10:55:50 +0000 Subject: [PATCH 02/29] Update tests to get around old feed expiration bug --- tests/tests/market_tests.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 9fed320473..e47be22b99 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -43,7 +43,7 @@ BOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture) */ BOOST_AUTO_TEST_CASE(issue_338_etc) { try { - generate_blocks(HARDFORK_436_TIME); + generate_blocks(HARDFORK_615_TIME); // get around Graphene issue #615 feed expiration bug generate_block(); set_expiration( db, trx ); @@ -165,8 +165,8 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) BOOST_CHECK_EQUAL( 262, call.debt.value ); BOOST_CHECK_EQUAL( 6923, call.collateral.value ); - // generate blocks to let the settle order execute - generate_blocks( HARDFORK_436_TIME + fc::days(2) ); + // generate blocks to let the settle order execute (price feed will expire after it) + generate_blocks( HARDFORK_615_TIME + fc::hours(25) ); // call2 get settled #343 BOOST_CHECK_EQUAL( 252, get_balance(seller_id, usd_id) ); BOOST_CHECK_EQUAL( 8177, get_balance(seller_id, core_id) ); @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) transfer(borrower3_id, seller_id, asset(1000, usd_id)); // Re-create sell_low, slightly below the call price, will not be matched, will expire soon - sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time() )->id; + sell_low = create_sell_order(seller_id(db), asset(7, usd_id), asset(59), db.head_block_time()+fc::seconds(300) )->id; // This would match but is blocked by sell_low, it has an amount same as call's debt which will be full filled later sell_med = create_sell_order(seller_id(db), asset(262, usd_id), asset(2620))->id; // 1/10 // Another big order above sell_med, blocked @@ -214,7 +214,8 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2 // generate a block, sell_low will expire - generate_block(); + BOOST_TEST_MESSAGE( "Expire sell_low" ); + generate_blocks( HARDFORK_615_TIME + fc::hours(26) ); BOOST_CHECK( db.find( sell_low ) == nullptr ); // #453 multiple order matching issue occurs From b2a86dd4eed8a7a4f602c4e5ae9709aae12a8f75 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 3 Feb 2018 05:29:12 +0000 Subject: [PATCH 03/29] Market engine improvements. For: * #338 Margin call order fills at price of matching limit_order * #343 Inconsistent sorting of call orders between matching against a limit order and a force settle order * #453 Multiple limit order and call order matching issue * #606 Undercollateralized short positions should be called regardless of asks --- libraries/chain/db_market.cpp | 226 +++++++++++++++++- libraries/chain/db_update.cpp | 3 + libraries/chain/hardfork.d/CORE_338.hf | 4 + libraries/chain/hardfork.d/CORE_343.hf | 5 + libraries/chain/hardfork.d/CORE_453.hf | 4 + libraries/chain/hardfork.d/CORE_606.hf | 4 + .../chain/include/graphene/chain/database.hpp | 2 + .../include/graphene/chain/market_object.hpp | 7 +- libraries/chain/market_evaluator.cpp | 6 +- 9 files changed, 250 insertions(+), 11 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_338.hf create mode 100644 libraries/chain/hardfork.d/CORE_343.hf create mode 100644 libraries/chain/hardfork.d/CORE_453.hf create mode 100644 libraries/chain/hardfork.d/CORE_606.hf diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index c43c84943a..9a62bf808f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -364,6 +364,154 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo return maybe_cull_small_order( *this, *updated_order_object ); } +bool database::apply_order_hf_201803(const limit_order_object& new_order_object, bool allow_black_swan) +{ + auto order_id = new_order_object.id; + asset_id_type sell_asset_id = new_order_object.sell_asset_id(); + asset_id_type recv_asset_id = new_order_object.receive_asset_id(); + + // We only need to check if the new order will match with others if it is at the front of the book + const auto& limit_price_idx = get_index_type().indices().get(); + auto limit_itr = limit_price_idx.lower_bound( boost::make_tuple( new_order_object.sell_price, order_id ) ); + if( limit_itr != limit_price_idx.begin() ) + { + --limit_itr; + if( limit_itr->sell_asset_id() == sell_asset_id && limit_itr->receive_asset_id() == recv_asset_id ) + return false; + } + + // Order matching should be in favor of the taker. + // When a new limit order is created, e.g. an ask, need to check if it will match the highest bid. + // We were checking call orders first. However, due to MSSR (maximum_short_squeeze_ratio), + // effective price of call orders may be lower than limit orders, so we should also check limit orders here. + + // Question: will a new limit order trigger a black swan event? + // + // 1. as of writing, it's possible due to the call-order-and-limit-order overlapping issue: + // https://github.com/bitshares/bitshares-core/issues/606 . + // when it happens, a call order can be very big but don't match with the opposite, + // even when price feed is too far away, further than swan price, + // if the new limit order is in the same direction with the call orders, it can eat up all the opposite, + // then the call order will lose support and trigger a black swan event. + // 2. after issue 606 is fixed, there will be no limit order on the opposite side "supporting" the call order, + // so a new order in the same direction with the call order won't trigger a black swan event. + // 3. calling is one direction. if the new limit order is on the opposite direction, + // no matter if matches with the call, it won't trigger a black swan event. + // + // Since it won't trigger a black swan, no need to check here. + + // currently we don't do cross-market (triangle) matching. + // the limit order will only match with a call order if meet all of these: + // 1. it's buying collateral, which means sell_asset is the MIA, receive_asset is the backing asset. + // 2. sell_asset is not a prediction market + // 3. sell_asset is not globally settled + // 4. sell_asset has a valid price feed + // 5. the call order doesn't have enough collateral + // 6. the limit order provided a good price + bool to_check_call_orders = false; + const asset_object& sell_asset = sell_asset_id( *this ); + //const asset_object& recv_asset = recv_asset_id( *this ); + const asset_bitasset_data_object* sell_abd = nullptr; + if( sell_asset.is_market_issued() ) + { + sell_abd = &sell_asset.bitasset_data( *this ); + if( sell_abd->options.short_backing_asset == recv_asset_id + && !sell_abd->is_prediction_market + && !sell_abd->has_settlement() + && !sell_abd->current_feed.settlement_price.is_null() ) + { + to_check_call_orders = true; + } + } + + // this is the opposite side + auto max_price = ~new_order_object.sell_price; + limit_itr = limit_price_idx.lower_bound( max_price.max() ); + auto limit_end = limit_price_idx.upper_bound( max_price ); + bool to_check_limit_orders = (limit_itr != limit_end); + + if( to_check_call_orders ) + { + // check if there are margin calls + const auto& call_price_idx = get_index_type().indices().get(); + auto call_min = price::min( recv_asset_id, sell_asset_id ); + price min_call_price = sell_abd->current_feed.max_short_squeeze_price(); + while( true ) + { + auto call_itr = call_price_idx.lower_bound( call_min ); + // there is this new limit order means there are short positions, so call_itr might be valid + const call_order_object& call_order = *call_itr; + price call_order_price = ~call_order.call_price; + if( call_order_price >= sell_abd->current_feed.settlement_price ) // has enough collateral + to_check_call_orders = false; + else + { + if( call_order_price < min_call_price ) // feed protected https://github.com/cryptonomex/graphene/issues/436 + call_order_price = min_call_price; + if( call_order_price > new_order_object.sell_price ) // new limit order is too far away, can't match + to_check_call_orders = false; + } + + if( !to_check_call_orders ) // finished checking call orders + break; + + if( to_check_limit_orders ) // need to check both calls and limits + { + // fill as many limit orders as possible + bool finished = false; + while( !finished && limit_itr != limit_end && call_order_price > ~limit_itr->sell_price ) + { + auto old_limit_itr = limit_itr; + ++limit_itr; + // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); + } + if( finished ) // if the new limit order is gone + { + to_check_limit_orders = false; // no need to check more limit orders as well + break; + } + if( limit_itr == limit_end ) // if no more limit order to check + to_check_limit_orders = false; // no need to check more limit orders as well + } + + // now fill the call order + auto match_result = match( new_order_object, call_order, call_order_price ); + + if( match_result != 2 ) // if the new limit order is gone + { + to_check_limit_orders = false; // no need to check more limit orders as well + break; + } + // else the call order should be gone, do thing here + + } // end while + + } // end check call orders + + if( to_check_limit_orders ) // still and only need to check limit orders + { + bool finished = false; + while( !finished && limit_itr != limit_end ) + { + auto old_limit_itr = limit_itr; + ++limit_itr; + // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); + } + } + + // TODO really need to find again? + const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); + if( updated_order_object == nullptr ) + return true; + + // before #555 we would have done maybe_cull_small_order() logic as a result of fill_order() being called by match() above + // however after #555 we need to get rid of small orders -- #555 hardfork defers logic that was done too eagerly before, and + // this is the point it's deferred to. + return maybe_cull_small_order( *this, *updated_order_object ); +} + /** * Matches the two orders, * @@ -422,6 +570,48 @@ int database::match( const limit_order_object& bid, const limit_order_object& as return match( bid, ask, match_price ); } +int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price ) +{ + FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); + FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() ); + FC_ASSERT( bid.for_sale > 0 && ask.debt > 0 && ask.collateral > 0 ); + + bool filled_limit = false; + bool filled_call = false; + + asset usd_for_sale = bid.amount_for_sale(); + asset usd_to_buy = ask.get_debt(); + + asset call_pays, call_receives, order_pays, order_receives; + if( usd_to_buy >= usd_for_sale ) + { // fill limit order + call_receives = usd_for_sale; + order_receives = usd_for_sale * match_price; // round down here, in favor of call order + call_pays = order_receives; + order_pays = usd_for_sale; + + filled_limit = true; + filled_call = ( usd_to_buy == usd_for_sale ); + } + else + { // fill call order + call_receives = usd_to_buy; + order_receives = usd_to_buy * match_price; // round down here, in favor of call order + call_pays = order_receives; + order_pays = usd_to_buy; + + filled_call = true; + } + + FC_ASSERT( filled_call || filled_limit ); + + int result = 0; + result |= fill_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker + result |= fill_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker + FC_ASSERT( result != 0 ); + return result; +} + asset database::match( const call_order_object& call, const force_settlement_object& settle, @@ -518,6 +708,11 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co FC_ASSERT( order.get_collateral().asset_id == pays.asset_id ); FC_ASSERT( order.get_collateral() >= pays ); + const asset_object& mia = receives.asset_id(*this); + FC_ASSERT( mia.is_market_issued() ); + + const auto& mia_bdo = mia.bitasset_data( *this ); + optional collateral_freed; modify( order, [&]( call_order_object& o ){ o.debt -= receives.amount; @@ -527,9 +722,10 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co collateral_freed = o.get_collateral(); o.collateral = 0; } + else if( head_block_time() > HARDFORK_CORE_343_TIME ) + o.call_price = price::call_price( o.get_debt(), o.get_collateral(), + mia_bdo.current_feed.maintenance_collateral_ratio ); }); - const asset_object& mia = receives.asset_id(*this); - assert( mia.is_market_issued() ); const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); @@ -643,8 +839,10 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan bool filled_limit = false; bool margin_called = false; + auto head_time = head_block_time(); while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end ) { + bool filled_limit_in_loop = false; bool filled_call = false; price match_price; asset usd_for_sale; @@ -659,12 +857,12 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan match_price.validate(); // would be margin called, but there is no matching order #436 - bool feed_protected = ( bitasset.current_feed.settlement_price > ~call_itr->call_price ); - if( feed_protected && (head_block_time() > HARDFORK_436_TIME) ) + if( ( head_time > HARDFORK_436_TIME ) + && ( bitasset.current_feed.settlement_price > ~call_itr->call_price ) ) return margin_called; // would be margin called, but there is no matching order - if( match_price > ~call_itr->call_price ) + if( head_time <= HARDFORK_CORE_606_TIME && match_price > ~call_itr->call_price ) return margin_called; /* @@ -702,6 +900,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan call_pays = order_receives; order_pays = usd_for_sale; + filled_limit_in_loop = true; filled_limit = true; filled_call = (usd_to_buy == usd_for_sale); } else { // fill call @@ -711,20 +910,31 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan order_pays = usd_to_buy; filled_call = true; - if( filled_limit ) + if( filled_limit && head_time <= HARDFORK_CORE_453_TIME ) wlog( "Multiple limit match problem (issue 338) occurred at block #${block}", ("block",head_block_num()) ); } FC_ASSERT( filled_call || filled_limit ); + FC_ASSERT( filled_call || filled_limit_in_loop ); auto old_call_itr = call_itr; - if( filled_call ) ++call_itr; + if( filled_call && head_time <= HARDFORK_CORE_343_TIME ) + ++call_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); + if( head_time > HARDFORK_CORE_343_TIME ) + call_itr = call_price_index.lower_bound( call_min ); auto old_limit_itr = limit_itr; auto next_limit_itr = std::next( limit_itr ); - if( filled_limit ) ++limit_itr; + if( head_time <= HARDFORK_CORE_453_TIME ) + { + if( filled_limit ) ++limit_itr; + } + else + { + if( filled_limit_in_loop ) ++limit_itr; + } // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker bool really_filled = fill_order(*old_limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); if( !filled_limit && really_filled ) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 43cafbbd99..cf2f1da86b 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -235,6 +235,9 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s highest = std::max( limit_itr->sell_price, settle_price ); } + // FIXME + // possible BUG: position with lowest call_price doesn't always have least collateral + // related to https://github.com/bitshares/bitshares-core/issues/343 auto least_collateral = call_itr->collateralization(); if( ~least_collateral >= highest ) { diff --git a/libraries/chain/hardfork.d/CORE_338.hf b/libraries/chain/hardfork.d/CORE_338.hf new file mode 100644 index 0000000000..bfeb3a6a9d --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_338.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #338 Fix "margin call order fills at price of matching limit_order" +#ifndef HARDFORK_CORE_338_TIME +#define HARDFORK_CORE_338_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/hardfork.d/CORE_343.hf b/libraries/chain/hardfork.d/CORE_343.hf new file mode 100644 index 0000000000..e6106fe6e6 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_343.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #343 +// Fix "Inconsistent sorting of call orders between matching against a limit order and a force settle order" +#ifndef HARDFORK_CORE_343_TIME +#define HARDFORK_CORE_343_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/hardfork.d/CORE_453.hf b/libraries/chain/hardfork.d/CORE_453.hf new file mode 100644 index 0000000000..5de4f952b0 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_453.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #453 Fix "Multiple limit order and call order matching issue" +#ifndef HARDFORK_CORE_453_TIME +#define HARDFORK_CORE_453_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/hardfork.d/CORE_606.hf b/libraries/chain/hardfork.d/CORE_606.hf new file mode 100644 index 0000000000..589529e946 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_606.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #606 Fix "Undercollateralized short positions should be called regardless of asks" +#ifndef HARDFORK_CORE_606_TIME +#define HARDFORK_CORE_606_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 133dc1c4b9..21cb4337e8 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -352,6 +352,7 @@ namespace graphene { namespace chain { * already on the books. */ bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); + bool apply_order_hf_201803(const limit_order_object& new_order_object, bool allow_black_swan = true); /** * Matches the two orders, @@ -367,6 +368,7 @@ namespace graphene { namespace chain { template int match( const limit_order_object& bid, const OrderType& ask, const price& match_price ); int match( const limit_order_object& bid, const limit_order_object& ask, const price& trade_price ); + int match( const limit_order_object& bid, const call_order_object& ask, const price& trade_price ); /// @return the amount of asset settled asset match(const call_order_object& call, const force_settlement_object& settle, diff --git a/libraries/chain/include/graphene/chain/market_object.hpp b/libraries/chain/include/graphene/chain/market_object.hpp index 4480566a71..fae32a0028 100644 --- a/libraries/chain/include/graphene/chain/market_object.hpp +++ b/libraries/chain/include/graphene/chain/market_object.hpp @@ -64,6 +64,8 @@ class limit_order_object : public abstract_object asset amount_for_sale()const { return asset( for_sale, sell_price.base.asset_id ); } asset amount_to_receive()const { return amount_for_sale() * sell_price; } + asset_id_type sell_asset_id()const { return sell_price.base.asset_id; } + asset_id_type receive_asset_id()const { return sell_price.quote.asset_id; } }; struct by_id; @@ -115,12 +117,13 @@ class call_order_object : public abstract_object asset get_debt()const { return asset( debt, debt_type() ); } asset amount_to_receive()const { return get_debt(); } asset_id_type debt_type()const { return call_price.quote.asset_id; } + asset_id_type collateral_type()const { return call_price.base.asset_id; } price collateralization()const { return get_collateral() / get_debt(); } account_id_type borrower; share_type collateral; ///< call_price.base.asset_id, access via get_collateral - share_type debt; ///< call_price.quote.asset_id, access via get_collateral - price call_price; ///< Debt / Collateral + share_type debt; ///< call_price.quote.asset_id, access via get_debt + price call_price; ///< Collateral / Debt pair get_market()const { diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 4e3cf91996..060c50618f 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -111,7 +111,11 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o obj.deferred_paid_fee = _deferred_paid_fee; }); limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it - bool filled = db().apply_order(new_order_object); + bool filled; + if( db().head_block_time() <= HARDFORK_CORE_338_TIME ) + filled = db().apply_order( new_order_object ); + else + filled = db().apply_order_hf_201803( new_order_object ); FC_ASSERT( !op.fill_or_kill || filled ); From adebe1e5483b676c92c5491bf7a11309b219c9d4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 5 Feb 2018 01:36:38 +0000 Subject: [PATCH 04/29] Refactor and fix new limit order matching. #625 --- libraries/chain/db_market.cpp | 117 +++++++----------- libraries/chain/hardfork.d/CORE_625.hf | 4 + .../chain/include/graphene/chain/database.hpp | 2 +- libraries/chain/market_evaluator.cpp | 6 +- 4 files changed, 51 insertions(+), 78 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_625.hf diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 9a62bf808f..6a0c11abec 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -313,7 +313,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order ) return false; } -bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) +bool database::apply_order_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan) { auto order_id = new_order_object.id; const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id); @@ -364,7 +364,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo return maybe_cull_small_order( *this, *updated_order_object ); } -bool database::apply_order_hf_201803(const limit_order_object& new_order_object, bool allow_black_swan) +bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan) { auto order_id = new_order_object.id; asset_id_type sell_asset_id = new_order_object.sell_asset_id(); @@ -380,10 +380,15 @@ bool database::apply_order_hf_201803(const limit_order_object& new_order_object, return false; } + // this is the opposite side (on the book) + auto max_price = ~new_order_object.sell_price; + limit_itr = limit_price_idx.lower_bound( max_price.max() ); + auto limit_end = limit_price_idx.upper_bound( max_price ); + // Order matching should be in favor of the taker. // When a new limit order is created, e.g. an ask, need to check if it will match the highest bid. // We were checking call orders first. However, due to MSSR (maximum_short_squeeze_ratio), - // effective price of call orders may be lower than limit orders, so we should also check limit orders here. + // effective price of call orders may be worse than limit orders, so we should also check limit orders here. // Question: will a new limit order trigger a black swan event? // @@ -408,10 +413,11 @@ bool database::apply_order_hf_201803(const limit_order_object& new_order_object, // 4. sell_asset has a valid price feed // 5. the call order doesn't have enough collateral // 6. the limit order provided a good price + bool to_check_call_orders = false; const asset_object& sell_asset = sell_asset_id( *this ); - //const asset_object& recv_asset = recv_asset_id( *this ); const asset_bitasset_data_object* sell_abd = nullptr; + price call_match_price; if( sell_asset.is_market_issued() ) { sell_abd = &sell_asset.bitasset_data( *this ); @@ -420,88 +426,51 @@ bool database::apply_order_hf_201803(const limit_order_object& new_order_object, && !sell_abd->has_settlement() && !sell_abd->current_feed.settlement_price.is_null() ) { - to_check_call_orders = true; + call_match_price = ~sell_abd->current_feed.max_short_squeeze_price(); + if( ~new_order_object.sell_price <= call_match_price ) // new limit order price is good enough to match a call + to_check_call_orders = true; } } - // this is the opposite side - auto max_price = ~new_order_object.sell_price; - limit_itr = limit_price_idx.lower_bound( max_price.max() ); - auto limit_end = limit_price_idx.upper_bound( max_price ); - bool to_check_limit_orders = (limit_itr != limit_end); - + bool finished = false; // whether the new order is gone if( to_check_call_orders ) { - // check if there are margin calls - const auto& call_price_idx = get_index_type().indices().get(); - auto call_min = price::min( recv_asset_id, sell_asset_id ); - price min_call_price = sell_abd->current_feed.max_short_squeeze_price(); - while( true ) - { - auto call_itr = call_price_idx.lower_bound( call_min ); - // there is this new limit order means there are short positions, so call_itr might be valid - const call_order_object& call_order = *call_itr; - price call_order_price = ~call_order.call_price; - if( call_order_price >= sell_abd->current_feed.settlement_price ) // has enough collateral - to_check_call_orders = false; - else - { - if( call_order_price < min_call_price ) // feed protected https://github.com/cryptonomex/graphene/issues/436 - call_order_price = min_call_price; - if( call_order_price > new_order_object.sell_price ) // new limit order is too far away, can't match - to_check_call_orders = false; - } - - if( !to_check_call_orders ) // finished checking call orders - break; - - if( to_check_limit_orders ) // need to check both calls and limits - { - // fill as many limit orders as possible - bool finished = false; - while( !finished && limit_itr != limit_end && call_order_price > ~limit_itr->sell_price ) - { - auto old_limit_itr = limit_itr; - ++limit_itr; - // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. - finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); - } - if( finished ) // if the new limit order is gone - { - to_check_limit_orders = false; // no need to check more limit orders as well - break; - } - if( limit_itr == limit_end ) // if no more limit order to check - to_check_limit_orders = false; // no need to check more limit orders as well - } - - // now fill the call order - auto match_result = match( new_order_object, call_order, call_order_price ); - - if( match_result != 2 ) // if the new limit order is gone - { - to_check_limit_orders = false; // no need to check more limit orders as well - break; - } - // else the call order should be gone, do thing here - - } // end while - - } // end check call orders - - if( to_check_limit_orders ) // still and only need to check limit orders - { - bool finished = false; - while( !finished && limit_itr != limit_end ) + // check limit orders first, match the ones with better price in comparison to call orders + while( !finished && limit_itr != limit_end && limit_itr->sell_price > call_match_price ) { auto old_limit_itr = limit_itr; ++limit_itr; // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); } + + if( !finished ) + { + // check if there are margin calls + const auto& call_price_idx = get_index_type().indices().get(); + auto call_min = price::min( recv_asset_id, sell_asset_id ); + auto call_itr = call_price_idx.lower_bound( call_min ); + // feed protected https://github.com/cryptonomex/graphene/issues/436 + auto call_end = call_price_idx.lower_bound( ~sell_abd->current_feed.settlement_price ); + while( !finished && call_itr != call_end ) + { + auto old_call_itr = call_itr; + ++call_itr; // would be safe, since we'll end the loop if a call order is partially matched + // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + finished = ( match( new_order_object, *old_call_itr, call_match_price ) != 2 ); + } + } + } + + // still need to check limit orders + while( !finished && limit_itr != limit_end ) + { + auto old_limit_itr = limit_itr; + ++limit_itr; + // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + finished = ( match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 ); } - // TODO really need to find again? const limit_order_object* updated_order_object = find< limit_order_object >( order_id ); if( updated_order_object == nullptr ) return true; @@ -911,7 +880,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan filled_call = true; if( filled_limit && head_time <= HARDFORK_CORE_453_TIME ) - wlog( "Multiple limit match problem (issue 338) occurred at block #${block}", ("block",head_block_num()) ); + wlog( "Multiple limit match problem (issue 453) occurred at block #${block}", ("block",head_block_num()) ); } FC_ASSERT( filled_call || filled_limit ); diff --git a/libraries/chain/hardfork.d/CORE_625.hf b/libraries/chain/hardfork.d/CORE_625.hf new file mode 100644 index 0000000000..126c2e2ebc --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_625.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #625 Fix "Potential erratic order matching issue involving margin call orders" +#ifndef HARDFORK_CORE_625_TIME +#define HARDFORK_CORE_625_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 21cb4337e8..59c946ba21 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -351,8 +351,8 @@ namespace graphene { namespace chain { * This function takes a new limit order, and runs the markets attempting to match it with existing orders * already on the books. */ + bool apply_order_before_hardfork_625(const limit_order_object& new_order_object, bool allow_black_swan = true); bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); - bool apply_order_hf_201803(const limit_order_object& new_order_object, bool allow_black_swan = true); /** * Matches the two orders, diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 060c50618f..f111d4661f 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -112,10 +112,10 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o }); limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it bool filled; - if( db().head_block_time() <= HARDFORK_CORE_338_TIME ) - filled = db().apply_order( new_order_object ); + if( db().head_block_time() <= HARDFORK_CORE_625_TIME ) + filled = db().apply_order_before_hardfork_625( new_order_object ); else - filled = db().apply_order_hf_201803( new_order_object ); + filled = db().apply_order( new_order_object ); FC_ASSERT( !op.fill_or_kill || filled ); From c3d0accc771f5ffde6cf0c74875d9122363a8c53 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 11:32:55 +0000 Subject: [PATCH 05/29] Minor tweak for fill_order(call_order_object) --- libraries/chain/db_market.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 6a0c11abec..be6462d5af 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -680,8 +680,6 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co const asset_object& mia = receives.asset_id(*this); FC_ASSERT( mia.is_market_issued() ); - const auto& mia_bdo = mia.bitasset_data( *this ); - optional collateral_freed; modify( order, [&]( call_order_object& o ){ o.debt -= receives.amount; @@ -693,7 +691,7 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co } else if( head_block_time() > HARDFORK_CORE_343_TIME ) o.call_price = price::call_price( o.get_debt(), o.get_collateral(), - mia_bdo.current_feed.maintenance_collateral_ratio ); + mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); }); const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this); From ea12aa834b9eca69b32ff73e80cc70008e3c8283 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 23:09:07 +0000 Subject: [PATCH 06/29] Change hard fork to occur at maintenance interval --- libraries/chain/db_maint.cpp | 41 ++++++++++++++++++++++++++++ libraries/chain/db_market.cpp | 14 ++++++---- libraries/chain/market_evaluator.cpp | 2 +- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 9e9ac0f349..6c00f89b25 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -766,6 +766,38 @@ void database::process_bids( const asset_bitasset_data_object& bad ) _cancel_bids_and_revive_mpa( to_revive, bad ); } +void update_and_match_call_orders( database& db ) +{ + // Update call_price + asset_id_type current_asset; + const asset_bitasset_data_object* abd = nullptr; + // by_collateral index won't change after call_price updated, so it's safe to iterate + for( const auto& call_obj : db.get_index_type().indices().get() ) + { + if( current_asset != call_obj.debt_type() ) // debt type won't be asset_id_type(), abd will always get initialized + { + current_asset = call_obj.debt_type(); + abd = ¤t_asset(db).bitasset_data(db); + } + if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe + continue; + db.modify( call_obj, [&]( call_order_object& call ) { + call.call_price = price::call_price( call.get_debt(), call.get_collateral(), + abd->current_feed.maintenance_collateral_ratio ); + }); + } + // Match call orders + const auto& asset_idx = db.get_index_type().indices().get(); + auto itr = asset_idx.lower_bound( true /** market issued */ ); + while( itr != asset_idx.end() ) + { + const asset_object& a = *itr; + ++itr; + // be here, next_maintenance_time should have been updated already + db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker + } +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { const auto& gpo = get_global_properties(); @@ -917,11 +949,20 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g if( (dgpo.next_maintenance_time < HARDFORK_613_TIME) && (next_maintenance_time >= HARDFORK_613_TIME) ) deprecate_annual_members(*this); + // To reset call_price of all call orders, then match by new rule + bool to_update_and_match_call_orders = false; + if( (dgpo.next_maintenance_time <= HARDFORK_CORE_343_TIME) && (next_maintenance_time > HARDFORK_CORE_343_TIME) ) + to_update_and_match_call_orders = true; + modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) { d.next_maintenance_time = next_maintenance_time; d.accounts_registered_this_interval = 0; }); + // We need to do it after updated next_maintenance_time, to apply new rules here + if( to_update_and_match_call_orders ) + update_and_match_call_orders(*this); + // Reset all BitAsset force settlement volumes to zero for( const auto& d : get_index_type().indices() ) { diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index be6462d5af..9ee7749869 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -457,6 +457,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo auto old_call_itr = call_itr; ++call_itr; // would be safe, since we'll end the loop if a call order is partially matched // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. + // assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here. finished = ( match( new_order_object, *old_call_itr, call_match_price ) != 2 ); } } @@ -689,7 +690,7 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co collateral_freed = o.get_collateral(); o.collateral = 0; } - else if( head_block_time() > HARDFORK_CORE_343_TIME ) + else if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_343_TIME ) o.call_price = price::call_price( o.get_debt(), o.get_collateral(), mia.bitasset_data(*this).current_feed.maintenance_collateral_ratio ); }); @@ -807,6 +808,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan bool margin_called = false; auto head_time = head_block_time(); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end ) { bool filled_limit_in_loop = false; @@ -829,7 +831,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan return margin_called; // would be margin called, but there is no matching order - if( head_time <= HARDFORK_CORE_606_TIME && match_price > ~call_itr->call_price ) + if( maint_time <= HARDFORK_CORE_606_TIME && match_price > ~call_itr->call_price ) return margin_called; /* @@ -877,7 +879,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan order_pays = usd_to_buy; filled_call = true; - if( filled_limit && head_time <= HARDFORK_CORE_453_TIME ) + if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) wlog( "Multiple limit match problem (issue 453) occurred at block #${block}", ("block",head_block_num()) ); } @@ -885,16 +887,16 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan FC_ASSERT( filled_call || filled_limit_in_loop ); auto old_call_itr = call_itr; - if( filled_call && head_time <= HARDFORK_CORE_343_TIME ) + if( filled_call && maint_time <= HARDFORK_CORE_343_TIME ) ++call_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); - if( head_time > HARDFORK_CORE_343_TIME ) + if( maint_time > HARDFORK_CORE_343_TIME ) call_itr = call_price_index.lower_bound( call_min ); auto old_limit_itr = limit_itr; auto next_limit_itr = std::next( limit_itr ); - if( head_time <= HARDFORK_CORE_453_TIME ) + if( maint_time <= HARDFORK_CORE_453_TIME ) { if( filled_limit ) ++limit_itr; } diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index f111d4661f..03ab50f7a1 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -112,7 +112,7 @@ object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_o }); limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it bool filled; - if( db().head_block_time() <= HARDFORK_CORE_625_TIME ) + if( db().get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_625_TIME ) filled = db().apply_order_before_hardfork_625( new_order_object ); else filled = db().apply_order( new_order_object ); From 0b6bbca4be3b0b94dd086cc45bab15b96743aef5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 05:05:26 +0000 Subject: [PATCH 07/29] Update black swan event detection for #338 --- libraries/chain/db_maint.cpp | 2 ++ libraries/chain/db_market.cpp | 2 ++ libraries/chain/db_update.cpp | 42 +++++++++++++++++++++-------------- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 6c00f89b25..c6be38b842 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -769,6 +769,7 @@ void database::process_bids( const asset_bitasset_data_object& bad ) void update_and_match_call_orders( database& db ) { // Update call_price + wlog( "Updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); asset_id_type current_asset; const asset_bitasset_data_object* abd = nullptr; // by_collateral index won't change after call_price updated, so it's safe to iterate @@ -796,6 +797,7 @@ void update_and_match_call_orders( database& db ) // be here, next_maintenance_time should have been updated already db.check_call_orders( a, true, false ); // allow black swan, and call orders are taker } + wlog( "Done updating all call orders for hardfork core-343 at block ${n}", ("n",db.head_block_num()) ); } void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 9ee7749869..db97f8d02a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -402,6 +402,8 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // so a new order in the same direction with the call order won't trigger a black swan event. // 3. calling is one direction. if the new limit order is on the opposite direction, // no matter if matches with the call, it won't trigger a black swan event. + // (if a match at MSSP caused a black swan event, it means the call order is already undercollateralized, + // which should trigger a black swan event earlier.) // // Since it won't trigger a black swan, no need to check here. diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index cf2f1da86b..d5e7e520b4 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -209,6 +209,20 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); + auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); + auto call_itr = call_price_index.lower_bound( call_min ); + auto call_end = call_price_index.upper_bound( call_max ); + + if( call_itr == call_end ) return false; // no call orders + + price highest = settle_price; + + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + if( maint_time > HARDFORK_CORE_338_TIME ) + // due to #338, we won't check for black swan on incoming limit order, so need to check with MSSP here + highest = bitasset.current_feed.max_short_squeeze_price(); + const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); @@ -217,41 +231,35 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s // stop when limit orders are selling too little USD for too much CORE auto lowest_possible_bid = price::min( mia.id, bitasset.options.short_backing_asset ); - assert( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); + FC_ASSERT( highest_possible_bid.base.asset_id == lowest_possible_bid.base.asset_id ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( highest_possible_bid ); auto limit_end = limit_price_index.upper_bound( lowest_possible_bid ); - auto call_min = price::min( bitasset.options.short_backing_asset, mia.id ); - auto call_max = price::max( bitasset.options.short_backing_asset, mia.id ); - auto call_itr = call_price_index.lower_bound( call_min ); - auto call_end = call_price_index.upper_bound( call_max ); - - if( call_itr == call_end ) return false; // no call orders - - price highest = settle_price; if( limit_itr != limit_end ) { - assert( settle_price.base.asset_id == limit_itr->sell_price.base.asset_id ); - highest = std::max( limit_itr->sell_price, settle_price ); + FC_ASSERT( highest.base.asset_id == limit_itr->sell_price.base.asset_id ); + highest = std::max( limit_itr->sell_price, highest ); } - // FIXME - // possible BUG: position with lowest call_price doesn't always have least collateral - // related to https://github.com/bitshares/bitshares-core/issues/343 auto least_collateral = call_itr->collateralization(); if( ~least_collateral >= highest ) { + wdump( (*call_itr) ); elog( "Black Swan detected: \n" " Least collateralized call: ${lc} ${~lc}\n" // " Highest Bid: ${hb} ${~hb}\n" - " Settle Price: ${sp} ${~sp}\n" - " Max: ${h} ${~h}\n", + " Settle Price: ${~sp} ${sp}\n" + " Max: ${~h} ${h}\n", ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) ("h",highest.to_real())("~h",(~highest).to_real()) ); FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); - globally_settle_asset(mia, ~least_collateral ); + if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) + // globol settle at feed price if possible + globally_settle_asset(mia, settle_price ); + else + globally_settle_asset(mia, ~least_collateral ); return true; } return false; From 8005788d96589b8966376e10f04018bf0fb542ed Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 05:09:47 +0000 Subject: [PATCH 08/29] Fix typo --- libraries/chain/db_update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index d5e7e520b4..0399f55cd8 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -256,7 +256,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s ("h",highest.to_real())("~h",(~highest).to_real()) ); FC_ASSERT( enable_black_swan, "Black swan was detected during a margin update which is not allowed to trigger a blackswan" ); if( maint_time > HARDFORK_CORE_338_TIME && ~least_collateral <= settle_price ) - // globol settle at feed price if possible + // global settle at feed price if possible globally_settle_asset(mia, settle_price ); else globally_settle_asset(mia, ~least_collateral ); From 3f699c117889a8b17d1143d7b2bd9d24d2eb0822 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 05:19:15 +0000 Subject: [PATCH 09/29] Skip checking call orders on manually cancel #606 --- libraries/chain/market_evaluator.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 03ab50f7a1..e156792de1 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -142,10 +142,13 @@ asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& d.cancel_limit_order(*_order, false /* don't create a virtual op*/); - // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. - // Do I need to check calls in both assets? - d.check_call_orders(base_asset(d)); - d.check_call_orders(quote_asset(d)); + if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_606_TIME ) + { + // Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book. + // Do I need to check calls in both assets? + d.check_call_orders(base_asset(d)); + d.check_call_orders(quote_asset(d)); + } return refunded; } FC_CAPTURE_AND_RETHROW( (o) ) } From 29925b12b4716b67c818660743fc3ff14a4f9f9d Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 05:25:29 +0000 Subject: [PATCH 10/29] Skip checking calls on limit order expiration #606 --- libraries/chain/db_update.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 0399f55cd8..72ead5e172 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -268,20 +268,25 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s void database::clear_expired_orders() { try { //Cancel expired limit orders + auto head_time = head_block_time(); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; auto& limit_index = get_index_type().indices().get(); - while( !limit_index.empty() && limit_index.begin()->expiration <= head_block_time() ) + while( !limit_index.empty() && limit_index.begin()->expiration <= head_time ) { const limit_order_object& order = *limit_index.begin(); auto base_asset = order.sell_price.base.asset_id; auto quote_asset = order.sell_price.quote.asset_id; cancel_limit_order( order ); - // check call orders - // Comments below are copied from limit_order_cancel_evaluator::do_apply(...) - // Possible optimization: order can be called by cancelling a limit order - // if the canceled order was at the top of the book. - // Do I need to check calls in both assets? - check_call_orders( base_asset( *this ) ); - check_call_orders( quote_asset( *this ) ); + if( maint_time <= HARDFORK_CORE_606_TIME ) + { + // check call orders + // Comments below are copied from limit_order_cancel_evaluator::do_apply(...) + // Possible optimization: order can be called by cancelling a limit order + // if the canceled order was at the top of the book. + // Do I need to check calls in both assets? + check_call_orders( base_asset( *this ) ); + check_call_orders( quote_asset( *this ) ); + } } //Process expired force settlement orders @@ -341,7 +346,7 @@ void database::clear_expired_orders() } // Has this order not reached its settlement date? - if( order.settlement_date > head_block_time() ) + if( order.settlement_date > head_time ) { if( next_asset() ) { From 462cf57ff7abf97ae556cc5adc14372683c54c9c Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 13 Feb 2018 21:31:23 +0000 Subject: [PATCH 11/29] Rename fill_order funtions to indicate order types --- libraries/chain/db_market.cpp | 28 +++++++++---------- .../chain/include/graphene/chain/database.hpp | 12 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index db97f8d02a..dcbc710d32 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -73,7 +73,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett collateral_gathered += pays; const auto& order = *call_itr; ++call_itr; - FC_ASSERT( fill_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker + FC_ASSERT( fill_call_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker } modify( bitasset, [&]( asset_bitasset_data_object& obj ){ @@ -528,11 +528,11 @@ int database::match( const limit_order_object& usd, const OrderType& core, const core_pays == core.amount_for_sale() ); int result = 0; - result |= fill_order( usd, usd_pays, usd_receives, false, match_price, false ); // although this function is a template, + result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // although this function is a template, // right now it only matches one limit order // with another limit order, // the first param is a new order, thus taker - result |= fill_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker + result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker assert( result != 0 ); return result; } @@ -578,8 +578,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask FC_ASSERT( filled_call || filled_limit ); int result = 0; - result |= fill_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker - result |= fill_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker + result |= fill_limit_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker + result |= fill_call_order( ask, call_pays, call_receives, match_price, true ) << 1; // the call order is maker FC_ASSERT( result != 0 ); return result; } @@ -613,13 +613,13 @@ asset database::match( const call_order_object& call, assert( settle_pays == settle_for_sale || call_receives == call.get_debt() ); - fill_order( call, call_pays, call_receives, fill_price, true ); // call order is maker - fill_order( settle, settle_pays, settle_receives, fill_price, false ); // force settlement order is taker + fill_call_order( call, call_pays, call_receives, fill_price, true ); // call order is maker + fill_settle_order( settle, settle_pays, settle_receives, fill_price, false ); // force settlement order is taker return call_receives; } FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } -bool database::fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, +bool database::fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, const price& fill_price, const bool is_maker ) { try { cull_if_small |= (head_block_time() < HARDFORK_555_TIME); @@ -672,8 +672,8 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c } FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } -bool database::fill_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ) +bool database::fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, + const price& fill_price, const bool is_maker ) { try { //idump((pays)(receives)(order)); FC_ASSERT( order.get_debt().asset_id == receives.asset_id ); @@ -731,8 +731,8 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co return collateral_freed.valid(); } FC_CAPTURE_AND_RETHROW( (order)(pays)(receives) ) } -bool database::fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ) +bool database::fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, + const price& fill_price, const bool is_maker ) { try { bool filled = false; @@ -892,7 +892,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan if( filled_call && maint_time <= HARDFORK_CORE_343_TIME ) ++call_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker - fill_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); + fill_call_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); if( maint_time > HARDFORK_CORE_343_TIME ) call_itr = call_price_index.lower_bound( call_min ); @@ -907,7 +907,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan if( filled_limit_in_loop ) ++limit_itr; } // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker - bool really_filled = fill_order(*old_limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); + bool really_filled = fill_limit_order(*old_limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); if( !filled_limit && really_filled ) { wlog( "Cull_small issue occurred at block #${block}", ("block",head_block_num()) ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 59c946ba21..e2a13e19f6 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -380,12 +380,12 @@ namespace graphene { namespace chain { /** * @return true if the order was completely filled and thus freed. */ - bool fill_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, - const price& fill_price, const bool is_maker ); - bool fill_order( const call_order_object& order, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ); - bool fill_order( const force_settlement_object& settle, const asset& pays, const asset& receives, - const price& fill_price, const bool is_maker ); + bool fill_limit_order( const limit_order_object& order, const asset& pays, const asset& receives, bool cull_if_small, + const price& fill_price, const bool is_maker ); + bool fill_call_order( const call_order_object& order, const asset& pays, const asset& receives, + const price& fill_price, const bool is_maker ); + bool fill_settle_order( const force_settlement_object& settle, const asset& pays, const asset& receives, + const price& fill_price, const bool is_maker ); bool check_call_orders( const asset_object& mia, bool enable_black_swan = true, bool for_new_limit_order = false ); From 3e3c457870be3725f36b2b79facf016f65931eff Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 13 Feb 2018 21:49:19 +0000 Subject: [PATCH 12/29] Remove unnecessary template match() funtion --- libraries/chain/db_market.cpp | 19 +++++-------------- .../chain/include/graphene/chain/database.hpp | 12 +++++------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index dcbc710d32..60d763df22 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -485,17 +485,16 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo } /** - * Matches the two orders, + * Matches the two orders, the first parameter is taker, the second is maker. * * @return a bit field indicating which orders were filled (and thus removed) * * 0 - no orders were matched - * 1 - bid was filled - * 2 - ask was filled + * 1 - taker was filled + * 2 - maker was filled * 3 - both were filled */ -template -int database::match( const limit_order_object& usd, const OrderType& core, const price& match_price ) +int database::match( const limit_order_object& usd, const limit_order_object& core, const price& match_price ) { assert( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); assert( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); @@ -528,20 +527,12 @@ int database::match( const limit_order_object& usd, const OrderType& core, const core_pays == core.amount_for_sale() ); int result = 0; - result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // although this function is a template, - // right now it only matches one limit order - // with another limit order, - // the first param is a new order, thus taker + result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // the first param is taker result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker assert( result != 0 ); return result; } -int database::match( const limit_order_object& bid, const limit_order_object& ask, const price& match_price ) -{ - return match( bid, ask, match_price ); -} - int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price ) { FC_ASSERT( bid.sell_asset_id() == ask.debt_type() ); diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index e2a13e19f6..bb4a432dd4 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -355,20 +355,18 @@ namespace graphene { namespace chain { bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true); /** - * Matches the two orders, + * Matches the two orders, the first parameter is taker, the second is maker. * * @return a bit field indicating which orders were filled (and thus removed) * * 0 - no orders were matched - * 1 - bid was filled - * 2 - ask was filled + * 1 - taker was filled + * 2 - maker was filled * 3 - both were filled */ ///@{ - template - int match( const limit_order_object& bid, const OrderType& ask, const price& match_price ); - int match( const limit_order_object& bid, const limit_order_object& ask, const price& trade_price ); - int match( const limit_order_object& bid, const call_order_object& ask, const price& trade_price ); + int match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price ); + int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price ); /// @return the amount of asset settled asset match(const call_order_object& call, const force_settlement_object& settle, From dbb2dce43fd6a0959e9b0ecc906c7cd7db8a7ac5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 15 Feb 2018 11:52:20 +0000 Subject: [PATCH 13/29] Update comments, remove code that is commented out --- libraries/chain/db_market.cpp | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 60d763df22..b780a30772 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -413,7 +413,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // 2. sell_asset is not a prediction market // 3. sell_asset is not globally settled // 4. sell_asset has a valid price feed - // 5. the call order doesn't have enough collateral + // 5. the call order's collateral ratio is below MCR // 6. the limit order provided a good price bool to_check_call_orders = false; @@ -818,29 +818,15 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan match_price.validate(); - // would be margin called, but there is no matching order #436 + // Feed protected (don't call if CR>MCR) https://github.com/cryptonomex/graphene/issues/436 if( ( head_time > HARDFORK_436_TIME ) && ( bitasset.current_feed.settlement_price > ~call_itr->call_price ) ) return margin_called; - // would be margin called, but there is no matching order + // Old rule: margin calls can only buy high https://github.com/bitshares/bitshares-core/issues/606 if( maint_time <= HARDFORK_CORE_606_TIME && match_price > ~call_itr->call_price ) return margin_called; - /* - if( feed_protected ) - { - ilog( "Feed protected margin call executing (HARDFORK_436_TIME not here yet)" ); - idump( (*call_itr) ); - idump( (*limit_itr) ); - } - */ - - // idump((*call_itr)); - // idump((*limit_itr)); - - // ilog( "match_price <= ~call_itr->call_price performing a margin call" ); - margin_called = true; auto usd_to_buy = call_itr->get_debt(); From 145b0ada8f0592f00042df9578672d28eaf473f1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 15 Feb 2018 12:27:59 +0000 Subject: [PATCH 14/29] Apply_order(): also match calls with CR == MCR. Be consistent with check_call_orders(). --- libraries/chain/db_market.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index b780a30772..0ee0b736f6 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -413,7 +413,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo // 2. sell_asset is not a prediction market // 3. sell_asset is not globally settled // 4. sell_asset has a valid price feed - // 5. the call order's collateral ratio is below MCR + // 5. the call order's collateral ratio is below or equals to MCR // 6. the limit order provided a good price bool to_check_call_orders = false; @@ -453,7 +453,7 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo auto call_min = price::min( recv_asset_id, sell_asset_id ); auto call_itr = call_price_idx.lower_bound( call_min ); // feed protected https://github.com/cryptonomex/graphene/issues/436 - auto call_end = call_price_idx.lower_bound( ~sell_abd->current_feed.settlement_price ); + auto call_end = call_price_idx.upper_bound( ~sell_abd->current_feed.settlement_price ); while( !finished && call_itr != call_end ) { auto old_call_itr = call_itr; From c049b53b1cc76c4fd8adcba2116e57f663afcd35 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 7 Apr 2018 07:25:29 -0400 Subject: [PATCH 15/29] Remove cull_small_test for easier merging --- tests/tests/market_tests.cpp | 73 ------------------------------------ 1 file changed, 73 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index e47be22b99..ead751a1cf 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -229,78 +229,5 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) } FC_LOG_AND_RETHROW() } -/*** - * reproduce check_call_orders cull_small issue - */ -BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) -{ try { // matching a limit order with call order - generate_block(); - - set_expiration( db, trx ); - - ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); - - const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); - const auto& core = asset_id_type()(db); - asset_id_type usd_id = bitusd.id; - asset_id_type core_id = core.id; - - int64_t init_balance(1000000); - - transfer(committee_account, buyer_id, asset(init_balance)); - transfer(committee_account, borrower_id, asset(init_balance)); - transfer(committee_account, borrower2_id, asset(init_balance)); - transfer(committee_account, borrower3_id, asset(init_balance)); - transfer(committee_account, borrower4_id, asset(init_balance)); - update_feed_producers( bitusd, {feedproducer.id} ); - - price_feed current_feed; - current_feed.maintenance_collateral_ratio = 1750; - current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 ); - publish_feed( bitusd, feedproducer, current_feed ); - // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700 - const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1)); - call_order_id_type call_id = call.id; - // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700 - const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500)); - call_order_id_type call2_id = call2.id; - // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700 - const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500)); - call_order_id_type call3_id = call3.id; - transfer(borrower, seller, bitusd.amount(10)); - transfer(borrower2, seller, bitusd.amount(100000)); - transfer(borrower3, seller, bitusd.amount(100000)); - - BOOST_CHECK_EQUAL( 10, call.debt.value ); - BOOST_CHECK_EQUAL( 1, call.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call2.debt.value ); - BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call3.debt.value ); - BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); - - BOOST_CHECK_EQUAL( 200010, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); - - // adjust price feed to get call_order into margin call territory - current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10); - publish_feed( bitusd, feedproducer, current_feed ); - // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE - - // This would match with call at price 11 USD / 1 CORE, but call only owes 10 USD, - // so the seller will pay 10 USD but get nothing. - // The remaining 1 USD is too little to get any CORE, so the limit order will be cancelled - BOOST_CHECK( !create_sell_order(seller, bitusd.amount(11), core.amount(1)) ); - BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); // the seller paid 10 USD - BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From e5fc5d4a2313ae868c5966646e88d67c710e091a Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 14:09:34 +0000 Subject: [PATCH 16/29] Test case for #338 #343 #453 #606 #625 #649 fixes --- tests/tests/market_tests.cpp | 251 +++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index ead751a1cf..e1d0fb0f8b 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -229,5 +229,256 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) } FC_LOG_AND_RETHROW() } +/*** + * Fixed bitshares-core issue #338 #343 #606 #625 #649 + */ +BOOST_AUTO_TEST_CASE(hardfork_core_338_test) +{ try { + generate_blocks(HARDFORK_CORE_338_TIME); // assume all hard forks occur at same time + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This sell order above MSSP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(90), bitusd.amount(10))->id; + // This buy order at MSSP will be matched only if no margin call (margin call takes precedence) + limit_order_id_type buy_med = create_sell_order(buyer, asset(110), bitusd.amount(10))->id; + // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence) + limit_order_id_type buy_high = create_sell_order(buyer, asset(111), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) ); + + // This order slightly below the call price will be matched: #606 fixed + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(5900) ) ); + + // firstly it will match with buy_high, at buy_high's price: #625 fixed + BOOST_CHECK( !db.find( buy_high ) ); + BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 110 ); + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 90 ); + + // buy_high pays 111 CORE, receives 10 USD goes to buyer's balance + BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 90 - 110 - 111, get_balance(buyer, core) ); + // sell order pays 10 USD, receives 111 CORE, remaining 690 USD for sale, still at price 7/59 + + // then it will match with call, at mssp: 1/11 = 690/7590 : #338 fixed + BOOST_CHECK_EQUAL( 2293, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 7701, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 310, call.debt.value ); + BOOST_CHECK_EQUAL( 7410, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + + // call's call_price will be updated after the match, to 741/31/1.75 CORE/USD = 2964/217 + // it's above settlement price (10/1) so won't be margin called again + BOOST_CHECK( price(asset(2964),asset(217,usd_id)) == call.call_price ); + + // This would match with call before, but would match with call2 after #343 fixed + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6000) ) ); + BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 110 ); + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 90 ); + + // fill price would be mssp: 1/11 = 700/7700 : #338 fixed + BOOST_CHECK_EQUAL( 1593, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 310, call.debt.value ); + BOOST_CHECK_EQUAL( 7410, call.collateral.value ); + BOOST_CHECK_EQUAL( 300, call2.debt.value ); + BOOST_CHECK_EQUAL( 7800, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + // call2's call_price will be updated after the match, to 78/3/1.75 CORE/USD = 312/21 + BOOST_CHECK( price(asset(312),asset(21,usd_id)) == call2.call_price ); + // it's above settlement price (10/1) so won't be margin called + + // at this moment, collateralization of call is 7410 / 310 = 23.9 + // collateralization of call2 is 7800 / 300 = 26 + // collateralization of call3 is 16000 / 1000 = 16 + + // force settle + force_settle( seller, bitusd.amount(10) ); + + BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 15401, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 310, call.debt.value ); + BOOST_CHECK_EQUAL( 7410, call.collateral.value ); + BOOST_CHECK_EQUAL( 300, call2.debt.value ); + BOOST_CHECK_EQUAL( 7800, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + + // generate blocks to let the settle order execute (price feed will expire after it) + generate_blocks( HARDFORK_CORE_338_TIME + fc::hours(25) ); + + // call3 get settled, at settlement price 1/10: #343 fixed + BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 15501, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 310, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 7410, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 300, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 7800, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 990, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15900, call3_id(db).collateral.value ); + + set_expiration( db, trx ); + update_feed_producers( usd_id(db), {feedproducer_id} ); + + // at this moment, collateralization of call is 7410 / 310 = 23.9 + // collateralization of call2 is 7800 / 300 = 26 + // collateralization of call3 is 15900 / 990 = 16.06 + + // adjust price feed to get call3 into black swan territory, but not the other call orders + current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id); + publish_feed( usd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 1/20, mssp = 1/22 + + // black swan event will occur: #649 fixed + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + // short positions will be closed + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + + // generate a block + generate_block(); + + +} FC_LOG_AND_RETHROW() } + +/*** + * Fixed bitshares-core issue #453 + */ +BOOST_AUTO_TEST_CASE(hardfork_core_453_test) +{ try { + generate_blocks(HARDFORK_CORE_453_TIME); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // no margin call so far + + // This order would match call when it's margin called, it has an amount same as call's debt which will be full filled later + limit_order_id_type sell_med = create_sell_order(seller_id(db), asset(1000, usd_id), asset(10000))->id; // 1/10 + // Another big order above sell_med, amount bigger than call2's debt + limit_order_id_type sell_med2 = create_sell_order(seller_id(db), asset(1200, usd_id), asset(12120))->id; // 1/10.1 + // Another small order above sell_med2 + limit_order_id_type sell_med3 = create_sell_order(seller_id(db), asset(120, usd_id), asset(1224))->id; // 1/10.2 + + // adjust price feed to get the call orders into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // Fixed #453 multiple order matching issue occurs + BOOST_CHECK( !db.find( sell_med ) ); // sell_med get filled + BOOST_CHECK( !db.find( sell_med2 ) ); // sell_med2 get filled + BOOST_CHECK( !db.find( sell_med3 ) ); // sell_med3 get filled + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK( !db.find( call2_id ) ); // the second call order get filled + BOOST_CHECK( db.find( call3_id ) ); // the third call order is still there + + // generate a block + generate_block(); + + +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From 82765cbb8a2984dde6abcf7b63bc2cf9ae75b2a8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 23:14:53 +0000 Subject: [PATCH 17/29] Remove unused CORE_338.hf file --- libraries/chain/hardfork.d/CORE_338.hf | 4 ---- tests/tests/market_tests.cpp | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 libraries/chain/hardfork.d/CORE_338.hf diff --git a/libraries/chain/hardfork.d/CORE_338.hf b/libraries/chain/hardfork.d/CORE_338.hf deleted file mode 100644 index bfeb3a6a9d..0000000000 --- a/libraries/chain/hardfork.d/CORE_338.hf +++ /dev/null @@ -1,4 +0,0 @@ -// bitshares-core issue #338 Fix "margin call order fills at price of matching limit_order" -#ifndef HARDFORK_CORE_338_TIME -#define HARDFORK_CORE_338_TIME (fc::time_point_sec( 1600000000 )) -#endif diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index e1d0fb0f8b..537ef7a248 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -234,7 +234,7 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) */ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) { try { - generate_blocks(HARDFORK_CORE_338_TIME); // assume all hard forks occur at same time + generate_blocks(HARDFORK_CORE_343_TIME); // assume all hard forks occur at same time generate_block(); set_expiration( db, trx ); @@ -364,7 +364,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); // generate blocks to let the settle order execute (price feed will expire after it) - generate_blocks( HARDFORK_CORE_338_TIME + fc::hours(25) ); + generate_blocks( HARDFORK_CORE_343_TIME + fc::hours(25) ); // call3 get settled, at settlement price 1/10: #343 fixed BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) ); From 8ea398d5ea1531cad7fa189d0eb37c637385699a Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 7 Feb 2018 23:32:29 +0000 Subject: [PATCH 18/29] Update tests for hard fork on maintenance interval --- tests/tests/market_tests.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 537ef7a248..f8269a3e93 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -234,8 +234,9 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) */ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) { try { - generate_blocks(HARDFORK_CORE_343_TIME); // assume all hard forks occur at same time - generate_block(); + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -287,7 +288,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // settlement price = 1/10, mssp = 1/11 // This sell order above MSSP will not be matched with a call - limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); @@ -364,11 +365,12 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); // generate blocks to let the settle order execute (price feed will expire after it) - generate_blocks( HARDFORK_CORE_343_TIME + fc::hours(25) ); + generate_block(); + generate_blocks( db.head_block_time() + fc::hours(24) ); // call3 get settled, at settlement price 1/10: #343 fixed - BOOST_CHECK_EQUAL( 1583, get_balance(seller, bitusd) ); - BOOST_CHECK_EQUAL( 15501, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 1583, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 15501, get_balance(seller_id, core_id) ); BOOST_CHECK_EQUAL( 310, call_id(db).debt.value ); BOOST_CHECK_EQUAL( 7410, call_id(db).collateral.value ); BOOST_CHECK_EQUAL( 300, call2_id(db).debt.value ); @@ -406,8 +408,9 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) */ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) { try { - generate_blocks(HARDFORK_CORE_453_TIME); - generate_block(); + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -416,7 +419,6 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); asset_id_type usd_id = bitusd.id; - asset_id_type core_id = core.id; int64_t init_balance(1000000); From 9ab327ac53b52ca2e49f1d0b5a4fa15224393634 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 02:29:33 +0000 Subject: [PATCH 19/29] Re-add CORE_338.hf --- libraries/chain/hardfork.d/CORE_338.hf | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 libraries/chain/hardfork.d/CORE_338.hf diff --git a/libraries/chain/hardfork.d/CORE_338.hf b/libraries/chain/hardfork.d/CORE_338.hf new file mode 100644 index 0000000000..bfeb3a6a9d --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_338.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #338 Fix "margin call order fills at price of matching limit_order" +#ifndef HARDFORK_CORE_338_TIME +#define HARDFORK_CORE_338_TIME (fc::time_point_sec( 1600000000 )) +#endif From a8391d89372f9498ebe1da29ba64a44371317221 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 03:20:50 +0000 Subject: [PATCH 20/29] Add cross-hardfork tests #338 #343 #453 #606 #649 --- tests/tests/market_tests.cpp | 419 ++++++++++++++++++++++++++++++++++- 1 file changed, 414 insertions(+), 5 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index f8269a3e93..df1292aa26 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(issue_338_etc) // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); call_order_id_type call_id = call.id; - // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); call_order_id_type call2_id = call2.id; // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 @@ -263,7 +263,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); call_order_id_type call_id = call.id; - // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); call_order_id_type call2_id = call2.id; // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 @@ -386,9 +386,10 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) // collateralization of call3 is 15900 / 990 = 16.06 // adjust price feed to get call3 into black swan territory, but not the other call orders - current_feed.settlement_price = asset(1, usd_id) / asset(20, core_id); + // Note: after hard fork, black swan should occur when callateralization < mssp, but not at < feed + current_feed.settlement_price = asset(1, usd_id) / asset(16, core_id); publish_feed( usd_id(db), feedproducer_id(db), current_feed ); - // settlement price = 1/20, mssp = 1/22 + // settlement price = 1/16, mssp = 10/176 // black swan event will occur: #649 fixed BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); @@ -436,7 +437,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); call_order_id_type call_id = call.id; - // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 63/7 + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); call_order_id_type call2_id = call2.id; // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 @@ -483,4 +484,412 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) } FC_LOG_AND_RETHROW() } +/*** + * Fixed bitshares-core issue #453 #606: multiple order matching without black swan + */ +BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test) +{ try { // create orders before hard fork, which will be matched on hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_453_TIME - mi); // assume all hard forks occur at same time + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This order below the call price will not be matched before hard fork: 1/8 #606 + limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id; + // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606 + limit_order_id_type sell_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id; + // This order above the MSSP will not be matched before hard fork + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(700), core.amount(6400))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_med2 = create_sell_order(seller, bitusd.amount(7), core.amount(65))->id; + + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // generate a block to include operations above + generate_block(); + // go over the hard fork, make sure feed doesn't expire + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // sell_low and call should get matched first + BOOST_CHECK( !db.find( sell_low ) ); + BOOST_CHECK( !db.find( call_id ) ); + // sell_low2 and call2 should get matched + BOOST_CHECK( !db.find( call2_id ) ); + // sell_low2 and call3 should get matched: fixed #453 + BOOST_CHECK( !db.find( sell_low2 ) ); + // sell_med and call3 should get matched + BOOST_CHECK( !db.find( sell_med ) ); + // call3 now is not at margin call state, so sell_med2 won't get matched + BOOST_CHECK_EQUAL( db.find( sell_med2 )->for_sale.value, 7 ); + // sell_high should still be there, didn't match anything + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + // all match price would be limit order price + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 7000+8056+6400, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 1000-7-700, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 16000-56-6400, call3_id(db).collateral.value ); + // call3's call_price should be updated: 9544/293/1.75 = 9544*4 / 293*7 = 38176/2051 CORE/USD + BOOST_CHECK( price(asset(38176),asset(2051,usd_id)) == call3_id(db).call_price ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Fixed bitshares-core issue #338 #453 #606: multiple order matching with black swan + */ +BOOST_AUTO_TEST_CASE(hard_fork_338_cross_test) +{ try { // create orders before hard fork, which will be matched on hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_338_TIME - mi); // assume all hard forks occur at same time + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + // create yet another position with 400% collateral, call price is 20/1.75 CORE/USD = 80/7 + const call_order_object& call4 = *borrow( borrower4, bitusd.amount(1000), asset(20000)); + call_order_id_type call4_id = call4.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This order below the call price will not be matched before hard fork: 1/8 #606 + limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id; + // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606 + limit_order_id_type sell_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(7), core.amount(64))->id; + + // adjust price feed to get call_order into black swan territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(16); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/16, mssp = 10/176 + + // due to sell_low, black swan won't occur + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + + BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // generate a block to include operations above + generate_block(); + // go over the hard fork, make sure feed doesn't expire + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // sell_low and call should get matched first + BOOST_CHECK( !db.find( sell_low ) ); + BOOST_CHECK( !db.find( call_id ) ); + // sell_low2 and call2 should get matched + BOOST_CHECK( !db.find( call2_id ) ); + // sell_low2 and call3 should get matched: fixed #453 + BOOST_CHECK( !db.find( sell_low2 ) ); + // sell_med and call3 should get matched + BOOST_CHECK( !db.find( sell_med ) ); + + // at this moment, + // collateralization of call3 is (16000-56-64) / (1000-7-7) = 15880/986 = 16.1, it's > 16 but < 17.6 + // although there is no sell order, it should trigger a black swan event right away, + // because after hard fork new limit order won't trigger black swan event + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call3_id ) ); + BOOST_CHECK( !db.find( call4_id ) ); + + // global settlement fund should be 15880 + 1000 * 15880 / 986 + BOOST_CHECK_EQUAL( 15880 + 1000 * 15880 / 986, usd_id(db).bitasset_data(db).settlement_fund.value ); + // global settlement price should be settlement_fund/(1000+986), but not 15880/986 due to rounding + BOOST_CHECK( price(asset(986+1000,usd_id),asset(15880+1000*15880/986) ) == usd_id(db).bitasset_data(db).settlement_price ); + + BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 7000+8056+64, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 1000, get_balance(borrower4_id, usd_id) ); + BOOST_CHECK_EQUAL( init_balance-1000*15880/986, get_balance(borrower4_id, core_id) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Fixed bitshares-core issue #649: Black swan detection fetch call order by call_price but not collateral ratio + */ +BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test) +{ try { // create orders before hard fork, which will be matched on hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This would match with call at price 700/6400 + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6400)) ); + BOOST_CHECK_EQUAL( 3000-700, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6400, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 300, call.debt.value ); + BOOST_CHECK_EQUAL( 8600, call.collateral.value ); + + // at this moment, + // collateralization of call is 8600 / 300 = 28.67 + // collateralization of call2 is 15500 / 1000 = 15.5 + // collateralization of call3 is 16000 / 1000 = 16 + + // adjust price feed to get call_order into black swan territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(20); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/20, mssp = 1/22 + + // due to #649, black swan won't occur + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); + + // generate a block to include operations above + generate_block(); + // go over the hard fork, make sure feed doesn't expire + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + // a black swan event should occur + BOOST_CHECK( usd_id(db).bitasset_data(db).has_settlement() ); + BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call3_id ) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Fixed bitshares-core issue #343: change sorting of call orders when matching against limit order + */ +BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) +{ try { // create orders before hard fork, which will be matched on hard fork + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_343_TIME - mi); // assume all hard forks occur at same time + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This would match with call at price 700/6400 + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6400)) ); + BOOST_CHECK_EQUAL( 3000-700, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6400, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 300, call.debt.value ); + BOOST_CHECK_EQUAL( 8600, call.collateral.value ); + + // at this moment, + // collateralization of call is 8600 / 300 = 28.67 + // collateralization of call2 is 15500 / 1000 = 15.5 + // collateralization of call3 is 16000 / 1000 = 16 + + // generate a block to include operations above + generate_block(); + // go over the hard fork, make sure feed doesn't expire + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + // This will match with call2 at price 7/77 (#343 fixed) + BOOST_CHECK( !create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) ); + BOOST_CHECK_EQUAL( 3000-700-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6400+77, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 300, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15500-77, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 16000, call3_id(db).collateral.value ); + + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 98a23104899476d143f9fcfe6e3730e7f5aef369 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 8 Feb 2018 05:00:57 +0000 Subject: [PATCH 21/29] Update tests for global settlement price --- tests/tests/market_tests.cpp | 51 ++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index df1292aa26..58054d9726 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -690,15 +690,18 @@ BOOST_AUTO_TEST_CASE(hard_fork_338_cross_test) BOOST_CHECK( !db.find( call3_id ) ); BOOST_CHECK( !db.find( call4_id ) ); - // global settlement fund should be 15880 + 1000 * 15880 / 986 - BOOST_CHECK_EQUAL( 15880 + 1000 * 15880 / 986, usd_id(db).bitasset_data(db).settlement_fund.value ); - // global settlement price should be settlement_fund/(1000+986), but not 15880/986 due to rounding - BOOST_CHECK( price(asset(986+1000,usd_id),asset(15880+1000*15880/986) ) == usd_id(db).bitasset_data(db).settlement_price ); + // since 16.1 > 16, global settlement should at feed price 16/1 + // so settlement fund should be 986*16 + 1000*16 + BOOST_CHECK_EQUAL( 1986*16, usd_id(db).bitasset_data(db).settlement_fund.value ); + // global settlement price should be 16/1, since no rounding here + BOOST_CHECK( price(asset(1,usd_id),asset(16) ) == usd_id(db).bitasset_data(db).settlement_price ); BOOST_CHECK_EQUAL( 3000-1000-1007-7, get_balance(seller_id, usd_id) ); BOOST_CHECK_EQUAL( 7000+8056+64, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) ); + BOOST_CHECK_EQUAL( init_balance-16000+15880-986*16, get_balance(borrower3_id, core_id) ); BOOST_CHECK_EQUAL( 1000, get_balance(borrower4_id, usd_id) ); - BOOST_CHECK_EQUAL( init_balance-1000*15880/986, get_balance(borrower4_id, core_id) ); + BOOST_CHECK_EQUAL( init_balance-1000*16, get_balance(borrower4_id, core_id) ); generate_block(); @@ -763,21 +766,25 @@ BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test) publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 1/10, mssp = 1/11 - // This would match with call at price 700/6400 - BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700), core.amount(6400)) ); - BOOST_CHECK_EQUAL( 3000-700, get_balance(seller_id, usd_id) ); - BOOST_CHECK_EQUAL( 6400, get_balance(seller_id, core_id) ); - BOOST_CHECK_EQUAL( 300, call.debt.value ); - BOOST_CHECK_EQUAL( 8600, call.collateral.value ); + // This would match with call at price 707/6464 + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(707), core.amount(6464)) ); + BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 293, call.debt.value ); + BOOST_CHECK_EQUAL( 8536, call.collateral.value ); // at this moment, - // collateralization of call is 8600 / 300 = 28.67 + // collateralization of call is 8536 / 293 = 29.1 // collateralization of call2 is 15500 / 1000 = 15.5 // collateralization of call3 is 16000 / 1000 = 16 + generate_block(); + set_expiration( db, trx ); + update_feed_producers( usd_id(db), {feedproducer_id} ); + // adjust price feed to get call_order into black swan territory - current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(20); - publish_feed( bitusd, feedproducer, current_feed ); + current_feed.settlement_price = price(asset(1,usd_id) / asset(20)); + publish_feed( usd_id(db), feedproducer_id(db), current_feed ); // settlement price = 1/20, mssp = 1/22 // due to #649, black swan won't occur @@ -785,6 +792,7 @@ BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test) // generate a block to include operations above generate_block(); + BOOST_CHECK( !usd_id(db).bitasset_data(db).has_settlement() ); // go over the hard fork, make sure feed doesn't expire generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); @@ -794,6 +802,21 @@ BOOST_AUTO_TEST_CASE(hard_fork_649_cross_test) BOOST_CHECK( !db.find( call2_id ) ); BOOST_CHECK( !db.find( call3_id ) ); + // since least collateral ratio 15.5 < 20, global settlement should at least collateral ratio 15.5/1 + // so settlement fund should be 15500 + 15500 + 15.5 * 293 + BOOST_CHECK_EQUAL( 15500*2 + 293 * 155 / 10, usd_id(db).bitasset_data(db).settlement_fund.value ); + // global settlement price should be settlement_fund/(2000+293), but not 15.5/1 due to rounding + BOOST_CHECK( price(asset(2293,usd_id),asset(15500*2+293*155/10) ) == usd_id(db).bitasset_data(db).settlement_price ); + + BOOST_CHECK_EQUAL( 3000-707, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6464, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, usd_id) ); + BOOST_CHECK_EQUAL( init_balance-6464-293*155/10, get_balance(borrower_id, core_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2_id, usd_id) ); + BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower2_id, core_id) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3_id, usd_id) ); + BOOST_CHECK_EQUAL( init_balance-15500, get_balance(borrower3_id, core_id) ); + generate_block(); } FC_LOG_AND_RETHROW() } From 43d3795479cc24cd5581bd2b38bf3c314d83026a Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 9 Feb 2018 11:07:11 +0000 Subject: [PATCH 22/29] Add tests for multiple assets crossing hardfork --- tests/tests/market_tests.cpp | 166 ++++++++++++++++++++++++++++------- 1 file changed, 136 insertions(+), 30 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 58054d9726..ec22f8ccfb 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -485,7 +485,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) } FC_LOG_AND_RETHROW() } /*** - * Fixed bitshares-core issue #453 #606: multiple order matching without black swan + * Fixed bitshares-core issue #453 #606: multiple order matching without black swan, multiple bitassets */ BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test) { try { // create orders before hard fork, which will be matched on hard fork @@ -498,8 +498,12 @@ BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test) ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& biteur = create_bitasset("EURBIT", feedproducer_id); + const auto& bitcny = create_bitasset("CNYBIT", feedproducer_id); const auto& core = asset_id_type()(db); asset_id_type usd_id = bitusd.id; + asset_id_type eur_id = biteur.id; + asset_id_type cny_id = bitcny.id; asset_id_type core_id = core.id; int64_t init_balance(1000000); @@ -509,51 +513,117 @@ BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test) transfer(committee_account, borrower2_id, asset(init_balance)); transfer(committee_account, borrower3_id, asset(init_balance)); update_feed_producers( bitusd, {feedproducer.id} ); + update_feed_producers( biteur, {feedproducer.id} ); + update_feed_producers( bitcny, {feedproducer.id} ); price_feed current_feed; current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); publish_feed( bitusd, feedproducer, current_feed ); + current_feed.settlement_price = biteur.amount( 1 ) / core.amount(5); + publish_feed( biteur, feedproducer, current_feed ); + current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(5); + publish_feed( bitcny, feedproducer, current_feed ); // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 - const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); - call_order_id_type call_id = call.id; + const call_order_object& call_usd = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_usd_id = call_usd.id; + const call_order_object& call_eur = *borrow( borrower, biteur.amount(1000), asset(15000)); + call_order_id_type call_eur_id = call_eur.id; + const call_order_object& call_cny = *borrow( borrower, bitcny.amount(1000), asset(15000)); + call_order_id_type call_cny_id = call_cny.id; // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 - const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); - call_order_id_type call2_id = call2.id; + const call_order_object& call_usd2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call_usd2_id = call_usd2.id; + const call_order_object& call_eur2 = *borrow( borrower2, biteur.amount(1000), asset(15500)); + call_order_id_type call_eur2_id = call_eur2.id; + const call_order_object& call_cny2 = *borrow( borrower2, bitcny.amount(1000), asset(15500)); + call_order_id_type call_cny2_id = call_cny2.id; // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 - const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); - call_order_id_type call3_id = call3.id; + const call_order_object& call_usd3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + call_order_id_type call_usd3_id = call_usd3.id; + const call_order_object& call_eur3 = *borrow( borrower3, biteur.amount(1000), asset(16000)); + call_order_id_type call_eur3_id = call_eur3.id; + const call_order_object& call_cny3 = *borrow( borrower3, bitcny.amount(1000), asset(16000)); + call_order_id_type call_cny3_id = call_cny3.id; transfer(borrower, seller, bitusd.amount(1000)); transfer(borrower2, seller, bitusd.amount(1000)); transfer(borrower3, seller, bitusd.amount(1000)); - - BOOST_CHECK_EQUAL( 1000, call.debt.value ); - BOOST_CHECK_EQUAL( 15000, call.collateral.value ); - BOOST_CHECK_EQUAL( 1000, call2.debt.value ); - BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); - BOOST_CHECK_EQUAL( 1000, call3.debt.value ); - BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + transfer(borrower, seller, biteur.amount(1000)); + transfer(borrower2, seller, biteur.amount(1000)); + transfer(borrower3, seller, biteur.amount(1000)); + transfer(borrower, seller, bitcny.amount(1000)); + transfer(borrower2, seller, bitcny.amount(1000)); + transfer(borrower3, seller, bitcny.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call_usd.debt.value ); + BOOST_CHECK_EQUAL( 15000, call_usd.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_usd2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call_usd2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_usd3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call_usd3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 1000, call_eur.debt.value ); + BOOST_CHECK_EQUAL( 15000, call_eur.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_eur2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call_eur2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_eur3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call_eur3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, biteur) ); + BOOST_CHECK_EQUAL( 1000, call_cny.debt.value ); + BOOST_CHECK_EQUAL( 15000, call_cny.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_cny2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call_cny2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call_cny3.debt.value ); + BOOST_CHECK_EQUAL( 16000, call_cny3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitcny) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // adjust price feed to get call_order into margin call territory current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); publish_feed( bitusd, feedproducer, current_feed ); + current_feed.settlement_price = biteur.amount( 1 ) / core.amount(10); + publish_feed( biteur, feedproducer, current_feed ); + current_feed.settlement_price = bitcny.amount( 1 ) / core.amount(10); + publish_feed( bitcny, feedproducer, current_feed ); // settlement price = 1/10, mssp = 1/11 // This order below the call price will not be matched before hard fork: 1/8 #606 - limit_order_id_type sell_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id; + limit_order_id_type sell_usd_low = create_sell_order(seller, bitusd.amount(1000), core.amount(7000))->id; // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606 - limit_order_id_type sell_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id; + limit_order_id_type sell_usd_low2 = create_sell_order(seller, bitusd.amount(1007), core.amount(8056))->id; // This order above the MSSP will not be matched before hard fork - limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + limit_order_id_type sell_usd_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_usd_med = create_sell_order(seller, bitusd.amount(700), core.amount(6400))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_usd_med2 = create_sell_order(seller, bitusd.amount(7), core.amount(65))->id; + + // This order below the call price will not be matched before hard fork: 1/8 #606 + limit_order_id_type sell_eur_low = create_sell_order(seller, biteur.amount(1000), core.amount(7000))->id; + // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606 + limit_order_id_type sell_eur_low2 = create_sell_order(seller, biteur.amount(1007), core.amount(8056))->id; + // This order above the MSSP will not be matched before hard fork + limit_order_id_type sell_eur_high = create_sell_order(seller, biteur.amount(7), core.amount(78))->id; // This would match but is blocked by sell_low?! #606 - limit_order_id_type sell_med = create_sell_order(seller, bitusd.amount(700), core.amount(6400))->id; + limit_order_id_type sell_eur_med = create_sell_order(seller, biteur.amount(700), core.amount(6400))->id; // This would match but is blocked by sell_low?! #606 - limit_order_id_type sell_med2 = create_sell_order(seller, bitusd.amount(7), core.amount(65))->id; + limit_order_id_type sell_eur_med2 = create_sell_order(seller, biteur.amount(7), core.amount(65))->id; + + // This order below the call price will not be matched before hard fork: 1/8 #606 + limit_order_id_type sell_cny_low = create_sell_order(seller, bitcny.amount(1000), core.amount(7000))->id; + // This is a big order, price below the call price will not be matched before hard fork: 1007/9056 = 1/8 #606 + limit_order_id_type sell_cny_low2 = create_sell_order(seller, bitcny.amount(1007), core.amount(8056))->id; + // This order above the MSSP will not be matched before hard fork + limit_order_id_type sell_cny_high = create_sell_order(seller, bitcny.amount(7), core.amount(78))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_cny_med = create_sell_order(seller, bitcny.amount(700), core.amount(6400))->id; + // This would match but is blocked by sell_low?! #606 + limit_order_id_type sell_cny_med2 = create_sell_order(seller, bitcny.amount(7), core.amount(65))->id; BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) ); + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // generate a block to include operations above @@ -562,26 +632,62 @@ BOOST_AUTO_TEST_CASE(hard_fork_453_cross_test) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); // sell_low and call should get matched first - BOOST_CHECK( !db.find( sell_low ) ); - BOOST_CHECK( !db.find( call_id ) ); + BOOST_CHECK( !db.find( sell_usd_low ) ); + BOOST_CHECK( !db.find( call_usd_id ) ); // sell_low2 and call2 should get matched - BOOST_CHECK( !db.find( call2_id ) ); + BOOST_CHECK( !db.find( call_usd2_id ) ); // sell_low2 and call3 should get matched: fixed #453 - BOOST_CHECK( !db.find( sell_low2 ) ); + BOOST_CHECK( !db.find( sell_usd_low2 ) ); // sell_med and call3 should get matched - BOOST_CHECK( !db.find( sell_med ) ); + BOOST_CHECK( !db.find( sell_usd_med ) ); + // call3 now is not at margin call state, so sell_med2 won't get matched + BOOST_CHECK_EQUAL( db.find( sell_usd_med2 )->for_sale.value, 7 ); + // sell_high should still be there, didn't match anything + BOOST_CHECK_EQUAL( db.find( sell_usd_high )->for_sale.value, 7 ); + + // sell_low and call should get matched first + BOOST_CHECK( !db.find( sell_eur_low ) ); + BOOST_CHECK( !db.find( call_eur_id ) ); + // sell_low2 and call2 should get matched + BOOST_CHECK( !db.find( call_eur2_id ) ); + // sell_low2 and call3 should get matched: fixed #453 + BOOST_CHECK( !db.find( sell_eur_low2 ) ); + // sell_med and call3 should get matched + BOOST_CHECK( !db.find( sell_eur_med ) ); + // call3 now is not at margin call state, so sell_med2 won't get matched + BOOST_CHECK_EQUAL( db.find( sell_eur_med2 )->for_sale.value, 7 ); + // sell_high should still be there, didn't match anything + BOOST_CHECK_EQUAL( db.find( sell_eur_high )->for_sale.value, 7 ); + + // sell_low and call should get matched first + BOOST_CHECK( !db.find( sell_cny_low ) ); + BOOST_CHECK( !db.find( call_cny_id ) ); + // sell_low2 and call2 should get matched + BOOST_CHECK( !db.find( call_cny2_id ) ); + // sell_low2 and call3 should get matched: fixed #453 + BOOST_CHECK( !db.find( sell_cny_low2 ) ); + // sell_med and call3 should get matched + BOOST_CHECK( !db.find( sell_cny_med ) ); // call3 now is not at margin call state, so sell_med2 won't get matched - BOOST_CHECK_EQUAL( db.find( sell_med2 )->for_sale.value, 7 ); + BOOST_CHECK_EQUAL( db.find( sell_cny_med2 )->for_sale.value, 7 ); // sell_high should still be there, didn't match anything - BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + BOOST_CHECK_EQUAL( db.find( sell_cny_high )->for_sale.value, 7 ); // all match price would be limit order price BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, usd_id) ); - BOOST_CHECK_EQUAL( 7000+8056+6400, get_balance(seller_id, core_id) ); - BOOST_CHECK_EQUAL( 1000-7-700, call3_id(db).debt.value ); - BOOST_CHECK_EQUAL( 16000-56-6400, call3_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, eur_id) ); + BOOST_CHECK_EQUAL( 3000-1000-1007-7-700-7, get_balance(seller_id, cny_id) ); + BOOST_CHECK_EQUAL( (7000+8056+6400)*3, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 1000-7-700, call_usd3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 16000-56-6400, call_usd3_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7-700, call_eur3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 16000-56-6400, call_eur3_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7-700, call_cny3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 16000-56-6400, call_cny3_id(db).collateral.value ); // call3's call_price should be updated: 9544/293/1.75 = 9544*4 / 293*7 = 38176/2051 CORE/USD - BOOST_CHECK( price(asset(38176),asset(2051,usd_id)) == call3_id(db).call_price ); + BOOST_CHECK( price(asset(38176),asset(2051,usd_id)) == call_usd3_id(db).call_price ); + BOOST_CHECK( price(asset(38176),asset(2051,eur_id)) == call_eur3_id(db).call_price ); + BOOST_CHECK( price(asset(38176),asset(2051,cny_id)) == call_cny3_id(db).call_price ); generate_block(); From 22a2ecc4ae5cf4e9b3b3ad4d2727933158001c2c Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 15 Feb 2018 12:55:25 +0000 Subject: [PATCH 23/29] Update tests about matching calls with CR == MCR --- tests/tests/market_tests.cpp | 43 +++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index ec22f8ccfb..47e44bbe94 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -965,8 +965,8 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); call_order_id_type call2_id = call2.id; - // create yet another position with 320% collateral, call price is 16/1.75 CORE/USD = 64/7 - const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(16000)); + // create yet another position with 350% collateral, call price is 17.5/1.75 CORE/USD = 77/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(17500)); call_order_id_type call3_id = call3.id; transfer(borrower, seller, bitusd.amount(1000)); transfer(borrower2, seller, bitusd.amount(1000)); @@ -977,7 +977,7 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) BOOST_CHECK_EQUAL( 1000, call2.debt.value ); BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); BOOST_CHECK_EQUAL( 1000, call3.debt.value ); - BOOST_CHECK_EQUAL( 16000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); @@ -996,7 +996,7 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) // at this moment, // collateralization of call is 8600 / 300 = 28.67 // collateralization of call2 is 15500 / 1000 = 15.5 - // collateralization of call3 is 16000 / 1000 = 16 + // collateralization of call3 is 17500 / 1000 = 17.5 // generate a block to include operations above generate_block(); @@ -1006,16 +1006,39 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) set_expiration( db, trx ); // This will match with call2 at price 7/77 (#343 fixed) - BOOST_CHECK( !create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) ); - BOOST_CHECK_EQUAL( 3000-700-7, get_balance(seller_id, usd_id) ); - BOOST_CHECK_EQUAL( 6400+77, get_balance(seller_id, core_id) ); + BOOST_CHECK( !create_sell_order(seller_id(db), asset(7*50,usd_id), asset(65*50)) ); + BOOST_CHECK_EQUAL( 3000-700-7*50, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6400+77*50, get_balance(seller_id, core_id) ); BOOST_CHECK_EQUAL( 300, call_id(db).debt.value ); BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value ); - BOOST_CHECK_EQUAL( 1000-7, call2_id(db).debt.value ); - BOOST_CHECK_EQUAL( 15500-77, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value ); BOOST_CHECK_EQUAL( 1000, call3_id(db).debt.value ); - BOOST_CHECK_EQUAL( 16000, call3_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 17500, call3_id(db).collateral.value ); + + // at this moment, + // collateralization of call is 8600 / 300 = 28.67 + // collateralization of call2 is 11650 / 650 = 17.9 + // collateralization of call3 is 17500 / 1000 = 17.5 + + // This will match with call3 at price 7/77 (#343 fixed) + BOOST_CHECK( !create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) ); + BOOST_CHECK_EQUAL( 3000-700-7*50-7, get_balance(seller_id, usd_id) ); + BOOST_CHECK_EQUAL( 6400+77*50+77, get_balance(seller_id, core_id) ); + BOOST_CHECK_EQUAL( 300, call_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8600, call_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7*50, call2_id(db).debt.value ); + BOOST_CHECK_EQUAL( 15500-77*50, call2_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 1000-7, call3_id(db).debt.value ); + BOOST_CHECK_EQUAL( 17500-77, call3_id(db).collateral.value ); + + // at this moment, + // collateralization of call is 8600 / 300 = 28.67 + // collateralization of call2 is 11650 / 650 = 17.9 + // collateralization of call3 is 17423 / 993 = 17.55 + // no more margin call now + BOOST_CHECK( create_sell_order(seller_id(db), asset(7,usd_id), asset(65)) ); generate_block(); From 62b3a7b2311dacda2b92baf78074940bc4d03336 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 7 Apr 2018 07:37:11 -0400 Subject: [PATCH 24/29] Recover check_call_order_cull_small_test --- tests/tests/market_tests.cpp | 74 ++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 47e44bbe94..b36d85120c 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1044,4 +1044,78 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) } FC_LOG_AND_RETHROW() } +/*** + * reproduce check_call_orders cull_small issue + */ +BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) +{ try { // matching a limit order with call order + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + asset_id_type usd_id = bitusd.id; + asset_id_type core_id = core.id; + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + transfer(committee_account, borrower4_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount( 5 ); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 200% collateral, call price is 10/175 CORE/USD = 40/700 + const call_order_object& call = *borrow( borrower, bitusd.amount(10), asset(1)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500)); + call_order_id_type call3_id = call3.id; + transfer(borrower, seller, bitusd.amount(10)); + transfer(borrower2, seller, bitusd.amount(100000)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 200010, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd.amount( 120 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // This would match with call at price 11 USD / 1 CORE, but call only owes 10 USD, + // so the seller will pay 10 USD but get nothing. + // The remaining 1 USD is too little to get any CORE, so the limit order will be cancelled + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(11), core.amount(1)) ); + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 200000, get_balance(seller, bitusd) ); // the seller paid 10 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From e311769aa66b0ce010877e65116f077668cea562 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 7 Apr 2018 09:36:01 -0400 Subject: [PATCH 25/29] Avoid dereferencing invalidated iterator after hf --- libraries/chain/db_market.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 0ee0b736f6..790085dcf4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -885,11 +885,8 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan } // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker bool really_filled = fill_limit_order(*old_limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); - if( !filled_limit && really_filled ) - { - wlog( "Cull_small issue occurred at block #${block}", ("block",head_block_num()) ); + if( really_filled ) limit_itr = next_limit_itr; - } } // whlie call_itr != call_end From cd7bb4b3c5f8918841e0b1ffec597c7eb0eb7a88 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 9 Apr 2018 07:44:54 -0400 Subject: [PATCH 26/29] Minor code tweak --- libraries/chain/db_maint.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index c6be38b842..a06b95c70c 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -782,7 +782,7 @@ void update_and_match_call_orders( database& db ) } if( !abd || abd->is_prediction_market ) // nothing to do with PM's; check !abd just to be safe continue; - db.modify( call_obj, [&]( call_order_object& call ) { + db.modify( call_obj, [abd]( call_order_object& call ) { call.call_price = price::call_price( call.get_debt(), call.get_collateral(), abd->current_feed.maintenance_collateral_ratio ); }); From 5eb43de4ac362e30a6bc6b612fc9f9e03327b41a Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 9 Apr 2018 15:47:41 -0400 Subject: [PATCH 27/29] Add big limit order test case after hard fork #625 --- tests/tests/market_tests.cpp | 155 +++++++++++++++++++++++++++++++++-- 1 file changed, 150 insertions(+), 5 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index b36d85120c..ec45932ceb 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -405,7 +405,7 @@ BOOST_AUTO_TEST_CASE(hardfork_core_338_test) } FC_LOG_AND_RETHROW() } /*** - * Fixed bitshares-core issue #453 + * Fixed bitshares-core issue #453: multiple limit order filling issue */ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) { try { @@ -484,6 +484,155 @@ BOOST_AUTO_TEST_CASE(hardfork_core_453_test) } FC_LOG_AND_RETHROW() } +/*** + * Tests (big) limit order matching logic after #625 got fixed + */ +BOOST_AUTO_TEST_CASE(hardfork_core_625_big_limit_order_test) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_625_TIME - mi); // assume all hard forks occur at same time + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(buyer2)(buyer3)(seller)(borrower)(borrower2)(borrower3)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(committee_account, buyer_id, asset(init_balance)); + transfer(committee_account, buyer2_id, asset(init_balance)); + transfer(committee_account, buyer3_id, asset(init_balance)); + transfer(committee_account, borrower_id, asset(init_balance)); + transfer(committee_account, borrower2_id, asset(init_balance)); + transfer(committee_account, borrower3_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(5); + publish_feed( bitusd, feedproducer, current_feed ); + // start out with 300% collateral, call price is 15/1.75 CORE/USD = 60/7 + const call_order_object& call = *borrow( borrower, bitusd.amount(1000), asset(15000)); + call_order_id_type call_id = call.id; + // create another position with 310% collateral, call price is 15.5/1.75 CORE/USD = 62/7 + const call_order_object& call2 = *borrow( borrower2, bitusd.amount(1000), asset(15500)); + call_order_id_type call2_id = call2.id; + // create yet another position with 500% collateral, call price is 25/1.75 CORE/USD = 100/7 + const call_order_object& call3 = *borrow( borrower3, bitusd.amount(1000), asset(25000)); + transfer(borrower, seller, bitusd.amount(1000)); + transfer(borrower2, seller, bitusd.amount(1000)); + transfer(borrower3, seller, bitusd.amount(1000)); + + BOOST_CHECK_EQUAL( 1000, call.debt.value ); + BOOST_CHECK_EQUAL( 15000, call.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call2.debt.value ); + BOOST_CHECK_EQUAL( 15500, call2.collateral.value ); + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 3000, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 15000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( init_balance - 15500, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( init_balance - 25000, get_balance(borrower3, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower3, bitusd) ); + + // adjust price feed to get call and call2 (but not call3) into margin call territory + current_feed.settlement_price = bitusd.amount( 1 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 1/10, mssp = 1/11 + + // This sell order above MSSP will not be matched with a call + limit_order_id_type sell_high = create_sell_order(seller, bitusd.amount(7), core.amount(78))->id; + BOOST_CHECK_EQUAL( db.find( sell_high )->for_sale.value, 7 ); + + BOOST_CHECK_EQUAL( 2993, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + // This buy order is too low will not be matched with a sell order + limit_order_id_type buy_low = create_sell_order(buyer, asset(80), bitusd.amount(10))->id; + // This buy order at MSSP will be matched only if no margin call (margin call takes precedence) + limit_order_id_type buy_med = create_sell_order(buyer2, asset(11000), bitusd.amount(1000))->id; + // This buy order above MSSP will be matched with a sell order (limit order with better price takes precedence) + limit_order_id_type buy_high = create_sell_order(buyer3, asset(111), bitusd.amount(10))->id; + + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer3, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 80, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(buyer2, core) ); + BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) ); + + // Create a big sell order slightly below the call price, will be matched with several orders + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(700*4), core.amount(5900*4) ) ); + + // firstly it will match with buy_high, at buy_high's price + BOOST_CHECK( !db.find( buy_high ) ); + // buy_high pays 111 CORE, receives 10 USD goes to buyer3's balance + BOOST_CHECK_EQUAL( 10, get_balance(buyer3, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 111, get_balance(buyer3, core) ); + + // then it will match with call, at mssp: 1/11 = 1000/11000 + BOOST_CHECK( !db.find( call_id ) ); + // call pays 11000 CORE, receives 1000 USD to cover borrower's position, remaining CORE goes to borrower's balance + BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + + // then it will match with call2, at mssp: 1/11 = 1000/11000 + BOOST_CHECK( !db.find( call2_id ) ); + // call2 pays 11000 CORE, receives 1000 USD to cover borrower2's position, remaining CORE goes to borrower2's balance + BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(borrower2, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower2, bitusd) ); + + // then it will match with buy_med, at buy_med's price. Since buy_med is too big, it's partially filled. + // buy_med receives the remaining USD of sell order, minus market fees, goes to buyer2's balance + BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) ); // 700*4-10-1000-1000=790, minus 1% market fee 790*100/10000=7 + BOOST_CHECK_EQUAL( init_balance - 11000, get_balance(buyer2, core) ); + // buy_med pays at 1/11 = 790/8690 + BOOST_CHECK_EQUAL( db.find( buy_med )->for_sale.value, 11000-8690 ); + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // check seller balance + BOOST_CHECK_EQUAL( 193, get_balance(seller, bitusd) ); // 3000 - 7 - 700*4 + BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); // 111 + 11000 + 11000 + 8690 + + // Cancel buy_med + cancel_limit_order( buy_med(db) ); + BOOST_CHECK( !db.find( buy_med ) ); + BOOST_CHECK_EQUAL( 783, get_balance(buyer2, bitusd) ); + BOOST_CHECK_EQUAL( init_balance - 8690, get_balance(buyer2, core) ); + + // Create another sell order slightly below the call price, won't fill + limit_order_id_type sell_med = create_sell_order( seller, bitusd.amount(7), core.amount(59) )->id; + BOOST_CHECK_EQUAL( db.find( sell_med )->for_sale.value, 7 ); + // check seller balance + BOOST_CHECK_EQUAL( 193-7, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 30801, get_balance(seller, core) ); + + // call3 is not in margin call territory so won't be matched + BOOST_CHECK_EQUAL( 1000, call3.debt.value ); + BOOST_CHECK_EQUAL( 25000, call3.collateral.value ); + + // buy_low's price is too low that won't be matched + BOOST_CHECK_EQUAL( db.find( buy_low )->for_sale.value, 80 ); + + // generate a block + generate_block(); + +} FC_LOG_AND_RETHROW() } + /*** * Fixed bitshares-core issue #453 #606: multiple order matching without black swan, multiple bitassets */ @@ -1057,8 +1206,6 @@ BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); - asset_id_type usd_id = bitusd.id; - asset_id_type core_id = core.id; int64_t init_balance(1000000); @@ -1079,10 +1226,8 @@ BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) call_order_id_type call_id = call.id; // create another position with 310% collateral, call price is 15.5/175 CORE/USD = 62/700 const call_order_object& call2 = *borrow( borrower2, bitusd.amount(100000), asset(15500)); - call_order_id_type call2_id = call2.id; // create yet another position with 350% collateral, call price is 17.5/175 CORE/USD = 77/700 const call_order_object& call3 = *borrow( borrower3, bitusd.amount(100000), asset(17500)); - call_order_id_type call3_id = call3.id; transfer(borrower, seller, bitusd.amount(10)); transfer(borrower2, seller, bitusd.amount(100000)); transfer(borrower3, seller, bitusd.amount(100000)); From d6cdc89f041d31b753b1e6f63203df6e742994fa Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 9 Apr 2018 18:39:44 -0400 Subject: [PATCH 28/29] Minor logging format tweak --- libraries/chain/db_update.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 72ead5e172..09beb79a5e 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -249,7 +249,7 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s " Least collateralized call: ${lc} ${~lc}\n" // " Highest Bid: ${hb} ${~hb}\n" " Settle Price: ${~sp} ${sp}\n" - " Max: ${~h} ${h}\n", + " Max: ${~h} ${h}\n", ("lc",least_collateral.to_real())("~lc",(~least_collateral).to_real()) // ("hb",limit_itr->sell_price.to_real())("~hb",(~limit_itr->sell_price).to_real()) ("sp",settle_price.to_real())("~sp",(~settle_price).to_real()) From 8dcd9ca6480090d9569384e09b823b1645624c66 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 12 Apr 2018 16:01:18 -0400 Subject: [PATCH 29/29] Add FC_ASSERT and change some assert to FC_ASSERT --- libraries/chain/db_market.cpp | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 790085dcf4..758ed98e04 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -496,9 +496,9 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo */ int database::match( const limit_order_object& usd, const limit_order_object& core, const price& match_price ) { - assert( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); - assert( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); - assert( usd.for_sale > 0 && core.for_sale > 0 ); + FC_ASSERT( usd.sell_price.quote.asset_id == core.sell_price.base.asset_id ); + FC_ASSERT( usd.sell_price.base.asset_id == core.sell_price.quote.asset_id ); + FC_ASSERT( usd.for_sale > 0 && core.for_sale > 0 ); auto usd_for_sale = usd.amount_for_sale(); auto core_for_sale = core.amount_for_sale(); @@ -523,13 +523,13 @@ int database::match( const limit_order_object& usd, const limit_order_object& co core_pays = usd_receives; usd_pays = core_receives; - assert( usd_pays == usd.amount_for_sale() || - core_pays == core.amount_for_sale() ); + FC_ASSERT( usd_pays == usd.amount_for_sale() || + core_pays == core.amount_for_sale() ); int result = 0; result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // the first param is taker result |= fill_limit_order( core, core_pays, core_receives, true, match_price, true ) << 1; // the second param is maker - assert( result != 0 ); + FC_ASSERT( result != 0 ); return result; } @@ -773,6 +773,11 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan if( bitasset.is_prediction_market ) return false; if( bitasset.current_feed.settlement_price.is_null() ) return false; + auto head_time = head_block_time(); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + if( for_new_limit_order ) + FC_ASSERT( maint_time <= HARDFORK_CORE_625_TIME ); // `for_new_limit_order` is only true before HF 338 / 625 + const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); @@ -800,8 +805,6 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan bool filled_limit = false; bool margin_called = false; - auto head_time = head_block_time(); - auto maint_time = get_dynamic_global_properties().next_maintenance_time; while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end ) { bool filled_limit_in_loop = false;