From dd0bef712e5a5c74d1316ab821d1daffa5e5aa29 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 23 Apr 2020 12:58:10 -0400 Subject: [PATCH 1/7] Implement BSIP 77: Initial collateral ratio (ICR) https://github.com/bitshares/bsips/blob/master/bsip-0077.md --- libraries/chain/asset_evaluator.cpp | 21 ++++++++++++---- libraries/chain/asset_object.cpp | 24 +++++++++++++++++++ libraries/chain/hardfork.d/BSIP_77.hf | 6 +++++ .../include/graphene/chain/asset_object.hpp | 5 ++++ libraries/chain/market_evaluator.cpp | 13 +++++++--- libraries/chain/proposal_evaluator.cpp | 11 ++++++++- libraries/protocol/asset.cpp | 7 ++++++ libraries/protocol/asset_ops.cpp | 7 ++++++ .../include/graphene/protocol/asset.hpp | 3 +++ .../include/graphene/protocol/asset_ops.hpp | 23 +++++++++++++++--- 10 files changed, 109 insertions(+), 11 deletions(-) create mode 100644 libraries/chain/hardfork.d/BSIP_77.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index dcff3f69fa..947ecbc6bf 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -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 ) @@ -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 ) { @@ -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() ) { @@ -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 ) { @@ -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." ); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 50e874f858..a3df1ec30d 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -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 ) @@ -79,7 +84,16 @@ 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 + 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; + } return; } @@ -102,7 +116,16 @@ 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 + 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; + } } @@ -186,6 +209,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) diff --git a/libraries/chain/hardfork.d/BSIP_77.hf b/libraries/chain/hardfork.d/BSIP_77.hf new file mode 100644 index 0000000000..f476e5999f --- /dev/null +++ b/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 diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 3541f87f5b..ab3e0ccb91 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -196,6 +196,11 @@ 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; /// True if this asset implements a @ref prediction_market bool is_prediction_market = false; diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 10a02125a4..960072d0b2 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -354,7 +354,8 @@ 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 @@ -362,9 +363,15 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope // 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 " diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index a6a25d32b6..086f7b26a9 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -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 @@ -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); } @@ -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) { diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 71e7665563..8588d91c3a 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -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 > diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 90c1e2917a..09198b0a72 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -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 @@ -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 ) diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index 9b0b9fea48..674b130ec1 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -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 ) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 858c25b512..d61bb53122 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -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 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 @@ -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 extensions; /// Perform internal consistency checks. /// @throws fc::exception if any check fails @@ -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) @@ -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, ) @@ -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 ) From b8c6843d3aa53970178b75af2018dffc390b9d29 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 10:45:34 -0400 Subject: [PATCH 2/7] Update some old test cases to cover bsip77 --- tests/common/database_fixture.hpp | 3 ++- tests/tests/bitasset_tests.cpp | 6 +++++- tests/tests/operation_tests.cpp | 22 ++++++++++++++++++++-- 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index a3cc5624df..7bbb4e5791 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -199,8 +199,9 @@ struct database_fixture { bool skip_key_index_test = false; uint32_t anon_acct_count; bool hf1270 = false; + bool bsip77 = false; - database_fixture(const fc::time_point_sec &initial_timestamp = + database_fixture(const fc::time_point_sec &initial_timestamp = fc::time_point_sec(GRAPHENE_TESTING_GENESIS_TIMESTAMP)); ~database_fixture(); diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 0c58af042d..797e3a2e66 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -927,7 +927,7 @@ BOOST_AUTO_TEST_CASE( hf_1270_test ) generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); generate_block( skip ); - for( int i = 0; i < 8; ++i ) + for( int i = 0; i < 10; ++i ) { idump( (i) ); int blocks = 0; @@ -943,6 +943,10 @@ BOOST_AUTO_TEST_CASE( hf_1270_test ) generate_blocks( HARDFORK_CORE_1270_TIME - mi, true, skip ); generate_blocks( db.get_dynamic_global_properties().next_maintenance_time, true, skip ); } + else if( i == 8 ) // go beyond hard fork BSIP77 + { + generate_blocks( HARDFORK_BSIP_77_TIME, true, skip ); + } set_expiration( db, trx ); ACTORS( (seller)(borrower)(feedproducer)(feedproducer2)(feedproducer3) ); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index ecf906b6a9..97e3b65aca 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -184,7 +184,10 @@ BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_583 ) { try { - generate_blocks( HARDFORK_CORE_583_TIME ); + auto hf_time = HARDFORK_CORE_583_TIME; + if( bsip77 ) + hf_time = HARDFORK_BSIP_77_TIME; + generate_blocks( hf_time ); generate_block(); set_expiration( db, trx ); @@ -340,6 +343,12 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) } } +BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) +{ + bsip77 = true; + INVOKE( old_call_order_update_test_after_hardfork_583 ); +} + BOOST_AUTO_TEST_CASE( more_call_order_update_test ) { try { @@ -447,7 +456,10 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) { try { - generate_blocks( HARDFORK_CORE_583_TIME ); + auto hf_time = HARDFORK_CORE_583_TIME; + if( bsip77 ) + hf_time = HARDFORK_BSIP_77_TIME; + generate_blocks( hf_time ); generate_block(); set_expiration( db, trx ); @@ -556,6 +568,12 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_583 ) } } +BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) +{ + bsip77 = true; + INVOKE( more_call_order_update_test_after_hardfork_583 ); +} + BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) { call_order_update_operation op; From 6e8ecba733d4a0f5151989add221269a5c2aff39 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 10:46:34 -0400 Subject: [PATCH 3/7] Remove an outdated test case about TCR --- tests/tests/operation_tests.cpp | 68 --------------------------------- 1 file changed, 68 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 97e3b65aca..cafeaec3a1 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -608,74 +608,6 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) op.validate(); // still valid } -// Tests that target_cr option can't be set before hard fork core-834 -// TODO: remove this test case after hard fork -BOOST_AUTO_TEST_CASE( call_order_update_target_cr_hardfork_time_test ) -{ - try { - set_expiration( db, trx ); - - ACTORS((sam)(alice)(bob)); - const auto& bitusd = create_bitasset("USDBIT", sam.id); - const auto& core = asset_id_type()(db); - asset_id_type bitusd_id = bitusd.id; - asset_id_type core_id = core.id; - - transfer(committee_account, sam_id, asset(10000000)); - transfer(committee_account, alice_id, asset(10000000)); - transfer(committee_account, bob_id, asset(10000000)); - update_feed_producers( bitusd, {sam.id} ); - - price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); - current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default - current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default - publish_feed( bitusd, sam, current_feed ); - - FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); - - auto call_update_proposal = [this]( const account_object& proposer, - const account_object& updater, - const asset& delta_collateral, - const asset& delta_debt, - const optional target_cr ) - { - call_order_update_operation op; - op.funding_account = updater.id; - op.delta_collateral = delta_collateral; - op.delta_debt = delta_debt; - op.extensions.value.target_collateral_ratio = target_cr; - - const auto& curfees = db.get_global_properties().parameters.get_current_fees(); - const auto& proposal_create_fees = curfees.get(); - proposal_create_operation prop; - prop.fee_paying_account = proposer.id; - prop.proposed_ops.emplace_back( op ); - prop.expiration_time = db.head_block_time() + fc::days(1); - prop.fee = asset( proposal_create_fees.fee + proposal_create_fees.price_per_kbyte ); - - signed_transaction tx; - tx.operations.push_back( prop ); - db.current_fee_schedule().set_fee( tx.operations.back() ); - set_expiration( db, tx ); - PUSH_TX( db, tx, ~0 ); - }; - - generate_blocks(HARDFORK_CORE_834_TIME); - set_expiration( db, trx ); - - BOOST_TEST_MESSAGE( "bob tries to propose a proposal with target_cr set, " - "will success after hard fork time" ); - // now able to propose - call_update_proposal( bob_id(db), alice_id(db), bitusd_id(db).amount(10), core_id(db).amount(40), 65535 ); - - generate_block(); - - } catch (fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - /** * This test sets up a situation where a margin call will be executed and ensures that * it is properly filled. From 49ea968a04c7d4173d84ad34f7c1018d1308b664 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 12:43:44 -0400 Subject: [PATCH 4/7] Add test case for BSIP77: hard fork time test --- tests/common/database_fixture.cpp | 38 +++++- tests/common/database_fixture.hpp | 11 +- tests/tests/operation_tests.cpp | 190 ++++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+), 4 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 0ad8c1c0fb..72b54f8b1b 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -434,6 +434,7 @@ void database_fixture::vote_for_committee_and_witnesses(uint16_t num_committee, op.fee = db.current_fee_schedule().calculate_fee( op ); + trx.operations.clear(); trx.operations.push_back(op); trx.validate(); PUSH_TX(db, trx, ~0); @@ -696,16 +697,17 @@ const account_object& database_fixture::get_account( const string& name )const return *itr; } -const asset_object& database_fixture::create_bitasset( +asset_create_operation database_fixture::make_bitasset( const string& name, account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, uint16_t market_fee_percent /* = 100 */ /* 1% */, uint16_t flags /* = charge_market_fee */, uint16_t precision /* = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS */, asset_id_type backing_asset /* = CORE */, - share_type max_supply /* = GRAPHENE_MAX_SHARE_SUPPLY */ + share_type max_supply, /* = GRAPHENE_MAX_SHARE_SUPPLY */ + optional initial_cr /* = {} */ ) -{ try { +{ asset_create_operation creator; creator.issuer = issuer; creator.fee = asset(); @@ -720,6 +722,24 @@ const asset_object& database_fixture::create_bitasset( creator.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); creator.bitasset_opts = bitasset_options(); creator.bitasset_opts->short_backing_asset = backing_asset; + creator.bitasset_opts->extensions.value.initial_collateral_ratio = initial_cr; + return creator; +} + +const asset_object& database_fixture::create_bitasset( + const string& name, + account_id_type issuer /* = GRAPHENE_WITNESS_ACCOUNT */, + uint16_t market_fee_percent /* = 100 */ /* 1% */, + uint16_t flags /* = charge_market_fee */, + uint16_t precision /* = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS */, + asset_id_type backing_asset /* = CORE */, + share_type max_supply, /* = GRAPHENE_MAX_SHARE_SUPPLY */ + optional initial_cr /* = {} */ + ) +{ try { + asset_create_operation creator = make_bitasset( name, issuer, market_fee_percent, flags, + precision, backing_asset, max_supply, initial_cr ); + trx.operations.clear(); trx.operations.push_back(std::move(creator)); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -751,6 +771,7 @@ const asset_object& database_fixture::create_prediction_market( creator.bitasset_opts = bitasset_options(); creator.bitasset_opts->short_backing_asset = backing_asset; creator.is_prediction_market = true; + trx.operations.clear(); trx.operations.push_back(std::move(creator)); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -771,6 +792,7 @@ const asset_object& database_fixture::create_user_issued_asset( const string& na creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; creator.common_options.flags = charge_market_fee; creator.common_options.issuer_permissions = charge_market_fee; + trx.operations.clear(); trx.operations.push_back(std::move(creator)); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -811,6 +833,7 @@ void database_fixture::issue_uia( const account_object& recipient, asset amount op.issuer = amount.asset_id(db).issuer; op.asset_to_issue = amount; op.issue_to_account = recipient.id; + trx.operations.clear(); trx.operations.push_back(op); PUSH_TX( db, trx, ~0 ); trx.operations.clear(); @@ -856,6 +879,7 @@ const account_object& database_fixture::create_account( const public_key_type& key /* = public_key_type() */ ) { + trx.operations.clear(); trx.operations.push_back(make_account(name, key)); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -923,6 +947,7 @@ const committee_member_object& database_fixture::create_committee_member( const { committee_member_create_operation op; op.committee_member_account = owner.id; + trx.operations.clear(); trx.operations.push_back(op); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -944,6 +969,7 @@ const witness_object& database_fixture::create_witness( const account_object& ow witness_create_operation op; op.witness_account = owner.id; op.block_signing_key = signing_private_key.get_public_key(); + trx.operations.clear(); trx.operations.push_back(op); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, skip_flags ); @@ -959,6 +985,7 @@ const worker_object& database_fixture::create_worker( const account_id_type owne op.initializer = burn_worker_initializer(); op.work_begin_date = db.head_block_time(); op.work_end_date = op.work_begin_date + duration; + trx.operations.clear(); trx.operations.push_back(op); trx.validate(); processed_transaction ptx = PUSH_TX(db, trx, ~0); @@ -1020,6 +1047,7 @@ asset database_fixture::cancel_limit_order( const limit_order_object& order ) limit_order_cancel_operation cancel_order; cancel_order.fee_paying_account = order.seller; cancel_order.order = order.id; + trx.operations.clear(); trx.operations.push_back(cancel_order); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); trx.validate(); @@ -1052,6 +1080,7 @@ void database_fixture::transfer( trans.from = from.id; trans.to = to.id; trans.amount = amount; + trx.operations.clear(); trx.operations.push_back(trans); if( fee == asset() ) @@ -1128,6 +1157,7 @@ void database_fixture::publish_feed(const account_id_type& publisher, op.asset_id = asset2; op.feed.settlement_price = ~price(a1.amount(amount1),a2.amount(amount2)); op.feed.core_exchange_rate = ~price(core.amount(amount1), a2.amount(amount2)); + trx.operations.clear(); trx.operations.push_back(std::move(op)); PUSH_TX( db, trx, ~0); generate_block(); @@ -1232,6 +1262,7 @@ void database_fixture::fund_fee_pool( const account_object& from, const asset_ob fund.from_account = from.id; fund.asset_id = asset_to_fund.id; fund.amount = amount; + trx.operations.clear(); trx.operations.push_back( fund ); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); @@ -1501,6 +1532,7 @@ void database_fixture::set_htlc_committee_parameters() uop.new_parameters.current_fees = new_fee_schedule; cop.proposed_ops.emplace_back(uop); + trx.operations.clear(); trx.operations.push_back(cop); graphene::chain::processed_transaction proc_trx = db.push_transaction(trx); trx.clear(); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 7bbb4e5791..df153fb195 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -283,13 +283,22 @@ struct database_fixture { const asset_object& get_asset( const string& symbol )const; const account_object& get_account( const string& name )const; + asset_create_operation make_bitasset( const string& name, + account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT, + uint16_t market_fee_percent = 100 /*1%*/, + uint16_t flags = charge_market_fee, + uint16_t precision = 2, + asset_id_type backing_asset = {}, + share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY, + optional initial_cr = {} ); const asset_object& create_bitasset(const string& name, account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT, uint16_t market_fee_percent = 100 /*1%*/, uint16_t flags = charge_market_fee, uint16_t precision = 2, asset_id_type backing_asset = {}, - share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY ); + share_type max_supply = GRAPHENE_MAX_SHARE_SUPPLY, + optional initial_cr = {} ); const asset_object& create_prediction_market(const string& name, account_id_type issuer = GRAPHENE_WITNESS_ACCOUNT, uint16_t market_fee_percent = 100 /*1%*/, diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index cafeaec3a1..4dd6ede0bd 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -343,6 +343,196 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) } } +/// Tests the "initial_collateral_ratio" parameter can only be set after the BSIP77 hard fork +// TODO remove this test case after the hard fork +BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_583_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + // Before bsip77 hard fork, unable to create a bitasset with ICR + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1000 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1001 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1750 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 32000 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBIT", sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 32001 ), fc::exception ); + + // Can create a bitasset without ICR + const auto& bitusd = create_bitasset( "USDBIT", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY ); + asset_id_type usd_id = bitusd.id; + + // helper function for setting ICR for an asset + auto set_icr_for_asset = [&](asset_id_type aid, optional icr) { + const asset_object& ao = aid(db); + const asset_bitasset_data_object& abo = ao.bitasset_data(db); + asset_update_bitasset_operation uop; + uop.issuer = ao.issuer; + uop.asset_to_update = aid; + uop.new_options = abo.options; + uop.new_options.extensions.value.initial_collateral_ratio = icr; + trx.operations.clear(); + trx.operations.push_back( uop ); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + }; + + // Before bsip77 hard fork, unable to update a bitasset with ICR + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 0 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1000 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1001 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1750 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32000 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32001 ), fc::exception ); + + // helper function for creating a proposal which contains an asset_create_operation with ICR + auto propose_create_bitasset = [&]( string name, optional icr ) { + asset_create_operation acop = make_bitasset( name, sam_id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, icr ); + proposal_create_operation cop; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + 100; + cop.proposed_ops.emplace_back( acop ); + trx.operations.clear(); + trx.operations.push_back( cop ); + trx.validate(); + set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + }; + + // Before bsip77 hard fork, unable to create a proposal with an asset_create_operation with ICR + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 0 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 1 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 1000 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 1001 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 1750 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 32000 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITA", 32001 ), fc::exception ); + + // helper function for creating a proposal which contains an asset_update_bitasset_operation with ICR + auto propose_set_icr_for_asset = [&](asset_id_type aid, optional icr) { + const asset_object& ao = aid(db); + const asset_bitasset_data_object& abo = ao.bitasset_data(db); + asset_update_bitasset_operation uop; + uop.issuer = ao.issuer; + uop.asset_to_update = aid; + uop.new_options = abo.options; + uop.new_options.extensions.value.initial_collateral_ratio = icr; + + proposal_create_operation cop; + cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; + cop.expiration_time = db.head_block_time() + 100; + cop.proposed_ops.emplace_back( uop ); + trx.operations.clear(); + trx.operations.push_back( cop ); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + }; + + // Before bsip77 hard fork, unable to create a proposal with an asset_update_bitasset_op with ICR + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 0 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1000 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1001 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1750 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32000 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32001 ), fc::exception ); + + // Pass the hard fork time + generate_blocks( HARDFORK_BSIP_77_TIME ); + set_expiration( db, trx ); + + // Unable to create a bitasset with an invalid ICR + BOOST_CHECK_THROW( create_bitasset( "USDBITB", sam_id, 0, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBITB", sam_id, 1, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBITB", sam_id, 1000, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception ); + BOOST_CHECK_THROW( create_bitasset( "USDBITB", sam_id, 32001, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 0 ), fc::exception ); + // Able to create a bitasset with a valid ICR + asset_id_type usdc_id = create_bitasset( "USDBITC", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1001 ).id; + asset_id_type usdd_id = create_bitasset( "USDBITD", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1750 ).id; + asset_id_type usde_id = create_bitasset( "USDBITE", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 32000 ).id; + // Able to create a bitasset without ICR + asset_id_type usdf_id = create_bitasset( "USDBITF", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, {} ).id; + + BOOST_CHECK( usdc_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1001 ); + BOOST_CHECK( usdd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1750 ); + BOOST_CHECK( usde_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 32000 ); + BOOST_CHECK( !usdf_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + + // Unable to update a bitasset with an invalid ICR + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 0 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 1000 ), fc::exception ); + BOOST_CHECK_THROW( set_icr_for_asset( usd_id, 32001 ), fc::exception ); + // Able to update a bitasset with a valid ICR + set_icr_for_asset( usd_id, 1001 ); + BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1001 ); + set_icr_for_asset( usd_id, 1750 ); + BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 1750 ); + set_icr_for_asset( usd_id, 32000 ); + BOOST_CHECK( usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio == 32000 ); + // Able to update a bitasset, unset its ICR + set_icr_for_asset( usd_id, {} ); + BOOST_CHECK( !usd_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + + // Unable to create a proposal with an asset_create_operation with an invalid ICR + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITG", 0 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITG", 1 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITG", 1000 ), fc::exception ); + BOOST_CHECK_THROW( propose_create_bitasset( "USDBITG", 32001 ), fc::exception ); + // able to create a proposal with a valid ICR or no ICR + propose_create_bitasset( "USDBITG", 1001 ); + propose_create_bitasset( "USDBITG", 1750 ); + propose_create_bitasset( "USDBITG", 32000 ); + propose_create_bitasset( "USDBITG", {} ); + + // Unable to create a proposal with an asset_update_bitasset_op with an invalid ICR + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 0 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 1000 ), fc::exception ); + BOOST_CHECK_THROW( propose_set_icr_for_asset( usd_id, 32001 ), fc::exception ); + // Able to create a proposal with a valid ICR or no ICR + propose_set_icr_for_asset( usd_id, 1001 ); + propose_set_icr_for_asset( usd_id, 1750 ); + propose_set_icr_for_asset( usd_id, 32000 ); + propose_set_icr_for_asset( usd_id, {} ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( old_call_order_update_test_after_hardfork_bsip77_when_icr_not_set ) { bsip77 = true; From 4f02fcf05487006ac34efce31c4ca8baa92ea86b Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 15:51:02 -0400 Subject: [PATCH 5/7] Update comment for a test case for bsip77 --- tests/tests/operation_tests.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 4dd6ede0bd..f3f795bc22 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -343,9 +343,11 @@ BOOST_AUTO_TEST_CASE( asset_settle_cancel_operation_test_after_hf588 ) } } -/// Tests the "initial_collateral_ratio" parameter can only be set after the BSIP77 hard fork -// TODO remove this test case after the hard fork -BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_test ) +/// Test case for bsip77: +/// * the "initial_collateral_ratio" parameter can only be set after the BSIP77 hard fork +/// * the parameter should be within a range +// TODO removed the hard fork part after the hard fork, keep the valid range part +BOOST_AUTO_TEST_CASE( bsip77_hardfork_time_and_param_valid_range_test ) { try { From 9963c8c7966ffc3b131530cdce84ca2ba26d7cbc Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 18:00:52 -0400 Subject: [PATCH 6/7] Update derived data when ICR changed --- libraries/chain/asset_evaluator.cpp | 11 +++++++++++ libraries/chain/asset_object.cpp | 18 +++++++++++------- .../include/graphene/chain/asset_object.hpp | 4 ++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 947ecbc6bf..0427ff71a3 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -581,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 @@ -610,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; } diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index a3df1ec30d..7da399444d 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -88,11 +88,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // update data derived from MCR current_maintenance_collateralization = current_feed.maintenance_collateralization(); // update data derived from ICR - 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; + refresh_current_initial_collateralization(); } return; } @@ -120,6 +116,16 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // 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 ); @@ -128,8 +134,6 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } } - - asset asset_object::amount_from_string(string amount_string) const { try { bool negative_found = false; diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index ab3e0ccb91..d28eb1a5cb 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -202,6 +202,10 @@ namespace graphene { namespace chain { /// 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; From 2131c6825fb2e151198145068037321c76240ff5 Mon Sep 17 00:00:00 2001 From: abitmore Date: Fri, 24 Apr 2020 18:03:43 -0400 Subject: [PATCH 7/7] Add tests about how ICR changes affect borrowing --- tests/tests/operation_tests.cpp | 201 ++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index f3f795bc22..b4fd6c89c9 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -766,6 +766,207 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr INVOKE( more_call_order_update_test_after_hardfork_583 ); } +BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_set ) +{ + try { + + auto hf_time = HARDFORK_BSIP_77_TIME; + generate_blocks( hf_time ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((dan)(sam)(alice)(bob)); + const auto& bitusd = create_bitasset( "USDBIT", sam.id, 100, charge_market_fee, 2, {}, + GRAPHENE_MAX_SHARE_SUPPLY, 1050 ); // ICR = 1.05 + const auto& core = asset_id_type()(db); + + asset_id_type usd_id = bitusd.id; + + // helper function for setting ICR for an asset + auto set_icr_for_asset = [&](asset_id_type aid, optional icr) { + const asset_object& ao = aid(db); + const asset_bitasset_data_object& abo = ao.bitasset_data(db); + asset_update_bitasset_operation uop; + uop.issuer = ao.issuer; + uop.asset_to_update = aid; + uop.new_options = abo.options; + uop.new_options.extensions.value.initial_collateral_ratio = icr; + trx.operations.clear(); + trx.operations.push_back( uop ); + trx.validate(); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + }; + + transfer(committee_account, dan_id, asset(10000000)); + transfer(committee_account, sam_id, asset(10000000)); + transfer(committee_account, alice_id, asset(10000000)); + transfer(committee_account, bob_id, asset(10000000)); + update_feed_producers( bitusd, {sam.id} ); + + price_feed current_feed; current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(100); + current_feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + current_feed.maximum_short_squeeze_ratio = 1100; // need to set this explicitly, testnet has a different default + publish_feed( bitusd, sam, current_feed ); + + FC_ASSERT( bitusd.bitasset_data(db).current_feed.settlement_price == current_feed.settlement_price ); + + BOOST_TEST_MESSAGE( "ICR 1.05, MCR 1.75" ); + BOOST_TEST_MESSAGE( "attempting to borrow using <=1.75x collateral at 1:1 price should not be allowed" ); + GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17499) ), fc::exception ); + GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception ); + + BOOST_TEST_MESSAGE( "alice borrow using 1.7501x collateral at 1:1 price should be allowed" ); + BOOST_CHECK( borrow( alice, bitusd.amount(10000), core.amount(17501) ) != nullptr ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 17501 ); + BOOST_TEST_MESSAGE( "ICR 1.05, MCR 1.75, Alice CR 1.7501" ); + + // Update ICR + BOOST_TEST_MESSAGE( "Updating ICR to 1.85" ); + set_icr_for_asset( usd_id, 1850 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 1.7501" ); + + BOOST_TEST_MESSAGE( "alice adding more collateral should be allowed" ); + BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18000-17501) ) != nullptr ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18000 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 1.8000" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral should not be allowed if CR<=1.85 and not margin called" ); + GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1) ), fc::exception ); + + BOOST_TEST_MESSAGE( "alice borrow using 1.8502x collateral at 1:1 price should be allowed" ); + BOOST_CHECK( borrow( alice, bitusd.amount(0), core.amount(18502-18000) ) != nullptr ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18502 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 1.8502" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral to >1.85x should be allowed" ); + cover( alice, bitusd.amount(0), core.amount(1) ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 10000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 18501 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 1.8501" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral to <=1.85x should not be allowed if not margin called" ); + GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1) ), fc::exception ); + + BOOST_TEST_MESSAGE( "alice borrow using 4x collateral at 1:1 price" ); + BOOST_CHECK( borrow( alice, bitusd.amount(100000-10000), core.amount(400000-18501) ) != nullptr ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 4.0000" ); + + BOOST_TEST_MESSAGE( "alice place an order to sell usd at 1.05" ); + const limit_order_id_type alice_sell_id = create_sell_order( alice, bitusd.amount(1000), core.amount(1050) )->id; + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 ); + + BOOST_TEST_MESSAGE( "bob attempting to borrow too much using 1.75x collateral at 1:1 price should not be allowed" ); + GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000), core.amount(17500) ), fc::exception ); + + BOOST_TEST_MESSAGE( "bob attempting to borrow less using 1.75x collateral at 1:1 price should be allowed and margin called" ); + BOOST_CHECK( !borrow( bob, bitusd.amount(100), core.amount(175) ) ); + BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 ); + BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 ); + + BOOST_TEST_MESSAGE( "bob attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" ); + const call_order_id_type bob_call_id = borrow( bob, bitusd.amount(100), asset(200))->id; + BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 ); + BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 200 ); + + BOOST_TEST_MESSAGE( "bob attempting to borrow too much more using 1.75x collateral at 1:1 price should not be allowed" ); + GRAPHENE_REQUIRE_THROW( borrow( bob, bitusd.amount(10000-100), core.amount(17500-200) ), fc::exception ); + + BOOST_TEST_MESSAGE( "bob attempting to reduce collateral to 1.75x at 1:1 price should be allowed and margin called" ); + BOOST_CHECK( !borrow( bob, bitusd.amount(0), core.amount(175-200) ) ); + BOOST_REQUIRE_EQUAL( get_balance( bob, bitusd ), 100 + 100 ); + BOOST_REQUIRE_EQUAL( get_balance( bob, core ), 10000000 - 105 - 105 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, bitusd ), 100000 - 1000 ); + BOOST_REQUIRE_EQUAL( get_balance( alice, core ), 10000000 - 400000 + 105 + 105 ); + BOOST_CHECK( !db.find( bob_call_id ) ); + + BOOST_TEST_MESSAGE( "alice cancel sell order" ); + cancel_limit_order( alice_sell_id(db) ); + + BOOST_TEST_MESSAGE( "dan attempting to borrow using 2x collateral at 1:1 price now that there is a valid order" ); + borrow( dan, bitusd.amount(5000), asset(10000)); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 5000 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 ); + + BOOST_TEST_MESSAGE( "sam update price feed so dan's position will enter margin call territory." ); + current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(180); + publish_feed( bitusd, sam, current_feed ); + + BOOST_TEST_MESSAGE( "dan covering 2500 usd and freeing 5000 core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5000) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan covering 2500 usd and freeing 5001 core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(2500), core.amount(5001) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 5000 more core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5000) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 4999 more core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(4999) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan covering 2500 usd and freeing 4999 core should be allowed..." ); + cover( dan, bitusd.amount(2500), asset(4999)); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999 ); + + BOOST_TEST_MESSAGE( "dan covering 0 usd and freeing 1 core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( cover( dan, bitusd.amount(0), core.amount(1) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan adding 1 core as collateral should be allowed..." ); + borrow( dan, bitusd.amount(0), asset(1)); + BOOST_REQUIRE_EQUAL( get_balance( dan, bitusd ), 2500 ); + BOOST_REQUIRE_EQUAL( get_balance( dan, core ), 10000000 - 10000 + 4999 - 1 ); + + BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 5002 more core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), core.amount(5002) ), fc::exception ); + + BOOST_TEST_MESSAGE( "dan borrow 2500 more usd wth 5003 more core should not be allowed..." ); + GRAPHENE_REQUIRE_THROW( borrow( dan, bitusd.amount(2500), asset(5003) ), fc::exception ); + + // CR of Alice's postion is now 4.0 / 1.8 ~= 2.2222 + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 2.222222" ); + + BOOST_TEST_MESSAGE( "alice adding more collateral should be allowed" ); + const call_order_id_type alice_call_id = borrow( alice, bitusd.amount(0), asset(1))->id; + BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 400000 + 1 ); + BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 2.222228" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral to >1.85x should be allowed" ); + cover( alice, bitusd.amount(0), core.amount(67000) ); + BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333001 ); + BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 ); + BOOST_TEST_MESSAGE( "ICR 1.85, MCR 1.75, Alice CR 1.850006" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral to <=1.85x should not be allowed if not margin called" ); + GRAPHENE_REQUIRE_THROW( cover( alice, bitusd.amount(0), core.amount(1) ), fc::exception ); + + // Update ICR + BOOST_TEST_MESSAGE( "Updating ICR to 1.84" ); + set_icr_for_asset( usd_id, 1840 ); + BOOST_TEST_MESSAGE( "ICR 1.84, MCR 1.75, Alice CR 1.850006" ); + + BOOST_TEST_MESSAGE( "alice reducing collateral to >1.84x should be allowed" ); + cover( alice, bitusd.amount(0), core.amount(1) ); + BOOST_CHECK_EQUAL( alice_call_id(db).collateral.value, 333000 ); + BOOST_CHECK_EQUAL( alice_call_id(db).debt.value, 100000 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE( call_order_update_validation_test ) { call_order_update_operation op;