Skip to content

Commit

Permalink
Merge pull request #838 from bitshares/bsip38-call-target-cr
Browse files Browse the repository at this point in the history
BSIP38 add target_cr option to call order
  • Loading branch information
abitmore authored May 2, 2018
2 parents 55d245d + 610dbf5 commit 14c9786
Show file tree
Hide file tree
Showing 19 changed files with 1,286 additions and 43 deletions.
1 change: 1 addition & 0 deletions libraries/chain/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ add_library( graphene_chain
account_object.cpp
asset_object.cpp
fba_object.cpp
market_object.cpp
proposal_object.cpp
vesting_balance_object.cpp

Expand Down
56 changes: 40 additions & 16 deletions libraries/chain/db_market.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -464,16 +464,23 @@ bool database::apply_order(const limit_order_object& new_order_object, bool allo
// check if there are margin calls
const auto& call_price_idx = get_index_type<call_order_index>().indices().get<by_price>();
auto call_min = price::min( recv_asset_id, sell_asset_id );
auto call_itr = call_price_idx.lower_bound( call_min );
// feed protected https://github.com/cryptonomex/graphene/issues/436
auto call_end = call_price_idx.upper_bound( ~sell_abd->current_feed.settlement_price );
while( !finished && call_itr != call_end )
while( !finished )
{
auto old_call_itr = call_itr;
++call_itr; // would be safe, since we'll end the loop if a call order is partially matched
// match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop.
// assume hard fork core-343 and core-625 will take place at same time, always check call order with least call_price
auto call_itr = call_price_idx.lower_bound( call_min );
if( call_itr == call_price_idx.end()
|| call_itr->debt_type() != sell_asset_id
// feed protected https://github.com/cryptonomex/graphene/issues/436
|| call_itr->call_price > ~sell_abd->current_feed.settlement_price )
break;
// assume hard fork core-338 and core-625 will take place at same time, not checking HARDFORK_CORE_338_TIME here.
finished = ( match( new_order_object, *old_call_itr, call_match_price ) != 2 );
int match_result = match( new_order_object, *call_itr, call_match_price,
sell_abd->current_feed.settlement_price,
sell_abd->current_feed.maintenance_collateral_ratio );
// match returns 1 or 3 when the new order was fully filled. In this case, we stop matching; otherwise keep matching.
// since match can return 0 due to BSIP38 (hard fork core-834), we no longer only check if the result is 2.
if( match_result == 1 || match_result == 3 )
finished = true;
}
}
}
Expand Down Expand Up @@ -574,7 +581,8 @@ int database::match( const limit_order_object& usd, const limit_order_object& co
return result;
}

int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price )
int database::match( const limit_order_object& bid, const call_order_object& ask, const price& match_price,
const price& feed_price, const uint16_t maintenance_collateral_ratio )
{
FC_ASSERT( bid.sell_asset_id() == ask.debt_type() );
FC_ASSERT( bid.receive_asset_id() == ask.collateral_type() );
Expand All @@ -583,16 +591,25 @@ 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
// TODO remove when we're sure it's always false
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
// TODO remove when we're sure it's always false
bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option
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()) );
if( before_core_hardfork_834 )
ilog( "match(limit,call) is called before hardfork core-834 at block #${block}", ("block",head_block_num()) );

bool cull_taker = false;

asset usd_for_sale = bid.amount_for_sale();
asset usd_to_buy = ask.get_debt();
// TODO if we're sure `before_core_hardfork_834` is always false, remove the check
asset usd_to_buy = ( before_core_hardfork_834 ?
ask.get_debt() :
asset( ask.get_max_debt_to_cover( match_price, feed_price, maintenance_collateral_ratio ),
ask.debt_type() ) );

asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy > usd_for_sale )
Expand Down Expand Up @@ -637,7 +654,8 @@ int database::match( const limit_order_object& bid, const call_order_object& ask
int result = 0;
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 );
// result can be 0 when call order has target_collateral_ratio option set.

return result;
}

Expand Down Expand Up @@ -834,14 +852,14 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
});

