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:
Nathan Hourt 2015-06-26 15:11:41 -04:00
parent 8b010b1f99
commit dad1ca3bee
8 changed files with 118 additions and 125 deletions

View file

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

View file

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

View file

@ -109,7 +109,49 @@ void database::cancel_order( const limit_order_object& order, bool create_virtua
// TODO: create a virtual cancel operation
}
remove( order );
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;
}
/**
@ -306,7 +348,7 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
/**
* Starting with the least collateralized orders, fill them if their
* call price is above the max(lowest bid,call_limit).
* call price is above the max(lowest bid,call_limit).
*
* This method will return true if it filled a short or limit
*
@ -316,7 +358,7 @@ bool database::fill_order(const force_settlement_object& settle, const asset& pa
*
* @return true if a margin call was executed.
*/
bool database::check_call_orders( const asset_object& mia, bool enable_black_swan )
bool database::check_call_orders(const asset_object& mia, bool enable_black_swan)
{ try {
if( !mia.is_market_issued() ) return false;
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
@ -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 );
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,32 +402,26 @@ 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() );
globally_settle_asset(mia, call_itr->get_debt() / call_itr->get_collateral());
return true;
}
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;
@ -435,10 +440,10 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
auto old_call_itr = call_itr;
if( filled_call ) ++call_itr;
fill_order( *old_call_itr, call_pays, call_receives );
fill_order(*old_call_itr, call_pays, call_receives);
auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr;
fill_order( *old_limit_itr, order_pays, order_receives );
fill_order(*old_limit_itr, order_pays, order_receives);
} // whlie call_itr != call_end
return filled_limit;
@ -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 );

View file

@ -33,6 +33,7 @@
namespace graphene { namespace chain {
class account_object;
class database;
using namespace graphene::db;
/**

View file

@ -64,8 +64,8 @@
/**
* These ratios are fixed point numbers with a denominator of GRAPHENE_COLLATERAL_RATIO_DENOM, the
* minimum maitenance collateral is therefore 1.001x and the default
* maintenance ratio is 1.75x
* minimum maitenance collateral is therefore 1.001x and the default
* maintenance ratio is 1.75x
*/
///@{
#define GRAPHENE_COLLATERAL_RATIO_DENOM 1000
@ -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

View file

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

View file

@ -355,7 +355,7 @@ namespace graphene { namespace chain {
uint64_t account_len5_fee = 5*UINT64_C(1000000000); ///< about $100
uint64_t account_len4_fee = 5*UINT64_C(2000000000); ///< about $200
uint64_t account_len3_fee = 5*3000000000; ///< about $300
uint64_t account_len2_fee = 5*4000000000; ///< about $400
uint64_t account_len2_fee = 5*4000000000; ///< about $400
uint32_t asset_create_fee = 5ll*500000000; ///< about $35 for LTM, the cost to register the cheapest asset
uint32_t asset_update_fee = 150000; ///< the cost to modify a registered asset
uint32_t asset_issue_fee = 700000; ///< the cost to print a UIA and send it to an account
@ -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 )

View file

@ -21,7 +21,7 @@
#include <fc/uint128.hpp>
namespace graphene { namespace chain {
void_result limit_order_create_evaluator::do_evaluate( const limit_order_create_operation& op )
void_result limit_order_create_evaluator::do_evaluate(const limit_order_create_operation& op)
{ try {
database& d = db();
@ -32,9 +32,9 @@ void_result limit_order_create_evaluator::do_evaluate( const limit_order_create_
_receive_asset = &op.min_to_receive.asset_id(d);
if( _sell_asset->options.whitelist_markets.size() )
FC_ASSERT( _sell_asset->options.whitelist_markets.find( _receive_asset->id ) != _sell_asset->options.whitelist_markets.end() );
FC_ASSERT( _sell_asset->options.whitelist_markets.find(_receive_asset->id) != _sell_asset->options.whitelist_markets.end() );
if( _sell_asset->options.blacklist_markets.size() )
FC_ASSERT( _sell_asset->options.blacklist_markets.find( _receive_asset->id ) == _sell_asset->options.blacklist_markets.end() );
FC_ASSERT( _sell_asset->options.blacklist_markets.find(_receive_asset->id) == _sell_asset->options.blacklist_markets.end() );
if( _sell_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_sell_asset ) );
if( _receive_asset->enforce_white_list() ) FC_ASSERT( _seller->is_authorized_asset( *_receive_asset ) );
@ -45,10 +45,10 @@ void_result limit_order_create_evaluator::do_evaluate( const limit_order_create_
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type limit_order_create_evaluator::do_apply( const limit_order_create_operation& op )
object_id_type limit_order_create_evaluator::do_apply(const limit_order_create_operation& op)
{ try {
const auto& seller_stats = _seller->statistics(db());
db().modify( seller_stats, [&]( account_statistics_object& bal ){
db().modify(seller_stats, [&](account_statistics_object& bal) {
if( op.amount_to_sell.asset_id == asset_id_type() )
{
bal.total_core_in_orders += op.amount_to_sell.amount;
@ -57,70 +57,21 @@ object_id_type limit_order_create_evaluator::do_apply( const limit_order_create_
db().adjust_balance(op.seller, -op.amount_to_sell);
const auto& new_order_object = db().create<limit_order_object>( [&]( limit_order_object& obj ){
const auto& new_order_object = db().create<limit_order_object>([&](limit_order_object& obj){
obj.seller = _seller->id;
obj.for_sale = op.amount_to_sell.amount;
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 )
void_result limit_order_cancel_evaluator::do_evaluate(const limit_order_cancel_operation& o)
{ try {
database& d = db();
@ -130,7 +81,7 @@ void_result limit_order_cancel_evaluator::do_evaluate( const limit_order_cancel_
return void_result();
} FC_CAPTURE_AND_RETHROW( (o) ) }
asset limit_order_cancel_evaluator::do_apply( const limit_order_cancel_operation& o )
asset limit_order_cancel_evaluator::do_apply(const limit_order_cancel_operation& o)
{ try {
database& d = db();
@ -138,7 +89,7 @@ asset limit_order_cancel_evaluator::do_apply( const limit_order_cancel_operation
auto quote_asset = _order->sell_price.quote.asset_id;
auto refunded = _order->amount_for_sale();
db().cancel_order( *_order, false /* don't create a virtual op*/ );
db().cancel_order(*_order, false /* don't create a virtual op*/);
// Possible optimization: order can be called by canceling a limit order iff the canceled order was at the top of the book.
// Do I need to check calls in both assets?