From c247ef1bfdef6ffe7c6272ee4252c8ac176f7b4f Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 14 Feb 2018 01:27:36 +0000 Subject: [PATCH 01/56] Update test case about something for nothing #184 --- tests/tests/operation_tests.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index bd6fc00b04..268c866cfa 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1650,6 +1650,7 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) /** * Create an order such that when the trade executes at the * requested price the resulting payout to one party is 0 + * ( Reproduces https://github.com/bitshares/bitshares-core/issues/184 ) */ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) { @@ -1682,27 +1683,23 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3); generate_block(); - fc::usleep(fc::milliseconds(1000)); - //TODO: This will fail because of something-for-nothing bug(#345) - // Must be fixed with a hardfork -/** -* TODO: Remove this comment block when #345 is resolved. -* Added comment block to allow Travis-CI to pass by ignoring this test -* auto result = get_market_order_history(core_id, test_id); -* BOOST_CHECK_EQUAL(result.size(), 2); -* BOOST_CHECK(result[0].op.pays == core.amount(1)); -* BOOST_CHECK(result[0].op.receives == test.amount(2)); -* BOOST_CHECK(result[1].op.pays == test.amount(2)); -* BOOST_CHECK(result[1].op.receives == core.amount(1)); -*/ + auto result = get_market_order_history(core_id, test_id); + BOOST_CHECK_EQUAL(result.size(), 4); + BOOST_CHECK(result[0].op.pays == core.amount(0)); + BOOST_CHECK(result[0].op.receives == test.amount(1)); + BOOST_CHECK(result[1].op.pays == test.amount(1)); + BOOST_CHECK(result[1].op.receives == core.amount(0)); + BOOST_CHECK(result[2].op.pays == core.amount(1)); + BOOST_CHECK(result[2].op.receives == test.amount(2)); + BOOST_CHECK(result[3].op.pays == test.amount(2)); + BOOST_CHECK(result[3].op.receives == core.amount(1)); } catch( const fc::exception& e) { edump((e.to_detail_string())); throw; } } - /** * Create an order that cannot be filled immediately and have the * transaction fail. From fc5eff154d99ad8fb971a3d891a8d60d22ce589c Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Feb 2018 19:16:29 +0000 Subject: [PATCH 02/56] Tests something-for-nothing and cull_small issue --- tests/tests/market_tests.cpp | 152 ++++++++++++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index b36d85120c..04ca3871ff 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1045,9 +1045,10 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) } FC_LOG_AND_RETHROW() } /*** - * reproduce check_call_orders cull_small issue + * Reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. */ -BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) { try { // matching a limit order with call order generate_block(); @@ -1118,4 +1119,151 @@ BOOST_AUTO_TEST_CASE( check_call_order_cull_small_test ) } FC_LOG_AND_RETHROW() } +/*** + * Another test case + * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) +{ try { + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, + // so the seller will pay 10 USD but get nothing. + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Yet another test case + * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) +{ try { + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, + // so the seller will pay 10 USD but get nothing. + // The remaining USD will be in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + 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 3d0d5332848a17d29351ca6a84f22d7b3895dfd4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 14 Feb 2018 00:59:32 +0000 Subject: [PATCH 03/56] Handle something for nothing issue. #184 --- libraries/chain/db_market.cpp | 81 +++++++++++++++++++++----- libraries/chain/hardfork.d/CORE_184.hf | 4 ++ 2 files changed, 72 insertions(+), 13 deletions(-) create mode 100644 libraries/chain/hardfork.d/CORE_184.hf diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 790085dcf4..f9f11d0925 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -70,6 +70,15 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett if( pays > call_itr->get_collateral() ) pays = call_itr->get_collateral(); + // Be here, the call order can be paying nothing, in this case, we take at least 1 Satoshi from call order + if( pays.amount == 0 ) + { + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + pays.amount = 1; + else + wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) ); + } + collateral_gathered += pays; const auto& order = *call_itr; ++call_itr; @@ -526,6 +535,17 @@ int database::match( const limit_order_object& usd, const limit_order_object& co assert( usd_pays == usd.amount_for_sale() || core_pays == core.amount_for_sale() ); + // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. + if( usd_receives.amount == 0 ) + { + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + return 1; + else + wlog( "Something for nothing issue (#184, variant A) occurred at block #${block}", ("block",head_block_num()) ); + } + 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 @@ -568,6 +588,17 @@ int database::match( const limit_order_object& bid, const call_order_object& ask FC_ASSERT( filled_call || filled_limit ); + // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + // The maker won't be paying something for nothing according to code above + if( order_receives.amount == 0 ) + { + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + return 1; + else + wlog( "Something for nothing issue (#184, variant B) occurred at block #${block}", ("block",head_block_num()) ); + } + int result = 0; 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 @@ -589,7 +620,24 @@ asset database::match( const call_order_object& call, auto call_debt = call.get_debt(); asset call_receives = std::min(settle_for_sale, call_debt); - asset call_pays = call_receives * match_price; + asset call_pays = call_receives * match_price; // round down here, in favor of call order + + // Be here, the call order may be paying nothing. + // In this case, we favor the settle order after hard fork core-184 + // This may open a door to certain attacks, but should be fine due to fees + bool call_pays_rounded_up = false; + if( call_pays.amount == 0 ) + { + if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + { + wlog( "Something for nothing issue (#184, variant C) handled at block #${block}", ("block",head_block_num()) ); + call_pays.amount = 1; // this can trigger a black swan event, we'll check later + call_pays_rounded_up = true; + } + else + wlog( "Something for nothing issue (#184, variant C) occurred at block #${block}", ("block",head_block_num()) ); + } + asset settle_pays = call_receives; asset settle_receives = call_pays; @@ -607,6 +655,9 @@ asset database::match( const call_order_object& call, 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 + if( call_pays_rounded_up ) // implies next_maitenance_time > HARDFORK_CORE_184_TIME + GRAPHENE_ASSERT( !check_for_blackswan( settle_pays.asset_id(*this), true ), black_swan_exception, "" ); + return call_receives; } FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } @@ -865,6 +916,19 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan FC_ASSERT( filled_call || filled_limit ); FC_ASSERT( filled_call || filled_limit_in_loop ); + // Be here, the call order won't be paying something for nothing according to code above. + // After hard fork core-625, the limit order will be always a maker if entered this function, + // so it won't be paying something for nothing, since if it would, it would have been cancelled already. + // However, we need to check if it's culled after partially filled. + // Before hard fork core-625, + // when the limit order is a taker, it could be paying something for nothing here; + // when the limit order is a maker, it won't be paying something for nothing, + // however, if it's culled after partially filled, `limit_itr` may be invalidated so should not be dereferenced + if( order_receives.amount == 0 ) + { + wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); + } + auto old_call_itr = call_itr; if( filled_call && maint_time <= HARDFORK_CORE_343_TIME ) ++call_itr; @@ -873,22 +937,13 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan 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( maint_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_limit_order(*old_limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); - if( really_filled ) + bool really_filled = fill_limit_order( *limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); + if( really_filled || ( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) ) limit_itr = next_limit_itr; - } // whlie call_itr != call_end + } // while call_itr != call_end return margin_called; } FC_CAPTURE_AND_RETHROW() } diff --git a/libraries/chain/hardfork.d/CORE_184.hf b/libraries/chain/hardfork.d/CORE_184.hf new file mode 100644 index 0000000000..f40a286e68 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_184.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #184 Fix "Potential something-for-nothing fill bug" +#ifndef HARDFORK_CORE_184_TIME +#define HARDFORK_CORE_184_TIME (fc::time_point_sec( 1600000000 )) +#endif From d29edfc72e5b69f2825a8cf5be273a285524c4a8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 14 Feb 2018 02:33:54 +0000 Subject: [PATCH 04/56] Allow nothing for something for prediction market --- libraries/chain/db_market.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f9f11d0925..19553e824e 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -65,13 +65,13 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); while( call_itr != call_end ) { - auto pays = call_itr->get_debt() * settlement_price; + auto pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order if( pays > call_itr->get_collateral() ) pays = call_itr->get_collateral(); // Be here, the call order can be paying nothing, in this case, we take at least 1 Satoshi from call order - if( pays.amount == 0 ) + if( pays.amount == 0 && !bitasset.is_prediction_market ) { if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) pays.amount = 1; @@ -517,7 +517,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co if( usd_for_sale <= core_for_sale * match_price ) { core_receives = usd_for_sale; - usd_receives = usd_for_sale * match_price; + usd_receives = usd_for_sale * match_price; // round down, in favor of bigger order } else { @@ -526,7 +526,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co //Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price //Removing the assert seems to be safe -- apparently no asset is created or destroyed. usd_receives = core_for_sale; - core_receives = core_for_sale * match_price; + core_receives = core_for_sale * match_price; // round down, in favor of bigger order } core_pays = usd_receives; @@ -895,7 +895,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan if( usd_to_buy >= usd_for_sale ) { // fill order call_receives = usd_for_sale; - order_receives = usd_for_sale * match_price; + order_receives = usd_for_sale * match_price; // round down, in favor of call order call_pays = order_receives; order_pays = usd_for_sale; @@ -904,7 +904,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan filled_call = (usd_to_buy == usd_for_sale); } else { // fill call call_receives = usd_to_buy; - order_receives = usd_to_buy * match_price; + order_receives = usd_to_buy * match_price; // round down, in favor of call order call_pays = order_receives; order_pays = usd_to_buy; From 7a347eb9b548eb4a61ee943ba272b2fd21813b85 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 14 Feb 2018 18:53:42 +0000 Subject: [PATCH 05/56] Remove warning messages and add todo --- libraries/chain/db_market.cpp | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 19553e824e..3565e772a4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -75,7 +75,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett { if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) pays.amount = 1; - else + else // TODO remove this warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) ); } @@ -311,7 +311,7 @@ bool maybe_cull_small_order( database& db, const limit_order_object& order ) if( order.amount_to_receive().amount == 0 ) { if( order.deferred_fee > 0 && db.head_block_time() <= HARDFORK_CORE_604_TIME ) - { + { // TODO remove this warning after hard fork core-604 wlog( "At block ${n}, cancelling order without charging a fee: ${o}", ("n",db.head_block_num())("o",order) ); db.cancel_limit_order( order, true, true ); } @@ -538,13 +538,8 @@ int database::match( const limit_order_object& usd, const limit_order_object& co // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. - if( usd_receives.amount == 0 ) - { - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - return 1; - else - wlog( "Something for nothing issue (#184, variant A) occurred at block #${block}", ("block",head_block_num()) ); - } + if( usd_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + return 1; int result = 0; result |= fill_limit_order( usd, usd_pays, usd_receives, false, match_price, false ); // the first param is taker @@ -591,13 +586,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later // The maker won't be paying something for nothing according to code above - if( order_receives.amount == 0 ) - { - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - return 1; - else - wlog( "Something for nothing issue (#184, variant B) occurred at block #${block}", ("block",head_block_num()) ); - } + if( order_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + return 1; int result = 0; result |= fill_limit_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker @@ -629,12 +619,12 @@ asset database::match( const call_order_object& call, if( call_pays.amount == 0 ) { if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - { + { // TODO remove this warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant C) handled at block #${block}", ("block",head_block_num()) ); call_pays.amount = 1; // this can trigger a black swan event, we'll check later call_pays_rounded_up = true; } - else + else // TODO remove this warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant C) occurred at block #${block}", ("block",head_block_num()) ); } @@ -909,7 +899,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 && maint_time <= HARDFORK_CORE_453_TIME ) + if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) // TODO remove warning after hard fork core-453 wlog( "Multiple limit match problem (issue 453) occurred at block #${block}", ("block",head_block_num()) ); } @@ -925,7 +915,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan // when the limit order is a maker, it won't be paying something for nothing, // however, if it's culled after partially filled, `limit_itr` may be invalidated so should not be dereferenced if( order_receives.amount == 0 ) - { + { // TODO remove warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); } From 4012834d100e061e6cca8b28ad1ea4c982d818a9 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 19 Feb 2018 21:43:52 +0000 Subject: [PATCH 06/56] Handle something for nothing when call match limit --- libraries/chain/db_market.cpp | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 3565e772a4..830b7712c8 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -583,11 +583,22 @@ int database::match( const limit_order_object& bid, const call_order_object& ask FC_ASSERT( filled_call || filled_limit ); - // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. - // In this case, we see it as filled and cancel it later + // Be here, it's possible that taker is paying something for nothing. // The maker won't be paying something for nothing according to code above if( order_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - return 1; + { + // It's possible that taker is paying something for nothing due to call order too small. + // In this case, let the call pay 1 Satoshi + if( filled_call ) + { + order_receives.amount = 1; + call_pays = order_receives; + } + else + // It's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + return 1; + } int result = 0; result |= fill_limit_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker @@ -915,8 +926,20 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan // when the limit order is a maker, it won't be paying something for nothing, // however, if it's culled after partially filled, `limit_itr` may be invalidated so should not be dereferenced if( order_receives.amount == 0 ) - { // TODO remove warning after hard fork core-184 - wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); + { + if( maint_time > HARDFORK_CORE_184_TIME ) + { + if( filled_call ) // call would be completely filled // should always be true + { + order_receives.amount = 1; // round up to 1 Satoshi + call_pays = order_receives; + } + // else do nothing, since the limit order should have already been cancelled elsewhere + else // TODO remove warning after confirmed + wlog( "Something for nothing issue (#184, variant D-1) occurred at block #${block}", ("block",head_block_num()) ); + } + else // TODO remove warning after hard fork core-184 + wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); } auto old_call_itr = call_itr; From 0e9de1f1ed8540fd6e986fcae4484a313a7e0e3a Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 20 Feb 2018 19:42:02 +0000 Subject: [PATCH 07/56] Update settle order's something for nothing logic --- libraries/chain/db_market.cpp | 27 +++++++++++++++++---------- libraries/chain/db_update.cpp | 17 ++++++++++++++--- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 830b7712c8..48e44c7a5b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -624,16 +624,26 @@ asset database::match( const call_order_object& call, asset call_pays = call_receives * match_price; // round down here, in favor of call order // Be here, the call order may be paying nothing. - // In this case, we favor the settle order after hard fork core-184 - // This may open a door to certain attacks, but should be fine due to fees - bool call_pays_rounded_up = false; if( call_pays.amount == 0 ) { if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - { // TODO remove this warning after hard fork core-184 - wlog( "Something for nothing issue (#184, variant C) handled at block #${block}", ("block",head_block_num()) ); - call_pays.amount = 1; // this can trigger a black swan event, we'll check later - call_pays_rounded_up = true; + { + if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order + { + wlog( "Something for nothing issue (#184, variant C-1) handled at block #${block}", ("block",head_block_num()) ); + call_pays.amount = 1; + } + else + { + if( call_receives == settle.balance ) // the settle order is smaller + { + wlog( "Something for nothing issue (#184, variant C-2) handled at block #${block}", ("block",head_block_num()) ); + cancel_settle_order( settle ); + } + else // neither order will be completely filled, perhaps due to max_settlement too small + wlog( "Something for nothing issue (#184, variant C-3) handled at block #${block}", ("block",head_block_num()) ); + return asset( 0, settle.balance.asset_id ); + } } else // TODO remove this warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant C) occurred at block #${block}", ("block",head_block_num()) ); @@ -656,9 +666,6 @@ asset database::match( const call_order_object& call, 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 - if( call_pays_rounded_up ) // implies next_maitenance_time > HARDFORK_CORE_184_TIME - GRAPHENE_ASSERT( !check_for_blackswan( settle_pays.asset_id(*this), true ), black_swan_exception, "" ); - return call_receives; } FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 72ead5e172..cc7d60603d 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -296,9 +296,10 @@ void database::clear_expired_orders() asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); asset max_settlement_volume; price settlement_fill_price; + bool current_asset_finished = false; bool extra_dump = false; - auto next_asset = [¤t_asset, &settlement_index, &extra_dump] { + auto next_asset = [¤t_asset, ¤t_asset_finished, &settlement_index, &extra_dump] { auto bound = settlement_index.upper_bound(current_asset); if( bound == settlement_index.end() ) { @@ -313,6 +314,7 @@ void database::clear_expired_orders() ilog( "next_asset returning true, bound is ${b}", ("b", *bound) ); } current_asset = bound->settlement_asset_id(); + current_asset_finished = false; return true; }; @@ -368,7 +370,9 @@ void database::clear_expired_orders() } if( max_settlement_volume.asset_id != current_asset ) max_settlement_volume = mia_object.amount(mia.max_force_settlement_volume(mia_object.dynamic_data(*this).current_supply)); - if( mia.force_settled_volume >= max_settlement_volume.amount ) + // When current_asset_finished is true, this would be the 2nd time processing the same order. + // In this case, we move to the next asset. + if( mia.force_settled_volume >= max_settlement_volume.amount || current_asset_finished ) { /* ilog("Skipping force settlement in ${asset}; settled ${settled_volume} / ${max_volume}", @@ -423,7 +427,14 @@ void database::clear_expired_orders() break; } try { - settled += match(*itr, order, settlement_price, max_settlement, settlement_fill_price); + asset new_settled = match(*itr, order, settlement_price, max_settlement, settlement_fill_price); + if( maint_time > HARDFORK_CORE_184_TIME && new_settled.amount == 0 ) // unable to fill this settle order + { + if( find_object( order_id ) ) // the settle order hasn't been cancelled + current_asset_finished = true; + break; + } + settled += new_settled; } catch ( const black_swan_exception& e ) { wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) ); From 2253696e2ce7b648523dc08cbf9cf59f1c4fcf11 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 23 Feb 2018 19:49:13 +0000 Subject: [PATCH 08/56] Something for nothing: settle against global fund --- libraries/chain/asset_evaluator.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 9c7ac0cd62..99449ea88f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -521,6 +521,14 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: else FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund ); // should be strictly < except for PM with zero outcome + if( settled_amount.amount == 0 && !bitasset.is_prediction_market ) + { + if( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + FC_THROW( "Settle amount is too small to receive anything due to rounding" ); + else // TODO remove this warning after hard fork core-184 + wlog( "Something for nothing issue (#184, variant F) occurred at block #${block}", ("block",d.head_block_num()) ); + } + d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ obj.settlement_fund -= settled_amount.amount; }); From 8ba29b829a538212b3cf00edadd4b35576c85247 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 15:39:57 +0000 Subject: [PATCH 09/56] Operator to multiply asset by price then round up --- .../include/graphene/chain/protocol/asset.hpp | 3 ++- libraries/chain/protocol/asset.cpp | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/protocol/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp index 20f8543fdb..4cb02c7e7f 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -144,7 +144,8 @@ namespace graphene { namespace chain { bool operator >= ( const price& a, const price& b ); bool operator == ( const price& a, const price& b ); bool operator != ( const price& a, const price& b ); - asset operator * ( const asset& a, const price& b ); + asset operator * ( const asset& a, const price& b ); ///< Multiply and round down + asset operator ^ ( const asset& a, const price& b ); ///< Multiply and round up price operator * ( const price& p, const ratio_type& r ); price operator / ( const price& p, const ratio_type& r ); diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index 52f68152c5..dc3a531662 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -92,6 +92,25 @@ namespace graphene { namespace chain { FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) ); } + asset operator ^ ( const asset& a, const price& b ) + { + if( a.asset_id == b.base.asset_id ) + { + FC_ASSERT( b.base.amount.value > 0 ); + uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value + b.base.amount.value - 1)/b.base.amount.value; + FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY ); + return asset( result.convert_to(), b.quote.asset_id ); + } + else if( a.asset_id == b.quote.asset_id ) + { + FC_ASSERT( b.quote.amount.value > 0 ); + uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value + b.quote.amount.value - 1)/b.quote.amount.value; + FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY ); + return asset( result.convert_to(), b.base.asset_id ); + } + FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) ); + } + price operator / ( const asset& base, const asset& quote ) { try { FC_ASSERT( base.asset_id != quote.asset_id ); From 652cc633b0d9a43a43c57fe32426b9c9c60b6e15 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 16:29:38 +0000 Subject: [PATCH 10/56] Better rounding when matching 2 limit orders #342 --- libraries/chain/db_market.cpp | 46 ++++++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 48e44c7a5b..8a0019d54b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -514,10 +514,31 @@ int database::match( const limit_order_object& usd, const limit_order_object& co asset usd_pays, usd_receives, core_pays, core_receives; - if( usd_for_sale <= core_for_sale * match_price ) + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); + + bool cull_taker = false; + if( usd_for_sale <= core_for_sale * match_price ) // rounding down here should be fine { - core_receives = usd_for_sale; usd_receives = usd_for_sale * match_price; // round down, in favor of bigger order + + // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. + if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + return 1; + + if( before_hardfork_342 ) + core_receives = usd_for_sale; + else + { + // The remaining amount in order `usd` would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + core_receives = usd_receives ^ match_price; + cull_taker = true; + } } else { @@ -525,24 +546,25 @@ int database::match( const limit_order_object& usd, const limit_order_object& co //This assert is not always true -- see trade_amount_equals_zero in operation_tests.cpp //Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price //Removing the assert seems to be safe -- apparently no asset is created or destroyed. - usd_receives = core_for_sale; + core_receives = core_for_sale * match_price; // round down, in favor of bigger order + if( before_hardfork_342 ) + usd_receives = core_for_sale; + else + // The remaining amount in order `core` would be too small, + // so the order will be culled in fill_limit_order() below + usd_receives = core_receives ^ match_price; } core_pays = usd_receives; usd_pays = core_receives; - assert( usd_pays == usd.amount_for_sale() || - core_pays == core.amount_for_sale() ); - - // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. - // In this case, we see it as filled and cancel it later - // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. - if( usd_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - return 1; + if( before_hardfork_342 ) + 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( usd, usd_pays, usd_receives, cull_taker, 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; From 2546105cfa03e726b2eb96ad7345652366ad0e9a Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 17:44:46 +0000 Subject: [PATCH 11/56] Better rounding when matching limit:call #342 --- libraries/chain/db_market.cpp | 65 +++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 30 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 8a0019d54b..db55851fe6 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -524,7 +524,6 @@ int database::match( const limit_order_object& usd, const limit_order_object& co // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later - // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) return 1; @@ -547,6 +546,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co //Although usd_for_sale is greater than core_for_sale * match_price, core_for_sale == usd_for_sale * match_price //Removing the assert seems to be safe -- apparently no asset is created or destroyed. + // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. core_receives = core_for_sale * match_price; // round down, in favor of bigger order if( before_hardfork_342 ) usd_receives = core_for_sale; @@ -579,51 +579,56 @@ int database::match( const limit_order_object& bid, const call_order_object& ask bool filled_limit = false; bool filled_call = false; + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // TODO remove when we're sure it's always false + + bool cull_taker = 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 ) + 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 ); + // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. + // In this case, we see it as filled and cancel it later + // TODO remove hardfork check when we're sure it's always true (but keep the zero amount check) + if( order_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + return 1; + + if( before_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) + call_receives = usd_for_sale; + else + { + // The remaining amount in the limit order would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + call_receives = order_receives ^ match_price; + cull_taker = true; + } } 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 ); - - // Be here, it's possible that taker is paying something for nothing. - // The maker won't be paying something for nothing according to code above - if( order_receives.amount == 0 && get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - { - // It's possible that taker is paying something for nothing due to call order too small. - // In this case, let the call pay 1 Satoshi - if( filled_call ) + if( before_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) { - order_receives.amount = 1; - call_pays = order_receives; + order_receives = usd_to_buy * match_price; // round down here, in favor of call order + // TODO remove hardfork check when we're sure it's always true (but keep the zero amount check) + if( order_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + return 1; } - else - // It's possible that taker is paying something for nothing due to partially filled in last loop. - // In this case, we see it as filled and cancel it later - return 1; + else // has hardfork core-342 + order_receives = usd_to_buy ^ match_price; // round up here, in favor of limit order } + call_pays = order_receives; + order_pays = call_receives; + int result = 0; - result |= fill_limit_order( bid, order_pays, order_receives, false, match_price, false ); // the limit order is taker + result |= fill_limit_order( bid, order_pays, order_receives, cull_taker, 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; From aeced02368994321c812f1cebea7c7df8bbcbaaf Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 20:25:34 +0000 Subject: [PATCH 12/56] Better rounding when matching call:limit #342 --- libraries/chain/db_market.cpp | 116 +++++++++++++++++++--------------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index db55851fe6..3aa41f2c95 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -515,7 +515,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co asset usd_pays, usd_receives, core_pays, core_receives; auto maint_time = get_dynamic_global_properties().next_maintenance_time; - bool before_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; if( usd_for_sale <= core_for_sale * match_price ) // rounding down here should be fine @@ -527,7 +527,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co if( usd_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) return 1; - if( before_hardfork_342 ) + if( before_core_hardfork_342 ) core_receives = usd_for_sale; else { @@ -548,7 +548,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co // The maker won't be paying something for nothing, since if it would, it would have been cancelled already. core_receives = core_for_sale * match_price; // round down, in favor of bigger order - if( before_hardfork_342 ) + if( before_core_hardfork_342 ) usd_receives = core_for_sale; else // The remaining amount in order `core` would be too small, @@ -559,7 +559,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co core_pays = usd_receives; usd_pays = core_receives; - if( before_hardfork_342 ) + if( before_core_hardfork_342 ) assert( usd_pays == usd.amount_for_sale() || core_pays == core.amount_for_sale() ); @@ -580,7 +580,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask bool filled_call = false; auto maint_time = get_dynamic_global_properties().next_maintenance_time; - bool before_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // TODO remove when we're sure it's always false + // TODO remove when we're sure it's always false + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding bool cull_taker = false; @@ -598,7 +599,7 @@ int database::match( const limit_order_object& bid, const call_order_object& ask if( order_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) return 1; - if( before_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) + if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) call_receives = usd_for_sale; else { @@ -613,7 +614,7 @@ int database::match( const limit_order_object& bid, const call_order_object& ask else { // fill call order call_receives = usd_to_buy; - if( before_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) + if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) { order_receives = usd_to_buy * match_price; // round down here, in favor of call order // TODO remove hardfork check when we're sure it's always true (but keep the zero amount check) @@ -888,9 +889,17 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan auto head_time = head_block_time(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; + + bool after_hardfork_436 = ( head_time > HARDFORK_436_TIME ); + + bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding + bool before_core_hardfork_343 = ( maint_time <= HARDFORK_CORE_343_TIME ); // update call_price after partially filled + bool before_core_hardfork_453 = ( maint_time <= HARDFORK_CORE_453_TIME ); // multiple matching issue + bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call + 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; @@ -905,12 +914,11 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan match_price.validate(); // 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 ) ) + if( after_hardfork_436 && ( bitasset.current_feed.settlement_price > ~call_itr->call_price ) ) return margin_called; // 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 ) + if( before_core_hardfork_606 && match_price > ~call_itr->call_price ) return margin_called; margin_called = true; @@ -927,67 +935,73 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan } asset call_pays, call_receives, order_pays, order_receives; - if( usd_to_buy >= usd_for_sale ) + if( usd_to_buy > usd_for_sale ) { // fill order - call_receives = usd_for_sale; order_receives = usd_for_sale * match_price; // round down, in favor of call order - call_pays = order_receives; - order_pays = usd_for_sale; - filled_limit_in_loop = true; + // Be here, the limit order won't be paying something for nothing, since if it would, it would have + // been cancelled elsewhere already (a maker limit order won't be paying something for nothing): + // * after hard fork core-625, the limit order will be always a maker if entered this function; + // * before hard fork core-625, + // * when the limit order is a taker, it could be paying something for nothing only when + // the call order is smaller and is too small + // * when the limit order is a maker, it won't be paying something for nothing + if( order_receives.amount == 0 ) // TODO this should not happen. remove the warning after confirmed + { + if( before_core_hardfork_184 ) + wlog( "Something for nothing issue (#184, variant D-1) occurred at block #${block}", ("block",head_block_num()) ); + else + wlog( "Something for nothing issue (#184, variant D-2) occurred at block #${block}", ("block",head_block_num()) ); + } + + if( before_core_hardfork_342 ) + call_receives = usd_for_sale; + else + // The remaining amount in the limit order would be too small, + // so we should cull the order in fill_limit_order() below. + // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, + // so calling maybe_cull_small() will always cull it. + call_receives = usd_receives ^ match_price; + filled_limit = true; - filled_call = (usd_to_buy == usd_for_sale); + } else { // fill call call_receives = usd_to_buy; - order_receives = usd_to_buy * match_price; // round down, in favor of call order - call_pays = order_receives; - order_pays = usd_to_buy; + + if( before_core_hardfork_342 ) + { + order_receives = usd_to_buy * match_price; // round down, in favor of call order + + // Be here, the limit order would be paying something for nothing + if( order_receives.amount == 0 ) // TODO remove warning after hard fork core-342 + wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); + } + else + order_receives = usd_to_buy ^ match_price; // round up, in favor of limit order filled_call = true; - if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) // TODO remove warning after hard fork core-453 + + if( usd_to_buy == usd_for_sale ) + filled_limit = true; + else if( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) // TODO remove warning after hard fork core-453 wlog( "Multiple limit match problem (issue 453) occurred at block #${block}", ("block",head_block_num()) ); } - FC_ASSERT( filled_call || filled_limit ); - FC_ASSERT( filled_call || filled_limit_in_loop ); - - // Be here, the call order won't be paying something for nothing according to code above. - // After hard fork core-625, the limit order will be always a maker if entered this function, - // so it won't be paying something for nothing, since if it would, it would have been cancelled already. - // However, we need to check if it's culled after partially filled. - // Before hard fork core-625, - // when the limit order is a taker, it could be paying something for nothing here; - // when the limit order is a maker, it won't be paying something for nothing, - // however, if it's culled after partially filled, `limit_itr` may be invalidated so should not be dereferenced - if( order_receives.amount == 0 ) - { - if( maint_time > HARDFORK_CORE_184_TIME ) - { - if( filled_call ) // call would be completely filled // should always be true - { - order_receives.amount = 1; // round up to 1 Satoshi - call_pays = order_receives; - } - // else do nothing, since the limit order should have already been cancelled elsewhere - else // TODO remove warning after confirmed - wlog( "Something for nothing issue (#184, variant D-1) occurred at block #${block}", ("block",head_block_num()) ); - } - else // TODO remove warning after hard fork core-184 - wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); - } + call_pays = order_receives; + order_pays = call_receives; auto old_call_itr = call_itr; - if( filled_call && maint_time <= HARDFORK_CORE_343_TIME ) + if( filled_call && before_core_hardfork_343 ) ++call_itr; // when for_new_limit_order is true, the call order is maker, otherwise the call order is taker fill_call_order(*old_call_itr, call_pays, call_receives, match_price, for_new_limit_order ); - if( maint_time > HARDFORK_CORE_343_TIME ) + if( !before_core_hardfork_343 ) call_itr = call_price_index.lower_bound( call_min ); auto next_limit_itr = std::next( limit_itr ); // when for_new_limit_order is true, the limit order is taker, otherwise the limit order is maker bool really_filled = fill_limit_order( *limit_itr, order_pays, order_receives, true, match_price, !for_new_limit_order ); - if( really_filled || ( filled_limit && maint_time <= HARDFORK_CORE_453_TIME ) ) + if( really_filled || ( filled_limit && before_core_hardfork_453 ) ) limit_itr = next_limit_itr; } // while call_itr != call_end From 729fcedb2791574fa7b76723a85e3b104a4a5e23 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 21:27:02 +0000 Subject: [PATCH 13/56] Fix typo, remove unused variables --- 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 3aa41f2c95..888a3eb59a 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -576,9 +576,6 @@ int database::match( const limit_order_object& bid, const call_order_object& ask 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; - auto maint_time = get_dynamic_global_properties().next_maintenance_time; // TODO remove when we're sure it's always false bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding @@ -961,7 +958,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan // so we should cull the order in fill_limit_order() below. // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. - call_receives = usd_receives ^ match_price; + call_receives = order_receives ^ match_price; filled_limit = true; From 6d3a2720881a1631927fd93a79fadf17e0b73898 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 21:31:40 +0000 Subject: [PATCH 14/56] Better rounding when globally settling #342 --- libraries/chain/db_market.cpp | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 888a3eb59a..cb6b70c4e4 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -60,24 +60,28 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding + // cancel all call orders and accumulate it into collateral_gathered auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) ); auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) ); + asset pays; while( call_itr != call_end ) { - auto pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order - - if( pays > call_itr->get_collateral() ) - pays = call_itr->get_collateral(); - - // Be here, the call order can be paying nothing, in this case, we take at least 1 Satoshi from call order - if( pays.amount == 0 && !bitasset.is_prediction_market ) + if( before_core_hardfork_342 ) { - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) - pays.amount = 1; - else // TODO remove this warning after hard fork core-184 + pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order + + // Be here, the call order can be paying nothing + if( pays.amount == 0 && !bitasset.is_prediction_market ) // TODO remove this warning after hard fork core-342 wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) ); } + else + pays = call_itr->get_debt() ^ settlement_price; // round up, in favor of global settlement fund + + if( pays > call_itr->get_collateral() ) + pays = call_itr->get_collateral(); collateral_gathered += pays; const auto& order = *call_itr; From 2a0fa9119c22c0f08e9c22276e57c641aed0c09a Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 15 Mar 2018 10:37:44 +0000 Subject: [PATCH 15/56] Add core-342 hard fork definition file --- libraries/chain/hardfork.d/CORE_342.hf | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 libraries/chain/hardfork.d/CORE_342.hf diff --git a/libraries/chain/hardfork.d/CORE_342.hf b/libraries/chain/hardfork.d/CORE_342.hf new file mode 100644 index 0000000000..5023563833 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_342.hf @@ -0,0 +1,5 @@ +// bitshares-core issue #342 +// Mitigate rounding issue when matching orders +#ifndef HARDFORK_CORE_342_TIME +#define HARDFORK_CORE_342_TIME (fc::time_point_sec( 1600000000 )) +#endif From d71009ef5a5be52bc49a79c6c7e8f6ef33ded0cf Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 3 Apr 2018 16:17:32 -0400 Subject: [PATCH 16/56] Better rounding: settling against global fund #342 --- libraries/chain/asset_evaluator.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 99449ea88f..5550b51d31 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -508,14 +508,13 @@ void_result asset_settle_evaluator::do_evaluate(const asset_settle_evaluator::op operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op) { try { database& d = db(); - d.adjust_balance(op.account, -op.amount); const auto& bitasset = asset_to_settle->bitasset_data(d); if( bitasset.has_settlement() ) { const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); - auto settled_amount = op.amount * bitasset.settlement_price; + auto settled_amount = op.amount * bitasset.settlement_price; // round down, in favor of global settlement fund if( op.amount.amount == mia_dyn.current_supply ) settled_amount.amount = bitasset.settlement_fund; // avoid rounding problems else @@ -529,20 +528,34 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: wlog( "Something for nothing issue (#184, variant F) occurred at block #${block}", ("block",d.head_block_num()) ); } - d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ - obj.settlement_fund -= settled_amount.amount; - }); + asset pays = op.amount; + if( op.amount.amount != mia_dyn.current_supply + && settled_amount.amount != 0 + && d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_342_TIME ) + { + pays = settled_amount ^ bitasset.settlement_price; + } - d.adjust_balance(op.account, settled_amount); + d.adjust_balance( op.account, -pays ); + + if( settled_amount.amount > 0 ) + { + d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){ + obj.settlement_fund -= settled_amount.amount; + }); + + d.adjust_balance( op.account, settled_amount ); + } d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ - obj.current_supply -= op.amount.amount; - }); + obj.current_supply -= pays.amount; + }); return settled_amount; } else { + d.adjust_balance( op.account, -op.amount ); return d.create([&](force_settlement_object& s) { s.owner = op.account; s.balance = op.amount; From 52a165fe10975d1cd69fa7622173ebca0f26b585 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 5 Apr 2018 08:58:32 -0400 Subject: [PATCH 17/56] Better rounding when matching settle:call #342 --- libraries/chain/db_market.cpp | 50 ++++++++++++++++++++++++++++++++--- libraries/chain/db_update.cpp | 10 +++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index cb6b70c4e4..236202728b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -646,16 +646,21 @@ asset database::match( const call_order_object& call, FC_ASSERT(call.get_debt().asset_id == settle.balance.asset_id ); FC_ASSERT(call.debt > 0 && call.collateral > 0 && settle.balance.amount > 0); + auto maint_time = get_dynamic_global_properties().next_maintenance_time; + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding + auto settle_for_sale = std::min(settle.balance, max_settlement); auto call_debt = call.get_debt(); asset call_receives = std::min(settle_for_sale, call_debt); - asset call_pays = call_receives * match_price; // round down here, in favor of call order + asset call_pays = call_receives * match_price; // round down here, in favor of call order, for first check + // TODO possible optimization: check need to round up or down first // Be here, the call order may be paying nothing. + bool cull_settle_order = false; // whether need to cancel dust settle order if( call_pays.amount == 0 ) { - if( get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_184_TIME ) + if( maint_time > HARDFORK_CORE_184_TIME ) { if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order { @@ -677,6 +682,33 @@ asset database::match( const call_order_object& call, else // TODO remove this warning after hard fork core-184 wlog( "Something for nothing issue (#184, variant C) occurred at block #${block}", ("block",head_block_num()) ); } + else // the call order is not paying nothing, but still possible it's paying more than minimum required due to rounding + { + if( !before_core_hardfork_342 ) + { + if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order + { + call_pays = call_receives ^ match_price; // round up here, in favor of settle order + // be here, we should have: call_pays <= call_collateral + } + else + { + // be here, call_pays has been rounded down + + // be here, we should have: call_pays <= call_collateral + + if( call_receives == settle.balance ) // the settle order will be completely filled, assuming we need to cull it + cull_settle_order = true; + // else do nothing, since we can't cull the settle order + + call_receives = call_pays ^ match_price; // round up here to mitigate rouding issue (core-342) + + if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull + cull_settle_order = false; + // else do nothing, since we still need to cull the settle order or still can't cull the settle order + } + } + } asset settle_pays = call_receives; asset settle_receives = call_pays; @@ -688,13 +720,23 @@ asset database::match( const call_order_object& call, * can trigger a black swan. So now we must cancel the forced settlement * object. */ - GRAPHENE_ASSERT( call_pays < call.get_collateral(), black_swan_exception, "" ); + if( before_core_hardfork_342 ) + { + auto call_collateral = call.get_collateral(); + if( call_pays == call_collateral ) + wlog( "Incorrectly captured black swan event at block #${block}", ("block",head_block_num()) ); + GRAPHENE_ASSERT( call_pays < call_collateral, black_swan_exception, "" ); - assert( settle_pays == settle_for_sale || call_receives == call.get_debt() ); + assert( settle_pays == settle_for_sale || call_receives == call.get_debt() ); + } + // else do nothing, since black swan event won't happen, and the assertion is no longer true 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 + if( cull_settle_order ) + cancel_settle_order( settle ); + return call_receives; } FC_CAPTURE_AND_RETHROW( (call)(settle)(match_price)(max_settlement) ) } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index cc7d60603d..4e178843c2 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -435,6 +435,16 @@ void database::clear_expired_orders() break; } settled += new_settled; + // before hard fork core-342, if new_settled > 0, we'll have: + // * call order is completely filled (thus itr will change in next loop), or + // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). + // + // after hard fork core-342, if new_settled > 0, we'll have: + // * call order is completely filled (thus itr will change in next loop), or + // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or + // * reached max_settlement_volume limit, but it's possible that new_settled < max_settlement, + // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } catch ( const black_swan_exception& e ) { wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) ); From f71a9c2cb010e329bbd95aae661f7ec5afaa31cd Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 6 Apr 2018 08:51:16 -0400 Subject: [PATCH 18/56] Logging tweak --- libraries/chain/db_market.cpp | 3 ++- libraries/chain/db_update.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 236202728b..d6f976b353 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -970,7 +970,8 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan if( usd_to_buy * match_price > call_itr->get_collateral() ) { - elog( "black swan detected" ); + elog( "black swan detected on asset ${symbol} (${id}) at block ${b}", + ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) ); edump((enable_black_swan)); FC_ASSERT( enable_black_swan ); globally_settle_asset(mia, bitasset.current_feed.settlement_price ); diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 4e178843c2..0b8304ca28 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -245,15 +245,17 @@ bool database::check_for_blackswan( const asset_object& mia, bool enable_black_s if( ~least_collateral >= highest ) { wdump( (*call_itr) ); - elog( "Black Swan detected: \n" + elog( "Black Swan detected on asset ${symbol} (${id}) at block ${b}: \n" " Least collateralized call: ${lc} ${~lc}\n" // " Highest Bid: ${hb} ${~hb}\n" " Settle Price: ${~sp} ${sp}\n" " Max: ${~h} ${h}\n", + ("id",mia.id)("symbol",mia.symbol)("b",head_block_num()) ("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()) ); + edump((enable_black_swan)); 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 ) // global settle at feed price if possible @@ -447,7 +449,8 @@ void database::clear_expired_orders() // in this case, new_settled will be zero in next iteration of the loop, so no need to check here. } catch ( const black_swan_exception& e ) { - wlog( "black swan detected: ${e}", ("e", e.to_detail_string() ) ); + wlog( "Cancelling a settle_order since it may trigger a black swan: ${o}, ${e}", + ("o", order)("e", e.to_detail_string()) ); cancel_settle_order( order ); break; } From 891ea932093e36d5d041b449edcdd4f0fd4bd670 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 14 Feb 2018 01:28:44 +0000 Subject: [PATCH 19/56] Tests something for nothing after hardfork #184 --- tests/tests/operation_tests.cpp | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 268c866cfa..f66ee94616 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1700,6 +1700,53 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) } } +/** + * The something-for-nothing bug should be fixed https://github.com/bitshares/bitshares-core/issues/184 + */ +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) +{ + try { + INVOKE(issue_uia); + generate_blocks( HARDFORK_CORE_184_TIME ); + set_expiration( db, trx ); + + const asset_object& test = get_asset( UIA_TEST_SYMBOL ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + const account_object& core_seller = create_account( "shorter1" ); + const account_object& core_buyer = get_account("nathan"); + + transfer( committee_account(db), core_seller, asset( 100000000 ) ); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); + + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_buyer, test.amount(3), core.amount(1)); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 2); + + generate_block(); + + auto result = get_market_order_history(core_id, test_id); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK(result[0].op.pays == core.amount(1)); + BOOST_CHECK(result[0].op.receives == test.amount(2)); + BOOST_CHECK(result[1].op.pays == test.amount(2)); + BOOST_CHECK(result[1].op.receives == core.amount(1)); + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /** * Create an order that cannot be filled immediately and have the * transaction fail. From 38da7e60f82fdd2098815a591f0bb18c4584e62b Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 20 Feb 2018 20:00:27 +0000 Subject: [PATCH 20/56] Tests limit:call something-for-nothing after hf --- tests/tests/market_tests.cpp | 226 +++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 04ca3871ff..c34109144f 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1266,4 +1266,230 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) } FC_LOG_AND_RETHROW() } +/*** + * Fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // 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( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Another test case + * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Yet another test case + * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // The remaining USD will be in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 6fb3226b46e4af58acf691fecb57f51e017ae70d Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 5 Apr 2018 17:41:29 -0400 Subject: [PATCH 21/56] Test case for (asset * price) and (asset ^ price) --- tests/tests/basic_tests.cpp | 43 +++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index f85848e4a7..95ae54646a 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -242,6 +242,49 @@ BOOST_AUTO_TEST_CASE( price_test ) BOOST_CHECK( less_than_max2 * ratio_type(1,1) == less_than_max2 ); BOOST_CHECK( less_than_max2 * ratio_type(5,2) == price(asset(less_than_max2.base.amount*5/2/7),asset(1,asset_id_type(1))) ); + BOOST_CHECK( ( asset(1) * price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( ( asset(1) * price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( ( asset(1, asset_id_type(1)) * price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) ); + BOOST_CHECK( ( asset(1, asset_id_type(1)) * price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) ); + + BOOST_CHECK( ( asset(3) * price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) ); // round_down(3*5/3) + BOOST_CHECK( ( asset(5) * price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(1, asset_id_type(1)) ); // round_down(5*2/7) + BOOST_CHECK( ( asset(7, asset_id_type(1)) * price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(4) ); // round_down(7*2/3) + BOOST_CHECK( ( asset(9, asset_id_type(1)) * price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(7) ); // round_down(9*7/8) + + // asset and price doesn't match + BOOST_CHECK_THROW( asset(1) * price( asset(1, asset_id_type(2)), asset(1, asset_id_type(1)) ), fc::assert_exception ); + // divide by zero + BOOST_CHECK_THROW( asset(1) * price( asset(0), asset(1, asset_id_type(1)) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(1) * price( asset(1, asset_id_type(1)), asset(0) ), fc::assert_exception ); + // overflow + BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1) * price( asset(1), asset(2, asset_id_type(1)) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(2) * price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ), fc::assert_exception ); + + // Note: `==` executes before `^`, so need `( a ^ b ) == c` + BOOST_CHECK( ( asset(1) ^ price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( ( asset(1) ^ price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( ( asset(1, asset_id_type(1)) ^ price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) ); + BOOST_CHECK( ( asset(1, asset_id_type(1)) ^ price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) ); + + BOOST_CHECK( ( asset(3) ^ price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) ); // round_up(3*5/3) + BOOST_CHECK( ( asset(5) ^ price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(2, asset_id_type(1)) ); // round_up(5*2/7) + BOOST_CHECK( ( asset(7, asset_id_type(1)) ^ price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(5) ); // round_up(7*2/3) + BOOST_CHECK( ( asset(9, asset_id_type(1)) ^ price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(8) ); // round_up(9*7/8) + + // asset and price doesn't match + BOOST_CHECK_THROW( asset(1, asset_id_type(3)) ^ price( asset(1, asset_id_type(2)), asset(1) ), fc::assert_exception ); + // divide by zero + BOOST_CHECK_THROW( asset(1) ^ price( asset(0), asset(1, asset_id_type(1)) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(1) ^ price( asset(1, asset_id_type(1)), asset(0) ), fc::assert_exception ); + // overflow + BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1) ^ price( asset(1), asset(2, asset_id_type(1)) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(2) ^ price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ), fc::assert_exception ); + + // checks that operator^() executes before operator=() + auto x = asset(3) ^ price( asset(6), asset(10, asset_id_type(1)) ); + BOOST_CHECK( x == asset(5, asset_id_type(1)) ); + price_feed dummy; dummy.maintenance_collateral_ratio = 1002; dummy.maximum_short_squeeze_ratio = 1234; From 921b5bee8f8071303ce9bbc2f0ed49ad01e52560 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 28 Feb 2018 21:52:39 +0000 Subject: [PATCH 22/56] Adapt black swan test to new rounding rules #342 --- tests/tests/market_tests.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index c34109144f..0071919256 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -908,16 +908,16 @@ 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 ); + // since least collateral ratio 15.5 < 20, global settlement should execute at price = least collateral ratio 15.5/1 + // so settlement fund should be 15500 + 15500 + round_up(15.5 * 293) + BOOST_CHECK_EQUAL( 15500*2 + (293 * 155 + 9) / 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( price(asset(2293,usd_id),asset(15500*2+(293*155+9)/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( init_balance-6464-(293*155+9)/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) ); From d15080e557ea5bce91cb9f4c6b3bbb24902ebda8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 12 Apr 2018 18:49:56 -0400 Subject: [PATCH 23/56] Fix typo in comment --- libraries/chain/db_market.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 039f0aa7d4..602a0e5188 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -701,7 +701,7 @@ asset database::match( const call_order_object& call, cull_settle_order = true; // else do nothing, since we can't cull the settle order - call_receives = call_pays ^ match_price; // round up here to mitigate rouding issue (core-342) + call_receives = call_pays ^ match_price; // round up here to mitigate rounding issue (core-342) if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull cull_settle_order = false; From bbef791401f1f9a4fd10df6bbb2c9a0273efedc8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 12 Apr 2018 19:01:50 -0400 Subject: [PATCH 24/56] Add todo to remove warning message after hardfork --- libraries/chain/db_market.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 602a0e5188..8dff1c0ed8 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -723,7 +723,7 @@ asset database::match( const call_order_object& call, if( before_core_hardfork_342 ) { auto call_collateral = call.get_collateral(); - if( call_pays == call_collateral ) + if( call_pays == call_collateral ) // TODO remove warning after hard fork core-342 wlog( "Incorrectly captured black swan event at block #${block}", ("block",head_block_num()) ); GRAPHENE_ASSERT( call_pays < call_collateral, black_swan_exception, "" ); From ccec7817221aa66c7faf95c8972175f69da3efcc Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 13 Apr 2018 03:50:34 -0400 Subject: [PATCH 25/56] Move HF 625 check in check_call_orders to the top --- libraries/chain/db_market.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 8dff1c0ed8..5aa83cca0f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -894,6 +894,11 @@ bool database::fill_settle_order( const force_settlement_object& settle, const a */ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan, bool for_new_limit_order ) { try { + 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 + if( !mia.is_market_issued() ) return false; if( check_for_blackswan( mia, enable_black_swan ) ) @@ -903,11 +908,6 @@ 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(); From 2aedd11de027b873bd137f28fd6ba8c796fe69ea Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 13 Apr 2018 06:04:23 -0400 Subject: [PATCH 26/56] Change assert() in asset_object.hpp to FC_ASSERT() --- libraries/chain/include/graphene/chain/asset_object.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index a337152bf6..1ee8fe1651 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -145,7 +145,12 @@ namespace graphene { namespace chain { template const asset_bitasset_data_object& bitasset_data(const DB& db)const - { assert(bitasset_data_id); return db.get(*bitasset_data_id); } + { + FC_ASSERT( bitasset_data_id.valid(), + "Asset ${a} (${id}) is not a market issued asset.", + ("a",this->symbol)("id",this->id) ); + return db.get( *bitasset_data_id ); + } template const asset_dynamic_data_object& dynamic_data(const DB& db)const From c6b28b2f0534f3a5710543a9d6f029a614432382 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Apr 2018 07:59:53 -0400 Subject: [PATCH 27/56] Code refactory asset::multiply_and_round_up(price) --- libraries/chain/asset_evaluator.cpp | 2 +- libraries/chain/db_market.cpp | 18 ++++----- .../include/graphene/chain/protocol/asset.hpp | 5 ++- libraries/chain/protocol/asset.cpp | 5 ++- tests/tests/basic_tests.cpp | 40 ++++++++++--------- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 5550b51d31..01517e9da9 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -533,7 +533,7 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: && settled_amount.amount != 0 && d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_342_TIME ) { - pays = settled_amount ^ bitasset.settlement_price; + pays = settled_amount.multiply_and_round_up( bitasset.settlement_price ); } d.adjust_balance( op.account, -pays ); diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 5aa83cca0f..cd8975df6f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -78,7 +78,7 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) ); } else - pays = call_itr->get_debt() ^ settlement_price; // round up, in favor of global settlement fund + pays = call_itr->get_debt().multiply_and_round_up( settlement_price ); // round up, in favor of global settlement fund if( pays > call_itr->get_collateral() ) pays = call_itr->get_collateral(); @@ -539,7 +539,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co // so we should cull the order in fill_limit_order() below. // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. - core_receives = usd_receives ^ match_price; + core_receives = usd_receives.multiply_and_round_up( match_price ); cull_taker = true; } } @@ -557,7 +557,7 @@ int database::match( const limit_order_object& usd, const limit_order_object& co else // The remaining amount in order `core` would be too small, // so the order will be culled in fill_limit_order() below - usd_receives = core_receives ^ match_price; + usd_receives = core_receives.multiply_and_round_up( match_price ); } core_pays = usd_receives; @@ -608,7 +608,7 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // so we should cull the order in fill_limit_order() below. // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. - call_receives = order_receives ^ match_price; + call_receives = order_receives.multiply_and_round_up( match_price ); cull_taker = true; } } @@ -623,7 +623,7 @@ int database::match( const limit_order_object& bid, const call_order_object& ask return 1; } else // has hardfork core-342 - order_receives = usd_to_buy ^ match_price; // round up here, in favor of limit order + order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up here, in favor of limit order } call_pays = order_receives; @@ -688,7 +688,7 @@ asset database::match( const call_order_object& call, { if( call_receives == call_debt ) // the call order is smaller than or equal to the settle order { - call_pays = call_receives ^ match_price; // round up here, in favor of settle order + call_pays = call_receives.multiply_and_round_up( match_price ); // round up here, in favor of settle order // be here, we should have: call_pays <= call_collateral } else @@ -701,7 +701,7 @@ asset database::match( const call_order_object& call, cull_settle_order = true; // else do nothing, since we can't cull the settle order - call_receives = call_pays ^ match_price; // round up here to mitigate rounding issue (core-342) + call_receives = call_pays.multiply_and_round_up( match_price ); // round up here to mitigate rounding issue (core-342) if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull cull_settle_order = false; @@ -1007,7 +1007,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan // so we should cull the order in fill_limit_order() below. // The order would receive 0 even at `match_price`, so it would receive 0 at its own price, // so calling maybe_cull_small() will always cull it. - call_receives = order_receives ^ match_price; + call_receives = order_receives.multiply_and_round_up( match_price ); filled_limit = true; @@ -1023,7 +1023,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan wlog( "Something for nothing issue (#184, variant D) occurred at block #${block}", ("block",head_block_num()) ); } else - order_receives = usd_to_buy ^ match_price; // round up, in favor of limit order + order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order filled_call = true; diff --git a/libraries/chain/include/graphene/chain/protocol/asset.hpp b/libraries/chain/include/graphene/chain/protocol/asset.hpp index 4cb02c7e7f..ed250395bb 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset.hpp @@ -29,6 +29,8 @@ namespace graphene { namespace chain { extern const int64_t scaled_precision_lut[]; + struct price; + struct asset { asset( share_type a = 0, asset_id_type id = asset_id_type() ) @@ -94,6 +96,8 @@ namespace graphene { namespace chain { FC_ASSERT( precision < 19 ); return scaled_precision_lut[ precision ]; } + + asset multiply_and_round_up( const price& p )const; ///< Multiply and round up }; /** @@ -145,7 +149,6 @@ namespace graphene { namespace chain { bool operator == ( const price& a, const price& b ); bool operator != ( const price& a, const price& b ); asset operator * ( const asset& a, const price& b ); ///< Multiply and round down - asset operator ^ ( const asset& a, const price& b ); ///< Multiply and round up price operator * ( const price& p, const ratio_type& r ); price operator / ( const price& p, const ratio_type& r ); diff --git a/libraries/chain/protocol/asset.cpp b/libraries/chain/protocol/asset.cpp index dc3a531662..00eb9ab18c 100644 --- a/libraries/chain/protocol/asset.cpp +++ b/libraries/chain/protocol/asset.cpp @@ -92,8 +92,9 @@ namespace graphene { namespace chain { FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) ); } - asset operator ^ ( const asset& a, const price& b ) + asset asset::multiply_and_round_up( const price& b )const { + const asset& a = *this; if( a.asset_id == b.base.asset_id ) { FC_ASSERT( b.base.amount.value > 0 ); @@ -108,7 +109,7 @@ namespace graphene { namespace chain { FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY ); return asset( result.convert_to(), b.base.asset_id ); } - FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) ); + FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset::multiply_and_round_up(price)", ("asset",a)("price",b) ); } price operator / ( const asset& base, const asset& quote ) diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 95ae54646a..d32795edb6 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -261,29 +261,31 @@ BOOST_AUTO_TEST_CASE( price_test ) BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1) * price( asset(1), asset(2, asset_id_type(1)) ), fc::assert_exception ); BOOST_CHECK_THROW( asset(2) * price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ), fc::assert_exception ); - // Note: `==` executes before `^`, so need `( a ^ b ) == c` - BOOST_CHECK( ( asset(1) ^ price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) ); - BOOST_CHECK( ( asset(1) ^ price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) ); - BOOST_CHECK( ( asset(1, asset_id_type(1)) ^ price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) ); - BOOST_CHECK( ( asset(1, asset_id_type(1)) ^ price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) ); - - BOOST_CHECK( ( asset(3) ^ price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) ); // round_up(3*5/3) - BOOST_CHECK( ( asset(5) ^ price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(2, asset_id_type(1)) ); // round_up(5*2/7) - BOOST_CHECK( ( asset(7, asset_id_type(1)) ^ price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(5) ); // round_up(7*2/3) - BOOST_CHECK( ( asset(9, asset_id_type(1)) ^ price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(8) ); // round_up(9*7/8) + BOOST_CHECK( asset(1).multiply_and_round_up( price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( asset(1).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1, asset_id_type(1)) ); + BOOST_CHECK( asset(1, asset_id_type(1)).multiply_and_round_up( price( asset(1), asset(1, asset_id_type(1)) ) ) == asset(1) ); + BOOST_CHECK( asset(1, asset_id_type(1)).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(1) ) ) == asset(1) ); + + // round_up(3*5/3) + BOOST_CHECK( asset(3).multiply_and_round_up( price( asset(3), asset(5, asset_id_type(1)) ) ) == asset(5, asset_id_type(1)) ); + // round_up(5*2/7) + BOOST_CHECK( asset(5).multiply_and_round_up( price( asset(2, asset_id_type(1)), asset(7) ) ) == asset(2, asset_id_type(1)) ); + // round_up(7*2/3) + BOOST_CHECK( asset(7, asset_id_type(1)).multiply_and_round_up( price( asset(2), asset(3, asset_id_type(1)) ) ) == asset(5) ); + // round_up(9*7/8) + BOOST_CHECK( asset(9, asset_id_type(1)).multiply_and_round_up( price( asset(8, asset_id_type(1)), asset(7) ) ) == asset(8) ); // asset and price doesn't match - BOOST_CHECK_THROW( asset(1, asset_id_type(3)) ^ price( asset(1, asset_id_type(2)), asset(1) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(1, asset_id_type(3)).multiply_and_round_up( price( asset(1, asset_id_type(2)), asset(1) ) ), + fc::assert_exception ); // divide by zero - BOOST_CHECK_THROW( asset(1) ^ price( asset(0), asset(1, asset_id_type(1)) ), fc::assert_exception ); - BOOST_CHECK_THROW( asset(1) ^ price( asset(1, asset_id_type(1)), asset(0) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(1).multiply_and_round_up( price( asset(0), asset(1, asset_id_type(1)) ) ), fc::assert_exception ); + BOOST_CHECK_THROW( asset(1).multiply_and_round_up( price( asset(1, asset_id_type(1)), asset(0) ) ), fc::assert_exception ); // overflow - BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1) ^ price( asset(1), asset(2, asset_id_type(1)) ), fc::assert_exception ); - BOOST_CHECK_THROW( asset(2) ^ price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ), fc::assert_exception ); - - // checks that operator^() executes before operator=() - auto x = asset(3) ^ price( asset(6), asset(10, asset_id_type(1)) ); - BOOST_CHECK( x == asset(5, asset_id_type(1)) ); + BOOST_CHECK_THROW( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1).multiply_and_round_up( price( asset(1), asset(2, asset_id_type(1)) ) ), + fc::assert_exception ); + BOOST_CHECK_THROW( asset(2).multiply_and_round_up( price( asset(GRAPHENE_MAX_SHARE_SUPPLY/2+1, asset_id_type(1)), asset(1) ) ), + fc::assert_exception ); price_feed dummy; dummy.maintenance_collateral_ratio = 1002; From 47556743ad5f7fbd18cd5054edcee7c14d24b95c Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 17 Apr 2018 08:14:08 -0400 Subject: [PATCH 28/56] Update comments --- libraries/chain/db_market.cpp | 5 ++++- libraries/chain/db_update.cpp | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index cd8975df6f..bacee3e34d 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -701,7 +701,10 @@ asset database::match( const call_order_object& call, cull_settle_order = true; // else do nothing, since we can't cull the settle order - call_receives = call_pays.multiply_and_round_up( match_price ); // round up here to mitigate rounding issue (core-342) + call_receives = call_pays.multiply_and_round_up( match_price ); // round up here to mitigate rounding issue (core-342). + // It is important to understand here that the newly + // rounded up call_receives won't be greater than the + // old call_receives. if( call_receives == settle.balance ) // the settle order will be completely filled, no need to cull cull_settle_order = false; diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index ce3a743b51..4967792475 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -437,7 +437,7 @@ void database::clear_expired_orders() break; } settled += new_settled; - // before hard fork core-342, if new_settled > 0, we'll have: + // before hard fork core-342, `new_settled > 0` is always true, we'll have: // * call order is completely filled (thus itr will change in next loop), or // * settle order is completely filled (thus find_object(order_id) will be false so will break out), or // * reached max_settlement_volume limit (thus new_settled == max_settlement so will break out). From 8ff51af7c34a1182ced911c4a0f742c9342c173f Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Tue, 17 Apr 2018 18:27:31 -0300 Subject: [PATCH 29/56] add settle bsip35 tests --- tests/tests/operation_tests.cpp | 341 ++++++++++++++++++++++++++++++++ 1 file changed, 341 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index f66ee94616..031e3bd178 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2336,6 +2336,347 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) // TODO: Test with non-core asset and Bob account } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) +{ + try { + // get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_block(); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // add a feed to asset + update_feed_producers( bitusd, {paul.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, paul, current_feed ); + + // paul gets some bitusd + auto call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); + + // and transfer some to rachel + transfer(paul.id, rachel.id, asset(200, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); + + // michael selling core + auto call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); + BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); + + generate_blocks( db.head_block_time() + fc::hours(20) ); + set_expiration( db, trx ); + + // default feed and settlement expires at the same time + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), account_id_type(19)(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(6) ); + + // final checks + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // add a feed to asset + update_feed_producers( bitusd, {paul.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, paul, current_feed ); + + // paul gets some bitusd + auto call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); + + // and transfer some to rachel + transfer(paul.id, rachel.id, asset(200, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); + + // michael selling core + auto call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); + BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); + + generate_blocks( db.head_block_time() + fc::hours(20) ); + set_expiration( db, trx ); + + // default feed and settlement expires at the same time + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), account_id_type(19)(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(6) ); + + // final checks + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) +{ + try { + // get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_block(); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // allow global settle in bitusd + asset_update_operation op; + op.issuer = bitusd.issuer; + op.asset_to_update = bitusd.id; + op.new_options.issuer_permissions = global_settle; + op.new_options.flags = bitusd.options.flags; + op.new_options.core_exchange_rate = price({asset(1,asset_id_type(1)),asset(1)}); + trx.operations.push_back(op); + sign(trx, paul_private_key); + PUSH_TX(db, trx); + generate_block(); + trx.clear(); + + // add a feed to asset + update_feed_producers( bitusd, {paul.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, paul, current_feed ); + + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); + + // paul gets some bitusd + auto call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + + // and transfer some to rachel + transfer(paul_id, rachel_id, asset(200, bitusd_id)); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + + // michael selling core + auto call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add global settle + force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // all call orders are gone after global settle + BOOST_CHECK( db.find_object(call_paul_id) == nullptr); + BOOST_CHECK( db.find_object(call_michael_id) == nullptr); + + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // allow global settle in bitusd + asset_update_operation op; + op.issuer = bitusd_id(db).issuer; + op.asset_to_update = bitusd_id; + op.new_options.issuer_permissions = global_settle; + op.new_options.flags = bitusd.options.flags; + op.new_options.core_exchange_rate = price({asset(1,asset_id_type(1)),asset(1)}); + trx.operations.push_back(op); + sign(trx, paul_private_key); + PUSH_TX(db, trx); + generate_block(); + trx.clear(); + + // add a feed to asset + update_feed_producers( bitusd_id(db), {paul.id} ); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), paul, current_feed ); + + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); + + // paul gets some bitusd + auto call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + + // and transfer some to rachel + transfer(paul_id, rachel_id, asset(200, bitusd_id)); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + + // michael selling core + auto call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add global settle + force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // all call orders are gone after global settle + BOOST_CHECK( db.find_object(call_paul_id) == nullptr); + BOOST_CHECK( db.find_object(call_michael_id) == nullptr); + + } FC_LOG_AND_RETHROW() +} + // TODO: Write linear VBO tests BOOST_AUTO_TEST_SUITE_END() From ea8adac1256ebf4951bc7827e2d2362c052913ea Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Tue, 17 Apr 2018 19:14:42 -0300 Subject: [PATCH 30/56] change account_id_type(19) to paul_id --- tests/tests/operation_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 031e3bd178..2c69a91489 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2406,7 +2406,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), account_id_type(19)(db), current_feed ); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); @@ -2497,7 +2497,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), account_id_type(19)(db), current_feed ); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); From 80706de3a8ac851e9aefe1a6336657c4e6cdba5e Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Tue, 17 Apr 2018 20:33:10 -0300 Subject: [PATCH 31/56] fix last change to paul_id should be alice_id --- tests/tests/operation_tests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 2c69a91489..b20f349780 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2406,7 +2406,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), paul_id(db), current_feed ); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); @@ -2497,7 +2497,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), paul_id(db), current_feed ); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); From cb6c364e10cddaaeb5d3d3c3bb51db2bb3cd0a03 Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Tue, 17 Apr 2018 20:36:37 -0300 Subject: [PATCH 32/56] replace nullptr in call checks after global settle --- tests/tests/operation_tests.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b20f349780..9e04025deb 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2591,8 +2591,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); // all call orders are gone after global settle - BOOST_CHECK( db.find_object(call_paul_id) == nullptr); - BOOST_CHECK( db.find_object(call_michael_id) == nullptr); + BOOST_CHECK( !db.find_object(call_paul_id)); + BOOST_CHECK( !db.find_object(call_michael_id)); } FC_LOG_AND_RETHROW() @@ -2671,8 +2671,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); // all call orders are gone after global settle - BOOST_CHECK( db.find_object(call_paul_id) == nullptr); - BOOST_CHECK( db.find_object(call_michael_id) == nullptr); + BOOST_CHECK( !db.find_object(call_paul_id)); + BOOST_CHECK( !db.find_object(call_michael_id)); } FC_LOG_AND_RETHROW() } From f0d71df4344b82ac14fbc3186fad2d40fe2800ba Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Tue, 17 Apr 2018 20:43:39 -0300 Subject: [PATCH 33/56] change all auto calls to reference const objects --- tests/tests/operation_tests.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 9e04025deb..43a494268d 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2366,7 +2366,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) publish_feed( bitusd, paul, current_feed ); // paul gets some bitusd - auto call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); @@ -2379,7 +2379,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); // michael selling core - auto call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); call_order_id_type call_michael_id = call_michael.id; // add settle order and check rounding issue @@ -2457,7 +2457,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) publish_feed( bitusd, paul, current_feed ); // paul gets some bitusd - auto call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); @@ -2470,7 +2470,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); // michael selling core - auto call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); call_order_id_type call_michael_id = call_michael.id; // add settle order and check rounding issue @@ -2563,7 +2563,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); // paul gets some bitusd - auto call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); @@ -2576,7 +2576,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // michael selling core - auto call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); call_order_id_type call_michael_id = call_michael.id; // add global settle @@ -2643,7 +2643,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); // paul gets some bitusd - auto call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); @@ -2656,7 +2656,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // michael selling core - auto call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); call_order_id_type call_michael_id = call_michael.id; // add global settle From 41430dd7ec99b3de6c2dde498cc64d64680726d7 Mon Sep 17 00:00:00 2001 From: oxarbitrage Date: Wed, 18 Apr 2018 13:38:31 -0300 Subject: [PATCH 34/56] test cases F variation --- tests/tests/operation_tests.cpp | 61 +++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 43a494268d..6f6d4cb10c 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2677,6 +2677,67 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle ) +{ + try { + + // caal global settle test + INVOKE(trade_amount_equals_zero_global_settle); + const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = asset_id_type(); + const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); + account_id_type rachel_id = rachel.id; + const account_object& paul = *db.get_index_type().indices().get().find("paul"); + account_id_type paul_id = paul.id; + const account_object& michael = *db.get_index_type().indices().get().find("michael"); + account_id_type michael_id = michael.id; + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_hf_184 ) +{ + try { + // call global settle after hardfork 184 + INVOKE(trade_amount_equals_zero_global_settle_after_hf_184); + const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = asset_id_type(); + const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); + account_id_type rachel_id = rachel.id; + const account_object& paul = *db.get_index_type().indices().get().find("paul"); + account_id_type paul_id = paul.id; + const account_object& michael = *db.get_index_type().indices().get().find("michael"); + account_id_type michael_id = michael.id; + + // settle order will not execute after HF + GRAPHENE_REQUIRE_THROW( force_settle(rachel, bitusd.amount(4)), fc::exception ); + + generate_block(); + + // balances unchanged + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // failing here: 99999999 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + } FC_LOG_AND_RETHROW() +} + // TODO: Write linear VBO tests BOOST_AUTO_TEST_SUITE_END() From 2a17c894e3dfd5d7e93acfb8472381bade406405 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Apr 2018 13:37:12 -0400 Subject: [PATCH 35/56] BSIP35 tests: sleep after generated block --- tests/tests/operation_tests.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 6f6d4cb10c..da95a19646 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1683,6 +1683,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3); generate_block(); + fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread auto result = get_market_order_history(core_id, test_id); BOOST_CHECK_EQUAL(result.size(), 4); @@ -1734,6 +1735,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(core_seller, test), 2); generate_block(); + fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread auto result = get_market_order_history(core_id, test_id); BOOST_CHECK_EQUAL(result.size(), 2); From 705806efb7cde24e98bc491ea7ec602b4a0ee4be Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 18 Apr 2018 14:01:47 -0400 Subject: [PATCH 36/56] Fix bsip35 settle zero amount tests --- tests/tests/operation_tests.cpp | 42 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index da95a19646..e1685a5422 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2368,7 +2368,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) publish_feed( bitusd, paul, current_feed ); // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); @@ -2459,7 +2459,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) publish_feed( bitusd, paul, current_feed ); // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), asset(100)); + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); call_order_id_type call_paul_id = call_paul.id; BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); @@ -2547,7 +2547,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) op.asset_to_update = bitusd.id; op.new_options.issuer_permissions = global_settle; op.new_options.flags = bitusd.options.flags; - op.new_options.core_exchange_rate = price({asset(1,asset_id_type(1)),asset(1)}); + op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); trx.operations.push_back(op); sign(trx, paul_private_key); PUSH_TX(db, trx); @@ -2555,14 +2555,14 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) trx.clear(); // add a feed to asset - update_feed_producers( bitusd, {paul.id} ); + update_feed_producers( bitusd_id(db), {paul_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, paul, current_feed ); + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); // paul gets some bitusd const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); @@ -2593,8 +2593,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); // all call orders are gone after global settle - BOOST_CHECK( !db.find_object(call_paul_id)); - BOOST_CHECK( !db.find_object(call_michael_id)); + BOOST_CHECK( !db.find_object(call_paul_id) ); + BOOST_CHECK( !db.find_object(call_michael_id) ); } FC_LOG_AND_RETHROW() @@ -2627,7 +2627,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) op.asset_to_update = bitusd_id; op.new_options.issuer_permissions = global_settle; op.new_options.flags = bitusd.options.flags; - op.new_options.core_exchange_rate = price({asset(1,asset_id_type(1)),asset(1)}); + op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); trx.operations.push_back(op); sign(trx, paul_private_key); PUSH_TX(db, trx); @@ -2635,14 +2635,14 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) trx.clear(); // add a feed to asset - update_feed_producers( bitusd_id(db), {paul.id} ); + update_feed_producers( bitusd_id(db), {paul_id} ); price_feed current_feed; current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), paul, current_feed ); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core), 10000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); // paul gets some bitusd const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); @@ -2695,6 +2695,13 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle ) const account_object& michael = *db.get_index_type().indices().get().find("michael"); account_id_type michael_id = michael.id; + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + // add settle order and check rounding issue force_settle(rachel, bitusd.amount(4)); generate_block(); @@ -2724,6 +2731,13 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_ const account_object& michael = *db.get_index_type().indices().get().find("michael"); account_id_type michael_id = michael.id; + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + // settle order will not execute after HF GRAPHENE_REQUIRE_THROW( force_settle(rachel, bitusd.amount(4)), fc::exception ); @@ -2733,7 +2747,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_ BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // failing here: 99999999 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); From d53320f3b924a11cec443cef128d1714d790f08b Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 20 Apr 2018 15:02:31 -0400 Subject: [PATCH 37/56] Minor code cleanup about market tests --- tests/tests/market_tests.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index ad3a6b188a..163a0bdfec 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -24,17 +24,14 @@ #include -#include #include -#include -#include +#include #include "../common/database_fixture.hpp" using namespace graphene::chain; using namespace graphene::chain::test; -using namespace graphene::wallet; BOOST_FIXTURE_TEST_SUITE(market_tests, database_fixture) From bff821d5a4687fed7c521db3c27e167c1a725758 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 20 Apr 2018 15:08:06 -0400 Subject: [PATCH 38/56] Move settle tests to new file --- tests/tests/operation_tests.cpp | 416 ----------------------------- tests/tests/settle_tests.cpp | 454 ++++++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+), 416 deletions(-) create mode 100644 tests/tests/settle_tests.cpp diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index e1685a5422..abbe0aa115 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -2338,422 +2338,6 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test ) // TODO: Test with non-core asset and Bob account } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) -{ - try { - // get around Graphene issue #615 feed expiration bug - generate_blocks(HARDFORK_615_TIME); - generate_block(); - set_expiration( db, trx ); - - ACTORS((paul)(michael)(rachel)(alice)); - - // create assets - const auto& bitusd = create_bitasset("USDBIT", paul_id); - const auto& core = asset_id_type()(db); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = core.id; - - // fund accounts - transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); - - // add a feed to asset - update_feed_producers( bitusd, {paul.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, paul, current_feed ); - - // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); - call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); - - // and transfer some to rachel - transfer(paul.id, rachel.id, asset(200, bitusd.id)); - - BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); - BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); - BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); - BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); - - // michael selling core - const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); - call_order_id_type call_michael_id = call_michael.id; - - // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); - - BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); - BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); - BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); - BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); - BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); - - BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); - BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); - BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); - BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); - - generate_blocks( db.head_block_time() + fc::hours(20) ); - set_expiration( db, trx ); - - // default feed and settlement expires at the same time - // adding new feed so we have valid price to exit - update_feed_producers( bitusd_id(db), {alice_id} ); - current_feed.maintenance_collateral_ratio = 1750; - current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), alice_id(db), current_feed ); - - // now yes expire settlement - generate_blocks( db.head_block_time() + fc::hours(6) ); - - // final checks - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value ); - BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); - BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); - BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) -{ - try { - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((paul)(michael)(rachel)(alice)); - - // create assets - const auto& bitusd = create_bitasset("USDBIT", paul_id); - const auto& core = asset_id_type()(db); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = core.id; - - // fund accounts - transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); - - // add a feed to asset - update_feed_producers( bitusd, {paul.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, paul, current_feed ); - - // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); - call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); - - // and transfer some to rachel - transfer(paul.id, rachel.id, asset(200, bitusd.id)); - - BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); - BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); - BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); - BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); - - // michael selling core - const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); - call_order_id_type call_michael_id = call_michael.id; - - // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); - - BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); - BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); - BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); - BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); - BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); - - BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); - BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); - BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); - BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); - - generate_blocks( db.head_block_time() + fc::hours(20) ); - set_expiration( db, trx ); - - // default feed and settlement expires at the same time - // adding new feed so we have valid price to exit - update_feed_producers( bitusd_id(db), {alice_id} ); - current_feed.maintenance_collateral_ratio = 1750; - current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), alice_id(db), current_feed ); - - // now yes expire settlement - generate_blocks( db.head_block_time() + fc::hours(6) ); - - // final checks - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value ); - BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); - BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); - BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) -{ - try { - // get around Graphene issue #615 feed expiration bug - generate_blocks(HARDFORK_615_TIME); - generate_block(); - set_expiration( db, trx ); - - ACTORS((paul)(michael)(rachel)(alice)); - - // create assets - const auto& bitusd = create_bitasset("USDBIT", paul_id); - const auto& core = asset_id_type()(db); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = core.id; - - // fund accounts - transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); - - // allow global settle in bitusd - asset_update_operation op; - op.issuer = bitusd.issuer; - op.asset_to_update = bitusd.id; - op.new_options.issuer_permissions = global_settle; - op.new_options.flags = bitusd.options.flags; - op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); - trx.operations.push_back(op); - sign(trx, paul_private_key); - PUSH_TX(db, trx); - generate_block(); - trx.clear(); - - // add a feed to asset - update_feed_producers( bitusd_id(db), {paul_id} ); - price_feed current_feed; - current_feed.maintenance_collateral_ratio = 1750; - current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), paul_id(db), current_feed ); - - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); - - // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); - call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); - - // and transfer some to rachel - transfer(paul_id, rachel_id, asset(200, bitusd_id)); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - - // michael selling core - const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); - call_order_id_type call_michael_id = call_michael.id; - - // add global settle - force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); - generate_block(); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // all call orders are gone after global settle - BOOST_CHECK( !db.find_object(call_paul_id) ); - BOOST_CHECK( !db.find_object(call_michael_id) ); - - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) -{ - try { - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - set_expiration( db, trx ); - - ACTORS((paul)(michael)(rachel)(alice)); - - // create assets - const auto& bitusd = create_bitasset("USDBIT", paul_id); - const auto& core = asset_id_type()(db); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = core.id; - - // fund accounts - transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); - - // allow global settle in bitusd - asset_update_operation op; - op.issuer = bitusd_id(db).issuer; - op.asset_to_update = bitusd_id; - op.new_options.issuer_permissions = global_settle; - op.new_options.flags = bitusd.options.flags; - op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); - trx.operations.push_back(op); - sign(trx, paul_private_key); - PUSH_TX(db, trx); - generate_block(); - trx.clear(); - - // add a feed to asset - update_feed_producers( bitusd_id(db), {paul_id} ); - price_feed current_feed; - current_feed.maintenance_collateral_ratio = 1750; - current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); - publish_feed( bitusd_id(db), paul_id(db), current_feed ); - - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); - - // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); - call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); - - // and transfer some to rachel - transfer(paul_id, rachel_id, asset(200, bitusd_id)); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - - // michael selling core - const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); - call_order_id_type call_michael_id = call_michael.id; - - // add global settle - force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); - generate_block(); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // all call orders are gone after global settle - BOOST_CHECK( !db.find_object(call_paul_id)); - BOOST_CHECK( !db.find_object(call_michael_id)); - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle ) -{ - try { - - // caal global settle test - INVOKE(trade_amount_equals_zero_global_settle); - const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = asset_id_type(); - const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); - account_id_type rachel_id = rachel.id; - const account_object& paul = *db.get_index_type().indices().get().find("paul"); - account_id_type paul_id = paul.id; - const account_object& michael = *db.get_index_type().indices().get().find("michael"); - account_id_type michael_id = michael.id; - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); - generate_block(); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_hf_184 ) -{ - try { - // call global settle after hardfork 184 - INVOKE(trade_amount_equals_zero_global_settle_after_hf_184); - const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = asset_id_type(); - const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); - account_id_type rachel_id = rachel.id; - const account_object& paul = *db.get_index_type().indices().get().find("paul"); - account_id_type paul_id = paul.id; - const account_object& michael = *db.get_index_type().indices().get().find("michael"); - account_id_type michael_id = michael.id; - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // settle order will not execute after HF - GRAPHENE_REQUIRE_THROW( force_settle(rachel, bitusd.amount(4)), fc::exception ); - - generate_block(); - - // balances unchanged - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - } FC_LOG_AND_RETHROW() -} - // TODO: Write linear VBO tests BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp new file mode 100644 index 0000000000..f9aa99105b --- /dev/null +++ b/tests/tests/settle_tests.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2018 oxarbitrage, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( settle_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) +{ + try { + // get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_block(); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // add a feed to asset + update_feed_producers( bitusd, {paul.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, paul, current_feed ); + + // paul gets some bitusd + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); + + // and transfer some to rachel + transfer(paul.id, rachel.id, asset(200, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); + + // michael selling core + const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); + BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); + + generate_blocks( db.head_block_time() + fc::hours(20) ); + set_expiration( db, trx ); + + // default feed and settlement expires at the same time + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(6) ); + + // final checks + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // add a feed to asset + update_feed_producers( bitusd, {paul.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, paul, current_feed ); + + // paul gets some bitusd + const call_order_object& call_paul = *borrow( paul, bitusd.amount(1000), core.amount(100) ); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul, bitusd ), 1000 ); + + // and transfer some to rachel + transfer(paul.id, rachel.id, asset(200, bitusd.id)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 200); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); + BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); + + // michael selling core + const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + + BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); + BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); + BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 6); + BOOST_CHECK_EQUAL(get_balance(michael, core), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul, core), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul, bitusd), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul.debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul.collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael.debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael.collateral.value ); + + generate_blocks( db.head_block_time() + fc::hours(20) ); + set_expiration( db, trx ); + + // default feed and settlement expires at the same time + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(6) ); + + // final checks + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) +{ + try { + // get around Graphene issue #615 feed expiration bug + generate_blocks(HARDFORK_615_TIME); + generate_block(); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // allow global settle in bitusd + asset_update_operation op; + op.issuer = bitusd.issuer; + op.asset_to_update = bitusd.id; + op.new_options.issuer_permissions = global_settle; + op.new_options.flags = bitusd.options.flags; + op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); + trx.operations.push_back(op); + sign(trx, paul_private_key); + PUSH_TX(db, trx); + generate_block(); + trx.clear(); + + // add a feed to asset + update_feed_producers( bitusd_id(db), {paul_id} ); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); + + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); + + // paul gets some bitusd + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + + // and transfer some to rachel + transfer(paul_id, rachel_id, asset(200, bitusd_id)); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + + // michael selling core + const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add global settle + force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // all call orders are gone after global settle + BOOST_CHECK( !db.find_object(call_paul_id) ); + BOOST_CHECK( !db.find_object(call_michael_id) ); + + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + set_expiration( db, trx ); + + ACTORS((paul)(michael)(rachel)(alice)); + + // create assets + const auto& bitusd = create_bitasset("USDBIT", paul_id); + const auto& core = asset_id_type()(db); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = core.id; + + // fund accounts + transfer(committee_account, michael_id, asset( 100000000 ) ); + transfer(committee_account, paul_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + + // allow global settle in bitusd + asset_update_operation op; + op.issuer = bitusd_id(db).issuer; + op.asset_to_update = bitusd_id; + op.new_options.issuer_permissions = global_settle; + op.new_options.flags = bitusd.options.flags; + op.new_options.core_exchange_rate = price( asset(1,bitusd_id), asset(1,core_id) ); + trx.operations.push_back(op); + sign(trx, paul_private_key); + PUSH_TX(db, trx); + generate_block(); + trx.clear(); + + // add a feed to asset + update_feed_producers( bitusd_id(db), {paul_id} ); + price_feed current_feed; + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), paul_id(db), current_feed ); + + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); + + // paul gets some bitusd + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + call_order_id_type call_paul_id = call_paul.id; + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + + // and transfer some to rachel + transfer(paul_id, rachel_id, asset(200, bitusd_id)); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + + // michael selling core + const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); + call_order_id_type call_michael_id = call_michael.id; + + // add global settle + force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // all call orders are gone after global settle + BOOST_CHECK( !db.find_object(call_paul_id)); + BOOST_CHECK( !db.find_object(call_michael_id)); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle ) +{ + try { + + // caal global settle test + INVOKE(trade_amount_equals_zero_global_settle); + const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = asset_id_type(); + const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); + account_id_type rachel_id = rachel.id; + const account_object& paul = *db.get_index_type().indices().get().find("paul"); + account_id_type paul_id = paul.id; + const account_object& michael = *db.get_index_type().indices().get().find("michael"); + account_id_type michael_id = michael.id; + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // add settle order and check rounding issue + force_settle(rachel, bitusd.amount(4)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_hf_184 ) +{ + try { + // call global settle after hardfork 184 + INVOKE(trade_amount_equals_zero_global_settle_after_hf_184); + const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); + asset_id_type bitusd_id = bitusd.id; + asset_id_type core_id = asset_id_type(); + const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); + account_id_type rachel_id = rachel.id; + const account_object& paul = *db.get_index_type().indices().get().find("paul"); + account_id_type paul_id = paul.id; + const account_object& michael = *db.get_index_type().indices().get().find("michael"); + account_id_type michael_id = michael.id; + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + // settle order will not execute after HF + GRAPHENE_REQUIRE_THROW( force_settle(rachel, bitusd.amount(4)), fc::exception ); + + generate_block(); + + // balances unchanged + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() From e2b5addacbb39b6e3ab49687d3cfe7c86232c5ed Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 20 Apr 2018 15:18:39 -0400 Subject: [PATCH 39/56] Merge some tests about force settlement --- tests/tests/settle_tests.cpp | 76 ++++++------------------------------ 1 file changed, 12 insertions(+), 64 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index f9aa99105b..f5c65c915c 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -293,6 +293,16 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK( !db.find_object(call_paul_id) ); BOOST_CHECK( !db.find_object(call_michael_id) ); + // add settle order and check rounding issue + force_settle(rachel_id(db), bitusd_id(db).amount(4)); + generate_block(); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); } FC_LOG_AND_RETHROW() } @@ -373,70 +383,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) BOOST_CHECK( !db.find_object(call_paul_id)); BOOST_CHECK( !db.find_object(call_michael_id)); - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle ) -{ - try { - - // caal global settle test - INVOKE(trade_amount_equals_zero_global_settle); - const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = asset_id_type(); - const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); - account_id_type rachel_id = rachel.id; - const account_object& paul = *db.get_index_type().indices().get().find("paul"); - account_id_type paul_id = paul.id; - const account_object& michael = *db.get_index_type().indices().get().find("michael"); - account_id_type michael_id = michael.id; - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); - generate_block(); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - } FC_LOG_AND_RETHROW() -} - -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_global_settle_after_hf_184 ) -{ - try { - // call global settle after hardfork 184 - INVOKE(trade_amount_equals_zero_global_settle_after_hf_184); - const asset_object& bitusd = *db.get_index_type().indices().get().find("USDBIT"); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = asset_id_type(); - const account_object& rachel = *db.get_index_type().indices().get().find("rachel"); - account_id_type rachel_id = rachel.id; - const account_object& paul = *db.get_index_type().indices().get().find("paul"); - account_id_type paul_id = paul.id; - const account_object& michael = *db.get_index_type().indices().get().find("michael"); - account_id_type michael_id = michael.id; - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - - // settle order will not execute after HF - GRAPHENE_REQUIRE_THROW( force_settle(rachel, bitusd.amount(4)), fc::exception ); + // settle order will not execute after HF due to too small + GRAPHENE_REQUIRE_THROW( force_settle(rachel_id(db), bitusd_id(db).amount(4)), fc::exception ); generate_block(); From a1280ca21e98df9bd3b44a328b89303352286f4c Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 20 Apr 2018 16:12:22 -0400 Subject: [PATCH 40/56] Update global settlement tests for rounding issue --- tests/tests/settle_tests.cpp | 113 ++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 23 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index f5c65c915c..18d059722a 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -217,7 +217,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) +BOOST_AUTO_TEST_CASE( global_settle_rounding_test ) { try { // get around Graphene issue #615 feed expiration bug @@ -235,8 +235,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) // fund accounts transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); + transfer(committee_account, paul_id, asset( 10000000 ) ); + transfer(committee_account, alice_id, asset( 10000000 ) ); // allow global settle in bitusd asset_update_operation op; @@ -259,12 +259,14 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); publish_feed( bitusd_id(db), paul_id(db), current_feed ); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1001), core_id(db).amount(101)); call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1001 ); + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), core_id(db) ), 10000000-101); // and transfer some to rachel transfer(paul_id, rachel_id, asset(200, bitusd_id)); @@ -273,21 +275,31 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); - // michael selling core + // michael borrow some bitusd const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); call_order_id_type call_michael_id = call_michael.id; + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000-8); + // add global settle force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); generate_block(); + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 100 ); // 100 from paul, and 0 from michael + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); // michael paid nothing for 6 usd + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); // paul paid 100 core for 1001 usd + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); // all call orders are gone after global settle BOOST_CHECK( !db.find_object(call_paul_id) ); @@ -297,21 +309,42 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle ) force_settle(rachel_id(db), bitusd_id(db).amount(4)); generate_block(); + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 100 ); // paid nothing + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1003 ); // settled 4 usd + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); // rachel paid 4 usd and got nothing BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); + + // rachel settle more than 1 core + force_settle(rachel_id(db), bitusd_id(db).amount(13)); + generate_block(); + + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(100) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 99 ); // paid 1 core + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 990 ); // settled 13 usd + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 183); // rachel paid 13 usd and got 1 core + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) +BOOST_AUTO_TEST_CASE( global_settle_rounding_test_after_hf_184 ) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(HARDFORK_CORE_184_TIME - mi); // assume that hard fork core-184 and core-342 happen at same time generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); @@ -325,8 +358,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) // fund accounts transfer(committee_account, michael_id, asset( 100000000 ) ); - transfer(committee_account, paul_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); + transfer(committee_account, paul_id, asset( 10000000 ) ); + transfer(committee_account, alice_id, asset( 10000000 ) ); // allow global settle in bitusd asset_update_operation op; @@ -349,12 +382,14 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); publish_feed( bitusd_id(db), paul_id(db), current_feed ); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 10000000); // paul gets some bitusd - const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1000), core_id(db).amount(100)); + const call_order_object& call_paul = *borrow( paul_id(db), bitusd_id(db).amount(1001), core_id(db).amount(101)); call_order_id_type call_paul_id = call_paul.id; - BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), bitusd_id(db) ), 1001 ); + BOOST_REQUIRE_EQUAL( get_balance( paul_id(db), core_id(db) ), 10000000-101); // and transfer some to rachel transfer(paul_id, rachel_id, asset(200, bitusd_id)); @@ -363,21 +398,31 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); - // michael selling core + // michael borrow some bitusd const call_order_object& call_michael = *borrow(michael_id(db), bitusd_id(db).amount(6), core_id(db).amount(8)); call_order_id_type call_michael_id = call_michael.id; + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 100000000-8); + // add global settle force_global_settle(bitusd_id(db), bitusd_id(db).amount(10) / core_id(db).amount(1)); generate_block(); + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 102 ); // 101 from paul, and 1 from michael + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); - BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); // michael paid 1 core for 6 usd + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); // paul paid 101 core for 1001 usd + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); // all call orders are gone after global settle BOOST_CHECK( !db.find_object(call_paul_id)); @@ -389,12 +434,34 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_global_settle_after_hf_184 ) generate_block(); // balances unchanged + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 102 ); + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1007 ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); + + // rachel settle more than 1 core + force_settle(rachel_id(db), bitusd_id(db).amount(13)); + generate_block(); + + BOOST_CHECK( bitusd_id(db).bitasset_data(db).settlement_price + == price( bitusd_id(db).amount(1007), core_id(db).amount(102) ) ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).settlement_fund.value, 101 ); // paid 1 core + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 997 ); // settled 10 usd + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 190); // rachel paid 10 usd and got 1 core, 3 usd returned + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999999); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999899); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 801); + } FC_LOG_AND_RETHROW() } From ee6a0e203b25c0c212d72cb474627e39489be39d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 05:49:16 -0400 Subject: [PATCH 41/56] Move market rounding tests to a new file --- tests/tests/market_rounding_tests.cpp | 482 ++++++++++++++++++++++++++ tests/tests/market_tests.cpp | 444 ------------------------ 2 files changed, 482 insertions(+), 444 deletions(-) create mode 100644 tests/tests/market_rounding_tests.cpp diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp new file mode 100644 index 0000000000..8cea092e6d --- /dev/null +++ b/tests/tests/market_rounding_tests.cpp @@ -0,0 +1,482 @@ +/* + * Copyright (c) 2018 Abit More, and other contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE(market_rounding_tests, database_fixture) + +/*** + * Reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) +{ 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); + + 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)); + // 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)); + 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() } + +/*** + * Another test case + * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) +{ try { + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, + // so the seller will pay 10 USD but get nothing. + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Yet another test case + * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) +{ try { + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, + // so the seller will pay 10 USD but get nothing. + // The remaining USD will be in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // 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( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Another test case + * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Yet another test case + * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Also detects the cull_small issue in check_call_orders. + */ +BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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 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(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 10, call.debt.value ); + BOOST_CHECK_EQUAL( 1, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100010, 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) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); + publish_feed( bitusd, feedproducer, current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, + // Since the call would pay off all debt, let it pay 1 CORE from collateral + // The remaining USD will be in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/market_tests.cpp b/tests/tests/market_tests.cpp index 163a0bdfec..c81380997e 100644 --- a/tests/tests/market_tests.cpp +++ b/tests/tests/market_tests.cpp @@ -1190,448 +1190,4 @@ BOOST_AUTO_TEST_CASE(hard_fork_343_cross_test) } FC_LOG_AND_RETHROW() } -/*** - * Reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. - * Also detects the cull_small issue in check_call_orders. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) -{ 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); - - 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)); - // 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)); - 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() } - -/*** - * Another test case - * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. - * Also detects the cull_small issue in check_call_orders. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) -{ try { - 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 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(borrower3, seller, bitusd.amount(100000)); - - BOOST_CHECK_EQUAL( 10, call.debt.value ); - BOOST_CHECK_EQUAL( 1, call.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call3.debt.value ); - BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); - - BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, - // so the seller will pay 10 USD but get nothing. - // The remaining USD will be left in the order on the market - limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; - BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing - BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } - -/*** - * Yet another test case - * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. - * Also detects the cull_small issue in check_call_orders. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) -{ try { - 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 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(borrower3, seller, bitusd.amount(100000)); - - BOOST_CHECK_EQUAL( 10, call.debt.value ); - BOOST_CHECK_EQUAL( 1, call.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call3.debt.value ); - BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); - - BOOST_CHECK_EQUAL( 100010, 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) ); - - // create a limit order which will be matched later - limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; - BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); - BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); - publish_feed( bitusd, feedproducer, current_feed ); - // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE - - // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, - // so the seller will pay 10 USD but get nothing. - // The remaining USD will be in the order on the market - BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing - BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } - -/*** - * Fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) -{ try { - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - - 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, - // Since the call would pay off all debt, let it pay 1 CORE from collateral - // 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( 1, get_balance(seller, core) ); // the seller got 1 CORE - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } - -/*** - * Another test case - * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) -{ try { - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - - 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 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(borrower3, seller, bitusd.amount(100000)); - - BOOST_CHECK_EQUAL( 10, call.debt.value ); - BOOST_CHECK_EQUAL( 1, call.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call3.debt.value ); - BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); - - BOOST_CHECK_EQUAL( 100010, 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 33 USD / 3 CORE, but call only owes 10 USD, - // Since the call would pay off all debt, let it pay 1 CORE from collateral - // The remaining USD will be left in the order on the market - limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; - BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE - BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } - -/*** - * Yet another test case - * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. - * Also detects the cull_small issue in check_call_orders. - */ -BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) -{ try { - auto mi = db.get_global_properties().parameters.maintenance_interval; - generate_blocks(HARDFORK_CORE_184_TIME - mi); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - - 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 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(borrower3, seller, bitusd.amount(100000)); - - BOOST_CHECK_EQUAL( 10, call.debt.value ); - BOOST_CHECK_EQUAL( 1, call.collateral.value ); - BOOST_CHECK_EQUAL( 100000, call3.debt.value ); - BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); - - BOOST_CHECK_EQUAL( 100010, 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) ); - - // create a limit order which will be matched later - limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; - BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); - BOOST_CHECK_EQUAL( 100010-33, 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( 120 ) / core.amount(10); - publish_feed( bitusd, feedproducer, current_feed ); - // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE - - // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, - // Since the call would pay off all debt, let it pay 1 CORE from collateral - // The remaining USD will be in the order on the market - BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE - BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); - - generate_block(); - -} FC_LOG_AND_RETHROW() } - BOOST_AUTO_TEST_SUITE_END() From 9d21d36c77e94e22da7c0df34b5391bb623a3300 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 06:09:48 -0400 Subject: [PATCH 42/56] Move limit order tests to rounding tests file --- tests/tests/market_rounding_tests.cpp | 107 ++++++++++++++++++++++++++ tests/tests/operation_tests.cpp | 103 ------------------------- 2 files changed, 107 insertions(+), 103 deletions(-) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index 8cea092e6d..d857d9ce40 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -35,6 +35,113 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE(market_rounding_tests, database_fixture) +/** + * Create an order such that when the trade executes at the + * requested price the resulting payout to one party is 0 + * ( Reproduces https://github.com/bitshares/bitshares-core/issues/184 ) + */ +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) +{ + try { + generate_blocks( HARDFORK_555_TIME ); + set_expiration( db, trx ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + const account_object& core_seller = create_account( "seller1" ); + const account_object& core_buyer = create_account("buyer1"); + + transfer( committee_account(db), core_seller, asset( 100000000 ) ); + + issue_uia( core_buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); + + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_buyer, test.amount(3), core.amount(1)); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999997); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3); + + generate_block(); + fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread + + auto result = get_market_order_history(core_id, test_id); + BOOST_CHECK_EQUAL(result.size(), 4); + BOOST_CHECK(result[0].op.pays == core_id(db).amount(0)); + BOOST_CHECK(result[0].op.receives == test_id(db).amount(1)); + BOOST_CHECK(result[1].op.pays == test_id(db).amount(1)); + BOOST_CHECK(result[1].op.receives == core_id(db).amount(0)); + BOOST_CHECK(result[2].op.pays == core_id(db).amount(1)); + BOOST_CHECK(result[2].op.receives == test_id(db).amount(2)); + BOOST_CHECK(result[3].op.pays == test_id(db).amount(2)); + BOOST_CHECK(result[3].op.receives == core_id(db).amount(1)); + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/** + * The something-for-nothing bug should be fixed https://github.com/bitshares/bitshares-core/issues/184 + */ +BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_184_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + const account_object& core_seller = create_account( "seller1" ); + const account_object& core_buyer = create_account("buyer1"); + + transfer( committee_account(db), core_seller, asset( 100000000 ) ); + + issue_uia( core_buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); + + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_seller, core.amount(1), test.amount(2)); + create_sell_order(core_buyer, test.amount(3), core.amount(1)); + + BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); + BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); + BOOST_CHECK_EQUAL(get_balance(core_seller, test), 2); + + generate_block(); + fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread + + auto result = get_market_order_history(core_id, test_id); + BOOST_CHECK_EQUAL(result.size(), 2); + BOOST_CHECK(result[0].op.pays == core_id(db).amount(1)); + BOOST_CHECK(result[0].op.receives == test_id(db).amount(2)); + BOOST_CHECK(result[1].op.pays == test_id(db).amount(2)); + BOOST_CHECK(result[1].op.receives == core_id(db).amount(1)); + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /*** * Reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index abbe0aa115..fed5d2d438 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1646,109 +1646,6 @@ BOOST_AUTO_TEST_CASE( witness_feeds ) } } - -/** - * Create an order such that when the trade executes at the - * requested price the resulting payout to one party is 0 - * ( Reproduces https://github.com/bitshares/bitshares-core/issues/184 ) - */ -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) -{ - try { - INVOKE(issue_uia); - generate_blocks( HARDFORK_555_TIME ); - set_expiration( db, trx ); - - const asset_object& test = get_asset( UIA_TEST_SYMBOL ); - const asset_id_type test_id = test.id; - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - const asset_id_type core_id = core.id; - const account_object& core_seller = create_account( "shorter1" ); - const account_object& core_buyer = get_account("nathan"); - - transfer( committee_account(db), core_seller, asset( 100000000 ) ); - - BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0); - BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000); - BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); - BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); - - create_sell_order(core_seller, core.amount(1), test.amount(2)); - create_sell_order(core_seller, core.amount(1), test.amount(2)); - create_sell_order(core_buyer, test.amount(3), core.amount(1)); - - BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); - BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999997); - BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); - BOOST_CHECK_EQUAL(get_balance(core_seller, test), 3); - - generate_block(); - fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread - - auto result = get_market_order_history(core_id, test_id); - BOOST_CHECK_EQUAL(result.size(), 4); - BOOST_CHECK(result[0].op.pays == core.amount(0)); - BOOST_CHECK(result[0].op.receives == test.amount(1)); - BOOST_CHECK(result[1].op.pays == test.amount(1)); - BOOST_CHECK(result[1].op.receives == core.amount(0)); - BOOST_CHECK(result[2].op.pays == core.amount(1)); - BOOST_CHECK(result[2].op.receives == test.amount(2)); - BOOST_CHECK(result[3].op.pays == test.amount(2)); - BOOST_CHECK(result[3].op.receives == core.amount(1)); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -/** - * The something-for-nothing bug should be fixed https://github.com/bitshares/bitshares-core/issues/184 - */ -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) -{ - try { - INVOKE(issue_uia); - generate_blocks( HARDFORK_CORE_184_TIME ); - set_expiration( db, trx ); - - const asset_object& test = get_asset( UIA_TEST_SYMBOL ); - const asset_id_type test_id = test.id; - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - const asset_id_type core_id = core.id; - const account_object& core_seller = create_account( "shorter1" ); - const account_object& core_buyer = get_account("nathan"); - - transfer( committee_account(db), core_seller, asset( 100000000 ) ); - - BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 0); - BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 10000000); - BOOST_CHECK_EQUAL(get_balance(core_seller, test), 0); - BOOST_CHECK_EQUAL(get_balance(core_seller, core), 100000000); - - create_sell_order(core_seller, core.amount(1), test.amount(2)); - create_sell_order(core_seller, core.amount(1), test.amount(2)); - create_sell_order(core_buyer, test.amount(3), core.amount(1)); - - BOOST_CHECK_EQUAL(get_balance(core_buyer, core), 1); - BOOST_CHECK_EQUAL(get_balance(core_buyer, test), 9999998); - BOOST_CHECK_EQUAL(get_balance(core_seller, core), 99999998); - BOOST_CHECK_EQUAL(get_balance(core_seller, test), 2); - - generate_block(); - fc::usleep(fc::milliseconds(200)); // sleep a while to execute callback in another thread - - auto result = get_market_order_history(core_id, test_id); - BOOST_CHECK_EQUAL(result.size(), 2); - BOOST_CHECK(result[0].op.pays == core.amount(1)); - BOOST_CHECK(result[0].op.receives == test.amount(2)); - BOOST_CHECK(result[1].op.pays == test.amount(2)); - BOOST_CHECK(result[1].op.receives == core.amount(1)); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - /** * Create an order that cannot be filled immediately and have the * transaction fail. From c50463d3fbda92bc1c207016c7100210df657538 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 06:25:40 -0400 Subject: [PATCH 43/56] Update comments --- tests/tests/market_rounding_tests.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index d857d9ce40..fd7e73231f 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -143,7 +143,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) } /*** - * Reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) @@ -215,8 +215,10 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) /*** * Another test case - * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. + * + * In this test case, the limit order is taker. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) { try { @@ -286,8 +288,10 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) /*** * Yet another test case - * reproduces bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. + * + * In this test case, the limit order is maker. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) { try { @@ -361,7 +365,7 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) } FC_LOG_AND_RETHROW() } /*** - * Fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * Fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) { try { @@ -438,7 +442,9 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) /*** * Another test case - * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * for fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order. + * + * In this test case, the limit order is taker. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) { try { @@ -510,8 +516,10 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) /*** * Yet another test case - * for fixed bitshares-core issue #132: something for nothing when maching a limit order with a call order. + * for fixed bitshares-core issue #132: something for nothing when matching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. + * + * In this test case, the limit order is maker. */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) { try { From b7879bf9c37e5062687195a6e08713f4c24a3e64 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 08:35:15 -0400 Subject: [PATCH 44/56] BSIP35 test cases: matching 2 limit orders --- tests/tests/market_rounding_tests.cpp | 255 ++++++++++++++++++++++++++ 1 file changed, 255 insertions(+) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index fd7e73231f..f2c675d6db 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -142,6 +142,261 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_after_hf_184 ) } } +/*** + * This test case reproduces one of the scenarios described in bitshares-core issue #342: + * when matching a limit order with another limit order, a small taker order will pay more than minimum required. + */ +BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1 ) +{ + try { + generate_blocks( HARDFORK_555_TIME ); + set_expiration( db, trx ); + + ACTORS( (seller)(buyer) ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + + transfer( committee_account(db), seller, asset( 100000000 ) ); + + issue_uia( buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000); + + // seller sells 3 core for 31 test, price 10.33 test per core + limit_order_id_type sell_id = create_sell_order( seller, core.amount(3), test.amount(31) )->id; + + // buyer buys 2 core with 25 test, price 12.5 test per core + // the order is filled immediately + BOOST_CHECK( !create_sell_order( buyer, test.amount(25), core.amount(2) ) ); + + BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 1 ); // 2 core sold, 1 remaining + + BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller, test), 25); // seller got 25 test + BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999975); // buyer paid 25 test + + generate_block(); + + // buyer buys 2 core with 25 test, price 12.5 test per core + limit_order_id_type buy_id = create_sell_order( buyer_id, asset(25,test_id), asset(2,core_id) )->id; + + generate_block(); + + BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled + BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold, 15 remaining + + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 35); // seller got 10 more test + BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 3); // buyer got 1 more core + BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999950); + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a limit order with another limit order, + * a small taker order will only pay minimum required amount, and the rest will be returned. + */ +BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1_after_hf_342 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS( (seller)(buyer) ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + + transfer( committee_account(db), seller, asset( 100000000 ) ); + + issue_uia( buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000); + + // seller sells 3 core for 31 test, price 10.33 test per core + limit_order_id_type sell_id = create_sell_order( seller, core.amount(3), test.amount(31) )->id; + + // buyer buys 2 core with 25 test, price 12.5 test per core + // the order is filled immediately + BOOST_CHECK( !create_sell_order( buyer, test.amount(25), core.amount(2) ) ); + + BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 1 ); // 2 core sold, 1 remaining + + BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller, test), 21); // seller got 21 test: round_up(2*31/3)=round_up(20.67) + BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999979); // buyer paid 21 test but not 25 + + generate_block(); + set_expiration( db, trx ); + + // buyer buys 2 core with 25 test, price 12.5 test per core + limit_order_id_type buy_id = create_sell_order( buyer_id, asset(25,test_id), asset(2,core_id) )->id; + + generate_block(); + + BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled + BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold, 15 remaining + + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 31); // seller got 10 more test, in total 31 as expected + BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 3); // buyer got 1 more core + BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999954); + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/*** + * This test case reproduces one of the scenarios described in bitshares-core issue #342: + * when matching a limit order with another limit order, a small maker order will pay more than minimum required. + */ +BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2 ) +{ + try { + generate_blocks( HARDFORK_555_TIME ); + set_expiration( db, trx ); + + ACTORS( (seller)(buyer) ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + + transfer( committee_account(db), seller, asset( 100000000 ) ); + + issue_uia( buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000); + + // buyer buys 17 core with 3 test, price 3/17 = 0.176 test per core + limit_order_id_type tmp_buy_id = create_sell_order( buyer, test.amount(3), core.amount(17) )->id; + // seller sells 33 core for 5 test, price 5/33 = 0.1515 test per core + limit_order_id_type sell_id = create_sell_order( seller, core.amount(33), test.amount(5) )->id; + + BOOST_CHECK( !db.find_object( tmp_buy_id ) ); // buy order is filled + BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 16 ); // 17 core sold, 16 remaining + + BOOST_CHECK_EQUAL(get_balance(seller, core), 99999967); + BOOST_CHECK_EQUAL(get_balance(seller, test), 3); // seller got 3 test + BOOST_CHECK_EQUAL(get_balance(buyer, core), 17); // buyer got 17 core + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999997); // buyer paid 3 test + + generate_block(); + set_expiration( db, trx ); + + // buyer buys 15 core with 3 test, price 3/15 = 0.2 test per core + // even 15 < 16, since it's taker, we'll check with maker's price, then turns out the buy order is bigger + limit_order_id_type buy_id = create_sell_order( buyer_id, asset(3,test_id), asset(15,core_id) )->id; + + generate_block(); + + BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled + BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 1 ); // 2 test sold, 1 remaining + + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967); // seller paid the 16 core which was remaining in the order + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test + BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 33); // buyer got 16 more core + BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994); + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a limit order with another limit order, + * a small maker order will only pay minimum required amount, and the rest will be returned. + */ +BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2_after_hf_342 ) +{ + try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS( (seller)(buyer) ); + + const asset_object& test = create_user_issued_asset( "UIATEST" ); + const asset_id_type test_id = test.id; + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + const asset_id_type core_id = core.id; + + transfer( committee_account(db), seller, asset( 100000000 ) ); + + issue_uia( buyer, asset( 10000000, test_id ) ); + + BOOST_CHECK_EQUAL(get_balance(buyer, core), 0); + BOOST_CHECK_EQUAL(get_balance(buyer, test), 10000000); + BOOST_CHECK_EQUAL(get_balance(seller, test), 0); + BOOST_CHECK_EQUAL(get_balance(seller, core), 100000000); + + // buyer buys 17 core with 3 test, price 3/17 = 0.176 test per core + limit_order_id_type tmp_buy_id = create_sell_order( buyer, test.amount(3), core.amount(17) )->id; + // seller sells 33 core for 5 test, price 5/33 = 0.1515 test per core + limit_order_id_type sell_id = create_sell_order( seller, core.amount(33), test.amount(5) )->id; + + BOOST_CHECK( !db.find_object( tmp_buy_id ) ); // buy order is filled + BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 16 ); // 17 core sold, 16 remaining + + BOOST_CHECK_EQUAL(get_balance(seller, core), 99999967); + BOOST_CHECK_EQUAL(get_balance(seller, test), 3); // seller got 3 test + BOOST_CHECK_EQUAL(get_balance(buyer, core), 17); // buyer got 17 core + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999997); // buyer paid 3 test + + generate_block(); + set_expiration( db, trx ); + + // buyer buys 15 core with 3 test, price 3/15 = 0.2 test per core + // even 15 < 16, since it's taker, we'll check with maker's price, then turns out the buy order is bigger + limit_order_id_type buy_id = create_sell_order( buyer_id, asset(3,test_id), asset(15,core_id) )->id; + + generate_block(); + + BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled + BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 1 ); // 2 test sold, 1 remaining + + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967+16-14); // seller got refunded 2 core + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test + BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 31); // buyer got 14 more core: round_up(2*33/5)=round_up(13.2) + BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994); + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + /*** * Reproduces bitshares-core issue #132: something for nothing when matching a limit order with a call order. * Also detects the cull_small issue in check_call_orders. From 25a0de66824acc7607d0308a31de1cb9d8a27c21 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 14:37:55 -0400 Subject: [PATCH 45/56] Remove unused variables in rounding test cases --- tests/tests/market_rounding_tests.cpp | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index f2c675d6db..b54b763236 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -485,8 +485,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) 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); @@ -507,7 +505,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) call_order_id_type call_id = call.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(borrower3, seller, bitusd.amount(100000)); @@ -558,8 +555,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) 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); @@ -580,7 +575,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) call_order_id_type call_id = call.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(borrower3, seller, bitusd.amount(100000)); @@ -634,8 +628,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) 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); @@ -656,10 +648,8 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) 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)); @@ -713,8 +703,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) 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); @@ -735,7 +723,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) call_order_id_type call_id = call.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(borrower3, seller, bitusd.amount(100000)); @@ -788,8 +775,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) 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); @@ -810,7 +795,6 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) call_order_id_type call_id = call.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(borrower3, seller, bitusd.amount(100000)); From b1941ec6f90087e4b44fbe0e35e297b9219151cf Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 17:14:54 -0400 Subject: [PATCH 46/56] BSIP35 tests: matching limit(taker):call(maker) --- tests/tests/market_rounding_tests.cpp | 287 +++++++++++++++++++++++++- 1 file changed, 285 insertions(+), 2 deletions(-) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index b54b763236..730c96d12a 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -671,7 +671,8 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1_after_hardfork ) 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, + // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), + // but call only owes 10 USD, // Since the call would pay off all debt, let it pay 1 CORE from collateral // 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)) ); @@ -741,7 +742,8 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2_after_hardfork ) publish_feed( bitusd, feedproducer, current_feed ); // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE - // This would match with call at price 33 USD / 3 CORE, but call only owes 10 USD, + // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), + // but call only owes 10 USD, // Since the call would pay off all debt, let it pay 1 CORE from collateral // The remaining USD will be left in the order on the market limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; @@ -833,4 +835,285 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) } FC_LOG_AND_RETHROW() } +/*** + * This test case reproduces one of the scenarios described in bitshares-core issue #342: + * when matching a big taker limit order with a small maker call order, + * rounding was in favor of the small call order. + */ +BOOST_AUTO_TEST_CASE( limit_call_rounding_test1 ) +{ try { + 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); + + 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, 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 limit order's price 33 USD / 3 CORE, but call only owes 20 USD, + // so the seller will pay 20 USD but get 1 CORE, since round_down(20*3/33) = 1 + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a big taker limit order with a small maker call order, + * rounding in favor of the big limit order. + */ +BOOST_AUTO_TEST_CASE( limit_call_rounding_test1_after_hf_342 ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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); + + 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, 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 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), + // but call only owes 20 USD, + // so the seller will pay 20 USD and get 2 CORE, since round_up(20*11/120) = 2 + // The remaining USD will be left in the order on the market + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 2, get_balance(seller, core) ); // the seller got 2 CORE + BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * Due to #338, when matching a smaller taker limit order with a big maker call order, + * the small order will be filled at its own price. + * So unable or no need to reproduce one of the scenarios described in bitshares-core issue #342: + * when matching a small taker limit order with a big maker call order, + * the small limit order would be paying too much. + * But we'll just write the test case for #338 here. + */ +BOOST_AUTO_TEST_CASE( limit_call_rounding_test2 ) +{ try { + 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); + + 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, 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 limit order's price 15 USD / 1 CORE, + // so the seller will pay 15 USD and get 1 CORE + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled + BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled + BOOST_CHECK_EQUAL( 20-15, call.debt.value ); // call paid 15 USD + BOOST_CHECK_EQUAL( 2-1, call.collateral.value ); // call got 1 CORE + BOOST_CHECK_EQUAL( 100020-15, get_balance(seller, bitusd) ); // the seller paid 15 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a small taker limit order with a big maker call order, + * the small limit order would be paying minimum required. + */ +BOOST_AUTO_TEST_CASE( limit_call_rounding_test2_after_hf_342 ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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); + + 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, 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 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), + // so the seller will get 1 CORE, and pay round_up(1*120/11) = 11 USD, the extra 4 USD will be returned + BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled + BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled + BOOST_CHECK_EQUAL( 20-11, call.debt.value ); // call paid 11 USD + BOOST_CHECK_EQUAL( 2-1, call.collateral.value ); // call got 1 CORE + BOOST_CHECK_EQUAL( 100020-11, get_balance(seller, bitusd) ); // the seller paid 11 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower, core) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From e77c7bae07e8b8c1e77e1661d94c896ddf651adc Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 18:41:37 -0400 Subject: [PATCH 47/56] Minor tweak on bsip35 rounding test cases --- tests/tests/market_rounding_tests.cpp | 75 +++++++++++++++++---------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index 730c96d12a..35182417c3 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -44,6 +44,7 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) { try { generate_blocks( HARDFORK_555_TIME ); + generate_block(); set_expiration( db, trx ); const asset_object& test = create_user_issued_asset( "UIATEST" ); @@ -150,6 +151,7 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1 ) { try { generate_blocks( HARDFORK_555_TIME ); + generate_block(); set_expiration( db, trx ); ACTORS( (seller)(buyer) ); @@ -180,7 +182,8 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1 ) BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997); BOOST_CHECK_EQUAL(get_balance(seller, test), 25); // seller got 25 test BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core - BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999975); // buyer paid 25 test + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999975); // buyer paid 25 test, + // effective price is 25/2 which is much higher than 31/3 generate_block(); @@ -242,10 +245,10 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1_after_hf_342 ) BOOST_CHECK_EQUAL( sell_id(db).for_sale.value, 1 ); // 2 core sold, 1 remaining - BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997); - BOOST_CHECK_EQUAL(get_balance(seller, test), 21); // seller got 21 test: round_up(2*31/3)=round_up(20.67) BOOST_CHECK_EQUAL(get_balance(buyer, core), 2); // buyer got 2 core - BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999979); // buyer paid 21 test but not 25 + BOOST_CHECK_EQUAL(get_balance(buyer, test), 9999979); // buyer actually paid 21 test according to price 10.33 + BOOST_CHECK_EQUAL(get_balance(seller, core), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller, test), 21); // seller got 21 test generate_block(); set_expiration( db, trx ); @@ -256,12 +259,12 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test1_after_hf_342 ) generate_block(); BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled - BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold, 15 remaining + BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 15 ); // 10 test sold according to price 10.33, and 15 remaining - BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997); - BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 31); // seller got 10 more test, in total 31 as expected BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 3); // buyer got 1 more core BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999954); + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999997); + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 31); // seller got 10 more test, in total 31 as expected } catch( const fc::exception& e) { edump((e.to_detail_string())); @@ -277,6 +280,7 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2 ) { try { generate_blocks( HARDFORK_555_TIME ); + generate_block(); set_expiration( db, trx ); ACTORS( (seller)(buyer) ); @@ -322,6 +326,7 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2 ) BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967); // seller paid the 16 core which was remaining in the order BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test + // effective price 16/2 which is much higher than 33/5 BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 33); // buyer got 16 more core BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994); @@ -386,10 +391,10 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2_after_hf_342 ) BOOST_CHECK( !db.find_object( sell_id ) ); // sell order is filled BOOST_CHECK_EQUAL( buy_id(db).for_sale.value, 1 ); // 2 test sold, 1 remaining - BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967+16-14); // seller got refunded 2 core - BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test - BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 31); // buyer got 14 more core: round_up(2*33/5)=round_up(13.2) + BOOST_CHECK_EQUAL(get_balance(buyer_id, core_id), 31); // buyer got 14 more core according to price 0.1515 BOOST_CHECK_EQUAL(get_balance(buyer_id, test_id), 9999994); + BOOST_CHECK_EQUAL(get_balance(seller_id, core_id), 99999967+16-14); // seller got refunded 2 core + BOOST_CHECK_EQUAL(get_balance(seller_id, test_id), 5); // seller got 2 more test, effective price 14/2 which is close to 33/5 } catch( const fc::exception& e) { edump((e.to_detail_string())); @@ -403,6 +408,7 @@ BOOST_AUTO_TEST_CASE( limit_limit_rounding_test2_after_hf_342 ) */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) { try { // matching a limit order with call order + generate_blocks( HARDFORK_555_TIME ); generate_block(); set_expiration( db, trx ); @@ -477,6 +483,7 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test1 ) */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) { try { + generate_blocks( HARDFORK_555_TIME ); generate_block(); set_expiration( db, trx ); @@ -547,6 +554,7 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test2 ) */ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) { try { + generate_blocks( HARDFORK_555_TIME ); generate_block(); set_expiration( db, trx ); @@ -555,6 +563,8 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); + const asset_id_type bitusd_id = bitusd.id; + const asset_id_type core_id = core.id; int64_t init_balance(1000000); @@ -594,20 +604,22 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3 ) BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + generate_block(); + // 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 ); + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, // so the seller will pay 10 USD but get nothing. // The remaining USD will be in the order on the market BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); // the seller got nothing + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 0, get_balance(seller_id, core_id) ); // the seller got nothing BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance, get_balance(borrower_id, core_id) ); generate_block(); @@ -777,6 +789,8 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); const auto& core = asset_id_type()(db); + const asset_id_type bitusd_id = bitusd.id; + const asset_id_type core_id = core.id; int64_t init_balance(1000000); @@ -816,20 +830,22 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + generate_block(); + // 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 ); + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE // the limit order will match with call at price 33 USD / 3 CORE, but call only owes 10 USD, // Since the call would pay off all debt, let it pay 1 CORE from collateral // The remaining USD will be in the order on the market BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled - BOOST_CHECK_EQUAL( 100010-33, get_balance(seller, bitusd) ); // the seller paid 33 USD - BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 100010-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller_id, core_id) ); // the seller got 1 CORE BOOST_CHECK_EQUAL( 33-10, sell_id(db).for_sale.value ); // the sell order has some USD left - BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); - BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower_id, core_id) ); generate_block(); @@ -842,6 +858,7 @@ BOOST_AUTO_TEST_CASE( issue_132_limit_and_call_test3_after_hardfork ) */ BOOST_AUTO_TEST_CASE( limit_call_rounding_test1 ) { try { + generate_blocks( HARDFORK_555_TIME ); generate_block(); set_expiration( db, trx ); @@ -889,7 +906,8 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test1 ) // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE // This would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD, - // so the seller will pay 20 USD but get 1 CORE, since round_down(20*3/33) = 1 + // so the seller will pay the whole 20 USD and get 1 CORE, since 20 USD doesn't worth 2 CORE according to price 33/3, + // effective price is 20/1 which is worse than the limit order's desired 33/3. // The remaining USD will be left in the order on the market limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled @@ -960,7 +978,8 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test1_after_hf_342 ) // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), // but call only owes 20 USD, - // so the seller will pay 20 USD and get 2 CORE, since round_up(20*11/120) = 2 + // so the seller will pay 20 USD and get 2 CORE, since 20 USD worths a little more than 1 CORE according to price 120/11, + // effective price is 20/2 which is not worse than the limit order's desired 33/3. // The remaining USD will be left in the order on the market limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled @@ -984,6 +1003,7 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test1_after_hf_342 ) */ BOOST_AUTO_TEST_CASE( limit_call_rounding_test2 ) { try { + generate_blocks( HARDFORK_555_TIME ); generate_block(); set_expiration( db, trx ); @@ -1031,7 +1051,8 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test2 ) // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE // This would match with call at limit order's price 15 USD / 1 CORE, - // so the seller will pay 15 USD and get 1 CORE + // so the seller will pay 15 USD and get 1 CORE, + // effective price is 15/1. BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled BOOST_CHECK_EQUAL( 20-15, call.debt.value ); // call paid 15 USD @@ -1102,7 +1123,9 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test2_after_hf_342 ) // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE // This would match with call at price 120 USD / 11 CORE (assume hard fork core-342 and hard fork core-338 occur at same time), - // so the seller will get 1 CORE, and pay round_up(1*120/11) = 11 USD, the extra 4 USD will be returned + // so the seller will get 1 CORE, and pay 11 USD since 1 CORE worths a little more than 10 USD according to price 120/11, + // and the extra 4 USD will be returned but not overpaid, + // effective price is 11/1 which is close to 120/11. BOOST_CHECK( !create_sell_order(seller, bitusd.amount(15), core.amount(1)) ); // the sell order is filled BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled BOOST_CHECK_EQUAL( 20-11, call.debt.value ); // call paid 11 USD From 95906d8100198a070ab1894e465a8611bc559e56 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 21 Apr 2018 20:24:41 -0400 Subject: [PATCH 48/56] BSIP35 tests: matching call(taker):limit(maker) --- tests/tests/market_rounding_tests.cpp | 366 ++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) diff --git a/tests/tests/market_rounding_tests.cpp b/tests/tests/market_rounding_tests.cpp index 35182417c3..8c406f32e7 100644 --- a/tests/tests/market_rounding_tests.cpp +++ b/tests/tests/market_rounding_tests.cpp @@ -1139,4 +1139,370 @@ BOOST_AUTO_TEST_CASE( limit_call_rounding_test2_after_hf_342 ) } FC_LOG_AND_RETHROW() } +/*** + * This test case reproduces one of the scenarios described in bitshares-core issue #342: + * when matching a small taker call order with a big maker limit order, + * rounding was in favor of the small call order. + */ +BOOST_AUTO_TEST_CASE( call_limit_rounding_test1 ) +{ try { + generate_blocks( HARDFORK_555_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); + const asset_id_type bitusd_id = bitusd.id; + const 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, get_balance(borrower, core) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + generate_block(); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // The limit would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD, + // so the seller will pay the whole 20 USD and get 1 CORE, since 20 USD doesn't worth 2 CORE according to price 33/3, + // effective price is 20/1 which is worse than the limit order's desired 33/3. + // The remaining USD will be left in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller_id, core_id) ); // the seller got 1 CORE + BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance-1, get_balance(borrower_id, core_id) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a small taker call order with a big maker limit order, + * rounding in favor of the big limit order. + */ +BOOST_AUTO_TEST_CASE( call_limit_rounding_test1_after_hf_342 ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + 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); + const asset_id_type bitusd_id = bitusd.id; + const 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(20), asset(2)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(20)); + transfer(borrower3, seller, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 20, call.debt.value ); + BOOST_CHECK_EQUAL( 2, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 100020, 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-2, get_balance(borrower, core) ); + + // create a limit order which will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(33), core.amount(3))->id; + BOOST_CHECK_EQUAL( 33, sell_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + + generate_block(); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // The limit would match with call at limit order's price 33 USD / 3 CORE, but call only owes 20 USD, + // so the seller will pay 20 USD and get 2 CORE, since 20 USD worths a little more than 1 CORE according to price 33/3, + // effective price is 20/2 which is not worse than the limit order's desired 33/3. + // The remaining USD will be left in the order on the market + BOOST_CHECK( !db.find( call_id ) ); // the first call order get filled + BOOST_CHECK_EQUAL( 100020-33, get_balance(seller_id, bitusd_id) ); // the seller paid 33 USD + BOOST_CHECK_EQUAL( 2, get_balance(seller_id, core_id) ); // the seller got 2 CORE + BOOST_CHECK_EQUAL( 33-20, sell_id(db).for_sale.value ); // the sell order has some USD left + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance-2, get_balance(borrower_id, core_id) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * This test case reproduces one of the scenarios described in bitshares-core issue #342: + * when matching a big taker call order with a small maker limit order, + * the small limit order would be paying too much. + */ +BOOST_AUTO_TEST_CASE( call_limit_rounding_test2 ) +{ try { + generate_blocks( HARDFORK_555_TIME ); + generate_block(); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + const asset_id_type bitusd_id = bitusd.id; + const 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(50), asset(5)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(50)); + transfer(borrower3, seller2, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 50, call.debt.value ); + BOOST_CHECK_EQUAL( 5, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 50, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower, core) ); + + // create a buy order which will be matched + limit_order_id_type buy_id = create_sell_order(buyer, core.amount(1), bitusd.amount(10))->id; + BOOST_CHECK_EQUAL( 1, buy_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + + // create a limit order to fill the buy order, and remaining amounts will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(31), core.amount(2))->id; + BOOST_CHECK( !db.find( buy_id ) ); // the buy order is filled + BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) ); // buyer got 10 usd + BOOST_CHECK_EQUAL( 21, sell_id(db).for_sale.value ); // remaining amount of sell order is 21 + BOOST_CHECK_EQUAL( 50-31, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // seller got 1 core + + // create another limit order which will be matched later + limit_order_id_type sell_id2 = create_sell_order(seller2, bitusd.amount(14), core.amount(1))->id; + BOOST_CHECK_EQUAL( 14, sell_id2(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + + generate_block(); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // call will match with the limit orders at limit orders' prices, + // firstly, call will match with sell_id, which has 21 USD remaining, with price 31 USD / 2 CORE, + // so the seller will pay 21 USD, get 1 CORE since 21 USD doesn't worth 2 CORE according to price 31/2, + // effective price is 21/1 which is much bigger than 31/2; + // then, call will match with sell_id2, which has 14 USD remaining, with price 14 USD / 1 CORE, + // so the seller will pay 14 USD, get 1 CORE since 14 USD worths just 1 CORE according to price 14/1 + BOOST_CHECK( !db.find( sell_id ) ); // the sell order is filled + BOOST_CHECK( !db.find( sell_id2 ) ); // the other sell order is filled + BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled + BOOST_CHECK_EQUAL( 50-14-21, call_id(db).debt.value ); // call paid 14 USD and 21 USD + BOOST_CHECK_EQUAL( 5-1-1, call_id(db).collateral.value ); // call got 1 CORE and 1 CORE + BOOST_CHECK_EQUAL( 50-31, get_balance(seller_id, bitusd_id) ); // seller paid 31 USD in total + BOOST_CHECK_EQUAL( 1+1, get_balance(seller_id, core_id) ); // seller got 1 more CORE + BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2_id, bitusd_id) ); // seller2 paid 14 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller2_id, core_id) ); // seller2 got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower_id, core_id) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + +/*** + * This test case tests one of the scenarios described in bitshares-core issue #342 after hard fork: + * when matching a big taker call order with a small maker limit order, + * the small limit order would be paying minimum required. + */ +BOOST_AUTO_TEST_CASE( call_limit_rounding_test2_after_hf_342 ) +{ try { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + + set_expiration( db, trx ); + + ACTORS((buyer)(seller)(seller2)(borrower)(borrower2)(borrower3)(borrower4)(feedproducer)); + + const auto& bitusd = create_bitasset("USDBIT", feedproducer_id); + const auto& core = asset_id_type()(db); + const asset_id_type bitusd_id = bitusd.id; + const 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(50), asset(5)); + call_order_id_type call_id = call.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)); + transfer(borrower, seller, bitusd.amount(50)); + transfer(borrower3, seller2, bitusd.amount(100000)); + + BOOST_CHECK_EQUAL( 50, call.debt.value ); + BOOST_CHECK_EQUAL( 5, call.collateral.value ); + BOOST_CHECK_EQUAL( 100000, call3.debt.value ); + BOOST_CHECK_EQUAL( 17500, call3.collateral.value ); + + BOOST_CHECK_EQUAL( 50, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 100000, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(borrower, bitusd) ); + BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower, core) ); + + // create a buy order which will be matched + limit_order_id_type buy_id = create_sell_order(buyer, core.amount(1), bitusd.amount(10))->id; + BOOST_CHECK_EQUAL( 1, buy_id(db).for_sale.value ); + BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( 0, get_balance(buyer, bitusd) ); + + // create a limit order to fill the buy order, and remaining amounts will be matched later + limit_order_id_type sell_id = create_sell_order(seller, bitusd.amount(31), core.amount(2))->id; + BOOST_CHECK( !db.find( buy_id ) ); // the buy order is filled + BOOST_CHECK_EQUAL( 1000000-1, get_balance(buyer, core) ); + BOOST_CHECK_EQUAL( 10, get_balance(buyer, bitusd) ); // buyer got 10 usd + BOOST_CHECK_EQUAL( 21, sell_id(db).for_sale.value ); // remaining amount of sell order is 21 + BOOST_CHECK_EQUAL( 50-31, get_balance(seller, bitusd) ); + BOOST_CHECK_EQUAL( 1, get_balance(seller, core) ); // seller got 1 core + + // create another limit order which will be matched later + limit_order_id_type sell_id2 = create_sell_order(seller2, bitusd.amount(14), core.amount(1))->id; + BOOST_CHECK_EQUAL( 14, sell_id2(db).for_sale.value ); + BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2, bitusd) ); + BOOST_CHECK_EQUAL( 0, get_balance(seller2, core) ); + + generate_block(); + + // adjust price feed to get call_order into margin call territory + current_feed.settlement_price = bitusd_id(db).amount( 120 ) / core_id(db).amount(10); + publish_feed( bitusd_id(db), feedproducer_id(db), current_feed ); + // settlement price = 120 USD / 10 CORE, mssp = 120/11 USD/CORE + + // call will match with the limit orders at limit orders' prices, + // firstly, call will match with sell_id, which has 21 USD remaining, with price 31 USD / 2 CORE, + // so the seller will get 1 CORE since 21 USD doesn't work 2 CORE according to price 31/2, + // and the seller will pay 16 USD since 1 CORE worths a little more than 15 USD according to price 31/2, + // and the extra 5 USD will be returned to seller since it doesn't worth 1 CORE, + // effective price is 16/1 which is close to 31/2; + // secondly, call will match with sell_id2, which has 14 USD remaining, with price 14 USD / 1 CORE, + // so the seller will get 1 CORE and pay 14 USD since 14 USD just worths 1 CORE according to price 14/1 + BOOST_CHECK( !db.find( sell_id ) ); // the sell order is filled + BOOST_CHECK( !db.find( sell_id2 ) ); // the other sell order is filled + BOOST_CHECK( db.find( call_id ) != nullptr ); // the first call order did not get filled + BOOST_CHECK_EQUAL( 50-14-16, call_id(db).debt.value ); // call paid 14 USD and 16 USD + BOOST_CHECK_EQUAL( 5-1-1, call_id(db).collateral.value ); // call got 1 CORE and 1 CORE + BOOST_CHECK_EQUAL( 50-31+(21-16), get_balance(seller_id, bitusd_id) ); // seller paid 31 USD then get refunded 5 USD + BOOST_CHECK_EQUAL( 1+1, get_balance(seller_id, core_id) ); // seller got 1 more CORE + BOOST_CHECK_EQUAL( 100000-14, get_balance(seller2_id, bitusd_id) ); // seller2 paid 14 USD + BOOST_CHECK_EQUAL( 1, get_balance(seller2_id, core_id) ); // seller2 got 1 CORE + BOOST_CHECK_EQUAL( 0, get_balance(borrower_id, bitusd_id) ); + BOOST_CHECK_EQUAL( init_balance-5, get_balance(borrower_id, core_id) ); + + generate_block(); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From c6a63bb557c59faeb8283f03acd67274af9467d1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Apr 2018 14:24:50 -0400 Subject: [PATCH 49/56] Update settle tests with more rounding scenario --- tests/tests/settle_tests.cpp | 121 ++++++++++++++++++++++++++++++++--- 1 file changed, 112 insertions(+), 9 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 18d059722a..5e786c8940 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -35,7 +35,7 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( settle_tests, database_fixture ) -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) +BOOST_AUTO_TEST_CASE( settle_rounding_test ) { try { // get around Graphene issue #615 feed expiration bug @@ -77,12 +77,15 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); - // michael selling core + // michael gets some bitusd const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); call_order_id_type call_michael_id = call_michael.id; // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); + operation_result result = force_settle(rachel, bitusd.amount(4)); + + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); @@ -111,7 +114,8 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) generate_blocks( db.head_block_time() + fc::hours(6) ); // final checks - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK( !db.find( settle_id ) ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); // rachel paid 4 usd and got nothing BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); @@ -123,10 +127,58 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle ) BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + // settle more and check rounding issue + // by default 20% of total supply can be settled per maintenance interval + set_expiration( db, trx ); + operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); + + force_settlement_id_type settle_id2 = result2.get(); + BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 162); // 196-34 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 996, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + generate_blocks( db.head_block_time() + fc::hours(10) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(16) ); + set_expiration( db, trx ); + + // final checks + BOOST_CHECK( !db.find( settle_id2 ) ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core and paid 34 usd + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 162); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 996-34, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100-1, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) +BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) { try { auto mi = db.get_global_properties().parameters.maintenance_interval; @@ -168,12 +220,15 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(michael, bitusd), 0); BOOST_CHECK_EQUAL(get_balance(michael, core), 100000000); - // michael selling core + // michael gets some bitusd const call_order_object& call_michael = *borrow(michael, bitusd.amount(6), core.amount(8)); call_order_id_type call_michael_id = call_michael.id; // add settle order and check rounding issue - force_settle(rachel, bitusd.amount(4)); + const operation_result result = force_settle(rachel, bitusd.amount(4)); + + force_settlement_id_type settle_id = result.get(); + BOOST_CHECK_EQUAL( settle_id(db).balance.amount.value, 4 ); BOOST_CHECK_EQUAL(get_balance(rachel, core), 0); BOOST_CHECK_EQUAL(get_balance(rachel, bitusd), 196); @@ -195,15 +250,36 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) update_feed_producers( bitusd_id(db), {alice_id} ); current_feed.maintenance_collateral_ratio = 1750; current_feed.maximum_short_squeeze_ratio = 1100; - current_feed.settlement_price = bitusd_id(db).amount( 100 ) / core_id(db).amount(5); + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); publish_feed( bitusd_id(db), alice_id(db), current_feed ); // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); // final checks + BOOST_CHECK( !db.find( settle_id ) ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); // rachel's settle order is cancelled and he get refunded + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 1000, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + // settle more and check rounding issue + // by default 20% of total supply can be settled per maintenance interval + set_expiration( db, trx ); + const operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); + + force_settlement_id_type settle_id2 = result2.get(); + BOOST_CHECK_EQUAL( settle_id2(db).balance.amount.value, 34 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 166); // 200-34 BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); @@ -214,6 +290,33 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero_settle_after_hf_184 ) BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + generate_blocks( db.head_block_time() + fc::hours(10) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(16) ); + set_expiration( db, trx ); + + // final checks + BOOST_CHECK( !db.find( settle_id2 ) ); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 179); // paid 21 usd since 1 core worths a little more than 20 usd + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 6); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999992); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); + + BOOST_CHECK_EQUAL( 1000-21, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 100-1, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); } FC_LOG_AND_RETHROW() } From 0766bc0e572512cdab741dc40f70b13a87423de0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Apr 2018 15:49:39 -0400 Subject: [PATCH 50/56] Settle tests in operation_test2: add bsip35 tests --- tests/tests/operation_tests2.cpp | 90 +++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 8 deletions(-) diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 16f40cf3ed..3a95dccef0 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -987,8 +987,28 @@ BOOST_AUTO_TEST_CASE( witness_create ) * issuer and only if the global settle bit is set. */ BOOST_AUTO_TEST_CASE( global_settle_test ) -{ - try { +{ try { + uint32_t skip = database::skip_witness_signature + | database::skip_transaction_signatures + | database::skip_transaction_dupe_check + | database::skip_block_size_check + | database::skip_tapos_check + | database::skip_authority_check + | database::skip_merkle_check + ; + + generate_block( skip ); + + for( int i=0; i<2; i++ ) + { + if( i == 1 ) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi, true, skip); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); + } + set_expiration( db, trx ); + ACTORS((nathan)(ben)(valentine)(dan)); asset_id_type bit_usd_id = create_bitasset("USDBIT", nathan_id, 100, global_settle | charge_market_fee).get_id(); @@ -1059,11 +1079,23 @@ BOOST_AUTO_TEST_CASE( global_settle_test ) BOOST_CHECK_EQUAL(get_balance(valentine_id, bit_usd_id), 0); BOOST_CHECK_EQUAL(get_balance(valentine_id, asset_id_type()), 10045); BOOST_CHECK_EQUAL(get_balance(ben_id, bit_usd_id), 0); - BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10091); + if( i == 1 ) // BSIP35: better rounding + { + BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10090); + BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9850); + } + else + { + BOOST_CHECK_EQUAL(get_balance(ben_id, asset_id_type()), 10091); + BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9849); + } BOOST_CHECK_EQUAL(get_balance(dan_id, bit_usd_id), 0); - BOOST_CHECK_EQUAL(get_balance(dan_id, asset_id_type()), 9849); -} FC_LOG_AND_RETHROW() -} + + // undo above tx's and reset + generate_block( skip ); + db.pop_block(); + } +} FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( worker_create_test ) { try { @@ -1334,6 +1366,28 @@ BOOST_AUTO_TEST_CASE( burn_worker_test ) BOOST_AUTO_TEST_CASE( force_settle_test ) { + uint32_t skip = database::skip_witness_signature + | database::skip_transaction_signatures + | database::skip_transaction_dupe_check + | database::skip_block_size_check + | database::skip_tapos_check + | database::skip_authority_check + | database::skip_merkle_check + ; + + generate_block( skip ); + + for( int i=0; i<2; i++ ) + { + if( i == 1 ) + { + auto mi = db.get_global_properties().parameters.maintenance_interval; + generate_blocks(HARDFORK_CORE_342_TIME - mi, true, skip); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true, skip); + } + set_expiration( db, trx ); + + int blocks = 0; try { ACTORS( (nathan)(shorter1)(shorter2)(shorter3)(shorter4)(shorter5) ); @@ -1453,7 +1507,9 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) BOOST_CHECK( settle_id(db).owner == nathan_id ); // Wait for settlement to take effect - generate_blocks(settle_id(db).settlement_date); + generate_blocks( settle_id(db).settlement_date, true, skip ); + blocks += 2; + BOOST_CHECK(db.find(settle_id) == nullptr); BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 50 ); BOOST_CHECK_EQUAL( get_balance(nathan_id, bitusd_id), 14950); @@ -1483,7 +1539,8 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) // c2 2000 : 3998 1.9990 550 settled // c1 1000 : 2000 2.0000 - generate_blocks( settle_id(db).settlement_date ); + generate_blocks( settle_id(db).settlement_date, true, skip ); + blocks += 2; int64_t call1_payout = 0; int64_t call2_payout = 550*99/100; @@ -1491,6 +1548,13 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) int64_t call4_payout = 4000*99/100; int64_t call5_payout = 5000*99/100; + if( i == 1 ) // BSIP35: better rounding + { + call3_payout = 49 + (2950*99+100-1)/100; // round up + call4_payout = (4000*99+100-1)/100; // round up + call5_payout = (5000*99+100-1)/100; // round up + } + BOOST_CHECK_EQUAL( get_balance(shorter1_id, core_id), initial_balance-2*1000 ); // full collat still tied up BOOST_CHECK_EQUAL( get_balance(shorter2_id, core_id), initial_balance-2*1999 ); // full collat still tied up BOOST_CHECK_EQUAL( get_balance(shorter3_id, core_id), initial_balance-call3_payout ); // initial balance minus transfer to Nathan (as BitUSD) @@ -1518,6 +1582,16 @@ BOOST_AUTO_TEST_CASE( force_settle_test ) edump((e.to_detail_string())); throw; } + + // undo above tx's and reset + generate_block( skip ); + ++blocks; + while( blocks > 0 ) + { + db.pop_block(); + --blocks; + } + } } BOOST_AUTO_TEST_CASE( assert_op_test ) From 06aee789a4e2045d899e02b096d5a1a908be2865 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 22 Apr 2018 16:11:44 -0400 Subject: [PATCH 51/56] BSIP35: fix price calculation for settle:call --- libraries/chain/db_update.cpp | 39 ++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 4967792475..303d530c04 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -272,6 +272,11 @@ void database::clear_expired_orders() //Cancel expired limit orders auto head_time = head_block_time(); auto maint_time = get_dynamic_global_properties().next_maintenance_time; + + bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing + bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding + bool before_core_hardfork_606 = ( maint_time <= HARDFORK_CORE_606_TIME ); // feed always trigger call + auto& limit_index = get_index_type().indices().get(); while( !limit_index.empty() && limit_index.begin()->expiration <= head_time ) { @@ -279,7 +284,7 @@ void database::clear_expired_orders() auto base_asset = order.sell_price.base.asset_id; auto quote_asset = order.sell_price.quote.asset_id; cancel_limit_order( order ); - if( maint_time <= HARDFORK_CORE_606_TIME ) + if( before_core_hardfork_606 ) { // check call orders // Comments below are copied from limit_order_cancel_evaluator::do_apply(...) @@ -298,6 +303,7 @@ void database::clear_expired_orders() asset_id_type current_asset = settlement_index.begin()->settlement_asset_id(); asset max_settlement_volume; price settlement_fill_price; + price settlement_price; bool current_asset_finished = false; bool extra_dump = false; @@ -392,24 +398,23 @@ void database::clear_expired_orders() break; } - auto& pays = order.balance; - auto receives = (order.balance * mia.current_feed.settlement_price); - receives.amount = (fc::uint128_t(receives.amount.value) * - (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64(); - assert(receives <= order.balance * mia.current_feed.settlement_price); - - price settlement_price = pays / receives; - - // Calculate fill_price with a bigger volume to reduce impacts of rounding - // TODO replace the calculation with new operator*() and/or operator/() if( settlement_fill_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_fill_price = mia.current_feed.settlement_price + / ratio_type( GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent, + GRAPHENE_100_PERCENT ); + + if( before_core_hardfork_342 ) { - asset tmp_pays = max_settlement_volume; - asset tmp_receives = tmp_pays * mia.current_feed.settlement_price; - tmp_receives.amount = (fc::uint128_t(tmp_receives.amount.value) * - (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / GRAPHENE_100_PERCENT).to_uint64(); - settlement_fill_price = tmp_pays / tmp_receives; + auto& pays = order.balance; + auto receives = (order.balance * mia.current_feed.settlement_price); + receives.amount = ( fc::uint128_t(receives.amount.value) * + (GRAPHENE_100_PERCENT - mia.options.force_settlement_offset_percent) / + GRAPHENE_100_PERCENT ).to_uint64(); + assert(receives <= order.balance * mia.current_feed.settlement_price); + settlement_price = pays / receives; } + else if( settlement_price.base.asset_id != current_asset ) // only calculate once per asset + settlement_price = settlement_fill_price; auto& call_index = get_index_type().indices().get(); asset settled = mia_object.amount(mia.force_settled_volume); @@ -430,7 +435,7 @@ void database::clear_expired_orders() } try { asset new_settled = match(*itr, order, settlement_price, max_settlement, settlement_fill_price); - if( maint_time > HARDFORK_CORE_184_TIME && new_settled.amount == 0 ) // unable to fill this settle order + if( !before_core_hardfork_184 && new_settled.amount == 0 ) // unable to fill this settle order { if( find_object( order_id ) ) // the settle order hasn't been cancelled current_asset_finished = true; From 45028c239bb1df3971678b6726c852ab1cca736a Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 23 Apr 2018 13:47:47 -0400 Subject: [PATCH 52/56] Fix compiler warnings (unused variables) --- tests/common/database_fixture.hpp | 3 +- tests/tests/fee_tests.cpp | 49 ++++++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 2ee05c28d3..bd7611d625 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -127,7 +127,8 @@ extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; #define PREP_ACTOR(name) \ fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \ - public_key_type name ## _public_key = name ## _private_key.get_public_key(); + public_key_type name ## _public_key = name ## _private_key.get_public_key(); \ + BOOST_CHECK( name ## _public_key != public_key_type() ); #define ACTOR(name) \ PREP_ACTOR(name) \ diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 6292d408dc..c50aff4d15 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -223,7 +223,6 @@ BOOST_AUTO_TEST_CASE(asset_claim_pool_test) // Alice claimes fee pool of her asset and can't claim pool of Bob's asset const share_type core_prec = asset::scaled_precision( asset_id_type()(db).precision ); - const auto& fees = *db.get_global_properties().parameters.current_fees; // return number of core shares (times precision) auto _core = [&core_prec]( int64_t x ) -> asset @@ -1328,12 +1327,16 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) BOOST_TEST_MESSAGE( "Creating orders those will never match: ao1, ao2, bo1, bo2 .." ); // ao1: won't expire, won't match, fee in core limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(100000, usd_id) )->id; + BOOST_CHECK( db.find( ao1_id ) != nullptr ); // ao2: will expire, won't match, fee in core limit_order_id_type ao2_id = create_sell_order( alice_id, asset(800), asset(100000, usd_id), exp )->id; + BOOST_CHECK( db.find( ao2_id ) != nullptr ); // bo1: won't expire, won't match, fee in usd limit_order_id_type bo1_id = create_sell_order( bob_id, asset(1000, usd_id), asset(100000), max_exp, cer )->id; + BOOST_CHECK( db.find( bo1_id ) != nullptr ); // bo2: will expire, won't match, fee in usd limit_order_id_type bo2_id = create_sell_order( bob_id, asset(800, usd_id), asset(100000), exp, cer )->id; + BOOST_CHECK( db.find( bo2_id ) != nullptr ); alice_bc -= order_create_fee * 2; alice_bc -= 1000; @@ -1358,6 +1361,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // ao3: won't expire, partially match before hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao3 .." ); limit_order_id_type ao3_id = create_sell_order( alice_id, asset(900), asset(2700, usd_id) )->id; + BOOST_CHECK( db.find( ao3_id ) != nullptr ); create_sell_order( bob_id, asset(600, usd_id), asset(200) ); alice_bc -= order_create_fee; @@ -1378,6 +1382,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // ao4: will expire, will partially match before hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao4 .." ); limit_order_id_type ao4_id = create_sell_order( alice_id, asset(700), asset(1400, usd_id), exp )->id; + BOOST_CHECK( db.find( ao4_id ) != nullptr ); create_sell_order( bob_id, asset(200, usd_id), asset(100) ); alice_bc -= order_create_fee; @@ -1398,6 +1403,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // bo3: won't expire, will partially match before hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo3 .." ); limit_order_id_type bo3_id = create_sell_order( bob_id, asset(500, usd_id), asset(1500), max_exp, cer )->id; + BOOST_CHECK( db.find( bo3_id ) != nullptr ); create_sell_order( alice_id, asset(450), asset(150, usd_id) ); alice_bc -= order_create_fee; @@ -1420,6 +1426,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // bo4: will expire, will partially match before hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo4 .." ); limit_order_id_type bo4_id = create_sell_order( bob_id, asset(300, usd_id), asset(600), exp, cer )->id; + BOOST_CHECK( db.find( bo4_id ) != nullptr ); create_sell_order( alice_id, asset(140), asset(70, usd_id) ); alice_bc -= order_create_fee; @@ -1442,6 +1449,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // ao5: won't expire, partially match after hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao5 .." ); limit_order_id_type ao5_id = create_sell_order( alice_id, asset(606), asset(909, usd_id) )->id; + BOOST_CHECK( db.find( ao5_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 606; @@ -1457,6 +1465,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // ao6: will expire, partially match after hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao6 .." ); limit_order_id_type ao6_id = create_sell_order( alice_id, asset(333), asset(444, usd_id), exp2 )->id; + BOOST_CHECK( db.find( ao6_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 333; @@ -1472,6 +1481,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // bo5: won't expire, partially match after hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo5 .." ); limit_order_id_type bo5_id = create_sell_order( bob_id, asset(255, usd_id), asset(408), max_exp, cer )->id; + BOOST_CHECK( db.find( bo5_id ) != nullptr ); bob_bu -= order_create_fee; bob_bu -= 255; @@ -1489,6 +1499,7 @@ BOOST_AUTO_TEST_CASE( hf445_fee_refund_cross_test ) // bo6: will expire, partially match after hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo6 .." ); limit_order_id_type bo6_id = create_sell_order( bob_id, asset(127, usd_id), asset(127), exp2, cer )->id; + BOOST_CHECK( db.find( bo6_id ) != nullptr ); bob_bu -= order_create_fee; bob_bu -= 127; @@ -1844,7 +1855,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_test ) if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1; int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount; int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount; - + BOOST_CHECK( core_cancel_fee >= order_cancel_fee ); BOOST_TEST_MESSAGE( "Start" ); @@ -2325,6 +2336,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) if( usd_cancel_fee * cer_core_amount != order_cancel_fee * cer_usd_amount ) usd_cancel_fee += 1; int64_t core_create_fee = usd_create_fee * cer_core_amount / cer_usd_amount; int64_t core_cancel_fee = usd_cancel_fee * cer_core_amount / cer_usd_amount; + BOOST_CHECK( core_cancel_fee >= order_cancel_fee ); uint32_t skip = database::skip_witness_signature | database::skip_transaction_signatures @@ -2381,12 +2393,16 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) BOOST_TEST_MESSAGE( "Creating orders those will never match: ao1, ao2, bo1, bo2 .." ); // ao1: won't expire, won't match, fee in core limit_order_id_type ao1_id = create_sell_order( alice_id, asset(1000), asset(100000, usd_id) )->id; + BOOST_CHECK( db.find( ao1_id ) != nullptr ); // ao2: will expire, won't match, fee in core limit_order_id_type ao2_id = create_sell_order( alice_id, asset(800), asset(100000, usd_id), exp )->id; + BOOST_CHECK( db.find( ao2_id ) != nullptr ); // bo1: won't expire, won't match, fee in usd limit_order_id_type bo1_id = create_sell_order( bob_id, asset(1000, usd_id), asset(100000), max_exp, cer )->id; + BOOST_CHECK( db.find( bo1_id ) != nullptr ); // bo2: will expire, won't match, fee in usd limit_order_id_type bo2_id = create_sell_order( bob_id, asset(800, usd_id), asset(100000), exp, cer )->id; + BOOST_CHECK( db.find( bo2_id ) != nullptr ); alice_bc -= order_create_fee * 2; alice_bc -= 1000; @@ -2411,6 +2427,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao3: won't expire, partially match before hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao3 .." ); // 1:30 limit_order_id_type ao3_id = create_sell_order( alice_id, asset(900), asset(27000, usd_id) )->id; + BOOST_CHECK( db.find( ao3_id ) != nullptr ); create_sell_order( bob_id, asset(6000, usd_id), asset(200) ); alice_bc -= order_create_fee; @@ -2431,6 +2448,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao4: will expire, will partially match before hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao4 .." ); // 1:20 limit_order_id_type ao4_id = create_sell_order( alice_id, asset(700), asset(14000, usd_id), exp )->id; + BOOST_CHECK( db.find( ao4_id ) != nullptr ); create_sell_order( bob_id, asset(2000, usd_id), asset(100) ); alice_bc -= order_create_fee; @@ -2451,6 +2469,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo3: won't expire, will partially match before hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo3 .." ); // 1:30 limit_order_id_type bo3_id = create_sell_order( bob_id, asset(500, usd_id), asset(15000), max_exp, cer )->id; + BOOST_CHECK( db.find( bo3_id ) != nullptr ); create_sell_order( alice_id, asset(4500), asset(150, usd_id) ); alice_bc -= order_create_fee; @@ -2473,6 +2492,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo4: will expire, will partially match before hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo4 .." ); // 1:20 limit_order_id_type bo4_id = create_sell_order( bob_id, asset(300, usd_id), asset(6000), exp, cer )->id; + BOOST_CHECK( db.find( bo4_id ) != nullptr ); create_sell_order( alice_id, asset(1400), asset(70, usd_id) ); alice_bc -= order_create_fee; @@ -2496,6 +2516,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao11: won't expire, partially match after hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao11 .." ); // 1:18 limit_order_id_type ao11_id = create_sell_order( alice_id, asset(510), asset(9180, usd_id) )->id; + BOOST_CHECK( db.find( ao11_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 510; @@ -2511,6 +2532,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao12: will expire, partially match after hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao12 .." ); // 1:16 limit_order_id_type ao12_id = create_sell_order( alice_id, asset(256), asset(4096, usd_id), exp2 )->id; + BOOST_CHECK( db.find( ao12_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 256; @@ -2526,6 +2548,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo11: won't expire, partially match after hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo11 .." ); // 1:18 limit_order_id_type bo11_id = create_sell_order( bob_id, asset(388, usd_id), asset(6984), max_exp, cer )->id; + BOOST_CHECK( db.find( bo11_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 388; @@ -2543,6 +2566,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo12: will expire, partially match after hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo12 .." ); // 1:17 limit_order_id_type bo12_id = create_sell_order( bob_id, asset(213, usd_id), asset(3621), exp2, cer )->id; + BOOST_CHECK( db.find( bo12_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 213; @@ -2560,6 +2584,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao5: won't expire, partially match after hard fork 445, fee in core BOOST_TEST_MESSAGE( "Creating order ao5 .." ); // 1:15 limit_order_id_type ao5_id = create_sell_order( alice_id, asset(606), asset(9090, usd_id) )->id; + BOOST_CHECK( db.find( ao5_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 606; @@ -2576,10 +2601,11 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) if( false ) { // only can have either ao5 or ao6, can't have both BOOST_TEST_MESSAGE( "Creating order ao6 .." ); // 3:40 = 1:13.33333 limit_order_id_type ao6_id = create_sell_order( alice_id, asset(333), asset(4440, usd_id), exp )->id; + BOOST_CHECK( db.find( ao6_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 333; - int64_t ao6_remain = 333; + // int64_t ao6_remain = 333; // only can have either ao5 or ao6, can't have both BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2593,12 +2619,13 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) if( false ) { // only can have either bo5 or bo6, can't have both BOOST_TEST_MESSAGE( "Creating order bo5 .." ); // 1:16 limit_order_id_type bo5_id = create_sell_order( bob_id, asset(255, usd_id), asset(4080), max_exp, cer )->id; + BOOST_CHECK( db.find( bo5_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 255; pool_b -= core_create_fee; accum_b += usd_create_fee; - int64_t bo5_remain = 255; + //int64_t bo5_remain = 255; // only can have either bo5 or bo6, can't have both BOOST_CHECK_EQUAL( get_balance( alice_id, core_id ), alice_bc ); BOOST_CHECK_EQUAL( get_balance( alice_id, usd_id ), alice_bu ); @@ -2611,6 +2638,8 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo6: will expire, partially match after hard fork 445, fee in usd BOOST_TEST_MESSAGE( "Creating order bo6 .." ); // 1:10 limit_order_id_type bo6_id = create_sell_order( bob_id, asset(127, usd_id), asset(1270), exp, cer )->id; + BOOST_CHECK( db.find( bo6_id ) != nullptr ); + BOOST_CHECK( db.find( bo6_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 127; @@ -2723,12 +2752,16 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) BOOST_TEST_MESSAGE( "Creating more orders those will never match: ao7, ao8, bo7, bo8 .." ); // ~ 1:100 // ao7: won't expire, won't match, fee in core limit_order_id_type ao7_id = create_sell_order( alice_id, asset(1003), asset(100000, usd_id) )->id; + BOOST_CHECK( db.find( ao7_id ) != nullptr ); // ao8: will expire, won't match, fee in core limit_order_id_type ao8_id = create_sell_order( alice_id, asset(803), asset(100000, usd_id), exp1 )->id; + BOOST_CHECK( db.find( ao8_id ) != nullptr ); // bo7: won't expire, won't match, fee in usd limit_order_id_type bo7_id = create_sell_order( bob_id, asset(1003, usd_id), asset(100000), max_exp, cer )->id; + BOOST_CHECK( db.find( bo7_id ) != nullptr ); // bo8: will expire, won't match, fee in usd limit_order_id_type bo8_id = create_sell_order( bob_id, asset(803, usd_id), asset(100000), exp1, cer )->id; + BOOST_CHECK( db.find( bo8_id ) != nullptr ); alice_bc -= order_create_fee * 2; alice_bc -= 1003; @@ -2753,6 +2786,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao9: won't expire, partially match before hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao9 .." ); // 1:3 limit_order_id_type ao9_id = create_sell_order( alice_id, asset(909), asset(2727, usd_id) )->id; + BOOST_CHECK( db.find( ao9_id ) != nullptr ); create_sell_order( bob_id, asset(606, usd_id), asset(202) ); alice_bc -= order_create_fee; @@ -2773,6 +2807,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao10: will expire, will partially match before hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao10 .." ); // 1:2 limit_order_id_type ao10_id = create_sell_order( alice_id, asset(707), asset(1414, usd_id), exp )->id; + BOOST_CHECK( db.find( ao10_id ) != nullptr ); create_sell_order( bob_id, asset(202, usd_id), asset(101) ); alice_bc -= order_create_fee; @@ -2793,6 +2828,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo9: won't expire, will partially match before hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo9 .." ); // 1:3 limit_order_id_type bo9_id = create_sell_order( bob_id, asset(505, usd_id), asset(1515), max_exp, cer )->id; + BOOST_CHECK( db.find( bo9_id ) != nullptr ); create_sell_order( alice_id, asset(453), asset(151, usd_id) ); alice_bc -= order_create_fee; @@ -2815,6 +2851,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo10: will expire, will partially match before hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo10 .." ); // 1:2 limit_order_id_type bo10_id = create_sell_order( bob_id, asset(302, usd_id), asset(604), exp, cer )->id; + BOOST_CHECK( db.find( bo10_id ) != nullptr ); create_sell_order( alice_id, asset(142), asset(71, usd_id) ); alice_bc -= order_create_fee; @@ -2837,6 +2874,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao13: won't expire, partially match after hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao13 .." ); // 1:1.5 limit_order_id_type ao13_id = create_sell_order( alice_id, asset(424), asset(636, usd_id) )->id; + BOOST_CHECK( db.find( ao13_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 424; @@ -2852,6 +2890,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // ao14: will expire, partially match after hard fork core-604, fee in core BOOST_TEST_MESSAGE( "Creating order ao14 .." ); // 1:1.2 limit_order_id_type ao14_id = create_sell_order( alice_id, asset(525), asset(630, usd_id), exp )->id; + BOOST_CHECK( db.find( ao14_id ) != nullptr ); alice_bc -= order_create_fee; alice_bc -= 525; @@ -2867,6 +2906,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo13: won't expire, partially match after hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo13 .." ); // 1:1.5 limit_order_id_type bo13_id = create_sell_order( bob_id, asset(364, usd_id), asset(546), max_exp, cer )->id; + BOOST_CHECK( db.find( bo13_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 364; @@ -2884,6 +2924,7 @@ BOOST_AUTO_TEST_CASE( bsip26_fee_refund_cross_test ) // bo14: will expire, partially match after hard fork core-604, fee in usd BOOST_TEST_MESSAGE( "Creating order bo14 .." ); // 1:1.2 limit_order_id_type bo14_id = create_sell_order( bob_id, asset(365, usd_id), asset(438), exp, cer )->id; + BOOST_CHECK( db.find( bo14_id ) != nullptr ); bob_bu -= usd_create_fee; bob_bu -= 365; From 5b0d217804ba0050ad07752c0cbb4baebd81b649 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 23 Apr 2018 16:17:50 -0400 Subject: [PATCH 53/56] BSIP35 tests about maximum_force_settlement_volume --- tests/tests/settle_tests.cpp | 529 ++++++++++++++++++++++++++++++++++- 1 file changed, 519 insertions(+), 10 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 5e786c8940..23d4bcb787 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -113,7 +113,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); - // final checks + // checks BOOST_CHECK( !db.find( settle_id ) ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); // rachel paid 4 usd and got nothing BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 196); @@ -127,8 +127,10 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1002 ); // 1000 + 6 - 4 + // settle more and check rounding issue - // by default 20% of total supply can be settled per maintenance interval + // by default 20% of total supply can be settled per maintenance interval, here we test less than it set_expiration( db, trx ); operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); @@ -161,7 +163,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) generate_blocks( db.head_block_time() + fc::hours(16) ); set_expiration( db, trx ); - // final checks + // checks BOOST_CHECK( !db.find( settle_id2 ) ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core and paid 34 usd BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 162); @@ -170,11 +172,259 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - BOOST_CHECK_EQUAL( 996-34, call_paul_id(db).debt.value ); - BOOST_CHECK_EQUAL( 100-1, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 962, call_paul_id(db).debt.value ); // 996 - 34 + BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); // 100 - 1 BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 968 ); // 1002 - 34 + + // prepare for more tests + transfer(paul_id, rachel_id, asset(300, bitusd_id)); + borrow(michael_id(db), bitusd_id(db).amount(2), core_id(db).amount(3)); + + // settle even more and check rounding issue + // by default 20% of total supply can be settled per maintenance interval, here we test more than it + const operation_result result3 = force_settle(rachel_id(db), bitusd_id(db).amount(3)); + const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); + const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); + + force_settlement_id_type settle_id3 = result3.get(); + BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); + + force_settlement_id_type settle_id4 = result4.get(); + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); + + force_settlement_id_type settle_id5 = result5.get(); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // 162 + 300 - 3 - 434 - 5 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); // 6 + 2 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); // 99999992 - 3 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); // 800 - 300 + + BOOST_CHECK_EQUAL( 962, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); // 6 + 2 + BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); // 8 + 3 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 970 ); // 968 + 2 + + generate_blocks( db.head_block_time() + fc::hours(4) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(22) ); + set_expiration( db, trx ); + + // checks + // maximum amount that can be settled now is round_down(970 * 20%) = 194. + // settle_id3 (amount was 3) will be filled and get nothing. + // settle_id4 will pay 194 - 3 = 191 usd, will get round_down(191*5/101) = 9 core + BOOST_CHECK( !db.find( settle_id3 ) ); + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 243 ); // 434 - 191 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); // 1 + 9 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 768, call_paul_id(db).debt.value ); // 962 - 3 - 191 + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); // 99 - 9 + BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 776 ); // 970 - 3 - 191 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 194 ); // 3 + 191 + + generate_block(); + + // michael borrows more + set_expiration( db, trx ); + borrow(michael_id(db), bitusd_id(db).amount(18), core_id(db).amount(200)); + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 243 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); // 8 + 18 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); // 99999989 - 200 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 768, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); // 8 + 18 + BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); // 11 + 200 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 794 ); // 776 + 18 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 194 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((794+194) * 20%) = 197, + // already settled 194, so 197 - 194 = 3 more usd can be settled, + // so settle_id3 will pay 3 usd and get nothing + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 240 ); // 243 - 3 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 765, call_paul_id(db).debt.value ); // 768 - 3 + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 791 ); // 794 - 3 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 197 ); // 194 + 3 + + // michael borrows a little more + set_expiration( db, trx ); + borrow(michael_id(db), bitusd_id(db).amount(20), core_id(db).amount(20)); + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 240 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); // 26 + 20 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); // 99999789 - 20 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 765, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); // 26 + 20 + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); // 211 + 20 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 811 ); // 791 + 20 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 197 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((811+197) * 20%) = 201, + // already settled 197, so 201 - 197 = 4 more usd can be settled, + // so settle_id4 will pay 4 usd and get nothing + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 236 ); // 240 - 4 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 761, call_paul_id(db).debt.value ); // 765 - 4 + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 807 ); // 811 - 4 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 201 ); // 197 + 4 + + generate_block(); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // get to another maintenance interval + generate_blocks( db.head_block_time() + fc::hours(22) ); + set_expiration( db, trx ); + + // maximum amount that can be settled now is round_down(807 * 20%) = 161, + // settle_id4 will pay 161 usd, will get round_down(161*5/101) = 7 core + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 75 ); // 236 - 161 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 17); // 10 + 7 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 600, call_paul_id(db).debt.value ); // 761 - 161 + BOOST_CHECK_EQUAL( 83, call_paul_id(db).collateral.value ); // 90 - 7 + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 646 ); // 807 - 161 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 161 ); // reset to 0, then 161 more + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // generate some blocks + generate_blocks( db.head_block_time() + fc::hours(10) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // get to another maintenance interval + generate_blocks( db.head_block_time() + fc::hours(14) ); + set_expiration( db, trx ); + + // maximum amount that can be settled now is round_down(646 * 20%) = 129, + // but remaining amount in settle_id4 is only 75, + // and settle_id4 will pay 75 usd and get round_down(75*5/101) = 3 core, + // and settle_id5 (only has 5 usd) will pay 5 usd and get nothing. + BOOST_CHECK( !db.find( settle_id4 ) ); + BOOST_CHECK( !db.find( settle_id5 ) ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 20); // 17 + 3 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 520, call_paul_id(db).debt.value ); // 600 - 75 - 5 + BOOST_CHECK_EQUAL( 80, call_paul_id(db).collateral.value ); // 83 - 3 + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 566 ); // 646 - 75 - 5 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 80 ); // reset to 0, then 75 + 5 more + + generate_block(); + + // Note: the scenario that a big settle order matching several smaller call orders, + // and another scenario about force_settlement_offset_percent parameter, + // are tested in force_settle_test in operation_test2.cpp. + } FC_LOG_AND_RETHROW() } @@ -256,7 +506,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) // now yes expire settlement generate_blocks( db.head_block_time() + fc::hours(6) ); - // final checks + // checks BOOST_CHECK( !db.find( settle_id ) ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 0); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 200); // rachel's settle order is cancelled and he get refunded @@ -270,8 +520,10 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 1006 ); // 1000 + 6 + // settle more and check rounding issue - // by default 20% of total supply can be settled per maintenance interval + // by default 20% of total supply can be settled per maintenance interval, here we test less than it set_expiration( db, trx ); const operation_result result2 = force_settle(rachel_id(db), bitusd_id(db).amount(34)); @@ -304,7 +556,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) generate_blocks( db.head_block_time() + fc::hours(16) ); set_expiration( db, trx ); - // final checks + // checks BOOST_CHECK( !db.find( settle_id2 ) ); BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); // rachel got 1 core BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 179); // paid 21 usd since 1 core worths a little more than 20 usd @@ -313,10 +565,267 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 800); - BOOST_CHECK_EQUAL( 1000-21, call_paul_id(db).debt.value ); - BOOST_CHECK_EQUAL( 100-1, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 979, call_paul_id(db).debt.value ); // 1000 - 21 + BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); // 100 - 1 BOOST_CHECK_EQUAL( 6, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 8, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 985 ); // 1006 - 21 + + // prepare for more tests + transfer(paul_id, rachel_id, asset(300, bitusd_id)); + borrow(michael_id(db), bitusd_id(db).amount(2), core_id(db).amount(3)); + + // settle even more and check rounding issue + // by default 20% of total supply can be settled per maintenance interval, here we test more than it + const operation_result result3 = force_settle(rachel_id(db), bitusd_id(db).amount(3)); + const operation_result result4 = force_settle(rachel_id(db), bitusd_id(db).amount(434)); + const operation_result result5 = force_settle(rachel_id(db), bitusd_id(db).amount(5)); + + force_settlement_id_type settle_id3 = result3.get(); + BOOST_CHECK_EQUAL( settle_id3(db).balance.amount.value, 3 ); + + force_settlement_id_type settle_id4 = result4.get(); + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 434 ); + + force_settlement_id_type settle_id5 = result5.get(); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 1); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 37); // 179 + 300 - 3 - 434 - 5 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); // 6 + 2 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); // 99999992 - 3 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); // 800 - 300 + + BOOST_CHECK_EQUAL( 979, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 99, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); // 6 + 2 + BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); // 8 + 3 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 987 ); // 985 + 2 + + generate_blocks( db.head_block_time() + fc::hours(4) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // now yes expire settlement + generate_blocks( db.head_block_time() + fc::hours(22) ); + set_expiration( db, trx ); + + // checks + // settle_id3 will be cancelled due to too small. + // maximum amount that can be settled now is round_down(987 * 20%) = 197, + // according to price (101/5), the amount worths more than 9 core but less than 10 core, so 9 core will be settled, + // and 9 core worths 181.5 usd, so rachel will pay 182 usd and get 9 core + BOOST_CHECK( !db.find( settle_id3 ) ); + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 ); // 434 - 182 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); // 1 + 9 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // 37 + 3 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 8); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999989); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value ); // 979 - 182 + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); // 99 - 9 + BOOST_CHECK_EQUAL( 8, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 11, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 805 ); // 987 - 182 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 ); + + generate_block(); + + // michael borrows more + set_expiration( db, trx ); + borrow(michael_id(db), bitusd_id(db).amount(18), core_id(db).amount(200)); + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); // 8 + 18 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); // 99999989 - 200 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); // 8 + 18 + BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); // 11 + 200 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 823 ); // 805 + 18 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((823+182) * 20%) = 201, + // already settled 182, so 201 - 182 = 19 more usd can be settled, + // according to price (101/5), the amount worths less than 1 core, + // so nothing will happen. + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 26); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999789); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 26, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 211, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 823 ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 ); + + // michael borrows a little more + set_expiration( db, trx ); + borrow(michael_id(db), bitusd_id(db).amount(20), core_id(db).amount(20)); + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 252 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 10); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); // 26 + 20 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); // 99999789 - 20 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 797, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 90, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); // 26 + 20 + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); // 211 + 20 + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 843 ); // 823 + 20 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 182 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((843+182) * 20%) = 205, + // already settled 182, so 205 - 182 = 23 more usd can be settled, + // according to price (101/5), the amount worths more than 1 core but less than 2 core, + // so settle order will fill 1 more core, since 1 core worth more than 20 usd but less than 21 usd, + // so rachel will pay 21 usd and get 1 core + + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 231 ); // 252 - 21 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 11); // 10 + 1 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // no change + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 776, call_paul_id(db).debt.value ); // 797 - 21 + BOOST_CHECK_EQUAL( 89, call_paul_id(db).collateral.value ); // 90 - 1 + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 822 ); // 843 - 21 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 203 ); // 182 + 21 + + generate_block(); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // get to another maintenance interval + generate_blocks( db.head_block_time() + fc::hours(22) ); + set_expiration( db, trx ); + + // maximum amount that can be settled now is round_down(822 * 20%) = 164, + // according to price (101/5), the amount worths more than 8 core but less than 9 core, + // so settle order will fill 8 more core, since 8 core worth more than 161 usd but less than 162 usd, + // so rachel will pay 162 usd and get 8 core + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 69 ); // 231 - 162 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); // 11 + 8 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // no change + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 614, call_paul_id(db).debt.value ); // 776 - 162 + BOOST_CHECK_EQUAL( 81, call_paul_id(db).collateral.value ); // 89 - 8 + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 660 ); // 822 - 162 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 ); // reset to 0, then 162 more + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // generate some blocks + generate_blocks( db.head_block_time() + fc::hours(10) ); + set_expiration( db, trx ); + + // adding new feed so we have valid price to exit + update_feed_producers( bitusd_id(db), {alice_id} ); + current_feed.maintenance_collateral_ratio = 1750; + current_feed.maximum_short_squeeze_ratio = 1100; + current_feed.settlement_price = bitusd_id(db).amount( 101 ) / core_id(db).amount(5); + publish_feed( bitusd_id(db), alice_id(db), current_feed ); + + // get to another maintenance interval + generate_blocks( db.head_block_time() + fc::hours(14) ); + set_expiration( db, trx ); + + // maximum amount that can be settled now is round_down(660 * 20%) = 132, + // but remaining amount in settle_id4 is only 69, + // according to price (101/5), the amount (69 usd) worths more than 3 core but less than 4 core, + // so settle order will fill 3 more core, since 3 core worth more than 60 usd but less than 61 usd, + // so rachel will pay 61 usd and get 3 core, the rest (69-61=8 usd) will be returned due to too small. + // and settle_id5 (only has 5 usd) will be cancelled as well. + BOOST_CHECK( !db.find( settle_id4 ) ); + BOOST_CHECK( !db.find( settle_id5 ) ); + + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 22); // 19 + 3 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 53); // 40 + 8 + 5 + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + + BOOST_CHECK_EQUAL( 553, call_paul_id(db).debt.value ); // 614 - 61 + BOOST_CHECK_EQUAL( 78, call_paul_id(db).collateral.value ); // 81 - 3 + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 599 ); // 660 - 61 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 61 ); // reset to 0, then 61 more + + generate_block(); + + // Note: the scenario that a big settle order matching several smaller call orders, + // and another scenario about force_settlement_offset_percent parameter, + // are tested in force_settle_test in operation_test2.cpp. + } FC_LOG_AND_RETHROW() } From 9526e51177fa81cc30e4cb37e8b1b8a474517369 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 23 Apr 2018 16:23:38 -0400 Subject: [PATCH 54/56] Remove potential logging spam --- 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 bacee3e34d..f31fdf597b 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -674,8 +674,8 @@ asset database::match( const call_order_object& call, wlog( "Something for nothing issue (#184, variant C-2) handled at block #${block}", ("block",head_block_num()) ); cancel_settle_order( settle ); } - else // neither order will be completely filled, perhaps due to max_settlement too small - wlog( "Something for nothing issue (#184, variant C-3) handled at block #${block}", ("block",head_block_num()) ); + // else do nothing: neither order will be completely filled, perhaps due to max_settlement too small + return asset( 0, settle.balance.asset_id ); } } From fdc1612f4afa2fe393083ee91d48af197354ab7f Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 24 Apr 2018 17:24:09 -0400 Subject: [PATCH 55/56] BSIP35 tests: cover C-1 and cull-settle --- tests/tests/settle_tests.cpp | 238 ++++++++++++++++++++++++++++++----- 1 file changed, 210 insertions(+), 28 deletions(-) diff --git a/tests/tests/settle_tests.cpp b/tests/tests/settle_tests.cpp index 23d4bcb787..a11addb010 100644 --- a/tests/tests/settle_tests.cpp +++ b/tests/tests/settle_tests.cpp @@ -43,7 +43,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) generate_block(); set_expiration( db, trx ); - ACTORS((paul)(michael)(rachel)(alice)); + ACTORS((paul)(michael)(rachel)(alice)(bob)(ted)); // create assets const auto& bitusd = create_bitasset("USDBIT", paul_id); @@ -55,6 +55,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) transfer(committee_account, michael_id, asset( 100000000 ) ); transfer(committee_account, paul_id, asset(10000000)); transfer(committee_account, alice_id, asset(10000000)); + transfer(committee_account, bob_id, asset(10000000)); // add a feed to asset update_feed_producers( bitusd, {paul.id} ); @@ -344,6 +345,32 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) generate_block(); + // give ted some usd + transfer(paul_id, ted_id, asset(100, bitusd_id)); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 100); // new: 100 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); // 500 - 100 + + // ted settle + const operation_result result6 = force_settle(ted_id(db), bitusd_id(db).amount(20)); + const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); + const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); + + force_settlement_id_type settle_id6 = result6.get(); + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); + + force_settlement_id_type settle_id7 = result7.get(); + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); + + force_settlement_id_type settle_id8 = result8.get(); + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); + + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); // 100 - 20 - 21 - 22 + + generate_block(); + // adding new feed so we have valid price to exit update_feed_producers( bitusd_id(db), {alice_id} ); current_feed.maintenance_collateral_ratio = 1750; @@ -359,13 +386,18 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) // settle_id4 will pay 161 usd, will get round_down(161*5/101) = 7 core BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 75 ); // 236 - 161 BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 17); // 10 + 7 BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); BOOST_CHECK_EQUAL( 600, call_paul_id(db).debt.value ); // 761 - 161 BOOST_CHECK_EQUAL( 83, call_paul_id(db).collateral.value ); // 90 - 7 @@ -375,6 +407,51 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 646 ); // 807 - 161 BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 161 ); // reset to 0, then 161 more + // bob borrow some + const call_order_object& call_bob = *borrow( bob_id(db), bitusd_id(db).amount(19), core_id(db).amount(2) ); + call_order_id_type call_bob_id = call_bob.id; + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); // 10000000 - 2 + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); // new + + BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value ); + BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 665 ); // 646 + 19 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 161 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((665+161) * 20%) = 165, + // settle_id4 will pay 165-161=4 usd, will get nothing + // bob's call order will get partially settled since its collateral ratio is the lowest + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 71 ); // 75 - 4 + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 17); // no change + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); // no change + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); + + BOOST_CHECK_EQUAL( 15, call_bob_id(db).debt.value ); // 19 - 4 + BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value ); // no change + BOOST_CHECK_EQUAL( 600, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 83, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 661 ); // 665 - 4 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 165 ); // 161 + 4 + // adding new feed so we have valid price to exit update_feed_producers( bitusd_id(db), {alice_id} ); current_feed.maintenance_collateral_ratio = 1750; @@ -397,27 +474,40 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test ) generate_blocks( db.head_block_time() + fc::hours(14) ); set_expiration( db, trx ); - // maximum amount that can be settled now is round_down(646 * 20%) = 129, - // but remaining amount in settle_id4 is only 75, - // and settle_id4 will pay 75 usd and get round_down(75*5/101) = 3 core, - // and settle_id5 (only has 5 usd) will pay 5 usd and get nothing. + // maximum amount that can be settled now is round_down(661 * 20%) = 132, + // settle_id4's remaining amount is 71, + // firstly it will pay 15 usd to call_bob and get nothing, + // call_bob will pay off all debt, so it will be closed and remaining collateral (2 core) will be returned; + // then it will pay 71-15=56 usd to call_paul and get round_down(56*5/101) = 2 core; + // settle_id5 (has 5 usd) will pay 5 usd and get nothing; + // settle_id6 (has 20 usd) will pay 20 usd and get nothing; + // settle_id7 (has 21 usd) will pay 21 usd and get 1 core; + // settle_id8 (has 22 usd) will pay 15 usd and get nothing, since reached 132 BOOST_CHECK( !db.find( settle_id4 ) ); BOOST_CHECK( !db.find( settle_id5 ) ); + BOOST_CHECK( !db.find( settle_id6 ) ); + BOOST_CHECK( !db.find( settle_id7 ) ); + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 7 ); // 22 - 15 - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 20); // 17 + 3 + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 10000000); // 9999998 + 2 + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); // 17 + 2 BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 20); BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 1); // 0 + 1 + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); - BOOST_CHECK_EQUAL( 520, call_paul_id(db).debt.value ); // 600 - 75 - 5 - BOOST_CHECK_EQUAL( 80, call_paul_id(db).collateral.value ); // 83 - 3 + BOOST_CHECK( !db.find( call_bob_id ) ); + BOOST_CHECK_EQUAL( 483, call_paul_id(db).debt.value ); // 600 - 56 - 5 - 20 - 21 - 15 + BOOST_CHECK_EQUAL( 80, call_paul_id(db).collateral.value ); // 83 - 2 - 1 BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); - BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 566 ); // 646 - 75 - 5 - BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 80 ); // reset to 0, then 75 + 5 more + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 529 ); // 661 - 132 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 132 ); // reset to 0, then 132 more generate_block(); @@ -436,7 +526,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); set_expiration( db, trx ); - ACTORS((paul)(michael)(rachel)(alice)); + ACTORS((paul)(michael)(rachel)(alice)(bob)(ted)); // create assets const auto& bitusd = create_bitasset("USDBIT", paul_id); @@ -448,6 +538,7 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) transfer(committee_account, michael_id, asset( 100000000 ) ); transfer(committee_account, paul_id, asset(10000000)); transfer(committee_account, alice_id, asset(10000000)); + transfer(committee_account, bob_id, asset(10000000)); // add a feed to asset update_feed_producers( bitusd, {paul.id} ); @@ -741,6 +832,32 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) generate_block(); + // give ted some usd + transfer(paul_id, ted_id, asset(100, bitusd_id)); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 100); // new: 100 + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); // 500 - 100 + + // ted settle + const operation_result result6 = force_settle(ted_id(db), bitusd_id(db).amount(20)); + const operation_result result7 = force_settle(ted_id(db), bitusd_id(db).amount(21)); + const operation_result result8 = force_settle(ted_id(db), bitusd_id(db).amount(22)); + + force_settlement_id_type settle_id6 = result6.get(); + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); + + force_settlement_id_type settle_id7 = result7.get(); + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); + + force_settlement_id_type settle_id8 = result8.get(); + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); + + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); // 100 - 20 - 21 - 22 + + generate_block(); + // adding new feed so we have valid price to exit update_feed_producers( bitusd_id(db), {alice_id} ); current_feed.maintenance_collateral_ratio = 1750; @@ -758,13 +875,18 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) // so rachel will pay 162 usd and get 8 core BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 69 ); // 231 - 162 BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); // no change, since it's after settle_id4 + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); // no change since not expired + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); // no change since not expired BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); // 11 + 8 BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); // no change BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); BOOST_CHECK_EQUAL( 614, call_paul_id(db).debt.value ); // 776 - 162 BOOST_CHECK_EQUAL( 81, call_paul_id(db).collateral.value ); // 89 - 8 @@ -774,6 +896,52 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 660 ); // 822 - 162 BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 ); // reset to 0, then 162 more + // bob borrow some + const call_order_object& call_bob = *borrow( bob_id(db), bitusd_id(db).amount(19), core_id(db).amount(2) ); + call_order_id_type call_bob_id = call_bob.id; + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); // 10000000 - 2 + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); // new + + BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value ); + BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 679 ); // 660 + 19 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 ); + + generate_block(); + + // maximum amount that can be settled now is round_down((679+162) * 20%) = 168, + // already settled 162, so 168 - 162 = 6 more usd can be settled, + // according to price (101/5), the amount worths less than 1 core, + // so nothing will happen. + BOOST_CHECK_EQUAL( settle_id4(db).balance.amount.value, 69 ); + BOOST_CHECK_EQUAL( settle_id5(db).balance.amount.value, 5 ); + BOOST_CHECK_EQUAL( settle_id6(db).balance.amount.value, 20 ); + BOOST_CHECK_EQUAL( settle_id7(db).balance.amount.value, 21 ); + BOOST_CHECK_EQUAL( settle_id8(db).balance.amount.value, 22 ); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999998); + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 19); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 40); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); + BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 0); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 37); + + BOOST_CHECK_EQUAL( 19, call_bob_id(db).debt.value ); + BOOST_CHECK_EQUAL( 2, call_bob_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 614, call_paul_id(db).debt.value ); + BOOST_CHECK_EQUAL( 81, call_paul_id(db).collateral.value ); + BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); + BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); + + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 679 ); + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 162 ); + // adding new feed so we have valid price to exit update_feed_producers( bitusd_id(db), {alice_id} ); current_feed.maintenance_collateral_ratio = 1750; @@ -796,29 +964,43 @@ BOOST_AUTO_TEST_CASE( settle_rounding_test_after_hf_184 ) generate_blocks( db.head_block_time() + fc::hours(14) ); set_expiration( db, trx ); - // maximum amount that can be settled now is round_down(660 * 20%) = 132, - // but remaining amount in settle_id4 is only 69, - // according to price (101/5), the amount (69 usd) worths more than 3 core but less than 4 core, - // so settle order will fill 3 more core, since 3 core worth more than 60 usd but less than 61 usd, - // so rachel will pay 61 usd and get 3 core, the rest (69-61=8 usd) will be returned due to too small. - // and settle_id5 (only has 5 usd) will be cancelled as well. + // maximum amount that can be settled now is round_down(679 * 20%) = 135, + // settle_id4's remaining amount is 69, so it can be fully processed: + // firstly call_bob will be matched, since it owes only 19 usd which worths less than 1 core, + // it will pay 1 core, and the rest (2-1=1 core) will be returned, short position will be closed; + // then call_paul will be matched, + // according to price (101/5), the amount (69-19=50 usd) worths more than 2 core but less than 3 core, + // so settle_id4 will get 2 more core, since 2 core worth more than 40 usd but less than 41 usd, + // call_rachel will pay 41 usd and get 2 core, the rest (50-41=9 usd) will be returned due to too small. + // settle_id5 (has 5 usd) will be cancelled due to too small; + // settle_id6 (has 20 usd) will be cancelled as well due to too small; + // settle_id7 (has 21 usd) will be filled and get 1 core, since it worths more than 1 core; but no more fund can be returned; + // settle_id8 (has 22 usd) will be filled and get 1 core, and 1 usd will be returned. BOOST_CHECK( !db.find( settle_id4 ) ); BOOST_CHECK( !db.find( settle_id5 ) ); - - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 22); // 19 + 3 - BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 53); // 40 + 8 + 5 + BOOST_CHECK( !db.find( settle_id6 ) ); + BOOST_CHECK( !db.find( settle_id7 ) ); + BOOST_CHECK( !db.find( settle_id8 ) ); + + BOOST_CHECK_EQUAL(get_balance(bob_id(db), core_id(db)), 9999999); // 9999998 + 1 + BOOST_CHECK_EQUAL(get_balance(bob_id(db), bitusd_id(db)), 19); + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), core_id(db)), 22); // 19 + 1 + 2 + BOOST_CHECK_EQUAL(get_balance(rachel_id(db), bitusd_id(db)), 54); // 40 + 9 + 5 BOOST_CHECK_EQUAL(get_balance(michael_id(db), bitusd_id(db)), 46); BOOST_CHECK_EQUAL(get_balance(michael_id(db), core_id(db)), 99999769); BOOST_CHECK_EQUAL(get_balance(paul_id(db), core_id(db)), 9999900); - BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 500); + BOOST_CHECK_EQUAL(get_balance(paul_id(db), bitusd_id(db)), 400); + BOOST_CHECK_EQUAL(get_balance(ted_id(db), core_id(db)), 2); // 0 + 1 + 1 + BOOST_CHECK_EQUAL(get_balance(ted_id(db), bitusd_id(db)), 58); // 37 + 20 + 1 - BOOST_CHECK_EQUAL( 553, call_paul_id(db).debt.value ); // 614 - 61 - BOOST_CHECK_EQUAL( 78, call_paul_id(db).collateral.value ); // 81 - 3 + BOOST_CHECK( !db.find( call_bob_id ) ); + BOOST_CHECK_EQUAL( 531, call_paul_id(db).debt.value ); // 614 - 41 - 21 - 21 + BOOST_CHECK_EQUAL( 77, call_paul_id(db).collateral.value ); // 81 - 2 - 1 - 1 BOOST_CHECK_EQUAL( 46, call_michael_id(db).debt.value ); BOOST_CHECK_EQUAL( 231, call_michael_id(db).collateral.value ); - BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 599 ); // 660 - 61 - BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 61 ); // reset to 0, then 61 more + BOOST_CHECK_EQUAL( bitusd_id(db).dynamic_data(db).current_supply.value, 577 ); // 679 - 19 - 41 - 21 - 21 + BOOST_CHECK_EQUAL( bitusd_id(db).bitasset_data(db).force_settled_volume.value, 102 ); // reset to 0, then 19 + 41 + 21 + 21 generate_block(); From 2f4252c05216dcaf17a6c8a6e0ac4c20a0b86168 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 26 Apr 2018 09:24:08 -0400 Subject: [PATCH 56/56] Add ilog about hard fork time for future cleanup --- libraries/chain/db_market.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index f31fdf597b..88ed94aadf 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -582,7 +582,12 @@ int database::match( const limit_order_object& bid, const call_order_object& ask auto maint_time = get_dynamic_global_properties().next_maintenance_time; // TODO remove when we're sure it's always false + bool before_core_hardfork_184 = ( maint_time <= HARDFORK_CORE_184_TIME ); // something-for-nothing bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding + if( before_core_hardfork_184 ) + ilog( "match(limit,call) is called before hardfork core-184 at block #${block}", ("block",head_block_num()) ); + if( before_core_hardfork_342 ) + ilog( "match(limit,call) is called before hardfork core-342 at block #${block}", ("block",head_block_num()) ); bool cull_taker = false; @@ -596,8 +601,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask // Be here, it's possible that taker is paying something for nothing due to partially filled in last loop. // In this case, we see it as filled and cancel it later - // TODO remove hardfork check when we're sure it's always true (but keep the zero amount check) - if( order_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + // TODO remove hardfork check when we're sure it's always after hard fork (but keep the zero amount check) + if( order_receives.amount == 0 && !before_core_hardfork_184 ) return 1; if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) @@ -618,8 +623,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask if( before_core_hardfork_342 ) // TODO remove this "if" when we're sure it's always false (keep the code in else) { order_receives = usd_to_buy * match_price; // round down here, in favor of call order - // TODO remove hardfork check when we're sure it's always true (but keep the zero amount check) - if( order_receives.amount == 0 && maint_time > HARDFORK_CORE_184_TIME ) + // TODO remove hardfork check when we're sure it's always after hard fork (but keep the zero amount check) + if( order_receives.amount == 0 && !before_core_hardfork_184 ) return 1; } else // has hardfork core-342