From c80c839675e31167832573d17d47062a33f2208f Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Fri, 19 Jun 2015 14:47:42 -0400 Subject: [PATCH] black swan test with force settle after --- libraries/chain/asset_evaluator.cpp | 6 +++ libraries/chain/db_market.cpp | 9 +++- tests/common/database_fixture.cpp | 19 ++++++++ tests/common/database_fixture.hpp | 1 + tests/tests/operation_tests.cpp | 68 +++++++++++++++++++++++------ 5 files changed, 87 insertions(+), 16 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index a19f91a4..b41eed9c 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -370,6 +370,12 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: d.adjust_balance(op.account, settled_amount); + const auto& mia_dyn = asset_to_settle->dynamic_asset_data_id(d); + + d.modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + obj.current_supply -= op.amount.amount; + }); + return settled_amount; } else diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 9a7cc0fb..9c551640 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -37,10 +37,11 @@ namespace graphene { namespace chain { */ void database::globally_settle_asset( const asset_object& mia, const price& settlement_price ) { try { + /* elog( "BLACK SWAN!" ); debug_dump(); - edump( (mia.symbol)(settlement_price) ); + */ const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" ); @@ -54,6 +55,7 @@ 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(); + // 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 ) ); @@ -72,10 +74,13 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett obj.settlement_fund = collateral_gathered.amount; }); - /// TODO: after all margin positions are closed, the current supply will be reported as 0, but + /// After all margin positions are closed, the current supply will be reported as 0, but /// that is a lie, the supply didn't change. We need to capture the current supply before /// filling all call orders and then restore it afterward. Then in the force settlement /// evaluator reduce the supply + modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){ + obj.current_supply = original_mia_supply; + }); } FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 44bb134d..f526be7f 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -148,6 +148,11 @@ void database_fixture::verify_asset_supplies( )const if( asset_obj.id != asset_id_type() ) BOOST_CHECK_EQUAL(total_balances[asset_obj.id].value, asset_obj.dynamic_asset_data_id(db).current_supply.value); total_balances[asset_id_type()] += asset_obj.dynamic_asset_data_id(db).fee_pool; + if( asset_obj.is_market_issued() ) + { + const auto& bad = asset_obj.bitasset_data(db); + total_balances[bad.options.short_backing_asset] += bad.settlement_fund; + } } for( const witness_object& witness_obj : db.get_index_type>() ) { @@ -655,6 +660,20 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obj trx.operations.clear(); } +void database_fixture::force_settle( const account_object& who, asset what ) +{ try { + trx.set_expiration(db.head_block_time() + fc::minutes(1)); + trx.operations.clear(); + asset_settle_operation sop; + sop.account = who.id; + sop.amount = what; + trx.operations.push_back(sop); + for( auto& op : trx.operations ) op.visit( operation_set_fee( db.current_fee_schedule() ) ); + trx.validate(); + db.push_transaction(trx, ~0); + trx.operations.clear(); +} FC_CAPTURE_AND_RETHROW( (who)(what) ) } + void database_fixture::borrow( const account_object& who, asset what, asset collateral, price call_price ) { try { asset call_price_collateral((collateral.amount.value * 3)/4, collateral.asset_id ); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index bfa45805..5a53608a 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -147,6 +147,7 @@ struct database_fixture { key_id_type key = key_id_type() ); + void force_settle( const account_object& who, asset what ); void update_feed_producers( const asset_object& mia, flat_set producers ); void publish_feed( const asset_object& mia, const account_object& by, const price_feed& f ); void borrow( const account_object& who, asset what, asset collateral, price call_price = price()); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 8196c263..590884fd 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -206,6 +206,60 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test ) } } +/** + * This test sets up the minimum condition for a black swan to occur but does + * not test the full range of cases that may be possible during a black swan. + */ +BOOST_AUTO_TEST_CASE( black_swan ) +{ 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(1000), asset(2000), default_call_price ); + borrow( borrower2, bitusd.amount(1000), asset(4000), default_call_price ); + + BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( borrower2, bitusd ), 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( borrower , core ), init_balance - 2000 ); + BOOST_REQUIRE_EQUAL( get_balance( borrower2, core ), init_balance - 4000 ); + + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(200); + publish_feed( bitusd, feedproducer, current_feed ); + + /// this sell order is designed to trigger a black swan + auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(3000) ); + + FC_ASSERT( bitusd.bitasset_data(db).has_settlement() ); + wdump(( bitusd.bitasset_data(db) )); + + force_settle( borrower, bitusd.amount(100) ); + + BOOST_TEST_MESSAGE( "Verify that we cannot borrow after black swan" ); + BOOST_REQUIRE_THROW( borrow( borrower, bitusd.amount(1000), asset(2000), default_call_price ), fc::exception ); + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + BOOST_AUTO_TEST_CASE( create_account_test ) { @@ -1248,20 +1302,6 @@ BOOST_AUTO_TEST_CASE( unimp_bulk_discount_test ) //const account_object& shorter2 = create_account( "bob" ); BOOST_FAIL( "not implemented" ); } - -/** - * This test sets up the minimum condition for a black swan to occur but does - * not test the full range of cases that may be possible during a black swan. - */ -BOOST_AUTO_TEST_CASE( margin_call_black_swan ) -{ try { - FC_ASSERT( "TODO - Reimplement with new short semantics" ); - } catch( const fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - /** * Assume the referrer gets 99% of transaction fee */