diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index eef79aa977..e19eec7b8f 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -1003,6 +1003,14 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa if( !mia.is_market_issued() ) return false; const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) ); + + // price feeds can cause black swans in prediction markets + // The hardfork check may be able to be removed after the hardfork date + // if check_for_blackswan never triggered a black swan on a prediction market. + // NOTE: check_for_blackswan returning true does not always mean a black + // swan was triggered. + if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market ) + return false; if( check_for_blackswan( mia, enable_black_swan, &bitasset ) ) return false; diff --git a/libraries/chain/hardfork.d/CORE_460.hf b/libraries/chain/hardfork.d/CORE_460.hf new file mode 100644 index 0000000000..8f0bd3f6fa --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_460.hf @@ -0,0 +1,4 @@ +// bitshares-core issue #460 Prediction Market price feed should not cause black swan +#ifndef HARDFORK_CORE_460_TIME +#define HARDFORK_CORE_460_TIME (fc::time_point_sec( 1609372800 ) ) // 2020-12-31T00:00:00 +#endif diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index f5deed6ddd..fc3f1f615c 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -840,6 +840,68 @@ BOOST_AUTO_TEST_CASE( prediction_market_resolves_to_0 ) } } +/*** + * Prediction markets should not suffer a black swan (Issue #460) + */ +BOOST_AUTO_TEST_CASE( prediction_market_black_swan ) +{ + try { + ACTORS((judge)(dan)(nathan)); + + // progress to recent hardfork + generate_blocks( HARDFORK_CORE_1270_TIME ); + set_expiration( db, trx ); + + const auto& pmark = create_prediction_market("PMARK", judge_id); + + int64_t init_balance(1000000); + transfer(committee_account, judge_id, asset(init_balance)); + transfer(committee_account, dan_id, asset(init_balance)); + + update_feed_producers( pmark, { judge_id }); + price_feed feed; + feed.settlement_price = asset( 1, pmark.id ) / asset( 1 ); + publish_feed( pmark, judge, feed ); + + borrow( dan, pmark.amount(1000), asset(1000) ); + + // feed a price that will cause a black swan + feed.settlement_price = asset( 1, pmark.id ) / asset( 1000 ); + publish_feed( pmark, judge, feed ); + + // verify a black swan happened + GRAPHENE_REQUIRE_THROW(borrow( dan, pmark.amount(1000), asset(1000) ), fc::exception); + trx.clear(); + + // progress past hardfork + generate_blocks( HARDFORK_CORE_460_TIME + db.get_global_properties().parameters.maintenance_interval ); + set_expiration( db, trx ); + + // create another prediction market to test the hardfork + const auto& pmark2 = create_prediction_market("PMARKII", judge_id); + update_feed_producers( pmark2, { judge_id }); + price_feed feed2; + feed2.settlement_price = asset( 1, pmark2.id ) / asset( 1 ); + publish_feed( pmark2, judge, feed2 ); + + borrow( dan, pmark2.amount(1000), asset(1000) ); + + // feed a price that would have caused a black swan + feed2.settlement_price = asset( 1, pmark2.id ) / asset( 1000 ); + publish_feed( pmark2, judge, feed2 ); + + // verify a black swan did not happen + borrow( dan, pmark2.amount(1000), asset(1000) ); + + generate_block(~database::skip_transaction_dupe_check); + generate_blocks( db.get_dynamic_global_properties().next_maintenance_time ); + generate_block(); + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( create_account_test ) { try {