diff --git a/libraries/chain/asset.cpp b/libraries/chain/asset.cpp index e3b8315d..35287251 100644 --- a/libraries/chain/asset.cpp +++ b/libraries/chain/asset.cpp @@ -126,4 +126,26 @@ namespace graphene { namespace chain { FC_ASSERT( maintenance_collateral_ratio >= maximum_short_squeeze_ratio ); } FC_CAPTURE_AND_RETHROW( (*this) ) } + price price_feed::max_short_squeeze_price()const + { + asset collateral = settlement_price.quote; + fc::uint128 tmp( collateral.amount.value ); + tmp *= maximum_short_squeeze_ratio - 1000; + tmp /= 1000; + FC_ASSERT( tmp <= GRAPHENE_MAX_SHARE_SUPPLY ); + collateral.amount = tmp.to_uint64(); + return settlement_price.base / collateral; + } + price price_feed::maintenance_price()const + { + asset collateral = settlement_price.quote; + fc::uint128 tmp( collateral.amount.value ); + tmp *= maintenance_collateral_ratio - 1000; + tmp /= 1000; + FC_ASSERT( tmp <= GRAPHENE_MAX_SHARE_SUPPLY ); + collateral.amount = tmp.to_uint64(); + return settlement_price.base / collateral; + } + + } } // graphene::chain diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 3da104aa..be65bc5f 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -308,6 +308,7 @@ void_result asset_update_feed_producers_evaluator::do_apply(const asset_update_f a.update_median_feeds(db().head_block_time()); }); + return void_result(); } @@ -396,6 +397,9 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope a.update_median_feeds(d.head_block_time()); }); + /// TODO: only do this if the median feed actually changed, otherwise there is no point + db().check_call_orders( base ); + return void_result(); } FC_CAPTURE_AND_RETHROW((o)) } diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index a69ef05f..4b429035 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -388,10 +388,12 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa const limit_order_index& limit_index = get_index_type(); const auto& limit_price_index = limit_index.indices().get(); + // looking for limit orders selling the most USD for the least CORE auto max_price = price::max( mia.id, bitasset.options.short_backing_asset ); - auto min_price = bitasset.current_feed.max_short_squeeze_price(); - /* - if( require_orders ) + // stop when limit orders are selling too little USD for too much CORE + //auto min_price = bitasset.current_feed.max_short_squeeze_price(); + auto min_price = price::min( mia.id, bitasset.options.short_backing_asset ); + idump((bitasset.current_feed.settlement_price)(bitasset.current_feed.settlement_price.to_real())); { for( const auto& order : limit_price_index ) wdump((order)(order.sell_price.to_real())); @@ -404,24 +406,21 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa wdump((max_price)(max_price.to_real())); wdump((min_price)(min_price.to_real())); } - */ - FC_ASSERT( max_price.base.asset_id == min_price.base.asset_id ); + assert( max_price.base.asset_id == min_price.base.asset_id ); // wlog( "from ${a} Debt/Col to ${b} Debt/Col ", ("a", max_price.to_real())("b",min_price.to_real()) ); // NOTE limit_price_index is sorted from greatest to least auto limit_itr = limit_price_index.lower_bound( max_price ); auto limit_end = limit_price_index.upper_bound( min_price ); - /* if( limit_itr != limit_price_index.end() ) wdump((*limit_itr)(limit_itr->sell_price.to_real())); if( limit_end != limit_price_index.end() ) wdump((*limit_end)(limit_end->sell_price.to_real())); - */ if( limit_itr == limit_end ) { - //wlog( "no orders available to fill margin calls" ); + wlog( "no orders available to fill margin calls" ); return false; } diff --git a/libraries/chain/include/graphene/chain/asset.hpp b/libraries/chain/include/graphene/chain/asset.hpp index 37a6bb7b..576768de 100644 --- a/libraries/chain/include/graphene/chain/asset.hpp +++ b/libraries/chain/include/graphene/chain/asset.hpp @@ -158,10 +158,7 @@ namespace graphene { namespace chain { * debt * settlement_price < debt * maintenance * debt * maintenance_price() < debt * max_short_squeeze_price() */ - price maintenance_price()const - { - return ~price::call_price( settlement_price.base, settlement_price.quote, maintenance_collateral_ratio ); - } + price maintenance_price()const; /** When selling collateral to pay off debt, the least amount of debt to receive should be * min_usd = max_short_squeeze_price() * collateral @@ -169,10 +166,7 @@ namespace graphene { namespace chain { * This is provided to ensure that a black swan cannot be trigged due to poor liquidity alone, it * must be confirmed by having the max_short_squeeze_price() move below the black swan price. */ - price max_short_squeeze_price()const - { - return ~price::call_price( settlement_price.base, settlement_price.quote, maximum_short_squeeze_ratio ); - } + price max_short_squeeze_price()const; ///@} friend bool operator == ( const price_feed& a, const price_feed& b ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 06b09106..1f6594d8 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -41,6 +41,7 @@ namespace graphene { namespace chain { using std::cout; +using std::cerr; database_fixture::database_fixture() : app(), db( *app.chain_database() ) @@ -745,25 +746,25 @@ void database_fixture::print_market( const string& syma, const string& symb )con const auto& limit_idx = db.get_index_type(); const auto& price_idx = limit_idx.indices().get(); - cout << std::fixed; - cout.precision(5); - cout << std::setw(10) << std::left << "NAME" << " "; - cout << std::setw(16) << std::right << "FOR SALE" << " "; - cout << std::setw(16) << std::right << "FOR WHAT" << " "; - cout << std::setw(10) << std::right << "PRICE" << " "; - cout << std::setw(10) << std::right << "1/PRICE" << "\n"; - cout << string(70, '=') << std::endl; + cerr << std::fixed; + cerr.precision(5); + cerr << std::setw(10) << std::left << "NAME" << " "; + cerr << std::setw(16) << std::right << "FOR SALE" << " "; + cerr << std::setw(16) << std::right << "FOR WHAT" << " "; + cerr << std::setw(10) << std::right << "PRICE (S/W)" << " "; + cerr << std::setw(10) << std::right << "1/PRICE (W/S)" << "\n"; + cerr << string(70, '=') << std::endl; auto cur = price_idx.begin(); while( cur != price_idx.end() ) { - cout << std::setw( 10 ) << std::left << cur->seller(db).name << " "; - cout << std::setw( 10 ) << std::right << cur->for_sale.value << " "; - cout << std::setw( 5 ) << std::left << cur->amount_for_sale().asset_id(db).symbol << " "; - cout << std::setw( 10 ) << std::right << cur->amount_to_receive().amount.value << " "; - cout << std::setw( 5 ) << std::left << cur->amount_to_receive().asset_id(db).symbol << " "; - cout << std::setw( 10 ) << std::right << cur->sell_price.to_real() << " "; - cout << std::setw( 10 ) << std::right << (~cur->sell_price).to_real() << " "; - cout << "\n"; + cerr << std::setw( 10 ) << std::left << cur->seller(db).name << " "; + cerr << std::setw( 10 ) << std::right << cur->for_sale.value << " "; + cerr << std::setw( 5 ) << std::left << cur->amount_for_sale().asset_id(db).symbol << " "; + cerr << std::setw( 10 ) << std::right << cur->amount_to_receive().amount.value << " "; + cerr << std::setw( 5 ) << std::left << cur->amount_to_receive().asset_id(db).symbol << " "; + cerr << std::setw( 10 ) << std::right << cur->sell_price.to_real() << " "; + cerr << std::setw( 10 ) << std::right << (~cur->sell_price).to_real() << " "; + cerr << "\n"; ++cur; } } @@ -793,8 +794,10 @@ void database_fixture::print_call_orders()const cout << std::setw(10) << std::right << "TYPE" << " "; cout << std::setw(16) << std::right << "DEBT" << " "; cout << std::setw(16) << std::right << "COLLAT" << " "; - cout << std::setw(16) << std::right << "CALL PRICE" << " "; - cout << std::setw(16) << std::right << "~CALL PRICE" << "\n"; + cout << std::setw(16) << std::right << "CALL PRICE(D/C)" << " "; + cout << std::setw(16) << std::right << "~CALL PRICE(C/D)" << " "; + cout << std::setw(16) << std::right << "SWAN(D/C)" << " "; + cout << std::setw(16) << std::right << "SWAN(C/D)" << "\n"; cout << string(70, '='); for( const call_order_object& o : db.get_index_type().indices() ) @@ -805,6 +808,8 @@ void database_fixture::print_call_orders()const cout << std::setw( 16 ) << std::right << pretty( o.get_collateral() ) << " "; cout << std::setw( 16 ) << std::right << o.call_price.to_real() << " "; cout << std::setw( 16 ) << std::right << (~o.call_price).to_real() << " "; + cout << std::setw( 16 ) << std::right << (o.get_debt()/o.get_collateral()).to_real() << " "; + cout << std::setw( 16 ) << std::right << (~(o.get_debt()/o.get_collateral())).to_real() << " "; } std::cout << "\n"; } @@ -818,7 +823,7 @@ void database_fixture::print_joint_market( const string& syma, const string& sym cout << std::setw(10) << std::right << "TYPE" << " "; cout << std::setw(16) << std::right << "FOR SALE" << " "; cout << std::setw(16) << std::right << "FOR WHAT" << " "; - cout << std::setw(16) << std::right << "PRICE" << "\n"; + cout << std::setw(16) << std::right << "PRICE (S/W)" << "\n"; cout << string(70, '='); const auto& limit_idx = db.get_index_type(); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index d43031f6..f894d107 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -39,10 +39,25 @@ using namespace graphene::chain; BOOST_FIXTURE_TEST_SUITE( operation_tests, database_fixture ) +BOOST_AUTO_TEST_CASE( feed_limit_logic_test ) +{ + try { + asset usd(100,1); + asset core(200,1); + price_feed feed; + feed.settlement_price = usd / core; + + FC_ASSERT( usd * feed.settlement_price < usd * feed.maintenance_price() ); + FC_ASSERT( usd * feed.maintenance_price() < usd * feed.max_short_squeeze_price() ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} BOOST_AUTO_TEST_CASE( call_order_update_test ) { try { - BOOST_TEST_MESSAGE("creating actors dan and sam" ); ACTORS((dan)(sam)); const auto& bitusd = create_bitasset("BITUSD"); const auto& core = asset_id_type()(db); @@ -121,6 +136,315 @@ BOOST_AUTO_TEST_CASE( call_order_update_test ) } } +/** + * This test sets up a situation where a margin call will be executed and ensures that + * it is properly filled. + * + * A margin call can happen in the following situation: + * 0. there exists a bid above the mas short squeeze price + * 1. highest bid is lower than the call price of an order + * 2. the asset is not a prediction market + * 3. there is a valid price feed + */ +BOOST_AUTO_TEST_CASE( margin_call_limit_test ) +{ try { + ACTORS((buyer)(seller)(borrower)(borrower2)(feedproducer)); + + const auto& bitusd = create_bitasset("BITUSD"); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + + transfer(genesis_account, buyer_id, asset(init_balance)); + transfer(genesis_account, borrower_id, asset(init_balance)); + transfer(genesis_account, borrower2_id, asset(init_balance)); + update_feed_producers( bitusd, {feedproducer.id} ); + + price_feed current_feed; + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + auto default_call_price = ~price::call_price( bitusd.amount(100), asset(100), 1750); + + // starting out with price 1:1 + publish_feed( bitusd, feedproducer, current_feed ); + + // start out with 2:1 collateral + borrow( borrower, bitusd.amount(5000), asset(10000), default_call_price ); + borrow( borrower2, bitusd.amount(5000), asset(30000), default_call_price ); + elog( "selling USD at 1:1, should be ok because we have 2:1 collateral" ); + create_sell_order( borrower, bitusd.amount(5000), core.amount(5000) ); + elog( "buying USD at 1:1" ); + create_sell_order( buyer, core.amount(5000), bitusd.amount(5000) ); + + BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 0 ); + BOOST_REQUIRE_EQUAL( get_balance( buyer, bitusd ), 4950 ); // 1% market fee + BOOST_REQUIRE_EQUAL( get_balance( borrower, core ), init_balance - 10000 + 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( buyer, core ), init_balance - 5000 ); + + ilog( "print call orders..." ); + print_call_orders(); + ilog( "print market..." ); + print_market( "", "" ); + + // sell the BitUSD for 50% more which represents a 33% fall in the value of the collateral + const auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1400) ); + const auto order2 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1600) ); + const auto order3 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1700) ); + const auto order4 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) ); + const auto order5 = create_sell_order( borrower2, bitusd.amount(950), core.amount(1850) ); + + ilog( "print call orders..." ); + print_call_orders(); + ilog( "print market..." ); + print_market( "", "" ); + + FC_ASSERT( order, "order should not match because sell price is below short squeeze protection" ); + wdump((bitusd.bitasset_data(db).current_feed.max_short_squeeze_price().to_real())); + wdump((bitusd.bitasset_data(db).current_feed.maintenance_price().to_real())); + wdump(((order->sell_price).to_real())); + + // update the feed to indicate a 33% drop in value + BOOST_TEST_MESSAGE( "Update feed to indicate CORE fell in value by 33%" ); + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(200); + publish_feed( bitusd, feedproducer, current_feed ); + + + /* + const asset_object& bitusd = create_bitasset( "BITUSD" ); + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + + db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ + usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); + }); + + const account_object& shorter1 = create_account( "shorter1" ); + const account_object& shorter2 = create_account( "shorter2" ); + const account_object& buyer1 = create_account( "buyer1" ); + const account_object& buyer2 = create_account( "buyer2" ); + + transfer( genesis_account(db), shorter1, asset( 10000 ) ); + transfer( genesis_account(db), shorter2, asset( 10000 ) ); + transfer( genesis_account(db), buyer1, asset( 10000 ) ); + transfer( genesis_account(db), buyer2, asset( 10000 ) ); + + BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); + BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); + BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee + + const auto& call_index = db.get_index_type().indices().get(); + const auto call_itr = call_index.find(boost::make_tuple(shorter1.id, bitusd.id)); + BOOST_REQUIRE( call_itr != call_index.end() ); + const call_order_object& call = *call_itr; + BOOST_CHECK(call.get_collateral() == core.amount(2000)); + BOOST_CHECK(call.get_debt() == bitusd.amount(1000)); + BOOST_CHECK(call.call_price == price(core.amount(1500), bitusd.amount(1000))); + BOOST_CHECK_EQUAL(get_balance(shorter1, core), 9000); + + ilog( "=================================== START===================================\n\n"); + // this should cause the highest bid to below the margin call threshold + // which means it should be filled by the cover + auto unmatched = create_sell_order( buyer1, bitusd.amount(495), core.amount(750) ); + if( unmatched ) edump((*unmatched)); + BOOST_CHECK( !unmatched ); + BOOST_CHECK(call.get_debt() == bitusd.amount(505)); + BOOST_CHECK(call.get_collateral() == core.amount(1250)); + + auto below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1)); + BOOST_REQUIRE(below_call_price); + auto above_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(303)); + BOOST_REQUIRE(above_call_price); + auto above_id = above_call_price->id; + + cancel_limit_order(*below_call_price); + BOOST_CHECK_THROW(db.get_object(above_id), fc::exception); + BOOST_CHECK(call.get_debt() == bitusd.amount(305)); + BOOST_CHECK(call.get_collateral() == core.amount(947)); + + below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1)); + BOOST_REQUIRE(below_call_price); + auto below_id = below_call_price->id; + above_call_price = create_sell_order(buyer1, bitusd.amount(95), core.amount(144)); + BOOST_REQUIRE(above_call_price); + above_id = above_call_price->id; + auto match_below_call = create_sell_order(buyer2, core.amount(1), bitusd.amount(200)); + BOOST_CHECK(!match_below_call); + + BOOST_CHECK_THROW(db.get_object(above_id), fc::exception); + BOOST_CHECK_THROW(db.get_object(below_id), fc::exception); + BOOST_CHECK(call.get_debt() == bitusd.amount(210)); + BOOST_CHECK(call.get_collateral() == core.amount(803)); + */ + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( margin_call_limit_test_protected ) +{ try { + FC_ASSERT( !"TODO - Reimplement with new short semantics" ); + /* + const asset_object& bitusd = create_bitasset( "BITUSD" ); + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + + db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ + usd.current_feed.call_limit = core.amount(1) / bitusd.amount(1); + }); + + const account_object& shorter1 = create_account( "shorter1" ); + const account_object& shorter2 = create_account( "shorter2" ); + const account_object& buyer1 = create_account( "buyer1" ); + const account_object& buyer2 = create_account( "buyer2" ); + + transfer( genesis_account(db), shorter1, asset( 10000 ) ); + transfer( genesis_account(db), shorter2, asset( 10000 ) ); + transfer( genesis_account(db), buyer1, asset( 10000 ) ); + transfer( genesis_account(db), buyer2, asset( 10000 ) ); + + BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); + BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); + BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee + + ilog( "=================================== START===================================\n\n"); + // this should cause the highest bid to below the margin call threshold + // which means it should be filled by the cover + auto unmatched = create_sell_order( buyer1, bitusd.amount(990), core.amount(1500) ); + if( unmatched ) edump((*unmatched)); + BOOST_REQUIRE( unmatched ); + */ + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( dont_margin_call_limit_test ) +{ try { + FC_ASSERT( !"TODO - Reimplement with new short semantics" ); + /* + const asset_object& bitusd = create_bitasset( "BITUSD" ); + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + + db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ + usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); + }); + + const account_object& shorter1 = create_account( "shorter1" ); + const account_object& shorter2 = create_account( "shorter2" ); + const account_object& buyer1 = create_account( "buyer1" ); + const account_object& buyer2 = create_account( "buyer2" ); + + transfer( genesis_account(db), shorter1, asset( 10000 ) ); + transfer( genesis_account(db), shorter2, asset( 10000 ) ); + transfer( genesis_account(db), buyer1, asset( 10000 ) ); + transfer( genesis_account(db), buyer2, asset( 10000 ) ); + + BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); + BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); + BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee + + // this should cause the highest bid to below the margin call threshold + // which means it should be filled by the cover + auto unmatched = create_sell_order( buyer1, bitusd.amount(990), core.amount(1100) ); + if( unmatched ) edump((*unmatched)); + BOOST_REQUIRE( unmatched ); + */ + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( margin_call_short_test ) +{ try { + FC_ASSERT( !"TODO - Reimplement with new short semantics" ); + /* + const asset_object& bitusd = create_bitasset( "BITUSD" ); + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + + db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ + usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); + }); + + const account_object& shorter1 = create_account( "shorter1" ); + const account_object& shorter2 = create_account( "shorter2" ); + const account_object& buyer1 = create_account( "buyer1" ); + const account_object& buyer2 = create_account( "buyer2" ); + + transfer( genesis_account(db), shorter1, asset( 10000 ) ); + transfer( genesis_account(db), shorter2, asset( 10000 ) ); + transfer( genesis_account(db), buyer1, asset( 10000 ) ); + transfer( genesis_account(db), buyer2, asset( 10000 ) ); + + BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); + BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); + BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee + ilog( "=================================== START===================================\n\n"); + + // this should cause the highest bid to below the margin call threshold + // which means it should be filled by the cover + auto unmatched = create_short( buyer1, bitusd.amount(990), core.amount(1500) ); + if( unmatched ) edump((*unmatched)); + BOOST_REQUIRE( !unmatched ); + */ + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( margin_call_short_test_limit_protected ) +{ try { + FC_ASSERT( !"TODO - Reimplement with new short semantics" ); + /* + const asset_object& bitusd = create_bitasset( "BITUSD" ); + const asset_object& core = get_asset( GRAPHENE_SYMBOL ); + + db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ + usd.current_feed.call_limit = core.amount(3) / bitusd.amount(4); + }); + + const account_object& shorter1 = create_account( "shorter1" ); + const account_object& shorter2 = create_account( "shorter2" ); + const account_object& buyer1 = create_account( "buyer1" ); + const account_object& buyer2 = create_account( "buyer2" ); + + transfer( genesis_account(db), shorter1, asset( 10000 ) ); + transfer( genesis_account(db), shorter2, asset( 10000 ) ); + transfer( genesis_account(db), buyer1, asset( 10000 ) ); + transfer( genesis_account(db), buyer2, asset( 10000 ) ); + + BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); + BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); + BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee + ilog( "=================================== START===================================\n\n"); + + // this should cause the highest bid to below the margin call threshold + // which means it should be filled by the cover + auto unmatched = create_short( buyer1, bitusd.amount(990), core.amount(1500) ); + if( unmatched ) edump((*unmatched)); + BOOST_REQUIRE( unmatched ); + */ + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + + + + + + + + + + + BOOST_AUTO_TEST_CASE( create_account_test ) { try { @@ -947,234 +1271,6 @@ BOOST_AUTO_TEST_CASE( trade_amount_equals_zero ) } } -BOOST_AUTO_TEST_CASE( margin_call_limit_test ) -{ try { - FC_ASSERT( !"TODO - Reimplement with new short semantics" ); - /* - const asset_object& bitusd = create_bitasset( "BITUSD" ); - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - - db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ - usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); - }); - - const account_object& shorter1 = create_account( "shorter1" ); - const account_object& shorter2 = create_account( "shorter2" ); - const account_object& buyer1 = create_account( "buyer1" ); - const account_object& buyer2 = create_account( "buyer2" ); - - transfer( genesis_account(db), shorter1, asset( 10000 ) ); - transfer( genesis_account(db), shorter2, asset( 10000 ) ); - transfer( genesis_account(db), buyer1, asset( 10000 ) ); - transfer( genesis_account(db), buyer2, asset( 10000 ) ); - - BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); - BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); - BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee - - const auto& call_index = db.get_index_type().indices().get(); - const auto call_itr = call_index.find(boost::make_tuple(shorter1.id, bitusd.id)); - BOOST_REQUIRE( call_itr != call_index.end() ); - const call_order_object& call = *call_itr; - BOOST_CHECK(call.get_collateral() == core.amount(2000)); - BOOST_CHECK(call.get_debt() == bitusd.amount(1000)); - BOOST_CHECK(call.call_price == price(core.amount(1500), bitusd.amount(1000))); - BOOST_CHECK_EQUAL(get_balance(shorter1, core), 9000); - - ilog( "=================================== START===================================\n\n"); - // this should cause the highest bid to below the margin call threshold - // which means it should be filled by the cover - auto unmatched = create_sell_order( buyer1, bitusd.amount(495), core.amount(750) ); - if( unmatched ) edump((*unmatched)); - BOOST_CHECK( !unmatched ); - BOOST_CHECK(call.get_debt() == bitusd.amount(505)); - BOOST_CHECK(call.get_collateral() == core.amount(1250)); - - auto below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1)); - BOOST_REQUIRE(below_call_price); - auto above_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(303)); - BOOST_REQUIRE(above_call_price); - auto above_id = above_call_price->id; - - cancel_limit_order(*below_call_price); - BOOST_CHECK_THROW(db.get_object(above_id), fc::exception); - BOOST_CHECK(call.get_debt() == bitusd.amount(305)); - BOOST_CHECK(call.get_collateral() == core.amount(947)); - - below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1)); - BOOST_REQUIRE(below_call_price); - auto below_id = below_call_price->id; - above_call_price = create_sell_order(buyer1, bitusd.amount(95), core.amount(144)); - BOOST_REQUIRE(above_call_price); - above_id = above_call_price->id; - auto match_below_call = create_sell_order(buyer2, core.amount(1), bitusd.amount(200)); - BOOST_CHECK(!match_below_call); - - BOOST_CHECK_THROW(db.get_object(above_id), fc::exception); - BOOST_CHECK_THROW(db.get_object(below_id), fc::exception); - BOOST_CHECK(call.get_debt() == bitusd.amount(210)); - BOOST_CHECK(call.get_collateral() == core.amount(803)); - */ - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( margin_call_limit_test_protected ) -{ try { - FC_ASSERT( !"TODO - Reimplement with new short semantics" ); - /* - const asset_object& bitusd = create_bitasset( "BITUSD" ); - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - - db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ - usd.current_feed.call_limit = core.amount(1) / bitusd.amount(1); - }); - - const account_object& shorter1 = create_account( "shorter1" ); - const account_object& shorter2 = create_account( "shorter2" ); - const account_object& buyer1 = create_account( "buyer1" ); - const account_object& buyer2 = create_account( "buyer2" ); - - transfer( genesis_account(db), shorter1, asset( 10000 ) ); - transfer( genesis_account(db), shorter2, asset( 10000 ) ); - transfer( genesis_account(db), buyer1, asset( 10000 ) ); - transfer( genesis_account(db), buyer2, asset( 10000 ) ); - - BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); - BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); - BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee - - ilog( "=================================== START===================================\n\n"); - // this should cause the highest bid to below the margin call threshold - // which means it should be filled by the cover - auto unmatched = create_sell_order( buyer1, bitusd.amount(990), core.amount(1500) ); - if( unmatched ) edump((*unmatched)); - BOOST_REQUIRE( unmatched ); - */ - - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( dont_margin_call_limit_test ) -{ try { - FC_ASSERT( !"TODO - Reimplement with new short semantics" ); - /* - const asset_object& bitusd = create_bitasset( "BITUSD" ); - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - - db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ - usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); - }); - - const account_object& shorter1 = create_account( "shorter1" ); - const account_object& shorter2 = create_account( "shorter2" ); - const account_object& buyer1 = create_account( "buyer1" ); - const account_object& buyer2 = create_account( "buyer2" ); - - transfer( genesis_account(db), shorter1, asset( 10000 ) ); - transfer( genesis_account(db), shorter2, asset( 10000 ) ); - transfer( genesis_account(db), buyer1, asset( 10000 ) ); - transfer( genesis_account(db), buyer2, asset( 10000 ) ); - - BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); - BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); - BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee - - // this should cause the highest bid to below the margin call threshold - // which means it should be filled by the cover - auto unmatched = create_sell_order( buyer1, bitusd.amount(990), core.amount(1100) ); - if( unmatched ) edump((*unmatched)); - BOOST_REQUIRE( unmatched ); - */ - - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( margin_call_short_test ) -{ try { - FC_ASSERT( !"TODO - Reimplement with new short semantics" ); - /* - const asset_object& bitusd = create_bitasset( "BITUSD" ); - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - - db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ - usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1); - }); - - const account_object& shorter1 = create_account( "shorter1" ); - const account_object& shorter2 = create_account( "shorter2" ); - const account_object& buyer1 = create_account( "buyer1" ); - const account_object& buyer2 = create_account( "buyer2" ); - - transfer( genesis_account(db), shorter1, asset( 10000 ) ); - transfer( genesis_account(db), shorter2, asset( 10000 ) ); - transfer( genesis_account(db), buyer1, asset( 10000 ) ); - transfer( genesis_account(db), buyer2, asset( 10000 ) ); - - BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); - BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); - BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee - ilog( "=================================== START===================================\n\n"); - - // this should cause the highest bid to below the margin call threshold - // which means it should be filled by the cover - auto unmatched = create_short( buyer1, bitusd.amount(990), core.amount(1500) ); - if( unmatched ) edump((*unmatched)); - BOOST_REQUIRE( !unmatched ); - */ - - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( margin_call_short_test_limit_protected ) -{ try { - FC_ASSERT( !"TODO - Reimplement with new short semantics" ); - /* - const asset_object& bitusd = create_bitasset( "BITUSD" ); - const asset_object& core = get_asset( GRAPHENE_SYMBOL ); - - db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){ - usd.current_feed.call_limit = core.amount(3) / bitusd.amount(4); - }); - - const account_object& shorter1 = create_account( "shorter1" ); - const account_object& shorter2 = create_account( "shorter2" ); - const account_object& buyer1 = create_account( "buyer1" ); - const account_object& buyer2 = create_account( "buyer2" ); - - transfer( genesis_account(db), shorter1, asset( 10000 ) ); - transfer( genesis_account(db), shorter2, asset( 10000 ) ); - transfer( genesis_account(db), buyer1, asset( 10000 ) ); - transfer( genesis_account(db), buyer2, asset( 10000 ) ); - - BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) ); - BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) ); - BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee - ilog( "=================================== START===================================\n\n"); - - // this should cause the highest bid to below the margin call threshold - // which means it should be filled by the cover - auto unmatched = create_short( buyer1, bitusd.amount(990), core.amount(1500) ); - if( unmatched ) edump((*unmatched)); - BOOST_REQUIRE( unmatched ); - */ - - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} /** * Create an order that cannot be filled immediately and have the