1465 1479 1573 1669 1692 460 hf fixes

This commit is contained in:
sierra19XX 2021-03-22 22:54:43 +11:00
parent 14a3e7d9e9
commit fce9f37e9c
20 changed files with 455 additions and 22 deletions

View file

@ -827,6 +827,9 @@ processed_transaction database::_apply_transaction(const signed_transaction& trx
FC_ASSERT( trx.expiration <= now + chain_parameters.maximum_time_until_expiration, "",
("trx.expiration",trx.expiration)("now",now)("max_til_exp",chain_parameters.maximum_time_until_expiration));
FC_ASSERT( now <= trx.expiration, "", ("now",now)("trx.exp",trx.expiration) );
FC_ASSERT( head_block_time() <= HARDFORK_CORE_1573_TIME
|| trx.get_packed_size() <= chain_parameters.maximum_transaction_size,
"Transaction exceeds maximum transaction size." );
}
//Insert transaction into unique transactions database.

View file

@ -2065,6 +2065,33 @@ void update_median_feeds(database& db)
}
}
/****
* @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
*
@ -2534,6 +2561,10 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
if( (dgpo.next_maintenance_time <= HARDFORK_CORE_1270_TIME) && (next_maintenance_time > HARDFORK_CORE_1270_TIME) )
to_update_and_match_call_orders_for_hf_1270 = true;
// 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;

View file

@ -42,6 +42,26 @@ namespace graphene { namespace chain {
* No more asset updates may be issued.
*/
void database::globally_settle_asset( const asset_object& mia, const price& settlement_price )
{
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_1669 = ( maint_time <= HARDFORK_CORE_1669_TIME ); // whether to use call_price
if( before_core_hardfork_1669 )
{
globally_settle_asset_impl( mia, settlement_price,
get_index_type<call_order_index>().indices().get<by_price>() );
}
else
{
globally_settle_asset_impl( mia, settlement_price,
get_index_type<call_order_index>().indices().get<by_collateral>() );
}
}
template<typename IndexType>
void database::globally_settle_asset_impl( const asset_object& mia,
const price& settlement_price,
const IndexType& call_index )
{ try {
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" );
@ -52,34 +72,33 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
const asset_dynamic_data_object& mia_dyn = mia.dynamic_asset_data_id(*this);
auto original_mia_supply = mia_dyn.current_supply;
const auto& call_price_index = get_index_type<call_order_index>().indices().get<by_price>();
auto maint_time = get_dynamic_global_properties().next_maintenance_time;
bool before_core_hardfork_342 = ( maint_time <= HARDFORK_CORE_342_TIME ); // better rounding
// cancel all call orders and accumulate it into collateral_gathered
auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
auto call_itr = call_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
auto call_end = call_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
asset pays;
while( call_itr != call_end )
{
const call_order_object& order = *call_itr;
++call_itr;
if( before_core_hardfork_342 )
{
pays = call_itr->get_debt() * settlement_price; // round down, in favor of call order
pays = order.get_debt() * settlement_price; // round down, in favor of call order
// Be here, the call order can be paying nothing
if( pays.amount == 0 && !bitasset.is_prediction_market ) // TODO remove this warning after hard fork core-342
wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}", ("block",head_block_num()) );
wlog( "Something for nothing issue (#184, variant E) occurred at block #${block}",
("block",head_block_num()) );
}
else
pays = call_itr->get_debt().multiply_and_round_up( settlement_price ); // round up, in favor of global settlement fund
pays = order.get_debt().multiply_and_round_up( settlement_price ); // round up in favor of global-settle fund
if( pays > call_itr->get_collateral() )
pays = call_itr->get_collateral();
if( pays > order.get_collateral() )
pays = order.get_collateral();
collateral_gathered += pays;
const auto& order = *call_itr;
++call_itr;
FC_ASSERT( fill_call_order( order, pays, order.get_debt(), settlement_price, true ) ); // call order is maker
}
@ -962,6 +981,14 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
const asset_bitasset_data_object& bitasset = ( bitasset_ptr ? *bitasset_ptr : mia.bitasset_data(*this) );
// price feeds can cause black swans in prediction markets
// The hardfork check may be able to be removed after the hardfork date
// if check_for_blackswan never triggered a black swan on a prediction market.
// NOTE: check_for_blackswan returning true does not always mean a black
// swan was triggered.
if ( maint_time >= HARDFORK_CORE_460_TIME && bitasset.is_prediction_market )
return false;
if( check_for_blackswan( mia, enable_black_swan, &bitasset ) )
return false;

View file

@ -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( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -0,0 +1,4 @@
// bitshares-core issue #1479 nodes crashing on self-approving proposal
#ifndef HARDFORK_CORE_1479_TIME
#define HARDFORK_CORE_1479_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -0,0 +1,4 @@
// bitshares-core issue #1573 check transaction size
#ifndef HARDFORK_CORE_1573_TIME
#define HARDFORK_CORE_1573_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -0,0 +1,4 @@
// bitshares-core issue #1669 Stop using call_price when globally settling
#ifndef HARDFORK_CORE_1669_TIME
#define HARDFORK_CORE_1669_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -0,0 +1,4 @@
// bitshares-core issue #1692 validation check of bid_collateral
#ifndef HARDFORK_CORE_1692_TIME
#define HARDFORK_CORE_1692_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -0,0 +1,4 @@
// bitshares-core issue #460 Prediction Market price feed should not cause black swan
#ifndef HARDFORK_CORE_460_TIME
#define HARDFORK_CORE_460_TIME (fc::time_point_sec( 1615334400 )) // Wednesday, 10 March 2021 00:00:00 UTC
#endif

View file

@ -413,6 +413,14 @@ namespace graphene { namespace chain {
void cancel_bid(const collateral_bid_object& bid, bool create_virtual_op = true);
void execute_bid( const collateral_bid_object& bid, share_type debt_covered, share_type collateral_from_fund, const price_feed& current_feed );
private:
template<typename IndexType>
void globally_settle_asset_impl( const asset_object& bitasset,
const price& settle_price,
const IndexType& call_index );
public:
/**
* @brief Process a new limit order through the markets
* @param order The new order to process

View file

@ -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>

View file

@ -30,6 +30,25 @@
namespace graphene { namespace chain {
class hardfork_visitor_1479
{
public:
typedef void result_type;
uint64_t max_update_instance = 0;
uint64_t nested_update_count = 0;
template<typename T>
void operator()(const T &v) const {}
void operator()(const proposal_update_operation &v);
void operator()(const proposal_delete_operation &v);
// loop and self visit in proposals
void operator()(const graphene::chain::proposal_create_operation &v);
};
class son_hardfork_visitor
{
public:
@ -55,6 +74,7 @@ namespace graphene { namespace chain {
object_id_type do_apply( const proposal_create_operation& o );
transaction _proposed_trx;
hardfork_visitor_1479 vtor_1479;
};
class proposal_update_evaluator : public evaluator<proposal_update_evaluator>

View file

@ -116,6 +116,8 @@ namespace graphene { namespace chain {
flat_set<account_id_type>& owner,
vector<authority>& other,
bool ignore_custom_operation_required_auths )const;
virtual uint64_t get_packed_size()const;
};
/**

View file

@ -158,8 +158,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." );
@ -168,6 +170,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
@ -208,7 +218,7 @@ void_result call_order_update_evaluator::do_apply(const call_order_update_operat
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;
assert(dynamic_asset.current_supply >= 0);
});
@ -366,13 +376,6 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation
FC_ASSERT( !_bitasset_data->is_prediction_market, "Cannot bid on a prediction market!" );
if( o.additional_collateral.amount > 0 )
{
FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= o.additional_collateral,
"Cannot bid ${c} collateral when payer only has ${b}", ("c", o.additional_collateral.amount)
("b", d.get_balance(*_paying_account, o.additional_collateral.asset_id(d)).amount) );
}
const collateral_bid_index& bids = d.get_index_type<collateral_bid_index>();
const auto& index = bids.indices().get<by_account>();
const auto& bid = index.find( boost::make_tuple( o.debt_covered.asset_id, o.bidder ) );
@ -381,6 +384,22 @@ void_result bid_collateral_evaluator::do_evaluate(const bid_collateral_operation
else
FC_ASSERT( o.debt_covered.amount > 0, "Can't find bid to cancel?!");
if( o.additional_collateral.amount > 0 )
{
if( _bid && d.head_block_time() >= HARDFORK_CORE_1692_TIME ) // TODO: see if HF check can be removed after HF
{
asset delta = o.additional_collateral - _bid->get_additional_collateral();
FC_ASSERT( d.get_balance(*_paying_account, _bitasset_data->options.short_backing_asset(d)) >= delta,
"Cannot increase bid from ${oc} to ${nc} collateral when payer only has ${b}",
("oc", _bid->get_additional_collateral().amount)("nc", o.additional_collateral.amount)
("b", d.get_balance(*_paying_account, o.additional_collateral.asset_id(d)).amount) );
} else
FC_ASSERT( d.get_balance( *_paying_account,
_bitasset_data->options.short_backing_asset(d) ) >= o.additional_collateral,
"Cannot bid ${c} collateral when payer only has ${b}", ("c", o.additional_collateral.amount)
("b", d.get_balance(*_paying_account, o.additional_collateral.asset_id(d)).amount) );
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }

View file

@ -280,6 +280,27 @@ void son_hardfork_visitor::operator()( const son_report_down_operation &v )
});
}
void hardfork_visitor_1479::operator()(const proposal_update_operation &v)
{
if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )
max_update_instance = v.proposal.instance.value;
nested_update_count++;
}
void hardfork_visitor_1479::operator()(const proposal_delete_operation &v)
{
if( nested_update_count == 0 || v.proposal.instance.value > max_update_instance )
max_update_instance = v.proposal.instance.value;
nested_update_count++;
}
// loop and self visit in proposals
void hardfork_visitor_1479::operator()(const graphene::chain::proposal_create_operation &v)
{
for (const op_wrapper &op : v.proposed_ops)
op.op.visit(*this);
}
void_result proposal_create_evaluator::do_evaluate( const proposal_create_operation& o )
{ try {
const database& d = db();
@ -287,6 +308,7 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat
proposal_operation_hardfork_visitor vtor( block_time );
vtor( o );
vtor_1479( o );
const auto& global_parameters = d.get_global_properties().parameters;
@ -338,7 +360,7 @@ object_id_type proposal_create_evaluator::do_apply( const proposal_create_operat
database& d = db();
auto chain_time = d.head_block_time();
const proposal_object& proposal = d.create<proposal_object>( [&o, this, chain_time](proposal_object& proposal) {
const proposal_object& proposal = d.create<proposal_object>( [&o, this, chain_time, &d](proposal_object& proposal) {
_proposed_trx.expiration = o.expiration_time;
proposal.proposed_transaction = _proposed_trx;
proposal.proposer = o.fee_paying_account;
@ -360,6 +382,20 @@ object_id_type proposal_create_evaluator::do_apply( const proposal_create_operat
std::set_difference(required_active.begin(), required_active.end(),
proposal.required_owner_approvals.begin(), proposal.required_owner_approvals.end(),
std::inserter(proposal.required_active_approvals, proposal.required_active_approvals.begin()));
if( d.head_block_time() > HARDFORK_CORE_1479_TIME )
FC_ASSERT( vtor_1479.nested_update_count == 0 || proposal.id.instance() > vtor_1479.max_update_instance,
"Cannot update/delete a proposal with a future id!" );
else if( vtor_1479.nested_update_count > 0 && proposal.id.instance() <= vtor_1479.max_update_instance )
{
// prevent approval
transfer_operation top;
top.from = GRAPHENE_NULL_ACCOUNT;
top.to = GRAPHENE_RELAXED_COMMITTEE_ACCOUNT;
top.amount = asset( GRAPHENE_MAX_SHARE_SUPPLY );
proposal.proposed_transaction.operations.emplace_back( top );
wlog( "Issue 1479: ${p}", ("p",proposal) );
}
});
son_hardfork_visitor son_vtor(d, proposal.id);

View file

@ -59,6 +59,11 @@ void transaction::validate() const
operation_validate(op);
}
uint64_t transaction::get_packed_size() const
{
return fc::raw::pack_size(*this);
}
graphene::chain::transaction_id_type graphene::chain::transaction::id() const
{
auto h = digest();

View file

@ -610,7 +610,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 = 2;
creator.common_options.market_fee_percent = market_fee_percent;
if( issuer == GRAPHENE_WITNESS_ACCOUNT )

View file

@ -1381,4 +1381,70 @@ BOOST_FIXTURE_TEST_CASE( nonminimal_sig_test, database_fixture )
}
}
BOOST_AUTO_TEST_CASE( self_approving_proposal )
{ try {
ACTORS( (alice) );
fund( alice );
generate_blocks( HARDFORK_CORE_1479_TIME );
trx.clear();
set_expiration( db, trx );
proposal_update_operation pup;
pup.fee_paying_account = alice_id;
pup.proposal = proposal_id_type(0);
pup.active_approvals_to_add.insert( alice_id );
proposal_create_operation pop;
pop.proposed_ops.emplace_back(pup);
pop.fee_paying_account = alice_id;
pop.expiration_time = db.head_block_time() + fc::days(1);
trx.operations.push_back(pop);
const proposal_id_type pid1 = PUSH_TX( db, trx, ~0 ).operation_results[0].get<object_id_type>();
trx.clear();
BOOST_REQUIRE_EQUAL( 0, pid1.instance.value );
db.get<proposal_object>(pid1);
trx.operations.push_back(pup);
PUSH_TX( db, trx, ~0 );
// Proposal failed and still exists
db.get<proposal_object>(pid1);
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_CASE( self_deleting_proposal )
{ try {
ACTORS( (alice) );
fund( alice );
generate_blocks( HARDFORK_CORE_1479_TIME );
trx.clear();
set_expiration( db, trx );
proposal_delete_operation pdo;
pdo.fee_paying_account = alice_id;
pdo.proposal = proposal_id_type(0);
pdo.using_owner_authority = false;
proposal_create_operation pop;
pop.proposed_ops.emplace_back( pdo );
pop.fee_paying_account = alice_id;
pop.expiration_time = db.head_block_time() + fc::days(1);
trx.operations.push_back( pop );
const proposal_id_type pid1 = PUSH_TX( db, trx, ~0 ).operation_results[0].get<object_id_type>();
trx.clear();
BOOST_REQUIRE_EQUAL( 0, pid1.instance.value );
db.get<proposal_object>(pid1);
proposal_update_operation pup;
pup.fee_paying_account = alice_id;
pup.proposal = proposal_id_type(0);
pup.active_approvals_to_add.insert( alice_id );
trx.operations.push_back(pup);
PUSH_TX( db, trx, ~0 );
// Proposal failed and still exists
db.get<proposal_object>(pid1);
} FC_LOG_AND_RETHROW() }
BOOST_AUTO_TEST_SUITE_END()

View file

@ -462,4 +462,29 @@ BOOST_AUTO_TEST_CASE( broadcast_transaction_with_callback_test ) {
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE( broadcast_transaction_too_large ) {
try {
fc::ecc::private_key cid_key = fc::ecc::private_key::regenerate( fc::digest("key") );
const account_id_type cid_id = create_account( "cid", cid_key.get_public_key() ).id;
fund( cid_id(db) );
auto nb_api = std::make_shared< graphene::app::network_broadcast_api >( app );
generate_blocks( HARDFORK_CORE_1573_TIME + 10 );
set_expiration( db, trx );
transfer_operation trans;
trans.from = cid_id;
trans.to = account_id_type();
trans.amount = asset(1);
for(int i = 0; i < 250; ++i )
trx.operations.push_back( trans );
sign( trx, cid_key );
BOOST_CHECK_THROW( nb_api->broadcast_transaction( trx ), fc::exception );
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -438,6 +438,67 @@ BOOST_AUTO_TEST_CASE( prediction_market )
}
}
/***
* Prediction markets should not suffer a black swan (Issue #460)
*/
BOOST_AUTO_TEST_CASE( prediction_market_black_swan )
{
try {
ACTORS((judge)(dan)(nathan));
// progress to recent hardfork
generate_blocks( HARDFORK_CORE_1270_TIME );
set_expiration( db, trx );
const auto& pmark = create_prediction_market("PMARK", judge_id);
int64_t init_balance(1000000);
transfer(committee_account, judge_id, asset(init_balance));
transfer(committee_account, dan_id, asset(init_balance));
update_feed_producers( pmark, { judge_id });
price_feed feed;
feed.settlement_price = asset( 1, pmark.id ) / asset( 1 );
publish_feed( pmark, judge, feed );
borrow( dan, pmark.amount(1000), asset(1000) );
// feed a price that will cause a black swan
feed.settlement_price = asset( 1, pmark.id ) / asset( 1000 );
publish_feed( pmark, judge, feed );
// verify a black swan happened
GRAPHENE_REQUIRE_THROW(borrow( dan, pmark.amount(1000), asset(1000) ), fc::exception);
trx.clear();
// progress past hardfork
generate_blocks( HARDFORK_CORE_460_TIME + db.get_global_properties().parameters.maintenance_interval );
set_expiration( db, trx );
// create another prediction market to test the hardfork
const auto& pmark2 = create_prediction_market("PMARKII", judge_id);
update_feed_producers( pmark2, { judge_id });
price_feed feed2;
feed2.settlement_price = asset( 1, pmark2.id ) / asset( 1 );
publish_feed( pmark2, judge, feed2 );
borrow( dan, pmark2.amount(1000), asset(1000) );
// feed a price that would have caused a black swan
feed2.settlement_price = asset( 1, pmark2.id ) / asset( 1000 );
publish_feed( pmark2, judge, feed2 );
// verify a black swan did not happen
borrow( dan, pmark2.amount(1000), asset(1000) );
generate_block(~database::skip_transaction_dupe_check);
generate_blocks( db.get_dynamic_global_properties().next_maintenance_time );
generate_block();
} catch( const fc::exception& e) {
edump((e.to_detail_string()));
throw;
}
}
BOOST_AUTO_TEST_CASE( create_account_test )
{
@ -1464,6 +1525,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.