Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

check max_supply before borrowing MPAs #1498

Merged
merged 9 commits into from
Jan 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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));
jmjatlanta marked this conversation as resolved.
Show resolved Hide resolved
});
}
}
}

/******
* @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 @@ -451,7 +451,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;
pmconrad marked this conversation as resolved.
Show resolved Hide resolved
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