const account_object& borrower = order.borrower(*this);
if( collateral_freed || pays.asset_id == asset_id_type() )
if( collateral_freed.valid() || pays.asset_id == asset_id_type() )
{
const account_statistics_object& borrower_statistics = borrower.statistics(*this);
if( collateral_freed )
if( collateral_freed.valid() )
adjust_balance(borrower.get_id(), *collateral_freed);

modify( borrower_statistics, [&]( account_statistics_object& b ){
if( collateral_freed && collateral_freed->amount > 0 && collateral_freed->asset_id == asset_id_type())
if( collateral_freed.valid() && collateral_freed->amount > 0 && collateral_freed->asset_id == asset_id_type() )
b.total_core_in_orders -= collateral_freed->amount;
if( pays.asset_id == asset_id_type() )
b.total_core_in_orders -= pays.amount;
Expand All @@ -854,7 +872,7 @@ bool database::fill_call_order( const call_order_object& order, const asset& pay
push_applied_operation( fill_order_operation( order.id, order.borrower, pays, receives,
asset(0, pays.asset_id), fill_price, is_maker ) );

if( collateral_freed )
if( collateral_freed.valid() )
remove( order );

return collateral_freed.valid();
Expand Down Expand Up @@ -950,6 +968,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
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
bool before_core_hardfork_834 = ( maint_time <= HARDFORK_CORE_834_TIME ); // target collateral ratio option

while( !check_for_blackswan( mia, enable_black_swan ) && call_itr != call_end )
{
Expand Down Expand Up @@ -988,6 +1007,11 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
return true;
}

if( !before_core_hardfork_834 )
usd_to_buy.amount = call_itr->get_max_debt_to_cover( match_price,
bitasset.current_feed.settlement_price,
bitasset.current_feed.maintenance_collateral_ratio );

asset call_pays, call_receives, order_pays, order_receives;
if( usd_to_buy > usd_for_sale )
{ // fill order
Expand Down Expand Up @@ -1033,7 +1057,7 @@ bool database::check_call_orders(const asset_object& mia, bool enable_black_swan
else
order_receives = usd_to_buy.multiply_and_round_up( match_price ); // round up, in favor of limit order

filled_call = true;
filled_call = true; // this is safe, since BSIP38 (hard fork core-834) depends on BSIP31 (hard fork core-343)

if( usd_to_buy == usd_for_sale )
filled_limit = true;
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/hardfork.d/CORE_834.hf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// bitshares-core issue #834 "BSIP38: add target CR option to short positions"
#ifndef HARDFORK_CORE_834_TIME
#define HARDFORK_CORE_834_TIME (fc::time_point_sec( 1600000000 ))
#endif
3 changes: 2 additions & 1 deletion libraries/chain/include/graphene/chain/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ namespace graphene { namespace chain {
*/
///@{
int match( const limit_order_object& taker, const limit_order_object& maker, const price& trade_price );
int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price );
int match( const limit_order_object& taker, const call_order_object& maker, const price& trade_price,
const price& feed_price, const uint16_t maintenance_collateral_ratio );
/// @return the amount of asset settled
asset match(const call_order_object& call,
const force_settlement_object& settle,
Expand Down
7 changes: 6 additions & 1 deletion libraries/chain/include/graphene/chain/market_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,17 @@ class call_order_object : public abstract_object<call_order_object>
share_type debt; ///< call_price.quote.asset_id, access via get_debt
price call_price; ///< Collateral / Debt

optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call

pair<asset_id_type,asset_id_type> get_market()const
{
auto tmp = std::make_pair( call_price.base.asset_id, call_price.quote.asset_id );
if( tmp.first > tmp.second ) std::swap( tmp.first, tmp.second );
return tmp;
}

/// Calculate maximum quantity of debt to cover to satisfy @ref target_collateral_ratio.
share_type get_max_debt_to_cover( price match_price, price feed_price, const uint16_t maintenance_collateral_ratio )const;
};

/**
Expand Down Expand Up @@ -259,7 +264,7 @@ FC_REFLECT_DERIVED( graphene::chain::limit_order_object,
)

FC_REFLECT_DERIVED( graphene::chain::call_order_object, (graphene::db::object),
(borrower)(collateral)(debt)(call_price) )
(borrower)(collateral)(debt)(call_price)(target_collateral_ratio) )

FC_REFLECT_DERIVED( graphene::chain::force_settlement_object,
(graphene::db::object),
Expand Down
19 changes: 17 additions & 2 deletions libraries/chain/include/graphene/chain/protocol/market.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/ext.hpp>

namespace graphene { namespace chain {

Expand Down Expand Up @@ -94,8 +95,6 @@ namespace graphene { namespace chain {
void validate()const;
};



/**
* @ingroup operations
*
Expand All @@ -110,13 +109,25 @@ namespace graphene { namespace chain {
*/
struct call_order_update_operation : public base_operation
{
/**
* Options to be used in @ref call_order_update_operation.
*
* @note this struct can be expanded by adding more options in the end.
*/
struct options_type
{
optional<uint16_t> target_collateral_ratio; ///< maximum CR to maintain when selling collateral on margin call
};

/** this is slightly more expensive than limit orders, this pricing impacts prediction markets */
struct fee_parameters_type { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; };

asset fee;
account_id_type funding_account; ///< pays fee, collateral, and cover
asset delta_collateral; ///< the amount of collateral to add to the margin position
asset delta_debt; ///< the amount of the debt to be paid off, may be negative to issue new debt

typedef extension<options_type> extensions_type; // note: this will be jsonified to {...} but no longer [...]
extensions_type extensions;

account_id_type fee_payer()const { return funding_account; }
Expand Down Expand Up @@ -214,6 +225,10 @@ FC_REFLECT( graphene::chain::bid_collateral_operation::fee_parameters_type, (fee
FC_REFLECT( graphene::chain::fill_order_operation::fee_parameters_type, ) // VIRTUAL
FC_REFLECT( graphene::chain::execute_bid_operation::fee_parameters_type, ) // VIRTUAL

FC_REFLECT( graphene::chain::call_order_update_operation::options_type, (target_collateral_ratio) )

FC_REFLECT_TYPENAME( graphene::chain::call_order_update_operation::extensions_type )

FC_REFLECT( graphene::chain::limit_order_create_operation,(fee)(seller)(amount_to_sell)(min_to_receive)(expiration)(fill_or_kill)(extensions))
FC_REFLECT( graphene::chain::limit_order_cancel_operation,(fee)(fee_paying_account)(order)(extensions) )
FC_REFLECT( graphene::chain::call_order_update_operation, (fee)(funding_account)(delta_collateral)(delta_debt)(extensions) )
Expand Down
24 changes: 16 additions & 8 deletions libraries/chain/market_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,11 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope
{ try {
database& d = db();

// TODO: remove this check and the assertion after hf_834
if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_834_TIME )
FC_ASSERT( !o.extensions.value.target_collateral_ratio.valid(),
"Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." );

_paying_account = &o.funding_account(d);
_debt_asset = &o.delta_debt.asset_id(d);
FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.",
Expand Down Expand Up @@ -222,6 +227,8 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
optional<price> old_collateralization;
optional<share_type> old_debt;

optional<uint16_t> new_target_cr = o.extensions.value.target_collateral_ratio;

if( itr == call_idx.end() )
{
FC_ASSERT( o.delta_collateral.amount > 0 );
Expand All @@ -233,7 +240,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
call.debt = o.delta_debt.amount;
call.call_price = price::call_price(o.delta_debt, o.delta_collateral,
_bitasset_data->current_feed.maintenance_collateral_ratio);

call.target_collateral_ratio = new_target_cr;
});
}
else
Expand All @@ -243,13 +250,14 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
old_debt = call_obj->debt;

d.modify( *call_obj, [&]( call_order_object& call ){
call.collateral += o.delta_collateral.amount;
call.debt += o.delta_debt.amount;
if( call.debt > 0 )
{
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
_bitasset_data->current_feed.maintenance_collateral_ratio);
}
call.collateral += o.delta_collateral.amount;
call.debt += o.delta_debt.amount;
if( call.debt > 0 )
{
call.call_price = price::call_price(call.get_debt(), call.get_collateral(),
_bitasset_data->current_feed.maintenance_collateral_ratio);
}
call.target_collateral_ratio = new_target_cr;
});
}

Expand Down
Loading

0 comments on commit 14c9786

Please sign in to comment.