Refactor: Move limit order execution to database
This logic was previously located in limit_order_create_evaluator, but other code may need it in the future, so it should be made available at the database level.
This commit is contained in:
parent
8b010b1f99
commit
dad1ca3bee
8 changed files with 118 additions and 125 deletions
|
|
@ -54,7 +54,11 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o
|
|||
const asset_object& backing_backing = backing_bitasset_data.options.short_backing_asset(d);
|
||||
FC_ASSERT( !backing_backing.is_market_issued(),
|
||||
"May not create a bitasset backed by a bitasset backed by a bitasset." );
|
||||
}
|
||||
FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing_backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
} else
|
||||
FC_ASSERT( op.issuer != GRAPHENE_COMMITTEE_ACCOUNT || backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
FC_ASSERT( op.bitasset_options->feed_lifetime_sec > chain_parameters.block_interval &&
|
||||
op.bitasset_options->force_settlement_delay_sec > chain_parameters.block_interval );
|
||||
}
|
||||
|
|
@ -192,13 +196,28 @@ void_result asset_update_evaluator::do_evaluate(const asset_update_operation& o)
|
|||
{ try {
|
||||
database& d = db();
|
||||
|
||||
if( o.new_issuer ) FC_ASSERT(d.find_object(*o.new_issuer));
|
||||
|
||||
const asset_object& a = o.asset_to_update(d);
|
||||
auto a_copy = a;
|
||||
a_copy.options = o.new_options;
|
||||
a_copy.validate();
|
||||
|
||||
if( o.new_issuer )
|
||||
{
|
||||
FC_ASSERT(d.find_object(*o.new_issuer));
|
||||
if( a.is_market_issued() && *o.new_issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
{
|
||||
const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d);
|
||||
if( backing.is_market_issued() )
|
||||
{
|
||||
const asset_object& backing_backing = backing.bitasset_data(d).options.short_backing_asset(d);
|
||||
FC_ASSERT( backing_backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
} else
|
||||
FC_ASSERT( backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
}
|
||||
}
|
||||
|
||||
//There must be no bits set in o.permissions which are unset in a.issuer_permissions.
|
||||
FC_ASSERT(!(o.new_options.issuer_permissions & ~a.options.issuer_permissions),
|
||||
"Cannot reinstate previously revoked issuer permissions on an asset.");
|
||||
|
|
@ -257,6 +276,19 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita
|
|||
{
|
||||
FC_ASSERT(a.dynamic_asset_data_id(d).current_supply == 0);
|
||||
FC_ASSERT(d.find_object(o.new_options.short_backing_asset));
|
||||
|
||||
if( a.issuer == GRAPHENE_COMMITTEE_ACCOUNT )
|
||||
{
|
||||
const asset_object& backing = a.bitasset_data(d).options.short_backing_asset(d);
|
||||
if( backing.is_market_issued() )
|
||||
{
|
||||
const asset_object& backing_backing = backing.bitasset_data(d).options.short_backing_asset(d);
|
||||
FC_ASSERT( backing_backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
} else
|
||||
FC_ASSERT( backing.get_id() == asset_id_type(),
|
||||
"May not create a blockchain-controlled market asset which is not backed by CORE.");
|
||||
}
|
||||
}
|
||||
|
||||
bitasset_to_update = &b;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/limit_order_object.hpp>
|
||||
|
||||
#include <fc/uint128.hpp>
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,48 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua
|
|||
remove(order);
|
||||
}
|
||||
|
||||
bool database::apply_order(const limit_order_object& new_order_object, bool allow_black_swan)
|
||||
{
|
||||
auto order_id = new_order_object.id;
|
||||
const asset_object& sell_asset = get(new_order_object.amount_for_sale().asset_id);
|
||||
const asset_object& receive_asset = get(new_order_object.amount_to_receive().asset_id);
|
||||
|
||||
// Possible optimization: We only need to check calls if both are true:
|
||||
// - The new order is at the front of the book
|
||||
// - The new order is below the call limit price
|
||||
bool called_some = check_call_orders(sell_asset, allow_black_swan);
|
||||
called_some |= check_call_orders(receive_asset, allow_black_swan);
|
||||
if( called_some && !find_object(order_id) ) // then we were filled by call order
|
||||
return true;
|
||||
|
||||
const auto& limit_price_idx = get_index_type<limit_order_index>().indices().get<by_price>();
|
||||
|
||||
// TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to
|
||||
// determine whether or not this order has "changed the book" in a way that requires us to
|
||||
// check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs
|
||||
// constant time check. Potential optimization.
|
||||
|
||||
auto max_price = ~new_order_object.sell_price;
|
||||
auto limit_itr = limit_price_idx.lower_bound(max_price.max());
|
||||
auto limit_end = limit_price_idx.upper_bound(max_price);
|
||||
|
||||
bool finished = false;
|
||||
while( !finished && limit_itr != limit_end )
|
||||
{
|
||||
auto old_limit_itr = limit_itr;
|
||||
++limit_itr;
|
||||
// match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop.
|
||||
finished = (match(new_order_object, *old_limit_itr, old_limit_itr->sell_price) != 2);
|
||||
}
|
||||
|
||||
//Possible optimization: only check calls if the new order completely filled some old order
|
||||
//Do I need to check both assets?
|
||||
check_call_orders(sell_asset, allow_black_swan);
|
||||
check_call_orders(receive_asset, allow_black_swan);
|
||||
|
||||
return find_object(order_id) == nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the two orders,
|
||||
*
|
||||
|
|
@ -333,42 +375,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
auto max_price = price::max( mia.id, bitasset.options.short_backing_asset );
|
||||
// stop when limit orders are selling too little USD for too much CORE
|
||||
auto min_price = bitasset.current_feed.max_short_squeeze_price();
|
||||
/*
|
||||
// edump((bitasset.current_feed));
|
||||
edump((min_price.to_real())(min_price));
|
||||
edump((max_price.to_real())(max_price));
|
||||
//auto min_price = price::min( mia.id, bitasset.options.short_backing_asset );
|
||||
idump((bitasset.current_feed.settlement_price)(bitasset.current_feed.settlement_price.to_real()));
|
||||
{
|
||||
for( const auto& order : limit_price_index )
|
||||
wdump((order)(order.sell_price.to_real()));
|
||||
|
||||
for( const auto& call : call_price_index )
|
||||
idump((call)(call.call_price.to_real()));
|
||||
|
||||
// limit pirce index is sorted from highest price to lowest price.
|
||||
//auto limit_itr = limit_price_index.lower_bound( price::max( mia.id, bitasset.options.short_backing_asset ) );
|
||||
wdump((max_price)(max_price.to_real()));
|
||||
wdump((min_price)(min_price.to_real()));
|
||||
}
|
||||
*/
|
||||
|
||||
assert( max_price.base.asset_id == min_price.base.asset_id );
|
||||
// wlog( "from ${a} Debt/Col to ${b} Debt/Col ", ("a", max_price.to_real())("b",min_price.to_real()) );
|
||||
// NOTE limit_price_index is sorted from greatest to least
|
||||
auto limit_itr = limit_price_index.lower_bound( max_price );
|
||||
auto limit_end = limit_price_index.upper_bound( min_price );
|
||||
|
||||
/*
|
||||
if( limit_itr != limit_price_index.end() )
|
||||
wdump((*limit_itr)(limit_itr->sell_price.to_real()));
|
||||
if( limit_end != limit_price_index.end() )
|
||||
wdump((*limit_end)(limit_end->sell_price.to_real()));
|
||||
*/
|
||||
|
||||
if( limit_itr == limit_end )
|
||||
{
|
||||
// wlog( "no orders available to fill margin calls" );
|
||||
if( limit_itr == limit_end ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -389,24 +402,19 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
usd_for_sale = limit_itr->amount_for_sale();
|
||||
}
|
||||
else return filled_limit;
|
||||
// wdump((match_price));
|
||||
// edump((usd_for_sale));
|
||||
|
||||
match_price.validate();
|
||||
|
||||
// wdump((match_price)(~call_itr->call_price) );
|
||||
if( match_price > ~call_itr->call_price )
|
||||
{
|
||||
return filled_limit;
|
||||
}
|
||||
|
||||
auto usd_to_buy = call_itr->get_debt();
|
||||
// edump((usd_to_buy));
|
||||
|
||||
if( usd_to_buy * match_price > call_itr->get_collateral() )
|
||||
{
|
||||
FC_ASSERT( enable_black_swan );
|
||||
//elog( "black swan, we do not have enough collateral to cover at this price" );
|
||||
globally_settle_asset(mia, call_itr->get_debt() / call_itr->get_collateral());
|
||||
return true;
|
||||
}
|
||||
|
|
@ -414,7 +422,6 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
asset call_pays, call_receives, order_pays, order_receives;
|
||||
if( usd_to_buy >= usd_for_sale )
|
||||
{ // fill order
|
||||
//ilog( "filling all of limit order" );
|
||||
call_receives = usd_for_sale;
|
||||
order_receives = usd_for_sale * match_price;
|
||||
call_pays = order_receives;
|
||||
|
|
@ -422,9 +429,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
|
||||
filled_limit = true;
|
||||
filled_call = (usd_to_buy == usd_for_sale);
|
||||
}
|
||||
else // fill call
|
||||
{
|
||||
} else { // fill call
|
||||
call_receives = usd_to_buy;
|
||||
order_receives = usd_to_buy * match_price;
|
||||
call_pays = order_receives;
|
||||
|
|
@ -454,16 +459,6 @@ void database::pay_order( const account_object& receiver, const asset& receives,
|
|||
adjust_balance(receiver.get_id(), receives);
|
||||
}
|
||||
|
||||
/**
|
||||
* For Market Issued assets Managed by Delegates, any fees collected in the MIA need
|
||||
* to be sold and converted into CORE by accepting the best offer on the table.
|
||||
*/
|
||||
bool database::convert_fees( const asset_object& mia )
|
||||
{
|
||||
if( mia.issuer != account_id_type() ) return false;
|
||||
return false;
|
||||
}
|
||||
|
||||
asset database::calculate_market_fee( const asset_object& trade_asset, const asset& trade_amount )
|
||||
{
|
||||
assert( trade_asset.id == trade_amount.asset_id );
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@
|
|||
|
||||
namespace graphene { namespace chain {
|
||||
class account_object;
|
||||
class database;
|
||||
using namespace graphene::db;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@
|
|||
#define GRAPHENE_DEFAULT_BURN_PERCENT_OF_FEE (20*GRAPHENE_1_PERCENT)
|
||||
#define GRAPHENE_WITNESS_PAY_PERCENT_PRECISION (1000000000)
|
||||
#define GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE 1
|
||||
#define GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD GRAPHENE_BLOCKCHAIN_PRECISION * 100;
|
||||
#define GRAPHENE_GENESIS_TIMESTAMP (1431700000) /// Should be divisible by GRAPHENE_DEFAULT_BLOCK_INTERVAL
|
||||
|
||||
#define GRAPHENE_MAX_WORKER_NAME_LENGTH 63
|
||||
|
|
|
|||
|
|
@ -348,6 +348,16 @@ namespace graphene { namespace chain {
|
|||
void cancel_order(const force_settlement_object& order, bool create_virtual_op = true);
|
||||
void cancel_order(const limit_order_object& order, bool create_virtual_op = true);
|
||||
|
||||
/**
|
||||
* @brief Process a new limit order through the markets
|
||||
* @param order The new order to process
|
||||
* @return true if order was completely filled; false otherwise
|
||||
*
|
||||
* This function takes a new limit order, and runs the markets attempting to match it with existing orders
|
||||
* already on the books.
|
||||
*/
|
||||
bool apply_order(const limit_order_object& new_order_object, bool allow_black_swan = true);
|
||||
|
||||
/**
|
||||
* Matches the two orders,
|
||||
*
|
||||
|
|
@ -381,7 +391,6 @@ namespace graphene { namespace chain {
|
|||
// helpers to fill_order
|
||||
void pay_order( const account_object& receiver, const asset& receives, const asset& pays );
|
||||
|
||||
bool convert_fees( const asset_object& mia );
|
||||
asset calculate_market_fee(const asset_object& recv_asset, const asset& trade_amount);
|
||||
asset pay_market_fees( const asset_object& recv_asset, const asset& receives );
|
||||
|
||||
|
|
|
|||
|
|
@ -456,6 +456,7 @@ namespace graphene { namespace chain {
|
|||
share_type witness_pay_per_block = GRAPHENE_DEFAULT_WITNESS_PAY_PER_BLOCK; ///< CORE to be allocated to witnesses (per block)
|
||||
share_type worker_budget_per_day = GRAPHENE_DEFAULT_WORKER_BUDGET_PER_DAY; ///< CORE to be allocated to workers (per day)
|
||||
uint16_t max_predicate_opcode = GRAPHENE_DEFAULT_MAX_ASSERT_OPCODE; ///< predicate_opcode must be less than this number
|
||||
share_type fee_liquidation_threshold = GRAPHENE_DEFAULT_FEE_LIQUIDATION_THRESHOLD; ///< value in CORE at which accumulated fees in blockchain-issued market assets should be liquidated
|
||||
|
||||
void validate()const
|
||||
{
|
||||
|
|
@ -614,6 +615,7 @@ FC_REFLECT( graphene::chain::chain_parameters,
|
|||
(witness_pay_per_block)
|
||||
(worker_budget_per_day)
|
||||
(max_predicate_opcode)
|
||||
(fee_liquidation_threshold)
|
||||
)
|
||||
|
||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||
|
|
|
|||
|
|
@ -63,61 +63,12 @@ object_id_type limit_order_create_evaluator::do_apply( const limit_order_create_
|
|||
obj.sell_price = op.get_price();
|
||||
obj.expiration = op.expiration;
|
||||
});
|
||||
limit_order_id_type result = new_order_object.id; // save this because we may remove the object by filling it
|
||||
limit_order_id_type order_id = new_order_object.id; // save this because we may remove the object by filling it
|
||||
bool filled = db().apply_order(new_order_object);
|
||||
|
||||
// Possible optimization: We only need to check calls if both are true:
|
||||
// - The new order is at the front of the book
|
||||
// - The new order is below the call limit price
|
||||
bool called_some = db().check_call_orders(*_sell_asset);
|
||||
called_some |= db().check_call_orders(*_receive_asset);
|
||||
if( called_some && !db().find(result) ) // then we were filled by call order
|
||||
return result;
|
||||
FC_ASSERT( !op.fill_or_kill || filled );
|
||||
|
||||
const auto& limit_order_idx = db().get_index_type<limit_order_index>();
|
||||
const auto& limit_price_idx = limit_order_idx.indices().get<by_price>();
|
||||
|
||||
// TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to
|
||||
// determine whether or not this order has "changed the book" in a way that requires us to
|
||||
// check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs
|
||||
// constant time check. Potential optimization.
|
||||
|
||||
auto max_price = ~op.get_price(); //op.min_to_receive / op.amount_to_sell;
|
||||
auto limit_itr = limit_price_idx.lower_bound( max_price.max() );
|
||||
auto limit_end = limit_price_idx.upper_bound( max_price );
|
||||
|
||||
for( auto tmp = limit_itr; tmp != limit_end; ++tmp )
|
||||
{
|
||||
assert( tmp != limit_price_idx.end() );
|
||||
}
|
||||
|
||||
bool filled = false;
|
||||
//if( new_order_object.amount_to_receive().asset_id(db()).is_market_issued() )
|
||||
if( _receive_asset->is_market_issued() )
|
||||
{ // then we may also match against shorts
|
||||
if( _receive_asset->bitasset_data(db()).options.short_backing_asset == asset_id_type() )
|
||||
{
|
||||
bool converted_some = db().convert_fees( *_receive_asset );
|
||||
// just incase the new order was completely filled from fees
|
||||
if( converted_some && !db().find(result) ) // then we were filled by call order
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
while( !filled && limit_itr != limit_end )
|
||||
{
|
||||
auto old_limit_itr = limit_itr;
|
||||
++limit_itr;
|
||||
filled = (db().match( new_order_object, *old_limit_itr, old_limit_itr->sell_price ) != 2 );
|
||||
}
|
||||
|
||||
//Possible optimization: only check calls if the new order completely filled some old order
|
||||
//Do I need to check both assets?
|
||||
db().check_call_orders(*_sell_asset);
|
||||
db().check_call_orders(*_receive_asset);
|
||||
|
||||
FC_ASSERT( !op.fill_or_kill || db().find_object(result) == nullptr );
|
||||
|
||||
return result;
|
||||
return order_id;
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o)
|
||||
|
|
|
|||
Loading…
Reference in a new issue