Skip to content

Commit

Permalink
Merge pull request #1498 from bitshares/jmj_1465
Browse files Browse the repository at this point in the history
check max_supply before borrowing MPAs
  • Loading branch information
jmjatlanta authored Jan 25, 2019
2 parents aaf7701 + ab4dfeb commit 0f10387
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 5 deletions.
31 changes: 31 additions & 0 deletions libraries/chain/db_maint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,33 @@ void database::process_bitassets()
}
}

/****
* @brief a one-time data process to correct max_supply
*/
void process_hf_1465( database& db )
{
const auto head_num = db.head_block_num();
wlog( "Processing hard fork core-1465 at block ${n}", ("n",head_num) );
// for each market issued asset
const auto& asset_idx = db.get_index_type<asset_index>().indices().get<by_type>();
for( auto asset_itr = asset_idx.lower_bound(true); asset_itr != asset_idx.end(); ++asset_itr )
{
const auto& current_asset = *asset_itr;
graphene::chain::share_type current_supply = current_asset.dynamic_data(db).current_supply;
graphene::chain::share_type max_supply = current_asset.options.max_supply;
if (current_supply > max_supply && max_supply != GRAPHENE_MAX_SHARE_SUPPLY)
{
wlog( "Adjusting max_supply of ${asset} because current_supply (${current_supply}) is greater than ${old}.",
("asset", current_asset.symbol)
("current_supply", current_supply.value)
("old", max_supply));
db.modify<asset_object>( current_asset, [current_supply](asset_object& obj) {
obj.options.max_supply = graphene::chain::share_type(std::min(current_supply.value, GRAPHENE_MAX_SHARE_SUPPLY));
});
}
}
}

