Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement BSIP 77: Initial collateral ratio (ICR) #2157

Merged
merged 8 commits into from May 5, 2020
Merged
32 changes: 28 additions & 4 deletions libraries/chain/asset_evaluator.cpp
Expand Up @@ -55,18 +55,30 @@ namespace detail {
"Taker fee percent should not be defined before HARDFORK_BSIP_81_TIME");
}
}

// 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_result asset_create_evaluator::do_evaluate( const asset_create_operation& op )
{ 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 @@ -78,8 +90,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 @@ -98,6 +108,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 @@ -297,7 +308,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 @@ -436,6 +447,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 @@ -568,6 +581,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 @@ -597,6 +616,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
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
@@ -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
Expand Up @@ -196,6 +196,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
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
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);
}

struct proposal_operation_hardfork_visitor
Expand All @@ -50,6 +51,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 @@ -60,6 +65,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::committee_member_update_global_parameters_operation &op) const {
if (block_time < HARDFORK_CORE_1468_TIME) {
Expand Down
7 changes: 7 additions & 0 deletions libraries/protocol/asset.cpp
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
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 @@ -263,6 +269,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
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
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 @@ -538,6 +548,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 @@ -548,8 +561,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 @@ -611,6 +627,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