From 14b158364fb49ed5db15c026bc22cd4092e57b3b Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Fri, 19 Jun 2015 15:57:08 -0400 Subject: [PATCH] implement and test prediction market features --- libraries/chain/asset_evaluator.cpp | 2 + libraries/chain/call_order_evaluator.cpp | 55 +++++++++++++----------- libraries/chain/db_market.cpp | 3 +- tests/common/database_fixture.cpp | 41 ++++++++++++++++++ tests/common/database_fixture.hpp | 5 +++ tests/tests/operation_tests.cpp | 42 ++++++++++++++++++ 6 files changed, 121 insertions(+), 27 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b41eed9c..2fa3566d 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -348,6 +348,8 @@ object_id_type asset_settle_evaluator::do_evaluate(const asset_settle_evaluator: FC_ASSERT(asset_to_settle->is_market_issued()); const auto& bitasset = asset_to_settle->bitasset_data(d); FC_ASSERT(asset_to_settle->can_force_settle() || bitasset.has_settlement() ); + if( bitasset.is_prediction_market ) + FC_ASSERT( bitasset.has_settlement(), "global settlement must occur before force settling a prediction market" ); FC_ASSERT(d.get_balance(d.get(op.account), *asset_to_settle) >= op.amount); return d.get_index_type().get_next_id(); diff --git a/libraries/chain/call_order_evaluator.cpp b/libraries/chain/call_order_evaluator.cpp index 8fda9f2b..ec6e6638 100644 --- a/libraries/chain/call_order_evaluator.cpp +++ b/libraries/chain/call_order_evaluator.cpp @@ -65,7 +65,7 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope void_result call_order_update_evaluator::do_apply(const call_order_update_operation& o) -{ +{ try { database& d = db(); //wdump( (_bitasset_data->current_feed) ); @@ -97,6 +97,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat auto& call_idx = d.get_index_type().indices().get(); auto itr = call_idx.find( boost::make_tuple(o.funding_account, o.delta_debt.asset_id) ); const call_order_object* call_obj = nullptr; + if( itr == call_idx.end() ) { FC_ASSERT( o.delta_collateral.amount > 0 ); @@ -127,34 +128,38 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat d.remove( *call_obj ); return void_result(); } - auto collateral = call_obj->get_collateral(); - auto mp = _bitasset_data->current_feed.maintenance_price(); - -// edump((debt)(collateral)((debt/collateral).to_real())(mp.to_real()) ); - // edump((debt*mp)); - /// paying off the debt at the user specified call price should require - /// less collateral than paying off the debt at the maitenance price - auto col_at_call_price = debt * o.call_price; - auto col_at_min_callprice = debt * mp; - FC_ASSERT( col_at_call_price <= col_at_min_callprice, "", ("debt*o.callprice",debt*o.call_price)("debt*mp",debt*mp) ); - FC_ASSERT( col_at_call_price <= collateral ); - - //wdump( (o.call_price)(mp)(call_obj->call_price.to_real())(mp.to_real()) ); - //FC_ASSERT( call_obj->call_price <= mp ); - - auto call_order_id = call_obj->id; - - //ilog( "checking call orders" ); - - // check to see if the order needs to be margin called now, but don't allow black swans and require there to be - // limit orders available that could be used to fill the order. - if( d.check_call_orders( *_debt_asset, false ) ) + /** then we must check for margin calls and other issues */ + if( !_bitasset_data->is_prediction_market ) { - FC_ASSERT( !d.find_object( call_order_id ), "If updating the call order triggers a margin call, then it must completely cover the order" ); + auto collateral = call_obj->get_collateral(); + auto mp = _bitasset_data->current_feed.maintenance_price(); + + // edump((debt)(collateral)((debt/collateral).to_real())(mp.to_real()) ); + // edump((debt*mp)); + /// paying off the debt at the user specified call price should require + /// less collateral than paying off the debt at the maitenance price + auto col_at_call_price = debt * o.call_price; + auto col_at_min_callprice = debt * mp; + FC_ASSERT( col_at_call_price <= col_at_min_callprice, "", ("debt*o.callprice",debt*o.call_price)("debt*mp",debt*mp) ); + FC_ASSERT( col_at_call_price <= collateral ); + + //wdump( (o.call_price)(mp)(call_obj->call_price.to_real())(mp.to_real()) ); + //FC_ASSERT( call_obj->call_price <= mp ); + + auto call_order_id = call_obj->id; + + //ilog( "checking call orders" ); + + // check to see if the order needs to be margin called now, but don't allow black swans and require there to be + // limit orders available that could be used to fill the order. + if( d.check_call_orders( *_debt_asset, false ) ) + { + FC_ASSERT( !d.find_object( call_order_id ), "If updating the call order triggers a margin call, then it must completely cover the order" ); + } } return void_result(); -} +} FC_CAPTURE_AND_RETHROW( (o) ) } } } // graphene::chain diff --git a/libraries/chain/db_market.cpp b/libraries/chain/db_market.cpp index 9c551640..c90c737d 100644 --- a/libraries/chain/db_market.cpp +++ b/libraries/chain/db_market.cpp @@ -62,7 +62,6 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett while( call_itr != call_end ) { auto pays = call_itr->get_debt() * settlement_price; - wdump( (call_itr->get_debt() ) ); collateral_gathered += pays; const auto& order = *call_itr; ++call_itr; @@ -321,8 +320,8 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa { try { if( !mia.is_market_issued() ) return false; const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this); - if( bitasset.current_feed.settlement_price.is_null() ) return false; if( bitasset.is_prediction_market ) return false; + if( bitasset.current_feed.settlement_price.is_null() ) return false; const call_order_index& call_index = get_index_type(); const auto& call_price_index = call_index.indices().get(); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index a194b6f0..e4912fa0 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -398,6 +398,32 @@ const asset_object& database_fixture::create_bitasset( return db.get(ptx.operation_results[0].get()); } FC_CAPTURE_AND_RETHROW( (name)(flags) ) } +const asset_object& database_fixture::create_prediction_market( + const string& name, + account_id_type issuer /* = 1 */, + uint16_t market_fee_percent /* = 100 */ /* 1% */, + uint16_t flags /* = charge_market_fee */ + ) +{ try { + asset_create_operation creator; + creator.issuer = issuer; + creator.fee = asset(); + creator.symbol = name; + creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + creator.precision = 2; + creator.common_options.market_fee_percent = market_fee_percent; + creator.common_options.issuer_permissions = flags | global_settle; + creator.common_options.flags = flags & ~global_settle; + creator.common_options.core_exchange_rate = price({asset(1,1),asset(1)}); + creator.bitasset_options = asset_object::bitasset_options(); + creator.is_prediction_market = true; + trx.operations.push_back(std::move(creator)); + trx.validate(); + processed_transaction ptx = db.push_transaction(trx, ~0); + trx.operations.clear(); + return db.get(ptx.operation_results[0].get()); +} FC_CAPTURE_AND_RETHROW( (name)(flags) ) } + const asset_object& database_fixture::create_user_issued_asset( const string& name ) { asset_create_operation creator; @@ -661,6 +687,21 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obj trx.operations.clear(); } +void database_fixture::force_global_settle( const asset_object& what, const price& p ) +{ try { + trx.set_expiration(db.head_block_time() + fc::minutes(1)); + trx.operations.clear(); + asset_global_settle_operation sop; + sop.issuer = what.issuer; + sop.asset_to_settle = what.id; + sop.settle_price = p; + 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( (what)(p) ) } + void database_fixture::force_settle( const account_object& who, asset what ) { try { trx.set_expiration(db.head_block_time() + fc::minutes(1)); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 5a53608a..9f308fec 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_global_settle( const asset_object& what, const price& p ); 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 ); @@ -159,6 +160,10 @@ struct database_fixture { account_id_type issuer = account_id_type(1), uint16_t market_fee_percent = 100 /*1%*/, uint16_t flags = charge_market_fee); + const asset_object& create_prediction_market(const string& name, + account_id_type issuer = account_id_type(1), + uint16_t market_fee_percent = 100 /*1%*/, + uint16_t flags = charge_market_fee); const asset_object& create_user_issued_asset( const string& name ); void issue_uia( const account_object& recipient, asset amount ); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 041db4fc..0f04fb57 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -259,6 +259,48 @@ BOOST_AUTO_TEST_CASE( black_swan ) } } +BOOST_AUTO_TEST_CASE( prediction_market ) +{ try { + ACTORS((judge)(dan)(nathan)); + + const auto& pmark = create_prediction_market("PMARK", judge_id); + const auto& core = asset_id_type()(db); + + int64_t init_balance(1000000); + transfer(genesis_account, judge_id, asset(init_balance)); + transfer(genesis_account, dan_id, asset(init_balance)); + transfer(genesis_account, nathan_id, asset(init_balance)); + + auto default_call_price = ~price::call_price( pmark.amount(100), asset(100), 1750); + + BOOST_TEST_MESSAGE( "Require throw for mismatch collateral amounts" ); + BOOST_REQUIRE_THROW( borrow( dan, pmark.amount(1000), asset(2000), default_call_price ), fc::exception ); + + BOOST_TEST_MESSAGE( "Open position with equal collateral" ); + borrow( dan, pmark.amount(1000), asset(1000), default_call_price ); + + BOOST_TEST_MESSAGE( "Cover position with unequal asset should fail." ); + BOOST_REQUIRE_THROW( cover( dan, pmark.amount(500), asset(1000), default_call_price ), fc::exception ); + + BOOST_TEST_MESSAGE( "Cover half of position with equal ammounts" ); + cover( dan, pmark.amount(500), asset(500), default_call_price ); + + BOOST_TEST_MESSAGE( "Verify that forced settlment fails before global settlement" ); + BOOST_REQUIRE_THROW( force_settle( dan, pmark.amount(100) ), fc::exception ); + + BOOST_TEST_MESSAGE( "Shouldn't be allowed to force settle at more than 1 collateral per debt" ); + BOOST_REQUIRE_THROW( force_global_settle( pmark, pmark.amount(100) / core.amount(105) ), fc::exception ); + + force_global_settle( pmark, pmark.amount(100) / core.amount(95) ); + + BOOST_TEST_MESSAGE( "Verify that forced settlment succeedes after global settlement" ); + force_settle( dan, pmark.amount(100) ); + + } catch( const fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} BOOST_AUTO_TEST_CASE( create_account_test )