From 841b4426c57ec0317d40b7baaec9175230dc91d4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 May 2020 21:22:36 -0400 Subject: [PATCH 01/29] Add extensions to several asset operations for BSIP 48, 75 and 77 --- libraries/chain/asset_evaluator.cpp | 207 +++++++++++++++--- libraries/chain/asset_object.cpp | 45 +++- libraries/chain/hardfork.d/BSIP_48_75.hf | 8 + .../include/graphene/chain/asset_object.hpp | 44 +++- libraries/chain/market_evaluator.cpp | 2 + libraries/chain/proposal_evaluator.cpp | 17 ++ libraries/protocol/asset.cpp | 7 - libraries/protocol/asset_ops.cpp | 45 +++- .../include/graphene/protocol/asset.hpp | 10 +- .../include/graphene/protocol/asset_ops.hpp | 44 +++- .../include/graphene/protocol/types.hpp | 88 +++++++- 11 files changed, 446 insertions(+), 71 deletions(-) create mode 100644 libraries/chain/hardfork.d/BSIP_48_75.hf diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b350a6dcb4..2b29951d42 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -56,6 +56,59 @@ namespace detail { } } + // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME + void check_asset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const asset_options& options) + { + if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + // new issuer permissions should not be set until activation of BSIP_48_75 + FC_ASSERT( !(options.issuer_permissions & ~ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK), + "New asset issuer permission bits should not be set before HARDFORK_BSIP_48_75_TIME" ); + // Note: no check for flags here because we didn't check in the past + } + } + + // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME + void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const bitasset_options& options) + { + if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + // new params should not be set until activation of BSIP_48_75 + FC_ASSERT( !options.extensions.value.maintenance_collateral_ratio.valid(), + "Maintenance collateral ratio should not be defined by asset owner " + "before HARDFORK_BSIP_48_75_TIME" ); + FC_ASSERT( !options.extensions.value.maximum_short_squeeze_ratio.valid(), + "Maximum short squeeze ratio should not be defined by asset owner " + "before HARDFORK_BSIP_48_75_TIME" ); + } + } + + // TODO review and remove code below and links to it after HARDFORK_BSIP_48_75_TIME + void check_asset_update_extensions_hf_bsip_48_75( const fc::time_point_sec& block_time, + const asset_update_operation::ext& extensions ) + { + if ( !HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + // new extensions should not be set until activation of BSIP_48_75 + FC_ASSERT( !extensions.new_precision.valid(), + "new_precision should not be set before HARDFORK_BSIP_48_75_TIME" ); + FC_ASSERT( !extensions.skip_core_exchange_rate.valid(), + "skip_core_exchange_rate should not be set before HARDFORK_BSIP_48_75_TIME" ); + } + } + + // TODO review and remove code below and links to it after HARDFORK_BSIP_77_TIME + void check_asset_publish_feed_extensions_hf_bsip77( const fc::time_point_sec& block_time, + const asset_publish_feed_operation::ext& extensions ) + { + if ( !HARDFORK_BSIP_77_PASSED( block_time ) ) + { + // new extensions should not be set until activation of BSIP_77 + FC_ASSERT( !extensions.initial_collateral_ratio.valid(), + "Initial collateral ratio should not be defined before HARDFORK_BSIP_77_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) { @@ -93,12 +146,32 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o // Hardfork Checks: detail::check_asset_options_hf_1774(now, op.common_options); + detail::check_asset_options_hf_bsip_48_75(now, op.common_options); detail::check_asset_options_hf_bsip81(now, op.common_options); if( op.bitasset_opts ) { + detail::check_bitasset_options_hf_bsip_48_75( now, *op.bitasset_opts ); detail::check_bitasset_options_hf_bsip77( now, *op.bitasset_opts ); detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE } + // TODO move the assertions to asset_options::validate() if not triggered before hardfork + if( HARDFORK_BSIP_48_75_PASSED( now ) ) + { + FC_ASSERT( !(op.common_options.flags & ~ASSET_ISSUER_PERMISSION_MASK), + "Can not set an unknown bit in flags" ); + FC_ASSERT( !(op.common_options.flags & disable_mcr_update), + "Can not set disable_mcr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(op.common_options.flags & disable_icr_update), + "Can not set disable_icr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(op.common_options.flags & disable_mssr_update), + "Can not set disable_mssr_update flag, it is for issuer permission only" ); + if( !op.bitasset_opts.valid() ) + { + FC_ASSERT( !(op.common_options.flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), + "Can not set a flag for bitassets only to UIA" ); + } + } + 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 ); @@ -220,6 +293,8 @@ void_result asset_issue_evaluator::do_evaluate( const asset_issue_operation& o ) FC_ASSERT( o.issuer == a.issuer ); FC_ASSERT( !a.is_market_issued(), "Cannot manually issue a market-issued asset." ); + FC_ASSERT( a.can_create_new_supply(), "Can not create new supply" ); + to_account = &o.issue_to_account(d); FC_ASSERT( is_authorized_asset( d, *to_account, a ) ); @@ -318,7 +393,9 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) // Hardfork Checks: detail::check_asset_options_hf_1774(now, o.new_options); + detail::check_asset_options_hf_bsip_48_75(now, o.new_options); detail::check_asset_options_hf_bsip81(now, o.new_options); + detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); const asset_object& a = o.asset_to_update(d); auto a_copy = a; @@ -332,22 +409,35 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); if( a.dynamic_asset_data_id(d).current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions - FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions), - "Cannot reinstate previously revoked issuer permissions on an asset."); + FC_ASSERT(!(o.new_options.get_enabled_issuer_permissions_mask() & ~enabled_issuer_permissions_mask), + "Cannot reinstate previously revoked issuer permissions on an asset if current supply is non-zero."); + // precision can not be changed + FC_ASSERT( !o.extensions.value.new_precision.valid(), + "Cannot update precision if current supply is non-zero" ); } // changed flags must be subset of old issuer permissions - FC_ASSERT(!((o.new_options.flags ^ a.options.flags) & ~a.options.issuer_permissions), - "Flag change is forbidden by issuer permissions"); + FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & ~enabled_issuer_permissions_mask), + "Flag change is forbidden by issuer permissions" ); asset_to_update = &a; FC_ASSERT( o.issuer == a.issuer, "Incorrect issuer for asset! (${o.issuer} != ${a.issuer})", ("o.issuer", o.issuer)("a.issuer", a.issuer) ); + FC_ASSERT( a.can_update_max_supply() || a.options.max_supply == o.new_options.max_supply, + "Can not update max supply" ); + + if( o.extensions.value.new_precision.valid() ) + { + FC_ASSERT( *o.extensions.value.new_precision != a.precision, + "Specified a new precision but it does not change" ); + } + const auto& chain_parameters = d.get_global_properties().parameters; FC_ASSERT( o.new_options.whitelist_authorities.size() <= chain_parameters.maximum_asset_whitelist_authorities ); @@ -377,7 +467,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) } // For market-issued assets, if core change rate changed, update flag in bitasset data - if( asset_to_update->is_market_issued() + if( !o.extensions.value.skip_core_exchange_rate.valid() && asset_to_update->is_market_issued() && asset_to_update->options.core_exchange_rate != o.new_options.core_exchange_rate ) { const auto& bitasset = asset_to_update->bitasset_data(d); @@ -393,7 +483,16 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) d.modify(*asset_to_update, [&o](asset_object& a) { if( o.new_issuer ) a.issuer = *o.new_issuer; - a.options = o.new_options; + if( o.extensions.value.new_precision.valid() ) + a.precision = *o.extensions.value.new_precision; + if( o.extensions.value.skip_core_exchange_rate.valid() ) + { + const auto old_cer = a.options.core_exchange_rate; + a.options = o.new_options; + a.options.core_exchange_rate = old_cer; + } + else + a.options = o.new_options; }); return void_result(); @@ -468,6 +567,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita const time_point_sec now = d.head_block_time(); // Hardfork Checks: + detail::check_bitasset_options_hf_bsip_48_75( now, op.new_options ); detail::check_bitasset_options_hf_bsip77( now, op.new_options ); detail::check_bitasset_options_hf_bsip87( now, op.new_options ); // HF_REMOVABLE @@ -479,18 +579,51 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita const asset_bitasset_data_object& current_bitasset_data = asset_obj.bitasset_data(d); - FC_ASSERT( !current_bitasset_data.has_settlement(), "Cannot update a bitasset after a global settlement has executed" ); + FC_ASSERT( !current_bitasset_data.has_settlement(), + "Cannot update a bitasset after a global settlement has executed" ); + + if( !asset_obj.can_owner_update_mcr() ) + { + // check if MCR will change + const auto& old_mcr = current_bitasset_data.options.extensions.value.maintenance_collateral_ratio; + const auto& new_mcr = op.new_options.extensions.value.maintenance_collateral_ratio; + bool mcr_changed = ( ( old_mcr.valid() != new_mcr.valid() ) + || ( old_mcr.valid() && *old_mcr != *new_mcr ) ); + FC_ASSERT( !mcr_changed, "No permission to update MCR" ); + } + + if( !asset_obj.can_owner_update_icr() ) + { + // check if ICR will change + const auto& old_icr = current_bitasset_data.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 ) ); + FC_ASSERT( !icr_changed, "No permission to update ICR" ); + } + + if( !asset_obj.can_owner_update_mssr() ) + { + // check if MSSR will change + const auto& old_mssr = current_bitasset_data.options.extensions.value.maximum_short_squeeze_ratio; + const auto& new_mssr = op.new_options.extensions.value.maximum_short_squeeze_ratio; + bool mssr_changed = ( ( old_mssr.valid() != new_mssr.valid() ) + || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); + FC_ASSERT( !mssr_changed, "No permission to update MSSR" ); + } // hf 922_931 is a consensus/logic change. This hf cannot be removed. - bool after_hf_core_922_931 = ( d.get_dynamic_global_properties().next_maintenance_time > HARDFORK_CORE_922_931_TIME ); + bool after_hf_core_922_931 = ( d.get_dynamic_global_properties().next_maintenance_time + > HARDFORK_CORE_922_931_TIME ); // Are we changing the backing asset? if( op.new_options.short_backing_asset != current_bitasset_data.options.short_backing_asset ) { - FC_ASSERT( asset_obj.dynamic_asset_data_id(d).current_supply == 0, + const asset_dynamic_data_object& dyn = asset_obj.dynamic_asset_data_id(d); + FC_ASSERT( dyn.current_supply == 0, "Cannot update a bitasset if there is already a current supply." ); - FC_ASSERT( asset_obj.dynamic_asset_data_id(d).accumulated_collateral_fees == 0, + FC_ASSERT( dyn.accumulated_collateral_fees == 0, "Must claim collateral-denominated fees before changing backing asset." ); const asset_object& new_backing_asset = op.new_options.short_backing_asset(d); // check if the asset exists @@ -607,10 +740,34 @@ static bool update_bitasset_object_options( } // 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 ) ); + if( !should_update_feeds ) + { + 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 ) ); + should_update_feeds = icr_changed; + } + + // check if MCR will change + if( !should_update_feeds ) + { + const auto& old_mcr = bdo.options.extensions.value.maintenance_collateral_ratio; + const auto& new_mcr = op.new_options.extensions.value.maintenance_collateral_ratio; + bool mcr_changed = ( ( old_mcr.valid() != new_mcr.valid() ) + || ( old_mcr.valid() && *old_mcr != *new_mcr ) ); + should_update_feeds = mcr_changed; + } + + // check if MSSR will change + if( !should_update_feeds ) + { + const auto& old_mssr = bdo.options.extensions.value.maximum_short_squeeze_ratio; + const auto& new_mssr = op.new_options.extensions.value.maximum_short_squeeze_ratio; + bool mssr_changed = ( ( old_mssr.valid() != new_mssr.valid() ) + || ( old_mssr.valid() && *old_mssr != *new_mssr ) ); + should_update_feeds = mssr_changed; + } bdo.options = op.new_options; @@ -635,16 +792,11 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { - const auto old_feed = bdo.current_feed; + const price_feed old_feed = static_cast( bdo.current_feed ); bdo.update_median_feeds( db.head_block_time(), next_maint_time ); // 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 ( after_hf_core_868_890 && ! ( old_feed == static_cast( bdo.current_feed ) ) ); } return false; @@ -857,14 +1009,18 @@ operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator:: void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o) { try { - database& d = db(); + const database& d = db(); + const time_point_sec now = d.head_block_time(); + + // TODO remove check after hard fork + detail::check_asset_publish_feed_extensions_hf_bsip77( now, o.extensions.value ); const asset_object& base = o.asset_id(d); //Verify that this feed is for a market-issued asset and that asset is backed by the base FC_ASSERT( base.is_market_issued(), "Can only publish price feeds for market-issued assets" ); const asset_bitasset_data_object& bitasset = base.bitasset_data(d); - if( bitasset.is_prediction_market || d.head_block_time() <= HARDFORK_CORE_216_TIME ) + if( bitasset.is_prediction_market || now <= HARDFORK_CORE_216_TIME ) { FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" ); } @@ -873,7 +1029,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_ FC_ASSERT( o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset, "Quote asset type in settlement price should be same as backing asset of this asset" ); - if( d.head_block_time() > HARDFORK_480_TIME ) + if( now > HARDFORK_480_TIME ) { if( !o.feed.core_exchange_rate.is_null() ) { @@ -926,7 +1082,8 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope auto old_feed = bad.current_feed; // Store medians for this asset d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { - a.feeds[o.publisher] = make_pair( head_time, o.feed ); + a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, + o.extensions.value.initial_collateral_ratio ) ); a.update_median_feeds( head_time, next_maint_time ); }); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index e442465d36..c2faa4269b 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -49,9 +49,9 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin { bool after_core_hardfork_1270 = ( next_maintenance_time > HARDFORK_CORE_1270_TIME ); // call price caching issue current_feed_publication_time = current_time; - vector> current_feeds; + vector> current_feeds; // find feeds that were alive at current_time - for( const pair>& f : feeds ) + for( const pair>& f : feeds ) { if( (current_time - f.second.first).to_seconds() < options.feed_lifetime_sec && f.second.first != time_point_sec() ) @@ -67,7 +67,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin //... don't calculate a median, and set a null feed feed_cer_updated = false; // new median cer is null, won't update asset_object anyway, set to false for better performance current_feed_publication_time = current_time; - current_feed = price_feed(); + current_feed = price_feed_with_icr(); if( after_core_hardfork_1270 ) { // update data derived from MCR @@ -85,6 +85,13 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { + const auto& exts = options.extensions.value; + if( exts.maintenance_collateral_ratio.valid() ) + current_feed.maintenance_collateral_ratio = *exts.maintenance_collateral_ratio; + if( exts.maximum_short_squeeze_ratio.valid() ) + current_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; + if( exts.initial_collateral_ratio.valid() ) + current_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; // update data derived from MCR current_maintenance_collateralization = current_feed.maintenance_collateralization(); // update data derived from ICR @@ -94,16 +101,26 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin } // *** Begin Median Calculations *** - price_feed median_feed; + price_feed_with_icr median_feed; const auto median_itr = current_feeds.begin() + current_feeds.size() / 2; #define CALCULATE_MEDIAN_VALUE(r, data, field_name) \ std::nth_element( current_feeds.begin(), median_itr, current_feeds.end(), \ - [](const price_feed& a, const price_feed& b) { \ + [](const price_feed_with_icr& a, const price_feed_with_icr& b) { \ return a.field_name < b.field_name; \ }); \ median_feed.field_name = median_itr->get().field_name; - BOOST_PP_SEQ_FOR_EACH( CALCULATE_MEDIAN_VALUE, ~, GRAPHENE_PRICE_FEED_FIELDS ) +#define CHECK_AND_CALCULATE_MEDIAN_VALUE(r, data, field_name) \ + if( options.extensions.value.field_name.valid() ) { \ + median_feed.field_name = *options.extensions.value.field_name; \ + } else { \ + CALCULATE_MEDIAN_VALUE(r, data, field_name); \ + } + + BOOST_PP_SEQ_FOR_EACH( CALCULATE_MEDIAN_VALUE, ~, (settlement_price)(core_exchange_rate) ) + BOOST_PP_SEQ_FOR_EACH( CHECK_AND_CALCULATE_MEDIAN_VALUE, ~, + (maintenance_collateral_ratio)(maximum_short_squeeze_ratio)(initial_collateral_ratio) ) +#undef CHECK_AND_CALCULATE_MEDIAN_VALUE #undef CALCULATE_MEDIAN_VALUE // *** End Median Calculations *** @@ -126,14 +143,20 @@ void asset_bitasset_data_object::refresh_current_initial_collateralization() 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 + if( current_feed.initial_collateral_ratio > current_feed.maintenance_collateral_ratio ) // if ICR is above MCR + current_initial_collateralization = current_feed.calculate_initial_collateralization(); + else // if ICR is not above MCR current_initial_collateralization = current_maintenance_collateralization; } } +price price_feed_with_icr::calculate_initial_collateralization()const +{ + if( settlement_price.is_null() ) + return price(); + return ~settlement_price * ratio_type( initial_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM ); +} + asset asset_object::amount_from_string(string amount_string) const { try { bool negative_found = false; @@ -223,6 +246,8 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (gr (feed_cer_updated) ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::price_feed_with_icr ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_bitasset_data_object ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::chain::asset_dynamic_data_object ) diff --git a/libraries/chain/hardfork.d/BSIP_48_75.hf b/libraries/chain/hardfork.d/BSIP_48_75.hf new file mode 100644 index 0000000000..c3d03c3258 --- /dev/null +++ b/libraries/chain/hardfork.d/BSIP_48_75.hf @@ -0,0 +1,8 @@ +// hardfork check for +// - BSIP 48 : new issuer permissions "lock_max_supply" and "disable_new_supply", precision update, skip cer +// - BSIP 75 : asset owner set MCR, ICR and MSSR +#ifndef HARDFORK_BSIP_48_75_TIME +// Jan 1 2030, midnight; this is a dummy date until a hardfork date is scheduled +#define HARDFORK_BSIP_48_75_TIME (fc::time_point_sec( 1893456000 )) +#define HARDFORK_BSIP_48_75_PASSED(now) (now >= HARDFORK_BSIP_48_75_TIME) +#endif diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 97255a9610..9bd2c65bab 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -99,6 +99,16 @@ namespace graphene { namespace chain { bool is_transfer_restricted()const { return options.flags & transfer_restricted; } bool can_override()const { return options.flags & override_authority; } bool allow_confidential()const { return !(options.flags & asset_issuer_permission_flags::disable_confidential); } + /// @return true if max supply of the asset can be updated + bool can_update_max_supply()const { return !(options.flags & lock_max_supply); } + /// @return true if can create new supply for the asset + bool can_create_new_supply()const { return !(options.flags & disable_new_supply); } + /// @return true if the asset owner can update MCR directly + bool can_owner_update_mcr()const { return !(options.issuer_permissions & disable_mcr_update); } + /// @return true if the asset owner can update ICR directly + bool can_owner_update_icr()const { return !(options.issuer_permissions & disable_icr_update); } + /// @return true if the asset owner can update MSSR directly + bool can_owner_update_mssr()const { return !(options.issuer_permissions & disable_mssr_update); } /// Helper function to get an asset object with the given amount in this asset's type asset amount(share_type a)const { return asset(a, id); } @@ -207,6 +217,31 @@ namespace graphene { namespace chain { }; + /** + * @brief defines market parameters for margin positions, extended with an initial_collateral_ratio field + */ + struct price_feed_with_icr : public price_feed + { + /// After BSIP77, when creating a new debt position or updating an existing position, + /// the position will be checked against this parameter. + /// Fixed point between 1.000 and 10.000, implied fixed point denominator is GRAPHENE_COLLATERAL_RATIO_DENOM + uint16_t initial_collateral_ratio = GRAPHENE_DEFAULT_MAINTENANCE_COLLATERAL_RATIO; + + price_feed_with_icr( const price_feed& pf = {}, const optional& icr = {} ) + : price_feed( pf ), initial_collateral_ratio( icr.valid() ? *icr : pf.maintenance_collateral_ratio ) + {} + + /// 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()const; + + friend bool operator == ( const price_feed_with_icr& a, const price_feed_with_icr& b ) + { + return static_cast(a) == static_cast(b) + && a.initial_collateral_ratio == b.initial_collateral_ratio; + } + }; + /** * @brief contains properties that only apply to bitassets (market issued assets) * @@ -228,10 +263,10 @@ namespace graphene { namespace chain { /// Feeds published for this asset. If issuer is not committee, the keys in this map are the feed publishing /// accounts; otherwise, the feed publishers are the currently active committee_members and witnesses and this map /// should be treated as an implementation detail. The timestamp on each feed is the time it was published. - flat_map> feeds; + flat_map> feeds; /// This is the currently active price feed, calculated as the median of values from the currently active /// feeds. - price_feed current_feed; + price_feed_with_icr current_feed; /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. @@ -373,6 +408,9 @@ MAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_object) MAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_dynamic_data_object) MAP_OBJECT_ID_TO_TYPE(graphene::chain::asset_bitasset_data_object) +FC_REFLECT_DERIVED( graphene::chain::price_feed_with_icr, (graphene::protocol::price_feed), + (initial_collateral_ratio) ) + FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), (symbol) (precision) @@ -386,6 +424,8 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object), FC_REFLECT_TYPENAME( graphene::chain::asset_bitasset_data_object ) FC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_object ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::price_feed_with_icr ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_object ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_bitasset_data_object ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::chain::asset_dynamic_data_object ) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index 960072d0b2..fd01e0fffb 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -189,6 +189,8 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope FC_ASSERT( _debt_asset->is_market_issued(), "Unable to cover ${sym} as it is not a collateralized asset.", ("sym", _debt_asset->symbol) ); + FC_ASSERT( o.delta_debt.amount <= 0 || _debt_asset->can_create_new_supply(), "Can not create new supply" ); + _dynamic_data_obj = &_debt_asset->dynamic_asset_data_id(d); /*** diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index d73e343759..b151e4d73d 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -30,6 +30,12 @@ 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_bsip_48_75(const fc::time_point_sec& block_time, const asset_options& options); + void check_bitasset_options_hf_bsip_48_75(const fc::time_point_sec& block_time, const bitasset_options& options); + void check_asset_update_extensions_hf_bsip_48_75( const fc::time_point_sec& block_time, + const asset_update_operation::ext& extensions ); + void check_asset_publish_feed_extensions_hf_bsip77( const fc::time_point_sec& block_time, + const asset_publish_feed_operation::ext& extensions ); void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options); void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options); void check_bitasset_options_hf_bsip87(const fc::time_point_sec& block_time, @@ -54,8 +60,10 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::asset_create_operation &v) const { detail::check_asset_options_hf_1774(block_time, v.common_options); + detail::check_asset_options_hf_bsip_48_75(block_time, v.common_options); detail::check_asset_options_hf_bsip81(block_time, v.common_options); if( v.bitasset_opts.valid() ) { + detail::check_bitasset_options_hf_bsip_48_75( block_time, *v.bitasset_opts ); detail::check_bitasset_options_hf_bsip77( block_time, *v.bitasset_opts ); detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE } @@ -65,12 +73,15 @@ struct proposal_operation_hardfork_visitor void operator()(const graphene::chain::asset_update_operation &v) const { detail::check_asset_options_hf_1774(block_time, v.new_options); + detail::check_asset_options_hf_bsip_48_75(block_time, v.new_options); detail::check_asset_options_hf_bsip81(block_time, v.new_options); + detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value ); } void operator()(const graphene::chain::asset_update_bitasset_operation &v) const { + detail::check_bitasset_options_hf_bsip_48_75( block_time, v.new_options ); detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); detail::check_bitasset_options_hf_bsip87( block_time, v.new_options ); // HF_REMOVABLE @@ -82,6 +93,12 @@ struct proposal_operation_hardfork_visitor } + void operator()(const graphene::chain::asset_publish_feed_operation &v) const { + + detail::check_asset_publish_feed_extensions_hf_bsip77( block_time, v.extensions.value ); + + } + void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { if (block_time < HARDFORK_CORE_1468_TIME) { FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), diff --git a/libraries/protocol/asset.cpp b/libraries/protocol/asset.cpp index 8588d91c3a..71e7665563 100644 --- a/libraries/protocol/asset.cpp +++ b/libraries/protocol/asset.cpp @@ -294,13 +294,6 @@ 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 812df9b98c..831aafb060 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -103,7 +103,8 @@ void asset_create_operation::validate()const FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( is_valid_symbol(symbol) ); common_options.validate(); - if( common_options.issuer_permissions & (disable_force_settle|global_settle) ) + if( common_options.issuer_permissions + & (disable_force_settle|global_settle|disable_mcr_update|disable_icr_update|disable_mssr_update) ) FC_ASSERT( bitasset_opts.valid() ); if( is_prediction_market ) { @@ -126,6 +127,15 @@ void asset_update_operation::validate()const asset dummy = asset(1, asset_to_update) * new_options.core_exchange_rate; FC_ASSERT(dummy.asset_id == asset_id_type()); + + if( extensions.value.new_precision.valid() ) + FC_ASSERT( *extensions.value.new_precision <= 12 ); + + if( extensions.value.skip_core_exchange_rate.valid() ) + { + FC_ASSERT( *extensions.value.skip_core_exchange_rate == true, + "If skip_core_exchange_rate is specified, it can only be true" ); + } } void asset_update_issuer_operation::validate()const @@ -158,6 +168,12 @@ void asset_publish_feed_operation::validate()const FC_ASSERT( !feed.settlement_price.is_null() ); FC_ASSERT( !feed.core_exchange_rate.is_null() ); FC_ASSERT( feed.is_for( asset_id ) ); + + 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_reserve_operation::validate()const @@ -216,6 +232,16 @@ void bitasset_options::validate() const FC_ASSERT( *extensions.value.initial_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO ); FC_ASSERT( *extensions.value.initial_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); } + if( extensions.value.maintenance_collateral_ratio.valid() ) + { + FC_ASSERT( *extensions.value.maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO ); + FC_ASSERT( *extensions.value.maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); + } + if( extensions.value.maximum_short_squeeze_ratio.valid() ) + { + FC_ASSERT( *extensions.value.maximum_short_squeeze_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO ); + FC_ASSERT( *extensions.value.maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO ); + } if( extensions.value.force_settle_fee_percent.valid() ) FC_ASSERT( *extensions.value.force_settle_fee_percent <= GRAPHENE_100_PERCENT ); @@ -236,8 +262,10 @@ void asset_options::validate()const FC_ASSERT( max_market_fee >= 0 && max_market_fee <= GRAPHENE_MAX_SHARE_SUPPLY ); // There must be no high bits in permissions whose meaning is not known. FC_ASSERT( !(issuer_permissions & ~ASSET_ISSUER_PERMISSION_MASK) ); - // The global_settle flag may never be set (this is a permission only) - FC_ASSERT( !(flags & global_settle) ); + // The permission-only bits can not be set in flag + FC_ASSERT( !(flags & global_settle), + "Can not set global_settle flag, it is for issuer permission only" ); + // the witness_fed and committee_fed flags cannot be set simultaneously FC_ASSERT( (flags & (witness_fed_asset | committee_fed_asset)) != (witness_fed_asset | committee_fed_asset) ); core_exchange_rate.validate(); @@ -258,6 +286,12 @@ void asset_options::validate()const FC_ASSERT( *extensions.value.reward_percent <= GRAPHENE_100_PERCENT ); } +uint16_t asset_options::get_enabled_issuer_permissions_mask() const +{ + return ( (issuer_permissions & ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK) + | (~issuer_permissions & ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK) ); +} + void asset_claim_fees_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( amount_to_claim.amount > 0 ); @@ -278,6 +312,10 @@ 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_update_operation::ext ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_parameters_type ) @@ -292,6 +330,7 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_parameters_type ) + GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation ) diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index 674b130ec1..f5d7129dbb 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -205,10 +205,6 @@ namespace graphene { namespace protocol { /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. /// 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 ) @@ -226,10 +222,8 @@ namespace graphene { namespace protocol { FC_REFLECT( graphene::protocol::asset, (amount)(asset_id) ) FC_REFLECT( graphene::protocol::price, (base)(quote) ) -#define GRAPHENE_PRICE_FEED_FIELDS (settlement_price)(maintenance_collateral_ratio)(maximum_short_squeeze_ratio) \ - (core_exchange_rate) - -FC_REFLECT( graphene::protocol::price_feed, GRAPHENE_PRICE_FEED_FIELDS ) +FC_REFLECT( graphene::protocol::price_feed, + (settlement_price)(maintenance_collateral_ratio)(maximum_short_squeeze_ratio)(core_exchange_rate) ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::price ) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index fd4c53297c..574fb0dc74 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -58,10 +58,13 @@ namespace graphene { namespace protocol { share_type max_market_fee = GRAPHENE_MAX_SHARE_SUPPLY; /// The flags which the issuer has permission to update. See @ref asset_issuer_permission_flags - uint16_t issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + uint16_t issuer_permissions = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; /// The currently active flags on this permission. See @ref asset_issuer_permission_flags uint16_t flags = 0; + /// @return the bits in @ref flags which are allowed to be updated according to data in @ref issuer_permissions + uint16_t get_enabled_issuer_permissions_mask() const; + /// When a non-core asset is used to pay a fee, the blockchain must convert that asset to core asset in /// order to accept the fee. If this asset's fee pool is funded, the chain will automatically deposite fees /// in this asset to its accumulated fees, and withdraw from the fee pool the same amount as converted at @@ -107,6 +110,10 @@ namespace graphene { namespace protocol { /// 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; // BSIP-77 + /// After BSIP75, the asset owner can update MCR directly + fc::optional maintenance_collateral_ratio; // BSIP-75 + /// After BSIP75, the asset owner can update MSSR directly + fc::optional maximum_short_squeeze_ratio; // BSIP-75 fc::optional force_settle_fee_percent; // BSIP-87 }; @@ -294,6 +301,16 @@ namespace graphene { namespace protocol { */ struct asset_update_operation : public base_operation { + struct ext + { + /// After BSIP48, the precision of an asset can be updated if no supply is available + /// @note The parties involved still need to be careful + fc::optional new_precision; + /// After BSIP48, if this option is set to true, the asset's core_exchange_rate won't be updated. + /// This is especially useful for committee-owned bitassets which can not be updated quickly. + fc::optional skip_core_exchange_rate; + }; + struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; uint32_t price_per_kbyte = 10; @@ -308,7 +325,7 @@ namespace graphene { namespace protocol { /// If the asset is to be given a new issuer, specify his ID here. optional new_issuer; asset_options new_options; - extensions_type extensions; + extension extensions; account_id_type fee_payer()const { return issuer; } void validate()const; @@ -392,13 +409,19 @@ namespace graphene { namespace protocol { */ struct asset_publish_feed_operation : public base_operation { + struct ext + { + /// After BSIP77, price feed producers can feed ICR too + fc::optional initial_collateral_ratio; // BSIP-77 + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; ///< paid for by publisher account_id_type publisher; asset_id_type asset_id; ///< asset for which the feed is published price_feed feed; - extensions_type extensions; + extension extensions; account_id_type fee_payer()const { return publisher; } void validate()const; @@ -563,7 +586,12 @@ FC_REFLECT( graphene::protocol::asset_options, (extensions) ) -FC_REFLECT( graphene::protocol::bitasset_options::ext, (initial_collateral_ratio)(force_settle_fee_percent) ) +FC_REFLECT( graphene::protocol::bitasset_options::ext, + (initial_collateral_ratio) + (maintenance_collateral_ratio) + (maximum_short_squeeze_ratio) + (force_settle_fee_percent) + ) FC_REFLECT( graphene::protocol::bitasset_options, (feed_lifetime_sec) @@ -578,6 +606,9 @@ FC_REFLECT( graphene::protocol::bitasset_options, FC_REFLECT( graphene::protocol::additional_asset_options, (reward_percent)(whitelist_market_fee_sharing)(taker_fee_percent) ) +FC_REFLECT( graphene::protocol::asset_update_operation::ext, (new_precision)(skip_core_exchange_rate) ) +FC_REFLECT( graphene::protocol::asset_publish_feed_operation::ext, (initial_collateral_ratio) ) + FC_REFLECT( graphene::protocol::asset_create_operation::fee_parameters_type, (symbol3)(symbol4)(long_symbol)(price_per_kbyte) ) @@ -645,6 +676,10 @@ 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_update_operation::ext ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::ext ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation::fee_parameters_type ) @@ -659,6 +694,7 @@ GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_feed_p GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_publish_feed_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_issue_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_reserve_operation::fee_parameters_type ) + GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_create_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_global_settle_operation ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operation ) diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 567a1230bb..cb710e77e3 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -133,17 +133,58 @@ using chain_id_type = fc::sha256; using ratio_type = boost::rational; enum asset_issuer_permission_flags { - charge_market_fee = 0x01, /**< an issuer-specified percentage of all market trades in this asset is paid to the issuer */ - white_list = 0x02, /**< accounts must be whitelisted in order to hold this asset */ - override_authority = 0x04, /**< issuer may transfer asset back to himself */ - transfer_restricted = 0x08, /**< require the issuer to be one party to every transfer */ - disable_force_settle = 0x10, /**< disable force settling */ - global_settle = 0x20, /**< allow the bitasset issuer to force a global settling -- this may be set in permissions, but not flags */ - disable_confidential = 0x40, /**< allow the asset to be used with confidential transactions */ - witness_fed_asset = 0x80, /**< allow the asset to be fed by witnesses */ - committee_fed_asset = 0x100 /**< allow the asset to be fed by the committee */ + /// @note If one of these bits is set in asset issuer permissions, + /// it means the asset issuer (or owner for bitassets) has the permission to update + /// the corresponding flag, parameters or perform certain actions. + ///@{ + charge_market_fee = 0x01, ///< market trades in this asset may be charged + white_list = 0x02, ///< accounts must be whitelisted in order to hold or transact this asset + override_authority = 0x04, ///< issuer may transfer asset back to himself + transfer_restricted = 0x08, ///< require the issuer to be one party to every transfer + disable_force_settle = 0x10, ///< disable force settling + global_settle = 0x20, ///< allow the bitasset owner to force a global settling, permission only + disable_confidential = 0x40, ///< disallow the asset to be used with confidential transactions + witness_fed_asset = 0x80, ///< the bitasset is to be fed by witnesses + committee_fed_asset = 0x100, ///< the bitasset is to be fed by the committee + ///@} + /// @note If one of these bits is set in asset issuer permissions, + /// it means the asset issuer (or owner for bitassets) does NOT have the permission to update + /// the corresponding flag, parameters or perform certain actions. + /// This is to be compatible with old client software. + ///@{ + lock_max_supply = 0x200, ///< the max supply of the asset can not be updated + disable_new_supply = 0x400, ///< unable to create new supply for the asset + /// @note These parameters are for issuer permission only. + /// For each parameter, if it is set in issuer permission, + /// it means the bitasset owner can not update the corresponding parameter. + /// In this case, if the value of the parameter was set by the bitasset owner, it can not be updated; + /// if no value was set by the owner, the value can still be updated by the feed producers. + ///@{ + disable_mcr_update = 0x800, ///< the bitasset owner can not update MCR, permisison only + disable_icr_update = 0x1000, ///< the bitasset owner can not update ICR, permisison only + disable_mssr_update = 0x2000 ///< the bitasset owner can not update MSSR, permisison only + ///@} + ///@} }; -const static uint32_t ASSET_ISSUER_PERMISSION_MASK = + +// The bits that can be used in asset issuer permissions for non-UIA assets +const static uint16_t ASSET_ISSUER_PERMISSION_MASK = + charge_market_fee + | white_list + | override_authority + | transfer_restricted + | disable_force_settle + | global_settle + | disable_confidential + | witness_fed_asset + | committee_fed_asset + | lock_max_supply + | disable_new_supply + | disable_mcr_update + | disable_icr_update + | disable_mssr_update; +// The "enable" bits for non-UIA assets +const static uint16_t ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK = charge_market_fee | white_list | override_authority @@ -153,7 +194,24 @@ const static uint32_t ASSET_ISSUER_PERMISSION_MASK = | disable_confidential | witness_fed_asset | committee_fed_asset; -const static uint32_t UIA_ASSET_ISSUER_PERMISSION_MASK = +// The "disable" bits for non-UIA assets +const static uint16_t ASSET_ISSUER_PERMISSION_DISABLE_BITS_MASK = + lock_max_supply + | disable_new_supply + | disable_mcr_update + | disable_icr_update + | disable_mssr_update; +// The bits that can be used in asset issuer permissions for UIA assets +const static uint16_t UIA_ASSET_ISSUER_PERMISSION_MASK = + charge_market_fee + | white_list + | override_authority + | transfer_restricted + | disable_confidential + | lock_max_supply + | disable_new_supply; +// The bits that can be used in asset issuer permissions for UIA assets before hf48/75 +const static uint16_t DEFAULT_UIA_ASSET_ISSUER_PERMISSION = charge_market_fee | white_list | override_authority @@ -258,7 +316,13 @@ FC_REFLECT_ENUM(graphene::protocol::asset_issuer_permission_flags, (global_settle) (disable_confidential) (witness_fed_asset) - (committee_fed_asset)) + (committee_fed_asset) + (lock_max_supply) + (disable_new_supply) + (disable_mcr_update) + (disable_icr_update) + (disable_mssr_update) + ) namespace fc { namespace raw { extern template void pack( datastream& s, const graphene::protocol::public_key_type& tx, From ec02a5cd279b92c2b339ee49aef865e7b471eeea Mon Sep 17 00:00:00 2001 From: abitmore Date: Wed, 13 May 2020 22:35:06 -0400 Subject: [PATCH 02/29] Fix test cases --- tests/tests/custom_authority_tests.cpp | 16 ++++++++-------- tests/tests/operation_tests.cpp | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/tests/custom_authority_tests.cpp b/tests/tests/custom_authority_tests.cpp index e07aa10899..475aaa0c27 100644 --- a/tests/tests/custom_authority_tests.cpp +++ b/tests/tests/custom_authority_tests.cpp @@ -1299,11 +1299,11 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // Initialize: Create user-issued assets ////// upgrade_to_lifetime_member(assetissuer); - create_user_issued_asset("ACOIN1", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); - create_user_issued_asset("BCOIN1", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); - create_user_issued_asset("BCOIN2", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); - create_user_issued_asset("BCOIN3", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); - create_user_issued_asset("CCOIN1", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); + create_user_issued_asset("ACOIN1", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); + create_user_issued_asset("BCOIN1", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); + create_user_issued_asset("BCOIN2", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); + create_user_issued_asset("BCOIN3", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); + create_user_issued_asset("CCOIN1", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); generate_blocks(1); const asset_object &acoin1 = *db.get_index_type().indices().get().find("ACOIN1"); const asset_object &bcoin1 = *db.get_index_type().indices().get().find("BCOIN1"); @@ -2585,8 +2585,8 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // Initialize: Create user-issued assets ////// upgrade_to_lifetime_member(alice); - create_user_issued_asset("ALICECOIN", alice, UIA_ASSET_ISSUER_PERMISSION_MASK); - create_user_issued_asset( "SPECIALCOIN", alice, UIA_ASSET_ISSUER_PERMISSION_MASK); + create_user_issued_asset("ALICECOIN", alice, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); + create_user_issued_asset( "SPECIALCOIN", alice, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); generate_blocks(1); const asset_object &alicecoin = *db.get_index_type().indices().get().find("ALICECOIN"); const asset_object &specialcoin @@ -5261,7 +5261,7 @@ BOOST_AUTO_TEST_CASE(custom_auths) { try { // Initialize: Create user-issued assets ////// upgrade_to_lifetime_member(assetissuer); - create_user_issued_asset("SPECIALCOIN", assetissuer, UIA_ASSET_ISSUER_PERMISSION_MASK); + create_user_issued_asset("SPECIALCOIN", assetissuer, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); generate_blocks(1); const asset_object &specialcoin = *db.get_index_type().indices().get().find("SPECIALCOIN"); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b4fd6c89c9..211576b97e 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1515,7 +1515,7 @@ BOOST_AUTO_TEST_CASE( create_uia ) creator.common_options.max_supply = 100000000; creator.precision = 2; creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ - creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.issuer_permissions = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; creator.common_options.flags = charge_market_fee; creator.common_options.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); trx.operations.push_back(std::move(creator)); @@ -1576,7 +1576,7 @@ BOOST_AUTO_TEST_CASE( update_uia ) //Cannot convert to an MIA BOOST_TEST_MESSAGE( "Make sure we can't convert UIA to MIA" ); - REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, ASSET_ISSUER_PERMISSION_MASK); + REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK); REQUIRE_THROW_WITH_VALUE(op, new_options.core_exchange_rate, price(asset(5), asset(5))); BOOST_TEST_MESSAGE( "Test updating core_exchange_rate" ); @@ -1623,7 +1623,7 @@ BOOST_AUTO_TEST_CASE( update_uia ) op.new_options.issuer_permissions = test.options.issuer_permissions; op.new_options.flags = test.options.flags; BOOST_CHECK(!(test.options.issuer_permissions & white_list)); - REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, UIA_ASSET_ISSUER_PERMISSION_MASK); + REQUIRE_THROW_WITH_VALUE(op, new_options.issuer_permissions, DEFAULT_UIA_ASSET_ISSUER_PERMISSION); BOOST_TEST_MESSAGE( "We can change issuer to account_id_type(), but can't do it again" ); op.new_issuer = account_id_type(); From e698796f885697418433dae47a6153730ccadef4 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 May 2020 06:13:35 -0400 Subject: [PATCH 03/29] Add todo --- libraries/chain/asset_evaluator.cpp | 6 ++---- libraries/chain/asset_object.cpp | 1 + libraries/chain/include/graphene/chain/asset_object.hpp | 1 + libraries/protocol/include/graphene/protocol/asset.hpp | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 2b29951d42..4ea3820357 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -582,6 +582,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( !current_bitasset_data.has_settlement(), "Cannot update a bitasset after a global settlement has executed" ); + // TODO simplify code below when made sure operator==(optional,optional) works if( !asset_obj.can_owner_update_mcr() ) { // check if MCR will change @@ -591,7 +592,6 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_mcr.valid() && *old_mcr != *new_mcr ) ); FC_ASSERT( !mcr_changed, "No permission to update MCR" ); } - if( !asset_obj.can_owner_update_icr() ) { // check if ICR will change @@ -601,7 +601,6 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita || ( old_icr.valid() && *old_icr != *new_icr ) ); FC_ASSERT( !icr_changed, "No permission to update ICR" ); } - if( !asset_obj.can_owner_update_mssr() ) { // check if MSSR will change @@ -739,6 +738,7 @@ static bool update_bitasset_object_options( is_witness_or_committee_fed = true; } + // TODO simplify code below when made sure operator==(optional,optional) works // check if ICR will change if( !should_update_feeds ) { @@ -748,7 +748,6 @@ static bool update_bitasset_object_options( || ( old_icr.valid() && *old_icr != *new_icr ) ); should_update_feeds = icr_changed; } - // check if MCR will change if( !should_update_feeds ) { @@ -758,7 +757,6 @@ static bool update_bitasset_object_options( || ( old_mcr.valid() && *old_mcr != *new_mcr ) ); should_update_feeds = mcr_changed; } - // check if MSSR will change if( !should_update_feeds ) { diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index c2faa4269b..5145c347b4 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -130,6 +130,7 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { + // TODO move the 2 steps into one function if they're always done one after the other // update data derived from MCR current_maintenance_collateralization = current_feed.maintenance_collateralization(); // update data derived from ICR diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 9bd2c65bab..c8d18a7652 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -235,6 +235,7 @@ namespace graphene { namespace chain { /// Calculation: ~settlement_price * initial_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM price calculate_initial_collateralization()const; + // TODO remove friend bool operator == ( const price_feed_with_icr& a, const price_feed_with_icr& b ) { return static_cast(a) == static_cast(b) diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index f5d7129dbb..04da583106 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -207,6 +207,7 @@ namespace graphene { namespace protocol { price maintenance_collateralization()const; ///@} + // TODO rename since it doesn't compare all member variables thus is misleading friend bool operator == ( const price_feed& a, const price_feed& b ) { return std::tie( a.settlement_price, a.maintenance_collateral_ratio, a.maximum_short_squeeze_ratio ) == From 95b28204bee4acdafc4c05a62beefa0b7dbe510c Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 12 May 2020 21:46:53 -0400 Subject: [PATCH 04/29] Add functions to create proposals in db fixture --- tests/common/database_fixture.hpp | 44 +++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index df153fb195..b852a63e33 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -182,6 +182,14 @@ class clearable_block : public signed_block { void clear(); }; +namespace test { +/// set a reasonable expiration time for the transaction +void set_expiration( const database& db, transaction& tx ); + +bool _push_block( database& db, const signed_block& b, uint32_t skip_flags = 0 ); +processed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags = 0 ); +} // namespace test + struct database_fixture { // the reason we use an app is to exercise the indexes of built-in // plugins @@ -345,6 +353,34 @@ struct database_fixture { const fc::ecc::private_key& signing_private_key = generate_private_key("null_key"), uint32_t skip_flags = ~0); const worker_object& create_worker(account_id_type owner, const share_type daily_pay = 1000, const fc::microseconds& duration = fc::days(2)); + template + proposal_create_operation make_proposal_create_op( const T& op, account_id_type proposer = GRAPHENE_TEMP_ACCOUNT, + uint32_t timeout = 300, uint32_t review_period = 0 ) const + { + proposal_create_operation cop; + cop.fee_paying_account = proposer; + cop.expiration_time = db.head_block_time() + timeout; + cop.review_period_seconds = review_period; + cop.proposed_ops.emplace_back( op ); + for( auto& o : cop.proposed_ops ) db.current_fee_schedule().set_fee(o.op); + return cop; + } + template + const proposal_object& propose( const T& op, account_id_type proposer = GRAPHENE_TEMP_ACCOUNT, + uint32_t timeout = 300, uint32_t review_period = 0 ) + { + proposal_create_operation cop = make_proposal_create_op( op, proposer, timeout, review_period ); + trx.operations.clear(); + trx.operations.push_back( cop ); + for( auto& o : trx.operations ) db.current_fee_schedule().set_fee(o); + trx.validate(); + test::set_expiration( db, trx ); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const operation_result& op_result = ptx.operation_results.front(); + trx.operations.clear(); + verify_asset_supplies(db); + return db.get( op_result.get() ); + } uint64_t fund( const account_object& account, const asset& amount = asset(500000) ); digest_type digest( const transaction& tx ); void sign( signed_transaction& trx, const fc::ecc::private_key& key ); @@ -404,12 +440,4 @@ struct database_fixture { }; -namespace test { -/// set a reasonable expiration time for the transaction -void set_expiration( const database& db, transaction& tx ); - -bool _push_block( database& db, const signed_block& b, uint32_t skip_flags = 0 ); -processed_transaction _push_transaction( database& db, const signed_transaction& tx, uint32_t skip_flags = 0 ); -} - } } From 7d71ee0f8fb202d0ac4d4339fea0d1402be4b9f0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 May 2020 19:05:21 -0400 Subject: [PATCH 05/29] Add test case for bsip 48/75 hardfork protection --- tests/tests/bsip48_75_tests.cpp | 291 ++++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 tests/tests/bsip48_75_tests.cpp diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp new file mode 100644 index 0000000000..384aae2daf --- /dev/null +++ b/tests/tests/bsip48_75_tests.cpp @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2020 Abit More, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( bsip48_75_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( hardfork_protection_test ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_1270_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; + + uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set + uint16_t uiaflag = ~(bitmask ^ uiamask); // high bits are set + + vector ops; + + // Testing asset_create_operation + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = uiaflag; + acop.common_options.issuer_permissions = uiamask; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new permission bits + op.common_options.issuer_permissions = ( uiamask | lock_max_supply ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.common_options.issuer_permissions = ( uiamask | disable_new_supply ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.bitasset_opts = bitasset_options(); + op.bitasset_opts->minimum_feeds = 3; + op.common_options.flags = bitflag; + + op.common_options.issuer_permissions = ( bitmask | disable_mcr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.common_options.issuer_permissions = ( bitmask | disable_icr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.common_options.issuer_permissions = ( bitmask | disable_mssr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.common_options.issuer_permissions = bitmask; + + // Unable to set new extensions in bitasset options + op.bitasset_opts->extensions.value.maintenance_collateral_ratio = 1500; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.maintenance_collateral_ratio = {}; + + op.bitasset_opts->extensions.value.maximum_short_squeeze_ratio = 1500; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.bitasset_opts->extensions.value.maximum_short_squeeze_ratio = {}; + + acop = op; + } + + // Able to create asset without new data + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 100 ); + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 3 ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + ops.clear(); + // Able to propose the good operation + propose( acop ); + + // Testing asset_update_operation + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.market_fee_percent = 200; + op.new_options.flags &= ~witness_fed_asset; + + // Unable to set new permission bits + op.new_options.issuer_permissions = ( bitmask | lock_max_supply ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.new_options.issuer_permissions = ( bitmask | disable_new_supply ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.new_options.issuer_permissions = ( bitmask | disable_mcr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.new_options.issuer_permissions = ( bitmask | disable_icr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.new_options.issuer_permissions = ( bitmask | disable_mssr_update ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + + op.new_options.issuer_permissions = bitmask; + + // Unable to set new extensions + op.extensions.value.new_precision = 8; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.extensions.value.new_precision = {}; + + op.extensions.value.skip_core_exchange_rate = true; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.extensions.value.skip_core_exchange_rate = {}; + + auop = op; + } + + // Able to update asset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.options.market_fee_percent, 200 ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + ops.clear(); + // Able to propose the good operation + propose( auop ); + + + // Testing asset_update_bitasset_operation + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = samcoin_id; + aubop.new_options = samcoin_id(db).bitasset_data(db).options; + + trx.operations.clear(); + trx.operations.push_back( aubop ); + + { + auto& op = trx.operations.front().get(); + op.new_options.minimum_feeds = 1; + + // Unable to set new extensions + op.new_options.extensions.value.maintenance_collateral_ratio = 1500; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.maintenance_collateral_ratio = {}; + + op.new_options.extensions.value.maximum_short_squeeze_ratio = 1500; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.new_options.extensions.value.maximum_short_squeeze_ratio = {}; + + aubop = op; + } + + // Able to update bitasset without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).options.minimum_feeds, 1 ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + ops.clear(); + // Able to propose the good operation + propose( aubop ); + + // Testing asset_publish_feed_operation + update_feed_producers( samcoin, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,samcoin_id), asset(1) ); + f.core_exchange_rate = price( asset(1,samcoin_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + + asset_publish_feed_operation apfop; + apfop.publisher = feeder_id; + apfop.asset_id = samcoin_id; + apfop.feed = f; + + trx.operations.clear(); + trx.operations.push_back( apfop ); + + { + auto& op = trx.operations.front().get(); + + // Unable to set new extensions + op.extensions.value.initial_collateral_ratio = 1500; + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + ops.push_back( op ); + op.extensions.value.initial_collateral_ratio = {}; + + apfop = op; + } + + // Able to publish feed without new data + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin.bitasset_data(db).current_feed.initial_collateral_ratio, + f.maintenance_collateral_ratio ); + + // Unable to propose the invalid operations + for( const operation& op : ops ) + BOOST_CHECK_THROW( propose( op ), fc::exception ); + ops.clear(); + // Able to propose the good operation + propose( apfop ); + + // Check what we have now + idump( (samcoin) ); + idump( (samcoin.bitasset_data(db)) ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_SUITE_END() + From 7330afe104f8d784c62f2e496521d91d83d36ce1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 14 May 2020 21:41:20 -0400 Subject: [PATCH 06/29] Fix validation of asset flags --- libraries/chain/asset_evaluator.cpp | 37 +++++++++++-------- libraries/chain/proposal_evaluator.cpp | 11 ++++++ libraries/protocol/asset_ops.cpp | 18 +++++++++ .../include/graphene/protocol/asset_ops.hpp | 4 ++ .../include/graphene/protocol/types.hpp | 13 +++++++ 5 files changed, 67 insertions(+), 16 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 4ea3820357..b260f05fee 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -154,22 +154,10 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o detail::check_bitasset_options_hf_bsip87( now, *op.bitasset_opts ); // HF_REMOVABLE } - // TODO move the assertions to asset_options::validate() if not triggered before hardfork + // TODO move as many validations as possible to validate() if not triggered before hardfork if( HARDFORK_BSIP_48_75_PASSED( now ) ) { - FC_ASSERT( !(op.common_options.flags & ~ASSET_ISSUER_PERMISSION_MASK), - "Can not set an unknown bit in flags" ); - FC_ASSERT( !(op.common_options.flags & disable_mcr_update), - "Can not set disable_mcr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(op.common_options.flags & disable_icr_update), - "Can not set disable_icr_update flag, it is for issuer permission only" ); - FC_ASSERT( !(op.common_options.flags & disable_mssr_update), - "Can not set disable_mssr_update flag, it is for issuer permission only" ); - if( !op.bitasset_opts.valid() ) - { - FC_ASSERT( !(op.common_options.flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), - "Can not set a flag for bitassets only to UIA" ); - } + op.common_options.validate_flags( op.bitasset_opts.valid() ); } const auto& chain_parameters = d.get_global_properties().parameters; @@ -420,9 +408,26 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) "Cannot update precision if current supply is non-zero" ); } + // TODO move as many validations as possible to validate() if not triggered before hardfork + if( HARDFORK_BSIP_48_75_PASSED( now ) ) + { + o.new_options.validate_flags( a.is_market_issued() ); + } + // changed flags must be subset of old issuer permissions - FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & ~enabled_issuer_permissions_mask), - "Flag change is forbidden by issuer permissions" ); + if( HARDFORK_BSIP_48_75_PASSED( now ) ) + { + // Note: if an invalid bit was set, it can be unset regardless of the permissions + uint16_t check_bits = ( a.is_market_issued() ? VALID_FLAGS_MASK : UIA_VALID_FLAGS_MASK ); + + FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & check_bits & ~enabled_issuer_permissions_mask), + "Flag change is forbidden by issuer permissions" ); + } + else + { + FC_ASSERT( !((o.new_options.flags ^ a.options.flags) & ~a.options.issuer_permissions), + "Flag change is forbidden by issuer permissions" ); + } asset_to_update = &a; FC_ASSERT( o.issuer == a.issuer, diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index b151e4d73d..593503e0aa 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -68,6 +68,11 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip87( block_time, *v.bitasset_opts ); // HF_REMOVABLE } + // TODO move as many validations as possible to validate() if not triggered before hardfork + if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + v.common_options.validate_flags( v.bitasset_opts.valid() ); + } } void operator()(const graphene::chain::asset_update_operation &v) const { @@ -77,6 +82,12 @@ struct proposal_operation_hardfork_visitor detail::check_asset_options_hf_bsip81(block_time, v.new_options); detail::check_asset_update_extensions_hf_bsip_48_75( block_time, v.extensions.value ); + // TODO move as many validations as possible to validate() if not triggered before hardfork + if( HARDFORK_BSIP_48_75_PASSED( block_time ) ) + { + v.new_options.validate_flags( true ); + } + } void operator()(const graphene::chain::asset_update_bitasset_operation &v) const { diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 831aafb060..e23c536096 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -286,6 +286,24 @@ void asset_options::validate()const FC_ASSERT( *extensions.value.reward_percent <= GRAPHENE_100_PERCENT ); } +void asset_options::validate_flags( bool is_market_issued )const +{ + FC_ASSERT( !(flags & ~ASSET_ISSUER_PERMISSION_MASK), + "Can not set an unknown bit in flags" ); + // Note: global_settle is checked in validate(), so do not check again here + FC_ASSERT( !(flags & disable_mcr_update), + "Can not set disable_mcr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(flags & disable_icr_update), + "Can not set disable_icr_update flag, it is for issuer permission only" ); + FC_ASSERT( !(flags & disable_mssr_update), + "Can not set disable_mssr_update flag, it is for issuer permission only" ); + if( !is_market_issued ) + { + FC_ASSERT( !(flags & ~UIA_ASSET_ISSUER_PERMISSION_MASK), + "Can not set a flag for bitassets only to UIA" ); + } +} + uint16_t asset_options::get_enabled_issuer_permissions_mask() const { return ( (issuer_permissions & ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index 574fb0dc74..cc75725ef2 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -95,6 +95,10 @@ namespace graphene { namespace protocol { /// Perform internal consistency checks. /// @throws fc::exception if any check fails void validate()const; + + /// Perform checks about @ref flags. + /// @throws fc::exception if any check fails + void validate_flags( bool is_market_issued )const; }; /** diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index cb710e77e3..fa4fb4ae9a 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -217,6 +217,19 @@ const static uint16_t DEFAULT_UIA_ASSET_ISSUER_PERMISSION = | override_authority | transfer_restricted | disable_confidential; +// The bits that can be used in asset issuer permissions for non-UIA assets but not for UIA assets +const static uint16_t NON_UIA_ONLY_ISSUER_PERMISSION_MASK = + ASSET_ISSUER_PERMISSION_MASK ^ UIA_ASSET_ISSUER_PERMISSION_MASK; +// The bits that can be used in asset issuer permissions but can not be used in flags +const static uint16_t PERMISSION_ONLY_MASK = + global_settle + | disable_mcr_update + | disable_icr_update + | disable_mssr_update; +// The bits that can be used in flags for non-UIA assets +const static uint16_t VALID_FLAGS_MASK = ASSET_ISSUER_PERMISSION_MASK & ~PERMISSION_ONLY_MASK; +// the bits that can be used in flags for UIA assets +const static uint16_t UIA_VALID_FLAGS_MASK = UIA_ASSET_ISSUER_PERMISSION_MASK; enum reserved_spaces { relative_protocol_ids = 0, From 31746d3dc9611eeb64557b1501baa81cb46ce938 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 16:26:52 -0400 Subject: [PATCH 07/29] Add limitations about updating asset precision --- libraries/chain/asset_evaluator.cpp | 19 +++++++++++++++++-- .../graphene/chain/asset_evaluator.hpp | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index b260f05fee..1a29675279 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -392,7 +392,7 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( o.new_issuer ) { - FC_ASSERT( now < HARDFORK_CORE_199_TIME, + FC_ASSERT( now < HARDFORK_CORE_199_TIME, "Since Hardfork #199, updating issuer requires the use of asset_update_issuer_operation."); validate_new_issuer( d, a, *o.new_issuer ); } @@ -441,6 +441,21 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) { FC_ASSERT( *o.extensions.value.new_precision != a.precision, "Specified a new precision but it does not change" ); + + if( a.is_market_issued() ) + { + bitasset_data = &asset_to_update->bitasset_data(d); + FC_ASSERT( !bitasset_data->is_prediction_market, "Can not update precision of a prediction market" ); + } + + // If any other asset is backed by this asset, this asset's precision can't be updated + const auto& idx = d.get_index_type() + .indices().get(); + auto itr = idx.lower_bound( o.asset_to_update ); + bool backing_another_asset = ( itr != idx.end() && itr->options.short_backing_asset == o.asset_to_update ); + FC_ASSERT( !backing_another_asset, + "Asset ${a} is backed by this asset, can not update precision", + ("a",itr->asset_id) ); } const auto& chain_parameters = d.get_global_properties().parameters; @@ -475,7 +490,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) if( !o.extensions.value.skip_core_exchange_rate.valid() && asset_to_update->is_market_issued() && asset_to_update->options.core_exchange_rate != o.new_options.core_exchange_rate ) { - const auto& bitasset = asset_to_update->bitasset_data(d); + const auto& bitasset = ( bitasset_data ? *bitasset_data : asset_to_update->bitasset_data(d) ); if( !bitasset.asset_cer_updated ) { d.modify( bitasset, [](asset_bitasset_data_object& b) diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 068f2cf93e..579ff94a0f 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -79,6 +79,7 @@ namespace graphene { namespace chain { void_result do_apply( const asset_update_operation& o ); const asset_object* asset_to_update = nullptr; + const asset_bitasset_data_object* bitasset_data = nullptr; }; class asset_update_issuer_evaluator : public evaluator From 77f9ff6bd90d7e1b13d32cc784a41ef395cd6fb6 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 16:48:30 -0400 Subject: [PATCH 08/29] Fix a bug about updating maximum supply of assets The new max supply should not be lower than the asset's current supply --- libraries/chain/asset_evaluator.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 1a29675279..abcb753aa6 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -385,6 +385,8 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) detail::check_asset_options_hf_bsip81(now, o.new_options); detail::check_asset_update_extensions_hf_bsip_48_75( now, o.extensions.value ); + bool hf_bsip_48_75_passed = ( HARDFORK_BSIP_48_75_PASSED( now ) ); + const asset_object& a = o.asset_to_update(d); auto a_copy = a; a_copy.options = o.new_options; @@ -397,8 +399,9 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } + const auto& dyn_data = a.dynamic_asset_data_id(d); uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); - if( a.dynamic_asset_data_id(d).current_supply != 0 ) + if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions FC_ASSERT(!(o.new_options.get_enabled_issuer_permissions_mask() & ~enabled_issuer_permissions_mask), @@ -406,16 +409,22 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) // precision can not be changed FC_ASSERT( !o.extensions.value.new_precision.valid(), "Cannot update precision if current supply is non-zero" ); + + if( hf_bsip_48_75_passed ) // TODO review after hard fork, probably can assert unconditionally + { + FC_ASSERT( dyn_data.current_supply <= o.new_options.max_supply, + "Max supply should not be smaller than current supply" ); + } } // TODO move as many validations as possible to validate() if not triggered before hardfork - if( HARDFORK_BSIP_48_75_PASSED( now ) ) + if( hf_bsip_48_75_passed ) { o.new_options.validate_flags( a.is_market_issued() ); } // changed flags must be subset of old issuer permissions - if( HARDFORK_BSIP_48_75_PASSED( now ) ) + if( hf_bsip_48_75_passed ) { // Note: if an invalid bit was set, it can be unset regardless of the permissions uint16_t check_bits = ( a.is_market_issued() ? VALID_FLAGS_MASK : UIA_VALID_FLAGS_MASK ); From ca12bbe428785099d1b5b6def77323b6114a5cc1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 17:15:55 -0400 Subject: [PATCH 09/29] Requires global_settle permission enabled for PM The global_settle permission should always be enabled for PM --- libraries/chain/asset_evaluator.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index abcb753aa6..29324ca247 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -399,8 +399,20 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) validate_new_issuer( d, a, *o.new_issuer ); } - const auto& dyn_data = a.dynamic_asset_data_id(d); uint16_t enabled_issuer_permissions_mask = a.options.get_enabled_issuer_permissions_mask(); + if( hf_bsip_48_75_passed && a.is_market_issued() ) + { + bitasset_data = &a.bitasset_data(d); + if( bitasset_data->is_prediction_market ) + { + // Note: if the global_settle permission was unset, it should be corrected + FC_ASSERT( a_copy.can_global_settle(), + "The global_settle permission should be enabled for prediction markets" ); + enabled_issuer_permissions_mask |= global_settle; + } + } + + const auto& dyn_data = a.dynamic_asset_data_id(d); if( dyn_data.current_supply != 0 ) { // new issuer_permissions must be subset of old issuer permissions @@ -453,7 +465,8 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o) if( a.is_market_issued() ) { - bitasset_data = &asset_to_update->bitasset_data(d); + if( !bitasset_data ) + bitasset_data = &asset_to_update->bitasset_data(d); FC_ASSERT( !bitasset_data->is_prediction_market, "Can not update precision of a prediction market" ); } @@ -495,7 +508,7 @@ void_result asset_update_evaluator::do_apply(const asset_update_operation& o) d.cancel_settle_order(*itr); } - // For market-issued assets, if core change rate changed, update flag in bitasset data + // For market-issued assets, if core exchange rate changed, update flag in bitasset data if( !o.extensions.value.skip_core_exchange_rate.valid() && asset_to_update->is_market_issued() && asset_to_update->options.core_exchange_rate != o.new_options.core_exchange_rate ) { From 5f87be4709f25d12d290735a1866cc6077a3db02 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 19:13:23 -0400 Subject: [PATCH 10/29] Remove equality function of price_feed struct The old equality function of the price_feed struct was misleading since it did not compare all member variables. This commit replaces it with a new member function. --- libraries/chain/asset_evaluator.cpp | 8 ++++---- libraries/chain/db_update.cpp | 3 ++- .../chain/include/graphene/chain/asset_object.hpp | 7 ------- .../protocol/include/graphene/protocol/asset.hpp | 14 +++++++++----- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 29324ca247..b678335b0b 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -832,11 +832,11 @@ static bool update_bitasset_object_options( if( should_update_feeds ) { - const price_feed old_feed = static_cast( bdo.current_feed ); + const auto old_feed = bdo.current_feed; bdo.update_median_feeds( db.head_block_time(), next_maint_time ); // 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 == static_cast( bdo.current_feed ) ) ); + return ( after_hf_core_868_890 && !old_feed.margin_call_params_equal( bdo.current_feed ) ); } return false; @@ -1119,7 +1119,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope const asset_object& base = *asset_ptr; const asset_bitasset_data_object& bad = *bitasset_ptr; - auto old_feed = bad.current_feed; + auto old_feed = bad.current_feed; // Store medians for this asset d.modify( bad , [&o,head_time,next_maint_time](asset_bitasset_data_object& a) { a.feeds[o.publisher] = make_pair( head_time, price_feed_with_icr( o.feed, @@ -1127,7 +1127,7 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope a.update_median_feeds( head_time, next_maint_time ); }); - if( !(old_feed == bad.current_feed) ) + if( !old_feed.margin_call_params_equal(bad.current_feed) ) { // Check whether need to revive the asset and proceed if need if( bad.has_settlement() // has globally settled, implies head_block_time > HARDFORK_CORE_216_TIME diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index f98f09de6b..9e2f9eac5e 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -508,7 +508,8 @@ void database::update_expired_feeds() abdo.feed_cer_updated = false; } }); - if( !b.current_feed.settlement_price.is_null() && !( b.current_feed == old_median_feed ) ) // `==` check is safe here + if( !b.current_feed.settlement_price.is_null() + && !b.current_feed.margin_call_params_equal( old_median_feed ) ) { asset_ptr = &b.asset_id( *this ); check_call_orders( *asset_ptr, true, false, &b ); diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index c8d18a7652..8077bec02c 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -234,13 +234,6 @@ namespace graphene { namespace chain { /// 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()const; - - // TODO remove - friend bool operator == ( const price_feed_with_icr& a, const price_feed_with_icr& b ) - { - return static_cast(a) == static_cast(b) - && a.initial_collateral_ratio == b.initial_collateral_ratio; - } }; /** diff --git a/libraries/protocol/include/graphene/protocol/asset.hpp b/libraries/protocol/include/graphene/protocol/asset.hpp index 04da583106..efdcc4eac1 100644 --- a/libraries/protocol/include/graphene/protocol/asset.hpp +++ b/libraries/protocol/include/graphene/protocol/asset.hpp @@ -202,17 +202,21 @@ namespace graphene { namespace protocol { /// Another implementation of max_short_squeeze_price() before the core-1270 hard fork price max_short_squeeze_price_before_hf_1270()const; - /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. + /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call + /// territory. /// Calculation: ~settlement_price * maintenance_collateral_ratio / GRAPHENE_COLLATERAL_RATIO_DENOM price maintenance_collateralization()const; - ///@} - // TODO rename since it doesn't compare all member variables thus is misleading - friend bool operator == ( const price_feed& a, const price_feed& b ) + /// Whether the parameters that affect margin calls in this price feed object are the same as the parameters + /// in the passed-in object + bool margin_call_params_equal( const price_feed& b ) const { - return std::tie( a.settlement_price, a.maintenance_collateral_ratio, a.maximum_short_squeeze_ratio ) == + if( this == &b ) + return true; + return std::tie( settlement_price, maintenance_collateral_ratio, maximum_short_squeeze_ratio ) == std::tie( b.settlement_price, b.maintenance_collateral_ratio, b.maximum_short_squeeze_ratio ); } + ///@} void validate() const; bool is_for( asset_id_type asset_id ) const; From dd1f16935e747c9e5f76999e57513fefcae6c98d Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 20:48:12 -0400 Subject: [PATCH 11/29] Add test case for PM global_settle permission bug --- tests/tests/bsip48_75_tests.cpp | 75 +++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 384aae2daf..20ca5f7885 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -287,5 +287,80 @@ BOOST_AUTO_TEST_CASE( hardfork_protection_test ) } } +BOOST_AUTO_TEST_CASE( prediction_market_global_settle_permission ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_1270_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + + // create a prediction market + const asset_object& pm = create_prediction_market( "PDM", sam_id ); + asset_id_type pm_id = pm.id; + + BOOST_CHECK( pm_id(db).can_global_settle() ); + + // disable global_settle permission + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + auop.new_options.issuer_permissions &= ~global_settle; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !pm_id(db).can_global_settle() ); + + // create some supply + borrow( sam, asset(100, pm_id), asset(100) ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + + // try to enable global_settle again, should fail + auop.new_options.issuer_permissions |= global_settle; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + BOOST_CHECK( !pm_id(db).can_global_settle() ); + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + BOOST_CHECK( !pm_id(db).can_global_settle() ); + + // try to update the asset without enabling global_settle permission, should fail + auop.new_options.issuer_permissions &= ~global_settle; + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !pm_id(db).can_global_settle() ); + + // try to enable global_settle again, should succeed + auop.new_options.issuer_permissions |= global_settle; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( pm_id(db).can_global_settle() ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 579638045251352c441783a2976bb7710cd4ebd0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 20:49:04 -0400 Subject: [PATCH 12/29] Update tests about price_feed equality function --- tests/tests/basic_tests.cpp | 24 +++++++++++++++++++- tests/tests/bitasset_tests.cpp | 2 +- tests/tests/operation_tests2.cpp | 2 +- tests/tests/simple_maker_taker_fee_tests.cpp | 2 +- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/tests/tests/basic_tests.cpp b/tests/tests/basic_tests.cpp index 8fe1e28e55..3139b1928c 100644 --- a/tests/tests/basic_tests.cpp +++ b/tests/tests/basic_tests.cpp @@ -291,7 +291,29 @@ BOOST_AUTO_TEST_CASE( price_test ) dummy.maximum_short_squeeze_ratio = 1234; dummy.settlement_price = price(asset(1000), asset(2000, asset_id_type(1))); price_feed dummy2 = dummy; - BOOST_CHECK(dummy == dummy2); + price_feed dummy3 = dummy; + dummy3.core_exchange_rate = price( asset(11), asset(13, asset_id_type(1)) ); + BOOST_CHECK( dummy.margin_call_params_equal( dummy ) ); + BOOST_CHECK( dummy.margin_call_params_equal( dummy2 ) ); + BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) ); + dummy.maximum_short_squeeze_ratio = 1235; + BOOST_CHECK( dummy.margin_call_params_equal( dummy ) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy2 ) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) ); + dummy2.maximum_short_squeeze_ratio = 1235; + BOOST_CHECK( dummy.margin_call_params_equal( dummy ) ); + BOOST_CHECK( dummy.margin_call_params_equal( dummy2 ) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) ); + dummy2.maintenance_collateral_ratio = 1003; + BOOST_CHECK( dummy.margin_call_params_equal( dummy ) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy2 ) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) ); + dummy3.maximum_short_squeeze_ratio = 1235; + BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) ); + dummy3.settlement_price = price( asset(1), asset(3, asset_id_type(1)) ); + BOOST_CHECK( !dummy.margin_call_params_equal( dummy3 ) ); + dummy3.settlement_price = price( asset(1), asset(2, asset_id_type(1)) ); + BOOST_CHECK( dummy.margin_call_params_equal( dummy3 ) ); } BOOST_AUTO_TEST_CASE( price_multiplication_test ) diff --git a/tests/tests/bitasset_tests.cpp b/tests/tests/bitasset_tests.cpp index 8174628e31..9af6fa5e71 100644 --- a/tests/tests/bitasset_tests.cpp +++ b/tests/tests/bitasset_tests.cpp @@ -334,7 +334,7 @@ BOOST_AUTO_TEST_CASE( reset_backing_asset_on_non_witness_asset ) BOOST_TEST_MESSAGE("Verify feed producers are registered for JMJBIT"); const asset_bitasset_data_object& obj = bit_jmj_id(db).bitasset_data(db); BOOST_CHECK_EQUAL(obj.feeds.size(), 3ul); - BOOST_CHECK(obj.current_feed == price_feed()); + BOOST_CHECK( obj.current_feed.margin_call_params_equal( price_feed() ) ); BOOST_CHECK( bit_usd_id == obj.options.short_backing_asset ); } diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index bb7d153a53..e99182f02e 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -742,7 +742,7 @@ BOOST_AUTO_TEST_CASE( mia_feeds ) { const asset_bitasset_data_object& obj = bit_usd_id(db).bitasset_data(db); BOOST_CHECK_EQUAL(obj.feeds.size(), 3u); - BOOST_CHECK(obj.current_feed == price_feed()); + BOOST_CHECK( obj.current_feed.margin_call_params_equal( price_feed() ) ); } { const asset_object& bit_usd = bit_usd_id(db); diff --git a/tests/tests/simple_maker_taker_fee_tests.cpp b/tests/tests/simple_maker_taker_fee_tests.cpp index 97a609b634..90cc65568c 100644 --- a/tests/tests/simple_maker_taker_fee_tests.cpp +++ b/tests/tests/simple_maker_taker_fee_tests.cpp @@ -1949,4 +1949,4 @@ BOOST_FIXTURE_TEST_SUITE(simple_maker_taker_fee_tests, simple_maker_taker_databa } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file +BOOST_AUTO_TEST_SUITE_END() From 7b6936d281da6c6be97ff2f6cc639ce33dae085e Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 21:21:45 -0400 Subject: [PATCH 13/29] Slightly refactor code about ICR and MCR handling Calculate and update asset_object::current_*_collateralization in one function --- libraries/chain/asset_object.cpp | 35 +++++++------------ .../include/graphene/chain/asset_object.hpp | 14 ++++---- 2 files changed, 20 insertions(+), 29 deletions(-) diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 5145c347b4..e85a951a4e 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -70,10 +70,8 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin current_feed = price_feed_with_icr(); if( after_core_hardfork_1270 ) { - // update data derived from MCR - current_maintenance_collateralization = price(); - // update data derived from ICR - current_initial_collateralization = price(); + // update data derived from MCR, ICR and etc + refresh_cache(); } return; } @@ -92,10 +90,8 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin current_feed.maximum_short_squeeze_ratio = *exts.maximum_short_squeeze_ratio; if( exts.initial_collateral_ratio.valid() ) current_feed.initial_collateral_ratio = *exts.initial_collateral_ratio; - // update data derived from MCR - current_maintenance_collateralization = current_feed.maintenance_collateralization(); - // update data derived from ICR - refresh_current_initial_collateralization(); + // update data derived from MCR, ICR and etc + refresh_cache(); } return; } @@ -130,25 +126,18 @@ void graphene::chain::asset_bitasset_data_object::update_median_feeds( time_poin // Note: perhaps can defer updating current_maintenance_collateralization for better performance if( after_core_hardfork_1270 ) { - // TODO move the 2 steps into one function if they're always done one after the other - // update data derived from MCR - current_maintenance_collateralization = current_feed.maintenance_collateralization(); - // update data derived from ICR - refresh_current_initial_collateralization(); + // update data derived from MCR, ICR and etc + refresh_cache(); } } -void asset_bitasset_data_object::refresh_current_initial_collateralization() +void asset_bitasset_data_object::refresh_cache() { - if( current_feed.settlement_price.is_null() ) - current_initial_collateralization = price(); - else - { - if( current_feed.initial_collateral_ratio > current_feed.maintenance_collateral_ratio ) // if ICR is above MCR - current_initial_collateralization = current_feed.calculate_initial_collateralization(); - else // if ICR is not above MCR - current_initial_collateralization = current_maintenance_collateralization; - } + current_maintenance_collateralization = current_feed.maintenance_collateralization(); + if( current_feed.initial_collateral_ratio > current_feed.maintenance_collateral_ratio ) // if ICR is above MCR + current_initial_collateralization = current_feed.calculate_initial_collateralization(); + else // if ICR is not above MCR + current_initial_collateralization = current_maintenance_collateralization; } price price_feed_with_icr::calculate_initial_collateralization()const diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index 8077bec02c..917f6a1f7a 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -263,18 +263,20 @@ namespace graphene { namespace chain { price_feed_with_icr current_feed; /// This is the publication time of the oldest feed which was factored into current_feed. time_point_sec current_feed_publication_time; - /// Call orders with collateralization (aka collateral/debt) not greater than this value are in margin call territory. + + /// 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. + /// This value is derived from @ref current_feed (which includes `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(); + /// Derive @ref current_maintenance_collateralization and @ref current_initial_collateralization from + /// other member variables. + void refresh_cache(); /// True if this asset implements a @ref prediction_market bool is_prediction_market = false; From c48295dd4d49fd37ec77973dcda3bb254ff8368e Mon Sep 17 00:00:00 2001 From: abitmore Date: Sat, 16 May 2020 21:40:46 -0400 Subject: [PATCH 14/29] Add test case about updating an asset's max_supply --- tests/tests/bsip48_75_tests.cpp | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 20ca5f7885..b2a1651a7b 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -362,5 +362,67 @@ BOOST_AUTO_TEST_CASE( prediction_market_global_settle_permission ) } } +BOOST_AUTO_TEST_CASE( update_max_supply ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_1270_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)); + + // create a UIA + const asset_object& uia = create_user_issued_asset( "UIATEST", sam, charge_market_fee ); + asset_id_type uia_id = uia.id; + + // issue all to Sam + issue_uia( sam_id, uia.amount(uia.options.max_supply) ); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + + // update max supply to a smaller number + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.new_options.max_supply -= 1; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value + 1 ); + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value + 1 ); + + // able to set max supply to be equal to current supply + auop.new_options.max_supply += 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + + // no longer able to set max supply to a number smaller than current supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 33b12346a624b2fb066405599e6888671fb58c28 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 17:50:47 -0400 Subject: [PATCH 15/29] Add test case about lock_max_supply flag --- tests/tests/bsip48_75_tests.cpp | 142 ++++++++++++++++++++++++++++++-- 1 file changed, 136 insertions(+), 6 deletions(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index b2a1651a7b..2950e743e6 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -378,29 +378,35 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) asset_id_type uia_id = uia.id; // issue all to Sam - issue_uia( sam_id, uia.amount(uia.options.max_supply) ); + issue_uia( sam_id, uia.amount( GRAPHENE_MAX_SHARE_SUPPLY - 100 ) ); - BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); // update max supply to a smaller number asset_update_operation auop; auop.issuer = sam_id; auop.asset_to_update = uia_id; auop.new_options = uia_id(db).options; - auop.new_options.max_supply -= 1; + auop.new_options.max_supply -= 101; trx.operations.clear(); trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value + 1 ); + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply < current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 101 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); // advance to bsip48/75 hard fork generate_blocks( HARDFORK_BSIP_48_75_TIME ); set_expiration( db, trx ); BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value + 1 ); + BOOST_CHECK( uia_id(db).can_update_max_supply() ); // able to set max supply to be equal to current supply auop.new_options.max_supply += 1; @@ -408,15 +414,139 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) trx.operations.push_back( auop ); PUSH_TX(db, trx, ~0); - BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply == current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); // no longer able to set max supply to a number smaller than current supply auop.new_options.max_supply -= 1; trx.operations.clear(); trx.operations.push_back( auop ); BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.max_supply += 1; + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply == current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // increase max supply again + auop.new_options.max_supply += 2; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // decrease max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // update flag to disable updating of max supply + auop.new_options.flags |= lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // unable to update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.max_supply += 1; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // update flag to enable updating of max supply + auop.new_options.flags &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // able to update max supply + auop.new_options.max_supply += 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // update flag to disable updating of max supply + auop.new_options.flags |= lock_max_supply; + // update permission to disable updating of lock_max_supply flag + auop.new_options.issuer_permissions |= lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // unable to reinstall the permission + auop.new_options.issuer_permissions &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= lock_max_supply; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + + // unable to update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.max_supply += 1; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); - BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, uia_id(db).options.max_supply.value ); + // able to update other parameters + auto old_market_fee_percent = auop.new_options.market_fee_percent; + BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, old_market_fee_percent ); + + auop.new_options.market_fee_percent = 120u; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, 120u ); + + generate_block(); } catch (fc::exception& e) { edump((e.to_detail_string())); From f27730616983e1a467bbda1c3e6a65df3dc69718 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 18:18:40 -0400 Subject: [PATCH 16/29] Add function to reserve asset in db fixture --- tests/common/database_fixture.cpp | 14 ++++++++++++++ tests/common/database_fixture.hpp | 1 + 2 files changed, 15 insertions(+) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 0958e5e331..8932150b19 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -845,6 +845,20 @@ void database_fixture::issue_uia( account_id_type recipient_id, asset amount ) issue_uia( recipient_id(db), amount ); } +void database_fixture::reserve_asset( account_id_type account, asset amount ) +{ + BOOST_TEST_MESSAGE( "Reserving asset" ); + asset_reserve_operation op; + op.payer = account; + op.amount_to_reserve = amount; + trx.operations.clear(); + trx.operations.push_back(op); + set_expiration( db, trx ); + trx.validate(); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); +} + void database_fixture::change_fees( const fee_parameters::flat_set_type& new_params, uint32_t new_scale /* = 0 */ diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index b852a63e33..bb92910f4d 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -323,6 +323,7 @@ struct database_fixture { additional_asset_options_t options = additional_asset_options_t()); void issue_uia( const account_object& recipient, asset amount ); void issue_uia( account_id_type recipient_id, asset amount ); + void reserve_asset( account_id_type account, asset amount ); const account_object& create_account( const string& name, From 2e77e4cc23e1f5e88abfbe8fe4b119d9684b0aae Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 18:19:42 -0400 Subject: [PATCH 17/29] Add more test cases about lock_max_supply flag --- tests/tests/bsip48_75_tests.cpp | 134 +++++++++++++++++++++++++++++++- 1 file changed, 133 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 2950e743e6..6579f182c2 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -377,7 +377,7 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) const asset_object& uia = create_user_issued_asset( "UIATEST", sam, charge_market_fee ); asset_id_type uia_id = uia.id; - // issue all to Sam + // issue some to Sam issue_uia( sam_id, uia.amount( GRAPHENE_MAX_SHARE_SUPPLY - 100 ) ); BOOST_CHECK( uia_id(db).can_update_max_supply() ); @@ -535,6 +535,18 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + // unable to enable the lock_max_supply flag + auop.new_options.flags &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.flags |= lock_max_supply; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + // able to update other parameters auto old_market_fee_percent = auop.new_options.market_fee_percent; BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, old_market_fee_percent ); @@ -546,6 +558,126 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, 120u ); + // reserve all supply + reserve_asset( sam_id, uia_id(db).amount( GRAPHENE_MAX_SHARE_SUPPLY - 100 ) ); + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // still unable to update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.max_supply += 1; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // still unable to enable the lock_max_supply flag + auop.new_options.flags &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.flags |= lock_max_supply; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // able to reinstall the permission and do it + auop.new_options.issuer_permissions &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // still unable to update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.max_supply += 1; + + BOOST_CHECK( !uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // now able to enable the lock_max_supply flag + auop.new_options.flags &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // issue some + issue_uia( sam_id, uia_id(db).amount( 100 ) ); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // update permission to disable updating of lock_max_supply flag + auop.new_options.issuer_permissions |= lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // still can update max supply + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // unable to reinstall the permission + auop.new_options.issuer_permissions &= ~lock_max_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= lock_max_supply; + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 99 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // update max supply + auop.new_options.max_supply -= 1; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_update_max_supply() ); + // max_supply > current_supply + BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + generate_block(); } catch (fc::exception& e) { From 531cc2c37ea9f1d431307d2e42077c158d72da89 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 18:48:28 -0400 Subject: [PATCH 18/29] Add test case for disable_new_supply flag --- tests/tests/bsip48_75_tests.cpp | 211 ++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 6579f182c2..a6c9fa2618 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -686,5 +686,216 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) } } +BOOST_AUTO_TEST_CASE( disable_new_supply_uia ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)); + + // create a UIA + const asset_object& uia = create_user_issued_asset( "UIATEST", sam, charge_market_fee ); + asset_id_type uia_id = uia.id; + + BOOST_CHECK( uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // issue some to Sam + issue_uia( sam_id, uia_id(db).amount( 100 ) ); + + BOOST_CHECK( uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // prepare to update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + + // update flag to disable creation of new supply + auop.new_options.flags |= disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // unable to issue more coins + BOOST_CHECK_THROW( issue_uia( sam_id, uia_id(db).amount( 100 ) ), fc::exception ); + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // update flag to enable creation of new supply + auop.new_options.flags &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // issue some to Sam + issue_uia( sam_id, uia_id(db).amount( 100 ) ); + + BOOST_CHECK( uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + + // update flag to disable creation of new supply + auop.new_options.flags |= disable_new_supply; + // update permission to disable updating of disable_new_supply flag + auop.new_options.issuer_permissions |= disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to reinstall the permission + auop.new_options.issuer_permissions &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= disable_new_supply; + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to issue more coins + BOOST_CHECK_THROW( issue_uia( sam_id, uia_id(db).amount( 100 ) ), fc::exception ); + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to enable the disable_new_supply flag + auop.new_options.flags &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.flags |= disable_new_supply; + + BOOST_CHECK( !uia_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( disable_new_supply_pm ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)); + + fund( sam, asset(10000) ); + + // create a PM + const asset_object& pm = create_prediction_market( "PDM", sam_id ); + asset_id_type pm_id = pm.id; + + BOOST_CHECK( pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 0 ); + + // Sam borrow some + borrow( sam, asset(100, pm_id), asset(100) ); + + BOOST_CHECK( pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + + // prepare to update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + + // update flag to disable creation of new supply + auop.new_options.flags |= disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + + // unable to borrow more + BOOST_CHECK_THROW( borrow( sam, asset(100, pm_id), asset(100) ), fc::exception ); + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + + // update flag to enable creation of new supply + auop.new_options.flags &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 100 ); + + // Sam borrow some + borrow( sam, asset(100, pm_id), asset(100) ); + + BOOST_CHECK( pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + + // update flag to disable creation of new supply + auop.new_options.flags |= disable_new_supply; + // update permission to disable updating of disable_new_supply flag + auop.new_options.issuer_permissions |= disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to reinstall the permission + auop.new_options.issuer_permissions &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= disable_new_supply; + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to borrow more coins + BOOST_CHECK_THROW( borrow( sam, asset(100, pm_id), asset(100) ), fc::exception ); + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + + // unable to enable the disable_new_supply flag + auop.new_options.flags &= ~disable_new_supply; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.flags |= disable_new_supply; + + BOOST_CHECK( !pm_id(db).can_create_new_supply() ); + BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + BOOST_AUTO_TEST_SUITE_END() From 9a5917f15693d183387106e6aba5ae6ae6afc88a Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 18:54:52 -0400 Subject: [PATCH 19/29] Add proposal tests for the flags about supply --- tests/tests/bsip48_75_tests.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index a6c9fa2618..0d09757f4e 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -511,6 +511,9 @@ BOOST_AUTO_TEST_CASE( update_max_supply ) BOOST_CHECK_EQUAL( uia_id(db).options.max_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 98 ); BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY - 100 ); + // Able to propose the operation + propose( auop ); + // unable to reinstall the permission auop.new_options.issuer_permissions &= ~lock_max_supply; trx.operations.clear(); @@ -756,6 +759,9 @@ BOOST_AUTO_TEST_CASE( disable_new_supply_uia ) BOOST_CHECK( !uia_id(db).can_create_new_supply() ); BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 200 ); + // Able to propose the operation + propose( auop ); + // unable to reinstall the permission auop.new_options.issuer_permissions &= ~disable_new_supply; trx.operations.clear(); @@ -862,6 +868,9 @@ BOOST_AUTO_TEST_CASE( disable_new_supply_pm ) BOOST_CHECK( !pm_id(db).can_create_new_supply() ); BOOST_CHECK_EQUAL( pm_id(db).dynamic_data(db).current_supply.value, 200 ); + // Able to propose the operation + propose( auop ); + // unable to reinstall the permission auop.new_options.issuer_permissions &= ~disable_new_supply; trx.operations.clear(); From f61fb941a43deb0ec5f46fd61debd2f5cd55e4d8 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 19:09:53 -0400 Subject: [PATCH 20/29] Add test case for skip_core_exchange_rate --- tests/tests/bsip48_75_tests.cpp | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 0d09757f4e..9293416913 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -905,6 +905,77 @@ BOOST_AUTO_TEST_CASE( disable_new_supply_pm ) } } +BOOST_AUTO_TEST_CASE( skip_core_exchange_rate ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)); + + // create a UIA + const asset_object& uia = create_user_issued_asset( "UIATEST", sam, charge_market_fee ); + asset_id_type uia_id = uia.id; + + BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(1, uia_id), asset(1)) ); + + // prepare to update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + + // update CER + auop.new_options.core_exchange_rate = price(asset(2, uia_id), asset(1)); + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // CER changed + BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) ); + + // save value for later check + auto old_market_fee_percent = auop.new_options.market_fee_percent; + BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, old_market_fee_percent ); + + // set skip_core_exchange_rate to false, should fail + auop.new_options.core_exchange_rate = price(asset(3, uia_id), asset(1)); + auop.extensions.value.skip_core_exchange_rate = false; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + // CER didn't change + BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) ); + + // skip updating CER + auop.extensions.value.skip_core_exchange_rate = true; + auop.new_options.market_fee_percent = 120u; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + // CER didn't change + BOOST_CHECK( uia_id(db).options.core_exchange_rate == price(asset(2, uia_id), asset(1)) ); + // market_fee_percent changed + BOOST_CHECK_EQUAL( uia_id(db).options.market_fee_percent, 120u ); + + // Able to propose the operation + propose( auop ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 17eb11e72b9b2e747cdc0bb624801f57d1150d16 Mon Sep 17 00:00:00 2001 From: abitmore Date: Sun, 17 May 2020 20:44:00 -0400 Subject: [PATCH 21/29] Add test case about valid bits in asset flags --- tests/tests/bsip48_75_tests.cpp | 208 ++++++++++++++++++++++++++++++++ 1 file changed, 208 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 9293416913..91850c5e5f 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -976,6 +976,214 @@ BOOST_AUTO_TEST_CASE( skip_core_exchange_rate ) } } +BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) +{ + try { + + // Proceeds to a recent hard fork + generate_blocks( HARDFORK_CORE_1270_TIME ); + generate_block(); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + uint16_t bitmask = ASSET_ISSUER_PERMISSION_ENABLE_BITS_MASK; + uint16_t uiamask = DEFAULT_UIA_ASSET_ISSUER_PERMISSION; + + uint16_t bitflag = ~global_settle & ~committee_fed_asset; // high bits are set + uint16_t uiaflag = ~(bitmask ^ uiamask); // high bits are set + + // Able to create UIA with invalid flags + asset_create_operation acop; + acop.issuer = sam_id; + acop.symbol = "SAMCOIN"; + acop.precision = 2; + acop.common_options.core_exchange_rate = price(asset(1,asset_id_type(1)),asset(1)); + acop.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + acop.common_options.market_fee_percent = 100; + acop.common_options.flags = uiaflag; + acop.common_options.issuer_permissions = uiamask; + + trx.operations.clear(); + trx.operations.push_back( acop ); + + processed_transaction ptx = PUSH_TX(db, trx, ~0); + const asset_object& samcoin = db.get(ptx.operation_results[0].get()); + asset_id_type samcoin_id = samcoin.id; + + // There are invalid bits in flags + BOOST_CHECK( samcoin_id(db).options.flags & ~UIA_VALID_FLAGS_MASK ); + + // Able to create MPA with invalid flags + asset_create_operation acop2 = acop; + acop2.symbol = "SAMBIT"; + acop2.bitasset_opts = bitasset_options(); + acop2.common_options.flags = bitflag; + acop2.common_options.issuer_permissions = bitmask; + + trx.operations.clear(); + trx.operations.push_back( acop2 ); + + ptx = PUSH_TX(db, trx, ~0); + const asset_object& sambit = db.get(ptx.operation_results[0].get()); + asset_id_type sambit_id = sambit.id; + + // There are invalid bits in flags + BOOST_CHECK( sambit_id(db).options.flags & ~VALID_FLAGS_MASK ); + + // Unable to correct the invalid flags of the UIA + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = samcoin_id; + auop.new_options = samcoin_id(db).options; + auop.new_options.flags = 0; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to correct the invalid flags of the MPA + asset_update_operation auop2; + auop2.issuer = sam_id; + auop2.asset_to_update = sambit_id; + auop2.new_options = sambit_id(db).options; + auop2.new_options.flags = 0; + + trx.operations.clear(); + trx.operations.push_back( auop2 ); + + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + // take a look at flags of UIA + BOOST_CHECK( samcoin_id(db).options.flags != UIA_VALID_FLAGS_MASK ); + + // Try to update UIA but leave some invalid flags, should fail + auop.new_options = samcoin_id(db).options; + for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) + { + auop.new_options.flags = UIA_VALID_FLAGS_MASK | bit; + if( auop.new_options.flags == UIA_VALID_FLAGS_MASK ) + continue; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either if the bit is not a valid bit for MPA + if( !(bit & VALID_FLAGS_MASK) ) + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + } + + // Unset the invalid bits in flags, should succeed + auop.new_options.flags = UIA_VALID_FLAGS_MASK; + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( samcoin_id(db).options.flags, UIA_VALID_FLAGS_MASK ); + + // Able to propose too + propose( auop ); + + // take a look at flags of MPA + uint16_t valid_bitflag = VALID_FLAGS_MASK & ~committee_fed_asset; + BOOST_CHECK( sambit_id(db).options.flags != valid_bitflag ); + + // Try to update MPA but leave some invalid flags, should fail + auop2.new_options = sambit_id(db).options; + for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) + { + auop2.new_options.flags = valid_bitflag | bit; + if( auop2.new_options.flags == valid_bitflag ) + continue; + trx.operations.clear(); + trx.operations.push_back( auop2 ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( auop2 ), fc::exception ); + } + + // Unset the invalid bits in flags, should succeed + auop2.new_options.flags = valid_bitflag; + trx.operations.clear(); + trx.operations.push_back( auop2 ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( sambit_id(db).options.flags, valid_bitflag ); + + // Able to propose too + propose( auop2 ); + + // Unable to create a new UIA with an unknown flag + acop.symbol = "NEWSAMCOIN"; + for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) + { + acop.common_options.flags = UIA_VALID_FLAGS_MASK | bit; + if( acop.common_options.flags == UIA_VALID_FLAGS_MASK ) + continue; + trx.operations.clear(); + trx.operations.push_back( acop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( acop ), fc::exception ); + } + + // Able to create a new UIA with a valid flag + acop.common_options.flags = UIA_VALID_FLAGS_MASK; + trx.operations.clear(); + trx.operations.push_back( acop ); + ptx = PUSH_TX(db, trx, ~0); + const asset_object& newsamcoin = db.get(ptx.operation_results[0].get()); + asset_id_type newsamcoin_id = newsamcoin.id; + + BOOST_CHECK_EQUAL( newsamcoin_id(db).options.flags, UIA_VALID_FLAGS_MASK ); + + // Able to propose too + propose( acop ); + + // Unable to create a new MPA with an unknown flag + acop2.symbol = "NEWSAMBIT"; + for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) + { + acop2.common_options.flags = valid_bitflag | bit; + if( acop2.common_options.flags == valid_bitflag ) + continue; + trx.operations.clear(); + trx.operations.push_back( acop2 ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + // Unable to propose either + BOOST_CHECK_THROW( propose( acop2 ), fc::exception ); + } + + // Able to create a new UIA with a valid flag + acop2.common_options.flags = valid_bitflag; + trx.operations.clear(); + trx.operations.push_back( acop2 ); + ptx = PUSH_TX(db, trx, ~0); + const asset_object& newsambit = db.get(ptx.operation_results[0].get()); + asset_id_type newsambit_id = newsambit.id; + + BOOST_CHECK_EQUAL( newsambit_id(db).options.flags, valid_bitflag ); + + // Able to propose too + propose( acop2 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 2dbb6bcc1aaf67f6bfc84ae9cfcac92cb8c35ba0 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 18 May 2020 16:22:44 -0400 Subject: [PATCH 22/29] Add test cases about updating asset precision --- tests/tests/bsip48_75_tests.cpp | 118 ++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 91850c5e5f..f7cd8f4ddc 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1184,6 +1184,124 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) } } +BOOST_AUTO_TEST_CASE( update_asset_precision ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)); + + // create a prediction market + const asset_object& pm = create_prediction_market( "PDM", sam_id ); + asset_id_type pm_id = pm.id; + + BOOST_CHECK_EQUAL( pm_id(db).precision, 5 ); + + // prepare to update + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = pm_id; + auop.new_options = pm_id(db).options; + + // Unable to update precision of a PM + auop.extensions.value.new_precision = 4; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK_EQUAL( pm_id(db).precision, 5 ); + + // Able to propose the operation + propose( auop ); + + // create a UIA + const asset_object& uia = create_user_issued_asset( "UIATEST", sam, charge_market_fee ); + asset_id_type uia_id = uia.id; + + BOOST_CHECK_EQUAL( uia_id(db).precision, 2 ); + + // try to set new precision to be the same as the old precision, will fail + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.extensions.value.new_precision = 2; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 2 ); + + // try to set new precision to a number which is too big, will fail + auop.asset_to_update = uia_id; + auop.new_options = uia_id(db).options; + auop.extensions.value.new_precision = 13; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // Unable to propose either + BOOST_CHECK_THROW( propose( auop ), fc::exception ); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 2 ); + + // update precision to a valid number, should succeed + auop.extensions.value.new_precision = 3; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 3 ); + + // create some supply + issue_uia( sam_id, asset( 100, uia_id ) ); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 100 ); + + // try to update precision, will fail + auop.extensions.value.new_precision = 4; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 3 ); + + // destroy all supply + reserve_asset( sam_id, asset( 100, uia_id ) ); + + BOOST_CHECK_EQUAL( uia_id(db).dynamic_data(db).current_supply.value, 0 ); + + // update precision, should succeed + auop.extensions.value.new_precision = 4; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 4 ); + + // create a MPA which is backed by the UIA + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 10, charge_market_fee, 3, uia_id ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).bitasset_data(db).options.short_backing_asset == uia_id ); + + // try to update precision of the UIA, will fail + auop.extensions.value.new_precision = 3; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK_EQUAL( uia_id(db).precision, 4 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END() From 910c922b5a9e0702bd3d3e07120debf701f843c1 Mon Sep 17 00:00:00 2001 From: abitmore Date: Mon, 18 May 2020 17:03:52 -0400 Subject: [PATCH 23/29] Update database fixture to be able to feed ICR --- tests/common/database_fixture.cpp | 28 ++++++++++++---------------- tests/common/database_fixture.hpp | 12 +++++++----- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 8932150b19..c68ee681cc 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1126,7 +1126,8 @@ void database_fixture::update_feed_producers( const asset_object& mia, flat_set< verify_asset_supplies(db); } FC_CAPTURE_AND_RETHROW( (mia)(producers) ) } -void database_fixture::publish_feed( const asset_object& mia, const account_object& by, const price_feed& f ) +void database_fixture::publish_feed( const asset_object& mia, const account_object& by, const price_feed& f, + const optional icr ) { set_expiration( db, trx ); trx.operations.clear(); @@ -1136,7 +1137,11 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obje op.asset_id = mia.id; op.feed = f; if( op.feed.core_exchange_rate.is_null() ) + { op.feed.core_exchange_rate = op.feed.settlement_price; + op.feed.core_exchange_rate.quote.asset_id = asset_id_type(); + } + op.extensions.value.initial_collateral_ratio = icr; trx.operations.emplace_back( std::move(op) ); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); @@ -1146,23 +1151,10 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obje verify_asset_supplies(db); } -/*** - * @brief helper method to add a price feed - * - * Adds a price feed for asset2, pushes the transaction, and generates the block - * - * @param fixture the database_fixture - * @param publisher who is publishing the feed - * @param asset1 the base asset - * @param amount1 the amount of the base asset - * @param asset2 the quote asset - * @param amount2 the amount of the quote asset - * @param core_id id of core (helps with core_exchange_rate) - */ void database_fixture::publish_feed(const account_id_type& publisher, const asset_id_type& asset1, int64_t amount1, const asset_id_type& asset2, int64_t amount2, - const asset_id_type& core_id) + const asset_id_type& core_id, const optional icr) { const asset_object& a1 = asset1(db); const asset_object& a2 = asset2(db); @@ -1172,9 +1164,13 @@ 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)); + op.extensions.value.initial_collateral_ratio = icr; trx.operations.clear(); - trx.operations.push_back(std::move(op)); + trx.operations.emplace_back(std::move(op)); + for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration( db, trx ); PUSH_TX( db, trx, ~0); + verify_asset_supplies(db); generate_block(); trx.clear(); } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index bb92910f4d..f2ae3c308e 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -255,13 +255,13 @@ struct database_fixture { void update_feed_producers(asset_id_type mia, flat_set producers) { update_feed_producers(mia(db), producers); } void update_feed_producers(const asset_object& mia, flat_set producers); - void publish_feed(asset_id_type mia, account_id_type by, const price_feed& f) - { publish_feed(mia(db), by(db), f); } + void publish_feed(asset_id_type mia, account_id_type by, const price_feed& f, const optional icr = {}) + { publish_feed(mia(db), by(db), f, icr); } /*** * @brief helper method to add a price feed * - * Adds a price feed for asset2, pushes the transaction, and generates the block + * Adds a price feed for asset2, pushes the transaction, and generates a block * * @param publisher who is publishing the feed * @param asset1 the base asset @@ -269,13 +269,15 @@ struct database_fixture { * @param asset2 the quote asset * @param amount2 the amount of the quote asset * @param core_id id of core (helps with core_exchange_rate) + * @param icr initial collateral ratio */ void publish_feed(const account_id_type& publisher, const asset_id_type& asset1, int64_t amount1, const asset_id_type& asset2, int64_t amount2, - const asset_id_type& core_id); + const asset_id_type& core_id, const optional icr = {}); - void publish_feed(const asset_object& mia, const account_object& by, const price_feed& f); + void publish_feed( const asset_object& mia, const account_object& by, const price_feed& f, + const optional icr = {} ); const call_order_object* borrow( account_id_type who, asset what, asset collateral, optional target_cr = {} ) From 534f508caaeaf6f82c4c41af270395e0aeb99247 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 19 May 2020 18:15:51 -0400 Subject: [PATCH 24/29] Test asset creation with all permission bits --- tests/tests/bsip48_75_tests.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index f7cd8f4ddc..4dcfa24aee 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1122,8 +1122,10 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // Able to propose too propose( auop2 ); - // Unable to create a new UIA with an unknown flag + // Unable to create a new UIA with an unknown bit in flags acop.symbol = "NEWSAMCOIN"; + // With all possible bits in permissions set to 1 + acop2.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop.common_options.flags = UIA_VALID_FLAGS_MASK | bit; @@ -1136,7 +1138,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK_THROW( propose( acop ), fc::exception ); } - // Able to create a new UIA with a valid flag + // Able to create a new UIA with a valid flags field acop.common_options.flags = UIA_VALID_FLAGS_MASK; trx.operations.clear(); trx.operations.push_back( acop ); @@ -1149,8 +1151,10 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) // Able to propose too propose( acop ); - // Unable to create a new MPA with an unknown flag + // Unable to create a new MPA with an unknown bit in flags acop2.symbol = "NEWSAMBIT"; + // With all possible bits in permissions set to 1 + acop2.common_options.issuer_permissions = ASSET_ISSUER_PERMISSION_MASK; for( uint16_t bit = 0x8000; bit > 0; bit >>= 1 ) { acop2.common_options.flags = valid_bitflag | bit; @@ -1163,7 +1167,7 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK_THROW( propose( acop2 ), fc::exception ); } - // Able to create a new UIA with a valid flag + // Able to create a new MPA with a valid flags field acop2.common_options.flags = valid_bitflag; trx.operations.clear(); trx.operations.push_back( acop2 ); @@ -1173,6 +1177,10 @@ BOOST_AUTO_TEST_CASE( invalid_flags_in_asset ) BOOST_CHECK_EQUAL( newsambit_id(db).options.flags, valid_bitflag ); + BOOST_CHECK( !newsambit_id(db).can_owner_update_icr() ); + BOOST_CHECK( !newsambit_id(db).can_owner_update_mcr() ); + BOOST_CHECK( !newsambit_id(db).can_owner_update_mssr() ); + // Able to propose too propose( acop2 ); From 3979b2d23d1ee118db2a47ef80b795d48c56d4ba Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 19 May 2020 19:48:23 -0400 Subject: [PATCH 25/29] Add tests about asset owner's permissions ... ... to update ICR, MCR and MSSR --- tests/tests/bsip48_75_tests.cpp | 356 ++++++++++++++++++++++++++++++++ 1 file changed, 356 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 4dcfa24aee..c7ae51f650 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1310,6 +1310,362 @@ BOOST_AUTO_TEST_CASE( update_asset_precision ) } } +BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_icr_mcr_mssr ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + + BOOST_CHECK( mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + auto current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, f.maintenance_collateral_ratio ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, f.maximum_short_squeeze_ratio ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, feed_icr ); + + // disable owner's permission to update icr + asset_update_operation auop; + auop.issuer = sam_id; + auop.asset_to_update = mpa_id; + auop.new_options = mpa_id(db).options; + auop.new_options.issuer_permissions |= disable_icr_update; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update icr + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.initial_collateral_ratio.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + // disable owner's permission to update mcr + auop.new_options.issuer_permissions &= ~disable_icr_update; + auop.new_options.issuer_permissions |= disable_mcr_update; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update mcr + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maintenance_collateral_ratio.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + // disable owner's permission to update mssr + auop.new_options.issuer_permissions &= ~disable_mcr_update; + auop.new_options.issuer_permissions |= disable_mssr_update; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update mssr + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maximum_short_squeeze_ratio.reset(); + + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_CHECK( !mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + // enable owner's permission to update mssr + auop.new_options.issuer_permissions &= ~disable_mssr_update; + + trx.operations.clear(); + trx.operations.push_back( auop ); + + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // check that owner can update the ratios + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1650 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1150 ); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1650 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1150 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + + // Sam borrow some + borrow( sam, asset(1000, mpa_id), asset(2000) ); + + // disable owner's permission to update icr + auop.new_options.issuer_permissions |= disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update icr + aubop.new_options.extensions.value.initial_collateral_ratio = 1960; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.initial_collateral_ratio.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // able to update other ratios + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1100; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1100 ); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1100 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + + // unable to enable the permission to update icr + auop.new_options.issuer_permissions &= ~disable_icr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= disable_icr_update; + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // disable owner's permission to update mcr + auop.new_options.issuer_permissions |= disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update mcr + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1660; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.maintenance_collateral_ratio.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // able to update other params that still has permission E.G. mssr + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1010; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1010 ); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1010 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + + // unable to enable the permission to update mcr + auop.new_options.issuer_permissions &= ~disable_mcr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + auop.new_options.issuer_permissions |= disable_mcr_update; + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( mpa_id(db).can_owner_update_mssr() ); + + // disable owner's permission to update mssr + auop.new_options.issuer_permissions |= disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + PUSH_TX(db, trx, ~0); + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() ); + + // check that owner can not update mssr + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1020; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + aubop.new_options.extensions.value.maximum_short_squeeze_ratio.reset(); + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + // able to update other params that still has permission E.G. force_settlement_delay_sec + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1600; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1010; + aubop.new_options.force_settlement_delay_sec += 1; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + BOOST_REQUIRE_EQUAL( mpa_id(db).bitasset_data(db).options.force_settlement_delay_sec, + aubop.new_options.force_settlement_delay_sec ); + + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio.valid() ); + BOOST_REQUIRE( mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio.valid() ); + + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.initial_collateral_ratio, 1950 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( *mpa_id(db).bitasset_data(db).options.extensions.value.maximum_short_squeeze_ratio, 1010 ); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1010 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + + // unable to enable the permission to update mssr + auop.new_options.issuer_permissions &= ~disable_mssr_update; + trx.operations.clear(); + trx.operations.push_back( auop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + + BOOST_CHECK( !mpa_id(db).can_owner_update_icr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mcr() ); + BOOST_CHECK( !mpa_id(db).can_owner_update_mssr() ); + + // publish a new feed + f.settlement_price = price( asset(2,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(3,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1830; + f.maximum_short_squeeze_ratio = 1230; + + feed_icr = 1930; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + // the values set by asset owners still take effect + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 1600 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1010 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} BOOST_AUTO_TEST_SUITE_END() From 3fba84cfc653b1fb7a8a5044e28ac75a2c36a241 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 19 May 2020 21:09:41 -0400 Subject: [PATCH 26/29] Add tests for valid range of ICR, MCR and MSSR --- tests/tests/bsip48_75_tests.cpp | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index c7ae51f650..3c745fe238 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -1470,6 +1470,37 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_icr_mcr_mssr ) BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1150 ); BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, 1950 ); + // check the ratios' valid range + aubop.new_options.extensions.value.initial_collateral_ratio = 1000; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.initial_collateral_ratio = 32001; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.initial_collateral_ratio = 1950; + + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1000; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maintenance_collateral_ratio = 32001; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maintenance_collateral_ratio = 1650; + + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1000; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 32001; + trx.operations.clear(); + trx.operations.push_back( aubop ); + BOOST_CHECK_THROW( PUSH_TX(db, trx, ~0), fc::exception ); + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1150; + // Sam borrow some borrow( sam, asset(1000, mpa_id), asset(2000) ); @@ -1651,7 +1682,7 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_icr_mcr_mssr ) publish_feed( mpa_id, feeder_id, f, feed_icr ); - // the values set by asset owners still take effect + // the values set by the asset owner still take effect current_feed = mpa_id(db).bitasset_data(db).current_feed; BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); From 235b1002f97c4663caca9a9f698945836f650e64 Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 19 May 2020 21:17:55 -0400 Subject: [PATCH 27/29] Add test case for feeding ICR --- tests/tests/operation_tests.cpp | 183 ++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 211576b97e..cd317e93e9 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -967,6 +967,189 @@ BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr } } +BOOST_AUTO_TEST_CASE( more_call_order_update_test_after_hardfork_bsip77_when_icr_is_fed ) +{ + 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, {} ); // ICR is not set + const auto& core = asset_id_type()(db); + + 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, 1050 ); // ICR = 1.05 + + 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" ); + publish_feed( bitusd, sam, current_feed, 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, 1850 ); + + 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" ); + publish_feed( bitusd, sam, current_feed, 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; From 01c342447ff42ba8e6db4b44001cf7dbe8a3aeaf Mon Sep 17 00:00:00 2001 From: abitmore Date: Tue, 19 May 2020 21:27:36 -0400 Subject: [PATCH 28/29] Fix tests about CER feeding bug (Graphene HF480) --- tests/common/database_fixture.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index c68ee681cc..03bdc4b26b 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1139,7 +1139,8 @@ void database_fixture::publish_feed( const asset_object& mia, const account_obje if( op.feed.core_exchange_rate.is_null() ) { op.feed.core_exchange_rate = op.feed.settlement_price; - op.feed.core_exchange_rate.quote.asset_id = asset_id_type(); + if( db.head_block_time() > HARDFORK_480_TIME ) + op.feed.core_exchange_rate.quote.asset_id = asset_id_type(); } op.extensions.value.initial_collateral_ratio = icr; trx.operations.emplace_back( std::move(op) ); From bfbb40724d4108bf4404c28cb6033244363b6edc Mon Sep 17 00:00:00 2001 From: abitmore Date: Thu, 21 May 2020 09:52:17 -0400 Subject: [PATCH 29/29] Add tests about margin calls after ratios updated --- tests/tests/bsip48_75_tests.cpp | 151 ++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/tests/tests/bsip48_75_tests.cpp b/tests/tests/bsip48_75_tests.cpp index 3c745fe238..8fda0a6d87 100644 --- a/tests/tests/bsip48_75_tests.cpp +++ b/tests/tests/bsip48_75_tests.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -1698,5 +1699,155 @@ BOOST_AUTO_TEST_CASE( asset_owner_permissions_update_icr_mcr_mssr ) } } +BOOST_AUTO_TEST_CASE( asset_owner_update_mcr_mssr ) +{ + try { + + // advance to bsip48/75 hard fork + generate_blocks( HARDFORK_BSIP_48_75_TIME ); + set_expiration( db, trx ); + + ACTORS((sam)(feeder)(borrower)(seller)); + + auto init_amount = 10000000 * GRAPHENE_BLOCKCHAIN_PRECISION; + fund( sam, asset(init_amount) ); + fund( feeder, asset(init_amount) ); + fund( borrower, asset(init_amount) ); + fund( seller, asset(init_amount) ); + + // create a MPA with a zero market_fee_percent + const asset_object& mpa = create_bitasset( "TESTBIT", sam_id, 0, charge_market_fee ); + asset_id_type mpa_id = mpa.id; + asset_id_type core_id = asset_id_type(); + + // add a price feed publisher and publish a feed + update_feed_producers( mpa_id, { feeder_id } ); + + price_feed f; + f.settlement_price = price( asset(1,mpa_id), asset(1) ); + f.core_exchange_rate = price( asset(1,mpa_id), asset(1) ); + f.maintenance_collateral_ratio = 1850; + f.maximum_short_squeeze_ratio = 1250; + + uint16_t feed_icr = 1900; + + publish_feed( mpa_id, feeder_id, f, feed_icr ); + + auto current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, f.maintenance_collateral_ratio ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, f.maximum_short_squeeze_ratio ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, feed_icr ); + + // borrower borrows some and sends to seller + const call_order_object* call_ptr = borrow( borrower_id, asset(1000, mpa_id), asset(2000) ); + BOOST_REQUIRE( call_ptr ); + call_order_id_type call_id = call_ptr->id; + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 1000 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount ); + + transfer( borrower_id, seller_id, asset(1000, mpa_id) ); + + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 1000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount ); + + // seller places orders + const limit_order_object* order1_ptr = create_sell_order( seller, asset(100, mpa_id), asset(105) ); + BOOST_REQUIRE( order1_ptr ); + limit_order_id_type order1_id = order1_ptr->id; + BOOST_CHECK_EQUAL( order1_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( order1_id(db).amount_to_receive().amount.value, 105 ); + + const limit_order_object* order2_ptr = create_sell_order( seller, asset(100, mpa_id), asset(115) ); + BOOST_REQUIRE( order2_ptr ); + limit_order_id_type order2_id = order2_ptr->id; + BOOST_CHECK_EQUAL( order2_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( order2_id(db).amount_to_receive().amount.value, 115 ); + + BOOST_CHECK_EQUAL( call_id(db).debt.value, 1000 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount ); + + // asset owner updates MCR and MSSR + asset_update_bitasset_operation aubop; + aubop.issuer = sam_id; + aubop.asset_to_update = mpa_id; + aubop.new_options = mpa_id(db).bitasset_data(db).options; + aubop.new_options.extensions.value.maintenance_collateral_ratio = 3000; + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1100; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 3000 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1100 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, feed_icr ); + + // borrower should get margin called + BOOST_REQUIRE( db.find( call_id )); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 900 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1895 ); + + // limit order1 should be filled + BOOST_CHECK( !db.find( order1_id )); + + // limit order2 should not change due to MSSR + BOOST_REQUIRE( db.find( order2_id )); + BOOST_CHECK_EQUAL( order2_id(db).for_sale.value, 100 ); + BOOST_CHECK_EQUAL( order2_id(db).amount_to_receive().amount.value, 115 ); + + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount + 105 ); + + // asset owner updates MSSR + aubop.new_options.extensions.value.maximum_short_squeeze_ratio = 1200; + trx.operations.clear(); + trx.operations.push_back( aubop ); + PUSH_TX(db, trx, ~0); + + current_feed = mpa_id(db).bitasset_data(db).current_feed; + BOOST_CHECK( current_feed.settlement_price == f.settlement_price ); + BOOST_CHECK( current_feed.core_exchange_rate == f.core_exchange_rate ); + BOOST_CHECK_EQUAL( current_feed.maintenance_collateral_ratio, 3000 ); + BOOST_CHECK_EQUAL( current_feed.maximum_short_squeeze_ratio, 1200 ); + BOOST_CHECK_EQUAL( current_feed.initial_collateral_ratio, feed_icr ); + + // borrower should get margin called + BOOST_REQUIRE( db.find( call_id )); + BOOST_CHECK_EQUAL( call_id(db).debt.value, 800 ); + BOOST_CHECK_EQUAL( call_id(db).collateral.value, 1780 ); + + // limit order2 should be filled + BOOST_CHECK( !db.find( order2_id )); + + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, mpa_id ).amount.value, 0 ); + BOOST_CHECK_EQUAL( db.get_balance( borrower_id, core_id ).amount.value, init_amount - 2000 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, mpa_id ).amount.value, 800 ); + BOOST_CHECK_EQUAL( db.get_balance( seller_id, core_id ).amount.value, init_amount + 105 + 115 ); + + generate_block(); + + } catch (fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_SUITE_END()