Skip to content

Commit

Permalink
Merge pull request #2157 from bitshares/pr-bsip77-icr
Browse files Browse the repository at this point in the history
Implement BSIP 77: Initial collateral ratio (ICR)
  • Loading branch information
abitmore committed May 5, 2020
2 parents ecc5cbb + f5d4e44 commit 319fc3b
Show file tree
Hide file tree
Showing 14 changed files with 594 additions and 88 deletions.
32 changes: 28 additions & 4 deletions libraries/chain/asset_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,16 @@ namespace detail {
}
}

// TODO review and remove code below and links to it after HARDFORK_BSIP_77_TIME
void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options)
{
if ( !HARDFORK_BSIP_77_PASSED( block_time ) ) {
// ICR should not be set until activation of BSIP77
FC_ASSERT(!options.extensions.value.initial_collateral_ratio.valid(),
"Initial collateral ratio should not be defined before HARDFORK_BSIP_77_TIME");
}
}

void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op)
{
// HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes:
Expand All @@ -70,12 +80,14 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o
{ try {

const database& d = db();
// Define now from the current block time
const time_point_sec now = d.head_block_time();

const auto& chain_parameters = d.get_global_properties().parameters;
FC_ASSERT( op.common_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );
FC_ASSERT( op.common_options.blacklist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities );

detail::check_asset_options_hf_1774(d.head_block_time(), op.common_options);
detail::check_asset_options_hf_1774( now, op.common_options );

// Check that all authorities do exist
for( auto id : op.common_options.whitelist_authorities )
Expand All @@ -87,8 +99,6 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o
auto asset_symbol_itr = asset_indx.find( op.symbol );
FC_ASSERT( asset_symbol_itr == asset_indx.end() );

// Define now from the current block time
const time_point_sec now = d.head_block_time();
// This must remain due to "BOND.CNY" being allowed before this HF
if( now > HARDFORK_385_TIME )
{
Expand All @@ -107,6 +117,7 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o

if( op.bitasset_opts )
{
detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts );
const asset_object& backing = op.bitasset_opts->short_backing_asset(d);
if( backing.is_market_issued() )
{
Expand Down Expand Up @@ -306,7 +317,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)
validate_new_issuer( d, a, *o.new_issuer );
}

detail::check_asset_options_hf_1774(d.head_block_time(), o.new_options);
detail::check_asset_options_hf_1774( now, o.new_options );

if( a.dynamic_asset_data_id(d).current_supply != 0 )
{
Expand Down Expand Up @@ -445,6 +456,8 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita
{ try {
database& d = db();

detail::check_bitasset_options_hf_bsip77( d.head_block_time(), op.new_options );

const asset_object& asset_obj = op.asset_to_update(d);

FC_ASSERT( asset_obj.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset." );
Expand Down Expand Up @@ -580,6 +593,12 @@ static bool update_bitasset_object_options(
is_witness_or_committee_fed = true;
}

// check if ICR will change
const auto& old_icr = bdo.options.extensions.value.initial_collateral_ratio;
const auto& new_icr = op.new_options.extensions.value.initial_collateral_ratio;
bool icr_changed = ( ( old_icr.valid() != new_icr.valid() )
|| ( old_icr.valid() && *old_icr != *new_icr ) );

bdo.options = op.new_options;

// are we modifying the underlying? If so, reset the feeds
Expand Down Expand Up @@ -609,6 +628,11 @@ static bool update_bitasset_object_options(
// We need to call check_call_orders if the settlement price changes after hardfork core-868-890
return ( after_hf_core_868_890 && ! (old_feed == bdo.current_feed) );
}
else if( icr_changed ) // feeds not updated, but ICR changed
{
// update data derived from ICR
bdo.refresh_current_initial_collateralization();
}

return false;
}
Expand Down
30 changes: 29 additions & 1 deletion libraries/chain/asset_object.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,12 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
current_feed_publication_time = current_time;
current_feed = price_feed();
if( after_core_hardfork_1270 )
{
// update data derived from MCR
current_maintenance_collateralization = price();
// update data derived from ICR
current_initial_collateralization = price();
}
return;
}
if( current_feeds.size() == 1 )
Expand All @@ -79,7 +84,12 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
current_feed = current_feeds.front();
// Note: perhaps can defer updating current_maintenance_collateralization for better performance
if( after_core_hardfork_1270 )
{
// update data derived from MCR
current_maintenance_collateralization = current_feed.maintenance_collateralization();
// update data derived from ICR
refresh_current_initial_collateralization();
}
return;
}

Expand All @@ -102,10 +112,27 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin
current_feed = median_feed;
// Note: perhaps can defer updating current_maintenance_collateralization for better performance
if( after_core_hardfork_1270 )
{
// update data derived from MCR
current_maintenance_collateralization = current_feed.maintenance_collateralization();
// update data derived from ICR
refresh_current_initial_collateralization();
}
}


void asset_bitasset_data_object::refresh_current_initial_collateralization()
{
if( current_feed.settlement_price.is_null() )
current_initial_collateralization = price();
else
{
const auto& icr = options.extensions.value.initial_collateral_ratio;
if( icr.valid() && *icr > current_feed.maintenance_collateral_ratio ) // if ICR is set and is above MCR
current_initial_collateralization = current_feed.calculate_initial_collateralization( *icr );
else // if ICR is not set, or not above MCR
current_initial_collateralization = current_maintenance_collateralization;
}
}

asset asset_object::amount_from_string(string amount_string) const
{ try {
Expand Down Expand Up @@ -186,6 +213,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (gr
(current_feed)
(current_feed_publication_time)
(current_maintenance_collateralization)
(current_initial_collateralization)
(options)
(force_settled_volume)
(is_prediction_market)
Expand Down
6 changes: 6 additions & 0 deletions libraries/chain/hardfork.d/BSIP_77.hf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// BSIP 77 ("Initial Collateral Ratio" (ICR)) hardfork check
#ifndef HARDFORK_BSIP_77_TIME
// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled
#define HARDFORK_BSIP_77_TIME (fc::time_point_sec( 1893456000 ))
#define HARDFORK_BSIP_77_PASSED(now) (now >= HARDFORK_BSIP_77_TIME)
#endif
9 changes: 9 additions & 0 deletions libraries/chain/include/graphene/chain/asset_object.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,15 @@ namespace graphene { namespace chain {
/// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory.
/// This value is derived from @ref current_feed for better performance and should be kept consistent.
price current_maintenance_collateralization;
/// After BSIP77, when creating a new debt position or updating an existing position, the position
/// will be checked against the `initial_collateral_ratio` (ICR) parameter in the bitasset options.
/// This value is derived from @ref current_feed and `ICR` for better performance and should be kept
/// consistent.
price current_initial_collateralization;

/// Derive @ref current_initial_collateralization from other member variables.
/// Note: this assumes @ref current_maintenance_collateralization is fresh.
void refresh_current_initial_collateralization();

/// True if this asset implements a @ref prediction_market
bool is_prediction_market = false;
Expand Down
13 changes: 10 additions & 3 deletions libraries/chain/market_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -354,17 +354,24 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope
("a", ~call_obj->call_price )("b", _bitasset_data->current_feed.settlement_price)
);
}
else // after hard fork, always allow call order to be updated if collateral ratio is increased and debt is not increased
else // after hard fork core-583, always allow call order to be updated if collateral ratio
// is increased and debt is not increased
{
// We didn't fill any call orders. This may be because we
// aren't in margin call territory, or it may be because there
// were no matching orders. In the latter case,
// if collateral ratio is not increased or debt is increased, we throw.
// be here, we know no margin call was executed,
// so call_obj's collateral ratio should be set only by op
// ------
// Before BSIP77, CR of the new/updated position is required to be above MCR;
// after BSIP77, CR of the new/updated position is required to be above max(ICR,MCR).
// The `current_initial_collateralization` variable has been initialized according to the logic,
// so we directly use it here.
FC_ASSERT( ( !before_core_hardfork_1270
&& call_obj->collateralization() > _bitasset_data->current_maintenance_collateralization )
|| ( before_core_hardfork_1270 && ~call_obj->call_price < _bitasset_data->current_feed.settlement_price )
&& call_obj->collateralization() > _bitasset_data->current_initial_collateralization )
|| ( before_core_hardfork_1270
&& ~call_obj->call_price < _bitasset_data->current_feed.settlement_price )
|| ( old_collateralization.valid() && call_obj->debt <= *old_debt
&& call_obj->collateralization() > *old_collateralization ),
"Can only increase collateral ratio without increasing debt if would trigger a margin call that "
Expand Down
11 changes: 10 additions & 1 deletion libraries/chain/proposal_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
namespace graphene { namespace chain {

namespace detail {
void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options);
void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options);
void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options);
void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options);
void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op);
}

Expand All @@ -51,6 +52,10 @@ struct proposal_operation_hardfork_visitor
// hf_1774
detail::check_asset_options_hf_1774(block_time, v.common_options);

// HARDFORK_BSIP_77
if( v.bitasset_opts.valid() )
detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts );

// HARDFORK_BSIP_81
detail::check_asset_options_hf_bsip81(block_time, v.common_options);
}
Expand All @@ -61,6 +66,10 @@ struct proposal_operation_hardfork_visitor
// HARDFORK_BSIP_81
detail::check_asset_options_hf_bsip81(block_time, v.new_options);
}
void operator()(const graphene::chain::asset_update_bitasset_operation &v) const {
// HARDFORK_BSIP_77
detail::check_bitasset_options_hf_bsip77( block_time, v.new_options );
}

void operator()(const graphene::chain::asset_claim_fees_operation &v) const {
detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE
Expand Down
7 changes: 7 additions & 0 deletions libraries/protocol/asset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,13 @@ namespace graphene { namespace protocol {
return ~settlement_price * ratio_type( maintenance_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
}

price price_feed::calculate_initial_collateralization( uint16_t initial_collateral_ratio )const
{
if( settlement_price.is_null() )
return price();
return ~settlement_price * ratio_type( initial_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
}

// compile-time table of powers of 10 using template metaprogramming

template< int N >
Expand Down
7 changes: 7 additions & 0 deletions libraries/protocol/asset_ops.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,12 @@ void bitasset_options::validate() const
FC_ASSERT(minimum_feeds > 0);
FC_ASSERT(force_settlement_offset_percent <= GRAPHENE_100_PERCENT);
FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);

if( extensions.value.initial_collateral_ratio.valid() )
{
FC_ASSERT( *extensions.value.initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );
FC_ASSERT( *extensions.value.initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
}
}

void asset_options::validate()const
Expand Down Expand Up @@ -265,6 +271,7 @@ void asset_claim_pool_operation::validate()const {
} } // namespace graphene::protocol

GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_options )
GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options::ext )
GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options )
GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_options )
GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type )
Expand Down
3 changes: 3 additions & 0 deletions libraries/protocol/include/graphene/protocol/asset.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ namespace graphene { namespace protocol {
/// Calculation: ~settlement_price * maintenance_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM
price maintenance_collateralization()const;

/// The result will be used to check new debt positions and position updates.
/// Calculation: ~settlement_price * initial_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM
price calculate_initial_collateralization( uint16_t initial_collateral_ratio )const;
///@}

friend bool operator == ( const price_feed& a, const price_feed& b )
Expand Down
23 changes: 20 additions & 3 deletions libraries/protocol/include/graphene/protocol/asset_ops.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ namespace graphene { namespace protocol {
* @note Changes to this struct will break protocol compatibility
*/
struct bitasset_options {

struct ext
{
/// After BSIP77, when creating a new debt position or updating an existing position,
/// the position will be checked against this parameter.
/// Unused for prediction markets, although we allow it to be set for simpler implementation
fc::optional<uint16_t> initial_collateral_ratio;
};

/// Time before a price feed expires
uint32_t feed_lifetime_sec = GRAPHENE_DEFAULT_PRICE_FEED_LIFETIME;
/// Minimum number of unexpired feeds required to extract a median feed from
Expand All @@ -117,7 +126,8 @@ namespace graphene { namespace protocol {
/// This speicifies which asset type is used to collateralize short sales
/// This field may only be updated if the current supply of the asset is zero.
asset_id_type short_backing_asset;
extensions_type extensions;

extension<ext> extensions;

/// Perform internal consistency checks.
/// @throws fc::exception if any check fails
Expand Down Expand Up @@ -551,6 +561,9 @@ FC_REFLECT( graphene::protocol::asset_options,
(description)
(extensions)
)

FC_REFLECT( graphene::protocol::bitasset_options::ext, (initial_collateral_ratio) )

FC_REFLECT( graphene::protocol::bitasset_options,
(feed_lifetime_sec)
(minimum_feeds)
Expand All @@ -561,8 +574,11 @@ FC_REFLECT( graphene::protocol::bitasset_options,
(extensions)
)

FC_REFLECT( graphene::protocol::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing)(taker_fee_percent) )
FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) )
FC_REFLECT( graphene::protocol::additional_asset_options,
(reward_percent)(whitelist_market_fee_sharing)(taker_fee_percent) )

FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type,
(symbol3)(symbol4)(long_symbol)(price_per_kbyte) )
FC_REFLECT( graphene::protocol::asset_global_settle_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::protocol::asset_settle_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::protocol::asset_settle_cancel_operation::fee_parameters_type, )
Expand Down Expand Up @@ -624,6 +640,7 @@ FC_REFLECT( graphene::protocol::asset_reserve_operation,
FC_REFLECT( graphene::protocol::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) );

GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_options )
GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options::ext )
GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::bitasset_options )
GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::additional_asset_options )
GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type )
Expand Down
Loading

0 comments on commit 319fc3b

Please sign in to comment.