From b7a3bbd40900f5460b82138a0b10f814087bad7d Mon Sep 17 00:00:00 2001 From: John Jones Date: Thu, 7 Feb 2019 14:59:23 -0500 Subject: [PATCH] Prevent current > max supply and call_order_update proposal --- libraries/chain/market_evaluator.cpp | 5 +- libraries/chain/proposal_evaluator.cpp | 37 ++++++++--- tests/tests/call_order_tests.cpp | 86 ++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 9 deletions(-) diff --git a/libraries/chain/market_evaluator.cpp b/libraries/chain/market_evaluator.cpp index f14119410d..c52c4a15eb 100644 --- a/libraries/chain/market_evaluator.cpp +++ b/libraries/chain/market_evaluator.cpp @@ -205,10 +205,13 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope if( o.delta_debt.amount != 0 ) { + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount <= GRAPHENE_MAX_SHARE_SUPPLY ); + FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0 ); + d.adjust_balance( o.funding_account, o.delta_debt ); // Deduct the debt paid from the total supply of the debt asset. - d.modify(*_dynamic_data_obj, [&](asset_dynamic_data_object& dynamic_asset) { + d.modify( *_dynamic_data_obj, [ this, &o ]( asset_dynamic_data_object& dynamic_asset ) { dynamic_asset.current_supply += o.delta_debt.amount; }); } diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 348688d605..87f678f4a0 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -38,18 +38,27 @@ namespace detail { struct proposal_operation_hardfork_visitor { typedef void result_type; + const database& db; const fc::time_point_sec block_time; const fc::time_point_sec next_maintenance_time; - proposal_operation_hardfork_visitor( const fc::time_point_sec bt, const fc::time_point_sec nmt ) - : block_time(bt), next_maintenance_time(nmt) {} + proposal_operation_hardfork_visitor(const database& _db, const fc::time_point_sec bt ) + : db( _db ), block_time(bt), + next_maintenance_time( db.get_dynamic_global_properties().next_maintenance_time ) {} template void operator()(const T &v) const {} - // TODO review and cleanup code below after hard fork - // hf_834 void operator()(const graphene::chain::call_order_update_operation &v) const { + if (v.delta_debt.amount > 0 && v.delta_debt.asset_id != asset_id_type( 113 ) // CNY + && v.delta_debt.asset_id( db ).bitasset_data_id + && !(*( v.delta_debt.asset_id( db ).bitasset_data_id))(db).is_prediction_market ) + { + ilog( "20181206A: proposal contains call_order_update at ${bt}: ${op}", ("bt",block_time)("op",v) ); + FC_ASSERT( block_time < fc::time_point::now() - fc::seconds(30), "Soft fork - preventing proposal with call_order_update!"); + } + // TODO review and cleanup code below after hard fork + // hf_834 if (next_maintenance_time <= HARDFORK_CORE_834_TIME) { FC_ASSERT( !v.extensions.value.target_collateral_ratio.valid(), "Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." ); @@ -126,10 +135,22 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); } // loop and self visit in proposals - void operator()(const graphene::chain::proposal_create_operation &v) const { - for (const op_wrapper &op : v.proposed_ops) - op.op.visit(*this); + void operator()(const graphene::chain::proposal_create_operation &v) const + { + bool proposal_update_seen = false; + const bool nested_soft_fork = block_time > fc::time_point::now() - fc::seconds(30); + for( const op_wrapper& op : v.proposed_ops ) + { + op.op.visit( *this); + if ( nested_soft_fork && op.op.which() == operation::tag().value ) + { + FC_ASSERT( !proposal_update_seen, "At most one proposal update can be nested in a proposal!" ); + proposal_update_seen = true; + } + } } + + }; struct hardfork_visitor_214 // non-recursive proposal visitor @@ -172,7 +193,7 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati // Calling the proposal hardfork visitor const fc::time_point_sec block_time = d.head_block_time(); const fc::time_point_sec next_maint_time = d.get_dynamic_global_properties().next_maintenance_time; - proposal_operation_hardfork_visitor vtor( block_time, next_maint_time ); + proposal_operation_hardfork_visitor vtor( d, block_time ); vtor( o ); if( block_time < HARDFORK_CORE_214_TIME ) { // cannot be removed after hf, unfortunately diff --git a/tests/tests/call_order_tests.cpp b/tests/tests/call_order_tests.cpp index 2095de0ac1..0447354c3b 100644 --- a/tests/tests/call_order_tests.cpp +++ b/tests/tests/call_order_tests.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include "../common/database_fixture.hpp" @@ -345,4 +346,89 @@ BOOST_AUTO_TEST_CASE( call_order_object_test ) } FC_CAPTURE_LOG_AND_RETHROW( (0) ) } +#define BILLION 1000000000 + +BOOST_AUTO_TEST_CASE( mpa_supply_test ) +{ try { + + ACTORS( (feeder)(borrower) ); + fund( borrower, asset( BILLION ) ); + + generate_blocks( fc::time_point::now() ); + generate_block(); + + set_expiration( db, trx ); + const auto& bitusd = create_bitasset( "USDBIT", feeder_id ); + const asset_id_type usd_id = bitusd.id; + const asset_id_type core_id; + update_feed_producers( usd_id( db ), { feeder_id } ); + + price_feed feed; + feed.maintenance_collateral_ratio = 1750; // need to set this explicitly, testnet has a different default + feed.settlement_price = usd_id( db ).amount( BILLION ) / core_id( db ).amount( 1 ); + publish_feed( usd_id( db ), feeder_id( db ), feed ); + + borrow( borrower_id, usd_id( db ).amount( GRAPHENE_MAX_SHARE_SUPPLY / 2 ), asset( GRAPHENE_MAX_SHARE_SUPPLY * 10 / BILLION ) ); + borrow( borrower_id, usd_id( db ).amount( GRAPHENE_MAX_SHARE_SUPPLY / 2 ), asset( GRAPHENE_MAX_SHARE_SUPPLY * 10 / BILLION ) ); + GRAPHENE_REQUIRE_THROW( borrow( borrower_id, usd_id( db ).amount( 1 ), + asset( GRAPHENE_MAX_SHARE_SUPPLY * 10 / BILLION ) ), fc::assert_exception ); + + trx.clear(); + call_order_update_operation cup; + cup.funding_account = borrower_id; + cup.delta_collateral = asset( GRAPHENE_MAX_SHARE_SUPPLY * 10 / BILLION ); + cup.delta_debt = usd_id( db ).amount( 1 ); + proposal_create_operation pop; + pop.proposed_ops.emplace_back( cup ); + pop.fee_paying_account = borrower_id; + pop.expiration_time = db.head_block_time() + fc::days(1); + trx.operations.push_back( pop ); + sign( trx, borrower_private_key ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::assert_exception ); + trx.clear(); + + generate_block(); + + BOOST_CHECK( usd_id( db ).dynamic_asset_data_id( db ).current_supply.value == GRAPHENE_MAX_SHARE_SUPPLY ); + + const auto& btc = create_bitasset( "BTCUSD", feeder_id, 0, 0, 8, usd_id ); + const asset_id_type btc_id = btc.id; + update_feed_producers( btc_id( db ), { feeder_id } ); + + feed.maintenance_collateral_ratio = 1100; + feed.maximum_short_squeeze_ratio = 1001; + feed.settlement_price = btc_id( db ).amount( 1101 ) / usd_id( db ).amount( 1000 ); + feed.core_exchange_rate = btc_id( db ).amount( 1 ) / asset( 1 ); + publish_feed( btc_id( db ), feeder_id( db ), feed ); + + borrow( borrower_id, btc_id( db ).amount( GRAPHENE_MAX_SHARE_SUPPLY - 1 ), + usd_id( db ).amount( GRAPHENE_MAX_SHARE_SUPPLY - 10 ) ); + GRAPHENE_REQUIRE_THROW( borrow( borrower_id, btc_id( db ).amount( 2 ), usd_id( db ).amount( 10 ) ), fc::assert_exception ); + + trx.clear(); + cup.delta_collateral = usd_id( db ).amount( 10 ); + cup.delta_debt = btc_id( db ).amount( 2 ); + pop.proposed_ops.clear(); + pop.proposed_ops.emplace_back( cup ); + trx.operations.push_back( pop ); + sign( trx, borrower_private_key ); + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::assert_exception ); + trx.clear(); + + borrow( borrower_id, btc_id( db ).amount( 1 ), usd_id( db ).amount( 10 ) ); + + generate_block(); + + BOOST_CHECK( btc_id( db ).dynamic_asset_data_id( db ).current_supply.value == GRAPHENE_MAX_SHARE_SUPPLY ); + + force_settle( borrower_id, btc_id( db ).amount( GRAPHENE_MAX_SHARE_SUPPLY ) ); + + generate_blocks( db.head_block_time() + (*btc_id( db ).bitasset_data_id)( db ).options.force_settlement_delay_sec - 5 ); + + feed.settlement_price = btc_id( db ).amount( 1000 ) / usd_id( db ).amount( 1000 ); + publish_feed( btc_id( db ), feeder_id( db ), feed ); + + generate_block( 2 ); +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END()