/******
* @brief one-time data process for hard fork core-868-890
*
Expand Down Expand Up @@ -1225,6 +1252,10 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
&& !to_update_and_match_call_orders )
process_hf_935( *this );

// make sure current_supply is less than or equal to max_supply
if ( dgpo.next_maintenance_time <= HARDFORK_CORE_1465_TIME && next_maintenance_time > HARDFORK_CORE_1465_TIME )
process_hf_1465(*this);

modify(dgpo, [next_maintenance_time](dynamic_global_property_object& d) {
d.next_maintenance_time = next_maintenance_time;
d.accounts_registered_this_interval = 0;
Expand Down
4 changes: 4 additions & 0 deletions libraries/chain/hardfork.d/CORE_1465.hf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// bitshares-core issue #1465 check max_supply before processing call_order_update
#ifndef HARDFORK_CORE_1465_TIME
#define HARDFORK_CORE_1465_TIME (fc::time_point_sec( 1600000000 )) // 2020-09-13 12:26:40
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ namespace graphene { namespace chain {
const account_object* _paying_account = nullptr;
const call_order_object* _order = nullptr;
const asset_bitasset_data_object* _bitasset_data = nullptr;
const asset_dynamic_data_object* _dynamic_data_obj = nullptr;
};

class bid_collateral_evaluator : public evaluator<bid_collateral_evaluator>
Expand Down
15 changes: 12 additions & 3 deletions libraries/chain/market_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope
{ try {
database& d = db();

auto next_maintenance_time = d.get_dynamic_global_properties().next_maintenance_time;

// TODO: remove this check and the assertion after hf_834
if( d.get_dynamic_global_properties().next_maintenance_time <= HARDFORK_CORE_834_TIME )
if( next_maintenance_time <= HARDFORK_CORE_834_TIME )
FC_ASSERT( !o.extensions.value.target_collateral_ratio.valid(),
"Can not set target_collateral_ratio in call_order_update_operation before hardfork 834." );

Expand All @@ -167,6 +169,14 @@ 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) );

_dynamic_data_obj = &_debt_asset->dynamic_asset_data_id(d);
FC_ASSERT( next_maintenance_time <= HARDFORK_CORE_1465_TIME
|| _dynamic_data_obj->current_supply + o.delta_debt.amount <= _debt_asset->options.max_supply,
"Borrowing this quantity would exceed MAX_SUPPLY" );

FC_ASSERT( _dynamic_data_obj->current_supply + o.delta_debt.amount >= 0,
"This transaction would bring current supply below zero.");

_bitasset_data = &_debt_asset->bitasset_data(d);

/// if there is a settlement for this asset, then no further margin positions may be taken and
Expand Down Expand Up @@ -198,9 +208,8 @@ object_id_type call_order_update_evaluator::do_apply(const call_order_update_ope
d.adjust_balance( o.funding_account, o.delta_debt );

// Deduct the debt paid from the total supply of the debt asset.
d.modify(_debt_asset->dynamic_asset_data_id(d), [&](asset_dynamic_data_object& dynamic_asset) {
d.modify(*_dynamic_data_obj, [&](asset_dynamic_data_object& dynamic_asset) {
dynamic_asset.current_supply += o.delta_debt.amount;
FC_ASSERT(dynamic_asset.current_supply >= 0);
});
}

Expand Down
2 changes: 1 addition & 1 deletion tests/common/database_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ const asset_object& database_fixture::create_bitasset(
creator.issuer = issuer;
creator.fee = asset();
creator.symbol = name;
creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY;
creator.common_options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY / 2;
creator.precision = precision;
creator.common_options.market_fee_percent = market_fee_percent;
if( issuer == GRAPHENE_WITNESS_ACCOUNT )
Expand Down
106 changes: 105 additions & 1 deletion tests/tests/operation_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,6 @@ BOOST_AUTO_TEST_CASE( call_order_update_validation_test )

op.extensions.value.target_collateral_ratio = 65535;
op.validate(); // still valid

}

// Tests that target_cr option can't be set before hard fork core-834
Expand Down Expand Up @@ -2004,6 +2003,111 @@ BOOST_AUTO_TEST_CASE( reserve_asset_test )
}
}

BOOST_AUTO_TEST_CASE( call_order_update_evaluator_test )
{
try
{
ACTORS( (alice) (bob) );
transfer(committee_account, alice_id, asset(10000000 * GRAPHENE_BLOCKCHAIN_PRECISION));

const auto& core = asset_id_type()(db);

// attempt to increase current supply beyond max_supply
const auto& bitjmj = create_bitasset( "JMJBIT", alice_id );
auto bitjmj_id = bitjmj.get_id();
share_type original_max_supply = bitjmj.options.max_supply;

{
BOOST_TEST_MESSAGE( "Setting price feed to $100000 / 1" );
update_feed_producers( bitjmj, {alice_id} );
price_feed current_feed;
current_feed.settlement_price = bitjmj.amount( 100000 ) / core.amount(1);
publish_feed( bitjmj, alice, current_feed );
}

{
BOOST_TEST_MESSAGE( "Attempting a call_order_update that exceeds max_supply" );
call_order_update_operation op;
op.funding_account = alice_id;
op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION );
op.delta_debt = asset( bitjmj.options.max_supply + 1, bitjmj.id );
transaction tx;
tx.operations.push_back( op );
set_expiration( db, tx );
PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );
generate_block();
}

// advance past hardfork
generate_blocks( HARDFORK_CORE_1465_TIME );
set_expiration( db, trx );

// bitjmj should have its problem corrected
auto newbitjmj = bitjmj_id(db);
BOOST_REQUIRE_GT(newbitjmj.options.max_supply.value, original_max_supply.value);

// now try with an asset after the hardfork
const auto& bitusd = create_bitasset( "USDBIT", alice_id );

{
BOOST_TEST_MESSAGE( "Setting price feed to $100000 / 1" );
update_feed_producers( bitusd, {alice_id} );
price_feed current_feed;
current_feed.settlement_price = bitusd.amount( 100000 ) / core.amount(1);
publish_feed( bitusd, alice_id(db), current_feed );
}

{
BOOST_TEST_MESSAGE( "Attempting a call_order_update that exceeds max_supply" );
call_order_update_operation op;
op.funding_account = alice_id;
op.delta_collateral = asset( 1000000 * GRAPHENE_BLOCKCHAIN_PRECISION );
op.delta_debt = asset( bitusd.options.max_supply + 1, bitusd.id );
transaction tx;
tx.operations.push_back( op );
set_expiration( db, tx );
GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception );
}

{
BOOST_TEST_MESSAGE( "Creating 2 bitusd and transferring to bob (increases current supply)" );
call_order_update_operation op;
op.funding_account = alice_id;
op.delta_collateral = asset( 100 * GRAPHENE_BLOCKCHAIN_PRECISION );
op.delta_debt = asset( 2, bitusd.id );
transaction tx;
tx.operations.push_back( op );
set_expiration( db, tx );
PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );
transfer( alice_id(db), bob_id(db), asset( 2, bitusd.id ) );
}

{
BOOST_TEST_MESSAGE( "Again attempting a call_order_update_operation that is max_supply - 1 (should throw)" );
call_order_update_operation op;
op.funding_account = alice_id;
op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION );
op.delta_debt = asset( bitusd.options.max_supply - 1, bitusd.id );
transaction tx;
tx.operations.push_back( op );
set_expiration( db, tx );
GRAPHENE_REQUIRE_THROW(PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures ), fc::exception);
}

{
BOOST_TEST_MESSAGE( "Again attempting a call_order_update_operation that equals max_supply (should work)" );
call_order_update_operation op;
op.funding_account = alice_id;
op.delta_collateral = asset( 100000 * GRAPHENE_BLOCKCHAIN_PRECISION );
op.delta_debt = asset( bitusd.options.max_supply - 2, bitusd.id );
transaction tx;
tx.operations.push_back( op );
set_expiration( db, tx );
PUSH_TX( db, tx, database::skip_tapos_check | database::skip_transaction_signatures );
}
} FC_LOG_AND_RETHROW()
}

/**
* This test demonstrates how using the call_order_update_operation to
* trigger a margin call is legal if there is a matching order.
Expand Down

0 comments on commit 0f10387

Please sign in to comment.